Каталог готовых MCP-серверов закрывает типовое: файлы, гит, браузер, популярные SaaS. Внутренние системы в нём не появятся никогда — ни ваша админка, ни очередь задач, ни база, в которой лежит ответ на половину вопросов команды.
Хорошая новость: свой сервер — это не «реализовать протокол», а «обернуть готовую функцию». Библиотека берёт на себя JSON-RPC, а от вас требуется описать, что умеет ваша система. Если нужен контекст, что такое MCP и зачем он вообще, он есть в отдельной статье про MCP-серверы — здесь сразу практика.
Шаг 1. Решить, что выносить
Три примитива протокола различаются тем, кто принимает решение об использовании.
| Примитив | Кто вызывает | Для чего |
|---|---|---|
| Tools (инструменты) | Модель | Действия: найти, создать, отправить, посчитать |
| Resources (ресурсы) | Клиентское приложение | Пассивные данные для контекста: документы, схемы, выгрузки |
| Prompts (промпты) | Пользователь | Заготовки сценариев, обычно слэш-команды в интерфейсе |
Большинство первых серверов состоит из одних инструментов, и это нормально. Правила, которые экономят время потом:
- Один инструмент — одна операция.
get_order_statusлучше, чем универсальныйordersс полемaction. Модель выбирает по описанию, и чем уже назначение, тем точнее выбор. - Начинайте с чтения. Первая версия, которая умеет только смотреть, снимает большую часть рисков и сразу приносит пользу.
- Не выносите то, что агент может сделать сам. Если данные лежат в файле репозитория, отдельный инструмент не нужен.
Шаг 2. Сервер на Python
Официальный SDK ставится вместе с CLI-обвязкой:
uv init orders-mcp
cd orders-mcp
uv venv
source .venv/bin/activate
uv add "mcp[cli]"
Минимальный сервер целиком:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("orders")
@mcp.tool()
async def get_order_status(order_id: str) -> str:
"""Вернуть статус заказа по его идентификатору.
Args:
order_id: идентификатор заказа в формате ORD-12345
"""
order = await db.fetch_order(order_id)
if order is None:
return f"Заказ {order_id} не найден"
return f"Статус: {order.status}, обновлён {order.updated_at:%d.%m.%Y}"
if __name__ == "__main__":
mcp.run(transport="stdio")
Здесь важны две вещи, которые легко упустить. Аннотации типов превращаются в JSON-схему аргументов, поэтому order_id: str — не украшение, а часть контракта. Докстринг уходит в описание инструмента и читается моделью: это, по сути, кусок промпта, и писать его надо для читателя, который видит вашу систему впервые.
Ошибки возвращайте текстом, а не исключением. Строка «Заказ не найден» позволит модели объяснить пользователю, что произошло; необработанное исключение просто оборвёт вызов.
Шаг 3. То же на TypeScript
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
import { McpServer } from '@modelcontextprotocol/server'
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'
import { z } from 'zod'
const server = new McpServer({ name: 'orders', version: '1.0.0' })
server.registerTool(
'get_order_status',
{
description: 'Вернуть статус заказа по его идентификатору',
inputSchema: { orderId: z.string().describe('Идентификатор вида ORD-12345') },
},
async ({ orderId }) => {
const order = await fetchOrder(orderId)
return {
content: [{
type: 'text',
text: order ? `Статус: ${order.status}` : `Заказ ${orderId} не найден`,
}],
}
},
)
const transport = new StdioServerTransport()
await server.connect(transport)
Схема аргументов описывается через zod, а .describe() на каждом поле — то же самое, что докстринг в Python: подсказка модели, как заполнять аргумент.
Шаг 4. Подключить к клиенту
Локальный сервер общается с клиентом через stdio: клиент сам запускает процесс и разговаривает с ним через стандартные потоки. В конфигурации клиента это выглядит так:
{
"mcpServers": {
"orders": {
"command": "uv",
"args": ["--directory", "/absolute/path/orders-mcp", "run", "server.py"],
"env": { "ORDERS_API_TOKEN": "..." }
}
}
}
Пути обязаны быть абсолютными — относительные не резолвятся, и это причина примерно половины сообщений «сервер не подключился». Файл конфигурации Claude Desktop лежит в ~/Library/Application Support/Claude/claude_desktop_config.json на macOS и в %APPDATA%\Claude\claude_desktop_config.json на Windows; после правки клиент нужно полностью перезапустить.
Если сервер не появился, смотрите логи: на macOS это ~/Library/Logs/Claude/mcp*.log, куда попадает и весь stderr вашего процесса.
Шаг 5. Отладка в Inspector
Не подключайте сырой сервер к боевому клиенту — это худший способ узнать, что схема аргументов кривая. Для проверки есть референсный инструмент, он же клиент:
npx @modelcontextprotocol/inspector uv --directory /absolute/path/orders-mcp run server.py
Команда поднимает веб-интерфейс, где видно список инструментов, их схемы, вызовы с произвольными аргументами и трафик протокола. У того же пакета есть режим --cli для скриптов и CI и --tui для терминала; для запуска нужен Node 22.19 или новее.
Проверьте по шагам: инструмент виден в списке, схема совпадает с ожиданиями, вызов с правильными аргументами возвращает результат, вызов с мусорными — понятную ошибку.
Отдельная классика для stdio-серверов: любой print в стандартный вывод ломает протокол, потому что вывод занят сообщениями. Логи пишите в stderr.
Шаг 6. Локально или по сети
Stdio подходит, когда сервер живёт на той же машине, что и клиент: личные инструменты, доступ к локальным файлам, обёртки вокруг CLI. Плюс в простоте — процесс запускает клиент, аутентификация не нужна.
Удалённый сервер по HTTP нужен, когда доступ требуется команде, сервер ходит во внутренние системы или его нужно обновлять централизованно. Здесь добавляются авторизация, ограничение доступа и всё, что положено сетевому сервису. Начинать проще со stdio и переносить в сеть, когда инструмент себя оправдал.
Безопасность и границы
Спецификация MCP (актуальная версия — 2026-07-28) отдельно оговаривает: инструменты означают выполнение произвольного кода, их описания недоверенные, а вызовы должны подтверждаться пользователем. К этому добавляется практика:
- Отдельные права. Своя учётная запись и свой токен для сервера, только нужные операции. Сервер работает с правами того, кто его запустил.
- Читать раньше, чем писать. Инструменты, которые что-то меняют, добавляйте отдельным шагом и осознанно.
- Валидация на стороне сервера. Аргументы приходят от модели, а значит, косвенно от текста, который она прочитала. Проверяйте их так же, как ввод из внешней формы.
- Секреты через окружение. Никаких ключей в коде сервера и в описаниях инструментов.
- Аудит. Логируйте вызовы: кто, что, с какими аргументами. При разборе инцидента это единственный источник правды.
Вложить сюда стоит ровно столько же внимания, сколько в обычный внутренний сервис. MCP не добавляет магии — он добавляет ещё одного клиента вашей системы, и клиент этот действует по подсказкам из текста.
Частые ошибки
- Описания в стиле «делает запрос». Модель выбирает инструмент по описанию: чем оно конкретнее, тем реже выбирается не то.
- Один инструмент-комбайн. Универсальный вызов с полем
actionзаставляет модель угадывать, а вас — разбирать, почему она угадала неверно. - Возврат сырого JSON на сотни килобайт. Ответ попадает в контекст. Отдавайте то, что нужно для решения, а не всю запись из базы.
- Относительные пути в конфигурации. Самая частая причина «сервер не запускается».
- Логи в stdout. Молчаливая поломка stdio-протокола.
- Сразу писать в прод. Первая версия — только чтение, на тестовом контуре.
Что дальше
Когда базовый сервер работает, есть куда расти: в протоколе появились расширения — асинхронные задачи для долгих операций, интерактивные элементы интерфейса внутри диалога, структурированные инструкции для агентских сценариев. Всё это опциональное и согласуется при подключении, так что начинать с них не нужно.
Полезнее другое: посмотреть, как устроены зрелые серверы под похожие задачи. Разбор рабочих сборок и критериев выбора — в подборке MCP-серверов, а чтобы агент осмысленно пользовался вашими инструментами, ему пригодятся правила проекта — там же описано, куда их класть для Claude Code, Cursor и Copilot.
В IT-ХОЗЯЕВА такие интеграции разбирают на еженедельном воркшопе по vibe coding и в закрытых AI-чатах: приносите свою систему — вместе решим, что стоит выносить в инструменты, а что оставить за периметром. Разобрать архитектуру предметно можно с ментором из тарифа ХОЗЯИН за 2000 ₽/мес.