MCP (Model Context Protocol) - это протокол, описывающий способ подключения AI-моделей к внешним инструментам.
MCP-сервер - это приложение, разработанное согласно протоколу MCP. Можно указать для AI-модели данные для подключения к этому серверу и модель начнет использовать инструменты, предоставляемые этим сервером.
В данной статье мы напишем небольшое приложение для MCP-сервера. Целью этого приложения будет запись пользователя на прием к врачу.
В целях упрощения кода и ускорения разработки мы упустим большинство проверок в коде. В реальном проекте так лучше не делать.
Подготовка данных
Сначала мы подготовим небольшое апи для получения данных о врачах и расписание, в том числе получение расписания для конкретного врача.
Код апи здесь выкладывать не буду, но получится следующий набор эндпоинтов:
Получение списка докторов (/api/doctors)
[{"id":1,"name":"Doctor 1","speciality":"Терапевт"}]
Получение целого расписания (/api/schedule)
{"понедельник":{"1":[{"start_hour":"10:00","end_hour":"11:00"},{"start_hour":"13:00","end_hour":"14:00"},{"start_hour":"16:00","end_hour":"17:00"}],"2":[{"start_hour":"10:00","end_hour":"11:00"},{"start_hour":"11:00","end_hour":"12:00"},{"start_hour":"12:00","end_hour":"13:00"}],"3":[{"start_hour":"10:00","end_hour":"11:00"},{"start_hour":"11:00","end_hour":"12:00"},{"start_hour":"12:00","end_hour":"13:00"}]}}
Получение расписания одного доктора (/api/doctors/:id/schedule)
{"вторник":[{"start_hour":"10:00","end_hour":"11:00"},{"start_hour":"11:00","end_hour":"12:00"}],"понедельник":[{"start_hour":"10:00","end_hour":"11:00"},{"start_hour":"11:00","end_hour":"12:00"},{"start_hour":"12:00","end_hour":"13:00"}]}
Запись к врачу (/api/booking) - это POST-запрос, в который передается id врача, порядковый номер дня недели и интервал времени, в который надо записаться. В ответ эндпоинт просто возвращает строку, в которой описывается, на какое время записался человек.
MCP-сервер
Для разворачивания mcp-сервера будем использовать официальную библиотеку.
Каркас приложения будет следующий
func main() {
s := mcp.NewServer(&mcp.Implementation{Name: "go-mcp-http", Version: "0.1.0"}, nil)
// тут будут добавляться инструменты нашего mcp-вервtools-ы
handler := mcp.NewSSEHandler(func(request *http.Request) *mcp.Server {
return s
})
if err := http.ListenAndServe(":8080", handler); err != nil {
log.Fatal("Error starting server: " + err.Error())
}
}
В этом коде мы инициализируем объект mcp-сервера, при этом передав в него название и версию.
Далее мы объявляем метод-обработчик, в который будут направляться все запросы от AI.
В конце мы запускаем обычный http-сервер из go-библиотеки net/http
, передав в него в качестве обработчика наш метод, объявленный на предыдущем шаге.
Добавление инструментов состоит из двух шагов - написание функции-обработчика инструмента и добавление инструмента в mcp-сервер.
Написание функции-обработчика
Рассмотрим написание функции-обработчика на примере инструмента получения расписания для конкретного врача:
func getDoctorSchedule(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
type Args struct {
Id int32 `json:"id"`
}
var args Args
if err := json.Unmarshal(req.Params.Arguments, &args); err != nil {
return &mcp.CallToolResult{}, fmt.Errorf("невалидный JSON")
}
data, err := http.Get(fmt.Sprintf("http://localhost:9000/doctors/%d/schedule", args.Id))
if err != nil {
return &mcp.CallToolResult{}, fmt.Errorf("не смог получить список расписание")
}
defer data.Body.Close()
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: fmt.Sprint(io.ReadAll(data.Body))},
},
}, nil
}
Когда AI-модели нужно получить расписание конкретного врача, он вызовет соответствующий инструмент, который в свою очередь вызовет описанную выше функцию.
Параметры, которые нужно передать в функцию, определяет сама AI-модель на основе описания, которое мы опишем чуть позже. В данный инструмент AI-модель передаст id врача.
Внутри функции мы отправляем http-запрос в нашу АПИ для получения расписания для врача. АПИ вернет нам json, а мы возвращаем его в AI-модель.
На этом работа нашей функции закончена. Но при решении реальной задачи функция,естественно, может быть на много сложнее. По сути в ней описывается вся логика нашего инструмента. Возможно будет делаться несколько запросов, каким-то образом будут перебираться данные с ответов этих запросов, возможно будут собираться новые массивы данных и т.д.
Добавление инструмента в mcp-сервер
Теперь опишем второй шаг - добавление инструмента в mcp-сервер. Для этого нужно вызвать метод addTool
объекта mcp-сервера.
s.AddTool(&mcp.Tool{
Name: "getDoctorSchedule",
Description: "Получение расписания для конкретного врача по его id",
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"id": {
Type: "number",
},
},
},
}, getDoctorSchedule)
Добавление инструмента - довольно гибкий функционал. Там можно описать обязательные и дополнительные поля, форматы входящих и исходящих данных и т.д. Но мы заполним минимальный набор данных для корректной работы нашего приложения. Мы указываем имя инструмента, его описание и формат входящих данных.
По описанию AI-модель понимает, что делает наш инструмент. А по формату входящих данных он понимает, какие переменные нужно передать в инструмент.
В данном случае мы указали, что наш инструмент принимает один параметр id типа number.
Вторым параметром в метод AddTool передается функция-обработчик, которую мы написали на предыдущем шаге.
На этом описание нашего mcp-сервера закончено. Его можно запустить и сделать доступным его по url, а затем указать для AI-модели этот url и AI начнет использовать наш mcp-сервер.
Либо можно запустить его локально и указать, например, в настройках нашего любимого редактора кода, в котором есть встроенный AI. Например, для copilot на vscode, или в редакторе cursor.
Потестируем наш mcp-сервер
Я для тестирования выбрал вариант локального запуска и подключения к редактору cursor. Для записи к врачу это не самый удобный интерфейс, лучше конечно это делать на сайте. Но для тестирования вполне подойдет.
После подключения mcp-сервера к редактору он отобразился в настройках

Редактор сразу считал все инструменты, которые есть в моем mcp-сервере и отобразил их.
Теперь если я попрошу перечислить терапевтов, AI вызовет инструмент getDoctors и выведет доступных терапевтов

Я могу спросить, когда работает конкретный врач

А могу попросить записать меня к нему

В последнем ответе мы видим строку "вы записались к врачу Doctor 3 на Вторник в 10:00-11:00", которая сформировалась в нашей АПИ и вернулась в ответе запроса записи к врачу. Но эта строка попала туда через функцию-обработчик. То есть мы можем возвращать AI любой текст, в том числе напрямую то, что вернула АПИ.
Заключение
Мы собрали очень простой mcp-сервер,в котором почти нет логики. АПИ тоже возвращает очень мало данных. Но даже на этом примере можно понять, что mcp-сервер может быть очень полезным, если готовить его под конкретные задачи.