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


Интеграция приложения Службы приложений в качестве сервера MCP для чата GitHub Copilot (Node.js)

В этом руководстве вы узнаете, как предоставить функциональные возможности приложения Express.js через протокол MCP, добавить его в GitHub Copilot и взаимодействовать с приложением с помощью естественного языка в режиме агента Copilot Chat.

Снимок экрана: GitHub Copilot, вызывающий сервер Todos MCP, размещенный в службе приложений Azure.

Если веб-приложение уже имеет полезные функции, такие как покупки, бронирование отелей или управление данными, это легко сделать эти возможности доступными для:

Добавив сервер MCP в веб-приложение, вы можете агенту понять и использовать возможности приложения при реагировании на запросы пользователей. Это означает, что ваше приложение может сделать все, что может сделать агент.

  • Добавьте сервер MCP в веб-приложение.
  • Протестируйте сервер MCP локально в режиме агента чата GitHub Copilot.
  • Разверните сервер MCP в Службе приложений Azure и подключитесь к нему в GitHub Copilot Chat.

Prerequisites

В этом руководстве предполагается, что вы работаете с примером, используемым в руководстве. Развертывание веб-приложения Node.js + MongoDB в Azure.

По крайней мере, откройте образец приложения в GitHub Codespaces и разверните приложение, запустив команду azd up.

Добавление сервера MCP в веб-приложение

  1. В терминале пространства кода добавьте необходимые пакеты npm в проект:

    npm install @modelcontextprotocol/sdk@latest zod
    
    1. Открытие маршрутов/index.js. Для простоты сценария вы добавите весь код сервера MCP здесь.
  2. В начале файла routes/index.js добавьте следующие обязательные зависимости:

    const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
    const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
    const { z } = require('zod');
    
  3. В нижней части файла выше module.exports = router; добавьте следующий маршрут для сервера MCP.

    router.post('/api/mcp', async function(req, res, next) {
      try {
        // Stateless server instance for each request
        const server = new McpServer({
          name: "task-crud-server", 
          version: "1.0.0"
        });
    
        // Register tools
        server.registerTool(
          "create_task",
          {
            description: 'Create a new task',
            inputSchema: { taskName: z.string().describe('Name of the task to create') },
          },
          async ({ taskName }) => {
            const task = new Task({
              taskName: taskName,
              createDate: new Date(),
            });
            await task.save();
            return { content: [ { type: 'text', text: `Task created: ${JSON.stringify(task)}` } ] };
          }
        );
    
        server.registerTool(
          "get_tasks",
          {
            description: 'Get all tasks'
          },
          async () => {
            const tasks = await Task.find();
            return { content: [ { type: 'text', text: `All tasks: ${JSON.stringify(tasks, null, 2)}` } ] };
          }
        );
    
        server.registerTool(
          "get_task",
          {
            description: 'Get a task by ID',
            inputSchema: { id: z.string().describe('Task ID') },
          },
          async ({ id }) => {
            try {
              const task = await Task.findById(id);
              if (!task) {
                  throw new Error();
              }
              return { content: [ { type: 'text', text: `Task: ${JSON.stringify(task)}` } ] };
            } catch (error) {
                return { content: [ { type: 'text', text: `Task not found with ID: ${id}` } ], isError: true };
            }
          }
        );
    
        server.registerTool(
          "update_task",
          {
            description: 'Update a task',
            inputSchema: {
              id: z.string().describe('Task ID'),
              taskName: z.string().optional().describe('New task name'),
              completed: z.boolean().optional().describe('Task completion status')
            },
          },
          async ({ id, taskName, completed }) => {
            try {
              const updateData = {};
              if (taskName !== undefined) updateData.taskName = taskName;
              if (completed !== undefined) {
                updateData.completed = completed;
                if (completed === true) {
                  updateData.completedDate = new Date();
                }
              }
    
              const task = await Task.findByIdAndUpdate(id, updateData);
              if (!task) {
                throw new Error();
              }
              return { content: [ { type: 'text', text: `Task updated: ${JSON.stringify(task)}` } ] };
            } catch (error) {
              return { content: [ { type: 'text', text: `Task not found with ID: ${id}` } ], isError: true };
            }
          }
        );
    
        server.registerTool(
          "delete_task",
          {
            description: 'Delete a task',
            inputSchema: { id: z.string().describe('Task ID to delete') },
          },
          async ({ id }) => {
            try {
              const task = await Task.findByIdAndDelete(id);
              if (!task) {
                throw new Error();
              }
              return { content: [ { type: 'text', text: `Task deleted successfully: ${JSON.stringify(task)}` } ] };
            } catch (error) {
              return { content: [ { type: 'text', text: `Task not found with ID: ${id}` } ], isError: true };
            }
          }
        );
    
        // Create fresh transport for this request
        const transport = new StreamableHTTPServerTransport({
          sessionIdGenerator: undefined,
        });
    
        // Clean up when request closes
        res.on('close', () => {
          transport.close();
          server.close();
        });
    
        await server.connect(transport);
        await transport.handleRequest(req, res, req.body);
    
      } catch (error) {
        console.error('Error handling MCP request:', error);
        if (!res.headersSent) {
          res.status(500).json({
            jsonrpc: '2.0',
            error: {
              code: -32603,
              message: 'Internal server error',
            },
            id: null,
          });
        }
      }
    });
    

    Этот маршрут задает конечную точку сервера MCP на <url>/api/mcp и использует шаблон без сохранения состояния в MCP TypeScript SDK.

    • server.registerTool() добавляет средство на сервер MCP вместе с его реализацией.
    • Пакет SDK использует zod для проверки входных данных.
    • description в объекте конфигурации и describe() в inputSchema предоставляют удобочитаемые для пользователя описания для инструментов и входных данных. Они помогают вызывающему агенту понять, как использовать средства и их параметры.

    Этот маршрут дублирует функциональные возможности create-read-update-delete (CRUD) существующих маршрутов, что является излишним, но вы сохраните его для упрощения. Рекомендуется переместить логику приложения в модуль, а затем вызвать модуль из всех маршрутов.

Тестирование сервера MCP локально

  1. В терминале пространства кода запустите приложение, используя npm start.

  2. Выберите "Открыть в браузере", а затем добавьте задачу.

    Оставьте npm start запущенным. Сервер MCP сейчас работает на http://localhost:3000/api/mcp.

  3. Вернитесь в пространство кода, откройте Copilot Chat, а затем выберите режим агента в поле запроса.

  4. Нажмите кнопку "Сервис", а затем в раскрывающемся списке нажмите кнопку "Добавить другие инструменты".

    Снимок экрана: добавление сервера MCP в режиме агента чата GitHub Copilot.

  5. Выберите "Добавить СЕРВЕР MCP".

  6. Выберите HTTP (HTTP или Server-Sent события).

  7. Введите URL-адрес сервера, введите http://localhost:3000/api/mcp.

  8. В Введите идентификатор сервера введите todos-mcp или любое имя по вашему выбору.

  9. Выберите параметры рабочей области.

  10. В новом окне чата Copilot введите примерно следующее "Покажи мне дела".

  11. По умолчанию GitHub Copilot показывает подтверждение безопасности при вызове сервера MCP. Нажмите Продолжить.

    Снимок экрана: сообщение о безопасности по умолчанию из вызова MCP в GitHub Copilot Chat.

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

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

Разверните ваш сервер MCP в App Service.

  1. Вернитесь в терминал codespace, зафиксируйте изменения (метод GitHub Actions) или запустите azd up (метод Azure Developer CLI).

  2. В выходных данных AZD найдите URL-адрес приложения. URL-адрес выглядит следующим образом в выходных данных AZD:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <app-url>
     
  3. После завершения azd up, откройте .vscode/mcp.json. Изменить URL-адрес на <app-url>/api/mcp.

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

    Снимок экрана: запуск сервера MCP вручную из локального mcp.json файла.

  5. Запустите новое окно чата GitHub Copilot. Вы должны иметь возможность просматривать, создавать, обновлять и удалять задачи в агенте Copilot.

Рекомендации по обеспечению безопасности

Когда сервер MCP вызывается агентом, управляемым моделями большого языка (LLM), будьте внимательны к атакам типа внедрение команды. Рассмотрим следующие рекомендации по обеспечению безопасности:

  • Проверка подлинности и авторизация: Защитите сервер MCP с помощью проверки подлинности Microsoft Entra, чтобы доступ имели только авторизованные пользователи или агенты. См. пошаговое руководство по вызовам протокола контекста безопасной модели в службе приложений Azure из Visual Studio Code с проверкой подлинности Microsoft Entra.
  • Проверка входных данных и санитизация. Пример кода в этом руководстве использует zod для проверки входных данных, обеспечивая соответствие входящих данных ожидаемой схеме. Для дополнительной безопасности рассмотрите следующие вопросы:
    • Проверка и очистка всех входных данных пользователя перед обработкой, особенно для полей, используемых в запросах или выходных данных базы данных.
    • Экранирование выходных данных в ответах для предотвращения межсайтового скриптинга (XSS), если API используется браузерами.
    • Применение строгих схем и значений по умолчанию в моделях, чтобы избежать непредвиденных данных.
  • HTTPS: В примере используется служба приложений Azure, которая по умолчанию применяет ПРОТОКОЛ HTTPS и предоставляет бесплатные СЕРТИФИКАТЫ TLS/SSL для шифрования данных во время передачи.
  • Принцип наименьшей привилегии: предоставление только необходимых средств и данных, необходимых для вашего варианта использования. Избегайте предоставления конфиденциальных операций, если это не необходимо.
  • Ограничение скорости и регулирование. Используйте управление API или пользовательское ПО промежуточного слоя для предотвращения злоупотреблений и атак типа "отказ в обслуживании".
  • Ведение журнала и мониторинг. Доступ к журналам и использование конечных точек MCP для аудита и обнаружения аномалий. Отслеживайте подозрительные действия.
  • Конфигурация CORS: ограничьте кросс-доменные запросы до доверенных доменов, если доступ к вашему серверу MCP осуществляется из браузеров. Дополнительные сведения см. в разделе "Включение CORS".
  • Регулярные обновления: обновляйте зависимости, чтобы устранить известные уязвимости.

Дополнительные ресурсы

Интеграция ИИ в приложения Службы приложений Azure