MCP-сервер на Java: минимальный пример с одним инструментом
Пишем минимальный MCP-сервер на Java с одним инструментом, регистрируем его в Claude Code и учимся находить причину, если агент его не видит.
6 мин · · разработка · агенты
MCP-сервер на Java нужен, когда у вас есть JVM-код, который агент должен уметь вызывать: проверку окружения, запрос к внутреннему сервису, чтение данных из базы. Сервер при этом остаётся обычной программой, а агент (например, Claude Code) запускает её и обращается к ней по протоколу MCP. Ниже напишем минимальный сервер с одним инструментом, зарегистрируем его в агенте, который работает через шлюз Zukko, и разберём, где искать проблему, если агент инструмент не видит.
Как это устроено
В документации MCP описаны три роли: хост (приложение с моделью, например Claude Code), клиент (компонент внутри хоста, который держит соединение) и сервер (ваша программа, предоставляющая контекст). Сервер может отдавать три вида возможностей: инструменты (функции, которые вызывает модель), ресурсы (данные) и промпты (шаблоны). В минимальном примере нужен только первый вид.
Транспортов два: stdio, когда хост запускает вашу программу как дочерний процесс и обменивается с ней сообщениями через stdin и stdout, и Streamable HTTP для удалённых серверов. Для локального сервера на Java проще stdio: никакого порта и авторизации. Протокол построен на JSON-RPC 2.0; клиент запрашивает список инструментов (tools/list) и вызывает нужный (tools/call).
Важное следствие для stdio: стандартный вывод занят протоколом. Любой System.out.println в вашем коде испортит поток. Для отладочных сообщений используйте System.err или логгер, настроенный на stderr.
Зависимости
Нужна Java 17 или новее. В документации MCP Java SDK основной артефакт называется io.modelcontextprotocol.sdk:mcp: он включает ядро, JSON-сериализацию и транспорты stdio, SSE и Streamable HTTP без внешних веб-фреймворков.
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>УКАЖИТЕ_АКТУАЛЬНУЮ</version>
</dependency>Версию намеренно не фиксируем: SDK быстро развивается, и в нём есть несколько линий релизов. Возьмите свежую с Maven Central (для нескольких артефактов удобнее подключить mcp-bom). Сигнатуры ниже сверены с исходниками веток 1.x и 2.x, где конструктор stdio-транспорта принимает McpJsonMapper. В старых релизах (0.x) он принимал ObjectMapper, и примеры из интернета могут отличаться. В ветке 2.x часть использованных методов (Tool.builder() без аргументов и конструктор TextContent(String)) помечена устаревшей: код собирается с предупреждениями, а в новом коде можно перейти на Tool.builder(name, jsonMapper, schema) и TextContent.builder(text).build(). Если IDE подсвечивает ошибку, откройте раздел сервера в документации MCP Java SDK для вашей версии.
MCP сервер на Java: код с одним инструментом
Напишем инструмент check_port: он проверяет, слушает ли что-нибудь указанный TCP-порт на локальной машине. Это полезно агенту, когда он поднимает сервис и хочет убедиться, что тот стартовал. Хост намеренно фиксирован как localhost, чтобы инструмент нельзя было использовать для сканирования чужих адресов.
package demo;
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;Что здесь важно:
- Схема входа задана обычным JSON Schema. Именно по
descriptionи типам модель решает, как заполнить аргументы, поэтому пишите их внятно. - В обработчике вы получаете
request.arguments()какMap; числа приходят какNumber, поэтому приведение черезNumber, а не прямое(int). - Ответ собирается как список текстовых блоков.
- Сообщение о старте идёт в
System.err, не вSystem.out. - Блокировка в конце держит JVM живой. Если процесс завершается сразу после запуска, проверьте эту часть: поведение зависит от версии SDK.
API SDK меняется между релизами, поэтому первым делом соберите пример и при необходимости поправьте сигнатуры под вашу версию.
Сборка и запуск
Соберите исполняемый jar с зависимостями (например, плагином maven-shade-plugin или сборкой Spring Boot, на ваш вкус) и проверьте запуск вручную:
java -cp target/port-check-server.jar demo.PortCheckServerТишина в консоли это нормально: stdio-сервер ждёт, пока хост заговорит первым. Если вместо ожидания вы видите исключение, исправляйте его до подключения к агенту.
Для проверки без агента подойдёт MCP Inspector, инструмент разработчиков протокола. Он запускается через npx и принимает команду запуска сервера; точный синтаксис смотрите в его README. В Inspector видно список инструментов и можно вызвать check_port с тестовым значением.
Подключаем сервер к агенту
В документации Claude Code локальный stdio-сервер добавляется командой claude mcp add; всё, что после --, считается командой запуска сервера:
claude mcp add port-check -- java -jar /абсолютный/путь/port-check-server.jarЕсли собирали jar без манифеста с главным классом, используйте вариант с -cp ... demo.PortCheckServer. Путь делайте абсолютным: хост запускает сервер из своего рабочего каталога, и относительный путь — самая частая причина «сервер не появляется». Проверить статус можно командой claude mcp get port-check, а внутри сессии командой /mcp.
Чтобы сам Claude Code работал через шлюз, задайте переменные окружения (подробно в инструкции по Claude Code):
export ANTHROPIC_BASE_URL=https://api.zukko.pro
export ANTHROPIC_AUTH_TOKEN=fetch_ваш_ключ
export ANTHROPIC_MODEL=claude-sonnet-5-5Адрес без /v1: Claude Code добавляет его сам. Затем в сессии попросите: «Проверь, занят ли порт 8080». Агент должен вызвать check_port и ответить по результату. Общее описание протокола есть в статьях что такое MCP-сервер и MCP-серверы в Claude Code.
Что это добавляет к расходу
Описание каждого подключённого инструмента отправляется модели вместе с запросом. Допустим (это условное допущение, а не измерение), что описание вашего инструмента занимает 150 токенов, а за сессию агент делает 40 запросов. Тогда лишний вход составит 150 × 40 = 6 000 токенов. Для claude-sonnet-5-5 (вход 0.4 за 1M токенов на 18 октября 2026): 6 000 × 0.4 / 1 000 000 = $0.0024. В Anthropic-формате, которым пользуется Claude Code, повторяющийся префикс может читаться из кэша по цене чтения 0.05 за 1M. Сумма невелика, но если подключить десяток серверов с десятками инструментов, описания начнут занимать заметную часть контекста.
Если агент не видит инструмент
- Запустите команду сервера вручную: должно быть тихо, без трассировок.
- Проверьте абсолютные пути к
javaи jar. - Убедитесь, что в stdout ничего не пишется, включая логи зависимостей.
- Проверьте версию Java: нужна 17 или новее.
- После изменения конфигурации перезапустите сессию агента.
- Откройте сервер в MCP Inspector и посмотрите, отдаёт ли он список инструментов.
Частые вопросы
Нужен ли Spring, чтобы написать MCP-сервер на Java?
Нет, пример выше обходится без него. Spring-интеграции есть в SDK как необязательные модули и в проекте Spring AI. Если вы уже на Spring, эти интеграции удобнее; о подключении Spring AI к шлюзу есть заметка про Spring AI.
Как сделать сервер удалённым?
Вместо stdio используют транспорт Streamable HTTP: в SDK есть реализация на сервлетах и интеграции со Spring. Тогда вместо команды запуска агенту выдают URL. Продумайте авторизацию заранее.
Какой моделью пользоваться в агенте?
Выбирайте по цене, контексту и индексу силы и проверяйте на своих задачах. Список моделей и цены даёт GET https://api.zukko.pro/v1/models.
Работает ли это из России?
Доступ из России идёт через шлюз, оплата СБП, картой MasterCard или криптой. MCP-сервер работает локально и от этого не зависит.
Создайте ключ в кабинете, подключите Claude Code по инструкции выше и проверьте, что агент вызывает ваш check_port.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Claude Code YOLO: режимы разрешений и работа без подтвержденийОтдельного режима YOLO в Claude Code нет, зато есть шесть режимов разрешений и гибкие правила. Разбираем, как перестать нажимать «Да» и не лишиться файлов.
- Claude Code: чек-лист лучших практик для ежедневной работыПривычки, которые делают работу с Claude Code предсказуемой: короткий CLAUDE.md, план перед правками, мелкие задачи, проверка результата и контроль расхода.
- LangChain agents: инструменты, цикл и ограничение расходовСобираем агента на create_agent с двумя инструментами и запускаем на модели через OpenAI-совместимый шлюз. Показываем, как ограничить число шагов и считать токены.