MCP для начинающих: создание сервера и клиента, а затем их внедрение в корпоративные системы.
MCP для начинающих: задача N x M, основные идеи в простых словах, сервер и клиент для службы поддержки на TypeScript в Node.js, а также переход к безопасной развертке через HTTP.
Источники: 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. Хост, взаимодействующий с тремя бэкендами, обычно содержит три таких клиента, по одному на каждый сервер.
По сути, все сообщения используют 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.
- Аутентификация происходит до сервера, обычно в промежуточном слое или шлюзе, что позволяет серверу считать, что переменная
reqуже содержит проверенную идентичность. - Отсутствие состояния — это осознанное решение. При настройке
sessionIdGenerator: undefinedтранспорт не сохраняет информацию о сессии для каждого соединения, поэтому любая из инстансов, расположенных за балансировщиком нагрузки, может обработать любой запрос, что позволяет масштабировать систему как любой безсостоянийный API. - Возможность отслеживания больше не является факультативной. Каждый вызов инструмента представляет собой настоящий сетевой запрос, поэтому его следует логировать, отслеживать и ограничивать по частоте, как и любой внутренний вызов API.
7. Чек-лист факторов, учитываемых в корпоративных проектах
Запуск сервера MCP — это лишь простая пятая часть работы. Оставшиеся четыре пятых связаны с обеспечением его безопасности при интеграции с производственными системами, хранящими реальные данные сотрудников или клиентов, и эта часть выглядит следующим образом.
- Аутентификация и авторизация. Используйте OAuth2 или OIDC на шлюзе. Сопоставляйте идентификатор вызывающего пользователя или сервисного аккаунта с конкретными разрешениями определенного диапазона, чтобы не каждый инструмент был доступен каждому пользователю.
- Проектирование инструментов с минимальными привилегиями. Предпочитайте узкоспециализированные инструменты, такие как
get_ticket_status, вместо общих, таких какrun_sql_query. Инструмент, выполняющий одну четко определенную функцию, гораздо проще для анализа и аудита.
8. Где это применяется в реальных компаниях
- IT-отдел и внутренняя поддержка. Пример службы поддержки: создание и поиск заявок, сброс паролей, проверка статуса системы.
- Помощники в продажах и CRM. Получение истории счетов и составление писем-продолжений на основе реальных данных CRM, а не предположений модели.
- Помощники по обработке данных и аналитике. Набор инструментов для чтения с заранее утвержденными запросами к хранилищу данных вместо неограниченного использования SQL.
- Рабочие процессы в сфере здравоохранения. Инструменты для поиска свободных временных слотов для приема, проверки наличия страховки или выявления пробелов в координации медицинской помощи, которые всегда работают в рамках строгого контроля доступа в зависимости от роли пользователя и ведут полный журнал аудита из-за чувствительности данных. Здесь принципы использования узкоспециализированных инструментов, участия человека в процессе принятия решений и ведения полного журнала аудита, изложенные в разделе 7, часто являются требованием к соответствию нормативам.
- Инструменты для разработчиков. Ассистенты в IDE, которые позволяют запрашивать внутреннюю документацию, запускать процессы CI или проверять, кто находится на дежурстве для обслуживания той или иной службы.
Что у них общего: MCP не делает модель более умной. Изменяется лишь то, что ИИ получает контролируемый, аудируемый доступ к реальным системам и больше не обязан выдумывать правдоподобные ответы.
9. Ошибки, с которыми сталкиваются начинающие
- Один гигантский сервер, выполняющий всё. Разделение на отдельные системы или домены нарушает принципы хорошо структурированных микросервисов и увеличивает риск распространения сбоев.
- Игнорирование полей
description. Модель в значительной степени опирается на описания инструментов и параметров для принятия решений о том, когда и как что-то вызывать. Неоднозначные описания приводят к использованию неправильного инструмента или правильного инструмента с неверными аргументами. - Забывание поля
isError. Чистое исключение, выброшенное обработчиком инструмента, может привести к сбою всей клиентской связи, тогда как модели следует сообщить лишь о неудаче и предложить попробовать что-то другое. - Использование стандартного ввода-вывода в продакшене. Стандартный ввод-вывод подходит для локальной разработки и настольных систем. В продакшене с несколькими арендаторами требуется использование HTTP-транспорта через настоящий шлюз.
10. Краткое изложение
Если убрать все модные термины, основная идея MCP оказывается крайне простой: единый общий метод, позволяющий приложениям ИИ находить и вызывать инструменты, загружать данные и повторно использовать шаблоны запросов, что означает, что работа по интеграции выполняется один раз и служит всем соответствующим клиентам. Для корпораций именно такая стандартизация позволяет превратить агентов ИИ из эффектных демонстраций в инструменты, которые можно управлять, защищать и использовать в производственных условиях наряду со всем остальным.
Для тех, кто только начинает, разумный путь заключается в следующем:
- Создайте небольшой сервер MCP локально с использованием стандартных ввода-вывода, как в примере с службой поддержки.
- Добавьте клиент, который будет вызывать эти инструменты, чтобы весь процесс был виден.
- Разместите сервер за протоколом HTTP с аутентификацией или с шлюзом впереди.
- Только после этого переходите к ведению журнала аудита, ограничению частоты запросов и необходимости утверждения человеком любых деструктивных действий.
Начните с малого и локальных решений, постепенно внедряя корпоративные меры безопасности. Попытка с самого начала создать идеальный производственный сервер — это гораздо более рискованный подход.