MCPModel Context ProtocolAI Tools

Руководство по MCP (Model Context Protocol) для разработчиков

1 мин чтения

До MCP у каждого ИИ-инструмента была собственная система плагинов. У Claude Code — своя. У Cursor — другая. Ваш инструмент для работы с базой данных работал в Claude Code, но не в Cursor, поэтому вы писали его дважды. Смена инструмента означала переписывание всех интеграций. Таково было состояние ИИ-инструментов в 2024 году — каждый инструмент — остров, каждая интеграция — собственная.

MCP — Model Context Protocol — изменил это в 2025 году, а в 2026-м он повсюду. Его использует Claude Code. Его поддерживает Cursor. LangChain, LiteLLM и все крупные ИИ-платформы добавили поддержку MCP. Официальная спецификация MCP определяет протокол — это открытый стандарт (на основе JSON-RPC), который стандартизирует то, как ИИ-модели обнаруживают и вызывают инструменты. Напишите один MCP-сервер. Используйте его с любым совместимым с MCP клиентом.

Что такое MCP — и почему это важно

Протокол, а не маркетинг. MCP — протокол на основе JSON-RPC 2.0. MCP-сервер предоставляет инструменты, ресурсы и промпты. MCP-клиент (встроенный в ИИ-приложение) подключается к серверу, обнаруживает доступное и вызывает инструменты от имени LLM. Транспортный уровень подключаемый — stdio для локальных инструментов, HTTP с SSE для удалённых сервисов.

MCP против function calling — дополняют друг друга, не конкурируют. Function calling: LLM вызывает инструмент в рамках одного API-запроса. Определение инструмента отправляется в API-вызове. MCP: LLM обнаруживает доступные инструменты через стандартизированный протокол, а затем вызывает их — потенциально в нескольких сессиях и с несколькими инструментами. MCP стандартизирует обнаружение и описание инструментов. Function calling их исполняет. Вы можете использовать MCP для управления каталогом инструментов и function calling для их вызова — они работают вместе. За подробным сравнением реализаций function calling в OpenAI, Anthropic, Google и DeepSeek обратитесь к нашему руководству по function calling и использованию инструментов.

Почему MCP важен для разработчиков API. До MCP: вы написали инструмент для запросов к базе данных. Чтобы использовать его в Claude Code, вы написали плагин под Claude Code. Чтобы использовать в Cursor, вы написали интеграцию под Cursor. Чтобы использовать в своём приложении, вы написали собственный код. После MCP: напишите один MCP-сервер. Его сможет использовать каждый совместимый с MCP клиент. Это аналогия «USB-C для ИИ-инструментов» — она не идеальна, но это стандарт, который победил.

Архитектура MCP: серверы, клиенты и транспорты

MCP-сервер. Предоставляет инструменты, ресурсы и промпты. Пишется на Python, Node.js или Go — на чём угодно. Работает локально (транспорт stdio) или удалённо (транспорт HTTP+SSE). Сервер — это программа. Она запускается. Она слушает соединения. Она отвечает на запросы вызова инструментов. Она останавливается, когда клиент отключается.

MCP-клиент. Подключается к MCP-серверам, обнаруживает доступные инструменты, отправляет запросы вызова инструментов от LLM и возвращает результаты. Встроен в ИИ-приложения — Claude Code, Cursor, ваше приложение. Клиент — это мост между решением LLM («мне нужно запросить базу данных») и исполнением инструмента (SELECT * FROM users).

Транспорты. stdio: сервер запускается как подпроцесс клиента. Для связи используются стандартный ввод/вывод. Лучший выбор для локальных инструментов разработки — ноль сетевой настройки, ноль задержки. Транспорт stdio добавляет менее 1 мс издержек на круг сообщения на современной машине. HTTP+SSE: сервер работает как удалённый сервис. HTTP POST для запросов, Server-Sent Events для потоковых ответов. Лучший выбор для продакшн-сервисов — могут подключаться несколько клиентов, сервер можно обновлять независимо, не перезапуская IDE каждого разработчика.

Схема архитектуры:

LLM  → MCP Client  → Transport (stdio/HTTP)  → MCP Server  → External Resources (DB, API, Filesystem)

Создание вашего первого MCP-сервера

MCP-сервер погоды + цены акций. 15 минут. Два инструмента.

# mcp_server.py
import json
import sys
from typing import Any

class MCPServer:
    def __init__(self):
        self.tools = {
            "get_weather": {
                "description": "Get current weather for a city. Returns temperature in Celsius.",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "city": {"type": "string", "description": "City name, e.g. 'Tokyo'"}
                    },
                    "required": ["city"]
                }
            },
            "get_stock_price": {
                "description": "Get current stock price for a ticker symbol.",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
                    },
                    "required": ["symbol"]
                }
            }
        }

    def handle_request(self, request: dict) -> dict:
        method = request.get("method")

        if method == "tools/list":
            return {"tools": list(self.tools.values())}

        elif method == "tools/call":
            tool_name = request["params"]["name"]
            tool_args = request["params"]["arguments"]
            result = self._execute_tool(tool_name, tool_args)
            return {"content": [{"type": "text", "text": str(result)}]}

        else:
            return {"error": f"Unknown method: {method}"}

    def _execute_tool(self, name: str, args: dict) -> Any:
        if name == "get_weather":
            return f"Weather in {args['city']}: 22°C, partly cloudy"
        elif name == "get_stock_price":
            return f"{args['symbol']}: $185.50"
        else:
            return f"Unknown tool: {name}"

# stdio transport —reads JSON-RPC from stdin, writes to stdout
if __name__ == "__main__":
    server = MCPServer()
    for line in sys.stdin:
        request = json.loads(line)
        response = server.handle_request(request)
        sys.stdout.write(json.dumps(response) + "\n")
        sys.stdout.flush()

Подключение к Claude Desktop. Краткое руководство по MCP от Anthropic проводит через всю настройку. Добавьте это в ваш claude_desktop_config.json:

{
  "mcpServers": {
    "my-tools": {
      "command": "python",
      "args": ["mcp_server.py"]
    }
  }
}

Перезапустите Claude Desktop. Ваши два инструмента — get_weather и get_stock_price — теперь доступны. Claude обнаруживает их автоматически. Попробуйте: «Какая погода в Токио и какова цена акций Apple?» Claude вызовет оба инструмента параллельно и синтезирует ответ.

За полным руководством по Claude API — аутентификация, стриминг, использование инструментов и обработка ошибок — обратитесь к нашему руководству разработчика по Claude API.

Создание настоящего MCP-сервера: инструмент запросов к базе данных

Сервер погоды доказывает, что MCP работает на 50 строках Python. Продакшен-инструментам нужно больше: параметризованные запросы, пул соединений и сообщения об ошибках, из которых LLM может самокорректироваться. Вот полный MCP-сервер, который запрашивает базу данных SQLite, построенный на официальном Python SDK MCP (pip install mcp). Три инструмента — query_users, get_user_by_id, create_user. Копируйте, адаптируйте, разворачивайте.

# db_mcp_server.py —production-grade MCP database server
import sqlite3
import asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

server = Server("database-tools")

# Database connection —created once, reused across all tool calls
_connection: sqlite3.Connection | None = None

def get_db():
    global _connection
    if _connection is None:
        _connection = sqlite3.connect("app.db")
        _connection.row_factory = sqlite3.Row
        # WAL mode: concurrent reads without locking. Without this,
        # two simultaneous query_users calls serialize —the second
        # blocks until the first releases the read lock.
        _connection.execute("PRAGMA journal_mode=WAL")
    return _connection

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="query_users",
            description="Run a SELECT query against the users table. "
                        "Use for searching, filtering, or counting users. "
                        "Returns results as JSON array. "
                        "Column names: id, name, email, role, status, created_at.",
            inputSchema={
                "type": "object",
                "properties": {
                    "where_clause": {
                        "type": "string",
                        "description": "SQL WHERE clause without 'WHERE' keyword. "
                                       "Example: \"age > 25 AND status = 'active'\". "
                                       "Leave empty for all users."
                    },
                    "limit": {
                        "type": "integer",
                        "description": "Max rows to return. Default 50.",
                        "default": 50
                    }
                }
            }
        ),
        Tool(
            name="get_user_by_id",
            description="Fetch a single user by primary key. Returns user object or null.",
            inputSchema={
                "type": "object",
                "properties": {
                    "user_id": {
                        "type": "integer",
                        "description": "The user's ID in the database."
                    }
                },
                "required": ["user_id"]
            }
        ),
        Tool(
            name="create_user",
            description="Insert a new user into the database. "
                        "Returns the created user with their assigned ID.",
            inputSchema={
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "User's full name."},
                    "email": {"type": "string", "description": "Valid email address."},
                    "role": {
                        "type": "string",
                        "enum": ["admin", "editor", "viewer"],
                        "description": "User role. Default: viewer."
                    }
                },
                "required": ["name", "email"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    db = get_db()

    if name == "query_users":
        where = arguments.get("where_clause", "")
        limit = arguments.get("limit", 50)

        query = "SELECT * FROM users"
        params = []
        if where.strip():
            query += " WHERE " + where
        query += " LIMIT ?"
        params.append(limit)

        try:
            rows = db.execute(query, params).fetchall()
        except sqlite3.OperationalError as e:
            # Return column names so the LLM can self-correct bad WHERE clauses
            return [TextContent(
                type="text",
                text=f"Query failed: {e}. Valid columns: id, name, email, role, status, created_at."
            )]

        return [TextContent(
            type="text",
            text=str([dict(r) for r in rows])
        )]

    elif name == "get_user_by_id":
        user_id = arguments["user_id"]
        row = db.execute(
            "SELECT * FROM users WHERE id = ?", [user_id]
        ).fetchone()
        if row is None:
            return [TextContent(type="text", text=f"No user found with id={user_id}")]
        return [TextContent(type="text", text=str(dict(row)))]

    elif name == "create_user":
        name_val = arguments["name"]
        email = arguments["email"]
        role = arguments.get("role", "viewer")

        try:
            cursor = db.execute(
                "INSERT INTO users (name, email, role) VALUES (?, ?, ?)",
                [name_val, email, role]
            )
            db.commit()
            new_id = cursor.lastrowid
        except sqlite3.IntegrityError as e:
            return [TextContent(
                type="text",
                text=f"Insert failed: {e}. Email may already exist."
            )]

        new_user = db.execute(
            "SELECT * FROM users WHERE id = ?", [new_id]
        ).fetchone()
        return [TextContent(type="text", text=str(dict(new_user)))]

    return [TextContent(type="text", text=f"Unknown tool: {name}")]

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationCapabilities(
                sampling={},
                experimental={},
                roots={}
            ),
            notification_options=NotificationOptions()
        )

if __name__ == "__main__":
    asyncio.run(main())

Четыре решения в этом коде предотвращают ночные звонки в 3 часа утра.

Описания инструментов с примерами. LLM читает ваше описание инструмента, чтобы решить, какой инструмент вызвать. Голое "description": "Query the database" не даёт модели возможности различить инструменты — для запроса SELECT COUNT(*) она часто попытается вызвать get_user_by_id. Без явных инструкций модели часто выбирают не тот инструмент, когда у нескольких инструментов похожие описания. Описание выше включает, что делает инструмент, что возвращает и точные имена колонок. Такая конкретность устраняет неоднозначность.

Параметризованные запросы — никаких f-строк. db.execute("SELECT * FROM users WHERE id = ?", [user_id]) никогда не превращается в db.execute(f"SELECT * FROM users WHERE id = {user_id}"). Одна f-строка в MCP-сервере базы данных, и LLM, генерирующая user_id = "1 OR 1=1", утечёт все строки вашей таблицы users. LLM сгенерирует такой ввод. Это случалось в продакшне. Параметризованные запросы — жёсткое требование, а не приятный бонус.

Сообщения об ошибках с подсказками для восстановления. Когда query_users падает, потому что LLM угадала несуществующее имя колонки, ошибка включает список валидных колонок: "Valid columns: id, name, email, role, status, created_at." LLM читает это, исправляет запрос и повторяет вызов инструмента — ноль вмешательства человека. Голое "OperationalError: no such column" вызывает у Claude пожимание плечами и заблокированного пользователя.

Переиспользование соединения с режимом журнала WAL. PRAGMA journal_mode=WAL включает параллельные чтения без блокировок. Без него два одновременных вызова query_users сериализуются — второй ждёт 50 мс, пока первый освободит блокировку чтения. В цепочке инструментов, где Claude вызывает query_users, читает результаты, а затем вызывает get_user_by_id для каждой строки, ожидание блокировки накапливается через 5 последовательных вызовов до 250 мс задержки, видимой пользователю. Режим WAL устраняет узкое место.

MCP у провайдеров LLM

Поддержка MCP сильно различается по провайдерам. Anthropic создал протокол и имеет самую глубокую интеграцию. Остальные догоняют с разной скоростью. Вот где находится каждый крупный провайдер в середине 2026 года.

ПоставщикУровень поддержки MCPПоддержка транспортовГотовность к продакшнуКачество SDK
AnthropicНативный — автор протоколаstdio + HTTP/SSEПродакшен-готовность для обоих транспортовОфициальные SDK для Python и TypeScript с полным покрытием спецификации
OpenAIЧерез Agents SDK и адаптеры сообществаstdio (Agents SDK), HTTP через адаптерыAgents SDK: продакшн. Адаптеры: бета-качествоПоддерживаются сообществом; официального MCP SDK нет
GoogleТолько адаптеры сообществаstdio, ограниченный HTTP/SSEТолько для разработкиРанние пакеты сообщества; скудная документация
DeepSeekПуть, совместимый с OpenAIЧерез тулчейн адаптеров OpenAIРаботает, но не поддерживаетсяНет выделенного MCP SDK; наследует особенности адаптера OpenAI
Aggregation Platforms (инфраструктурный слой — маршрутизирует ко всем провайдерам ниже)MCP-шлюз — подключается один раз, маршрутизирует вездеHTTP/SSE с единой аутентификациейПродакшен с управляемой аутентификацией, ограничением скорости и журналированиемПлатформенные SDK для Python, JavaScript, LangChain

Паттерн MCP-шлюза. Агрегационная платформа действует как MCP-клиент, подключается к вашим MCP-серверам и предоставляет инструменты любому LLM через единую точку API. Вы пишете один MCP-сервер. Вы используете его с GPT-5.5, Claude Opus 4, Gemini 3 и DeepSeek V3 — все через POST https://api.tokspan.com/v1/chat/completions с одним и тем же API-ключом. Платформа обрабатывает перевод протокола, поэтому ваши MCP-инструменты работают независимо от активной модели. Одно определение сервера питает 6+ моделей. За 5-минутной настройкой обратитесь к краткому руководству TokSpan.

MCP против function calling: когда что использовать

Решение сводится к одному вопросу: должны ли ваши инструменты работать с несколькими LLM-клиентами?

Используйте только function calling, когда вы вызываете один LLM API напрямую — OpenAI, Anthropic или Google — и ваши инструменты специфичны для одного приложения. Вы передаёте определения инструментов прямо в массив tools запроса chat completions. Инфраструктура нулевая: нет отдельного серверного процесса, нет транспортного слоя, нет согласования протокола. Бот поддержки клиентов, который проверяет статус заказа через один внутренний REST-endpoint GET /orders/:id, нуждается в function calling, а не в MCP. Не переусложняйте — MCP добавляет серверный процесс, транспорт и рукопожатие протокола без единой выгоды, когда у вас один клиент.

Используйте MCP + function calling вместе, когда у вас несколько ИИ-клиентов — Claude Code на вашем ноутбуке, Cursor у коллеги и собственный дашборд на стейджинг-сервере — которым нужны одни и те же инструменты. Ваш инструмент запросов к базе данных, инструмент деплоя и просмотрщик логов определяются один раз в MCP-сервере. Каждый клиент обнаруживает их через tools/list и вызывает через function calling в рантайме.

Ваш каталог инструментов централизован. Обновления описаний и схем инструментов мгновенно распространяются на каждый клиент без переразвёртывания. Внутренняя платформенная команда в инженерной организации на 200 человек, использующая этот паттерн, сократила сопровождение интеграции инструментов с 8 часов на новый инструмент до 30 минут.

Используйте MCP как шлюз, когда вы маршрутизируете вызовы инструментов к нескольким LLM-провайдерам через агрегационную платформу. Одно определение MCP-сервера питает инструментами GPT-5.5, Claude Opus 4 и Gemini 3 одновременно. Платформа переводит между протоколом обнаружения инструментов MCP и API function calling каждого провайдера. Вы никогда не пишете определения инструментов под конкретного провайдера. Вы никогда не отлаживаете, почему инструмент работает с Claude, но молча падает с GPT-5.5. Одно определение сервера питает 6+ моделей через единую точку — конфигурация инструментов под каждого провайдера не требуется.

Неправильный выбор — по опыту. Рассмотрим типичный сценарий: разработчик в небольшом стартапе тратит две недели на настройку MCP-серверов с транспортом HTTP/SSE, middleware аутентификации и пулом соединений для трёх внутренних инструментов, используемых исключительно с Claude Code. Function calling заняло бы два часа. Две недели инженерного времени обменены на инфраструктуру, обслужившую ровно один клиент. Начинайте с function calling. Мигрируйте на MCP, когда у вас появится второй клиент — не раньше.

MCP в продакшне: аутентификация, ограничение скорости и обработка ошибок

stdio для разработки. HTTP+SSE для продакшна. Транспорт stdio запускает ваш MCP-сервер как подпроцесс — без аутентификации, без сетевой безопасности, один клиент на серверный процесс. Годится для claude_desktop_config.json на вашем ноутбуке. Не годится для сервиса, обслуживающего 50 разработчиков в вашей организации. Транспорт HTTP+SSE позволяет развернуть сервер как отдельный сервис за балансировщиком нагрузки, с правильной аутентификацией, мониторингом и независимыми циклами развёртывания.

Middleware аутентификации. Спецификация HTTP-транспорта MCP на середину 2026 года ещё не определяет стандартный механизм аутентификации. Вы добавляете свой. Общий паттерн: проверяйте API-ключ в заголовке Authorization: Bearer <key> до обработки любого MCP-сообщения. Для развёртывания на базе TokSpan платформа обрабатывает аутентификацию на уровне API-шлюза — ваш MCP-сервер получает предварительно аутентифицированные запросы на https://api.tokspan.com/v1/. Если вы самохостите, прикрутите middleware аутентификации перед обработчиком MCP.

Просроченный токен на глубине 3 цепочки инструментов — где tool_a вызывает tool_b, который вызывает tool_c — порождает непонятную JSON-RPC ошибку, на отслеживание до слоя аутентификации уходит 45 минут. Сначала сделайте аутентификацию правильно. За полным чеклистом безопасности обратитесь к нашему чеклисту безопасности продакшна.

# Auth middleware for MCP HTTP server (FastAPI pattern)
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
VALID_TOKENS = {"mcp-secret-abc123", "mcp-prod-xyz789"}  # Replace with DB lookup

@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    if request.url.path.startswith("/mcp/"):
        token = request.headers.get("Authorization", "").removeprefix("Bearer ")
        if token not in VALID_TOKENS:
            raise HTTPException(status_code=401, detail="Invalid MCP API key")
    return await call_next(request)

Ограничение скорости предотвращает инциденты, усиленные LLM. Один расплывчато описанный инструмент — run_sql без принудительного LIMIT — и LLM генерирует запрос, сканирующий 10 миллионов строк в 2 часа ночи, потому что пользователь спросил «покажи мне всё». Токен-бакитный ограничитель на стороне сервера ограничивает query_users 60 вызовами в минуту и create_user 10 вызовами в минуту. Используйте Redis для распределённого ограничения скорости между несколькими экземплярами сервера — INCR + EXPIRE на имя инструмента на API-ключ.

Без ограничения скорости один восторженный промпт LLM становится первопричиной продакшн-отказа базы данных. Вас разбудят в 3 часа ночи. Вы проследите это до определения инструмента, написанного за 15 минут и забытого шесть спринтов назад.

Обработка ошибок, помогающая LLM восстановиться. Не возвращайте "Error: something went wrong." LLM читает ваше сообщение об ошибке и пробует снова. Возвращайте три вещи: тип ошибки, конкретный параметр, который не прошёл, и валидные альтернативы. Эта ошибка — мёртвый груз: "Invalid input." А эта позволяет Claude самокорректироваться за одну попытку: "create_user failed: role must be one of [admin, editor, viewer]. Received: 'superadmin'." Разница между этими двумя сообщениями — разница между самовосстанавливающейся цепочкой инструментов и застрявшим пользователем, пялящимся на спиннер, пока он не сдастся и не откроет тикет.

Частые ловушки MCP: что ломается на практике

Вот четыре режима отказа, с которыми MCP-разработчики сталкиваются в первый месяц. Каждый можно избежать, если знать о нём до развёртывания.

Расплывчатые описания инструментов. Это причина номер один багов неверного выбора инструмента. "description": "Fetch data" ничего не говорит LLM. Claude Opus 4 угадает, какой инструмент использовать, и часто ошибается, когда у нескольких инструментов пересекающиеся описания. Пишите описания инструментов так, будто объясняете инструмент разработчику, который никогда не видел вашу кодовую базу. Включайте, что инструмент делает, что возвращает, пример валидного ввода и когда его НЕ использовать.

Описание query_users в сервере базы данных выше не многословно — оно ровно настолько точное, чтобы устранить неоднозначность.

Лимиты буфера stdio. Транспорт stdio использует OS-пайпы. Буфер пайпа по умолчанию в Linux: 64 КБ. Ваш инструмент query_large_dataset возвращает 2 МБ JSON. Вызов write() блокируется. Клиент зависает. Вы пялитесь на спиннер 30 секунд, затем kill -9 процесса.

Для любого инструмента, который может вернуть больше 64 КБ, реализуйте чанкинг ответа — разбивайте большие результаты на страницы с параметрами page и page_size. Или переходите на HTTP-транспорт, где лимиты размера ответа настраиваются на уровне фреймворка сервера. Установите жёсткий потолок ответа 512 КБ для каждого инструмента. Ни один инструмент в MCP-сервере не должен возвращать больше данных, чем LLM может полезно обработать в одном контекстном окне.

Коллизии имён инструментов между серверами. Вы подключаете два MCP-сервера: один от команды баз данных (get_status — лаг репликации), другой от команды эксплуатации (get_status — здоровье деплоя). Оба предоставляют инструмент с именем get_status. Claude Code добавляет префикс имени сервера: database-tools_get_status и ops-tools_get_status. Но некоторые MCP-клиенты, включая недавние версии Cursor, молча выбирают сервер, который первым ответил на tools/list. Исправление до смешного простое и повсеместно игнорируется: неймспейсите имя каждого инструмента с первого дня. db_query_users. ops_deploy_service. logs_search_errors. Префикс из двух символов предотвращает целую категорию молчаливых отказов.

Пробелы в валидации схем. LLM отправит "user_id": "42" (строку), когда ваша схема говорит "type": "integer". Она отправит "limit": -5, когда схема говорит "minimum": 1. Она отправит "role": "superadmin", когда схема говорит "enum": ["admin", "editor", "viewer"]. Ваш обработчик call_tool должен валидировать каждый аргумент, приводить типы, где безопасно (int("42") — 42), и возвращать конкретные ошибки валидации для всего остального. Никогда не предполагайте, что LLM уважает вашу JSON Schema. Она этого не делает. Ваш сервер — последняя линия обороны между галлюцинированным аргументом инструмента и вашей продакшн-базой данных.

FAQ

Нужен ли мне MCP, если я уже использую function calling?

Они дополняют друг друга, а не взаимоисключают. Function calling исполняет инструменты внутри API-вызова. MCP стандартизирует обнаружение и описание инструментов между приложениями. Используйте MCP для определения каталога инструментов — единого источника истины о том, какие инструменты существуют и как их вызывать. Используйте function calling в рантайме, чтобы фактически вызывать эти инструменты. MCP — слой интерфейса. Function calling — слой исполнения. Если у вас ровно одно клиентское приложение и три инструмента, пропустите MCP и используйте function calling напрямую. Вы поймёте, когда вам нужен MCP: в тот момент, когда обнаружите, что копируете определения инструментов между конфигами Claude Code и массивом tools вашего приложения.

Могу ли я использовать MCP с моделями OpenAI?

Да — через адаптеры или OpenAI Agents SDK. Нативная поддержка менее зрелая, чем у Anthropic, но функциональна для распространённых паттернов инструментов. Если вы используете агрегационную платформу с поддержкой MCP-шлюза, платформа обрабатывает перевод: ваш MCP-сервер работает с GPT-5.5 и GPT-5.5-mini без какого-либо OpenAI-специфичного MCP-кода с вашей стороны. Компромисс: адаптеры сообщества отстают от официального SDK Anthropic примерно на 3–6 месяцев по новым фичам спецификации MCP.

Готов ли MCP к продакшну в 2026 году?

Для локальных инструментов через транспорт stdio: да, и stdio-клиент Claude Code проверен в бою на миллионах часов разработчиков. Для удалённых сервисов через HTTP: да, с оговоркой, что вы должны добавить собственный middleware аутентификации — стандарт аутентификации HTTP-транспорта всё ещё развивается. Ядро протокола (формат JSON-RPC сообщений, обнаружение инструментов, исполнение инструментов) стабильно и, на момент написания, не имело ломающих изменений с обновления спецификации 2025-03-26. Если вы самохостите HTTP MCP-сервер, заложите 2–3 дня на настройку аутентификации, ограничения скорости и логирования.

Как MCP соотносится с агрегационными API-платформами?

Агрегационные платформы могут выступать как MCP-шлюзы: вы подключаете свои MCP-серверы к платформе один раз. Платформа маршрутизирует вызовы инструментов к любому LLM — GPT-5.5, Claude Opus 4, Gemini 3, DeepSeek V3. Вы получаете мультимодельное использование инструментов без конфигурации MCP под каждого провайдера. Один MCP-сервер. Все модели. За подробностями о том, как агрегационные платформы реализуют маршрутизацию MCP-шлюза на практике, обратитесь к документации интеграции MCP TokSpan.

Могу ли я подключить несколько MCP-серверов к одному клиенту?

Да. Claude Code, Cursor и большинство MCP-клиентов поддерживают одновременное подключение к нескольким серверам — добавьте каждый сервер в ваш конфиг с уникальным именем, и все инструменты всех серверов появятся в списке доступных инструментов LLM. Острый край: коллизии имён инструментов между серверами. Именуйте инструменты с префиксом неймспейса (db_query, ops_deploy) с первого дня. Не полагайтесь на то, что клиент разрешит неоднозначность — не каждый клиент это делает, а те, что делают, обрабатывают это непоследовательно.

Что происходит, когда два MCP-сервера предоставляют инструменты с одинаковым именем?

Зависит от клиента — и в этом проблема. Claude Code добавляет имя сервера, создавая уникальные идентификаторы: database-tools_get_status и ops-tools_get_status. Cursor молча дедуплицирует — побеждает сервер, первым ответивший на tools/list. Вообще не полагайтесь на разрешение неоднозначности на стороне клиента. Добавляйте серверно-специфичный неймспейс к имени каждого инструмента. db_get_status и ops_get_status устраняют неоднозначность в источнике.

Как отлаживать MCP-вызовы инструментов, когда что-то идёт не так?

Проверьте три места по порядку. Первое — логи клиента: Claude Code пишет MCP-сообщения в свою директорию логов. Второе — stderr вашего сервера: транспорт stdio отправляет весь вывод stderr в консоль клиента (stdin и stdout зарезервированы для сообщений протокола MCP, но stderr свободен для логирования). Третье — добавьте структурированное JSON-логирование на ваш сервер: {"event": "tool_call", "tool": "query_users", "args": {...}, "duration_ms": 45, "error": null}. Структурированные логи ловят те 80% MCP-багов, которые всплывают только во время он-колл дежурств, когда вы уже 15 минут в инциденте и понятия не имеете, какой вызов инструмента упал и почему.

Поддерживает ли MCP потоковые ответы от инструментов?

Нет в текущей спецификации. MCP-вызовы инструментов возвращают единый массив content — никаких частичных ответов, никаких обновлений прогресса, никаких Server-Sent Events от исполнения инструментов. Для долго выполняющихся операций вроде миграций базы данных или генерации отчётов немедленно возвращайте ID задания и предоставляйте отдельный инструмент get_job_status. LLM его опрашивает. Этот паттерн ID-задания плюс опрос — де-факто стандарт, используемый каждым крупным продакшн MCP-сервером на середину 2026 года. Прямая поддержка стриминга в дорожной карте спецификации MCP, но без подтверждённой даты релиза.

MCP унифицировал интеграцию ИИ-инструментов в 2025–2026 годах — один сервер, любой клиент, любая модель. Но стандартизация приглашает конкуренцию. Протокол Agent-to-Agent (A2A) от Google набирает обороты среди корпоративных команд, которым нужно меж-агентное взаимодействие за пределами простого вызова инструментов. OpenAI строит собственную экосистему агентного SDK с более глубокой платформенной интеграцией. Вопрос, за которым стоит следить до 2027 года, не в том, выживет ли MCP — сам протокол солиден, а спецификация стабильна — а в том, переживёт ли «один протокол, чтобы править всеми» аппетит индустрии к платформо-специфичным интеграциям, предлагающим более тесную связку и лучшую производительность ценой привязки.

MCP-сервер, который вы построили в первой секции этой статьи, работает с одним клиентом — Claude Desktop. MCP-шлюз подключает тот же сервер к каждому LLM в вашем стеке, поэтому определения инструментов, написанные один раз, питают каждую используемую вами модель. Документация интеграции MCP TokSpan покрывает настройку шлюза, конфигурацию аутентификации и продакшн-чеклист из секции выше — превращая 50-строчный stdio-сервер в мультимодельный слой инструментов, переживающий смену провайдеров без касания к строке кода инструментов.