إشعار
يتطلب الوصول إلى هذه الصفحة تخويلاً. يمكنك محاولة تسجيل الدخول أو تغيير الدلائل.
يتطلب الوصول إلى هذه الصفحة تخويلاً. يمكنك محاولة تغيير الدلائل.
في هذا الدرس، تبني خادم بروتوكول السياق النموذجي (MCP) الذي يعرض أدوات إدارة المهام باستخدام Express وحزمة تطوير البرمجيات MCP TypeScript. تقوم بنشر الخادم إلى Azure Container Apps وتتصل به من GitHub Copilot Chat في VS Code.
في هذا البرنامج التعليمي، سوف تتعلّم:
- أنشئ تطبيق إكسبريس يعرض أدوات MCP
- اختبار خادم MCP محليا باستخدام GitHub Copilot
- Containerize ونشر التطبيق في Azure Container Apps
- توصيل GitHub Copilot بخادم MCP المنشور
المتطلبات المسبقه
- حساب Azure مع اشتراك نشط. أنشئ واحدا مجانا.
- Azure CLI الإصدار 2.62.0 أو أحدث.
- Node.js 20 LTS أو أكثر.
- كود Visual Studio مع إضافة GitHub Copilot .
- Docker Desktop (اختياري - يكفي فقط لاختبار الحاوية محليا).
أنشئ منصة التطبيق
في هذا القسم، تنشئ مشروع Node.js جديد باستخدام Express وحزمة تطوير MCP TypeScript.
أنشئ مجلد المشروع وقم بتهيئته:
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/**/*"] }يستهدف هذا التكوين ES2022 بدقة Node.js للوحدات، ويخرج الملفات المترجمة إلى
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 بتسجيلات أدوات تعرض مخزن المهام لعملاء الذكاء الاصطناعي.
إنشاء
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تتعامل مع بروتوكول HTTP القابل للبث في MCP. تشغيلsessionIdGenerator: undefinedالخادم في وضع بدون حالة. - تستخدم الأدوات مخططات زود لتعريف معلمات الإدخال مع الأوصاف.
- يتطلب الأمر نقطة نهاية منفصلة
/healthلاختبارات صحة تطبيقات الحاويات.
-
اختبار خادم MCP محليا
قبل النشر على Azure، تحقق من عمل خادم MCP عن طريق تشغيله محليا والاتصال من GitHub Copilot.
بدء تشغيل خادم التطوير:
npx tsx src/index.tsافتح كود VS، افتح دردشة Copilot، واختر وضع الوكيل .
اختر زر الأدوات ، ثم اختر إضافة المزيد من الأدوات...>أضف خادم MCP.
حدد HTTP (HTTP أو أحداث Server-Sent).
أدخل رابط الخادم:
http://localhost:3000/mcpملاحظة
خادم التطوير المحلي ينصب افتراضيا على المنفذ 3000. عند الحاوية، يضبط ملف دوكر متغير
PORTالبيئة على 8080 ليطابق منفذ الهدف في تطبيقات الحاويات.أدخل معرف الخادم:
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 Container Apps
بعد أن تقوم بتحويل التطبيق إلى حاوية، قم بنشره في Azure Container Apps باستخدام Azure CLI. تقوم هذه الأوامر 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من القائمة. اختر Start إذا لزم الأمر.اختبر بطلب مثل "سرد جميع مهامي" للتأكد من استجابة خادم MCP المنتشرة.
تكوين التوسع للاستخدام التفاعلي
بشكل افتراضي، يمكن لتطبيقات حاويات Azure أن تتوسع إلى صفر نسخ. بالنسبة لخوادم MCP التي تخدم عملاء تفاعليين مثل Copilot، يسبب التشغيل البارد تأخيرات ملحوظة. حدد الحد الأدنى لعدد النسخ للحفاظ على تشغيل نسخة واحدة على الأقل:
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
اعتبارات الأمان
يستخدم هذا الدرس خادم MCP غير مصادق للبساطة. قبل تشغيل خادم MCP في الإنتاج، راجع التوصيات التالية. عندما يتصل وكيل مدعوم بنماذج لغوية كبيرة (LLMs) بخادم MCP الخاص بك، كن على علم بهجمات حقن الطلبات .
- المصادقة والتفويض: أمن خادم MCP الخاص بك باستخدام Microsoft Entra ID. انظر خوادم MCP الآمنة على تطبيقات الحاويات.
- التحقق من صحة المدخلات: توفر مخططات Zod أمان النوع، لكنها تضيف التحقق من قواعد الأعمال لمعلمات الأداة. فكر في مكتبات مثل zod-express-middleware للتحقق من صحة على مستوى الطلب.
- HTTPS: Azure Container Apps يفرض HTTPS بشكل افتراضي مع شهادات TLS تلقائية.
- أقل امتياز: اعرض فقط الأدوات التي تتطلبها حالتك. تجنب الأدوات التي تقوم بعمليات مدمرة دون تأكيد.
- CORS: تقييد المصادر المسموح بها على النطاقات الموثوقة في الإنتاج.
- التسجيل والمراقبة: سجل استدعاءات أدوات MCP للتدقيق. استخدم Azure Monitor وLog Analytics.
تنظيف الموارد
إذا لم تكن تخطط للاستمرار في استخدام هذا التطبيق، احذف مجموعة الموارد لإزالة جميع الموارد التي أنشأتها في هذا الدرس:
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait