# MCP-серверы в production: настройка, разработка и кастомные серверы

> Как создать свой MCP-сервер и запустить в production. Паттерны error handling, fallback chains, мониторинг, безопасность. Гайд для разработчиков с реальным опытом.
> Author: Roman Belov · Published: 2026-02-21 · Source: https://futurecraft.pro/ru/blog/mcp-production-custom-servers/

**MCP (Model Context Protocol)** — открытый протокол для подключения [AI-агентов](/ru/blog/multi-agent-architecture/) к внешним инструментам: базам данных, API, поисковым системам, мониторингу. MCP-сервер — это программа, которая реализует этот протокол и даёт AI-агенту доступ к конкретному инструменту. Если вы только начинаете работать с MCP — начните с раздела [«Архитектура MCP-протокола»](#архитектура-mcp-протокола-минимум-для-понимания) ниже.

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

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

Дальше начинается продакшн. Серверы молча падают, процессы зависают, квоты исчерпываются без предупреждения. В моём проекте [JourneyBay](/ru/blog/journeybay-2-0-launch/) работает стек из 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](/ru/blog/claude-code-complete-guide/) как дочерние процессы.

### 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:

```javascript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
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: {
      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 — ещё проще:

```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` в корне проекта:

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

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

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

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

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

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

```bash
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.

Реализация:

```javascript
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`

```javascript
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».

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

```javascript
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`):

```python
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 вызвать и с какими параметрами. Плохое описание = неправильный вызов или отказ от вызова.

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

```javascript
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: {
    prompt: z.string().describe("The prompt to send. Keep under 500 chars for best results."),
  },
});
```

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

```javascript
// Слишком абстрактно — 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. Результаты синтезируются, критичные замечания применяются.

Если один провайдер упёрся в квоту — подхватывает следующий (тот же принцип, что и в [мультипровайдерной LLM-архитектуре](/ru/blog/multi-provider-llm-architecture/)). Три-четыре 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):

```bash
# 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

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

```javascript
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`, который возвращает состояние сервера:

```json
{
  "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](https://www.coalitionforsecureai.org/securing-the-ai-agent-revolution-a-practical-guide-to-mcp-security/) с полной таксономией угроз 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 и делает одну вещь хорошо. Порог входа низкий, польза — ощутимая.

### Ресурсы

- [MCP спецификация (2025-11-25)](https://modelcontextprotocol.io/specification/2025-11-25) — актуальная версия протокола
- [TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) — `@modelcontextprotocol/sdk`
- [Python SDK](https://github.com/modelcontextprotocol/python-sdk) — `mcp[cli]` с FastMCP
- [MCP Inspector](https://github.com/modelcontextprotocol/inspector) — отладка серверов
- [CoSAI MCP Security Whitepaper](https://www.coalitionforsecureai.org/securing-the-ai-agent-revolution-a-practical-guide-to-mcp-security/) — таксономия угроз
- [Timeline of MCP Security Breaches](https://authzed.com/blog/timeline-mcp-breaches) — хронология уязвимостей

---

*Нужна помощь с разработкой MCP-серверов или AI-интеграциями? Я помогаю стартапам внедрять AI-решения и строить продукты — [belov.works](https://belov.works).*
