Logo Craft Homelab Docs Контакты Telegram
MCP: Model Context Protocol — серверы и клиенты
Tue Jan 06 2026

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.

Ключевые компоненты:

  1. Клиент (LLM Runtime/Application):

    • Иницирует сессию (initialize).
    • Запрашивает доступные инструменты и ресурсы (tools.list, resources.list).
    • Вызывает инструменты (tools/call) и запрашивает ресурсы (resources/read).
    • Обрабатывает результаты и управляет состоянием сессии.
  2. Сервер (Provider/Tool/Resource Host):

    • Реализует логику конкретных инструментов и ресурсов.
    • Обрабатывает запросы от клиента в рамках сессии.
    • Выполняет действия (инструменты) и предоставляет данные (ресурсы).
    • Использует протокол для структурированного ответа.
  3. Потоки сообщений:

    • Server -> Client: Пуш-уведомления (e.g., notifications/push для асинхронных событий).
    • Client -> Server: Запросы и ответы (e.g., tools/call -> tools/call/result).

Пример потока вызова инструмента:

  1. Клиент отправляет tools/call с именем инструмента и параметрами.
  2. Сервер получает запрос, выполняет логику.
  3. Сервер отправляет tools/call/result с результатом (или ошибкой).
  4. Клиент использует результат для формирования следующего шага.

Реализация: Сервер 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())

Пояснения к коду:

  1. Декораторы @server.call_tool() / @server.list_resources() / @server.read_resource(): Регистрируют обработчики для соответствующих типов запросов. Протокол требует реализовать базовые операции list и read для ресурсов.
  2. Асинхронность: Все обработчики async, так как выполнение операций ввода-вывода (БД, API) может быть блокирующим. asyncio необходим для эффективного использования потоков.
  3. Типизация аргументов: Python 3.9+ позволяет явно указывать типы аргументов (name: str, arguments: dict). Это улучшает читаемость и помогает статическим анализаторам.
  4. Обработка ошибок: raise ValueError — стандартный способ возврата ошибки по протоколу. Сервер MCP автоматически преобразует исключение в JSON-RPC ошибку с кодом и сообщением.
  5. URI для ресурсов: Использование уникальных URI (timestamp://now) — ключевой идентификатор ресурса. Сервер должен их уметь разрешать.
  6. stdio_server: Простой транспорт для локальной разработки. Для распределенных систем используйте WebSocket или HTTP серверы.

Узкие места в продакшене

  1. State Management: Сессия MCP — это не HTTP сессия с куки. Клиент несет ответственность за передачу sessionId в каждом запросе. Потеря sessionId (например, при перезапуске клиента) обнуляет состояние. Реализация надежного механизма сохранения и восстановления сессии — нетривиальная задача.
  2. Latency: Каждый вызов инструмента/ресурса — отдельный сетевой запрос. Цепочка из 5 вызовов добавит минимум 5 * RTT (Round-Trip Time). Для интерактивных сценариев это критично. Оптимизация: батчинг запросов (если протокол поддерживает, нативно не гарантирован), локальное кеширование результатов (TTL).
  3. Error Handling и Retries: Протокол четко определяет ошибки. Однако, логику повторов (retries) клиент должен реализовывать сам. Сервер может временно недоступен, инструмент может выбросить исключение. Нужен баланс между надежностью и неограниченными повторами.
  4. Security: Передача данных по протоколу требует шифрования (HTTPS/WSS). Аутентификация сервера клиентом и наоборот не входит в ядро протокола. Это остается на усмотрение реализации. Риск: компрометация API ключей, доступ к инструментам без должных проверок.
  5. Tool/Resource Discovery: Клиент должен явно запрашивать список доступных инструментов и ресурсов (tools.list, resources.list). Если сервер добавляет новые динамически, клиент не узнает об этом без перезапроса. Серверные уведомления (notifications/push) частично решают проблему, но их использование не обязательно.
  6. Schema Validation: Протокол требует строго соблюдения JSON-RPC 2.0. Ошибка в структуре запроса/ответа (например, отсутствующее обязательное поле) приведет к сбою взаимодействия. Нужны строгие библиотеки-клиенты и серверы с хорошей валидацией.
  7. 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 остаются более простыми и эффективными решениями.