Выбор фреймворка (Express.js) и определение эндпоинтов
Теперь, когда вы умеете создавать полезные инструменты для ИИ-агентов, настало время подключить их к реальной системе. До этого мы изучали, как писать функции-инструменты на TypeScript — например, для получения данных по API или генерации PDF. Но агент не сможет ими воспользоваться, пока они не станут доступны через MCP-сервер.
Сервер — это мост между LLM (например, Claude или YandexGPT) и вашими инструментами. Он должен понимать, что он может делать, как обрабатывать запросы и как вызывать нужные функции. Для этого MCP определяет строгий стандарт взаимодействия, который реализуется через HTTP-эндпоинты.
Выбор фреймворка: почему Express.js?
Для создания MCP-сервера на TypeScript нам нужен HTTP-фреймворк. Существует несколько вариантов: NestJS, Fastify, Koa, но мы выбираем Express.js — и вот почему:
- ✅ Простота и минимализм — Express.js не навязывает сложную архитектуру, что идеально для старта.
- ✅ Отличная поддержка TypeScript — легко настроить типизацию, автодополнение и контроль ошибок.
- ✅ Огромное сообщество и документация — тысячи примеров, готовых решений и плагинов.
- ✅ Гибкая middleware-архитектура — позволяет легко добавлять логирование, аутентификацию и обработку ошибок позже.
Express.js — как надёжный каркас для дома: он не делает всё за вас, но даёт прочную основу, на которой можно строить быстро и безопасно.
Хотя NestJS предлагает более строгую структуру и подходит для больших проектов, на этапе создания MVP и первого MCP-агента Express.js — лучший выбор. Он позволяет сосредоточиться на логике, а не на конфигурации.
Основные эндпоинты MCP-сервера
MCP-сервер должен реализовывать три ключевых эндпоинта (или маршрута, routes), чтобы быть совместимым с любым MCP-клиентом:
| Эндпоинт | Метод | Назначение |
|---|---|---|
/capabilities | GET | Описание возможностей сервера |
/prompt | POST | Обработка запроса от LLM |
/tool | POST | Вызов конкретного инструмента |
Разберём каждый из них.
/capabilities — визитная карточка сервера
Этот эндпоинт отвечает на GET-запрос и возвращает JSON с описанием всех доступных инструментов. Именно так LLM узнаёт, что может делать ваш агент.
Например, если вы создали инструмент для генерации PDF (как в теме Примеры создания пользовательских инструментов), он должен быть описан здесь — с именем, описанием и параметрами.
Правильный ответ:
{
"tools": [
{
"name": "generatePDF",
"description": "Генерирует PDF-файл из текста",
"input_schema": {
"type": "object",
"properties": {
"content": { "type": "string" }
},
"required": ["content"]
}
}
]
}
Неправильный подход — возвращать пустой объект или не реализовывать этот маршрут вовсе. Без /capabilities LLM просто не узнает, какие инструменты у вас есть.
/prompt — мозг агента
Этот POST-эндпоинт получает от LLM контекст и промпт, а затем возвращает либо ответ, либо запрос на вызов инструмента.
LLM анализирует входящий запрос пользователя и решает: ответить самому или использовать один из инструментов. Например:
Пользователь: "Создай PDF с текстом 'Привет, мир!'"
→ LLM понимает, что нужно использоватьgeneratePDF
→ Отправляет запрос на/prompt
→ Наш сервер должен вернуть команду вызова инструмента
Формат ответа MCP:
{
"type": "tool_call",
"name": "generatePDF",
"args": {
"content": "Привет, мир!"
}
}
Этот эндпоинт — центральный в архитектуре. Именно здесь происходит "принятие решений" на уровне LLM, а сервер лишь передаёт и получает данные.
/tool — исполнитель
Когда LLM решает вызвать инструмент, он отправляет запрос на /tool. Этот POST-эндпоинт получает имя инструмента и аргументы, выполняет функцию и возвращает результат.
Например:
{
"name": "generatePDF",
"args": {
"content": "Привет, мир!"
}
}
→ Сервер вызывает функцию generatePDF, получает ссылку на файл и возвращает:
{
"result": "https://example.com/generated/report.pdf"
}
Затем LLM может использовать этот результат в своём ответе пользователю.
Пример: каркас MCP-сервера на Express.js
Вот минимальный рабочий пример сервера на TypeScript с тремя эндпоинтами:
import express from 'express';
import { generatePDF } from './tools/pdf-tool'; // ваш инструмент из предыдущей темы
const app = express();
app.use(express.json()); // важно: парсинг JSON
// Эндпоинт /capabilities
app.get('/capabilities', (req, res) => {
res.json({
tools: [
{
name: 'generatePDF',
description: 'Генерирует PDF из текста',
input_schema: {
type: 'object',
properties: {
content: { type: 'string' }
},
required: ['content']
}
}
]
});
});
// Эндпоинт /prompt
app.post('/prompt', (req, res) => {
const { prompt, context } = req.body;
// Пока возвращаем вызов инструмента (в реальности — логика LLM)
res.json({
type: 'tool_call',
name: 'generatePDF',
args: { content: 'Пример текста' }
});
});
// Эндпоинт /tool
app.post('/tool', async (req, res) => {
const { name, args } = req.body;
if (name === 'generatePDF') {
const result = await generatePDF(args.content);
res.json({ result });
} else {
res.status(404).json({ error: 'Инструмент не найден' });
}
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`MCP-сервер запущен на http://localhost:${PORT}`);
});
Этот код — минимально жизнеспособный сервер, который можно запустить и подключить к MCP-клиенту. Он использует Express.js, обрабатывает все три эндпоинта и уже интегрирует ваш инструмент generatePDF.
Обратите внимание: мы используем
express.json()— это middleware, которое автоматически парсит входящие JSON. Без него сервер не сможет читать тела запросов.
Связь с предыдущей темой
Вы уже умеете создавать инструменты на TypeScript — отлично! Теперь вы видите, куда они подключаются: через /tool и /capabilities. Именно так ваш код становится частью ИИ-агента.
Что дальше?
Теперь у нас есть понимание, какие эндпоинты нужны и как они работают. Но текущий код — это один файл. В реальном проекте сервер будет расти: появятся новые инструменты, обработка ошибок, логирование, конфигурации.
Как организовать код, чтобы он оставался читаемым и масштабируемым?
Именно этому посвящена следующая тема — Базовая структура проекта MCP-сервера.
Вы уже почти можете "оживить" своего агента. Скоро он будет не просто теорией — а рабочей системой, готовой к интеграции.