Главная Документация Создание сервера

Как создать свой MCP-сервер

За 15 минут — от пустой папки до сервера, который Claude может вызывать.

Эта страница адаптирована из официальной документации MCP — здесь упрощённая версия с фокусом на главное. Полный туториал и SDK для других языков:

modelcontextprotocol.io/docs/develop/build-server

Из чего состоит MCP-сервер

У MCP-сервера есть три типа «способностей». Большинство серверов реализует только первый — инструменты (tools). Остальные — опциональны.

Tools — инструменты

Функции, которые AI может вызвать (с разрешения пользователя). Например: «получить погоду в Москве», «прочитать строку из базы», «отправить запрос к API».

Resources — ресурсы

Файловидные данные, которые клиент может прочитать сам: содержимое файла, результат API-запроса, схема таблицы. Используются реже инструментов.

Prompts — шаблоны

Заранее написанные промпт-шаблоны для типовых сценариев — пользователь выбирает их вручную в клиенте.

В этом руководстве мы сделаем простой сервер с одним инструментом. Этого достаточно, чтобы Claude увидел и вызвал вашу логику.

Что вам понадобится

  • Python 3.10+ (если выберете путь Python) и SDK mcp ≥ 1.2.0
  • Node.js 16+ (если выберете путь TypeScript) и пакет @modelcontextprotocol/sdk
  • Установленный Claude Desktop — чтобы протестировать сервер локально

Сервер на Python (рекомендуется для старта)

Будем использовать FastMCP — обёртку над SDK, которая автоматически генерирует схему инструментов из типов и докстрингов Python. Никакой ручной регистрации.

1. Установка зависимостей (используем uv — быстрый менеджер пакетов)

# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

uv init weather && cd weather
uv venv && source .venv/bin/activate
uv add "mcp[cli]" httpx

2. Минимальный сервер weather.py

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

@mcp.tool()
def hello(name: str) -> str:
    """Поприветствовать пользователя по имени."""
    return f"Привет, {name}!"

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

Декоратор @mcp.tool() превращает любую Python-функцию в MCP-инструмент. Имя функции становится именем инструмента, docstring — описанием, аннотации типов — схемой входных параметров.

3. Запуск

uv run weather.py

Сервер слушает stdio и ждёт соединения от MCP-клиента. Если видите окно без ошибок — всё в порядке.

Сервер на TypeScript

Альтернативный путь — официальный SDK на TypeScript. Удобно, если у вас уже npm-проект или вы хотите завернуть существующий REST API.

1. Установка

mkdir weather && cd weather
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
mkdir src && touch src/index.ts

2. Минимальный сервер src/index.ts

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: "weather", version: "1.0.0" });

server.registerTool(
    "hello",
    {
        description: "Поприветствовать пользователя по имени",
        inputSchema: { name: z.string().describe("Имя пользователя") }
    },
    async ({ name }) => ({
        content: [{ type: "text", text: `Привет, ${name}!` }]
    })
);

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

registerTool принимает имя, схему параметров через Zod и async-обработчик. Ответ должен быть в формате { content: [{ type: "text", text: "..." }] }.

3. Сборка и запуск

npx tsc
node build/index.js

Также понадобится tsconfig.json с target ES2022 и module Node16 — полный пример в репозитории SDK.

Подводный камень: логирование

В транспорте stdio любая запись в stdout сломает протокол JSON-RPC. Это самая частая причина «сервер не отвечает».

  • В Python используйте print(..., file=sys.stderr) или модуль logging. Обычный print() — запрещён.
  • В TypeScript используйте console.error() — он пишет в stderr. console.log() сломает сервер.
  • Для HTTP-транспорта (как у серверов MCPBay) это правило не действует — stdout безопасен.

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

Откройте конфиг (пути см. в разделе «Подключение»). Добавьте свой сервер в секцию mcpServers — для локальных stdio-серверов запись отличается от удалённых.

Запись для Python-сервера

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "/абсолютный/путь/к/weather",
        "run",
        "weather.py"
      ]
    }
  }
}

Запись для TypeScript-сервера

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/абсолютный/путь/к/weather/build/index.js"]
    }
  }
}

Замените абсолютный путь на свой. На macOS/Linux получите его командой pwd, на Windows — cd. На Windows используйте двойные обратные слэши или прямые слэши.

После сохранения полностью перезапустите Claude Desktop. Инструмент появится в выпадающем списке под полем ввода.

Что дальше

HTTP-транспорт

Для публичных серверов используйте Streamable HTTP вместо stdio. Это то, что использует MCPBay — сервер живёт по URL, не требует локальной установки клиенту.

MCP Inspector

Официальный отладчик: npx @modelcontextprotocol/inspector. Запускает GUI, где видно все запросы/ответы сервера.

SDK для других языков

Кроме Python и TypeScript есть официальные SDK для Java (Spring AI), Kotlin, C# (Microsoft.Extensions.AI), Rust, Go и Swift. Список и ссылки — в оригинальной документации.

Полная документация

Концепции, спецификация протокола, расширенные возможности (sampling, OAuth, completion):

modelcontextprotocol.io

Как опубликовать свой MCP в каталоге

Публикация — это когда ваш сервер появляется в публичном каталоге MCPBay, чтобы другие люди могли найти его и подключить в своём AI-клиенте — Claude, Cursor, ChatGPT.

Это не то же самое, что кнопка «Добавить MCP сервер»

В каталоге кнопка «Добавить MCP сервер» просто сохраняет сервер в ваш личный кабинет для собственного использования — его никто, кроме вас, не видит. Публикация — это отдельное действие: вы подаёте сервер на модерацию, и после неё его видят все.

Перед публикацией

Есть два пути. Свой хостинг: рабочий удалённый MCP-сервер, уже запущенный и доступный по публичному HTTPS-адресу — каталог ссылается на ваш эндпоинт. Или хостинг MCPBay: собственная инфраструктура не нужна — при подаче укажите GitHub-репозиторий с Dockerfile, и платформа соберёт и запустит сервер сама (см. раздел «Хостинг на MCPBay» ниже).

Как подать заявку

  1. Откройте форму «Добавить сервер».
  2. Заполните название, URL сервера (ваш MCP-эндпоинт), категорию, короткое описание и подробное описание, а также логотип. Логотип показывается на карточке в каталоге и в кабинетах пользователей.
  3. Хотите хостинг MCPBay? Отметьте «Захостить на mcpbay.pro», укажите ссылку на GitHub-репозиторий в поле «Исходный код» и привяжите GitHub — приложение MCPBay Hosting должно быть установлено на этот репозиторий. Поле «URL сервера» при этом можно оставить пустым — адрес выдаст платформа.
  4. Отправьте заявку. Она уходит на модерацию, и после одобрения сервер публикуется в публичном каталоге.
Открыть форму подачи

Советы для хорошей карточки

  • Понятное название и краткое описание того, что делает сервер.
  • Правильная категория — чтобы те, кто листает каталог, нашли сервер там, где ожидают.
  • Рабочий публичный URL (для серверов на своём хостинге) — модераторы открывают его и проверяют, что сервер отвечает.
  • Логотип — серверы без него показываются с нейтральной заглушкой.

Если вашему MCP нужны личные ключи или токены пользователя — понадобится манифест полей. Смотрите руководство по манифесту полей ниже — там описан формат и как его подключить.

Хотите, чтобы пользователи видели в кабинете своё использование вашего сервера? Добавьте интеграцию «Моё использование» ниже — связывание аккаунта и пер-юзерные события использования.

Хостинг на MCPBay

Не хотите держать собственный сервер? Включите хостинг при подаче заявки: платформа соберёт ваш MCP из GitHub-репозитория и запустит его на изолированной инфраструктуре. Сервер получит адрес вида https://имя.run.mcpbay.pro/mcp, а перед ним автоматически встанет OAuth-гейт платформы.

Требования к серверу

  • GitHub-репозиторий с исходниками и Dockerfile в корне (ветка main по умолчанию).
  • Привязанный GitHub-аккаунт и установленное приложение MCPBay Hosting на этот репозиторий — так платформа подтверждает владение и получает доступ к коду (приватные репозитории тоже работают).
  • CMD или ENTRYPOINT в Dockerfile — в exec-форме (JSON-массив, например CMD ["node","build/index.js"]), не в shell-форме.
  • Сервер слушает порт из переменной окружения PORT.
  • MCP-эндпоинт отвечает по пути /mcp (Streamable HTTP).
  • Один долгоживущий процесс — без фоновых демонов, переживающих основной процесс.

Манифест хостинга (mcpbay.json)

Положите файл mcpbay.json в корень репозитория, чтобы объявить env-переменные сервера. Переменные с "required": true обязательны: деплой будет ждать в статусе «Ждёт секреты», пока вы не зададите их значения в кабинете — раздел «Секреты окружения».

{
  "env": [
    { "name": "API_KEY", "required": true, "description": "external API key" }
  ]
}

Dockerfile проверяется уже при подаче заявки: CMD/ENTRYPOINT должны быть в exec-форме (JSON-массив). После запуска используйте кнопку «Проверить сейчас» в кабинете, чтобы убедиться, что сервер отвечает и инструменты видны.

Манифест необязателен: те же имена переменных можно указать прямо в форме заявки (по одному на строку). Значения из формы имеют приоритет над манифестом.

Что происходит после одобрения

  1. Платформа клонирует репозиторий и собирает контейнерный образ на изолированном сборщике.
  2. Образ проходит скан безопасности: критические уязвимости, для которых существует исправление, блокируют выкат.
  3. Сервер запускается в изолированной виртуальной машине и получает поддомен имя.run.mcpbay.pro.
  4. Перед сервером автоматически работает OAuth-гейт: пользователи подключают сервер, входя своим аккаунтом mcpbay.pro в ИИ-клиенте; запросы без токена получают 401. Статистику вызовов инструментов собирает платформа.

Секреты и переменные окружения

API-ключи и другие секреты вашего сервера задаются в личном кабинете — секция «Секреты окружения» в блоке деплоя заявки (появляется после первого деплоя). Значения шифруются, применяются к серверу как переменные окружения и никогда не показываются повторно. Имена с префиксами MCPBAY_ и FLY_, а также PORT, зарезервированы платформой.

Статус деплоя — сборка, адрес, ошибки — виден в кабинете в карточке вашей заявки.

Имейте в виду

Ёмкость хостинга ограничена — модерация одобряет заявки с учётом свободных мест и может приостановить или снять сервер с хостинга.

Пока хостинг активен, заявку нельзя удалить из кабинета — сначала остановка хостинга через модерацию.

Манифест ключей: чтобы у пользователя появились поля для токенов

Манифест — это небольшой JSON-файл, который описывает, какие ключи или токены ваш сервер запрашивает у каждого пользователя. С ним пользователь получает форму, куда вставляет свои значения; без него поля для ключей у вашего сервера не появляются.

Когда он нужен

Только если вашему MCP нужны личные ключи или токены пользователя от стороннего сервиса — например, собственный API-ключ пользователя для внешней платформы.

Если вашему MCP ключи пользователя не нужны — пропустите этот раздел: манифест не требуется, и для вас ничего не меняется.

Как это работает для пользователя

  1. Манифест объявляет поля, которые нужны вашему серверу.
  2. Каждый пользователь вставляет свои значения в разделе «Подключения» в своём /account.
  3. Значения хранятся в зашифрованном виде, по каждому пользователю отдельно — их никто не видит и они не показываются обратно.
  4. В момент вызова ваш MCP получает их автоматически (store-and-inject).

Нет манифеста — нет и формы для ключей у вашего сервера.

Формат

Один JSON-файл на провайдера, с именем manifests/<provider>.json. Ключи ниже фиксированы; строковые значения в примере — иллюстративные.

{
  "provider": "acme",
  "purpose": "ACME lets each seller manage their own catalog. Enter the API key from your ACME account settings.",
  "fields": [
    {
      "name": "api_key",
      "label": "ACME API Key",
      "kind": "secret",
      "help": "Found in ACME → Settings → API."
    }
  ]
}
  • provider — строчный слаг (шаблон ^[a-z][a-z0-9_-]{1,30}$). Он должен совпадать с полем credential_provider вашей заявки — именно так MCPBay связывает манифест с вашим сервером.
  • purpose — показывается пользователю: что он подключает и зачем.
  • fields[] — по одной записи на каждое запрашиваемое значение. В каждой записи:
  • name — слаг поля (шаблон ^[a-z][a-z0-9_]{0,40}$). Под этим ключом ваш MCP получает значение.
  • label — человекочитаемая подпись, которую видит пользователь рядом с полем.
  • kind — text или secret. secret отображается как маскированное поле-пароль только для записи: после сохранения значение обратно не показывается.
  • help — необязательная подсказка, например где взять ключ.

Советы

  • Объявляйте только действительно нужные поля — собирайте минимум (минимизация данных).
  • Напишите понятные purpose и help, чтобы пользователь точно понимал, что вставлять и где это взять.
  • Для всего чувствительного указывайте kind: "secret" — поле станет маскированным и только для записи.

Как его подключить

Манифесты проходят проверку оператором и поставляются вместе с сервисом. Сейчас оператор вручную связывает ваш манифест с заявкой (выставляет поле credential_provider).

Поэтому отправьте JSON манифеста вместе с заявкой — вставьте его в описание или пришлите через поддержку — и его подключат. Загрузка манифеста прямо через форму подачи пока недоступна.

«Моё использование»: чтобы пользователь видел свою статистику в кабинете

Пока ваш MCP не интегрирован, статистика обезличена, и в кабинете пользователя написано «Аккаунт не связан». После интеграции пользователь связывает аккаунт одноразовым кодом и затем видит своё использование и траты в разделе «Мои MCP» → «Моё использование» — это больше доверия и удержания. Это же — фундамент для будущих платных tools (биллинга).

Два пути линковки

  1. MCP на хостинге MCPBay. Разработчику делать ничего не нужно: платформа сама ставит перед сервером OAuth-гейт своего IdP и собирает статистику использования — пользователь просто входит аккаунтом mcpbay.pro в своём ИИ-клиенте. Привязка аккаунтов и пер-юзерная статистика для размещённых серверов — забота платформы, а не вашего кода.
  2. MCP со своей авторизацией. Если у вашего MCP уже есть собственная пер-юзерная личность (свой OAuth) — используйте device-code трек ниже без изменений: tool get_mcpbay_link_code + billing.create_link_code + usage.record_tool_use.

Как работает связывание (device-code flow)

  1. В кабинете пользователь жмёт «Моё использование» у несвязанного MCP и получает окно «введите код».
  2. В AI-клиенте ассистент вызывает инструмент get_mcpbay_link_code в вашем MCP. Сервер генерирует одноразовый код и возвращает его пользователю.
  3. Пользователь вставляет код в кабинете. MCPBay гасит его и связывает аккаунт mcpbay с личностью внутри вашего MCP.
  4. Дальше события использования, которые пишет ваш MCP, атрибутируются этому пользователю.

Что вы добавляете в свой MCP

  1. Стабильная пер-юзерная личность внутри MCP. external_subject — например claim sub из OAuth. Без неё связывание неприменимо: серверу без пер-юзерного субъекта некого атрибутировать.
  2. Инструмент с именем ровно get_mcpbay_link_code. Кабинет называет пользователю именно это имя, поэтому оно должно совпадать. Инструмент генерирует код из 8 символов алфавита A–Z2–9 (без 0/O/1/I), показывает его как XXXX-XXXX, хранит только SHA-256-hash через billing.create_link_code(...) и возвращает код с краткой инструкцией (ввести на mcpbay.pro в «Моё использование», действует 10 минут).
  3. События использования после каждого вызова. Вызывайте usage.record_tool_use(...) один раз на каждый вызов tool'а. Функция — тихий no-op, пока субъект не связан (а для бесплатных tools — пока сервер в «Мои MCP» пользователя, консент-гейт), поэтому вызывать можно безусловно.

Готовая заготовка (Python / FastMCP)

Вставьте в свой сервер, задайте SUBMISSION_ID и подключите get_current_subject() к своей пер-юзерной личности. Обе SQL-функции живут в общей Postgres MCPBay — роль БД вашего MCP получает на них EXECUTE при подключении (см. примечание ниже).

# ── MCPBay "My usage" integration ─────────────────────────────
# Mint a one-time link code + record per-user tool usage.
import hashlib
import secrets

import asyncpg  # or psycopg — any async Postgres driver
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("your-server")

# Your numeric id from the MCPBay catalog (ask the operator at onboarding).
SUBMISSION_ID = 0  # TODO: replace with your submission id

# Code alphabet: A–Z 2–9 without ambiguous chars (I, L, O, U, 0, 1).
# MCPBay normalises the entered code to ^[A-Z2-9]{8}$, so this is a safe subset.
_ALPHABET = "ABCDEFGHJKMNPQRSTVWXYZ23456789"


def _make_code() -> tuple[str, bytes]:
    """Return (display 'XXXX-XXXX', sha256 hash) — only the hash is stored."""
    raw = "".join(secrets.choice(_ALPHABET) for _ in range(8))
    digest = hashlib.sha256(raw.encode("ascii")).digest()
    return f"{raw[:4]}-{raw[4:]}", digest


async def get_current_subject() -> str | None:
    """Your OAuth identity for the current user (e.g. the 'sub' claim).
    Return a stable string per user, or None if no identity is present."""
    raise NotImplementedError  # TODO: wire to your auth


async def _pool() -> asyncpg.Pool:
    """Your shared MCPBay Postgres pool (DSN from env)."""
    raise NotImplementedError  # TODO: return your asyncpg pool


@mcp.tool()
async def get_mcpbay_link_code() -> str:
    """Get a one-time code to link this account to your mcpbay.pro cabinet.
    The user enters it under "My MCPs" → "My usage". Valid 10 minutes."""
    subject = await get_current_subject()
    if subject is None:
        return "Sign in first, then ask for a link code."
    display, code_hash = _make_code()
    pool = await _pool()
    async with pool.acquire() as conn:
        # Stores only the hash; ttl 1–60 min (default 10). One active code per
        # (submission, subject) — calling again reissues and voids the previous.
        expires_at = await conn.fetchval(
            "SELECT billing.create_link_code($1, $2, $3, $4)",
            SUBMISSION_ID, subject, code_hash, 10,
        )
    return (
        f"Your link code: {display}\n"
        f"Enter it on mcpbay.pro under 'My usage'. Valid ~10 minutes "
        f"(until {expires_at})."
    )


async def record_usage(tool_name: str, status: str, duration_ms: int) -> None:
    """Record one usage event. Silent no-op until the subject is linked —
    call it unconditionally after every tool, success or error."""
    subject = await get_current_subject()
    if subject is None:
        return
    pool = await _pool()
    async with pool.acquire() as conn:
        await conn.execute(
            "SELECT usage.record_tool_use($1, $2, $3, $4, $5)",
            SUBMISSION_ID, subject, tool_name, status, duration_ms,
        )

Запросите подключение к usage-API при размещении

Обе функции под защитой: least-privilege роль БД вашего MCP получает на них EXECUTE и привязывается к вашей заявке (анти-спуфинг привязывает связь к вашей роли, поэтому чужая заявка недостижима). Оператор настраивает это вручную при онбординге — self-service пока нет. Поэтому запросите подключение к usage-API, когда размещаете сервер.

Эталонная реализация: живой сервер SmartCart («Продукты Лента», smartcart.mcpbay.pro) реализует get_mcpbay_link_code и пишет использование ровно так — алфавит, хеширование и SQL-вызовы выше повторяют его код.

Готовы опубликовать свой сервер?

Подайте сервер в каталог MCPBay — со своим публичным HTTPS-эндпоинтом или сразу с хостингом на инфраструктуре MCPBay.

Подать заявку