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
Каждая сессия проходит три фазы:
-
Initialize — клиент отправляет
initializeс версией протокола и своими capabilities. Сервер отвечает своими capabilities: какие tools, resources, prompts он предоставляет. Клиент подтверждает уведомлениемinitialized. -
Operation — обмен запросами и ответами. Клиент вызывает
tools/call, читаетresources/read. Сервер отвечает JSON-RPC результатами. -
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. Надёжная схема:
- По истечении timeout — SIGTERM
- Через 5 секунд проверить: процесс жив? SIGKILL
- Очистить 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/Gemini | Qwen | |
|---|---|---|
| Kill sequence | SIGTERM → 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_projects | 5 мин | Проекты меняются редко |
get_project_file | 2 мин | Файлы обновляются чаще |
validate | 0 (без кеша) | Всегда свежий результат |
После 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..."
Практические правила:
- Начинай с глагола: «Send», «Search», «Get», «Create»
- Укажи ограничения: timeout, лимиты, формат ввода
- Перечисли возможные ошибки: Claude сможет среагировать
- describe() для каждого параметра: не полагайся на имя переменной
- Тестируй на реальных задачах: попроси 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 | Примеры | Когда нужны |
|---|---|---|
| Essential | GitHub, база данных, трекер задач | Каждый день, каждая задача |
| Production | LLM-провайдеры, поиск, мониторинг | Рабочие сессии |
| 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-серверов:
- Input Validation — промпт может содержать инструкции для tool’а. Если tool принимает произвольный текст и передаёт его в shell — привет, command injection.
- Trust Boundary Failures — LLM решает, какой tool вызвать. Это вероятностное решение, которым можно манипулировать через prompt injection.
- Supply Chain — зависимости MCP-сервера. Один уязвимый npm-пакет, и атакующий внутри вашего сервера.
- Data/Control Boundary — MCP-сервер имеет доступ к файловой системе, базе данных, API. Если ему дать больше прав, чем нужно, одна prompt injection может привести к утечке данных.
- 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 и делает одну вещь хорошо. Порог входа низкий, польза — ощутимая.
Ресурсы
- MCP спецификация (2025-11-25) — актуальная версия протокола
- TypeScript SDK —
@modelcontextprotocol/sdk - Python SDK —
mcp[cli]с FastMCP - MCP Inspector — отладка серверов
- CoSAI MCP Security Whitepaper — таксономия угроз
- Timeline of MCP Security Breaches — хронология уязвимостей
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 и снижает задержку.