Ваш Spring-сервис общается с ИИ? Нет — вот как это исправить за вечер
Сценарий типичного Java-разработчика в 2026 году: вы потратили месяцы на идеальный микросервис с Spring Boot. Там есть REST-контроллеры, JPA-репозитории, кэш, очереди. Сервис умеет всё — от расчёта кредитного рейтинга до генерации PDF-отчётов. Но приходит AI-агент (Claude, GPT-5, Llama 4) и говорит: «Дай мне доступ к твоим данным и функциям». И тут вы понимаете: REST API — это как записка на папирусе. Агенту нужно что-то более родное — MCP.
Суть: MCP (Model Context Protocol) — это стандарт, который позволяет LLM напрямую вызывать функции вашего сервиса, как будто это методы в коде агента. Никаких parse JSON, curl, токенов. Просто tool call.
Я покажу, как за один вечер обернуть ваш Spring-сервис в MCP-сервер. Без магии, с реальным кодом и разбором трёх граблей, на которые я сам наступил.
Проблема: REST умер для AI-агентов (ну почти)
Любой LLM умеет вызывать функции через tool calling. Но чтобы подключить ваш Spring-сервис, приходится:
- Писать отдельный слой для генерации JSON Schema каждой ручки.
- Кормить агента длиннющим промптом с описанием API.
- Терпеть ошибки из-за несовпадения форматов.
MCP решает это одним махом: ваш сервис сам говорит агенту «вот мои инструменты» через стандартный протокол. Один раз написали — подключайте любой LLM, от Claude Code до самописного агента на Python.
Решение: Spring Boot + MCP Java SDK = 40 минут
Берём Spring Boot 3.5.0 (на дворе 2026, ребята, 3.x — стабильный мейнстрим), добавляем MCP Java SDK 0.8.0, пишем 50 строк конфигурации — и ваш @RestController становится MCP-сервером.
⚠️ Предупреждение: Не пытайтесь просто навесить аннотацию @MCPTool над существующими контроллерами — вы сломаете всю логику. Нужна правильная прослойка.
1 Подготовка: проект и зависимости
Создаём обычный Spring Boot проект. Gradle или Maven — неважно. Добавляем в build.gradle:
implementation 'org.springframework.boot:spring-boot-starter-web:3.5.0'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.modelcontextprotocol:mcp-java-sdk:0.8.0'
implementation 'com.fasterxml.jackson.core:jackson-databind'Обратите внимание: mcp-java-sdk — это официальная библиотека от Anthropic, которая даёт готовые классы для MCP транспорта (STDIO и SSE). Нам понадобится SSE-транспорт, чтобы сервис работал как самостоятельный HTTP-сервер.
2 Скелет MCP-сервера: пишем конфигурацию
Создаём класс McpServerConfig:
@Configuration
public class McpServerConfig {
@Bean
public McpServer mcpServer(McpTransport transport, List<ToolProvider> providers) {
return new McpServer.Builder()
.transport(transport)
.serverInfo("spring-mcp-server", "1.0.0")
.toolProviders(providers)
.build();
}
@Bean
@Profile("sse")
public McpTransport sseTransport() {
return new SseServerTransport("/mcp/v1/messages");
}
@Bean
@Profile("stdio")
public McpTransport stdioTransport() {
return new StdioServerTransport();
}
}Здесь два профиля — для прода и дебага. Мой совет: сразу настраивайте оба.
3 Tool Provider: как сделать из любой Spring-бины инструмент MCP
Самый важный класс. Реализуем ToolProvider и оборачиваем через reflection вызовы ваших сервисов. Но проще — использовать готовый SpringToolProvider из SDK (если есть). На момент 2026 года в SDK есть базовый класс, который ищет аннотацию @McpTool. Давайте сделаем свой, чтобы контролировать процесс:
@Component
public class MyToolProvider implements ToolProvider {
private final YourBusinessService service;
public MyToolProvider(YourBusinessService service) {
this.service = service;
}
@Override
public List<Tool> getTools() {
return List.of(
new Tool(
"calculate_credit_score",
"Рассчитывает кредитный рейтинг по данным клиента",
new JsonObject(), // схема входных параметров (JSON Schema)
args -> {
String clientId = args.getString("clientId");
CreditScore score = service.calculate(clientId);
return new ContentResult(Map.of(
"score", score.getValue(),
"level", score.getLevel().name()
));
}
),
new Tool(
"generate_pdf",
"Генерирует PDF-отчёт для клиента",
new JsonObject(),
args -> {
byte[] pdf = service.generateReport(args.getString("template"));
return new ContentResult(Map.of("file", Base64.getEncoder().encodeToString(pdf)));
}
)
);
}
}Внимание на JSON Schema. В реальном коде нужно её правильно построить — иначе агент не поймёт, какие параметры передавать. SDK включает утилиты JsonSchemaBuilder:
JsonObject inputSchema = new JsonSchemaBuilder()
.addProperty("clientId", SchemaType.STRING, "ID клиента")
.addRequired("clientId")
.build();4 Запускаем и проверяем с Claude Desktop
После старта вашего Spring Boot приложения (например, --spring.profiles.active=sse) MCP-сервер слушает на порту 8080 (по умолчанию). Откройте Claude Desktop, добавьте новый MCP-сервер:
{
"mcpServers": {
"my-spring-service": {
"url": "http://localhost:8080/mcp/v1/messages",
"transport": "sse"
}
}
}Теперь в диалоге с Claude можно сказать: «Рассчитай кредитный рейтинг клиента 12345». И агент сам вызовет ваш инструмент. Магия, да? Только это не магия — это MCP.
Типичные грабли (и как на них не наступить)
Грабля 1: Неявные транзакции и потоки
Вызов инструмента может выполняться в другом потоке (если агент шлёт параллельные запросы). Spring-бины — норм, но если ваш сервис использует @Transactional, убедитесь, что транзакция не заканчивается раньше времени. Лучше сделать каждый tool метод synchronized или использовать @Transactional(propagation = Propagation.REQUIRES_NEW).
Грабля 2: Сериализация циклических связей
Jackon по умолчанию падает на циклических ссылках в JPA-сущностях. Ваш tool может вернуть объект с lazy-loaded коллекцией — и JSON Schema сломается. Решение: никогда не возвращайте JPA-сущности напрямую. Используйте DTO.
// Плохо
public Tool callCalculate() {
User user = userRepository.findById(id).get(); // LazyInitializationException
}
// Хорошо
public Tool callCalculate() {
UserDto dto = userService.getUserSafe(id); // явный fetch
return new ContentResult(dto);
}Грабля 3: Долгие операции и таймауты
MCP не заточен под долгие стриминговые ответы. Если ваш tool генерирует отчёт 30 секунд, агент отвалится по таймауту. Выход — сделать асинхронный tool, который возвращает статус «задача поставлена», а потом агент дёргает другой tool для проверки результата. Это костыль, но пока MCP 0.8 не поддерживает настоящий streaming.
Лайфхак: Для длинных операций используйте подход с taskId, как в финансовых MCP-серверах.
Как протестировать MCP-сервер без агента
Не хочется каждый раз запускать Claude Desktop. Используйте утилиту mcp-cli (она идёт с SDK). Просто:
mcp-cli connect stdio java -jar my-app.jarИли для SSE: mcp-cli connect sse http://localhost:8080/mcp/v1/messages.
Утилита покажет список инструментов и позволит вызвать их прямо из командной строки.
Что дальше: MCP и Spring AI
На 2026 год Spring AI уже поддерживает MCP на уровне McpAdapter. Вы можете подключить свой MCP-сервер к ChatClient без костылей. Подробнее — в гайде по архитектуре сайта под ИИ.
Но главный урок: MCP — это не временный хайп, а стандарт, который делает Java-бекенды родными для AI-агентов. Потратьте вечер — и ваши сервисы перестанут быть «немыми».
FAQ: быстрые ответы на вопросы, которые я слышу на митапах
| Вопрос | Ответ |
|---|---|
| А можно без SDK? | Да, напишите свой JSON-RPC хендлер. Но SDK избавляет от головной боли с транспортом. |
| А что с безопасностью? | MCP сам не аутентифицирует. Вешайте Spring Security на эндпоинты транспорта и проверяйте API-ключ в заголовке. |
| А если мой сервис — реактивный WebFlux? | Работает, но используйте отдельный transport: ReactiveSseServerTransport (есть в SDK). |
Самый неочевидный совет, который я усвоил за полгода MCP-разработки: не делайте один огромный tool «сделай всё». Агенты лучше понимают маленькие атомарные функции. Лучше 10 простых tool'ов, чем один с 20 параметрами. Иначе LLM будет путаться — проверено.