Compartilhar via


Integrar um aplicativo do App Service como um servidor MCP para GitHub Copilot Chat (Node.js)

Neste tutorial, você aprenderá a expor a funcionalidade de um aplicativo Express.js por meio do Protocolo de Contexto de Modelo (MCP), adicioná-lo como uma ferramenta ao GitHub Copilot e interagir com seu aplicativo usando linguagem natural no modo de agente do Copilot Chat.

Captura de tela mostrando o GitHub Copilot chamando o servidor Todos MCP hospedado no Serviço de Aplicativo do Azure.

Se seu aplicativo Web já tiver recursos úteis, como compras, reservas de hotéis ou gerenciamento de dados, é fácil disponibilizar esses recursos para:

Ao adicionar um servidor MCP ao seu aplicativo Web, você permite que um agente entenda e use os recursos do aplicativo quando ele responder aos prompts do usuário. Isso significa que tudo o que seu aplicativo pode fazer, o agente também pode fazer.

  • Adicione um servidor MCP ao seu aplicativo Web.
  • Teste o servidor MCP localmente no modo de agente do GitHub Copilot Chat.
  • Implante o servidor MCP no Serviço de Aplicativo do Azure e conecte-se a ele no GitHub Copilot Chat.

Prerequisites

Este tutorial pressupõe que você esteja trabalhando com o exemplo usado no Tutorial: Implantar um aplicativo Web Node.js + MongoDB no Azure.

No mínimo, abra o aplicativo de exemplo nos Codespaces do GitHub e implante o aplicativo executando azd up.

Adicionar o servidor MCP ao seu aplicativo Web

  1. No terminal do codespace, adicione os pacotes npm necessários ao seu projeto:

    npm install @modelcontextprotocol/sdk@latest zod
    
    1. Abra routes/index.js. Para simplificar o cenário, você adicionará todo o código do servidor MCP aqui.
  2. Na parte superior das rotas/index.js, as seguintes solicitações:

    const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
    const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
    const { z } = require('zod');
    
  3. Na parte inferior do arquivo, acima de module.exports = router;, adicione a seguinte rota para o 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,
          });
        }
      }
    });
    

    Essa rota define o ponto de extremidade do servidor MCP para <url>/api/mcp e usa o padrão de modo sem estado no TypeScript SDK do MCP.

    • server.registerTool() adiciona uma ferramenta ao servidor MCP com sua implementação.
    • O SDK usa zod para validação de entrada.
    • description no objeto de configuração e describe() em inputSchema fornecem descrições legíveis por humanos para ferramentas e entrada. Eles ajudam o agente de chamada a entender como usar as ferramentas e seus parâmetros.

    Essa rota duplica a funcionalidade CRUD (create-read-update-delete) das rotas existentes, o que é desnecessário, mas você a manterá para simplificar. Uma prática recomendada seria mover a lógica do aplicativo para um módulo e, em seguida, chamar o módulo de todas as rotas.

Testar o servidor MCP localmente

  1. No terminal do codespace, execute o aplicativo com npm start.

  2. Selecione Abrir no Navegador e, em seguida, adicione uma tarefa.

    Deixe npm start em execução. Seu servidor MCP está em execução em http://localhost:3000/api/mcp agora.

  3. De volta ao codespace, abra o Copilot Chat e, em seguida, selecione o modo Agente na caixa de prompt.

  4. Selecione o botão Ferramentas e, em seguida, selecione Adicionar Mais Ferramentas... na lista suspensa.

    Captura de tela mostrando como adicionar um servidor MCP no modo de agente do GitHub Copilot Chat.

  5. Selecione Adicionar Servidor MCP.

  6. Selecione HTTP (HTTP ou Eventos Server-Sent).

  7. Na URL do Servidor Enter, digite http://localhost:3000/api/mcp.

  8. No Enter Server ID, digite todos-mcp ou qualquer nome que você quiser.

  9. Selecione Configurações do Workspace.

  10. Em uma nova janela do Copilot Chat, digite algo como "Mostre-me as tarefas pendentes".

  11. Por padrão, o GitHub Copilot mostra uma confirmação de segurança quando você invoca um servidor MCP. Selecione Continuar.

    Captura de tela mostrando a mensagem de segurança padrão de uma invocação MCP no GitHub Copilot Chat.

    Agora você deve ver uma resposta que indica que a chamada à ferramenta MCP foi bem-sucedida.

    Captura de tela mostrando que a resposta da chamada da ferramenta MCP na janela de Chat do GitHub Copilot.

Implantar o servidor MCP no Serviço de Aplicativo

  1. De volta ao terminal do codespace, implante suas alterações confirmando suas alterações (método GitHub Actions) ou execute azd up (método da CLI do Desenvolvedor do Azure).

  2. Na saída do AZD, localize a URL do seu aplicativo. O URL fica assim na saída do AZD:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <app-url>
     
  3. Após o término de azd up, abra .vscode/mcp.json. Altere a URL para <app-url>/api/mcp.

  4. Acima da configuração do servidor MCP modificado, selecione Iniciar.

    Captura de tela mostrando como iniciar manualmente um servidor MCP do arquivo de mcp.json local.

  5. Inicie uma nova janela do GitHub Copilot Chat. Você deve ser capaz de exibir, criar, atualizar e excluir tarefas no agente copilot.

Melhores práticas de segurança

Quando o servidor MCP for chamado por um agente alimentado por LLM (grandes modelos de linguagem), esteja ciente dos ataques de injeção de prompt. Considere as seguintes práticas recomendadas de segurança:

  • Autenticação e autorização: proteja seu servidor MCP com a autenticação do Microsoft Entra para garantir que somente usuários ou agentes autorizados possam acessar suas ferramentas. Confira Chamadas do Protocolo de Contexto de Modelo Seguro para Serviço de Aplicativo do Azure a partir do Visual Studio Code com autenticação do Microsoft Entra para um guia passo a passo.
  • Validação e sanitização de entrada: o código de exemplo neste tutorial usa zod para validação de entrada, garantindo que os dados de entrada correspondam ao esquema esperado. Para obter segurança adicional, considere:
    • Validar e higienizar todas as entradas do usuário antes do processamento, especialmente para campos usados em consultas ou saídas de banco de dados.
    • Escapando da saída em respostas para impedir XSS (script entre sites) se sua API for consumida por navegadores.
    • Aplicando esquemas estritos e valores padrão em seus modelos para evitar dados inesperados.
  • HTTPS: O exemplo depende do Serviço de Aplicativo do Azure, que impõe HTTPS por padrão e fornece certificados TLS/SSL gratuitos para criptografar dados em trânsito.
  • Princípio de Privilégio Mínimo: exponha apenas as ferramentas e os dados necessários para seu caso de uso. Evite expor operações confidenciais, a menos que seja necessário.
  • Limitação de taxa e limitação: use o Gerenciamento de API ou middleware personalizado para evitar ataques de abuso e negação de serviço.
  • Registro em log e monitoramento: acesso de log e uso de pontos de extremidade MCP para auditoria e detecção de anomalias. Monitore atividades suspeitas.
  • Configuração do CORS: restrinja solicitações entre origens a domínios confiáveis se o servidor MCP for acessado de navegadores. Para obter mais informações, consulte Habilitar CORS.
  • Atualizações regulares: mantenha suas dependências atualizadas para atenuar vulnerabilidades conhecidas.

Mais recursos

Integrar a IA aos aplicativos do Serviço de Aplicativo do Azure