lenec ru

← все посты

MCP-серверы: как написать свой и зачем

12K

Model Context Protocol (MCP) — это способ подключить к coding-ассистенту любые внешние сервисы: твою БД, тикеты в Linear, документацию, метрики из Prometheus. Звучит абстрактно, но в реальности — это просто маленький локальный сервер с набором tools, который Claude Code или Cursor вызывает по запросу. В этой статье я покажу, как написать свой MCP-сервер с нуля.

Зачем

У меня в работе типичный сценарий: я пишу запрос в Postgres, и Claude мне предлагает SQL. Но он не знает мою схему. Я каждый раз копировал structure dump в чат — раздражает.

Решение — MCP-сервер с одним инструментом «schema» и одним «query». Claude сам видит, что есть инструменты, сам их вызывает, когда нужно понять структуру или прочитать что-то из БД. Я в чате не пишу SQL — я пишу «найди мне топ-10 пользователей по выручке за май», и Claude сам читает схему, сам пишет запрос, сам исполняет.

Что под капотом

MCP — это JSON-RPC поверх stdio (или SSE). Сервер слушает на stdin сообщения, отвечает в stdout. Клиент (Claude Code, Cursor, Continue) запускает сервер как дочерний процесс и общается с ним.

Протокол простой: клиент запрашивает «дай мне список твоих инструментов», сервер возвращает schemas. Дальше клиент вызывает конкретный инструмент с аргументами, сервер исполняет и возвращает результат.

Минимальный сервер на Node

Anthropic выпустил официальный SDK — @modelcontextprotocol/sdk. Через него скелет сервера — десяток строк.

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';

const server = new Server(
  { name: 'my-postgres', version: '0.1.0' },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'list_tables',
      description: 'Вернуть список таблиц в текущей БД',
      inputSchema: { type: 'object', properties: {} },
    },
    {
      name: 'describe_table',
      description: 'Вернуть структуру таблицы',
      inputSchema: {
        type: 'object',
        properties: { name: { type: 'string' } },
        required: ['name'],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name === 'list_tables') {
    const rows = await db.query("SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
    return { content: [{ type: 'text', text: JSON.stringify(rows) }] };
  }
  if (req.params.name === 'describe_table') {
    const name = req.params.arguments?.name;
    const rows = await db.query(
      "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1",
      [name]
    );
    return { content: [{ type: 'text', text: JSON.stringify(rows) }] };
  }
  throw new Error('unknown tool');
});

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

Запускается через node my-server.mjs. Никаких портов, никакого http — общение через stdin/stdout.

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

В .claude/mcp.json в корне проекта добавляешь:

{
  "mcpServers": {
    "my-postgres": {
      "command": "node",
      "args": ["./tools/mcp-postgres.mjs"],
      "env": { "DATABASE_URL": "$DATABASE_URL" }
    }
  }
}

Перезапускаешь Claude Code, и в /mcp видишь подключённый сервер с двумя инструментами. Дальше Claude сам решает, когда их вызвать. Я ничего больше не делаю.

Что я подключил у себя

  • postgres-mcp — описано выше.
  • linear-mcp — читает мои задачи и комменты, может создавать новые, обновлять статусы.
  • company-docs-mcp — мой собственный сервер, который ходит во внутреннюю Confluence и отдаёт страницы по запросу.
  • k8s-mcp — kubectl с белым списком команд (только read-only).

company-docs и k8s — самописные. Linear и postgres беру из готовых пакетов, они есть в @modelcontextprotocol/servers.

Безопасность

Ключевое: MCP-сервер запускается в окружении пользователя и имеет доступ ко всему, к чему ты сам имеешь. Если в инструменте написать «exec любую команду» — это и будет «exec любую команду». Я держу два правила:

  • Не делать инструменты с побочными эффектами без явного подтверждения. Удалить запись в БД, отправить письмо, выкатить деплой — только через подтверждение пользователя.
  • Read-only по умолчанию. Все query-инструменты в моих серверах работают через read-only пользователя БД с правами SELECT.

Стандартные грабли

  1. stdout — это канал общения с клиентом. Никаких console.log в свой код, иначе клиент сходит с ума. Все логи направляй в stderr или в файл.
  2. Аргументы tool — JSON. Их structure определяется inputSchema. Если schema говорит «name обязательно», а ты не передал — Claude может вызвать неправильно. Внимательно описывай schemas.
  3. Большие ответы. Если инструмент вернёт 100 КБ JSON, Claude его съест и расход токенов будет огромный. Делай пагинацию, давай сводки.

Зачем это всё

Без MCP я каждый раз руками копирую данные в чат. С MCP coding-ассистент имеет «глаза» в моих системах. Это качественный скачок: ассистент перестаёт быть генератором кода и становится оператором, который сам ходит в твою инфраструктуру.

Главное — не превращай MCP в комбайн. Один сервер — одна область. Не пытайся делать «универсальный» сервер с 30 инструментами под всё. Лучше пять серверов по 3–5 инструментов, заточенных под конкретные задачи.

Что почитать

Официальная документация — modelcontextprotocol.io. Там есть и спецификация, и SDK для нескольких языков, и каталог готовых серверов. Если у тебя уже есть Claude Code — попробуй сначала готовые, прежде чем писать свой. Своё пишется за 2–3 часа на простой случай, но готовые часто закроют 80% задач без копки.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.