Из чего состоит 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» ниже).
Как подать заявку
- Откройте форму «Добавить сервер».
- Заполните название, URL сервера (ваш MCP-эндпоинт), категорию, короткое описание и подробное описание, а также логотип. Логотип показывается на карточке в каталоге и в кабинетах пользователей.
- Хотите хостинг MCPBay? Отметьте «Захостить на mcpbay.pro», укажите ссылку на GitHub-репозиторий в поле «Исходный код» и привяжите GitHub — приложение MCPBay Hosting должно быть установлено на этот репозиторий. Поле «URL сервера» при этом можно оставить пустым — адрес выдаст платформа.
- Отправьте заявку. Она уходит на модерацию, и после одобрения сервер публикуется в публичном каталоге.
Советы для хорошей карточки
- Понятное название и краткое описание того, что делает сервер.
- Правильная категория — чтобы те, кто листает каталог, нашли сервер там, где ожидают.
- Рабочий публичный 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-массив). После запуска используйте кнопку «Проверить сейчас» в кабинете, чтобы убедиться, что сервер отвечает и инструменты видны.
Манифест необязателен: те же имена переменных можно указать прямо в форме заявки (по одному на строку). Значения из формы имеют приоритет над манифестом.
Что происходит после одобрения
- Платформа клонирует репозиторий и собирает контейнерный образ на изолированном сборщике.
- Образ проходит скан безопасности: критические уязвимости, для которых существует исправление, блокируют выкат.
- Сервер запускается в изолированной виртуальной машине и получает поддомен имя.run.mcpbay.pro.
- Перед сервером автоматически работает OAuth-гейт: пользователи подключают сервер, входя своим аккаунтом mcpbay.pro в ИИ-клиенте; запросы без токена получают 401. Статистику вызовов инструментов собирает платформа.
Секреты и переменные окружения
API-ключи и другие секреты вашего сервера задаются в личном кабинете — секция «Секреты окружения» в блоке деплоя заявки (появляется после первого деплоя). Значения шифруются, применяются к серверу как переменные окружения и никогда не показываются повторно. Имена с префиксами MCPBAY_ и FLY_, а также PORT, зарезервированы платформой.
Статус деплоя — сборка, адрес, ошибки — виден в кабинете в карточке вашей заявки.
Имейте в виду
Ёмкость хостинга ограничена — модерация одобряет заявки с учётом свободных мест и может приостановить или снять сервер с хостинга.
Пока хостинг активен, заявку нельзя удалить из кабинета — сначала остановка хостинга через модерацию.
Манифест ключей: чтобы у пользователя появились поля для токенов
Манифест — это небольшой JSON-файл, который описывает, какие ключи или токены ваш сервер запрашивает у каждого пользователя. С ним пользователь получает форму, куда вставляет свои значения; без него поля для ключей у вашего сервера не появляются.
Когда он нужен
Только если вашему MCP нужны личные ключи или токены пользователя от стороннего сервиса — например, собственный API-ключ пользователя для внешней платформы.
Если вашему MCP ключи пользователя не нужны — пропустите этот раздел: манифест не требуется, и для вас ничего не меняется.
Как это работает для пользователя
- Манифест объявляет поля, которые нужны вашему серверу.
- Каждый пользователь вставляет свои значения в разделе «Подключения» в своём /account.
- Значения хранятся в зашифрованном виде, по каждому пользователю отдельно — их никто не видит и они не показываются обратно.
- В момент вызова ваш 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 (биллинга).
Два пути линковки
- MCP на хостинге MCPBay. Разработчику делать ничего не нужно: платформа сама ставит перед сервером OAuth-гейт своего IdP и собирает статистику использования — пользователь просто входит аккаунтом mcpbay.pro в своём ИИ-клиенте. Привязка аккаунтов и пер-юзерная статистика для размещённых серверов — забота платформы, а не вашего кода.
- MCP со своей авторизацией. Если у вашего MCP уже есть собственная пер-юзерная личность (свой OAuth) — используйте device-code трек ниже без изменений: tool get_mcpbay_link_code + billing.create_link_code + usage.record_tool_use.
Как работает связывание (device-code flow)
- В кабинете пользователь жмёт «Моё использование» у несвязанного MCP и получает окно «введите код».
- В AI-клиенте ассистент вызывает инструмент get_mcpbay_link_code в вашем MCP. Сервер генерирует одноразовый код и возвращает его пользователю.
- Пользователь вставляет код в кабинете. MCPBay гасит его и связывает аккаунт mcpbay с личностью внутри вашего MCP.
- Дальше события использования, которые пишет ваш MCP, атрибутируются этому пользователю.
Что вы добавляете в свой MCP
- Стабильная пер-юзерная личность внутри MCP. external_subject — например claim sub из OAuth. Без неё связывание неприменимо: серверу без пер-юзерного субъекта некого атрибутировать.
- Инструмент с именем ровно get_mcpbay_link_code. Кабинет называет пользователю именно это имя, поэтому оно должно совпадать. Инструмент генерирует код из 8 символов алфавита A–Z2–9 (без 0/O/1/I), показывает его как XXXX-XXXX, хранит только SHA-256-hash через billing.create_link_code(...) и возвращает код с краткой инструкцией (ввести на mcpbay.pro в «Моё использование», действует 10 минут).
- События использования после каждого вызова. Вызывайте 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.
Подать заявку