MCPサーバー作成の公式クイックスタートをやってみた

MCPサーバー作成の公式クイックスタートをやってみた

Clock Icon2025.04.07

お疲れさまです。とーちです。

こちらの記事を読んで、MCPサーバーすごそうとなったのでMCPについてキャッチアップしたくなりました。

https://dev.classmethod.jp/articles/aws-documentation-mcp-integrate-cline/

また、こちらの資料を読んでいて知ったのですが、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 venvsource .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関数が定義されており、この関数は以下のようなことをします

  1. リクエストの準備:
    • headersという以下の要素を含むヘッダー情報を準備しています
      • User-Agent: アプリの名前と版数をユーザーエージェントとして指定
      • Accept: 「地理情報付きのJSONデータが欲しい」と伝える
  2. データの取得:
    • httpxというライブラリを使って、指定されたURLにアクセス(アクセスする際にヘッダーとして上記のheadersに格納した情報を使用)
    • 30秒以内に応答がないとあきらめる(timeout=30.0
  3. データ変換:
    • response.json(): サーバーから返ってきたデータをPythonで使いやすい形(辞書型)に変換

また、クイックスタートのコードの全体像としては、以下の2つのツールが含まれています。この2つのツールがLLMから使用可能となる形です。

気象警報ツール (get_alerts)

  • 入力: 米国の州を表す2文字のコード(例: CA, NY)
  • 処理:
    • 指定された州の現在有効な気象警報をNWS APIから取得
    • 各警報の詳細情報を整形して表示
  • 出力: 警報の種類、影響地域、重大度、説明、指示事項を含む整形されたテキスト

天気予報ツール (get_forecast)

  • 入力: 緯度と経度の座標
  • 処理:
    1. 座標から対応する予報グリッドポイントの情報を取得
    2. グリッドポイント情報から予報URLを取得
    3. 予報URLから詳細な天気予報データを取得
    4. 今後5つの予報期間の情報を整形
  • 出力: 各予報期間の名前、気温、風向風速、詳細な予報内容を含む整形されたテキスト

get_alerts関数の入力となる州を表す2文字のコードは、ユーザーの質問をClaudeが解析して生成し、クライアント経由でMCPサーバーに送信されます。MCPサーバー側はこの2文字のコードを受け取って処理するだけです。

get_forecastも同様で、緯度と経度の情報はユーザーの入力(例えば「サンフランシスコの天気を教えて」)をClaudeが解析し、適切な座標に変換してMCPサーバーに送信します。つまり、ツールとして登録した関数に何を入力すべきかの解釈はMCPサーバー側ではなくClaude側で行われているということですね。

これらを見ると、各ツールはあくまでも情報を提供するだけで、ごく一般的な作りだということがわかります。

デコレータの役割

重要なのが@mcp.tool()の記載です。@というのはPythonでデコレータを表します。デコレータというのはある関数(今回のコードではget_forecastget_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を一度終了させてからもう一度起動させます。

alt text

おっとエラーになりました。

これは、Claude for Desktopの設定ファイルの書き方に問題があり、MCPサーバーを起動出来ていなかったのが原因でした。

{
    "mcpServers": {
        "weather": {
            "command": "/Users/***/.local/bin/uv",
            "args": [
                "--directory",
                "<weather.pyが存在するディレクトリを絶対パスで指定>",
                "run",
                "weather.py"
            ]
        }
    }
}

commandのところで最初はuvとだけ指定していたのですが、これをフルパスで指定する必要がありました。

設定ファイルを修正してから再度Claude for Desktopを起動すると以下のような表示が出ました。

alt text

四角枠で囲った部分が追加されています。

ハンマーのボタンをクリックすると以下の表示が出ます。

alt text

試しに公式の質問に従って質問したところ、以下のような確認が表示されました。ちゃんと許可するかどうかを確認してくれるんですね。

alt text

質問の回答が返ってきました。おお、ちゃんと天気情報を取ってきているようです。

alt text

公式ドキュメントによるとこのとき以下のようなことが行われているとのことです(For Server Developers - Model Context Protocolより)

  1. クライアントはあなたの質問をClaudeに送信します
  2. Claudeは利用可能なツールを分析し、どのツールを使用するかを決定します
  3. クライアントはMCPサーバーを介して選択されたツールを実行します
  4. 結果はClaudeに送り返される
  5. Claudeは自然言語で応答する
  6. 応答が表示されます

このあたりクライアント向けクイックスタートもやればもう少し解像度高く理解出来そうな気がするので、クライアント向けのほうも後日やってみようと思います。

まとめ

今回はModel Context Protocol (MCP)サーバーを実際に作成し、Claude for Desktopと連携させてみました。MCPを使うことで、LLMに外部データソースへのアクセス機能を簡単に追加できることがわかりました。クライアント向けクイックスタートもぜひ試してみたいですね。

以上、とーちでした。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.