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-сервер может быть очень полезным, если готовить его под конкретные задачи.

Share with friends:

Facebook Twitter Vkontakte