Поделиться через


Создание сервера MCP TypeScript с помощью приложений контейнеров Azure

В этой статье объясняется, как создать сервер протокола MCP модели с помощью Node.js и TypeScript. Сервер запускает средства и службы в бессерверной среде. Используйте эту структуру в качестве отправной точки для создания пользовательских серверов MCP.

Перейдите к коду

Ознакомьтесь с примером сервера Протокола контекста модели TypeScript (MCP ). В нем показано, как использовать Node.js и TypeScript для создания удаленного сервера MCP и развертывания его в приложениях контейнеров Azure.

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

Обзор архитектуры

На следующей схеме показана простая архитектура примера приложения: схема, показывающая архитектуру из Visual Studio Code, в которой размещен агент и клиент MCP на СЕРВЕР MCP.

Сервер MCP выполняется как контейнеризованное приложение в Azure Container Apps (ACA). Серверная часть Node.js/TypeScript используется для предоставления инструментов клиенту MCP через протокол контекста модели. Все средства работают с серверной базой данных SQLite.

Себестоимость

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

Предпосылки

  1. Visual Studio Code — последняя версия для поддержки разработки сервера MCP.
  2. GitHub Copilot расширение для Visual Studio Code
  3. Расширение Чат GitHub Copilot для Visual Studio Code
  4. Azure Developer CLI (azd)

Контейнер разработки включает все зависимости, необходимые для этой статьи. Его можно запустить в GitHub Codespaces (в браузере) или локально с помощью Visual Studio Code.

Чтобы следовать этой статье, убедитесь, что выполнены следующие предварительные требования:

Открытие среды разработки

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

GitHub Codespaces запускает контейнер разработки, управляемый GitHub, с интерфейсом Visual Studio Code в браузере. Используйте GitHub Codespaces для самой простой настройки, так как она поставляется с необходимыми средствами и зависимостями, предварительно установленными для этой статьи.

Это важно

Все учетные записи GitHub могут использовать Codespaces в течение до 60 часов бесплатно каждый месяц на двухядровых экземплярах. Дополнительные сведения см. в ежемесячно предоставляемой информации о хранилище и основных часах в GitHub Codespaces.

Выполните следующие действия, чтобы создать новое пространство кода GitHub в main ветви Azure-Samples/mcp-container-ts репозитория GitHub.

  1. Щелкните правой кнопкой мыши следующую кнопку и нажмите кнопку "Открыть ссылку" в новом окне. Это действие позволяет иметь среду разработки и документацию, открытую параллельно.

    Открыть в GitHub Codespaces

  2. На странице "Создание пространства кода " просмотрите и выберите "Создать новое пространство кода".

  3. Дождитесь запуска рабочего пространства кода. Это может занять несколько минут.

  4. Войдите в Azure с помощью интерфейса командной строки разработчика Azure в терминале в нижней части экрана.

    azd auth login
    
  5. Скопируйте код из терминала и вставьте его в браузер. Следуйте инструкциям по проверке подлинности с помощью учетной записи Azure.

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

Замечание

Чтобы запустить сервер MCP локально, выполните следующие действия.

  1. Настройте среду, как описано в разделе установки локальной среды в примере репозитория.
  2. Настройте сервер MCP для использования локальной среды, следуя инструкциям в разделе "Настройка сервера MCP в Visual Studio Code" в примере репозитория.
  3. Перейдите к разделу Использовать средства сервера TODO MCP в режиме агента, чтобы продолжить.

Развертывание и запуск

Пример репозитория содержит все файлы кода и конфигурации для развертывания сервера MCP Azure. Ниже приведен пример процесса развертывания сервера MCP Azure.

Развертывание в Azure

Это важно

Ресурсы Azure в этом разделе начинают стоить деньги немедленно, даже если вы остановите команду, прежде чем она завершится.

  1. Выполните следующую команду Azure Developer CLI для подготовки ресурсов Azure и развертывания исходного кода:

    azd up
    
  2. Используйте следующую таблицу, чтобы ответить на запросы:

    Подсказка Ответ
    Имя среды Сохраните его коротким и в нижнем регистре. Добавьте имя или псевдоним. Например: my-mcp-server. Он используется в качестве части имени группы ресурсов.
    Подписка Выберите подписку, в которой вы хотите создать ресурсы.
    Расположение (для размещения) Выберите место рядом с вами из списка.
    Расположение для модели Azure OpenAI Выберите место рядом с вами из списка. Если то же местоположение доступно в качестве вашего первого выбора, выберите его.
  3. Дождитесь развертывания приложения. Развертывание обычно занимает от 5 до 10 минут.

  4. После завершения развертывания можно получить доступ к серверу MCP с помощью URL-адреса, предоставленного в выходных данных. URL-адрес выглядит следующим образом:

https://<env-name>.<container-id>.<region>.azurecontainerapps.io
  1. Скопируйте URL-адрес в буфер обмена. Он понадобится в следующем разделе.

Настройка сервера MCP в Visual Studio Code

Настройте сервер MCP в локальной среде VS Code, добавив URL-адрес mcp.json в файл в папку .vscode .

  1. Откройте файл mcp.json в папке .vscode.

  2. Найдите mcp-server-sse-remote раздел в файле. Это должно выглядеть следующим образом:

        "mcp-server-sse-remote": {
        "type": "sse",
        "url": "https://<container-id>.<location>.azurecontainerapps.io/sse"
    }
    
  3. Замените существующее url значение URL-адресом, скопированным на предыдущем шаге.

  4. Сохраните mcp.json файл в папке .vscode .

Использование средств сервера TODO MCP в режиме агента

После изменения сервера MCP можно использовать средства, которые он предоставляет в режиме агента. Чтобы использовать средства MCP в режиме агента:

  1. Откройте представление чата (Ctrl+Alt+I) и выберите режим агента в раскрывающемся списке.

  2. Нажмите кнопку "Сервис" , чтобы просмотреть список доступных средств. При необходимости выберите или отмените выбор инструментов, которые вы хотите использовать. Вы можете искать инструменты, вводя их в поле поиска.

  3. Введите запрос, например "Мне нужно отправить сообщение электронной почты моему менеджеру в среду" в поле ввода чата и обратите внимание, как средства автоматически вызываются по мере необходимости, как показано на следующем снимке экрана:

    Снимок экрана: вызов серверных средств MCP.

Замечание

По умолчанию при вызове средства необходимо подтвердить действие перед запуском средства. В противном случае средства могут выполняться локально на компьютере и выполнять действия, изменяющие файлы или данные.

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

Изучение примера кода

В этом разделе представлен обзор ключевых файлов и структуры кода в примере сервера MCP. Код организован в несколько основных компонентов:

  • index.ts: основная точка входа сервера MCP, которая настраивает Express.js HTTP-сервер и маршрутизацию.
  • server.ts: транспортный слой, который управляет подключениями SSE и событиями Server-Sent, а также обработкой протокола MCP.
  • tools.ts: содержит бизнес-логику и служебные функции для сервера MCP.
  • types.ts: определяет типы и интерфейсы TypeScript, используемые на сервере MCP.

index.ts — как сервер запускает и принимает HTTP-подключения.

Файл index.ts является основной точкой входа для сервера MCP. Он инициализирует сервер, настраивает Express.js HTTP-сервер и определяет маршрутизацию для конечных точек Server-Sent событий (SSE).

Создание экземпляра сервера MCP

Следующий фрагмент кода инициализирует сервер MCP с помощью StreamableHTTPServer класса, который является оболочкой для основного класса MCP Server . Этот класс обрабатывает транспортный слой для Server-Sent событий (SSE) и управляет клиентскими подключениями.

const server = new StreamableHTTPServer(
  new Server(
    {
      name: 'todo-http-server',
      version: '1.0.0',
    },
    {
      capabilities: {
        tools: {},
      },
    }
  )
);

Основные понятия:

  • Шаблон композиции: SSEPServer обертывает низкоуровневый Server класс
  • Объявление возможностей: сервер объявляет, что поддерживает средства (но не ресурсы или запросы)
  • Соглашение об именовании: имя сервера становится частью идентификации MCP

Настройка маршрутов Express

Следующий фрагмент кода настраивает сервер Express.js для обработки входящих HTTP-запросов для подключений SSE и обработки сообщений:

router.post('/messages', async (req: Request, res: Response) => {
  await server.handlePostRequest(req, res);
});

router.get('/sse', async (req: Request, res: Response) => {
  await server.handleGetRequest(req, res);
});

Основные понятия:

  • Шаблон двух конечных точек: GET для установления подключения SSE, POST для отправки сообщений
  • Шаблон делегирования: экспресс-маршруты немедленно делегируются в SSEPServer

Управление жизненным циклом процессов

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

process.on('SIGINT', async () => {
  log.error('Shutting down server...');
  await server.close();
  process.exit(0);
});

Основные понятия:

  • Корректное завершение работы: правильная очистка на CTRL+C
  • Асинхронная очистка: операция закрытия сервера асинхронна
  • Управление ресурсами: важно для подключений SSE

Транспортный уровень: server.ts

Файл server.ts реализует транспортный уровень для сервера MCP, в частности, обрабатывая подключения событий Server-Sent (SSE) и идентификацию и маршрутизацию сообщений протокола MCP.

Настройте подключение клиента SSE и создайте транспорт

Класс SSEPServer является основным уровнем транспорта для обработки событий Server-Sent (SSE) на сервере MCP. Он использует SSEServerTransport класс для управления отдельными клиентскими подключениями. Он управляет несколькими транспортами и их жизненным циклом.

export class SSEPServer {
  server: Server;
  transport: SSEServerTransport | null = null;
  transports: Record<string, SSEServerTransport> = {};

  constructor(server: Server) {
    this.server = server;
    this.setupServerRequestHandlers();
  }
}

Основные понятия:

  • Управление состоянием: отслеживает как текущий транспорт, так и все транспорты
  • Сопоставление сессий: transports идентификаторы сессий сопоставляются с экземплярами транспорта
  • Делегирование конструктора: немедленно настраивает обработчики запросов

Создание подключения SSE (handleGetRequest)

Метод handleGetRequest отвечает за установку нового подключения SSE при выполнении клиентом запроса GET к конечной точке /sse .

async handleGetRequest(req: Request, res: Response) {
  log.info(`GET ${req.originalUrl} (${req.ip})`);
  try {
    log.info("Connecting transport to server...");
    this.transport = new SSEServerTransport("/messages", res);
    TransportsCache.set(this.transport.sessionId, this.transport);

    res.on("close", () => {
      if (this.transport) {
        TransportsCache.delete(this.transport.sessionId);
      }
    });

    await this.server.connect(this.transport);
    log.success("Transport connected. Handling request...");
  } catch (error) {
    // Error handling...
  }
}

Основные понятия:

  • Создание транспорта: новый SSEServerTransport для каждого GET-запроса
  • Управление сеансами: автоматически созданный идентификатор сеанса, хранящийся в кэше
  • Обработчики событий: очистка при закрытии подключения
  • Подключение MCP: server.connect() устанавливает подключение к протоколу
  • Асинхронный поток: настройка подключения асинхронна с границами ошибок

Обработка сообщений (handlePostRequest)

Метод handlePostRequest обрабатывает входящие запросы POST для обработки сообщений MCP, отправленных клиентом. Он использует идентификатор сеанса из параметров запроса для поиска соответствующего экземпляра транспорта.

async handlePostRequest(req: Request, res: Response) {
  log.info(`POST ${req.originalUrl} (${req.ip}) - payload:`, req.body);

  const sessionId = req.query.sessionId as string;
  const transport = TransportsCache.get(sessionId);
  if (transport) {
    await transport.handlePostMessage(req, res, req.body);
  } else {
    log.error("Transport not initialized. Cannot handle POST request.");
    res.status(400).json(/* error response */);
  }
}

Основные понятия:

  • Поиск сеанса: использует sessionId параметр запроса для поиска транспорта
  • Проверка сеанса: сначала проверяет подключение SSE.
  • Делегирование сообщений: транспорт фактически обрабатывает сообщения
  • Ответы на ошибки: правильные коды ошибок HTTP для отсутствующих сеансов

Настройка обработчика протокола MCP (setupServerRequestHandlers)

Метод setupServerRequestHandlers регистрирует следующие обработчики для запросов протокола MCP:

  • Обработчик для ListToolsRequestSchema, который возвращает список доступных TODO-инструментов.
  • Обработчик для CallToolRequestSchema, который находит и выполняет запрашиваемый инструмент с предоставленными аргументами.

Этот метод использует схемы Zod для определения ожидаемых форматов запросов и ответов .

private setupServerRequestHandlers() {
  this.server.setRequestHandler(ListToolsRequestSchema, async (_request) => {
    return {
      tools: TodoTools,
    };
  });
  
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;
    
    const tool = TodoTools.find((tool) => tool.name === name);
    if (!tool) {
      return this.createJSONErrorResponse(`Tool "${name}" not found.`);
    }
    
    const response = await tool.execute(args as any);
    return { content: [{ type: "text", text: response }] };
  });
}

Основные понятия:

  • маршрутизацияSchema-Based: использует схемы Zod для обработки типобезопасных запросов
  • Обнаружение инструментов: ListToolsRequestSchema возвращает статический массив TodoTools
  • Выполнение инструмента: CallToolRequestSchema поиск и выполнение инструментов
  • Обработка ошибок: грациозная обработка неизвестных инструментов
  • Формат ответа: структура ответа, совместимая с MCP
  • Безопасность типов: типы TypeScript обеспечивают правильный передачу аргументов

Бизнес-логика: tools.ts

Файл tools.ts определяет фактическую функциональность, доступную клиентам MCP:

  • Метаданные инструмента (имя, описание, схемы)
  • Схемы проверки входных данных
  • Логика выполнения средства
  • Интеграция с уровнем базы данных

Этот сервер MCP определяет четыре средства управления TODO:

  • add_todo: создает новый элемент TODO
  • complete_todo: помечает элемент TODO как завершенный
  • delete_todo: удаляет элемент TODO
  • list_todos: перечисляет все элементы TODO
  • update_todo_text: обновляет текст существующего элемента TODO

Шаблон определения инструментов

Средства определяются как массив объектов, каждый из которых представляет определенную операцию TODO. В следующем фрагменте addTodo кода определяется средство:

{
  name: "addTodo",
  description: "Add a new TODO item to the list...",
  inputSchema: {
    type: "object",
    properties: {
      text: { type: "string" },
    },
    required: ["text"],
  },
  outputSchema: { type: "string" },
  async execute({ text }: { text: string }) {
    const info = await addTodo(text);
    return `Added TODO: ${text} (id: ${info.lastInsertRowid})`;
  },
}

Каждое определение инструмента имеет:

  • name: уникальный идентификатор средства
  • description: краткое описание назначения инструмента
  • inputSchema: схема Zod, определяющая ожидаемый формат входных данных
  • outputSchema: схема Zod, определяющая ожидаемый формат выходных данных
  • execute: функция, реализующая логику средства

Эти определения инструментов импортируются в server.ts и экспонируются через обработчик ListToolsRequestSchema.

Основные понятия:

  • Модульная конструкция инструмента: каждое средство является автономным объектом
  • Проверка схемы JSON: inputSchema определяет ожидаемые параметры
  • Безопасность типов: типы TypeScript соответствуют определениям схемы
  • Асинхронное выполнение: все выполнения инструментов являются асинхронными
  • Интеграция с базой данных: вызовы импортированных функций базы данных
  • ответыHuman-Readable: возвращает форматированные строки, а не необработанные данные

Экспорт массива инструментов

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

export const TodoTools = [
  { /* addTodo */ },
  { /* listTodos */ },
  { /* completeTodo */ },
  { /* deleteTodo */ },
  { /* updateTodoText */ },
];

Основные понятия:

  • Статическая регистрация: инструменты, определенные во время загрузки модуля
  • Структура массива: простой массив упрощает итерацию средств
  • Импорт/экспорт: Чистое разделение от логики сервера

Обработка ошибок выполнения инструмента

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

Средства обрабатывают различные сценарии ошибок:

async execute({ id }: { id: number }) {
  const info = await completeTodo(id);
  if (info.changes === 0) {
    return `TODO with id ${id} not found.`;
  }
  return `Marked TODO ${id} as completed.`;
}

Основные понятия:

  • Проверка ответа на базу данных: используется info.changes для обнаружения сбоев
  • Грациозная деградация: возвращает описательные сообщения об ошибках и вызов
  • User-Friendly ошибки: сообщения, подходящие для интерпретации ИИ

Уровень данных: db.ts

Файл db.ts управляет подключением к базе данных SQLite и обрабатывает операции CRUD для приложения TODO. Он использует библиотеку better-sqlite3 для синхронного доступа к базе данных.

Инициализация базы данных

База данных инициализируется путем подключения к SQLite и создания таблиц, если они не существуют. В следующем фрагменте кода показан процесс инициализации:

const db = new Database(":memory:", {
  verbose: log.info,
});

try {
  db.pragma("journal_mode = WAL");
  db.prepare(
    `CREATE TABLE IF NOT EXISTS ${DB_NAME} (
     id INTEGER PRIMARY KEY AUTOINCREMENT,
     text TEXT NOT NULL,
     completed INTEGER NOT NULL DEFAULT 0
   )`
  ).run();
  log.success(`Database "${DB_NAME}" initialized.`);
} catch (error) {
  log.error(`Error initializing database "${DB_NAME}":`, { error });
}

Основные понятия:

  • In-Memory База данных: :memory: означает, что данные теряются при перезапуске (только демонстрация или тестирование)
  • Режим WAL: Write-Ahead ведение журнала для повышения производительности
  • Определение схемы: простая таблица TODO с автоинкрементным идентификатором
  • Обработка ошибок: Грациозная обработка сбоев инициализации
  • Интеграция с ведением журнала: операции базы данных регистрируются для отладки

Шаблоны операций CRUD

Файл db.ts предоставляет четыре основных операции CRUD для управления элементами TODO:

Создание операции:

export async function addTodo(text: string) {
  log.info(`Adding TODO: ${text}`);
  const stmt = db.prepare(`INSERT INTO todos (text, completed) VALUES (?, 0)`);
  return stmt.run(text);
}

Операция чтения:

export async function listTodos() {
  log.info("Listing all TODOs...");
  const todos = db.prepare(`SELECT id, text, completed FROM todos`).all() as Array<{
    id: number;
    text: string;
    completed: number;
  }>;
  return todos.map(todo => ({
    ...todo,
    completed: Boolean(todo.completed),
  }));
}

Операция обновления:

export async function completeTodo(id: number) {
  log.info(`Completing TODO with ID: ${id}`);
  const stmt = db.prepare(`UPDATE todos SET completed = 1 WHERE id = ?`);
  return stmt.run(id);
}

Операция удаления:

export async function deleteTodo(id: number) {
  log.info(`Deleting TODO with ID: ${id}`);
  const row = db.prepare(`SELECT text FROM todos WHERE id = ?`).get(id) as
    | { text: string }
    | undefined;
  if (!row) {
    log.error(`TODO with ID ${id} not found`);
    return null;
  }
  db.prepare(`DELETE FROM todos WHERE id = ?`).run(id);
  log.success(`TODO with ID ${id} deleted`);
  return row;
}

Основные понятия:

  • Подготовленные выражения: защита от внедрения SQL-кода
  • Приведение типов: явные типы TypeScript для результатов запросов
  • Трансформация данных: конвертация целых чисел SQLite в логические значения
  • Атомарные операции. Каждая функция — это одна транзакция базы данных
  • Согласованность возвращаемых значений: метаданные операции возврата функций
  • Оборонительное программирование: шаблон проверки перед удалением

Проектирование схемы

Схема базы данных определяется в db.ts файле с помощью простой инструкции SQL. В todos таблице есть три поля:

CREATE TABLE todos (
  id INTEGER PRIMARY KEY AUTOINCREMENT,  -- Unique identifier
  text TEXT NOT NULL,                    -- TODO description  
  completed INTEGER NOT NULL DEFAULT 0   -- Boolean as integer
);

Утилиты помощника: helpers/ директория

Каталог helpers/ предоставляет служебные функции и классы для сервера.

Структурированное ведение журнала для отладки и мониторинга: helpers/logs.ts

Файл helpers/logs.ts предоставляет структурированную программу ведения журнала для сервера MCP. Она использует библиотеку debug для логирования и chalk для цветного вывода в консоли.

export const logger = (namespace: string) => {
  const dbg = debug('mcp:' + namespace);
  const log = (colorize: ChalkInstance, ...args: any[]) => {
    const timestamp = new Date().toISOString();
    const formattedArgs = [timestamp, ...args].map((arg) => {
      if (typeof arg === 'object') {
        return JSON.stringify(arg, null, 2);
      }
      return arg;
    });
    dbg(colorize(formattedArgs.join(' ')));
  };

  return {
    info(...args: any[]) { log(chalk.cyan, ...args); },
    success(...args: any[]) { log(chalk.green, ...args); },
    warn(...args: any[]) { log(chalk.yellow, ...args); },
    error(...args: any[]) { log(chalk.red, ...args); },
  };
};

Управление сеансами для транспорта SSE: helpers/cache.ts

Файл helpers/cache.ts используется Map для хранения транспорта SSE по идентификатору сеанса. Такой подход позволяет серверу быстро находить активные подключения и управлять ими.

import type { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse";

export const TransportsCache = new Map<string, SSEServerTransport>();

Замечание

Это TransportsCache простой кэш в памяти. В рабочей среде рекомендуется использовать более надежное решение, например Redis или базу данных для управления сеансами.

Сводка по потоку выполнения

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

Схема, показывающая полный путь запроса от клиента к серверу MCP и обратно.

Работы по очистке GitHub Codespaces

Удалите среду GitHub Codespaces, чтобы использовать максимальное количество бесплатных часов на ядро.

Это важно

Дополнительные сведения о бесплатном хранилище и основном времени работы вашей учетной записи GitHub, см. в Ежемесячное включенное хранилище и основные часы в GitHub Codespaces.

  1. Войдите на панель мониторинга GitHub Codespaces.

  2. Найдите ваши активные пространcтва кода, созданные из репозитория GitHub Azure-Samples//mcp-container-ts.

  3. Откройте контекстное меню для пространства кода и выберите Удалить.

Получите помощь

Добавьте вашу проблему в Issues репозитория.