Главная / Статьи

MCP для начинающих: создание сервера и клиента, а затем их внедрение в корпоративные системы.

MCP для начинающих: задача N x M, основные идеи в простых словах, сервер и клиент для службы поддержки на TypeScript в Node.js, а также переход к безопасной развертке через HTTP.

Издатель: REACTAPP.TOP14 мин чтения3185 слов

Источники: MCP Tools Inside Enterprise Applications: A Beginner-Friendly Deep Dive

Любой, кто недавно работал с ИИ-агентами, сталкивался с MCP — Model Context Protocol. Он используется в Claude Desktop, плагинах для IDE, внутренних корпоративных проектах «ИИ-порталов», а также всё чаще в промышленном программном обеспечении.

Это введение для начинающих, не предполагающее наличия предварительного опыта работы с MCP. В нём рассматривается проблема, которую решает протокол, описывается его архитектура, создаются реальные сервер и клиент на TypeScript в среде Node.js, а затем обсуждаются вопросы безопасности, управления и отслеживания, которые применяют компании в своих решениях. Диаграммы представлены в виде простого текстового формата ASCII, поэтому их можно сразу вставлять в заметки или документацию к коду.

1. Проблема, которую решает MCP

До появления MCP, чтобы заставить агента выполнять реальные действия, такие как чтение записи в CRM, открытие заявки в ITSM или запрос к хранилищу данных, требовалось делать ровно одно: писать специальный код для интеграции, предназначенный исключительно для этого агента и этой системы.

Это ещё терпимо, пока существует несколько агентов (бот для поддержки, помощник-кодер, внутренний помощник с знаниями) и несколько систем (Salesforce, Jira, база данных HR, склад). В итоге получается огромное количество вручную созданных соединителей.

BEFORE MCP — the N x M integration problem

  ┌───────────┐        ┌─────────────┐
  │  Agent A  │───────▶│  CRM API    │  (custom connector #1)
  └───────────┘        └─────────────┘
  ┌───────────┐        ┌─────────────┐
  │  Agent A  │───────▶│  Ticketing  │  (custom connector #2)
  └───────────┘        └─────────────┘
  ┌───────────┐        ┌─────────────┐
  │  Agent B  │───────▶│  CRM API    │  (custom connector #3 -
  └───────────┘        └─────────────┘   yes, AGAIN, for a different agent)
  ┌───────────┐        ┌─────────────┐
  │  Agent B  │───────▶│  Data       │  (custom connector #4)
  └───────────┘        │  Warehouse  │
                       └─────────────┘
  N agents x M systems = N x M custom, non-reusable integrations.
  Every new agent re-implements auth, retries, schemas, error handling.

USB решил аналогичную проблему с аппаратными периферийными устройствами, а ODBC и JDBC — с базами данных: слишком много специализированных подключателей и отсутствие общего стандарта. MCP — это решение компании Anthropic, опубликованное в виде открытого протокола, и не является функцией, доступной только для Claude; благодаря этому любое ИИ-приложение и любой бэкенд могут использовать общий язык общения.

AFTER MCP — one protocol, many servers, many clients

  ┌───────────┐                          ┌───────────────────┐
  │  Agent A  │──┐                   ┌─▶│  MCP Server: CRM   │
  └───────────┘  │    ┌───────────┐  │   └───────────────────┘
                 ├───▶│    MCP   │───┤  ┌───────────────────┐
  ┌───────────┐  │    │  (shared  │  ├─▶│ MCP Server: Ticket │
  │  Agent B  │──┘    │  protocol)│  │   └───────────────────┘
  └───────────┘       └───────────┘  │  ┌──────────────────┐
                                     └─▶│ MCP Server: DW   │
                                        └──────────────────┘
  Any MCP-compatible agent can now talk to any MCP server.
  Build the connector once, reuse it everywhere.

Суть можно изложить в одном предложении. MCP предоставляет приложениям на основе ИИ единый способ нахождения инструментов, вызова их, чтения данных и использования шаблонов запросов, благодаря чему интеграция пишется один раз, и любой соответствующий клиент может её повторно использовать.

2. Основные понятия простыми словами

Четыре термина используются довольно свободно, поэтому они определяются до написания любого кода.

  • Хост. Приложение на основе ИИ, с которым взаимодействует конечный пользователь — например, внутренний чат-ассистент, плагин IDE или помощник для обработки запросов; именно в нём находится LLM.
  • Клиент MCP. Компонент внутри хоста, отвечающий за управление единственным подключением к одному серверу MCP. Хост, взаимодействующий с тремя бэкендами, обычно содержит три таких клиента, по одному на каждый сервер.
  • Сервер MCP. Легковесная программа, которая предоставляет возможности определённой системы (инструменты, данные, команды) в соответствии с спецификацией MCP. Как разработчик в корпоративной среде, обычно именно её вы и создаёте.
  • Средство передачи данных. Канал, посредством которого взаимодействуют клиент и сервер. Существуют два основных варианта: stdio, при котором клиент запускает сервер как локальный дочерний процесс и обменивается сообщениями через стандартный ввод и вывод (удобно для локальной разработки и десктопных приложений), и Streamable HTTP, при котором сервер находится удалённо за протоколом HTTPS (обычный выбор для общедоступных серверов, работающих централизованно).
  • По сути, все сообщения используют JSON-RPC 2.0 — простой и хорошо устоявшийся формат для запросов, ответов и уведомлений. SDK генерирует такой формат автоматически, но понимание его структуры помогает понять, почему инструменты отладки MCP похожи на инструменты API, которые вы уже используете: они включают идентификатор запроса, имя метода, параметры, а также результат или ошибку.

    Что может предложить сервер: три примитива

    • Инструменты — это функции, которые можно вызывать, чтобы заставить модель выполнять действия или получать вычисленные результаты; модель сама решает, когда их вызвать, в зависимости от запроса пользователя. Примеры: create_support_ticket, get_employee_details.
    • Ресурсы — это только для чтения данные, которые хост может добавить в качестве контекста, например файлы или записи. Применение или пользователь определяют, какие ресурсы добавить. Примеры: статья из базы знаний, PDF-документ с правилами, файл журнала.
  • Инструкции — это повторно используемые, параметризованные шаблоны команд, которые выбирает пользователь, подобно командам с слешом. Пример: «Подготовьте краткое резюме этого инцидента для анализа после инцидента».
  • В большинстве корпоративных интеграций инструменты выполняют примерно 90% важной работы, поэтому они создаются в первую очередь.

    3. Архитектура: три слоя вместе

    ┌─────────────────────────── HOST APPLICATION ───────────────────────────┐
    │   e.g. an internal AI assistant, IDE plugin, support copilot           │
    │                                                                        │
    │   ┌───────────────┐        ┌───────────────┐       ┌───────────────┐   │
    │   │  MCP Client 1 │        │  MCP Client 2 │       │  MCP Client 3 │   │
    │   └───────┬───────┘        └───────┬───────┘       └───────┬───────┘   │
    └───────────┼────────────────────────┼───────────────────────┼───────────┘
                │ JSON-RPC over          │ JSON-RPC over         │ JSON-RPC over
                │ stdio / HTTPS          │ stdio / HTTPS         │ stdio / HTTPS
                ▼                        ▼                       ▼
       ┌──────────────────┐     ┌──────────────────┐     ┌─────────────────┐
       │   MCP Server     │     │   MCP Server     │     │   MCP Server    │
       │  wraps HR system │     │  wraps Ticketing │     │  wraps Data     │
       │  (tools: lookup, │     │  (tools: create, │     │  Warehouse      │
       │   update)        │     │   status, close) │     │  (tools: query) │
       └──────────────────┘     └──────────────────┘     └─────────────────┘
    

    Каждый сервер MCP должен быть маленьким и специализированным: существует отдельный сервер для каждой системы бэкенда, без единого гигантского сервера, охватывающего всё. Эта концепция заимствована из микросервисов — с ограниченным радиусом воздействия, возможностью независимой развертки, масштабирования, а также ответственностью за него лежит у команды, управляющей соответствующим бэкендом.

    4. Создание первого сервера MCP (Node.js и TypeScript)

    Примером может служить сервер MCP для службы технической поддержки, включающий два инструмента: один для создания заявки на поддержку, другой — для проверки статуса заявки. Он отражает очень распространённую корпоративную практику использования внутренних API для управления заявками или ITSM-систем.

    4.1 Настройка проекта

    mkdir helpdesk-mcp-server && cd helpdesk-mcp-server
    npm init -y
    npm install @modelcontextprotocol/sdk zod
    npm install -D typescript tsx @types/node
    npx tsc --init
    

    @modelcontextprotocol/sdk — это официальный пакет. Он обрабатывает передачу данных в формате JSON-RPC, транспорты и примитивы, поэтому с самим JSON-RPC не приходится работать вручную. zod — это библиотека для проверки схем. Входные данные инструментов MCP проверяются по схеме ещё до запуска кода-обработчика, что имеет огромное значение в корпоративных средах, поскольку некорректные или вредоносные запросы блокируются на этапе входа.

    4.2 Код сервера

    // src/server.ts
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import { z } from "zod";
    
    // --- A stand-in for a real internal ticketing API client ---
    // In a real enterprise server this would call your ITSM system
    // (ServiceNow, Jira Service Management, Zendesk, an internal API, etc.)
    const ticketStore = new Map<string, { status: string; subject: string }>();
    let nextId = 1000;
    // 1. Create the server instance.
    //    "name" and "version" identify this server to any client that connects.
    const server = new McpServer({
      name: "helpdesk-mcp-server",
      version: "1.0.0",
    });
    // 2. Register a tool: create_support_ticket
    server.registerTool(
      "create_support_ticket",
      {
        title: "Create Support Ticket",
        description:
          "Creates a new IT helpdesk ticket for the requesting employee.",
        inputSchema: {
          subject: z.string().describe("Short summary of the issue"),
          priority: z.enum(["low", "medium", "high", "urgent"]),
          employeeId: z.string().describe("Requesting employee's ID"),
        },
        outputSchema: {
          ticketId: z.string(),
          status: z.string(),
        },
      },
      async ({ subject, priority, employeeId }) => {
        const ticketId = `TCK-${nextId++}`;
        ticketStore.set(ticketId, { status: "open", subject });
        const output = { ticketId, status: "open" };
        // MCP tool results return a "content" array (what a human/LLM reads)
        // and, optionally, "structuredContent" (typed data other code can use).
        return {
          content: [
            {
              type: "text",
              text: `Created ticket ${ticketId} (priority: ${priority}) for employee ${employeeId}.`,
            },
          ],
          structuredContent: output,
        };
      }
    );
    // 3. Register a second tool: get_ticket_status
    server.registerTool(
      "get_ticket_status",
      {
        title: "Get Ticket Status",
        description: "Looks up the current status of an existing support ticket.",
        inputSchema: {
          ticketId: z.string(),
        },
        outputSchema: {
          status: z.string(),
        },
      },
      async ({ ticketId }) => {
        const ticket = ticketStore.get(ticketId);
        if (!ticket) {
          // Returning isError lets the model know the call failed
          // WITHOUT crashing the whole conversation.
          return {
            content: [{ type: "text", text: `No ticket found with ID ${ticketId}.` }],
            isError: true,
          };
        }
        return {
          content: [{ type: "text", text: `Ticket ${ticketId} is currently "${ticket.status}".` }],
          structuredContent: { status: ticket.status },
        };
      }
    );
    // 4. Wire the server to a transport and start listening.
    //    stdio is perfect for local development and desktop-hosted tools.
    const transport = new StdioServerTransport();
    await server.connect(transport);
    

    4.3 Функции каждой части

    • Экземпляр McpServer. Этот объект является сердцем сервера. Он отслеживает все зарегистрированные инструменты, ресурсы и команды, а также обрабатывает процедуру взаимодействия MCP (согласование возможностей) при подключении клиента. Список инструментов никогда не создается вручную, поскольку SDK формирует его на основе вызовов registerTool.
    • inputSchema с использованием Zod. Это не просто документация — SDK использует её для генерации JSON Schema, которое указывает клиенту и большой языковой модели, какие аргументы допустимы, а также для проверки каждого поступающего запроса. Клиент, передающий значение priority: "banana", отклоняется ещё до того, как запрос дойдёт до обработчика. Для корпоративных проектов такая проверка с использованием схемы является важным средством обеспечения безопасности, а не просто удобством.
  • outputSchema и structuredContent. Инструмент может возвращать краткое описание, понятное человеку (content), а также объект, доступный для обработки компьютером (structuredContent). Текст отображается в протоколе чата, в то время как приложение-запросчик может парсировать структурированные данные для формирования интерфейса или выполнения дальнейшей логики.
  • isError: true. В MCP существует четко определенное понятие ошибки на уровне инструмента, отличное от ошибки на уровне протокола. Это означает, что операция завершилась неудачно, но диалог должен продолжаться; модель может прочитать ошибку и решить, стоит ли попробовать снова, спросить у пользователя или использовать другой инструмент. Не следует допускать сбоя всей сессии агента из-за отсутствия одного ID заявки.
  • Транспорт (StdioServerTransport). Элемент, отвечающий за передачу байтов. Для локальных инструментов, таких как IDE разработчика или приложения-ассистента на рабочем столе, стандартный вариант с использованием стандартного ввода-вывода является наиболее простым, поскольку хост запускает сервер в качестве дочернего процесса. В случае централизованной работы в корпоративной среде используется транспорт на основе HTTP, описанный в разделе 6.
  • 4.4 Запуск

    npx tsx src/server.ts
    

    Сам по себе сервер ничего не показывает, поскольку он ожидает подключения клиента через stdin и stdout. Далее идет клиент.

    5. Клиент внутри корпоративного приложения

    Новички часто создают сервер, а затем задаются вопросом, что на самом деле с ним взаимодействует. Вот минимальный клиент, представленный как часть внутреннего сервера на Node.js, обеспечивающего работу ИИ-ассистента компании.

    // src/client.ts
    import { Client } from "@modelcontextprotocol/sdk/client/index.js";
    import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
    
    async function main() {
      // 1. Describe how to launch the server. Here we spawn it as a
      //    local subprocess - in production you'd more commonly point
      //    this at a remote HTTP-based server instead (see Section 6).
      const transport = new StdioClientTransport({
        command: "npx",
        args: ["tsx", "src/server.ts"],
      });
      // 2. Create a client and connect. This performs the MCP
      //    handshake and capability negotiation automatically.
      const client = new Client({ name: "internal-ai-assistant", version: "1.0.0" });
      await client.connect(transport);
      // 3. Discover what tools this server offers - this is the same
      //    mechanism an LLM uses to "learn" what it can do.
      const { tools } = await client.listTools();
      console.log("Available tools:", tools.map((t) => t.name));
      // 4. Call a tool, just like the LLM would.
      const result = await client.callTool({
        name: "create_support_ticket",
        arguments: {
          subject: "VPN keeps disconnecting",
          priority: "high",
          employeeId: "E-4821",
        },
      });
      console.log(result.content);
      await client.close();
    }
    main();
    

    Почему это важно с концептуальной точки зрения

    Основная идея заключается в открытии новых возможностей. Клиент вызывает функцию listTools() и получает машинно читаемый каталог всего, что предлагает сервер, причем каждый инструмент сопровождается своей схемой. Именно это делает MCP динамичным: хост, основанный на технологии LLM, не нуждается в предустановленных инструментах в своем запросе. При подключении он спрашивает сервер, что тот может сделать, и ответ становится частью информации, которая сообщается модели о возможных действиях в рамках данного разговора.

    Управление в корпоративной среде основывается именно на этом механизме. Если какой-либо инструмент не должен быть доступен определенному пользователю или среде, наиболее эффективным способом контроля является его отсутствие в результатах вызова функции listTools(), а не надежда на то, что модель сама решит его не использовать.

    6. Переход от локального прототипа к корпоративному внедрению

    Настройка stdio подходит для обучения и использования локальных инструментов разработчика, но она не соответствует способу работы инфраструктуры в большинстве предприятий: инфраструктура размещается централизованно, защищена механизмами аутентификации, ведется логирование, и к ней одновременно могут обращаться множество приложений-хостов. Именно здесь на сцену выходят транспорт Streamable HTTP и API-шлюз.

    Вот тот же сервер службы поддержки, адаптированный для работы через HTTP и размещенный за простым приложением Express:

    // src/httpServer.ts
    import express from "express";
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
    
    const app = express();
    app.use(express.json());
    app.post("/mcp", async (req, res) => {
      // In a real enterprise deployment, authentication middleware would
      // run BEFORE this point - verifying a bearer token, checking scopes,
      // and attaching the caller's identity to the request.
      const server = buildHelpdeskServer(); // same registerTool calls as before
      const transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: undefined, // stateless mode: simplest to scale horizontally
      });
      res.on("close", () => transport.close());
      await server.connect(transport);
      await transport.handleRequest(req, res, req.body);
    });
    app.listen(3000, () => console.log("MCP server listening on :3000"));
    

    Существенное изменение касается скорее не кода, а точки, где лежит ответственность. Три аспекта, которые ранее были неявными в stdio, становятся явными проблемами корпоративного масштаба в среде HTTP.

    1. Аутентификация происходит до сервера, обычно в промежуточном слое или шлюзе, что позволяет серверу считать, что переменная req уже содержит проверенную идентичность.
    2. Отсутствие состояния — это осознанное решение. При настройке sessionIdGenerator: undefined транспорт не сохраняет информацию о сессии для каждого соединения, поэтому любая из инстансов, расположенных за балансировщиком нагрузки, может обработать любой запрос, что позволяет масштабировать систему как любой безсостоянийный API.
    3. Возможность отслеживания больше не является факультативной. Каждый вызов инструмента представляет собой настоящий сетевой запрос, поэтому его следует логировать, отслеживать и ограничивать по частоте, как и любой внутренний вызов API.

    7. Чек-лист факторов, учитываемых в корпоративных проектах

    Запуск сервера MCP — это лишь простая пятая часть работы. Оставшиеся четыре пятых связаны с обеспечением его безопасности при интеграции с производственными системами, хранящими реальные данные сотрудников или клиентов, и эта часть выглядит следующим образом.

    • Аутентификация и авторизация. Используйте OAuth2 или OIDC на шлюзе. Сопоставляйте идентификатор вызывающего пользователя или сервисного аккаунта с конкретными разрешениями определенного диапазона, чтобы не каждый инструмент был доступен каждому пользователю.
    • Проектирование инструментов с минимальными привилегиями. Предпочитайте узкоспециализированные инструменты, такие как get_ticket_status, вместо общих, таких как run_sql_query. Инструмент, выполняющий одну четко определенную функцию, гораздо проще для анализа и аудита.
  • Проверка входных данных. Рассматривайте проверку схемы, как и в случае с Zod выше, как настоящий защитный барьер, а не просто удобство для проверки типов.
  • Участие человека при выполнении разрушительных действий. При любых необратимых операциях, таких как удаление записи, отправка внешнего электронного письма или одобрение платежа, требуйте явного подтверждения от пользователя перед тем, как инструмент выполнит действие.
  • Логирование для аудита. Фиксируйте, кто вызывал каждый инструмент, с какими аргументами и с каким результатом. Именно это позволяет позже объяснить действия, выполненные с помощью ИИ, что является строгим требованием в регулируемых сферах, таких как здравоохранение и финансы.
  • Ограничения скорости и таймауты. Применяйте те же механизмы контроля, что и к другому внутреннему трафику API, чтобы агент, застрявший в цикле, не мог засорить бэкенд.
  • Идемпотентность. Обеспечивайте безопасность использования инструментов с побочными эффектами при двукратном вызове с одинаковыми данными, поскольку агенты иногда пытаются выполнить операцию заново.
  • Версионирование. По мере изменения схем инструментов намеренно создавайте для них версии, чтобы старые версии не приводили к скрытым сбоям.
  • 8. Где это применяется в реальных компаниях

    • IT-отдел и внутренняя поддержка. Пример службы поддержки: создание и поиск заявок, сброс паролей, проверка статуса системы.
    • Помощники в продажах и CRM. Получение истории счетов и составление писем-продолжений на основе реальных данных CRM, а не предположений модели.
    • Помощники по обработке данных и аналитике. Набор инструментов для чтения с заранее утвержденными запросами к хранилищу данных вместо неограниченного использования SQL.
    • Рабочие процессы в сфере здравоохранения. Инструменты для поиска свободных временных слотов для приема, проверки наличия страховки или выявления пробелов в координации медицинской помощи, которые всегда работают в рамках строгого контроля доступа в зависимости от роли пользователя и ведут полный журнал аудита из-за чувствительности данных. Здесь принципы использования узкоспециализированных инструментов, участия человека в процессе принятия решений и ведения полного журнала аудита, изложенные в разделе 7, часто являются требованием к соответствию нормативам.
    • Инструменты для разработчиков. Ассистенты в IDE, которые позволяют запрашивать внутреннюю документацию, запускать процессы CI или проверять, кто находится на дежурстве для обслуживания той или иной службы.

    Что у них общего: MCP не делает модель более умной. Изменяется лишь то, что ИИ получает контролируемый, аудируемый доступ к реальным системам и больше не обязан выдумывать правдоподобные ответы.

    9. Ошибки, с которыми сталкиваются начинающие

    • Один гигантский сервер, выполняющий всё. Разделение на отдельные системы или домены нарушает принципы хорошо структурированных микросервисов и увеличивает риск распространения сбоев.
    • Игнорирование полей description. Модель в значительной степени опирается на описания инструментов и параметров для принятия решений о том, когда и как что-то вызывать. Неоднозначные описания приводят к использованию неправильного инструмента или правильного инструмента с неверными аргументами.
    • Забывание поля isError. Чистое исключение, выброшенное обработчиком инструмента, может привести к сбою всей клиентской связи, тогда как модели следует сообщить лишь о неудаче и предложить попробовать что-то другое.
    • Использование стандартного ввода-вывода в продакшене. Стандартный ввод-вывод подходит для локальной разработки и настольных систем. В продакшене с несколькими арендаторами требуется использование HTTP-транспорта через настоящий шлюз.
  • Отсутствие журнала аудита. Если вы не можете указать, кто попросил ИИ выполнить действие и что именно он сделал, держите его подальше от производственных данных.
  • 10. Краткое изложение

    Если убрать все модные термины, основная идея MCP оказывается крайне простой: единый общий метод, позволяющий приложениям ИИ находить и вызывать инструменты, загружать данные и повторно использовать шаблоны запросов, что означает, что работа по интеграции выполняется один раз и служит всем соответствующим клиентам. Для корпораций именно такая стандартизация позволяет превратить агентов ИИ из эффектных демонстраций в инструменты, которые можно управлять, защищать и использовать в производственных условиях наряду со всем остальным.

    Для тех, кто только начинает, разумный путь заключается в следующем:

    1. Создайте небольшой сервер MCP локально с использованием стандартных ввода-вывода, как в примере с службой поддержки.
    2. Добавьте клиент, который будет вызывать эти инструменты, чтобы весь процесс был виден.
    3. Разместите сервер за протоколом HTTP с аутентификацией или с шлюзом впереди.
    4. Только после этого переходите к ведению журнала аудита, ограничению частоты запросов и необходимости утверждения человеком любых деструктивных действий.

    Начните с малого и локальных решений, постепенно внедряя корпоративные меры безопасности. Попытка с самого начала создать идеальный производственный сервер — это гораздо более рискованный подход.