Создание собственного MCP-сервера

Подключить готовый сервер — несколько команд. Но рано или поздно нужный инструмент окажется только внутри корпоративной системы, старого REST API или самописного скрипта. Именно тогда создание собственного MCP-сервера из опции превращается в необходимость.

Эта статья — путь от чистого листа до рабочего сервера. Мы разберём FastMCP (Python), официальный TypeScript SDK, отладку через MCP Inspector и паттерн code execution, который меняет архитектуру агентных систем.

Когда нужен собственный сервер

Готовых серверов для GitHub, Postgres, Slack и десятков других сервисов уже хватает — их мы разбирали в следующей статье. Собственный сервер нужен когда:

  • Система внутренняя (корпоративный API, легаси-бэкенд, внутренняя БД)
  • Готовый сервер существует, но не даёт нужный уровень контроля (своя авторизация, кастомная трансформация данных)
  • Нужна бизнес-логика на стороне сервера — агрегация из нескольких источников, кэширование, фильтрация чувствительных полей

Выбор стека: FastMCP или TypeScript SDK

Для Python-проектов выбор очевиден — FastMCP. Это высокоуровневый фреймворк, который в 2024 году вошёл в официальный Python MCP SDK и сегодня, по оценкам авторов, лежит в основе ~70% всех MCP-серверов. Декоратором @mcp.tool вы получаете автоматическую генерацию JSON Schema из аннотаций типов, валидацию параметров и документацию — без единой строки бойлерплейта.

Для TypeScript-проектов — официальный @modelcontextprotocol/sdk от Anthropic. Чуть больше кода вручную, зато полный контроль над протоколом и нативная интеграция с Node.js-экосистемой.

flowchart TD A[Идея интеграции] --> B{Существует\nготовый сервер?} B -- Да --> C[claude mcp add] B -- Нет --> D[Создать сервер] D --> E{Стек?} E -- Python --> F[FastMCP\npip install fastmcp] E -- TypeScript --> G[@modelcontextprotocol/sdk\nnpm install] F --> H[Определить @mcp.tool\n@mcp.resource @mcp.prompt] G --> H2[Определить registerTool\nс Zod-схемой] H --> I[MCP Inspector\nnpx @modelcontextprotocol/inspector] H2 --> I I -- Ошибки --> H I -- OK --> J[claude mcp add\nили .mcp.json] J --> K[Сервер доступен в Claude Code]
flowchart TD
    A[Идея интеграции] --> B{Существует\nготовый сервер?}
    B -- Да --> C[claude mcp add]
    B -- Нет --> D[Создать сервер]
    D --> E{Стек?}
    E -- Python --> F[FastMCP\npip install fastmcp]
    E -- TypeScript --> G[@modelcontextprotocol/sdk\nnpm install]
    F --> H[Определить @mcp.tool\n@mcp.resource @mcp.prompt]
    G --> H2[Определить registerTool\nс Zod-схемой]
    H --> I[MCP Inspector\nnpx @modelcontextprotocol/inspector]
    H2 --> I
    I -- Ошибки --> H
    I -- OK --> J[claude mcp add\nили .mcp.json]
    J --> K[Сервер доступен в Claude Code]
Путь от идеи интеграции до рабочего MCP-сервера в Claude Code

FastMCP: от нуля до работающего сервера

Установка — одна строка:

pip install fastmcp
# или через uv (рекомендуется для изоляции)
uv add fastmcp

Минимальный сервер с одним инструментом:

from fastmcp import FastMCP

mcp = FastMCP("my-server")

@mcp.tool
def search_docs(query: str, limit: int = 10) -> list[dict]:
    """Ищет документы во внутренней базе знаний."""
    # Здесь реальная логика — запрос к БД, Elasticsearch и т.д.
    return [{"id": 1, "title": f"Результат для '{query}'", "score": 0.95}]

if __name__ == "__main__":
    mcp.run()  # stdio по умолчанию

Обратите внимание: query: str и limit: int = 10 — это всё, что нужно для генерации схемы. FastMCP извлечёт типы из аннотаций и docstring станет описанием инструмента для модели.

Check yourself
Посмотрите на эту сигнатуру: ```python @mcp.tool def get_report(period: str, include_drafts: bool = False) -> dict: """...""" ``` Какую JSON Schema сгенерирует FastMCP для параметров этого инструмента? Попробуйте набросать схему самостоятельно перед ответом.

#### Инструменты (tools)

Инструменты — основной примитив для действий. Правила хорошего инструмента:

  • Имя — глагол: search_, create_, update_, delete_
  • Docstring — что делает и когда использовать (это читает Claude, а не человек)
  • Типы — точные: str, int, list[str], Literal["asc", "desc"]
  • Возвращаемое значение — JSON-сериализуемое (dict, list, str, int)
from typing import Literal
from pydantic import Field

@mcp.tool
def query_database(
    sql: str = Field(description="SELECT-запрос. Только чтение."),
    timeout_ms: int = Field(default=5000, ge=100, le=30000)
) -> dict:
    """Выполняет SQL-запрос к аналитической БД (read-only).
    
    Используйте для получения агрегированных данных и отчётов.
    Не поддерживает INSERT/UPDATE/DELETE — только SELECT.
    """
    # Реализация
    return {"rows": [], "count": 0}

Field из Pydantic позволяет добавить описание к каждому параметру отдельно — это особенно важно для инструментов с неочевидной семантикой.

#### Ресурсы (resources)

Ресурсы — read-only данные, которые Claude может прочитать по URI. В отличие от инструментов, они не выполняют действия — только отдают контент.

import json

# Статический ресурс — фиксированный URI
@mcp.resource("config://app/settings")
def get_settings() -> str:
    """Текущие настройки приложения в формате JSON."""
    return json.dumps({"env": "production", "version": "2.1.0"})

# Динамический ресурс — параметр в URI
@mcp.resource("user://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
    """Профиль пользователя по ID."""
    # Запрос к базе
    return json.dumps({"id": user_id, "name": "Alice"})

Когда использовать ресурс вместо инструмента? Простое правило: если операция — чтение постоянно существующих данных (конфиг, профиль, документ), используйте ресурс. Если операция меняет состояние или требует параметров, влияющих на логику выполнения, — инструмент.

#### Промпты (prompts)

Промпты — шаблоны сообщений, которые сервер предлагает клиентам. Удобны для стандартизации запросов к вашей системе:

from fastmcp import FastMCP
from fastmcp.prompts import Message

@mcp.prompt
def analyze_ticket(ticket_id: str, language: str = "ru") -> list[Message]:
    """Шаблон для анализа тикета поддержки."""
    return [
        Message(role="user", content=f"Проанализируй тикет #{ticket_id} на языке {language}")
    ]
Check yourself
Нужно дать Claude доступ к списку всех проектов в вашей системе управления задачами. Что правильнее: ресурс или инструмент? Обоснуйте.

#### Запуск: stdio vs HTTP

# stdio — для локальных claude mcp add
mcp.run()  # или mcp.run(transport="stdio")

# HTTP — для облачного деплоя
mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)

Для подключения stdio-сервера к Claude Code:

claude mcp add --transport stdio my-server -- python /path/to/server.py

Для HTTP:

claude mcp add --transport http my-server http://localhost:8000/mcp

TypeScript SDK: альтернатива для Node.js-стека

Если проект уже на TypeScript, официальный SDK даёт нативную интеграцию:

npm install @modelcontextprotocol/sdk
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

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

server.registerTool(
  "fetch_metrics",
  {
    description: "Получить метрики сервиса за период",
    inputSchema: z.object({
      service: z.string().describe("Имя сервиса"),
      period: z.enum(["1h", "24h", "7d"]).default("24h")
    })
  },
  async ({ service, period }) => {
    // Логика запроса к Prometheus, Datadog и т.д.
    return { content: [{ type: "text", text: JSON.stringify({ service, p99: 45 }) }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Zod используется для валидации — та же экосистема, что в большинстве TypeScript-проектов. Схема генерируется автоматически из Zod-объекта.

MCP Inspector: отладка до подключения к Claude

Прежде чем подключать сервер к Claude Code, нужно убедиться, что он работает правильно. MCP Inspector — интерактивный отладчик, запускаемый через npx без установки:

# Для Python-сервера
npx @modelcontextprotocol/inspector uv run python server.py

# Для TypeScript-сервера
npx @modelcontextprotocol/inspector node dist/index.js

# Для PyPI-пакета
npx @modelcontextprotocol/inspector uvx my-mcp-server

Inspector открывает браузерный UI с несколькими вкладками:

  • Tools — список всех инструментов, их схемы, форма для ручного вызова и результат. Здесь проверяем правильность схем и поведение при edge cases
  • Resources — список ресурсов, возможность прочитать содержимое каждого
  • Prompts — список шаблонов, вызов с параметрами
  • Notifications — все логи сервера в реальном времени, включая ошибки протокола

Типичный workflow отладки:

1. Запустить Inspector с сервером

2. Открыть вкладку Tools и вызвать каждый инструмент с тестовыми данными

3. Проверить схему: правильные типы, описания, обязательность полей

4. Подать невалидные входные данные — убедиться, что сервер возвращает нормальные ошибки, а не крашится

5. Только потом подключать через claude mcp add

Инспектор особенно полезен при работе с ресурсами-шаблонами: можно напрямую передать user://alice/profile и увидеть, что отдаёт сервер.

Check yourself
Вы написали MCP-сервер и запустили Inspector. Инструмент `create_issue` вызывается без ошибок, но Claude Code не находит его при использовании. Какой первый шаг диагностики?

Code execution with MCP: другой уровень

Это архитектурный паттерн, который Anthropic описывает в отдельной статье. Суть: вместо того чтобы вызывать каждый MCP-инструмент по одному через агента, дать агенту инструмент выполнения кода — и пусть он сам пишет программы, которые оркеструют вызовы.

Стандартный подход (N вызовов инструментов):

[Claude] → tool_call: get_transcript(meeting_id) → [150k tokens ответа]
[Claude] → tool_call: create_salesforce_record(data) → ...

Code execution подход (2 вызова):

[Claude] → tool_call: execute_code("""
  import servers.google_drive as drive
  import servers.salesforce as sf
  
  transcript = drive.get_transcript(meeting_id)
  summary = transcript[:500]  # фильтрация ДО возврата в контекст
  sf.create_record(summary=summary, ...)
""")

Результат, описанный Anthropic: реальный воркфлоу с передачей транскрипта и записью в Salesforce сократил потребление токенов с ~150 000 до ~2 000 — экономия 98.7%. Промежуточные данные не покидают среду выполнения и не засоряют контекстное окно.

Для реализации MCP-сервер экспонирует инструмент execute_code, который принимает Python/TypeScript-строку и выполняет её в изолированной среде. Остальные серверы организованы как модули (папка ./servers/google-drive/, ./servers/salesforce/) с импортируемыми функциями. Агент может прогрессивно обнаруживать их — читать директории, загружать только нужные определения.

Паттерн подходит не везде: нужна изолированная среда выполнения (sandbox), явный контроль над тем, какой код разрешён, и серьёзная работа по безопасности. Но для внутренних корпоративных агентов, работающих с большими объёмами данных, — это принципиальное изменение модели.

Путь к production

Когда сервер проверен через Inspector и работает локально, следующие шаги:

Докеризация и деплой:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY server.py .
EXPOSE 8000
CMD ["python", "server.py"]

Коммандный доступ через .mcp.json:

{
  "mcpServers": {
    "my-server": {
      "type": "streamable-http",
      "url": "${MY_SERVER_URL:-http://localhost:8000}/mcp"
    }
  }
}

URL через переменную окружения — каждый разработчик подставляет свой адрес (локальный или staging), конфиг коммитится в git. Это тот же паттерн из предыдущей статьи.

Для аутентификации в production рекомендуется OAuth 2.0 (сервер реализует PKCE-флоу) или статические API-ключи через Authorization: Bearer ${TOKEN} в заголовках. FastMCP имеет встроенную поддержку обоих вариантов.

Логирование и мониторинг: MCP Inspector показывает логи в реальном времени при разработке. В production логируйте каждый вызов инструмента с параметрами и временем выполнения — это поможет отлаживать агентное поведение, которое иначе очень сложно воспроизвести.


Quick recall
Почему в FastMCP одних только аннотаций типов (`query: str, limit: int`) достаточно для генерации JSON Schema?
Quick recall
Почему для Python-проектов рекомендуется FastMCP, а не официальный TypeScript SDK?
Quick recall
Когда создание собственного MCP-сервера переходит из опции в необходимость?

See also