MCP (Model Context Protocol)
Интеграция больших языковых моделей (LLM) с внешними источниками данных и инструментами — это не просто “желудь”, а фундаментальная инженерная задача. Прямые API вызовы к базам данных или системам часто хрупки, негибки и плохо масштабируются. Появление Model Context Protocol (MCP) от Anthropic предлагает структурированный подход к этой проблеме, превращая статичный контекст LLM в динамическую систему, связанную с внешними серверами. Рассмотрим его архитектуру, практическую реализацию и подводные камни.
Технический вызов: Динамический контекст LLM
LLM по своей природе оперируют статичным контекстом, заданным в момент запроса. Для доступа к актуальным данным или использованию инструментов (запрос БД, API вызов, работа с файлами) традиционно требуются сложные middleware-решения, специфичные для каждой интеграции. MCP предлагает унифицированный протокол, позволяющий LLM “общаться” с независимыми серверами, предоставляющими ресурсы (данные) и инструменты (действия). Это разделяет модель логики интеграции и саму LLM, но требует строгого соблюдения протокола и управления состоянием.
Глубокий разбор: Архитектура MCP
MCP построен на концепции сессии между клиентом (LLM-приложением) и сервером (провайдером инструментов/данных). Сессия инициируется клиентом и поддерживает состояние запросов/ответов. Протокол асинхронный, основан на JSON-RPC 2.0 поверх HTTP(S) или WebSocket.
Ключевые компоненты:
-
Клиент (LLM Runtime/Application):
- Иницирует сессию (
initialize). - Запрашивает доступные инструменты и ресурсы (
tools.list,resources.list). - Вызывает инструменты (
tools/call) и запрашивает ресурсы (resources/read). - Обрабатывает результаты и управляет состоянием сессии.
- Иницирует сессию (
-
Сервер (Provider/Tool/Resource Host):
- Реализует логику конкретных инструментов и ресурсов.
- Обрабатывает запросы от клиента в рамках сессии.
- Выполняет действия (инструменты) и предоставляет данные (ресурсы).
- Использует протокол для структурированного ответа.
-
Потоки сообщений:
Server -> Client: Пуш-уведомления (e.g.,notifications/pushдля асинхронных событий).Client -> Server: Запросы и ответы (e.g.,tools/call->tools/call/result).
Пример потока вызова инструмента:
- Клиент отправляет
tools/callс именем инструмента и параметрами. - Сервер получает запрос, выполняет логику.
- Сервер отправляет
tools/call/resultс результатом (или ошибкой). - Клиент использует результат для формирования следующего шага.
Реализация: Сервер MCP на Python
Используем библиотеку mcp (официальный пакет Anthropic). Создадим сервер, предоставляющий инструмент echo (эхо) и ресурс timestamp.
# mcp_server_example.py
import asyncio
import json
from datetime import datetime
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import (
CallToolRequest,
CallToolResult,
ListToolsRequest,
Tool,
ListResourcesRequest,
Resource,
ReadResourceRequest,
ReadResourceResult,
InitializeRequest,
InitializeResult,
ServerCapabilities,
TextContent
)
# Создаем экземпляр сервера
server = Server("example-server")
@server.call_tool()
async def handle_echo(name: str, arguments: dict) -> str:
"""Эхо-инструмент: возвращает переданный текст."""
if "text" not in arguments:
raise ValueError("Аргумент 'text' обязателен")
return f"Echo: {arguments['text']}"
@server.list_resources()
async def list_resources() -> list[Resource]:
"""Возвращает список доступных ресурсов."""
return [Resource(
uri="timestamp://now",
name="Current Timestamp",
description="Текущая временная метка ISO 8601",
mimeType="text/plain"
)]
@server.read_resource()
async def read_resource(uri: str) -> str:
"""Читает содержимое ресурса по URI."""
if uri == "timestamp://now":
return datetime.now().isoformat()
raise ValueError(f"Неизвестный ресурс: {uri}")
async def main():
# Настройка stdio транспорта
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializeRequest(
protocolVersion="2024-11-05",
capabilities=ServerCapabilities(
tools=True,
resources=True,
),
),
)
if __name__ == "__main__":
asyncio.run(main())
Пояснения к коду:
- Декораторы
@server.call_tool()/@server.list_resources()/@server.read_resource(): Регистрируют обработчики для соответствующих типов запросов. Протокол требует реализовать базовые операцииlistиreadдля ресурсов. - Асинхронность: Все обработчики
async, так как выполнение операций ввода-вывода (БД, API) может быть блокирующим.asyncioнеобходим для эффективного использования потоков. - Типизация аргументов: Python 3.9+ позволяет явно указывать типы аргументов (
name: str,arguments: dict). Это улучшает читаемость и помогает статическим анализаторам. - Обработка ошибок:
raise ValueError— стандартный способ возврата ошибки по протоколу. Сервер MCP автоматически преобразует исключение в JSON-RPC ошибку с кодом и сообщением. - URI для ресурсов: Использование уникальных URI (
timestamp://now) — ключевой идентификатор ресурса. Сервер должен их уметь разрешать. stdio_server: Простой транспорт для локальной разработки. Для распределенных систем используйте WebSocket или HTTP серверы.
Узкие места в продакшене
- State Management: Сессия MCP — это не HTTP сессия с куки. Клиент несет ответственность за передачу
sessionIdв каждом запросе. ПотеряsessionId(например, при перезапуске клиента) обнуляет состояние. Реализация надежного механизма сохранения и восстановления сессии — нетривиальная задача. - Latency: Каждый вызов инструмента/ресурса — отдельный сетевой запрос. Цепочка из 5 вызовов добавит минимум 5 * RTT (Round-Trip Time). Для интерактивных сценариев это критично. Оптимизация: батчинг запросов (если протокол поддерживает, нативно не гарантирован), локальное кеширование результатов (TTL).
- Error Handling и Retries: Протокол четко определяет ошибки. Однако, логику повторов (retries) клиент должен реализовывать сам. Сервер может временно недоступен, инструмент может выбросить исключение. Нужен баланс между надежностью и неограниченными повторами.
- Security: Передача данных по протоколу требует шифрования (HTTPS/WSS). Аутентификация сервера клиентом и наоборот не входит в ядро протокола. Это остается на усмотрение реализации. Риск: компрометация API ключей, доступ к инструментам без должных проверок.
- Tool/Resource Discovery: Клиент должен явно запрашивать список доступных инструментов и ресурсов (
tools.list,resources.list). Если сервер добавляет новые динамически, клиент не узнает об этом без перезапроса. Серверные уведомления (notifications/push) частично решают проблему, но их использование не обязательно. - Schema Validation: Протокол требует строго соблюдения JSON-RPC 2.0. Ошибка в структуре запроса/ответа (например, отсутствующее обязательное поле) приведет к сбою взаимодействия. Нужны строгие библиотеки-клиенты и серверы с хорошей валидацией.
- Scalability (Сервера): Один MCP-сервер может обрабатывать множество сессий асинхронно. Однако, если инструменты требуют долгих блокирующих операций или интенсивных вычислений, это станет узким местом. Ключ к масштабируемости: асинхронность операций ввода-вывода (БД, сети) и, возможно, распределение нагрузки между экземплярами сервера.
Когда MCP? Когда нет?
Используйте MCP, когда:
- Вам нужна строгая абстракция: Разделение логики LLM от конкретных реализаций инструментов/ресурсов критично.
- Инструменты/ресурсы динамические: Их набор или функциональность меняется часто. MCP позволяет обновлять серверы независимо от клиента.
- Множество клиентов/серверов: Нужен унифицированный способ для разных LLM-приложений подключаться к одному набору бэкенд-сервисов.
- Сложные цепочки вызовов: LLM должен последовательно использовать несколько инструментов для достижения цели (например, “Найди пользователя по email, затем получи его историю заказов, потом посчитай средний чек”).
Избегайте MCP, когда:
- Простой статический контекст: Достаточно RAG (Retrieval-Augmented Generation) или прямого включения данных в промпт. MCP — избыточен.
- Критически важна сверхнизкая задержка: Цепочка сетевых запросов MCP добавит значительную latency по сравнению с локальным кешированием или прямым API вызовом.
- Одноразовая интеграция: Для одной конкретной задачи сложность реализации MCP может быть неоправданной. Простой API клиент или SDK проще.
- Простые инструменты: Если инструмент — это “вызвать GET /api/data”, MCP добавляет еще один уровень абстракции без реальной пользы.
Вывод: MCP — это мощный, но не панацея протокол для создания гибких, распределенных экосистем вокруг LLM. Его сила — в стандартизации и разделении ответственности. Однако, цена этой гибкости — дополнительные сложности в управлении состоянием, задержках и безопасности. Применяйте его осознанно, когда потенциальные выгоды (динамичность, масштабируемость, разделение) перевешивают накладные расходы, особенно в системах с высокими требованиями к latency или простоты. Для большинства статических интеграций RAG или прямые API остаются более простыми и эффективными решениями.