MCP-серверы в production: настройка, разработка и кастомные серверы
Что такое 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
MCP (Model Context Protocol) — открытый протокол для подключения AI-агентов к внешним инструментам: базам данных, API, поисковым системам, мониторингу. MCP-сервер — это программа, которая реализует этот протокол и даёт AI-агенту доступ к конкретному инструменту. Если вы только начинаете работать с MCP — начните с раздела «Архитектура MCP-протокола» ниже.
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 — хронология уязвимостей
Нужна помощь с разработкой MCP-серверов или AI-интеграциями? Я помогаю стартапам внедрять AI-решения и строить продукты — belov.works.
Часто задаваемые вопросы
Какова нагрузка на память при одновременной работе 10+ MCP-серверов в Claude Code?
Как организовать ротацию API-ключей для MCP-серверов?
env в .mcp.json, никогда через args. Для ключей с ротацией (OAuth-токены, краткоживущие credentials) оптимальный паттерн — wrapper-скрипт, который получает актуальный токен из менеджера секретов (1Password CLI, AWS Secrets Manager) при старте процесса и пробрасывает его как переменную окружения. MCP-сервер читает process.env.API_KEY при каждом вызове и никогда не хранит токен в памяти. Поскольку stdio-серверы перезапускаются вместе с клиентом, ротация происходит автоматически при каждом запуске Claude Code.