Перейти к основному содержимому
ИнженерияMar 28, 2026

Создание первого MCP-сервера: практическое руководство для разработчиков

OS
Open Soft Team

Engineering Team

Что такое Model Context Protocol (MCP)?

Model Context Protocol (MCP) — это открытый стандарт, созданный компанией Anthropic, который определяет способ взаимодействия AI-приложений с внешними источниками данных и инструментами. MCP можно сравнить с USB-C для AI-интеграций: подобно тому как USB-C предоставляет единый универсальный разъём для зарядки, передачи данных и видеовывода, MCP предоставляет единый универсальный протокол для подключения AI-моделей к базам данных, API, файловым системам и любым другим сервисам. MCP устраняет необходимость создания отдельных интеграций между каждым AI-приложением и каждым инструментом.

В 2026 году MCP стал доминирующим стандартом AI-коммуникаций. По прогнозам Gartner, 40% корпоративных приложений будут включать AI-агентов к концу 2026 года, и именно MCP делает это практически возможным в масштабе. До появления MCP подключение AI-модели к N инструментам требовало N отдельных интеграций. При M AI-приложениях и N инструментах требовалось M x N адаптеров. MCP сводит это к M + N: каждое AI-приложение реализует один MCP-клиент, каждый инструмент реализует один MCP-сервер.

Архитектура MCP: хосты, клиенты, серверы и транспорты

Понимание архитектуры MCP необходимо прежде, чем писать код. Протокол определяет четыре ключевые роли:

КомпонентРольПример
ХостAI-приложение, с которым взаимодействует пользовательClaude Desktop, Cursor, VS Code Copilot
КлиентОбработчик протокола MCP внутри хостаВстроен в хост-приложение
СерверПредоставляет инструменты, ресурсы и промпты через MCPВаш кастомный сервер (то, что мы будем создавать)
ТранспортКоммуникационный слой между клиентом и серверомstdio, HTTP+SSE, Streamable HTTP

Поток коммуникации работает следующим образом:

  1. Хост запускается и инициализирует MCP-клиент для каждого настроенного сервера.
  2. Клиент подключается к серверу через транспорт (stdio для локальных, HTTP для удалённых).
  3. Клиент отправляет запрос initialize, согласовывая версию протокола и возможности.
  4. Сервер отвечает списком доступных инструментов, ресурсов и промптов.
  5. Когда AI-модели нужны внешние данные или действия, клиент вызывает соответствующий метод сервера.
  6. Сервер выполняет операцию и возвращает результаты через сообщения JSON-RPC 2.0.

Инструменты vs Ресурсы vs Промпты

MCP-серверы могут предоставлять три типа возможностей:

  • Инструменты (Tools) — функции, которые AI-модель может вызывать. Они принимают структурированный ввод и возвращают структурированный вывод. Пример: query_database(sql: string) или send_email(to: string, subject: string, body: string).
  • Ресурсы (Resources) — данные, которые AI-модель может читать. Идентифицируются URI и возвращают контент. Пример: file:///path/to/document.md или postgres://localhost/mydb/users.
  • Промпты (Prompts) — переиспользуемые шаблоны промптов, предоставляемые сервером. Помогают стандартизировать взаимодействие AI с доменом сервера. Пример: шаблон summarize_ticket для Jira MCP-сервера.

Пошагово: создание MCP-сервера на TypeScript

Построим практический MCP-сервер, предоставляющий инструмент для запросов к базе данных SQLite. Это распространённый сценарий: предоставить AI-модели безопасный доступ только на чтение к данным приложения.

Шаг 1: Инициализация проекта

mkdir mcp-sqlite-server && cd mcp-sqlite-server
npm init -y
npm install @modelcontextprotocol/sdk better-sqlite3
npm install -D typescript @types/node @types/better-sqlite3
npx tsc --init

Настройте tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

Шаг 2: Реализация сервера

Создайте src/index.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import Database from "better-sqlite3";
import { z } from "zod";

// Инициализация базы данных
const db = new Database(process.env.DB_PATH || "./data.db", {
  readonly: true,
});

// Создание MCP-сервера
const server = new McpServer({
  name: "sqlite-query",
  version: "1.0.0",
});

// Регистрация инструмента для запросов к БД
server.tool(
  "query",
  "Выполнить SQL-запрос только на чтение к базе данных SQLite",
  {
    sql: z.string().describe("SQL SELECT-запрос для выполнения"),
  },
  async ({ sql }) => {
    // Безопасность: разрешены только SELECT-запросы
    const normalized = sql.trim().toUpperCase();
    if (!normalized.startsWith("SELECT")) {
      return {
        content: [
          {
            type: "text",
            text: "Ошибка: разрешены только SELECT-запросы.",
          },
        ],
        isError: true,
      };
    }

    try {
      const rows = db.prepare(sql).all();
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(rows, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Ошибка запроса: ${(error as Error).message}`,
          },
        ],
        isError: true,
      };
    }
  }
);

// Регистрация инструмента для получения списка таблиц
server.tool(
  "list_tables",
  "Получить список всех таблиц в базе данных со схемами",
  {},
  async () => {
    const tables = db
      .prepare(
        `SELECT name, sql FROM sqlite_master
         WHERE type='table' AND name NOT LIKE 'sqlite_%'
         ORDER BY name`
      )
      .all();

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(tables, null, 2),
        },
      ],
    };
  }
);

// Регистрация ресурса для схемы базы данных
server.resource(
  "schema",
  "sqlite://schema",
  async (uri) => {
    const tables = db
      .prepare(
        `SELECT name, sql FROM sqlite_master
         WHERE type='table' AND name NOT LIKE 'sqlite_%'`
      )
      .all();

    return {
      contents: [
        {
          uri: uri.href,
          mimeType: "application/json",
          text: JSON.stringify(tables, null, 2),
        },
      ],
    };
  }
);

// Запуск сервера с транспортом stdio
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("SQLite MCP-сервер запущен на stdio");
}

main().catch(console.error);

Шаг 3: Сборка и локальное тестирование

npx tsc
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | node dist/index.js

Вы должны увидеть JSON-RPC ответ с возможностями сервера.

Шаг 4: Тот же сервер на Python

Для Python-разработчиков — эквивалентная реализация с использованием официального MCP Python SDK:

# server.py
import sqlite3
import json
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("sqlite-query")

DB_PATH = "data.db"

@mcp.tool()
def query(sql: str) -> str:
    """Выполнить SQL-запрос только на чтение к базе данных SQLite."""
    normalized = sql.strip().upper()
    if not normalized.startswith("SELECT"):
        raise ValueError("Разрешены только SELECT-запросы.")

    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        cursor = conn.execute(sql)
        rows = [dict(row) for row in cursor.fetchall()]
        return json.dumps(rows, indent=2, default=str)
    finally:
        conn.close()

@mcp.tool()
def list_tables() -> str:
    """Получить список всех таблиц в базе данных со схемами."""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        cursor = conn.execute(
            "SELECT name, sql FROM sqlite_master "
            "WHERE type='table' AND name NOT LIKE 'sqlite_%'"
        )
        tables = [dict(row) for row in cursor.fetchall()]
        return json.dumps(tables, indent=2)
    finally:
        conn.close()

@mcp.resource("sqlite://schema")
def get_schema() -> str:
    """Вернуть схему базы данных как ресурс."""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        cursor = conn.execute(
            "SELECT name, sql FROM sqlite_master WHERE type='table'"
        )
        return json.dumps([dict(r) for r in cursor.fetchall()], indent=2)
    finally:
        conn.close()

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

Установка зависимостей и запуск:

pip install mcp[cli]
python server.py

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

Claude Desktop нативно поддерживает MCP-серверы. Для подключения отредактируйте файл конфигурации:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "sqlite-query": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"],
      "env": {
        "DB_PATH": "/absolute/path/to/your/data.db"
      }
    }
  }
}

Для Python-версии:

{
  "mcpServers": {
    "sqlite-query": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"]
    }
  }
}

Перезапустите Claude Desktop. В интерфейсе чата должна появиться иконка молотка, обозначающая доступные MCP-инструменты. Теперь можно задавать Claude вопросы вроде «Какие таблицы есть в базе?» или «Покажи 10 последних пользователей по дате регистрации», и он будет использовать ваш MCP-сервер для прямых запросов к базе данных.

Подключение к Cursor

Cursor также поддерживает MCP-серверы. Добавьте конфигурацию в .cursor/mcp.json в корне проекта:

{
  "mcpServers": {
    "sqlite-query": {
      "command": "node",
      "args": ["./dist/index.js"],
      "env": {
        "DB_PATH": "./data.db"
      }
    }
  }
}

После сохранения перезапустите Cursor. MCP-инструменты появятся в панели AI-ассистента и смогут вызываться при генерации и отладке кода.

Тестирование и отладка MCP-сервера

Использование MCP Inspector

MCP Inspector — официальный инструмент отладки. Он предоставляет веб-интерфейс для взаимодействия с вашим сервером:

npx @modelcontextprotocol/inspector node dist/index.js

Откроется браузерный интерфейс по адресу http://localhost:5173, где вы можете:

  • Просматривать все зарегистрированные инструменты, ресурсы и промпты
  • Вызывать инструменты с произвольными входными данными и проверять ответы
  • Мониторить поток JSON-RPC сообщений в реальном времени
  • Тестировать обработку ошибок, отправляя некорректные запросы

Модульное тестирование с SDK

Напишите автоматизированные тесты с использованием in-memory транспорта:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

describe("SQLite MCP Server", () => {
  let client: Client;

  beforeEach(async () => {
    const [clientTransport, serverTransport] =
      InMemoryTransport.createLinkedPair();
    const server = createServer(); // ваша фабрика серверов
    await server.connect(serverTransport);
    client = new Client({ name: "test", version: "1.0" });
    await client.connect(clientTransport);
  });

  it("должен вернуть список таблиц", async () => {
    const result = await client.callTool({
      name: "list_tables",
      arguments: {},
    });
    expect(result.content[0].text).toContain("users");
  });

  it("должен отклонить не-SELECT запросы", async () => {
    const result = await client.callTool({
      name: "query",
      arguments: { sql: "DROP TABLE users" },
    });
    expect(result.isError).toBe(true);
  });
});

Распространённые проблемы при отладке

ПроблемаПричинаРешение
Сервер не отображается в Claude DesktopОшибка в пути или синтаксисе JSONПроверьте JSON, используйте абсолютные пути
Ошибки «Tool not found»Сервер не зарегистрировал инструменты до подключенияРегистрируйте инструменты до вызова server.connect()
Таймаут при вызове инструментовДолгие операции без отчёта о прогрессеДобавьте уведомления о прогрессе через server.sendProgress()
Вывод в stderr ломает протоколconsole.log пишет в stdout (stdio транспорт)Используйте console.error() для логирования с stdio
Соединение обрывается при простоеТаймаут транспортаРеализуйте heartbeat или используйте HTTP-транспорт

Лучшие практики для продакшена

  1. Валидация входных данных: всегда валидируйте и санитизируйте ввод инструментов. Используйте Zod-схемы (TypeScript) или Pydantic-модели (Python) для строгой проверки типов.

  2. Только чтение по умолчанию: начинайте с доступа только на чтение. Добавляйте возможности записи только при явной необходимости, и всегда требуйте подтверждения для деструктивных операций.

  3. Обработка ошибок: возвращайте структурированные сообщения об ошибках с isError: true. Никогда не раскрывайте внутренние стек-трейсы или строки подключения к БД.

  4. Логирование: логируйте все вызовы инструментов с временными метками, входными данными и длительностью выполнения. Используйте stderr для логов (не stdout) при stdio-транспорте.

  5. Ограничение частоты: реализуйте ограничения частоты вызовов для каждого инструмента, чтобы зацикленные AI-агенты не перегрузили бэкенд.

  6. Таймауты: устанавливайте таймауты на все обработчики инструментов. AI-модель может вызвать инструмент, запускающий дорогой запрос — защитите инфраструктуру.

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

  8. Версионирование: следуйте семантическому версионированию для MCP-сервера. Хендшейк initialize включает согласование версий — ломающие изменения требуют мажорной версии.

FAQ

В: Чем MCP отличается от function calling? О: Function calling (используемый OpenAI, Anthropic и другими) определяет инструменты инлайново в каждом API-запросе. MCP выносит определения инструментов в отдельные серверы, которые любой MCP-совместимый хост может обнаружить и использовать. Function calling — на уровне запроса; MCP — постоянный протокол со стейтфул-сессиями.

В: Можно ли использовать MCP с моделями, отличными от Claude? О: Да. MCP — открытый протокол. OpenAI, Google DeepMind и Microsoft внедрили поддержку MCP в свои платформы по состоянию на начало 2026 года. Любое AI-приложение, реализующее MCP-клиент, может подключиться к любому MCP-серверу.

В: MCP работает только с локальными инструментами? О: Нет. Хотя stdio-транспорт предназначен для локальных серверов, HTTP+SSE и Streamable HTTP транспорты поддерживают удалённые MCP-серверы. Вы можете развернуть MCP-серверы как облачные сервисы, доступные по сети.

В: Как MCP обрабатывает аутентификацию? О: Протокол поддерживает OAuth 2.0 для удалённых серверов. Локальные stdio-серверы наследуют контекст безопасности хост-процесса. Для корпоративных развёртываний MCP-шлюзы могут централизовать аутентификацию и авторизацию.

В: На каких языках можно строить MCP-серверы? О: Официальные SDK существуют для TypeScript, Python, Java, Kotlin, C# и Swift. Сообщество поддерживает SDK для Rust, Go, Ruby и PHP. Протокол языконезависим — любой язык, способный читать/писать JSON-RPC через stdio или HTTP, может реализовать MCP-сервер.

Теги