Integrera en App Service-app som en MCP-server för GitHub Copilot Chat (Node.js)

I den här självstudien får du lära dig hur du exponerar en Express.js-appens funktioner via Model Context Protocol (MCP), lägger till den som ett verktyg i GitHub Copilot och interagerar med din app med naturligt språk i Copilot Chat-agentläge.

Skärmbild som visar GitHub Copilot som anropar Todos MCP-server i Azure App Service.

Om ditt webbprogram redan har användbara funktioner, till exempel shopping, hotellbokning eller datahantering, är det enkelt att göra dessa funktioner tillgängliga för:

Genom att lägga till en MCP-server i webbappen gör du det möjligt för en agent att förstå och använda appens funktioner när den svarar på användarfrågor. Det innebär att allt din app kan göra kan agenten också göra.

  • Lägg till en MCP-server i webbappen.
  • Testa MCP-servern lokalt i GitHub Copilot Chat-agentläge.
  • Distribuera MCP-servern till Azure App Service och anslut till den i GitHub Copilot Chat.

Prerequisites

Den här självstudien förutsätter att du arbetar med exemplet som används i Självstudie: Distribuera en Node.js + MongoDB-webbapp till Azure.

Öppna minst exempelprogrammet i GitHub Codespaces och distribuera appen genom att köra azd up.

Lägga till MCP-server i webbappen

  1. Lägg till de npm-paket som krävs i projektet i kodområdesterminalen:

    npm install @modelcontextprotocol/sdk@latest zod
    
    1. Öppna vägar/index.js. För enkelhetens skull lägger du till all MCP-serverkod här.
  2. Överst i vägar/index.jslägger du till följande kräver:

    const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
    const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
    const { z } = require('zod');
    
  3. Längst ned i filen lägger du till följande väg för MCP-servern ovan module.exports = router; .

    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,
          });
        }
      }
    });
    

    Den här vägen anger mcp-serverslutpunkten till <url>/api/mcp och använder det tillståndslösa lägesmönstret i MCP TypeScript SDK.

    • server.registerTool() lägger till ett verktyg till MCP-servern med dess implementering.
    • SDK använder zod för indataverifiering.
    • description i konfigurationsobjektet och describe() i inputSchema tillhandahålla läsbara beskrivningar för verktyg och indata. De hjälper anropande agenten att förstå hur de använder verktygen och deras parametrar.

    Den här rutten duplicerar CRUD-funktionaliteten (create-read-update-delete) för de befintliga rutterna, vilket är onödigt, men du behåller den för enkelhetens skull. Bästa praxis är att flytta applogik till en modul och sedan anropa modulen från alla vägar.

Testa MCP-servern lokalt

  1. I kodområdesterminalen kör du programmet med npm start.

  2. Välj Öppna i webbläsare och lägg sedan till en aktivitet.

    Lämna npm start igång. MCP-servern är igång http://localhost:3000/api/mcp nu.

  3. Gå tillbaka till kodområdet, öppna Copilot Chat och välj sedan Agentläge i promptrutan.

  4. Välj knappen Verktyg och välj sedan Lägg till fler verktyg... i listrutan.

    Skärmbild som visar hur du lägger till en MCP-server i GitHub Copilot Chat-agentläge.

  5. Välj Lägg till MCP-server.

  6. Välj HTTP (HTTP eller Server-Sent-händelser).

  7. I Ange server-URL skriver du http://localhost:3000/api/mcp.

  8. I Ange server-ID skriver du todos-mcp eller vilket namn du vill.

  9. Välj Inställningar för arbetsyta.

  10. I ett nytt Copilot Chat-fönster skriver du något i stil med "Visa mig todos".

  11. Som standard visar GitHub Copilot en säkerhetsbekräftelse när du anropar en MCP-server. Välj Fortsätt.

    Skärmbild som visar standardsäkerhetsmeddelandet från ett MCP-anrop i GitHub Copilot Chat.

    Nu bör du se ett svar som anger att MCP-verktygsanropet lyckas.

    Skärmbild som visar svaret från ett anrop med MCP-verktyget i GitHub Copilot Chat-fönstret.

Distribuera MCP-servern till App Service

  1. Tillbaka i kodområdesterminalen distribuerar du dina ändringar genom att genomföra dina ändringar (GitHub Actions-metod) eller köra azd up (Azure Developer CLI-metod).

  2. Leta reda på appens URL i AZD-utdata. URL:en ser ut så här i AZD-utdata:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <app-url>
     
  3. När du är klar med azd up öppnar du .vscode/mcp.json. Ändra URL:en till <app-url>/api/mcp.

  4. Välj Starta ovanför den ändrade MCP-serverkonfigurationen.

    Skärmbild som visar hur du startar en MCP-server manuellt från den lokala mcp.json filen.

  5. Starta ett nytt GitHub Copilot Chat-fönster. Du bör kunna visa, skapa, uppdatera och ta bort uppgifter i Copilot-agenten.

Metodtips för säkerhet

När MCP-servern anropas av en agent som drivs av stora språkmodeller (LLM) bör du vara medveten om snabba inmatningsattacker . Överväg följande metodtips för säkerhet:

  • Autentisering och auktorisering: Skydda MCP-servern med Microsoft Entra-autentisering för att säkerställa att endast behöriga användare eller agenter kan komma åt dina verktyg. En stegvis guide finns i Secure Model Context Protocol-anrop till Azure App Service från Visual Studio Code med Microsoft Entra-autentisering .
  • Validering och sanering av indata: Exempelkoden i den här självstudien använder zod för indataverifiering, vilket säkerställer att inkommande data matchar det förväntade schemat. Överväg följande för ytterligare säkerhet:
    • Validera och sanera alla användarindata före bearbetning, särskilt för fält som används i databasfrågor eller utdata.
    • Undfly utdata i svar för att förhindra XSS (cross-site scripting) om ditt API används av webbläsare.
    • Tillämpa strikta scheman och standardvärden i dina modeller för att undvika oväntade data.
  • HTTPS: Exemplet förlitar sig på Azure App Service, som framtvingar HTTPS som standard och tillhandahåller kostnadsfria TLS/SSL-certifikat för att kryptera data under överföring.
  • Lägsta behörighetsprincip: Exponera endast nödvändiga verktyg och data som krävs för ditt användningsfall. Undvik att exponera känsliga operationer om det inte är nödvändigt.
  • Hastighetsbegränsning och begränsning: Använd API Management eller anpassade mellanprogram för att förhindra missbruk och överbelastningsattacker.
  • Loggning och övervakning: Loggåtkomst och användning av MCP-slutpunkter för granskning och avvikelseidentifiering. Övervaka misstänkt aktivitet.
  • CORS-konfiguration: Begränsa mellanursprungsbegäranden till pålitliga domäner om din MCP-server nås från webbläsare. Mer information finns i Aktivera CORS.
  • Regelbundna uppdateringar: Håll dina beroenden uppdaterade för att minimera kända sårbarheter.

Fler resurser

Integrera AI i dina Azure App Service-program