MCP для початківців: створення сервера та клієнта, а потім їх використання в корпоративних проектах.
MCP для початківців: проблема N x M, основні ідеї у простих словах, сервер та клієнт допомоги на TypeScript у Node.js, а також перехід до безпечних розгортань через HTTP.
Джерела: MCP Tools Inside Enterprise Applications: A Beginner-Friendly Deep Dive
Кожен, хто нещодавно працював з AI-агентами, стикався з MCP — Model Context Protocol. Він використовується в Claude Desktop, у плагінах IDE, у внутрішніх корпоративних проектах „AI-порталів“ та все частіше у комерційному програмному забезпеченні.
Це посібник для початківців, який не передбачає попереднього досвіду роботи з 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 надає AI-додаткам єдиний спосіб знаходити інструменти, викликати їх, читати дані та використовувати шаблони запитів, тож інтеграцію потрібно писати лише один раз, і будь-який відповідний клієнт може її повторно використовувати.
2. Основні концепції простою мовою
Чотири терміни використовуються досить неформально, тому їх уточнюють ще до написання коду.
- Хост. AI-заснований додаток, з яким працює кінцевий користувач, наприклад внутрішній чат-асистент, плагін IDE чи помічник з підтримкою, і саме він містить LLM.
- Klient 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 IT Helpdesk із двома інструментами: один для створення заявки на підтримку, інший — для перевірки статусу заявки. Він відображає дуже поширену корпоративну практику — інтеграцію внутрішнього 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 handshake (домовленість щодо можливостей) під час підключення клієнта. Список інструментів ніколи не створюється вручну, оскільки SDK формує його на основі викликівregisterTool. inputSchemaз Zod. Це більше, ніж просто документація. SDK використовує його для створення JSON Schema, яке вказує клієнту та LLM, які аргументи є дійсними, а також для перевірки кожного виклику після його отримання. Клієнт, який надсилаєpriority: „banana“, відхиляється ще до того, як досягнеться обробник. Для корпорацій така перевірка на основі схеми є справжнім засобом безпеки, а не просто зручністю.
outputSchema та structuredContent. Інструмент може повертати огляд, зрозумілий людині (content), а також об’єкт, зрозумілий машині (structuredContent). Текст відображається у записі чату, тоді як програма-запитувач може обробити структуровані дані для формування інтерфейсу користувача чи виконання подальшої логіки.isError: true. MCP передбачає окреме поняття помилки на рівні інструменту, відмінне від помилки на рівні протоколу. Це означає, що операція зазнала невдачі, але розмова має продовжуватися, тож модель може прочитати помилку та вирішити, чи спробувати ще раз, запитати користувача чи скористатися іншим інструментом. Ви не хочете, щоб уся сесія агента зупинилася через відсутність певного ідентифікатора замовлення.StdioServerTransport). Частина, яка переміщує байти. Для локальних інструментів, таких як IDE розробника чи десктопний асистент, стандартний варіант stdio є найпростішим, оскільки хост запускає сервер як дочірній процес. Усе, що працює централізовано в корпорації, використовуватиме транспорт на основі 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. Де це використовується у реальних компаніях
- ІТ та внутрішня підтримка. Приклад служби підтримки: створення та пошук заявок, скидання паролів, перевірка статусу системи.
- Помічники у продажах та CRM. Отримання історії облікових записів та складання листів-підтримок на основі реальних даних CRM, а не припущень моделі.
- Асистенти з обробки даних та аналітики. Набір інструментів для запитів з обмеженими правами доступу над складом замість необмеженого SQL.
- Робочі процеси у сфері охорони здоров’я. Інструменти, які шукають вільні часи для прийому, перевіряють наявність страховки чи виявляють проблеми у координації догляду, завжди працюють за суворими правилами доступу та обліку операцій через високу конфіденційність даних. Тут принципи використання вузькоспеціалізованих інструментів, участі людини у процесі та повного обліку операцій, описані у розділі 7, часто є вимогою до дотримання стандартів.
- Інструменти для розробників. Асистенти IDE, які звертаються до внутрішньої документації, запускають процеси CI чи перевіряють, хто знаходиться на чергуванні для певного сервісу.
Що є спільним у всьому цьому: MCP не робить модель розумнішою. Змінюється те, що ШІ отримує контрольований, підлягаючий аудиту доступ до реальних систем та більше не змушений вигадувати правдоподібні відповіді.
9. Підводні камені, з якими стикаються початківці
- Один величезний сервер „роби все“. Розділення за допомогою системи бекенду чи домену. Це відповідає принципам хороших мікросервісів та зменшує масштаб поширення проблем.
- Пропуск полів
description. Модель сильно покладається на описи інструментів та параметрів, щоб вирішити, коли та як щось викликати. Нечіткі описи призводять до використання неправильного інструменту або правильного інструменту з неправильними аргументами. - Забування поля
isError. Необроблена виняток, викинута обробником інструменту, може зруйнувати всю клієнтську з’єднаність, тоді як моделі слід повідомити про невдачу та спробувати щось інше. - Використання stdio у продакшені. Stdio підходить для локальної розробки та настільних серверів. Продакшен з кількома користувачами вимагає використання HTTP-транспорту за допомогою справжнього шлюзу.
10. Коротка версія
Якщо прибрати всі модні терміни, основна ідея MCP залишається дуже простою: один спільний метод для додатків ШІ для пошуку та виклику інструментів, отримання даних та повторного використання шаблонів запитів, що означає, що робота з інтеграцією, виконана один раз, слугує всім клієнтам, які дотримуються стандартів. Для корпорацій саме ця стандартизація дозволяє перетворити агентів ШІ з ефектних демонстрацій на інструменти, які можна керувати, захищати та використовувати в продакшені поруч з усіма іншими системами.
Для тих, хто тільки починає, розумний підхід полягає у наступному:
- Створіть невеликий сервер MCP локально за допомогою stdio, як у прикладі служби підтримки.
- Додайте клієнта, який викликає ці інструменти, щоб весь процес був видимим.
Почніть з малого та локального рівня, поступово впроваджуючи корпоративні вимоги. Спроба створити ідеальний сервер для продакшну вже з першого дня — це набагато ненадійніший шлях до мети.