Заметки по разработке MCP-сервера

Заметки по разработке MCP-сервера

Введение

Когда началась работа над проектом по добавлению функциональности чат-бота с ИИ, мне поручили часть, связанную с «предоставлением LLM возможности запрашивать данные системы». Чтобы LLM могла отвечать на вопросы пользователей, ей в конечном итоге необходимо видеть данные нашей системы в режиме реального времени, и моей задачей было выяснить, как соединить эти два компонента.

Я уже был знаком с концепцией MCP (Model Context Protocol). Я встречал упоминания о нём в новостях и документации как о стандартном протоколе, который подключает к LLM «инструменты», позволяющие ей взаимодействовать с внешними системами, а также знал, что несколько ИИ-сервисов недавно начали его поддерживать. Но это означало, что я «знал о нём», а не то, что я «создавал его». Между способностью объяснить концепцию и реальной настройкой проекта Spring Boot в качестве MCP-сервера и доведением его до рабочего состояния оказался больший, чем ожидалось, разрыв.

Эта статья — запись того, что я узнал, преодолевая этот разрыв, а также того, насколько Spring AI действительно упрощает этот процесс.

1. Проблема — знать концепцию, но не знать, с чего начать

1-1. Проблема, которую мне нужно было решить в этом проекте

Архитектура чат-бота с ИИ в нашей команде была организована следующим образом.

UI → AI 서버 → LLM
        ↓ (MCP)
     MCP 서버
        ↓
MSA로 각각의 데이터를 수집하고 있는 서비스들

Когда пользователь спрашивает: «Есть ли устройства, которые в данный момент не собирают данные?», ИИ-сервер использует LLM, чтобы определить, что «для ответа на этот вопрос необходим инструмент для запроса списка серверов», и передаёт этот вызов инструмента через MCP созданному мной MCP-серверу. MCP-сервер преобразует запрос в фактический вызов REST API службы управления серверами, выполняет его, организует результат в формате, который LLM может легко понять, и возвращает его.

Именно этот MCP-сервер входил в мои обязанности. Иными словами, это была задача с нуля создать «существующей внутренней системы функциональность, которую ИИможет использовать в пригодной для использования форме заново предоставляющий сервер»с нуля.

1-2. Что требовалось разработать

Изучив спецификацию MCP, легко понять, что это протокол, посредством которого обмениваются сообщениями JSON-RPC, такими как initialize, tools/list и tools/call. Однако, как только я приступил к разработке, возникло множество конкретных вопросов.

  • Нужно ли было реализовать этот протокол непосредственно в виде контроллера Spring Boot? Если бы мне пришлось вручную писать маршрутизацию JSON-RPC, управление сессиями (Mcp-Session-Id) и транспорт Streamable HTTP, получилось бы, что хвост виляет собакой.
  • Как следует представить «Tool» в коде? Один метод — это просто один инструмент или нужно реализовать отдельный интерфейс?
  • Кто создаёт спецификацию входных и выходных данных инструмента (JSON Schema)? Клиент MCP (на стороне LLM) должен знать, какие параметры принимает инструмент, прежде чем вызвать его. Если бы мне каждый раз приходилось писать схему вручную, казалось, что добавление даже одного инструмента породит огромное количество шаблонного кода.
  • В нашем сервисе уже есть несколько внутренних сервисов, включая управление серверами и управление устройствами, и у каждого имеются собственные соглашения об использовании REST API и клиентская библиотека. При обёртывании их в MCP-инструменты как следует структурировать этот проект, чтобы другие участники команды не запутались, когда позднее будут добавлять другие сервисы?

Иными словами, проблема заключалась не в том, «что такое MCP». «Как структурироватьэкосистему в рамках этого и организовать структуру и написать это в Spring Boot»— вот в чём на самом деле заключалась проблема.

2. Решение — поддержка MCP-сервера в Spring AI

Я решил решить эту проблему с помощью Spring AI (официального проекта Spring для интеграции с ИИ) — фреймворка из экосистемы Spring. Spring AI предоставляет starter для реализации MCP-серверов и берёт на себя две основные задачи.

  • Уровень протокола обработка: Фреймворк обрабатывает маршрутизацию сообщений протокола MCP, таких как initialize, tools/list и tools/call, а также управление сессиями.
  • Аннотация на основе инструмент и т. д.запись: Разработчикам достаточно добавить к обычному методу аннотацию со словами «это инструмент», после чего фреймворк автоматически берет на себя все остальное (генерацию схемы, регистрацию и подключение вызова).

Причина такого выбора была очевидной.

  • существующий с нашим стеком естественная интеграция: Наш бэкенд полностью построен на Spring Boot + Gradle, а клиентские библиотеки для каждого сервиса также работают в экосистеме Spring. Если MCP-сервер использует тот же стек, участники команды сразу понимают его без необходимости чему-либо обучаться.
  • аннотация на основе инструмент добавление затраты низкие: Если добавление нового инструмента сводится к «написанию одного метода + добавлению нескольких аннотаций», другие участники команды смогут просто повторять этот шаблон, добавляя в будущем инструменты для других предметных областей.
  • протокол детали реализации свобода: Такие элементы, как формат сообщений JSON-RPC, управление сессиями и формат ответов с ошибками, должны быть реализованы точно в соответствии со спецификацией, чтобы обеспечить корректное взаимодействие с клиентом (MCP-клиентом на стороне LLM). Решающее значение имело то, что фреймворк взял на себя эти низкоуровневые детали.

В результате одна зависимость в build.gradle и несколько строк конфигурации в application.yml составили весь каркас MCP-сервера этого проекта.

spring:
  ai:
    mcp:
      server:
        name: my-mcp-server
        protocol: STREAMABLE
        type: SYNC
        annotation-scanner:
          enabled: true

3. Примеры реализации — что Spring AI действительно берет на себя

Далее я систематизировал моменты, когда во время написания кода действительно подумал: «А, вот зачем нужен фреймворк».

3-1. Непосредственная проверка с помощью MCP Inspector

Даже если протокол теоретически обрабатывается автоматически, для уверенности нужно увидеть это собственными глазами. Для этого можно использовать MCP Inspector. Это веб-инструмент для тестирования и отладки MCP-серверов, официально распространяемый Anthropic. Его можно сразу запустить следующей единственной строкой без отдельной установки.

npx @modelcontextprotocol/inspector

После запуска с приведенным выше кодом можно тестировать и проверять разрабатываемый инструмент через веб-интерфейс. На экране «Серверы» нажмите «Добавить серверы» и выберите тип транспорта «Потоковый HTTP», затем введите адрес конечной точки MCP разрабатываемого сервера (например, http://localhost:8088/mcp), чтобы подключиться. После подключения список инструментов, зарегистрированных мной с помощью аннотаций, отображается без изменений на вкладке «Инструменты», включая их имена, описания и схемы входных данных; можно выбрать инструмент, заполнить его параметры и напрямую вызвать его.

Нажимая элементы непосредственно на этом экране, я смог убедиться, что именно такой процесс происходит на уровне протокола.

  1. При отправке запроса initialize сервер создает сессию и возвращает Mcp-Session-Id в заголовке ответа.
  2. Последующие запросы можно отправлять, передавая этот идентификатор сессии в заголовке, а
  3. при вызове tools/list список зарегистрированных мной с помощью аннотаций инструментов автоматически возвращается вместе с их именами, описаниями и схемами входных данных.
  4. При фактическом вызове инструмента через tools/call выполняется написанный мной метод Java, а его возвращаемое значение автоматически оборачивается в формат ответа MCP (content, isError и т. д.).

В написанном мной коде нет ни одной строки логики обработки этого протокола. Если бы я реализовывал эту часть самостоятельно, то, думаю, первые несколько недель проекта ушли бы исключительно на «повторную реализацию спецификации MCP».

Во время тестирования меня также озадачил один момент. При вызове инструмента, принимающего параметр типа массива (List), я просто ввел одно значение в поле ввода Inspector, из-за чего возникла ошибка проверки схемы: «обнаружен integer, ожидался array». Схема, возвращаемая самим сервером, действительно была массивом, но я не знал, что в поле ввода нужно указывать JSON-массив, например ["value1", "value2"]. Это трудно понять только по документации; я обнаружил эту деталь лишь самостоятельно, вызывая инструменты один за другим.

3-2. @McpTool / @McpToolParam — структура, в которой один метод становится одним инструментом

Если упростить один из созданных нами инструментов, он выглядит так.

@McpTool(
        name = "findServers",
        description = "서버 목록을 조회합니다. "
                + "이름 부분 문자열로 필터링할 수 있습니다.",
        annotations = @McpTool.McpAnnotations(
                title = "서버 목록 조회",
                readOnlyHint = true,
                destructiveHint = false,
                idempotentHint = true,
                openWorldHint = false
        )
)
public List<GatewaySummary> findGateways(
        @McpToolParam(description = "서버 이름 필터용 문자열", required = false)
        String nameFilter
) {
    // 내부적으로는 서버 관리 서비스의 기존 API 클라이언트를 그대로 호출
}

При выполнении этого кода фреймворк автоматически обрабатывает следующее.

  • Автоматически генерирует JSON Schema ({"type": "string"}) путем анализа сигнатуры метода (String nameFilter)
  • Описание служит основой для LLM при определении того, «когда использовать этот инструмент». Иными словами, то, насколько конкретно сформулировано это описание, напрямую влияет на то, сможет ли LLM эффективно выбрать и использовать инструмент.
  • readOnlyHint, destructiveHint, idempotentHint и openWorldHint в McpAnnotations — это метаданные, определенные в спецификации MCP и описывающие, «какое влияние этот инструмент оказывает на систему». Поскольку все наши инструменты доступны только для чтения, мы стандартизировали их значения как readOnlyHint=true и destructiveHint=false. Эти подсказки используются клиентом для определения того, «можно ли безопасно вызывать этот инструмент повторно».
  • Тип возвращаемого значения также сериализуется в JSON и включается в ответ без какого-либо отдельного кода преобразования.

Иными словами, фактически написанный мной код был «существующий API клиент для вызова результатов и организации типичного Java метода»—и это всё. Специфичные для MCP части состояли всего из нескольких аннотаций; всё остальное соответствовало подходу, с которым разработчики Spring уже хорошо знакомы.

3-3. Объединение нескольких бэкендов в единую согласованную структуру

В компании уже существует несколько бэкенд-сервисов, отвечающих за управление серверами, управление оборудованием, сбор данных и оповещения/мониторинг, причем у каждого есть собственная клиентская библиотека. В этом проекте мы повторно использовали их без изменений и организовали инструменты по папкам, соответствующим предметным областям.

tools/
├── server/    ServerTools
├── device/     DeviceModelTools
├── collect/    DataCollectionTools
└── alert/      AlarmTools, ConditionTools

Причина создания такой структуры была практической. Когда позже другой участник команды добавит инструмент для новой предметной области, я хотел, чтобы сразу было понятно: «нужно просто создать в этой папке еще один класс по аналогичному шаблону». Spring AI автоматически сканирует бины с аннотациями и регистрирует их как инструменты, поэтому от того, насколько аккуратно была спроектирована эта структура, напрямую зависело, «насколько легко следующий разработчик сможет ее расширить».

4. Результаты и дальнейшие задачи

Результаты

  • Мы создали несколько инструментов MCP в четырех предметных областях: управление серверами, управление оборудованием, сбор данных и оповещения/мониторинг.
  • Мы проверили корректность работы каждого инструмента посредством фактических рукопожатий протокола (initialize → tools/list → tools/call) с использованием MCP Inspector и curl.
  • Все основные интеграции предметных областей, предусмотренные первоначальным планом, завершены, за исключением задач, связанных с кластерной инфраструктурой.

Области для улучшения перед развертыванием в production

  • Аутентификация: В настоящее время для конечной точки MCP не предусмотрена отдельная аутентификация. Изначально мы разрабатывали ее в предположении, что вызывать ее из корпоративной сети будет только AI-сервер, однако перед фактическим развертыванием в production это необходимо добавить.
  • Расширение: Мы планируем поделиться созданной структурой (структурой папок по предметным областям и подходом к повторному использованию клиентских библиотек) с другими участниками команды и продолжить ее расширение для охвата оставшихся предметных областей.

5. Когда Spring AI — хороший выбор (преимущества и недостатки)

Для тех, кто рассматривает возможность создания MCP-сервера с помощью Spring AI, я обобщу преимущества и недостатки, с которыми столкнулся на собственном опыте.

Преимущества

  • Практически не существует шаблонного кода.Фреймворк берет на себя всё: маршрутизацию JSON-RPC, управление сессиями, транспорт Streamable HTTP и генерацию JSON Schema. Разработчикам нужно сосредоточиться только на методах, которые станут инструментами, и их аннотациях.
  • Существующие Spring Boot ресурсы без изменений можно повторно использовать .Знакомые подходы, такие как DI, управление конфигурацией, существующие клиентские библиотеки и ведение журналов, сохраняются без изменений. Нет необходимости с нуля изучать новый фреймворк.
  • Расширение простое.Добавление инструмента сводится к «одному методу + одной аннотации», поэтому такая структура также хорошо подходит для распределения работы между несколькими людьми: каждый может отвечать за отдельную предметную область и вести разработку параллельно.

Недостатки

  • Версия обновляется быстро.Это также означает, что API может измениться, поэтому при непосредственном использовании последней версии в рабочей среде необходимо соблюдать осторожность даже при одном небольшом обновлении.
  • Когда фреймворк скрывает детали, низкоуровневые детали которые вы хотите контролировать напрямую когда вы можете счесть это затруднительным — сделать это.Если у вас есть особые требования, отклоняющиеся от стандартного процесса (например, собственный транспорт или расширение протокола), устранить абстракции фреймворка может оказаться ещё сложнее.

В заключение, «Spring Boot на основе сервис быстро MCP сервер настроить хочу»если это ваша цель, то, на мой взгляд, этого достаточно: порог входа низкий, а благодаря интуитивно понятному дизайну решение очень удобно.

6. Заключение

Работая над этим проектом, я понял, что по-настоящему сложная часть разработки MCP-сервера — не сам протокол. Spring AI взял на себя большую часть работы с протоколом. На самом деле много времени заняли проектные решения, которые фреймворк не может принять за нас, например: «Как следует назвать и описать инструмент, чтобы LLM мог эффективно выбрать и использовать его?» и «По каким критериям следует разделить и представить несколько внутренних систем в виде инструментов, чтобы не запутать других людей?»

Знать концепцию MCP и фактически воплотить её в архитектуре сервиса — это явно разные вещи. Теперь всякий раз, когда в нашу систему добавляется новая функция, я естественным образом начинаю думать: «Как представить её в виде инструмента, чтобы ИИ мог её использовать?» Я считаю, что такой уровень абстракции, позволяющий ИИ безопасно и единообразно подключаться к внутренним системам, будет становиться всё важнее по мере дальнейшего расширения возможностей ИИ.

sauce0127

Site footer