Туториалы

MCP в продакшене: от настройки до custom-серверов

Что такое MCP (Model Context Protocol)?

Model Context Protocol (MCP) — это открытый стандарт для подключения AI-агентов к внешним инструментам, API и источникам данных через единый протокол, поддержанный Anthropic, OpenAI и Microsoft через Linux Foundation. Он позволяет языковым моделям вызывать инструменты на локальных или удалённых MCP-серверах через структурированный JSON-RPC интерфейс — в публичных реестрах доступно более 10 000 серверов.

TL;DR

  • -MCP-экосистема: 10 000+ публичных серверов, 97 млн загрузок SDK в месяц, поддержана Anthropic + OpenAI + Microsoft через Linux Foundation
  • -Custom-серверы решают три реальные проблемы: готовые работают плохо, не покрывают ваш workflow, или обновляются непредсказуемо
  • -stdio-транспорт — правильный выбор для локальных серверов; Streamable HTTP только для мультиарендных облачных деплоев
  • -Критичное правило: stdio-сервер не должен писать в stdout ничего, кроме JSON-RPC — `console.log()` ломает весь протокол
  • -Production-паттерны: типизированные ошибки, fallback chains, health checks и graceful shutdown

10 000 MCP-серверов в публичных реестрах. 97 миллионов загрузок SDK в месяц. OpenAI, Google и Microsoft поддержали протокол через Linux Foundation. MCP стал стандартом для интеграции AI-агентов с внешними системами.

Но большинство гайдов заканчиваются на «подключите npm-пакет». Подключили, работает, дальше что?

Дальше начинается продакшн. Серверы молча падают, процессы зависают, квоты исчерпываются без предупреждения. В моём проекте JourneyBay работает стек из custom MCP-серверов: обёртки над CLI-инструментами, мосты к внутренним API, специализированные поисковые индексы. В этой статье расскажу, как их строить, отлаживать и не сойти с ума.

Зачем строить свои MCP-серверы

Публичных серверов хватает. На npm, PyPI и в официальном реестре Anthropic сотни готовых пакетов: GitHub, Slack, PostgreSQL, Notion. Подключил, прописал в конфиг, заработало.

Три ситуации, когда готовых серверов недостаточно:

Готовый сервер есть, но работает плохо. У OpenAI и Gemini есть собственные MCP-серверы. На бумаге всё красиво. На практике: Gemini-сервер я так и не смог настроить — документация расходилась с реальным поведением. Codex-сервер от OpenAI работал, но каждый вызов стартовал новую сессию со всеми подключёнными MCP-инструментами и полным контекстом. Результат — раздутый контекст, медленный старт, непредсказуемое поведение. Проще оказалось написать 250-строчную обёртку над CLI, которая делает ровно одно и делает это быстро.

Готовый сервер не покрывает workflow. Для Substack есть неофициальные MCP-серверы, но мне нужен был семантический поиск по собственным статьям с embeddings и кешем. Существующие серверы работают с API Substack, но не строят индексы и не ищут по смыслу. Когда нужна специфичная логика поверх стандартного API — проще написать свой.

Контроль над reliability. Публичные серверы обновляются без предупреждения. Новая версия может сломать tool descriptions, изменить формат ответа или добавить зависимости. Custom-сервер работает ровно так, как ты его написал, и ломается только когда ты сам что-то меняешь.

Экосистема MCP в феврале 2026

Протоколу чуть больше года. За это время:

  • SDK скачивают 97 миллионов раз в месяц (данные npmjs + PyPI)
  • Активных серверов в реестрах больше 10 000 (было 5 800 в середине 2025)
  • MCP-клиентов (IDE, чат-боты, агенты) больше 300
  • В декабре 2025 MCP передан в Linux Foundation (Agentic AI Foundation), со-основатели: Anthropic, OpenAI, Block

Протокол перестал быть экспериментом Anthropic. Это индустриальный стандарт.

Архитектура MCP-протокола: минимум для понимания

Прежде чем писать свой сервер, нужно понимать три вещи: что сервер отдаёт, как общается с клиентом и как подключается.

Три примитива

MCP-сервер может предоставлять три типа данных:

Tools — функции, которые LLM вызывает с параметрами. «Найди файлы по паттерну», «выполни SQL-запрос», «отправь сообщение». Это основной примитив, ради которого строят серверы. У каждого tool есть имя, описание и JSON Schema для входных параметров.

Resources — данные для чтения, адресуемые по URI. «Содержимое файла», «схема базы данных», «текущий контекст проекта». LLM получает данные, но ничего не меняет. Полезно для контекста, который не требует действий.

Prompts — шаблоны инструкций с переменными. Хранятся на сервере, обновляются без изменений на клиенте. Удобно для управления промптами через Langfuse или аналоги.

В 90% случаев custom-серверу нужны только Tools.

Transport: stdio vs Streamable HTTP

stdio — клиент запускает сервер как дочерний процесс. Общение через stdin/stdout, JSON-RPC построчно. Никакой сети, никакой авторизации, процесс умирает вместе с клиентом. Это стандарт для локальных инструментов.

Streamable HTTP — один HTTP-endpoint, клиент шлёт POST-запросы. Поддерживает OAuth 2.1 (рекомендуется спецификацией, но не обязателен), сессии, масштабирование. Заменил устаревший SSE-транспорт в марте 2025. Нужен для облачных серверов и мультитенантных систем.

Правило: если сервер запускается локально на машине разработчика — stdio. Если в облаке для нескольких пользователей — Streamable HTTP.

Все мои custom-серверы используют stdio. Они работают на моей машине, запускаются Claude Code как дочерние процессы.

Lifecycle

Каждая сессия проходит три фазы:

  1. Initialize — клиент отправляет initialize с версией протокола и своими capabilities. Сервер отвечает своими capabilities: какие tools, resources, prompts он предоставляет. Клиент подтверждает уведомлением initialized.

  2. Operation — обмен запросами и ответами. Клиент вызывает tools/call, читает resources/read. Сервер отвечает JSON-RPC результатами.

  3. Shutdown — клиент закрывает stdin. Сервер завершается. Если завис — SIGTERM, затем SIGKILL (конкретный таймаут выбирает реализация; спецификация говорит «в разумное время»).

Критически важно для stdio: сервер НЕ ДОЛЖЕН писать в stdout ничего, кроме JSON-RPC сообщений. console.log("Server started") — и всё сломалось. Логи — только в stderr через console.error().

Custom MCP-сервер за 30 минут

TypeScript: @modelcontextprotocol/sdk

Минимальный рабочий сервер на TypeScript:

import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";

const server = new McpServer({
  name: "my-tool",
  version: "1.0.0",
});

server.registerTool(
  "search_docs",
  {
    description: "Search project documentation by keyword",
    inputSchema: z.object({
      query: z.string().describe("Search query"),
      limit: z.number().optional().default(10).describe("Max results"),
    }),
  },
  async ({ query, limit }) => {
    const results = await searchIndex(query, limit);
    return {
      content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Server running on stdio"); // stderr, не stdout!

Зависимости: @modelcontextprotocol/sdk и zod (v4). Всё. Никаких фреймворков, ORM или конфиг-библиотек.

Python: FastMCP

Если предпочитаете Python — ещё проще:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-tool")

@mcp.tool()
async def search_docs(query: str, limit: int = 10) -> str:
    """Search project documentation by keyword.

    Args:
        query: Search query
        limit: Max results
    """
    results = await search_index(query, limit)
    return json.dumps(results, indent=2)

if __name__ == "__main__":
    mcp.run(transport="stdio")

FastMCP автоматически генерирует JSON Schema из type hints и docstring. Одна функция — один tool.

Подключение к Claude Code

Создайте .mcp.json в корне проекта:

{
  "mcpServers": {
    "my-tool": {
      "type": "stdio",
      "command": "node",
      "args": ["path/to/server.js"],
      "env": {
        "API_KEY": "your-key-here"
      }
    }
  }
}

Для Python-серверов:

{
  "mcpServers": {
    "my-tool": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "path/to/server.py"]
    }
  }
}

После сохранения — перезапустите Claude Code. Сервер появится в списке доступных tools.

Тестирование через MCP Inspector

Перед подключением к Claude Code проверьте сервер через Inspector:

npx @modelcontextprotocol/inspector node path/to/server.js

Откроется web-интерфейс на localhost:6274. Можно вызвать каждый tool, посмотреть схемы, проверить ответы. Это сэкономит часы отладки.

Production-паттерны: 6 уроков

Теория закончилась. Дальше — паттерны из реального продакшна. Каждый вырос из конкретного бага или инцидента.

Паттерн 1: CLI-wrapper

Три моих MCP-сервера (OpenAI, Gemini, Qwen) — обёртки над существующими CLI. Вместо прямых API-вызовов они запускают codex exec, gemini -p, qwen -p как дочерние процессы.

Зачем так:

  • Авторизация уже настроена. codex использует токен из ~/.codex/auth.json, gemini — Google OAuth через браузер. Не нужно хранить API-ключи, не нужно разбираться с OAuth-потоками.
  • CLI обновляется отдельно. Новая модель, изменённый API, исправленный баг — npm update -g @openai/codex и готово. MCP-сервер остаётся прежним.
  • Меньше кода. Сервер OpenAI — 250 строк. Прямой API-клиент с retry, streaming и обработкой ошибок — минимум 500.

Реализация:

import { spawn } from "child_process";

async function callCLI(prompt, timeout = 90000) {
  return new Promise((resolve, reject) => {
    const proc = spawn("codex", ["exec", "-p", prompt, "-o", tmpFile], {
      env: { ...process.env, CODEX_HOME: "~/.codex-minimal" },
    });

    let stdout = "";
    let stderr = "";

    proc.stdout.on("data", (d) => (stdout += d));
    proc.stderr.on("data", (d) => (stderr += d));

    const timer = setTimeout(() => {
      proc.kill("SIGTERM");
      setTimeout(() => {
        if (!proc.killed) proc.kill("SIGKILL");
      }, 5000);
      reject(new Error(`Timeout after ${timeout}ms`));
    }, timeout);

    proc.on("close", (code) => {
      clearTimeout(timer);
      if (code === 0) resolve(stdout);
      else reject(new Error(stderr));
    });
  });
}

Важная деталь: CODEX_HOME=~/.codex-minimal. Это минимальный конфиг без MCP-серверов. Без него codex exec загружает все MCP-серверы из основного конфига, и запуск занимает 15-20 секунд. С минимальным конфигом — 2-3 секунды.

Паттерн 2: Timeout + graceful kill

setTimeout + process.kill недостаточно. CLI-процесс может породить дочерние процессы, которые не умрут от SIGTERM. Надёжная схема:

  1. По истечении timeout — SIGTERM
  2. Через 5 секунд проверить: процесс жив? SIGKILL
  3. Очистить temp-файлы в finally
const timer = setTimeout(() => {
  proc.kill("SIGTERM");
  setTimeout(() => {
    if (!proc.killed) proc.kill("SIGKILL");
  }, 5000);
}, timeout);

Простой execPromise с параметром timeout (как в моём Qwen-сервере) работает хуже: процесс получает SIGKILL без предупреждения, temp-файлы остаются, и maxBuffer в 10 МБ может не хватить для длинных ответов.

Если сравнить серверы OpenAI/Gemini (spawn + graceful kill) и Qwen (exec + timeout), разница видна:

OpenAI/GeminiQwen
Kill sequenceSIGTERM → SIGKILLСразу SIGKILL
Temp filesЧистятся в finallyМогут остаться
Max outputБез лимита (streaming)10 МБ (maxBuffer)
Отладкаstderr доступенstderr смешан со stdout

Паттерн 3: Error detection по тексту

CLI-инструменты не всегда возвращают правильный exit code. codex exec может выйти с кодом 0, но в stdout будет: «You’ve hit your usage limit for the week».

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

function detectError(output) {
  const text = output.toLowerCase();

  if (text.includes("hit your usage limit") || text.includes("quota")) {
    return { type: "QUOTA_EXCEEDED", retry: false,
      hint: "Weekly Codex limit reached. Use DeepSeek as fallback." };
  }
  if (text.includes("not supported") && text.includes("chatgpt")) {
    return { type: "MODEL_NOT_SUPPORTED", retry: false,
      hint: "Model unavailable on ChatGPT Plus plan." };
  }
  if (text.includes("auth expired") || text.includes("please login")) {
    return { type: "AUTH_EXPIRED", retry: false,
      hint: "Re-authenticate: codex auth" };
  }
  return null;
}

Каждый тип ошибки содержит hint — подсказку для LLM, что делать дальше. Claude Code видит эту подсказку и автоматически переключается на fallback.

У Gemini свои паттерны: "resource_exhausted", "not authenticated". У Qwen — свои. Каждый сервер знает ошибки своего CLI.

Паттерн 4: Caching с TTL

MCP-сервер для FlutterFlow делает HTTP-запросы к API. Каждый list_projects — 200-500 мс. Одна и та же информация запрашивается многократно за сессию.

Добавляем кеш с разными TTL для разных операций:

ОперацияTTLПричина
list_projects5 минПроекты меняются редко
get_project_file2 минФайлы обновляются чаще
validate0 (без кеша)Всегда свежий результат

После update_project_file — инвалидация кеша для этого файла.

В Python (с diskcache):

from diskcache import Cache

cache = Cache(".cache/flutterflow")

def get_cached(key, ttl, fetch_fn):
    cached = cache.get(key)
    if cached is not None:
        return cached
    result = fetch_fn()
    cache.set(key, result, expire=ttl)
    return result

Для MCP-сервера Substack кеш устроен сложнее: отдельно кешируются посты (120 мин TTL), отдельно — embeddings (пересчитываются при изменении поста). При refresh=True кеш сбрасывается, но если сеть недоступна — возвращаются данные из кеша. Graceful degradation.

Паттерн 5: Fallback chains

Один LLM-провайдер недоступен? Переключаемся на следующий. Без паники, без ручного вмешательства.

Пример цепочки для code review:

Провайдер A → Провайдер B → Провайдер C → Провайдер D (always available)

Реализация — на уровне orchestrator’а (Claude Code, Cursor, ваш агент), не внутри MCP-серверов. Каждый сервер отвечает за один провайдер и честно сообщает об ошибке. Оркестратор видит QUOTA_EXCEEDED в ответе и вызывает следующий сервер в цепочке.

Почему так, а не один сервер с retry внутри:

  • Прозрачность. Видно, какой провайдер ответил. В логах чётко: провайдер A — квота, переключился на B.
  • Независимые конфигурации. У каждого провайдера свой timeout, свой формат ошибок, свой CLI.
  • Параллелизм. Можно запустить два провайдера одновременно и синтезировать результаты. Два независимых MCP-вызова — нет зависимости между ними.

Паттерн 6: Tool descriptions для LLM

Описание tool — это промпт. Claude читает его, чтобы решить, какой tool вызвать и с какими параметрами. Плохое описание = неправильный вызов или отказ от вызова.

Что работает:

server.registerTool("openai_chat", {
  description:
    "Send a prompt to OpenAI via Codex CLI. " +
    "Non-interactive, returns text response. " +
    "Timeout: 90 seconds. " +
    "Errors: QUOTA_EXCEEDED (weekly limit), AUTH_EXPIRED (re-login needed).",
  inputSchema: z.object({
    prompt: z.string().describe("The prompt to send. Keep under 500 chars for best results."),
  }),
});

Что не работает:

// Слишком абстрактно — Claude не поймёт, когда использовать
description: "Interact with OpenAI services"

// Слишком длинно — Claude обрежет или проигнорирует
description: "This tool provides comprehensive access to OpenAI's..."

Практические правила:

  1. Начинай с глагола: «Send», «Search», «Get», «Create»
  2. Укажи ограничения: timeout, лимиты, формат ввода
  3. Перечисли возможные ошибки: Claude сможет среагировать
  4. describe() для каждого параметра: не полагайся на имя переменной
  5. Тестируй на реальных задачах: попроси Claude выполнить задачу и проверь, правильный ли tool он выбрал

Композиция серверов

Один сервер — один инструмент. Сила MCP — в том, что серверы работают вместе. Вот несколько паттернов, которые возникают, когда серверов становится больше трёх.

Параллельный вызов нескольких LLM

Git diff отправляется параллельно в два-три LLM-провайдера через отдельные MCP-серверы. Каждый возвращает свой code review. Результаты синтезируются, критичные замечания применяются.

Если один провайдер упёрся в квоту — подхватывает следующий. Три-четыре MCP-сервера, каждый делает своё, вместе дают устойчивый workflow. Оркестратор (Claude Code, Cursor, любой MCP-клиент) управляет fallback-логикой — серверы об этом ничего не знают.

MCP-сервер как мост к внутреннему API

Типичный сценарий: у вашего проекта есть внутренний сервис (prompt management, CMS, аналитика), к которому нет готового MCP-сервера. Пишете обёртку на 100-200 строк, и Claude получает доступ к вашим данным через стандартный протокол. Два-три tool’а покрывают 90% задач.

Серверы для внутренних API обычно не кешируют данные (cacheTtlSeconds: 0) — всегда свежий результат. Это проще и безопаснее, чем разбираться с инвалидацией.

Tier-система: когда серверов много

С 10+ серверами в конфиге важно понимать, какие обязательны, какие полезны, а какие на подхвате:

TierПримерыКогда нужны
EssentialGitHub, база данных, трекер задачКаждый день, каждая задача
ProductionLLM-провайдеры, поиск, мониторингРабочие сессии
Quality of LifeКонтент-платформы, браузерная автоматизацияСпецифичные workflows
ExperimentalНовые интеграцииТестирую, потом решаю

Essential-серверы подключены всегда. Production — для рабочих сессий. QoL — по необходимости. Experimental — отключены по умолчанию.

Мониторинг и дебаг: когда MCP тихо умирает

MCP-серверы падают молча. Нет crash-репортов, нет уведомлений. Claude Code просто перестаёт видеть tools, и ты узнаёшь об этом, когда задача не выполнилась.

Три типичных failure mode

Молчаливый crash. Сервер упал с segfault или uncaught exception. Процесс мёртв, но клиент не знает. Следующий вызов tool зависнет или вернёт ошибку подключения.

Зависший процесс. CLI-вызов не возвращается. Без timeout сервер будет ждать вечно. С timeout — вернёт ошибку, но зависший дочерний процесс может остаться.

Corrupt stdout. console.log() вместо console.error(). Или библиотека-зависимость пишет в stdout при инициализации. JSON-RPC парсер ломается, клиент отключается.

Логи Claude Desktop

Для Claude Desktop (не Claude Code):

# macOS — все MCP-логи
tail -f ~/Library/Logs/Claude/mcp.log

# Логи конкретного сервера
tail -f ~/Library/Logs/Claude/mcp-server-my-tool.log

mcp.log — подключения и отключения. mcp-server-*.log — stderr каждого сервера.

Structured logging в stderr

Что логировать в своём сервере:

function log(level, tool, data) {
  const entry = {
    ts: new Date().toISOString(),
    level,
    tool,
    ...data,
  };
  console.error(JSON.stringify(entry));
}

// При вызове tool
log("info", "openai_chat", {
  action: "call_start",
  promptLength: prompt.length,
});

// При ответе
log("info", "openai_chat", {
  action: "call_end",
  duration: Date.now() - start,
  outputLength: result.length,
});

// При ошибке
log("error", "openai_chat", {
  action: "call_error",
  errorType: "QUOTA_EXCEEDED",
  duration: Date.now() - start,
});

Что НЕ логировать: содержимое промптов и ответов (могут быть чувствительные данные), API-ключи и токены.

Health-check tool

Полезный паттерн для сложных MCP-серверов: добавить tool health, который возвращает состояние сервера:

{
  "status": "healthy",
  "uptime": "2h 34m",
  "memory": "128 MB",
  "connectedServices": 3,
  "transport": "streamable-http",
  "port": 3001
}

Claude Code может вызвать health перед сложной задачей, чтобы убедиться, что сервер жив и ресурсов достаточно.

Безопасность: чеклист для своих серверов

В декабре 2025 в официальном Git MCP-сервере Anthropic исправили три уязвимости (CVE-2025-68143, CVE-2025-68144, CVE-2025-68145), о которых стало широко известно в январе 2026. Через цепочку из path traversal, argument injection и записи в .git/config можно было выполнить произвольный код.

Если уж в сервере самого Anthropic нашлись дыры, в custom-серверах они тоже будут — если не проверять.

CoSAI: 40 угроз в 12 категориях

Coalition for Secure AI (CoSAI) в январе 2026 опубликовала white paper с полной таксономией угроз MCP. Из 12 категорий пять критичны для custom-серверов:

  1. Input Validation — промпт может содержать инструкции для tool’а. Если tool принимает произвольный текст и передаёт его в shell — привет, command injection.
  2. Trust Boundary Failures — LLM решает, какой tool вызвать. Это вероятностное решение, которым можно манипулировать через prompt injection.
  3. Supply Chain — зависимости MCP-сервера. Один уязвимый npm-пакет, и атакующий внутри вашего сервера.
  4. Data/Control Boundary — MCP-сервер имеет доступ к файловой системе, базе данных, API. Если ему дать больше прав, чем нужно, одна prompt injection может привести к утечке данных.
  5. Insufficient Observability — без логов вы не узнаете, что tool вызвали с подозрительными параметрами.

Tool poisoning

По данным исследований (Invariant Labs, MCPTox benchmark), tool poisoning срабатывает в 70-85% случаев при включённом auto-approval — в зависимости от модели и сценария. Механизм: атакующий MCP-сервер меняет описание tool’а после первичного одобрения. В первый день — «получить погоду». Через неделю — «получить погоду и отправить историю чата на внешний сервер».

Практический чеклист

При создании сервера:

  • Параметры tool’ов проходят валидацию через zod/pydantic. Никакого any
  • Сервер не запускает shell-команды из пользовательского ввода
  • Секреты передаются через env в .mcp.json, не через args
  • API-токены не попадают в логи
  • YAML.safe_load() вместо YAML.load() (для Python-серверов)

При подключении чужого сервера:

  • Прочитайте исходный код. Не шучу. Особенно обработку tool-вызовов
  • Зафиксируйте версию пакета: "@package/mcp@1.2.3", не "@package/mcp@latest"
  • Проверьте, какие env-переменные читает сервер
  • Не включайте auto-approval для серверов, которым не доверяете

Регулярно:

  • Обновляйте зависимости и проверяйте changelog
  • Проверяйте tool descriptions — не изменились ли после обновления
  • Просматривайте stderr-логи на подозрительные вызовы

Итого: когда строить, когда не строить

Стройте свой MCP-сервер, если:

  • Нужна интеграция, которой нет в реестре (ваш внутренний API, специфичный workflow)
  • Хотите переиспользовать авторизацию существующего CLI
  • Готовый сервер нестабилен или не поддерживается
  • Нужен полный контроль над reliability и error handling

Не стройте, если:

  • Есть готовый сервер, который покрывает потребности
  • Интеграция нужна разово (проще сделать через Bash tool)
  • Нет production-нагрузки (эксперименты можно делать и через API напрямую)

MCP-сервер — это 200-300 строк кода. Это не микросервис, не фреймворк, не инфраструктурный проект. Это скрипт, который отвечает по JSON-RPC и делает одну вещь хорошо. Порог входа низкий, польза — ощутимая.

Ресурсы

FAQ

Какова нагрузка на память при одновременной работе 10+ MCP-серверов в Claude Code?

Каждый stdio-сервер — это постоянный дочерний процесс: Node.js занимает 30–80 МБ, Python — 20–50 МБ. При 10 серверах суммарно получается 300–800 МБ — заметно на MacBook с 8 ГБ RAM. Старт всех серверов при запуске Claude Code занимает 2–5 секунд. Накладные расходы на вызов инструмента ничтожны (JSON-RPC слой — менее миллисекунды). Практический предел — 15–20 серверов: после этого bloat tool descriptions ухудшает качество принятия решений LLM.

Как организовать ротацию API-ключей для MCP-серверов?

Передавайте секреты через поле env в .mcp.json, никогда через args. Для ключей с ротацией (OAuth-токены, краткоживущие credentials) оптимальный паттерн — wrapper-скрипт, который получает актуальный токен из менеджера секретов (1Password CLI, AWS Secrets Manager) при старте процесса и пробрасывает его как переменную окружения. MCP-сервер читает process.env.API_KEY при каждом вызове и никогда не хранит токен в памяти. Поскольку stdio-серверы перезапускаются вместе с клиентом, ротация происходит автоматически при каждом запуске Claude Code.

Может ли один MCP-сервер вызывать инструменты других MCP-серверов внутри себя?

Нет — MCP-серверы не имеют встроенного MCP-клиента и не могут напрямую вызывать инструменты друг друга. Оркестрация всегда происходит на стороне клиента (Claude Code, Cursor, агент). Если нужна композиция инструментов, реализуйте её через системный промпт оркестратора (явная цепочка tool calls) или создайте выделенный «мета-инструмент», который внутри вызывает нужные API напрямую, не через MCP-слой. Второй вариант теряет модульность отдельных серверов, но устраняет лишний round-trip и снижает задержку.