Oktatóanyag: Node.js MCP-kiszolgáló üzembe helyezése az Azure Container Appsben

Ebben az oktatóanyagban létrehoz egy Model Context Protocol (MCP) kiszolgálót, amely az Express és az MCP TypeScript SDK használatával teszi elérhetővé a feladatkezelő eszközöket. Üzembe helyezheti a kiszolgálót az Azure Container Appsben, és csatlakozhat hozzá a GitHub Copilot Chatből a VS Code-ban.

Ebben az útmutatóban Ön:

  • Olyan Express-alkalmazás létrehozása, amely mcp-eszközöket tesz elérhetővé
  • Az MCP-kiszolgáló helyi tesztelése a GitHub Copilottal
  • Az alkalmazás tárolóba helyezése és üzembe helyezése az Azure Container Appsben
  • A GitHub Copilot csatlakoztatása az üzembe helyezett MCP-kiszolgálóhoz

Előfeltételek

Az alkalmazásállvány létrehozása

Ebben a szakaszban egy új Node.js projektet hoz létre az Express és az MCP TypeScript SDK használatával.

  1. Hozza létre és inicializálja a projektkönyvtárat:

    mkdir tasks-mcp-server && cd tasks-mcp-server
    npm init -y
    
  2. Telepítse a függőségeket:

    npm install @modelcontextprotocol/sdk express zod
    npm install -D typescript @types/node @types/express tsx
    
  3. Létrehoz tsconfig.json:

    {
        "compilerOptions": {
            "target": "ES2022",
            "module": "Node16",
            "moduleResolution": "Node16",
            "outDir": "./dist",
            "rootDir": "./src",
            "strict": true,
            "esModuleInterop": true,
            "declaration": true
        },
        "include": ["src/**/*"]
    }
    

    Ez a konfiguráció az ES2022-t célozza meg Node.js modulfelbontással, a lefordított fájlokat pedig a dist/-ra állítja ki, és lehetővé teszi a szigorú típusellenőrzést.

  4. Frissítsd package.json az ES modulok engedélyezésére, valamint build és indító szkriptek hozzáadására. Adja hozzá vagy cserélje le a type mezőket scripts :

    {
        "type": "module",
        "scripts": {
            "build": "tsc",
            "start": "node dist/index.js",
            "dev": "tsx watch src/index.ts"
        }
    }
    

    Fontos

    "type": "module"beállítása. Az MCP-kiszolgáló kódja felső szintű await-t használ, amelyet csak az ES modulok támogatnak.

  5. Létrehozás src/taskStore.ts a memóriában lévő adattárhoz:

    export interface TaskItem {
        id: number;
        title: string;
        description: string;
        isComplete: boolean;
        createdAt: string;
    }
    
    class TaskStore {
        private tasks: TaskItem[] = [
            {
                id: 1,
                title: "Buy groceries",
                description: "Milk, eggs, bread",
                isComplete: false,
                createdAt: new Date().toISOString(),
            },
            {
                id: 2,
                title: "Write docs",
                description: "Draft the MCP tutorial",
                isComplete: true,
                createdAt: new Date(Date.now() - 86400000).toISOString(),
            },
        ];
        private nextId = 3;
    
        getAll(): TaskItem[] {
            return [...this.tasks];
        }
    
        getById(id: number): TaskItem | undefined {
            return this.tasks.find((t) => t.id === id);
        }
    
        create(title: string, description: string): TaskItem {
            const task: TaskItem = {
                id: this.nextId++,
                title,
                description,
                isComplete: false,
                createdAt: new Date().toISOString(),
            };
            this.tasks.push(task);
            return task;
        }
    
        toggleComplete(id: number): TaskItem | undefined {
            const task = this.tasks.find((t) => t.id === id);
            if (!task) return undefined;
            task.isComplete = !task.isComplete;
            return task;
        }
    
        delete(id: number): boolean {
            const index = this.tasks.findIndex((t) => t.id === id);
            if (index < 0) return false;
            this.tasks.splice(index, 1);
            return true;
        }
    }
    
    export const store = new TaskStore();
    

    A TaskItem felület határozza meg a feladatadat-alakzatot. Az TaskStore osztály a mintaadatokkal előre feltöltött memóriabeli tömböket kezeli, és metódusokat biztosít a feladatok listázására, keresésére, létrehozására, váltására és törlésére. Egy modulszintű szingleton van exportálva az MCP-eszközök használatához.

Az MCP-eszközök definiálása

Ezután definiálja az MCP-kiszolgálót olyan eszközregisztrációkkal, amelyek elérhetővé teszik a feladattárolót az AI-ügyfelek számára.

  1. Létrehoz src/index.ts:

    import express, { Request, Response } from "express";
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
    import { z } from "zod";
    import { store } from "./taskStore.js";
    
    const app = express();
    app.use(express.json());
    
    // Health endpoint for Container Apps probes
    app.get("/health", (_req: Request, res: Response) => {
        res.json({ status: "healthy" });
    });
    
    // Create the MCP server
    const mcpServer = new McpServer({
        name: "TasksMCP",
        version: "1.0.0",
    });
    
    // Register tools
    mcpServer.tool("list_tasks", "List all tasks with their ID, title, description, and completion status.", {}, async () => {
        return {
            content: [{ type: "text", text: JSON.stringify(store.getAll(), null, 2) }],
        };
    });
    
    mcpServer.tool(
        "get_task",
        "Get a single task by its numeric ID.",
        { task_id: z.number().describe("The numeric ID of the task to retrieve") },
        async ({ task_id }) => {
            const task = store.getById(task_id);
            return {
                content: [
                    {
                        type: "text",
                        text: task ? JSON.stringify(task, null, 2) : `Task with ID ${task_id} not found.`,
                    },
                ],
            };
        }
    );
    
    mcpServer.tool(
        "create_task",
        "Create a new task with the given title and description. Returns the created task.",
        {
            title: z.string().describe("A short title for the task"),
            description: z.string().describe("A detailed description of what the task involves"),
        },
        async ({ title, description }) => {
            const task = store.create(title, description);
            return {
                content: [{ type: "text", text: JSON.stringify(task, null, 2) }],
            };
        }
    );
    
    mcpServer.tool(
        "toggle_task_complete",
        "Toggle a task's completion status between complete and incomplete.",
        { task_id: z.number().describe("The numeric ID of the task to toggle") },
        async ({ task_id }) => {
            const task = store.toggleComplete(task_id);
            const msg = task
                ? `Task ${task.id} is now ${task.isComplete ? "complete" : "incomplete"}.`
                : `Task with ID ${task_id} not found.`;
            return { content: [{ type: "text", text: msg }] };
        }
    );
    
    mcpServer.tool(
        "delete_task",
        "Delete a task by its numeric ID.",
        { task_id: z.number().describe("The numeric ID of the task to delete") },
        async ({ task_id }) => {
            const deleted = store.delete(task_id);
            const msg = deleted ? `Task ${task_id} deleted.` : `Task with ID ${task_id} not found.`;
            return { content: [{ type: "text", text: msg }] };
        }
    );
    
    // Mount the MCP streamable HTTP transport
    const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
    
    app.post("/mcp", async (req: Request, res: Response) => {
        await transport.handleRequest(req, res, req.body);
    });
    
    app.get("/mcp", async (req: Request, res: Response) => {
        await transport.handleRequest(req, res);
    });
    
    app.delete("/mcp", async (req: Request, res: Response) => {
        await transport.handleRequest(req, res);
    });
    
    // Connect the transport to the MCP server
    await mcpServer.connect(transport);
    
    // Start the Express server
    const PORT = parseInt(process.env.PORT || "3000", 10);
    app.listen(PORT, () => {
        console.log(`MCP server running on http://localhost:${PORT}/mcp`);
    });
    

    Összefoglalás:

    • McpServer a TypeScript SDK-ból határozza meg az MCP-kiszolgálót eszközregisztrációkkal.
    • StreamableHTTPServerTransport kezeli az MCP streamelhető HTTP protokollt. A beállítás sessionIdGenerator: undefined állapot nélküli módban futtatja a kiszolgálót.
    • Az eszközök Zod-sémákkal definiálják a bemeneti paramétereket leírásokkal.
    • A Container Apps állapottesztjeihez külön /health végpontra van szükség.

Az MCP-kiszolgáló helyi tesztelése

Az Azure-ban való üzembe helyezés előtt ellenőrizze, hogy az MCP-kiszolgáló működik-e helyileg futtatva és a GitHub Copilotból való csatlakozással.

  1. Indítsa el a fejlesztői kiszolgálót:

    npx tsx src/index.ts
    
  2. Nyissa meg a VS Code-ot, nyissa meg a Copilot-csevegést, és válassza az Ügynök módot.

  3. Válassza az Eszközök gombot, majd válassza a További eszközök hozzáadása...>Adja hozzá az MCP-kiszolgálót.

  4. Válassza a HTTP (HTTP vagy Server-Sent események) lehetőséget.

  5. Adja meg a kiszolgáló URL-címét: http://localhost:3000/mcp

    Megjegyzés:

    A helyi fejlesztési kiszolgáló alapértelmezés szerint a 3000-s portot adja meg. Tárolóba helyezésekor a Dockerfile a PORT környezeti változót 8080-ra állítja, hogy megfeleljen a Container Apps célportjának.

  6. Adjon meg egy kiszolgálóazonosítót: tasks-mcp

  7. Válassza a Munkaterület beállításai lehetőséget.

  8. Tesztelés a következő üzenettel: "Az összes feladat megjelenítése"

  9. Válassza a Folytatás lehetőséget, amikor a Copilot kéri az eszköz meghívásának megerősítését.

Nézze meg, hogy a Copilot visszaadja-e a memóriabeli tárolóból származó feladatok listáját.

Jótanács

Próbálkozzon más kérdésekkel, például a "Feladat létrehozása a kérelem áttekintéséhez", az "1. tevékenység megjelölése befejezettként" vagy a "2. tevékenység törlése" kérdésekkel.

Az alkalmazás tárolóba helyezése

Csomagolja be az alkalmazást Docker-tárolóként, hogy helyileg tesztelhesse, mielőtt üzembe helyezené az Azure-ban.

  1. Hozzon létre egy Dockerfile:

    FROM node:20-slim AS build
    WORKDIR /app
    COPY package*.json .
    RUN npm ci
    COPY tsconfig.json .
    COPY src/ src/
    RUN npm run build
    
    FROM node:20-slim
    WORKDIR /app
    COPY package*.json .
    RUN npm ci --omit=dev
    COPY --from=build /app/dist ./dist
    ENV PORT=8080
    EXPOSE 8080
    CMD ["node", "dist/index.js"]
    

    A többfázisú build az első szakaszban lefordítja a TypeScriptet, majd létrehoz egy éles rendszerképet, amely csak futtatókörnyezeti függőségekkel és a lefordított JavaScript-kimenettel rendelkezik. A PORT környezeti változó 8080-ra van állítva, hogy megfeleljen a Container Apps célportjának.

  2. Helyi ellenőrzés:

    docker build -t tasks-mcp-server .
    docker run -p 8080:8080 tasks-mcp-server
    

    Megerősíteni: curl http://localhost:8080/health

Üzembe helyezés az Azure Container Appsben

Az alkalmazás tárolóba helyezése után helyezze üzembe az Azure Container Appsben az Azure CLI használatával. A az containerapp up parancs létrehozza a tárolórendszerképet a felhőben, így ehhez a lépéshez nincs szükség a Dockerre a gépen.

  1. Környezeti változók beállítása:

    RESOURCE_GROUP="mcp-tutorial-rg"
    LOCATION="eastus"
    ENVIRONMENT_NAME="mcp-env"
    APP_NAME="tasks-mcp-server-node"
    
  2. Erőforráscsoport létrehozása:

    az group create --name $RESOURCE_GROUP --location $LOCATION
    
  3. Container Apps-környezet létrehozása:

    az containerapp env create \
        --name $ENVIRONMENT_NAME \
        --resource-group $RESOURCE_GROUP \
        --location $LOCATION
    
  4. A tárolóalkalmazás üzembe helyezése:

    az containerapp up \
        --name $APP_NAME \
        --resource-group $RESOURCE_GROUP \
        --environment $ENVIRONMENT_NAME \
        --source . \
        --ingress external \
        --target-port 8080
    
  5. Konfigurálja a CORS-t a GitHub Copilot-kérelmek engedélyezéséhez:

    az containerapp ingress cors enable \
        --name $APP_NAME \
        --resource-group $RESOURCE_GROUP \
        --allowed-origins "*" \
        --allowed-methods "GET,POST,DELETE,OPTIONS" \
        --allowed-headers "*"
    

    Megjegyzés:

    A termelési környezetben cserélje le a származási helyek helyettesítő mintáit specifikus és megbízható forrásokra. Útmutatásért tekintse meg a Container Apps biztonságos MCP-kiszolgálóit .

  6. Ellenőrizze az üzembe helyezést:

    APP_URL=$(az containerapp show \
        --name $APP_NAME \
        --resource-group $RESOURCE_GROUP \
        --query "properties.configuration.ingress.fqdn" -o tsv)
    
    curl https://$APP_URL/health
    

A GitHub Copilot csatlakoztatása az üzembe helyezett kiszolgálóhoz

Most, hogy az MCP-kiszolgáló az Azure-ban fut, konfigurálja a VS Code-ot a GitHub Copilot üzembe helyezett végponthoz való csatlakoztatásához.

  1. A projektben hozza létre vagy frissítse .vscode/mcp.jsona következőt:

    {
        "servers": {
            "tasks-mcp-server": {
                "type": "http",
                "url": "https://<your-app-fqdn>/mcp"
            }
        }
    }
    

    Cserélje le <your-app-fqdn> a központi telepítési kimenet teljes tartománynevét.

  2. A VS Code-ban nyissa meg a Copilot-csevegést ügynök módban.

  3. Ha a kiszolgáló nem jelenik meg automatikusan, válassza az Eszközök gombot, és ellenőrizze, hogy tasks-mcp-server szerepel-e a listában. Szükség esetén válassza a Start lehetőséget.

  4. Tesztelje a "List all my tasks" (Az összes feladat listázása) üzenettel, hogy az üzembe helyezett MCP-kiszolgáló válaszol-e.

Skálázás konfigurálása interaktív használatra

Az Azure Container Apps alapértelmezés szerint nulla replikára skálázható. Az olyan interaktív ügyfeleket kiszolgáló MCP-kiszolgálók esetében, mint a Copilot, a hidegindítások észrevehető késéseket okoznak. Állítson be egy minimális replikaszámot, hogy legalább egy példány fusson:

az containerapp update \
    --name $APP_NAME \
    --resource-group $RESOURCE_GROUP \
    --min-replicas 1

Biztonsági szempontok

Ez az oktatóanyag egy nem hitelesített MCP-kiszolgálót használ az egyszerűség kedvéért. Mielőtt éles környezetben futtat egy MCP-kiszolgálót, tekintse át az alábbi javaslatokat. Amikor egy nagy nyelvi modellekkel (LLM-ekkel) rendelkező ügynök meghívja az MCP-kiszolgálót, vegye figyelembe a gyors injektálási támadásokat.

  • Hitelesítés és engedélyezés: Az MCP-kiszolgáló védelme a Microsoft Entra-azonosító használatával. Lásd: Biztonságos MCP-kiszolgálók a Container Appsben.
  • Bemeneti ellenőrzés: A zod sémák típusbiztonságot biztosítanak, de üzleti szabályérvényesítést adnak hozzá az eszközparaméterekhez. A kérésszintű ellenőrzéshez fontolja meg az olyan kódtárakat, mint a zod-express-middleware .
  • HTTPS: Az Azure Container Apps alapértelmezés szerint automatikus TLS-tanúsítványokkal kényszeríti a HTTPS-t.
  • Minimális jogosultság: Csak azokat az eszközöket tegye elérhetővé, amelyet a használati eset igényel. Kerülje azokat az eszközöket, amelyek megerősítés nélkül végeznek romboló műveleteket.
  • CORS: Az engedélyezett források korlátozása az éles környezetben megbízható tartományokra.
  • Naplózás és figyelés: Az MCP-eszközök meghívásainak naplózása audit céljából. Használja az Azure Monitort és a Log Analyticset.

Erőforrások tisztítása

Ha nem tervezi folytatni az alkalmazás használatát, törölje az erőforráscsoportot az oktatóanyagban létrehozott összes erőforrás eltávolításához:

az group delete --resource-group $RESOURCE_GROUP --yes --no-wait

Következő lépés