Аудитория: backend- и AI-разработчики с Python или TypeScript, которым нужно подключить Claude, Cursor или VS Code к внутренним API, БД и файлам без отдельного адаптера на каждую модель. Результат: рабочий MCP Server — от STDIO-прототипа до HTTP+SSE в продакшене, плюс практика с базой знаний. Структура: механика протокола, шесть типичных ошибок, таблицы транспорта и capabilities, код Hello World / Tools / Resources / Prompts, отладка JSON-RPC, восемь шагов rollout, экосистема. Теория протокола: deep dive MCP.
tools/list, JSON Schema и stateful-сессии JSON-RPC 2.0.PATH между shell и launchd/systemd.Это практический слой под статьёй о протоколе — здесь собираем сервер, там разбираем N×M и экосистему.
Host (Cursor, Claude Desktop) запускает MCP Client. Client держит 1:1 сессию с MCP Server по JSON-RPC 2.0. Сервер публикует три типа capabilities и проксирует вызовы во внешние системы.
Жизненный цикл запроса: initialize → capability negotiation → tools/list → tools/call с валидацией аргументов по JSON Schema на стороне сервера до выполнения side effect.
| Capability | RPC | Семантика | Граница производительности |
|---|---|---|---|
| Tools | tools/list, tools/call | Мутации и запросы с side effect | Таймаут tool-call должен учитывать cold start + IO; типично 10–30 s steady |
| Resources | resources/list, resources/read | Read-only по URI | Размер payload влияет на token budget Host; стриминг пока ограничен spec |
| Prompts | prompts/list, prompts/get | Параметризованные шаблоны | Снижает дублирование system prompt; фиксируйте schema параметров |
| Transport | Механизм | Latency (локально) | Ограничение масштаба |
|---|---|---|---|
| STDIO | stdin/stdout, subprocess | < 5 ms cold (pinned bundle) | 1 процесс Host ↔ 1 инстанс Server |
| HTTP + SSE | Server-Sent Events + POST /message | + RTT 20–80 ms (EU) | Session affinity на LB; не stateless |
Требования: Node.js 20 LTS или Python 3.11+, Git, MCP-совместимый Host. Зафиксируйте minor-версии runtime — drift окружения даёт ~40 % «tool not registered» в field-отчётах команд.
mkdir mcp-hello && cd mcp-hello npm init -y npm install @modelcontextprotocol/sdk zod # Python: python3 -m venv .venv && pip install mcp
| SDK | Пакет | Старт | Профиль |
|---|---|---|---|
| TypeScript | @modelcontextprotocol/sdk | node dist/index.js | STDIO, быстрая интеграция Cursor |
| Python | mcp | python server.py | Data API, ML-пайплайны |
Минимум: ответ на initialize, регистрация tool echo. Ожидание: Host показывает ровно один tool в tools/list.
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: "hello-mcp", version: "1.0.0" });
server.tool(
"echo",
{ message: z.string().describe("Текст для вывода") },
async ({ message }) => ({ content: [{ type: "text", text: message }] })
);
await server.connect(new StdioServerTransport());
// ~/.cursor/mcp.json
{
"mcpServers": {
"hello-mcp": {
"command": "node",
"args": ["/abs/path/mcp-hello/dist/index.js"]
}
}
}
Проверка: абсолютные пути в command/args; тот же OS-user, что и Host. После смены config — перезапуск Host и повтор tools/list.
Tool выполняет мутацию или запрос с побочным эффектом. Обязательны: уникальное имя, Zod-schema, документированные side effects, явный timeout handler.
server.tool(
"get_customer_status",
{
customerId: z.string().uuid(),
includeOrders: z.boolean().default(false),
},
async ({ customerId, includeOrders }) => {
const res = await fetch(`${process.env.CRM_BASE}/customers/${customerId}`);
if (!res.ok) throw new Error(`CRM HTTP ${res.status}`);
return { content: [{ type: "text", text: JSON.stringify(await res.json()) }] };
}
);
Resources — read-only. Стабильные URI: kb://doc/{id}, file:///policy/handbook.md. Host читает через resources/read до tool-call, если контекст критичен.
server.resource(
"handbook",
"kb://policy/handbook",
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: await fs.readFile("./data/handbook.md", "utf8"),
}],
})
);
Prompts снижают дублирование длинных system instructions. Параметры фиксируйте в schema — иначе Host и Server расходятся в интерпретации.
server.prompt(
"incident_triage",
{ service: z.string(), severity: z.enum(["P1", "P2", "P3"]) },
({ service, severity }) => ({
messages: [{
role: "user",
content: { type: "text", text: `Triage ${severity} для ${service}: логи, root cause, 3 шага.` },
}],
})
);
При shared-доступе команды переходите с STDIO на HTTP+SSE. SSE держит long-lived connection — load balancer обязан обеспечить session affinity, иначе POST /message попадёт не в тот transport instance.
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
const app = express();
let transport;
app.get("/sse", async (req, res) => {
transport = new SSEServerTransport("/message", res);
await server.connect(transport);
});
app.post("/message", express.json(), (req, res) => transport.handlePostMessage(req, res));
app.listen(8787, "0.0.0.0");
| Параметр | Dev | Prod |
|---|---|---|
| TLS | localhost | nginx/Caddy termination, HSTS |
| First-call timeout | 60 s | 30 s после prewarm |
| Auth | опционально | Bearer/OAuth на entrypoint сервера |
| LB affinity | N/A | sticky cookie или single pod per session |
| Симптом | Данные | Корень | Действие |
|---|---|---|---|
| Пустой tools/list | exit code, stderr | Неверный cwd/PATH | Абсолютный путь; тот же user |
| Медленный первый call | npm/pip fetch | npx -y JIT | Pin версий; prewarm image |
| -32602 Invalid params | diff schema | Zod vs client expectation | Сверить output tools/list |
| Steady timeout | CPU, FD count | Blocking IO | A/B с read-only tool |
npx @modelcontextprotocol/inspector node dist/index.js # Цепочка: initialize → tools/list (≥1) → tools/call echo
Restart=on-failure, EnvironmentFile.mcp.json.tools/call с correlation_id; метрики p95 latency, error rate, active SSE sessions.Связка Resources (Markdown-доки) + tool semantic_search (векторный или BM25 индекс). Поток: resources/list → search → цитирование URI — снижает hallucination в support- и compliance-сценариях.
server.tool(
"semantic_search",
{ query: z.string().min(3), topK: z.number().int().max(10).default(5) },
async ({ query, topK }) => {
const hits = await index.search(query, topK);
return {
content: [{
type: "text",
text: hits.map(h => `${h.uri} (${h.score})\n${h.snippet}`).join("\n---\n"),
}],
};
}
);
npx -y без pin.MCP Server на dev-Mac — правильный старт, но три предела видны быстро: STDIO/SSE рвётся при sleep, PATH и secrets расходятся в команде, многошаговые agent workflow требуют стабильного uptime. Generic Linux VM без нативного macOS ломает toolchain для Xcode/Metal, если server трогает Apple-native API.
Для постоянного запуска MCP Server и agent-оркестрации выделенный узел MACCOME Mac mini M4 / M4 Pro 7×24 обычно дешевле по TCO, чем борьба с sleep на ноутбуке. Тарифы: цены аренды; онбординг: центр помощи.
Частые вопросы
Python или TypeScript для первого MCP Server?
TypeScript — быстрый STDIO и Cursor. Python — data-пайплайны. Оба production-ready; выбор по стеку команды.
STDIO или HTTP+SSE?
STDIO для solo-dev. HTTP+SSE при remote team — TLS, session affinity, централизованная auth.
Как связаны MCP и Agent Skills?
Skills — prompt-пакеты в Host; MCP — открытый tool protocol. Подробнее в гайде Agent Skill.
Какой хост для prod MCP?
Избегайте sleep ноутбука для long-lived SSE. MACCOME — выделенные M4/M4 Pro 7×24. Цены и центр помощи.