你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
在本教程中,你将生成一个模型上下文协议(MCP)服务器,该服务器使用 Express 和 MCP TypeScript SDK 公开任务管理工具。 将服务器部署到 Azure 容器应用,并从 VS Code 中的 GitHub Copilot 聊天连接到它。
在本教程中,你将了解:
- 创建公开 MCP 工具的 Express 应用
- 使用 GitHub Copilot 在本地测试 MCP 服务器
- 容器化应用并将其部署到 Azure 容器应用
- 将 GitHub Copilot 连接到部署的 MCP 服务器
先决条件
- 拥有有效订阅的 Azure 帐户。 免费创建一个。
- Azure CLI 2.62.0 或更高版本。
- Node.js 20 LTS 或更高版本。
- Visual Studio Code 与 GitHub Copilot 扩展。
- Docker Desktop (可选 - 只需在本地测试容器)。
创建应用基架
在本部分中,你将使用 Express 和 MCP TypeScript SDK 创建新的 Node.js 项目。
创建项目目录并初始化它:
mkdir tasks-mcp-server && cd tasks-mcp-server npm init -y安装依赖项:
npm install @modelcontextprotocol/sdk express zod npm install -D typescript @types/node @types/express tsx创建
tsconfig.json:{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "declaration": true }, "include": ["src/**/*"] }此配置针对采用 Node.js 模块解析的 ES2022,将编译后的文件输出到
dist/,并启用严格的类型检查。更新
package.json以启用 ES 模块并添加生成和启动脚本。 添加或替换type和scripts字段:{ "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsx watch src/index.ts" } }重要
设置
"type": "module"。 MCP 服务器代码使用顶级await,仅在 ES 模块中受支持。为内存中数据存储创建
src/taskStore.ts: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();该
TaskItem接口定义任务数据形状。 该TaskStore类管理预填充了示例数据的内存中数组,并提供用于列出、查找、创建、切换和删除任务的方法。 导出模块级单一实例供 MCP 工具使用。
定义 MCP 工具
接下来,您需要定义 MCP 服务器,并通过工具注册将任务存储公开给 AI 客户端。
创建
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`); });要点:
-
McpServer在 TypeScript SDK 中,通过工具注册来定义 MCP 服务器。 -
StreamableHTTPServerTransport处理 MCP 可流式传输 HTTP 协议。 设置sessionIdGenerator: undefined以无状态模式运行服务器。 - 工具使用 Zod 架构定义具有说明的输入参数。
- 容器应用的状态探测需要单独的
/health终结点。
-
在本地测试 MCP 服务器
在部署到 Azure 之前,请验证 MCP 服务器是否正常工作,方法是在本地运行它并从 GitHub Copilot 进行连接。
启动开发服务器:
npx tsx src/index.ts打开 VS Code,打开 Copilot 对话助手,然后选择 代理 模式。
选择 “工具 ”按钮,然后选择“ 添加更多工具...”>添加 MCP 服务器。
选择“HTTP(HTTP 或 Server-Sent 事件)”。
输入服务器 URL:
http://localhost:3000/mcp注释
本地开发服务器默认为端口 3000。 容器化后,Dockerfile 会将
PORT环境变量设置为 8080 以匹配容器应用目标端口。输入服务器 ID:
tasks-mcp选择 工作区设置。
使用提示进行测试: “显示所有任务”
在 Copilot 请求工具调用确认时 选择“继续 ”。
您应该看到 Copilot 从内存存储中返回任务列表。
小窍门
尝试其他提示,例如“创建任务以查看 PR”、“将任务 1 标记为已完成”或“删除任务 2”。
容器化应用程序
将应用程序打包为 Docker 容器,以便在部署到 Azure 之前在本地对其进行测试。
创建
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"]多阶段构建在第一阶段编译 TypeScript,然后创建一个仅包含运行时依赖项和已编译的 JavaScript 输出的生产镜像。 环境变量
PORT设置为 8080 以匹配容器应用目标端口。在本地验证:
docker build -t tasks-mcp-server . docker run -p 8080:8080 tasks-mcp-server确认:
curl http://localhost:8080/health
部署到 Azure 容器应用
容器化应用程序后,使用 Azure CLI 将其部署到 Azure 容器应用。 该 az containerapp up 命令在云中生成容器映像,因此,此步骤不需要计算机上的 Docker。
设置环境变量:
RESOURCE_GROUP="mcp-tutorial-rg" LOCATION="eastus" ENVIRONMENT_NAME="mcp-env" APP_NAME="tasks-mcp-server-node"创建资源组:
az group create --name $RESOURCE_GROUP --location $LOCATION创建容器应用环境:
az containerapp env create \ --name $ENVIRONMENT_NAME \ --resource-group $RESOURCE_GROUP \ --location $LOCATION部署容器应用:
az containerapp up \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --environment $ENVIRONMENT_NAME \ --source . \ --ingress external \ --target-port 8080配置 CORS 以允许 GitHub Copilot 请求:
az containerapp ingress cors enable \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --allowed-origins "*" \ --allowed-methods "GET,POST,DELETE,OPTIONS" \ --allowed-headers "*"注释
在生产环境中,请将通配符
*源替换为具体的受信任源。 有关指导,请参阅 容器应用中的安全 MCP 服务器 。验证部署:
APP_URL=$(az containerapp show \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --query "properties.configuration.ingress.fqdn" -o tsv) curl https://$APP_URL/health
将 GitHub Copilot 连接到已部署的服务器
现在 MCP 服务器在 Azure 中运行,请将 VS Code 配置为将 GitHub Copilot 连接到已部署的终结点。
在项目中,创建或更新
.vscode/mcp.json:{ "servers": { "tasks-mcp-server": { "type": "http", "url": "https://<your-app-fqdn>/mcp" } } }将
<your-app-fqdn>替换为部署输出中的 FQDN。在 VS Code 中,在代理模式下打开 Copilot 聊天。
如果未自动显示服务器,请选择 “工具” 按钮并验证
tasks-mcp-server是否已列出。 根据需要选择 “开始 ”。使用 “列出所有任务” 等提示进行测试,以确认部署的 MCP 服务器响应。
配置缩放以供交互式使用
默认情况下,Azure 容器应用可以扩展到零个副本。 对于为 Copilot 等交互式客户端提供服务的 MCP 服务器,冷启动会导致明显的延迟。 设置最小副本计数以保持至少一个实例运行:
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
安全注意事项
本教程为简单起见,使用未经身份验证的 MCP 服务器。 在生产环境中运行 MCP 服务器之前,请查看以下建议。 当由大型语言模型(LLM)提供支持的代理调用 MCP 服务器时,请注意 提示注入 攻击。
- 身份验证和授权:使用 Microsoft Entra ID 保护 MCP 服务器。 请参阅 容器应用中的安全 MCP 服务器。
- 输入验证:Zod 架构提供类型安全性,但为工具参数添加业务规则验证。 考虑使用诸如zod-express-middleware这样的库进行请求级验证。
- HTTPS:Azure 容器应用默认使用自动 TLS 证书强制实施 HTTPS。
- 最低特权:仅公开用例所需的工具。 避免使用在未经确认的情况下执行破坏性操作的工具。
- CORS:将允许的源限制为生产中的受信任域。
- 日志记录和监视:记录 MCP 工具调用进行审核。 使用 Azure Monitor 和 Log Analytics。
清理资源
如果不打算继续使用此应用程序,请删除资源组以删除在本教程中创建的所有资源:
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait