在這個教學中,你將建立一個模型情境協定(MCP)伺服器,透過 FastAPI 和 MCP Python SDK 來暴露任務管理工具。 你要把伺服器部署到 Azure 容器應用程式,然後用 VS Code 從 GitHub Copilot Chat 連接。
在本教學課程中,您會:
- 建立一個 FastAPI 應用程式來揭露 MCP 工具
- 用 GitHub Copilot 在本地測試 MCP 伺服器
- 將應用程式容器化並部署至 Azure 容器應用程式
- 將 GitHub Copilot 連接到已部署的 MCP 伺服器
先決條件
- 具有有效訂閱的 Azure 帳戶。 免費創建一個。
- Azure CLI 版本 2.62.0 或更新版本。
- Python 3.10 或更新版本。
- 具有 GitHub Copilot 延伸模組的 Visual Studio Code
- Docker 桌面 (可選,僅需在本地測試容器)。
建立應用程式架構
在本節中,你將使用 FastAPI 和 MCP Python SDK 建立一個新的 Python 專案。
建立專案目錄並建立虛擬環境:
mkdir tasks-mcp-server && cd tasks-mcp-server python -m venv .venv source .venv/bin/activate創建
requirements.txt:fastapi>=0.115.0 uvicorn>=0.30.0 mcp[cli]>=1.2.0安裝相依性:
pip install -r requirements.txt為記憶體內資料儲存建立
task_store.py:from dataclasses import dataclass, field from datetime import datetime, timezone @dataclass class TaskItem: id: int title: str description: str is_complete: bool = False created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) def to_dict(self) -> dict: return { "id": self.id, "title": self.title, "description": self.description, "is_complete": self.is_complete, "created_at": self.created_at.isoformat(), } class TaskStore: def __init__(self): self._tasks: list[TaskItem] = [ TaskItem(1, "Buy groceries", "Milk, eggs, bread"), TaskItem(2, "Write docs", "Draft the MCP tutorial", True), ] self._next_id = 3 def get_all(self) -> list[dict]: return [t.to_dict() for t in self._tasks] def get_by_id(self, task_id: int) -> dict | None: task = next((t for t in self._tasks if t.id == task_id), None) return task.to_dict() if task else None def create(self, title: str, description: str) -> dict: task = TaskItem(self._next_id, title, description) self._next_id += 1 self._tasks.append(task) return task.to_dict() def toggle_complete(self, task_id: int) -> dict | None: task = next((t for t in self._tasks if t.id == task_id), None) if task is None: return None task.is_complete = not task.is_complete return task.to_dict() def delete(self, task_id: int) -> bool: task = next((t for t in self._tasks if t.id == task_id), None) if task is None: return False self._tasks.remove(task) return True # For demonstration only — not thread-safe. store = TaskStore()TaskItem資料類別以序列化方法定義資料模型to_dict()。 該TaskStore類別管理一個預先填充樣本資料的記憶體清單,並提供 CRUD 方法。 模組層級store的單例在整個應用程式中共享以簡化流程。
定義 MCP 工具
在本節中,你定義 AI 模型可調用的 MCP 工具,並將 MCP 伺服器掛載到 FastAPI 應用程式中。
創建
mcp_server.py:from mcp.server.fastmcp import FastMCP from task_store import store mcp = FastMCP("TasksMCP", stateless_http=True) @mcp.tool() async def list_tasks() -> list[dict]: """List all tasks with their ID, title, description, and completion status.""" return store.get_all() @mcp.tool() async def get_task(task_id: int) -> dict | None: """Get a single task by its numeric ID. Args: task_id: The numeric ID of the task to retrieve. """ return store.get_by_id(task_id) @mcp.tool() async def create_task(title: str, description: str) -> dict: """Create a new task with the given title and description. Returns the created task. Args: title: A short title for the task. description: A detailed description of what the task involves. """ return store.create(title, description) @mcp.tool() async def toggle_task_complete(task_id: int) -> str: """Toggle a task's completion status between complete and incomplete. Args: task_id: The numeric ID of the task to toggle. """ task = store.toggle_complete(task_id) if task: status = "complete" if task["is_complete"] else "incomplete" return f"Task {task['id']} is now {status}." return f"Task with ID {task_id} not found." @mcp.tool() async def delete_task(task_id: int) -> str: """Delete a task by its numeric ID. Args: task_id: The numeric ID of the task to delete. """ if store.delete(task_id): return f"Task {task_id} deleted." return f"Task with ID {task_id} not found."重點︰
-
FastMCP("TasksMCP", stateless_http=True)在 Python SDK 中使用 Stateless HTTP 模式建立 MCP 伺服器。 可串流的 HTTP 端點預設使用/mcp子路徑。 - 每個
@mcp.tool()函式都成為可調用的工具。 docstring 和參數註解功能幫助 AI 模型了解如何使用每個工具。
-
創建
app.py。 此檔案定義了掛載 MCP 伺服器的 FastAPI 應用程式:from contextlib import AsyncExitStack, asynccontextmanager from fastapi import FastAPI from fastapi.responses import JSONResponse from mcp_server import mcp @asynccontextmanager async def lifespan(app: FastAPI): async with AsyncExitStack() as stack: await stack.enter_async_context(mcp.session_manager.run()) yield app = FastAPI(lifespan=lifespan) app.mount("/", mcp.streamable_http_app()) @app.get("/health") async def health(): return JSONResponse({"status": "healthy"})MCP 伺服器應用程式會掛載在根節點(
/)。 SDK 的可串流 HTTP 端點預設為/mcp,因此完整的端點路徑為/mcp。容器應用程式健康探測則使用獨立
/health端點。 MCP 端點預期會收到 JSON-RPC POST 請求,並不適合作為健康檢查。
在本機測試 MCP 伺服器
在部署到 Azure 之前,先透過本地運行 MCP 伺服器並從 GitHub Copilot 連接來確認它正常運作。
開始申請:
uvicorn app:app --reload --port 8080打開 VS Code,然後打開 Copilot 聊天 ,選擇 Agent 模式。
選擇 工具 按鈕,然後選擇 新增更多工具...>新增 MCP 伺服器。
選取HTTP(HTTP 或 Server-Sent 事件)。
輸入伺服器網址:
http://localhost:8080/mcp輸入伺服器 ID:
tasks-mcp選取 [工作區設定]。
在新的 Copilot 聊天提示中,輸入 :「Show me all tasks」
當 Copilot 提示確認 MCP 工具時,選擇 繼續 。
您應該會看到從記憶體內存放區傳回的工作清單。
小提示
試試其他提示,例如「建立任務以檢視 PR」、「標記任務 1 為完成」或「刪除任務 2」。
將應用程式容器化
把應用程式打包成 Docker 容器,這樣你可以在本地測試再部署到 Azure。
建立
Dockerfile:FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8080 CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]Dockerfile 使用 Python 3.12 slim 基礎映像,從
requirements.txt安裝相依性,然後複製應用程式程式碼。 Uvicorn 在 8080 埠口提供 FastAPI 應用程式。確認容器是否在本地建置並執行:
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-py"建立資源群組與容器應用程式環境:
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:
az containerapp ingress cors enable \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --allowed-origins "*" \ --allowed-methods "GET,POST,DELETE,OPTIONS" \ --allowed-headers "*"備註
在實際執行環境中,將萬用字元來源替換成特定的可信來源。 請參閱 容器應用程式上的 Secure 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" } } }用部署輸出的 FQDN 替換
<your-app-fqdn>。在 VS Code 中,以客服模式開啟 Copilot 聊天。
驗證
tasks-mcp-server會出現在工具清單中。 如果需要,選擇 開始 。用像 「建立任務以部署預備環境」這樣的提示來測試。
為互動使用設定縮放
預設情況下,Azure 容器應用程式可以擴展到零複本。 對於服務互動客戶端如 Copilot 的 MCP 伺服器,冷啟動會造成明顯延遲。 設定最小副本數量,以維持至少一個實例在運行:
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
安全性考慮
本教學使用未認證的 MCP 伺服器以簡化操作。 在正式環境中運行 MCP 伺服器前,請先檢視以下建議。 當由大型語言模型 (LLM) 技術支援的 Agent 呼叫您的 MCP 伺服器時,請注意提示注入攻擊。
- 認證與授權:用 Microsoft Entra ID 保護你的 MCP 伺服器。 請參閱 容器應用程式上的 Secure MCP 伺服器。
- 輸入驗證:務必驗證工具參數。 使用 Pydantic 強制工具輸入的資料驗證。
- HTTPS:Azure 容器應用程式預設會強制執行 HTTPS,並自動使用 TLS 憑證。
- 最低權限:只公開你使用情境所需的工具。 避免使用未經確認就進行破壞性操作的工具。
- CORS:在執行環境中只允許受信任的網域作為來源。
- 日誌與監控:記錄 MCP 工具的調用以進行稽核。 使用 Azure 監視器 和日誌分析。
清理資源
如果你不打算繼續使用這個應用程式,請刪除資源群組,移除你在教學中建立的所有資源:
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait