Backend

Как сделать MCP-сервер для AI-ассистента на Node.js

Как сделать MCP-сервер для AI-ассистента на Node.js

Если вы активно пользуетесь Claude Desktop или другими продвинутыми AI-ассистентами, вы наверняка заметили, что их главная проблема — «изоляция». Модель может отлично писать код, но она не знает, что происходит в вашей локальной базе данных, не видит структуру ваших файлов в реальном времени и не может дернуть API вашего внутреннего сервиса.

Протокол MCP (Model Context Protocol) от Anthropic призван решить эту проблему. По сути, это открытый стандарт, который позволяет создать «мост» между LLM и любыми данными или инструментами. Вместо того чтобы писать сложные интеграции под каждую модель, вы создаете один MCP-сервер, который предоставляет инструменты (tools) и ресурсы, а AI-клиент сам решает, когда и как их вызвать.

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

Что такое MCP на пальцах?

Представьте, что LLM — это очень умный мозг, но у него нет рук. MCP-сервер — это и есть те самые «руки».

Архитектура выглядит так:
LLM (Клиент) $\leftrightarrow$ MCP Host (например, Claude Desktop) $\leftrightarrow$ Ваш MCP Server $\leftrightarrow$ Ваша БД/API/Файлы.

Сервер сообщает клиенту: «Я умею делать вот эти пять вещей (функции)». Когда пользователь просит что-то, что требует этих действий, клиент отправляет запрос серверу, тот выполняет код на Node.js и возвращает результат обратно в контекст модели.

Подготовка среды

Для разработки нам понадобится Node.js (рекомендую версию 18+ или 20 LTS) и менеджер пакетов npm или pnpm.

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

Создаем директорию и настраиваем проект. Мы будем использовать TypeScript, так как в MCP строгая типизация критически важна для того, чтобы модель правильно понимала аргументы функций.

bash
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node ts-node
npx tsc —init

В tsconfig.json убедитесь, что у вас установлены следующие параметры для корректной компиляции:
json
{
«compilerOptions»: {
«target»: «ES2022»,
«module»: «NodeNext»,
«moduleResolution»: «NodeNext»,
«outDir»: «./build»,
«esModuleInterop»: true,
«strict»: true
}
}

Разработка сервера: Пошаговый разбор

Суть любого MCP-сервера заключается в трех вещах: Resources (данные), Tools (функции) и Prompts (шаблоны). В этом примере мы сосредоточимся на Tools, так как это самый мощный функционал.

1. Создание базового экземпляра сервера

Создайте файл index.ts. Начнем с импорта SDK и инициализации сервера.

typescript
import { Server } from «@modelcontextprotocol/sdk/server/index.js»;
import { StdioServerTransport } from «@modelcontextprotocol/sdk/server/stdio.js»;
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from «@modelcontextprotocol/sdk/types.js»;

const server = new Server(
{
name: «my-custom-assistant»,
version: «1.0.0»,
},
{
capabilities: {
tools: {}, // Объявляем, что сервер поддерживает инструменты
},
}
);

2. Описание доступных инструментов

Чтобы AI понял, зачем нужен ваш инструмент, нужно максимально подробно описать его назначение и схему аргументов. Это фактически «документация для нейронки».

typescript
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: «get_system_metrics»,
description: «Возвращает текущую нагрузку на процессор и использование памяти сервера»,
inputSchema: {
type: «object»,
properties: {
detail: {
type: «string»,
description: «Уровень детализации: ‘brief’ или ‘full'»
},
},
},
},
{
name: «read_local_log»,
description: «Читает последние N строк из указанного лог-файла»,
inputSchema: {
type: «object»,
properties: {
filePath: { type: «string» },
lines: { type: «number», default: 10 },
},
required: [«filePath»],
},
},
],
};
});

3. Реализация бизнес-логики (Обработка вызовов)

Теперь нужно прописать, что именно происходит, когда модель вызывает инструмент. Мы используем CallToolRequestSchema.

typescript
import * as fs from «fs/promises»;
import os from «os»;

server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;

switch (name) {
case «get_system_metrics»: {
const freeMem = os.freemem();
const totalMem = os.totalmem();
const load = os.loadavg()[0];

  return {
    content: [{ 
      type: "text", 
      text: `CPU Load: ${load}, RAM Free: ${Math.round(freeMem / 1024 / 1024)} MB / ${Math.round(totalMem / 1024 / 1024)} MB` 
    }],
  };
}

case "read_local_log": {
  const { filePath, lines } = args as { filePath: string, lines: number };
  try {
    const data = await fs.readFile(filePath, "utf8");
    const lastLines = data.split("\n").slice(-lines).join("\n");
    return {
      content: [{ type: "text", text: lastLines }],
    };
  } catch (error: any) {
    return {
      content: [{ type: "text", text: `Ошибка чтения файла: ${error.message}` }],
      isError: true,
    };
  }
}

default:
  throw new Error("Tool not found");

}
});

4. Запуск транспорта

MCP может работать через HTTP или stdio (стандартный ввод/вывод). Для локальных ассистентов (вроде Claude Desktop) используется stdio, так как это проще и безопаснее.

typescript
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(«MCP Server running on stdio»);
}

main().catch((err) => {
console.error(«Fatal error in main():», err);
process.exit(1);
});

Важный нюанс: В MCP-серверах через stdio console.log используется для общения с клиентом. Поэтому все ваши отладочные сообщения должны идти в console.error, иначе протокол «сломается», так как клиент примет ваш лог за ответ сервера.

Сборка и интеграция с клиентом

Теперь скомпилируем проект в JS:
bash
npx tsc

Чтобы подключить этот сервер к Claude Desktop, нужно отредактировать конфигурационный файл claude_desktop_config.json.

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

Добавьте туда ваш сервер:
json
{
«mcpServers»: {
«my-node-server»: {
«command»: «node»,
«args»: [«/абсолютный/путь/к/вашему/проекту/build/index.js»]
}
}
}

Перезапустите Claude Desktop, и вы увидите иконку «молотка», означающую, что инструменты доступны.

Оптимизация и безопасность

Когда вы выводите сервер в продакшн или используете его для работы с реальными данными, помните о следующих моментах:

  1. Валидация путей: В примере с read_local_log мы просто читаем файл. В реальности это огромная дыра в безопасности (Path Traversal). Всегда проверяйте, что путь к файлу находится внутри разрешенной директории.
  2. Таймауты: LLM может ждать ответа довольно долго, но если ваш инструмент «зависнет» (например, тяжелый SQL запрос), клиент может оборвать соединение. Используйте Promise.race для установки жестких лимитов на выполнение.
  3. Размер контекста: Не пытайтесь вернуть в ответе 10 МБ текста. Модели имеют лимит контекстного окна. Если данных много, возвращайте краткую выжимку или ссылку на файл, предложив модели запросить конкретные части.

Итоги

Создание MCP-сервера на Node.js позволяет превратить AI из простого чат-бота в полноценного оператора вашей инфраструктуры. Основной секрет успеха здесь — в качественном описании инструментов. Чем точнее вы опишете description и inputSchema, тем реже модель будет ошибаться в параметрах.

Теперь вы можете расширить этот сервер, добавив подключение к PostgreSQL, интеграцию с Jira API или даже управление умным домом через MQTT. Главное — помнить, что вы создаете интерфейс, который будет использовать не человек, а алгоритм, поэтому однозначность и предсказуемость ответов важнее, чем их красота.