Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tutorial, creará un servidor MCP (Model Context Protocol) que expone herramientas de administración de tareas usando Express y el SDK de MCP TypeScript. Implemente el servidor en Azure Container Apps y conéctese a él desde GitHub Copilot Chat en VS Code.
En este tutorial, usted hará lo siguiente:
- Creación de una aplicación Express que expone herramientas de MCP
- Probar el servidor MCP localmente con GitHub Copilot
- Containerización y despliegue de la aplicación en Azure Container Apps
- Conexión de GitHub Copilot al servidor MCP implementado
Prerrequisitos
- Una cuenta de Azure con una suscripción activa. cree una de forma gratuita.
- Cli de Azure versión 2.62.0 o posterior.
- Node.js 20 LTS o posterior.
- Visual Studio Code con la extensión GitHub Copilot.
- Docker Desktop (opcional: solo es necesario para probar el contenedor localmente).
Crea el esqueleto de la aplicación
En esta sección, creará un nuevo proyecto de Node.js con Express y el SDK de TypeScript de MCP.
Cree el directorio del proyecto e inicialícelo:
mkdir tasks-mcp-server && cd tasks-mcp-server npm init -yInstale las dependencias:
npm install @modelcontextprotocol/sdk express zod npm install -D typescript @types/node @types/express tsxCrear
tsconfig.json:{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "declaration": true }, "include": ["src/**/*"] }Esta configuración está dirigida a ES2022 con resolución de módulos Node.js, genera archivos compilados en
dist/y habilita la comprobación estricta de tipos.Actualice
package.jsonpara habilitar los módulos de ES y agregue scripts de compilación e inicio. Agregue o reemplace lostypecampos yscripts:{ "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsx watch src/index.ts" } }Importante
Establezca
"type": "module". El código de servidor MCP usa el nivelawaitsuperior , que solo se admite en los módulos ES.Cree
src/taskStore.tspara el almacén de datos en memoria: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();La
TaskIteminterfaz define la forma de datos de la tarea. LaTaskStoreclase administra una matriz en memoria rellenada previamente con datos de ejemplo y proporciona métodos para enumerar, buscar, crear, alternar y eliminar tareas. Se exporta un singleton de nivel de módulo para su uso por las herramientas de MCP.
Definición de las herramientas de MCP
A continuación, defina el servidor MCP con registros de herramientas que exponen el almacén de tareas a los clientes de IA.
Crear
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`); });Puntos clave:
-
McpServerdefine el servidor MCP desde el SDK de TypeScript junto con los registros de herramientas. -
StreamableHTTPServerTransportcontrola el protocolo HTTP que se puede transmitir por secuencias de MCP. La configuraciónsessionIdGenerator: undefinedejecuta el servidor en modo sin estado. - Las herramientas usan esquemas Zod para definir parámetros de entrada con descripciones.
- Se requiere un punto de conexión
/healthindependiente para los sondeos de estado de las Container Apps.
-
Probar el servidor MCP localmente
Antes de implementar en Azure, compruebe que el servidor MCP funciona ejecutandolo localmente y conectándose desde GitHub Copilot.
Inicie el servidor de desarrollo:
npx tsx src/index.tsAbra VS Code, abra Chat de Copilot y seleccione Modo de agente .
Seleccione el botón Herramientas y, a continuación, seleccione Agregar más herramientas...>Agregue el servidor MCP.
Seleccione HTTP (HTTP o eventos Server-Sent).
Escriba la dirección URL del servidor:
http://localhost:3000/mcpNota:
El servidor de desarrollo local tiene como valor predeterminado el puerto 3000. Cuando se incluye en contenedores, Dockerfile establece la
PORTvariable de entorno en 8080 para que coincida con el puerto de destino de Container Apps.Escriba un identificador de servidor:
tasks-mcpSeleccione Configuración del área de trabajo.
Prueba con un indicador: "Muéstrame todas las tareas"
Seleccione Continuar cuando copilot solicite confirmación de invocación de la herramienta.
Debería ver cómo Copilot devuelve la lista de tareas de su almacén en memoria.
Sugerencia
Pruebe otras indicaciones como "Crear una tarea para revisar la PR", "Marcar la tarea 1 como completada" o "Eliminar la tarea 2".
Contenerizar la aplicación
Empaquete la aplicación como contenedor de Docker para que pueda probarla localmente antes de implementarla en Azure.
Crear 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 compilación en varias fases compila TypeScript en la primera fase y, a continuación, crea una imagen de producción con solo las dependencias de tiempo de ejecución y el resultado JavaScript compilado. La
PORTvariable de entorno se establece en 8080 para que coincida con el puerto de destino de Container Apps.Compruebe localmente:
docker build -t tasks-mcp-server . docker run -p 8080:8080 tasks-mcp-serverConfirmar:
curl http://localhost:8080/health
Implementación en Azure Container Apps
Después de incluir la aplicación en contenedores, impleméntela en Azure Container Apps mediante la CLI de Azure. El az containerapp up comando compila la imagen de contenedor en la nube, por lo que no necesita Docker en la máquina para este paso.
Establecer variables de entorno:
RESOURCE_GROUP="mcp-tutorial-rg" LOCATION="eastus" ENVIRONMENT_NAME="mcp-env" APP_NAME="tasks-mcp-server-node"Cree un grupo de recursos:
az group create --name $RESOURCE_GROUP --location $LOCATIONCree un entorno de Container Apps:
az containerapp env create \ --name $ENVIRONMENT_NAME \ --resource-group $RESOURCE_GROUP \ --location $LOCATIONImplemente la aplicación contenedora:
az containerapp up \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --environment $ENVIRONMENT_NAME \ --source . \ --ingress external \ --target-port 8080Configure CORS para permitir solicitudes de GitHub Copilot:
az containerapp ingress cors enable \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --allowed-origins "*" \ --allowed-methods "GET,POST,DELETE,OPTIONS" \ --allowed-headers "*"Nota:
Para la producción, sustituya los orígenes comodín
*por orígenes específicos de confianza. Consulte Protección de servidores MCP en Container Apps para obtener instrucciones.Compruebe la implementación:
APP_URL=$(az containerapp show \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --query "properties.configuration.ingress.fqdn" -o tsv) curl https://$APP_URL/health
Conexión de GitHub Copilot al servidor implementado
Ahora que el servidor MCP se ejecuta en Azure, configure VS Code para conectar GitHub Copilot al punto de conexión implementado.
En el proyecto, cree o actualice
.vscode/mcp.json:{ "servers": { "tasks-mcp-server": { "type": "http", "url": "https://<your-app-fqdn>/mcp" } } }Reemplace
<your-app-fqdn>por el FQDN de la salida de la implementación.En VS Code, abra El chat de Copilot en modo agente.
Si el servidor no aparece automáticamente, seleccione el botón Herramientas y compruebe
tasks-mcp-serverque aparece. Seleccione Iniciar si es necesario.Pruebe con un mensaje como "Enumerar todas mis tareas" para confirmar que el servidor MCP implementado responde.
Configuración del escalado para uso interactivo
De forma predeterminada, Azure Container Apps puede escalar a cero réplicas. En el caso de los servidores MCP que sirven a clientes interactivos como Copilot, los inicios en frío provocan retrasos notables. Establezca un número mínimo de réplicas para mantener al menos una instancia en ejecución:
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
Consideraciones de seguridad
En este tutorial se usa un servidor MCP no autenticado para simplificar. Antes de ejecutar un servidor MCP en producción, revise las siguientes recomendaciones. Cuando un agente con tecnología de modelos de lenguaje grandes (LLM) llama al servidor MCP, tenga en cuenta los ataques de inyección de solicitudes.
- Autenticación y autorización: proteja el servidor MCP mediante el identificador de Microsoft Entra. Consulte Protección de servidores MCP en Container Apps.
- Validación de entrada: los esquemas Zod proporcionan seguridad de tipos, pero agregan validación de reglas de negocio para los parámetros de la herramienta. Considere bibliotecas como zod-express-middleware para la validación a nivel de solicitud.
- HTTPS: Azure Container Apps aplica HTTPS de forma predeterminada con certificados TLS automáticos.
- Privilegios mínimos: exponga solo las herramientas que requiere su caso de uso. Evite herramientas que realicen operaciones destructivas sin confirmación.
- CORS: restringir orígenes permitidos a dominios de confianza en producción.
- Registro y supervisión: Registra las invocaciones de la herramienta MCP para auditoría. Utilice Azure Monitor y Log Analytics.
Limpieza de recursos
Si no tiene previsto seguir usando esta aplicación, elimine el grupo de recursos para quitar todos los recursos que creó en este tutorial:
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait
Paso siguiente
Contenido relacionado
- Introducción a los servidores MCP en Azure Container Apps
- Implementación de un servidor MCP en Container Apps (.NET)
- Implementación de un servidor MCP en Container Apps (Python)
- Implementación de un servidor MCP en Container Apps (Java)
- Solución de problemas de servidores MCP en Container Apps
- MCP TypeScript SDK