MCP
Model Context Protocol (MCP) 是 Anthropic 推出的協定,讓 AI 助理可以呼叫外部工具。透過 MCP,我們可以擴展 Claude 的能力,讓它存取資料或執行特定功能。
MCP 支援兩種傳輸方式:
本文介紹 stdio 方式,它比較簡單適合入門。
專案
建立一個簡單的天氣查詢 MCP 伺服器:
- 提供 5 個城市的預設天氣資料
- 定義
get_weather
工具讓 Claude 呼叫 - 當使用者詢問天氣時,Claude 自動使用這個工具
環境準備
# 建立專案 mkdir weather-mcp-server cd weather-mcp-server # 初始化並安裝套件 uv init --python 3.11 uv add mcp
結構
weather-mcp-server/ ├── .mcp.json # Claude Code 設定 ├── src/ │ └── weather_server/ │ ├── __init__.py │ ├── weather_data.py # 天氣資料 │ └── stdio_server.py # MCP 伺服器 └── pyproject.toml
步驟 1:定義天氣資料
檔案:src/weather_server/weather_data.py
from typing import TypedDict class WeatherData(TypedDict): temperature: float condition: str humidity: int WEATHER_DATA: dict[str, WeatherData] = { "New York": {"temperature": 22.0, "condition": "Sunny", "humidity": 60}, "London": {"temperature": 12.0, "condition": "Rainy", "humidity": 85}, "Tokyo": {"temperature": 24.0, "condition": "Sunny", "humidity": 65}, "Sydney": {"temperature": 18.0, "condition": "Cloudy", "humidity": 70}, "Paris": {"temperature": 15.0, "condition": "Foggy", "humidity": 80}, } def get_weather(city: str) -> WeatherData | None: for city_name, data in WEATHER_DATA.items(): if city_name.lower() == city.lower(): return data return None def get_supported_cities() -> list[str]: return list(WEATHER_DATA.keys())
步驟 2:實作 MCP 伺服器
檔案:src/weather_server/stdio_server.py
import asyncio import logging from typing import Any from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent from .weather_data import get_weather, get_supported_cities # 設定日誌 logging.basicConfig( level=logging.INFO, handlers=[ logging.StreamHandler(), # stderr logging.FileHandler('/tmp/weather-mcp-server.log') ] ) logger = logging.getLogger("weather-server") app = Server("weather-server") @app.list_tools() async def list_tools() -> list[Tool]: """定義可用的工具""" return [ Tool( name="get_weather", description=f"查詢城市天氣。支援:{', '.join(get_supported_cities())}", inputSchema={ "type": "object", "properties": { "city": {"type": "string", "description": "城市名稱"} }, "required": ["city"], }, ) ] @app.call_tool() async def call_tool(name: str, arguments: Any) -> list[TextContent]: """處理工具呼叫""" if name != "get_weather": raise ValueError(f"未知的工具:{name}") city = arguments.get("city") if not city: raise ValueError("缺少參數:city") logger.info(f"查詢:{city}") weather = get_weather(city) if weather is None: supported = get_supported_cities() text = f"沒有 '{city}' 的資料。支援:{', '.join(supported)}" else: text = ( f"{city} 的天氣:\n" f"溫度:{weather['temperature']}°C\n" f"狀況:{weather['condition']}\n" f"濕度:{weather['humidity']}%" ) return [TextContent(type="text", text=text)] async def main(): logger.info("啟動天氣 MCP 伺服器") async with stdio_server() as (read_stream, write_stream): await app.run(read_stream, write_stream, app.create_initialization_options()) if __name__ == "__main__": asyncio.run(main())
步驟 3:設定 Claude Code
檔案:.mcp.json
{ "mcpServers": { "weather": { "command": "uv", "args": ["run", "python", "-m", "weather_server.stdio_server"], "env": {"PYTHONPATH": "src"} } } }
關鍵概念
為什麼要用 -m 參數?
因為程式碼使用相對引入(from .weather_data import ...
),必須作為模組執行。
# 錯誤 python src/weather_server/stdio_server.py # 正確 PYTHONPATH=src python -m weather_server.stdio_server
stdio 傳輸運作原理
Claude Code 啟動時: 1. 讀取 .mcp.json 2. 執行指定指令(建立子程序) 3. 透過 stdin/stdout 溝通(JSON-RPC) 4. 日誌輸出到 stderr(被捕捉但不顯示)
為什麼需要檔案日誌?
因為 stderr 被 Claude Code 捕捉後不會顯示,所以同時寫到檔案。監看日誌:
tail -f /tmp/weather-mcp-server.log
測試
手動測試伺服器
PYTHONPATH=src uv run python -m weather_server.stdio_server
應該看到啟動訊息,按 Ctrl+C 停止。
在 Claude Code 中測試
- 重新啟動 Claude Code
- 執行
/mcp
確認 weather 伺服器已連接 - 詢問 Claude:"東京現在的天氣如何?"
- 另開終端機監看日誌:
tail -f /tmp/weather-mcp-server.log
Tool 定義說明
Tool( name="get_weather", # 工具名稱 description="...", # 告訴 Claude 這個工具的用途 inputSchema={ # JSON Schema 定義參數 "type": "object", "properties": { "city": {"type": "string"} }, "required": ["city"] } )
Claude 會根據 description 和 inputSchema,在適當時機自動呼叫工具。
常見問題
伺服器連接失敗?
確認 .mcp.json
有設定:
- 使用
-m
參數 - 設定
PYTHONPATH=src
如何看日誌?
tail -f /tmp/weather-mcp-server.log
修改程式碼後如何重新載入?
重新啟動 Claude Code 即可。
沒有留言:
張貼留言