Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Dans ce tutoriel, vous créez un serveur MCP (Model Context Protocol) qui expose des outils de gestion des tâches à l’aide d’Express et du KIT SDK TypeScript MCP. Vous déployez le serveur sur Azure Container Apps et vous y connectez à partir de GitHub Copilot Chat dans VS Code.
Dans ce tutoriel, vous allez :
- Créer une application Express qui expose les outils MCP
- Tester le serveur MCP localement avec GitHub Copilot
- Conteneuriser et déployer l’application sur Azure Container Apps
- Connecter GitHub Copilot au serveur MCP déployé
Prerequisites
- Un compte Azure avec un abonnement actif. Créez-en un gratuitement.
- Azure CLI version 2.62.0 ou ultérieure.
- Node.js 20 LTS ou version ultérieure.
- Visual Studio Code avec l’extension GitHub Copilot .
- Docker Desktop (facultatif , nécessaire uniquement pour tester le conteneur localement).
Créer la structure de l’application
Dans cette section, vous allez créer un projet Node.js avec Express et le Kit de développement logiciel (SDK) TypeScript MCP.
Créez le répertoire du projet et initialisez-le :
mkdir tasks-mcp-server && cd tasks-mcp-server npm init -yInstallez des dépendances :
npm install @modelcontextprotocol/sdk express zod npm install -D typescript @types/node @types/express tsxCréer
tsconfig.json:{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "declaration": true }, "include": ["src/**/*"] }Cette configuration cible ES2022 avec la résolution de module Node.js génère des fichiers compilés vers
dist/et active une vérification de type stricte.Mettez à jour
package.jsonpour activer les modules ES et ajouter des scripts de génération et de démarrage. Ajoutez ou remplacez les champstypeetscripts.{ "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsx watch src/index.ts" } }Important
Définissez
"type": "module". Le code du serveur MCP utilise le niveauawaitsupérieur, qui est uniquement pris en charge dans les modules ES.Créez
src/taskStore.tspour le magasin de données en mémoire :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();L’interface
TaskItemdéfinit la forme de données de tâche. LaTaskStoreclasse gère un tableau en mémoire prérempli avec des exemples de données et fournit des méthodes pour répertorier, rechercher, créer, basculer et supprimer des tâches. Un singleton au niveau du module est exporté pour une utilisation par les outils MCP.
Définir les outils MCP
Ensuite, vous définissez le serveur MCP avec des inscriptions d’outils qui exposent le magasin de tâches aux clients IA.
Créer
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`); });Points clés :
-
McpServerdu kit SDK TypeScript définit le serveur MCP avec des enregistrements d'outils. -
StreamableHTTPServerTransportgère le protocole HTTP streamable MCP. Le paramètresessionIdGenerator: undefinedexécute le serveur en mode sans état. - Les outils utilisent des schémas Zod pour définir des paramètres d’entrée avec des descriptions.
- Un point de terminaison
/healthdistinct est requis pour les sondes d’intégrité des Container Apps.
-
Tester le serveur MCP localement
Avant de déployer sur Azure, vérifiez que le serveur MCP fonctionne en l’exécutant localement et en vous connectant à partir de GitHub Copilot.
Démarrez le serveur de développement :
npx tsx src/index.tsOuvrez VS Code, ouvrez Copilot Chat, puis sélectionnez Le mode Agent .
Sélectionnez le bouton Outils , puis sélectionnez Ajouter d’autres outils...>Ajoutez le serveur MCP.
Sélectionnez HTTP (HTTP ou événements envoyés par le serveur).
Entrez l’URL du serveur :
http://localhost:3000/mcpNote
Le serveur de développement local est défini par défaut sur le port 3000. Lorsqu’il est conteneurisé, le fichier Dockerfile définit la
PORTvariable d’environnement sur 8080 pour qu’elle corresponde au port cible Container Apps.Entrez un ID de serveur :
tasks-mcpSélectionnez Paramètres de l’espace de travail.
Tester avec une invite : « Afficher toutes les tâches »
Sélectionnez Continuer lorsque Copilot demande la confirmation de l’appel de l’outil.
Vous devriez voir Copilot retourner la liste des tâches depuis votre magasin en mémoire.
Conseil / Astuce
Essayez d'autres invites comme « Créer une tâche pour examiner la demande de tirage », « Marquer la tâche 1 comme terminée » ou « Supprimer la tâche 2 ».
Conteneuriser l’application
Empaqueter l’application en tant que conteneur Docker afin de pouvoir la tester localement avant de le déployer sur Azure.
Créez un
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"]La build à plusieurs étapes compile TypeScript dans la première étape, puis crée une image de production avec uniquement des dépendances d’exécution et la sortie JavaScript compilée. La
PORTvariable d’environnement est définie sur 8080 pour correspondre au port cible Container Apps.Vérifiez localement :
docker build -t tasks-mcp-server . docker run -p 8080:8080 tasks-mcp-serverConfirmer:
curl http://localhost:8080/health
Déployer sur Azure Container Apps
Après avoir conteneurisé l’application, déployez-la sur Azure Container Apps à l’aide d’Azure CLI. La az containerapp up commande génère l’image conteneur dans le cloud. Vous n’avez donc pas besoin de Docker sur votre machine pour cette étape.
Définissez des variables d’environnement :
RESOURCE_GROUP="mcp-tutorial-rg" LOCATION="eastus" ENVIRONMENT_NAME="mcp-env" APP_NAME="tasks-mcp-server-node"Créez un groupe de ressources :
az group create --name $RESOURCE_GROUP --location $LOCATIONCréez un environnement Container Apps :
az containerapp env create \ --name $ENVIRONMENT_NAME \ --resource-group $RESOURCE_GROUP \ --location $LOCATIONDéployez l’application conteneur :
az containerapp up \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --environment $ENVIRONMENT_NAME \ --source . \ --ingress external \ --target-port 8080Configurez CORS pour autoriser les requêtes GitHub Copilot :
az containerapp ingress cors enable \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --allowed-origins "*" \ --allowed-methods "GET,POST,DELETE,OPTIONS" \ --allowed-headers "*"Note
Pour la production, remplacez les origines génériques
*par des origines spécifiques approuvées. Consultez les serveurs MCP sécurisés sur Container Apps pour obtenir des conseils.Vérifiez le déploiement :
APP_URL=$(az containerapp show \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --query "properties.configuration.ingress.fqdn" -o tsv) curl https://$APP_URL/health
Connecter GitHub Copilot au serveur déployé
Maintenant que le serveur MCP s’exécute dans Azure, configurez VS Code pour connecter GitHub Copilot au point de terminaison déployé.
Dans votre projet, créez ou mettez à jour
.vscode/mcp.json:{ "servers": { "tasks-mcp-server": { "type": "http", "url": "https://<your-app-fqdn>/mcp" } } }Remplacez
<your-app-fqdn>par le FQDN de la sortie du déploiement.Dans VS Code, ouvrez Copilot Chat en mode Agent.
Si le serveur n’apparaît pas automatiquement, sélectionnez le bouton Outils et vérifiez
tasks-mcp-serverqu’il est répertorié. Sélectionnez Démarrer si nécessaire.Testez avec une invite telle que « Répertorier toutes mes tâches » pour confirmer que le serveur MCP déployé répond.
Configurer la mise à l’échelle pour une utilisation interactive
Par défaut, Azure Container Apps peut effectuer une mise à l’échelle vers zéro réplica. Pour les serveurs MCP qui servent des clients interactifs comme Copilot, les démarrages à froid provoquent des retards notables. Définissez un nombre minimal de réplicas pour conserver au moins une instance en cours d’exécution :
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
Considérations relatives à la sécurité
Ce tutoriel utilise un serveur MCP non authentifié pour plus de simplicité. Avant d’exécuter un serveur MCP en production, passez en revue les recommandations suivantes. Lorsqu’un agent alimenté par de grands modèles de langage (LLMs) appelle votre serveur MCP, tenez compte des attaques d’injection de commande.
- Authentification et autorisation : Sécurisez votre serveur MCP à l’aide de l’ID Microsoft Entra. Consultez les serveurs MCP sécurisés sur Container Apps.
- Validation d’entrée : les schémas Zod fournissent une sécurité de type, mais ajoutent une validation de règle métier pour les paramètres d’outil. Considérez les bibliothèques comme zod-express-middleware pour la validation au niveau de la demande.
- HTTPS : Azure Container Apps applique HTTPS par défaut avec des certificats TLS automatiques.
- Privilège minimum : exposez uniquement les outils dont votre cas d’usage a besoin. Évitez les outils qui effectuent des opérations destructrices sans confirmation.
- CORS : Restreindre les origines autorisées aux domaines de confiance en production.
- Journalisation et surveillance : journaliser les appels de l'outil MCP pour l'audit. Utilisez Azure Monitor et Log Analytics.
Nettoyer les ressources
Si vous ne prévoyez pas de continuer à utiliser cette application, supprimez le groupe de ressources pour supprimer toutes les ressources que vous avez créées dans ce tutoriel :
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait