🔰

今更MCPサーバー入門してみた

に公開

まえがき

最近はMCPの名前を聞かない日がないというくらいには人気のプロトコルですね。
これだけ騒がれているのに、先延ばしにして触っていなかったので公式ドキュメントを参考に入門しました。

公式ブログ

https://modelcontextprotocol.io/introduction

MCPとは?よくわからない概念

公式によるとMCP は AI アプリケーション向けの USB-C ポートのようなものと考えることができます...?
アプリケーションと AI を繋ぐためのなんかの線ってこと?それは物理?なぜ C ポート?と色々な疑問が自分の中で出てきてよくわからなくなりました。

ただ以下の記事を読んで、他の人の作ったものではなく自作したMCPサーバーを使うのが無難だと感じた。
https://zenn.dev/karaage0703/articles/bc369a11a82263

とりあえず作って確かめてみるしか無いのでやってみた。

天気 API をたたく MCPサーバーをClaude Desktop から使用する

以下から Claude Desktop をインストールする

https://claude.ai/download

ダウンロード完了!

公式チュートリアル

https://modelcontextprotocol.io/quickstart/server
とりあえず作成するサーバーは2つ

  • get-alerts(警報取得)
  • get-forecast(予報取得)
  • そして、このサーバーを MCP ホスト(この場合は Claude for Desktop)に接続...なるほど

とりあえずAPIを使用するサーバー2つと指示するClaude Desktopがあるのはなんとなくわかった。
LLM が外部の気象データにアクセスできるようになって、ユーザーの質問に対してリアルタイムの天気情報を提供できるように!

これまでLLMはユーザーの質問には答えられるがAPIを操作したりはできなかった。だから最新情報とかをとってこれなかったり、苦手な分野(計算など)は対応できなかったがこれで対応できるということらしい。それは流行るよね。

uv のインストール

便利とは聞いていたが入れてなかったので以下のコマンドで導入

curl -LsSf https://astral.sh/uv/install.sh | sh

このあとターミナルを一度再起動します。

uv init weather

で以下のような表示が出ます。(ファイルパスはユーザーごとに異なる。)

Initialized project `weather` at `/Users/[ユーザー名]/.../mcp tutrial/weather`

そのあと

cd weather
uv venv
source .venv/bin/activate

あとは以下で依存関係のインストール。

uv add "mcp[cli]" httpx

してから
weather.py というファイルを作成します。

touch weather.py

weather.py の内容

from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
  • from typing import Any:Any 型をインポートしている。
  • import httpx:requests ライブラリより高性能らしい非同期 HTTP 通信ライブラリ
  • from mcp.server.fastmcp import FastMCP:これが目新しいですね。FastAPI をベースにしたものらしいです。
    -mcp = FastMCP("weather"):FastMCP サーバーインスタンスを作成
    -NWS_API_BASE = "https://api.weather.gov":無料で利用可能な信頼性の高い天気情報ソース(米国海洋大気庁(NWS)の天気APIのベースURL)とのこと
    -USER_AGENT = "weather-app/1.0":これが現時点ではわかりません...
    とりあえず数行で使えそうです

ヘルパー関数

API からデータを問い合わせ、フォーマットするためのヘルパー関数を追加

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')}
"""

なんというか、python なのに型周りがっつりやっててすごいコードだと思いました。Web 系だともうそれが標準レベルなんですかね...
エラーハンドリングを含む安全な API 呼び出しをしてるみたいです。

  • async def make_nws_request(url: str) -> dict[str, Any] | None:非同期関数を作成しています。引数の url を文字列の型でとり、戻り値は文字と Any 型の辞書型か None を返すという指定。
  • header:USEA_AGENT 定数と GeoJSON 形式のデータの要求を辞書型で定義?
  • async with httpx.AsyncClient() as client:非同期で HTTP リクエストをに処理するためのクライアントを client という別名で定義
  • try と except:とりあえず get メソッドを走らせて,30 秒のタイムアウトとステータスコードの返却を要求
  • あとは API で取得したデータを整形して戻り値にしています。
Event: 警報のタイプ(例: 雷雨警報、洪水警報)
Area: 警報が適用される地域
Severity: 警報の深刻度(例: 軽度、中程度、深刻)
Description: 警報の詳細な説明
Instructions: 警報に対する推奨される行動

ツール実行部分

@mcp.tool()
async def get_alerts(state: str) -> str:

    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:

    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)
  • @mcp.tools:おそらく FastMCP 特有のデコレーター。mcp.tools として使用することを指定

  • async def get_alerts(state: str) -> str:非同期関数として state を文字列の引数としてとり、文字列の戻り値を返す

  • url = f"{NWS_API_BASE}/alerts/active/area/{state}":動的に API リクエスト用の url を作成。NWS_API_BASE は別の場所で定義された定数

  • data = await make_nws_request(url):await で make_nws_request 関数の結果が返されるまで待機する非同期処理を書いてる。

  • data がないならエラーメッセージ

  • data["features"]が空ならこれもエラーメッセージ

  • async def get_forecast(latitude: float, longitude: float) -> str:緯度と経度を float 型の引数で受け取って文字列を返しています。
    -  points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}":これで緯度経度を指定して API に url を渡します

  • points_data = await make_nws_request(points_url):関数呼び出しを非同期で

  • forecast_data がなかったらエラーメッセージ

  • periods = forecast_data["properties"]["periods"]:API から帰ってきたデータ取得

  • forecasts = []:空の配列

  • for period in periods[:5]: とりあえず最初の 5 つだけスライスで処理

  • 期間名(例:「Tonight」「Monday」)
    気温と単位(例:「75°F」)
    風の情報(速度と方向)
    詳細な予報文

  • return "\n---\n".join(forecasts)

すべてのフォーマットされた予報を、区切り文字("\n---\n")を使って 1 つの文字列に結合します
あとは main で runserverを走らせる!

if __name__ == "__main__":
    # Initialize and run the server
    mcp.run(transport='stdio')
uv run weather.py

をおすと待機状態になるので一応🙆

Testing your server with Claude for Desktop

使用したいMCPサーバーを設定するには、Claude for Desktopの構成ファイルを編集する必要があります。
mcpServersキーの下にサーバーを追加します。

code ~/Library/Application\ Support/Claude/claude_desktop_config.json
{
    "mcpServers": {
        "weather": {
            "command": "uvのパス",
            "args": [
                "--directory",
                "..../weather",
                "run",
                "weather.py"
            ]
        }
    }
}

weatherというMCPサーバーを設定する
uvのパスに以下のコマンドで出てきたパスを入れる(これを入れないと自分は表示されなかったです)

which uv

-「weather」という名前の MCP サーバーが存在すること

  • コマンドとしてuvを使用
  • このサーバーを起動するには、--directory ..../weather run weather.py などの引数
    (..../weatherはpyファイルがある場所)
    を指定
uv --directory "/Users/[User]/Desktop/renshu/mcp tutrial/weather" run weather.py

これで待機状態なので多分動いてる

Claude Desktop起動

🔨をクリックすると

二つのツールが使用可能になってます!
あとはClaude Desktopで普通に質問するだけ
What are the active weather alerts in Texas?
で以下のような表示が出ます。

一度だけ許可します。
すると...?

APIからのレスポンスをもとに解答してくれました!
ただ
What are the active weather alerts in Tokyo?
だと普通にWebから検索してしまいます。APIが対応していないんですね。

この辺りはちゃんと指示できるような何かを設定する必要があるんだと思います。

Discussion