
MCPサーバー作成の公式クイックスタートをやってみた
お疲れさまです。とーちです。
こちらの記事を読んで、MCPサーバーすごそうとなったのでMCPについてキャッチアップしたくなりました。
また、こちらの資料を読んでいて知ったのですが、MCPサーバーを作るクイックスタートが公開されているようです。これは良さそうだと思ったのでクイックスタートをやってみることにしました。
クイックスタートのURLは以下になります。
MCPとは?
そもそもMCPとは?といった部分については上記の資料等をご確認いただければと思いますが、自分なりに理解したことをまとめると以下のようになります。
- MCPとは
- アプリケーションが LLM にコンテキストを提供する方法を標準化するためのもの
- MCP は、AI アプリケーション用の USB-C ポートのようなもので、ソースごとにMCPを作成することでLLMが扱うデータを拡張していくことができる
公式ドキュメントでは以下のあたりの説明が概要を掴むのに分かりやすいかなと思いました。
それではやっていきましょう。
サーバーの作成
環境準備
まずuvをインストールします。
> curl -LsSf https://astral.sh/uv/install.sh | sh
downloading uv 0.6.12 aarch64-apple-darwin
no checksums to verify
installing to /Users/username/.local/bin
uv
uvx
everything's installed!
uvはpython用のパッケージマネージャで、非常に高速なのが特徴のようです。仮想環境の作成なども出来ます。以下URLで分かりやすく解説されていましたのでご参照ください。
まずはuv init weather
でPythonプロジェクトの作成と初期化を行います。
uv venv
と source .venv/bin/activate
でPython仮想環境を作成します。
bash-5.2$ uv init weather
bash-5.2$ cd weather/
bash-5.2$ uv venv
bash-5.2$ source .venv/bin/activate
(weather) bash-5.2$
uv add "mcp[cli]" httpx
で必要パッケージをインストールします。mcp[cli]
でmcp
という名前のパッケージを、cli
というオプション機能を含めてインストールしています。
(weather) bash-5.2$ uv add "mcp[cli]" httpx
Resolved 28 packages in 1.05s
Prepared 26 packages in 261ms
Installed 26 packages in 61ms
+ annotated-types==0.7.0
+ anyio==4.9.0
+ certifi==2025.1.31
+ click==8.1.8
+ h11==0.14.0
+ httpcore==1.0.7
+ httpx==0.28.1
+ httpx-sse==0.4.0
+ idna==3.10
+ markdown-it-py==3.0.0
+ mcp==1.6.0
+ mdurl==0.1.2
+ pydantic==2.11.2
+ pydantic-core==2.33.1
+ pydantic-settings==2.8.1
+ pygments==2.19.1
+ python-dotenv==1.1.0
+ rich==14.0.0
+ shellingham==1.5.4
+ sniffio==1.3.1
+ sse-starlette==2.2.1
+ starlette==0.46.1
+ typer==0.15.2
+ typing-extensions==4.13.1
+ typing-inspection==0.4.0
+ uvicorn==0.34.0
MCPサーバーコードの実装
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"
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')}
"""
@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')
※このコードはFor Server Developers - Model Context Protocolから引用しています。
コードの解説
ざっくりコードを読み解いてみましょう。まず冒頭の以下の記載についてです。
# Initialize FastMCP server
mcp = FastMCP("weather")
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
mcp = FastMCP("weather")
でMCPサーバーを初期化していますNWS_API_BASE
変数は米国国立気象局(National Weather Service)のAPIにリクエストを送るために使用されるベースURLです。make_nws_request
関数に渡されるURLの基本部分として使用されますUSER_AGENT
変数はHTTPリクエストを送る際にヘッダとしてつけるユーザーエージェント情報になります
各関数から共通で使われる関数としてmake_nws_request
関数が定義されており、この関数は以下のようなことをします
- リクエストの準備:
headers
という以下の要素を含むヘッダー情報を準備していますUser-Agent
: アプリの名前と版数をユーザーエージェントとして指定Accept
: 「地理情報付きのJSONデータが欲しい」と伝える
- データの取得:
httpx
というライブラリを使って、指定されたURLにアクセス(アクセスする際にヘッダーとして上記のheaders
に格納した情報を使用)- 30秒以内に応答がないとあきらめる(
timeout=30.0
)
- データ変換:
response.json()
: サーバーから返ってきたデータをPythonで使いやすい形(辞書型)に変換
また、クイックスタートのコードの全体像としては、以下の2つのツールが含まれています。この2つのツールがLLMから使用可能となる形です。
get_alerts
)
気象警報ツール (- 入力: 米国の州を表す2文字のコード(例: CA, NY)
- 処理:
- 指定された州の現在有効な気象警報をNWS APIから取得
- 各警報の詳細情報を整形して表示
- 出力: 警報の種類、影響地域、重大度、説明、指示事項を含む整形されたテキスト
get_forecast
)
天気予報ツール (- 入力: 緯度と経度の座標
- 処理:
- 座標から対応する予報グリッドポイントの情報を取得
- グリッドポイント情報から予報URLを取得
- 予報URLから詳細な天気予報データを取得
- 今後5つの予報期間の情報を整形
- 出力: 各予報期間の名前、気温、風向風速、詳細な予報内容を含む整形されたテキスト
get_alerts
関数の入力となる州を表す2文字のコードは、ユーザーの質問をClaudeが解析して生成し、クライアント経由でMCPサーバーに送信されます。MCPサーバー側はこの2文字のコードを受け取って処理するだけです。
get_forecast
も同様で、緯度と経度の情報はユーザーの入力(例えば「サンフランシスコの天気を教えて」)をClaudeが解析し、適切な座標に変換してMCPサーバーに送信します。つまり、ツールとして登録した関数に何を入力すべきかの解釈はMCPサーバー側ではなくClaude側で行われているということですね。
これらを見ると、各ツールはあくまでも情報を提供するだけで、ごく一般的な作りだということがわかります。
デコレータの役割
重要なのが@mcp.tool()
の記載です。@というのはPythonでデコレータを表します。デコレータというのはある関数(今回のコードではget_forecast
やget_alerts
)を修飾するための関数(今回のコードではmcp.tool()
)で、修飾したい関数の上に@で始まる行を記載する書き方となります。
「修飾する」というと何をやってるのかが分かりづらいのですが、要は「 関数を引数として受け取って、何らかの処理をした後、新しい関数を返す」 という処理を行うのがデコレータとなります。
今回でいうと、mcp.server.fastmcp
ライブラリに含まれるtool関数にget_alerts
等の関数自体が渡されることで、get_alerts
をMCPツールとして登録しているといった感じです(ざっくりいうと)。
私もコードを詳細に理解できているわけではないのですが、MCPツールとしての登録というのは関数名の取得や渡された関数の引数情報の解析などを行っているようです。登録処理の該当箇所は以下になると思います。
サーバーの動作確認
weather.pyを作成したら、動作確認してみます。
(weather) bash-5.2$ uv run weather.py
なにも表示されないですね。うまくいってるのかどうなのか…
Claude for Desktopとの連携設定
クイックスタートでは、Claude for DesktopをMCPクライアントとして使用する手順になっています。このクイックスタートとは別にMCPクライアントプログラムを自作するクイックスタートもあるのですが、とりあえずはClaude for Desktopをクライアントとして使ってみます。
Claude for Desktopの設定を変更して作成したMCPサーバーを使うように設定してみます。設定ファイルを作成して追記したあとにClaude for Desktopを一度終了させてからもう一度起動させます。
おっとエラーになりました。
これは、Claude for Desktopの設定ファイルの書き方に問題があり、MCPサーバーを起動出来ていなかったのが原因でした。
{
"mcpServers": {
"weather": {
"command": "/Users/***/.local/bin/uv",
"args": [
"--directory",
"<weather.pyが存在するディレクトリを絶対パスで指定>",
"run",
"weather.py"
]
}
}
}
commandのところで最初はuv
とだけ指定していたのですが、これをフルパスで指定する必要がありました。
設定ファイルを修正してから再度Claude for Desktopを起動すると以下のような表示が出ました。
四角枠で囲った部分が追加されています。
ハンマーのボタンをクリックすると以下の表示が出ます。
試しに公式の質問に従って質問したところ、以下のような確認が表示されました。ちゃんと許可するかどうかを確認してくれるんですね。
質問の回答が返ってきました。おお、ちゃんと天気情報を取ってきているようです。
公式ドキュメントによるとこのとき以下のようなことが行われているとのことです(For Server Developers - Model Context Protocolより)
- クライアントはあなたの質問をClaudeに送信します
- Claudeは利用可能なツールを分析し、どのツールを使用するかを決定します
- クライアントはMCPサーバーを介して選択されたツールを実行します
- 結果はClaudeに送り返される
- Claudeは自然言語で応答する
- 応答が表示されます
このあたりクライアント向けクイックスタートもやればもう少し解像度高く理解出来そうな気がするので、クライアント向けのほうも後日やってみようと思います。
まとめ
今回はModel Context Protocol (MCP)サーバーを実際に作成し、Claude for Desktopと連携させてみました。MCPを使うことで、LLMに外部データソースへのアクセス機能を簡単に追加できることがわかりました。クライアント向けクイックスタートもぜひ試してみたいですね。
以上、とーちでした。