背景・目的
下記で、Claude Desktopを試しました。今回はMCPサーバを試します。
実践
下記を基に実践します。
事前準備
-
uv
というPythonパッケージ管理ツールをインストールします% curl -LsSf https://astral.sh/uv/install.sh | sh downloading uv 0.7.9 x86_64-apple-darwin no checksums to verify installing to /Users/XXXX/.local/bin uv uvx everything's installed! %
-
インストールされました
% uv --version uv 0.7.9 (13a86a23b 2025-05-30) %
環境の設定
-
プロジェクトを作成します
% uv init weather Initialized project `weather` at `/Users/XXXXX/git/weather` % % cd weather
-
venvで仮想環境を作成します
% uv venv Using CPython 3.12.5 interpreter at: /Users/XXXXXX/.pyenv/versions/3.12.5/bin/python3.12 Creating virtual environment at: .venv Activate with: source .venv/bin/activate %
-
仮想環境を有効化します
% source .venv/bin/activate (weather) %
-
MCP Python SDKをインストールします
(weather) % uv add "mcp[cli]" httpx Resolved 29 packages in 742ms Prepared 27 packages in 650ms Installed 27 packages in 40ms + annotated-types==0.7.0 + anyio==4.9.0 + certifi==2025.4.26 + click==8.2.1 + h11==0.16.0 + httpcore==1.0.9 + httpx==0.28.1 + httpx-sse==0.4.0 + idna==3.10 + markdown-it-py==3.0.0 + mcp==1.9.2 + mdurl==0.1.2 + pydantic==2.11.5 + pydantic-core==2.33.2 + pydantic-settings==2.9.1 + pygments==2.19.1 + python-dotenv==1.1.0 + python-multipart==0.0.20 + rich==14.0.0 + shellingham==1.5.4 + sniffio==1.3.1 + sse-starlette==2.3.6 + starlette==0.47.0 + typer==0.16.0 + typing-extensions==4.13.2 + typing-inspection==0.4.1 + uvicorn==0.34.2 (weather) %
サーバの構築
パッケージのインポートとインスタンスの設定
-
weather.py
を作成し下記のコードを実装します - 下記のコードを実装します
from typing import Any import httpx from mcp.server.fastmcp import FastMCP # Initialize FastMCP server mcp = FastMCP("weather") # Constants NWS_API_BASE = "https://api.weather.gov" USER_AGENT = "weather-app/1.0"
- FastMCPクラス:Pythonの型ヒントとドキュメント文字列を使用してツール定義を自動的に生成する。MCPツールの作成と保守を容易にする
ヘルパー関数
- 下記を追加します
async def make_nws_request(url: str) -> dict[str, Any] | None: """Make a request to the NWS API with proper error handling.""" headers = { "User-Agent": USER_AGENT, "Accept": "application/geo+json" } async with httpx.AsyncClient() as client: try: response = await client.get(url, headers=headers, timeout=30.0) response.raise_for_status() return response.json() except Exception: return None def format_alert(feature: dict) -> str: """Format an alert feature into a readable string.""" props = feature["properties"] return f""" Event: {props.get('event', 'Unknown')} Area: {props.get('areaDesc', 'Unknown')} Severity: {props.get('severity', 'Unknown')} Description: {props.get('description', 'No description available')} Instructions: {props.get('instruction', 'No specific instructions provided')} """
- National Weather Service APIからのデータをクエリしてフォーマットするヘルパー関数
ツール実行の実装
- 下記を追加します
@mcp.tool() async def get_alerts(state: str) -> str: """Get weather alerts for a US state. Args: state: Two-letter US state code (e.g. CA, NY) """ url = f"{NWS_API_BASE}/alerts/active/area/{state}" data = await make_nws_request(url) if not data or "features" not in data: return "Unable to fetch alerts or no alerts found." if not data["features"]: return "No active alerts for this state." alerts = [format_alert(feature) for feature in data["features"]] return "\n---\n".join(alerts) @mcp.tool() async def get_forecast(latitude: float, longitude: float) -> str: """Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location """ # First get the forecast grid endpoint points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" points_data = await make_nws_request(points_url) if not points_data: return "Unable to fetch forecast data for this location." # Get the forecast URL from the points response forecast_url = points_data["properties"]["forecast"] forecast_data = await make_nws_request(forecast_url) if not forecast_data: return "Unable to fetch detailed forecast." # Format the periods into a readable forecast periods = forecast_data["properties"]["periods"] forecasts = [] for period in periods[:5]: # Only show next 5 periods forecast = f""" {period['name']}: Temperature: {period['temperature']}°{period['temperatureUnit']} Wind: {period['windSpeed']} {period['windDirection']} Forecast: {period['detailedForecast']} """ forecasts.append(forecast) return "\n---\n".join(forecasts)
- ツール実行ハンドラーは、各ツールのロジックを実際に実行する役割を担う
サーバの実行
-
下記を追加します
if __name__ == "__main__": # Initialize and run the server mcp.run(transport='stdio')
-
実行します
% uv run weather.py
Claude for Desktopでサーバをテストする
事前準備
下記でインストールした環境を使用します。
claude_desktop_configの修正
- claude_desktop_config.jsonを上記で追加したサーバを利用できるよう修正します(weatherを追加しました)
{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/XXXX/Desktop", "/Users/XXXX/Downloads" ] }, "weather": { "command": "uv", "args": [ "--directory", "/Users/XXXX/git/weather", "run", "weather.py" ] } } }
- weatherという名前のMCPサーバをがある
- 実行して起動するには、
uv --directory /path/ run weather.py
を実行する
テストする
アメリカの天気
東京の天気
- 東京の天気は、MCPサーバ上では取得できないので、下記のように表示されています
-
https://api.weather.gov では、日本の天気は取得できない
-
https://api.weather.gov/alerts/active/area/LA などはUSの地域に特化している
-
https://api.weather.gov/alerts/active/area/LA などはUSの地域に特化している
-
https://api.weather.gov では、日本の天気は取得できない
考察
今回は、MCPサーバ構築のチュートリアルを試しました。実際のコードを眺めてみるとJSONで返す点や、パラメータに何を受け取るかなど、LLMとのI/Fについて理解できました。
次回は、MCPクライアントを作成して理解を深めます。
参考