Compartir vía


Integración de una aplicación de App Service como un servidor MCP para GitHub Copilot Chat (Node.js)

En este tutorial, aprenderá a exponer una funcionalidad de Express.js aplicación a través del Protocolo de contexto de modelo (MCP), agregarla como una herramienta a GitHub Copilot e interactuar con la aplicación mediante lenguaje natural en el modo del agente de Chat de Copilot.

Captura de pantalla que muestra gitHub Copilot que llama al servidor Todos MCP hospedado en Azure App Service.

Si la aplicación web ya tiene características útiles, como compras, reservas de hoteles o administración de datos, es fácil hacer que esas funcionalidades estén disponibles para:

Al agregar un servidor MCP a la aplicación web, se habilita un agente para comprender y usar las funcionalidades de la aplicación cuando responde a las solicitudes del usuario. Esto significa que cualquier cosa que pueda hacer la aplicación, el agente también puede hacerlo.

  • Agregue un servidor MCP a la aplicación web.
  • Pruebe el servidor MCP localmente en el modo del agente de Chat de Copilot de GitHub.
  • Implemente el servidor MCP en Azure App Service y conéctese a él en GitHub Copilot Chat.

Prerequisites

En este tutorial se da por supuesto que está trabajando con el ejemplo que se usa en Tutorial: Implementación de una aplicación web de Node.js + MongoDB en Azure.

Como mínimo, abra la aplicación de ejemplo en GitHub Codespaces e implemente la aplicación ejecutando azd up.

Adición de un servidor MCP a la aplicación web

  1. En el terminal de codespace, agregue los paquetes npm necesarios al proyecto:

    npm install @modelcontextprotocol/sdk@latest zod
    
    1. Abra routes/index.js. Para simplificar el escenario, agregará aquí todo el código del servidor MCP.
  2. En la parte superior de routes/index.js, agregue los siguientes requerimientos:

    const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
    const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
    const { z } = require('zod');
    
  3. En la parte inferior del archivo, sobre module.exports = router;, agregue la siguiente ruta para el servidor 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,
          });
        }
      }
    });
    

    Esta ruta establece el punto de conexión del servidor MCP a <url>/api/mcp y usa el patrón de modo sin estado en el SDK de TypeScript de MCP.

    • server.registerTool() agrega una herramienta al servidor MCP con su implementación.
    • El SDK usa zod para la validación de entrada.
    • description en el objeto de configuración y describe() en inputSchema proporcionan descripciones legibles para herramientas y entradas. Ayudan al agente de llamada a comprender cómo usar las herramientas y sus parámetros.

    Esta ruta duplica la funcionalidad create-read-update-delete (CRUD) de las rutas existentes, que no es necesaria, pero la mantendrá por motivos de simplicidad. Un procedimiento recomendado sería mover la lógica de la aplicación a un módulo y, a continuación, llamar al módulo desde todas las rutas.

Probar el servidor MCP localmente

  1. En el terminal de codespace, ejecute la aplicación con npm start.

  2. Seleccione Abrir en el explorador y agregue una tarea.

    Deje npm start en ejecución. El servidor MCP se está ejecutando ahora en http://localhost:3000/api/mcp.

  3. De nuevo en el espacio de código, abre Copilot Chat y, a continuación, selecciona Modo de agente en el cuadro de entrada.

  4. Seleccione el botón Herramientas y, a continuación, seleccione Agregar más herramientas... en la lista desplegable.

    Captura de pantalla que muestra cómo agregar un servidor MCP en el modo del agente de Chat de Copilot de GitHub.

  5. Seleccione Agregar servidor MCP.

  6. Seleccione HTTP (HTTP o eventos Server-Sent).

  7. En Escriba dirección URL del servidor, escriba http://localhost:3000/api/mcp.

  8. En Escriba id. de servidor, escriba todos-mcp o cualquier nombre que desee.

  9. Seleccione Configuración del área de trabajo.

  10. En una nueva ventana de Chat de Copilot, escriba algo como "Muéstrame las tareas."

  11. De forma predeterminada, GitHub Copilot muestra una confirmación de seguridad al invocar un servidor MCP. Selecciona Continuar.

    Captura de pantalla que muestra el mensaje de seguridad predeterminado de una invocación de MCP en El chat de GitHub Copilot.

    Ahora debería ver una respuesta que indica que la llamada a la herramienta MCP es correcta.

    Captura de pantalla que muestra la respuesta de la llamada a la herramienta MCP en la ventana Chat de GitHub Copilot.

Implementación del servidor MCP en App Service

  1. De nuevo en el terminal de codespace, implemente los cambios confirmando los cambios (método Acciones de GitHub) o ejecute azd up (método de la CLI para desarrolladores de Azure).

  2. En la salida de AZD, busque la dirección URL de la aplicación. La dirección URL tiene este aspecto en la salida de AZD:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <app-url>
     
  3. Una vez que azd up finalice, abre .vscode/mcp.json. Cambie la dirección URL a <app-url>/api/mcp.

  4. Encima de la configuración modificada del servidor MCP, seleccione Iniciar.

    Captura de pantalla que muestra cómo iniciar manualmente un servidor MCP desde el archivo mcp.json local.

  5. Inicie una nueva ventana de Chat de GitHub Copilot. Debería poder ver, crear, actualizar y eliminar tareas en el agente de Copilot.

Procedimientos recomendados de seguridad

Cuando un agente con tecnología de modelos de lenguaje de gran escala (LLM) llama al servidor MCP, tenga en cuenta los ataques de inyección de instrucciones. Tenga en cuenta los siguientes procedimientos recomendados de seguridad:

  • Autenticación y autorización: proteja el servidor MCP con la autenticación de Microsoft Entra para asegurarse de que solo los usuarios o agentes autorizados puedan acceder a sus herramientas. Consulte Secure Model Context Protocol calls to Azure App Service from Visual Studio Code with Microsoft Entra authentication (Protocolo de contexto de modelo seguro de llamadas a Azure App Service desde Visual Studio Code con autenticación de Microsoft Entra ) para obtener una guía paso a paso.
  • Validación y saneamiento de entrada: el código de ejemplo de este tutorial usa zod para la validación de entrada, lo que garantiza que los datos entrantes coincidan con el esquema esperado. Para mayor seguridad, tenga en cuenta lo siguiente:
    • Validar y sanear todas las entradas de usuario antes del procesamiento, especialmente para los campos usados en las consultas de base de datos o la salida.
    • Escape de salida en respuestas para evitar el scripting entre sitios (XSS) si la API la consumen los exploradores.
    • Aplicar esquemas estrictos y valores predeterminados en los modelos para evitar datos inesperados.
  • HTTPS: El ejemplo se basa en Azure App Service, que aplica HTTPS de forma predeterminada y proporciona certificados TLS/SSL gratuitos para cifrar los datos en tránsito.
  • Principio de privilegios mínimos: exponga solo las herramientas y los datos necesarios para su caso de uso. Evite exponer operaciones confidenciales a menos que sea necesario.
  • Limitación de velocidad y limitación: use API Management o middleware personalizado para evitar ataques por denegación de servicio y abuso.
  • Registro y supervisión: registro de acceso y uso de puntos de conexión de MCP para la auditoría y la detección de anomalías. Supervisar la actividad sospechosa.
  • Configuración de CORS: restrinja las solicitudes entre orígenes a dominios de confianza si se accede al servidor MCP desde exploradores. Para obtener más información, vea Habilitar CORS.
  • Actualizaciones periódicas: mantenga actualizadas las dependencias para mitigar las vulnerabilidades conocidas.

Más recursos

Integración de la inteligencia artificial en las aplicaciones de Azure App Service