📕

# 🌦️MCPサーバー自作に挑戦_vol2:完結編!🎌 日本の気象データをAIエージェントにつないだらすごいことになった件

に公開

はじめに

こんにちは。これまでMCP(Model Context Protocol)の学習を続け、既存のサーバーへの接続や、公式クイックスタート(米国版Weatherサーバ)の実践を行ってきました。

今回はその集大成として、**「日本の気象庁データをデータソースとした、日本版Weather MCPサーバ」**の自作に挑戦しました。
前回「MCPサーバー自作に挑戦_vol1」は自作MCPサーバの要件定義とデータソースの作成をおこないました。
今回はついにClaude Desktop に気象データをMCPサーバでつなぎます。

結論から言うと、実装作業は**「環境構築の泥沼」でした。
2台のラップトップで環境を再現しようとして、1台のラップトップではPythonの依存関係やJSON設定の些細なミスで何度も挫折しました。
しかし、苦労して繋がった瞬間、AIエージェントによる
「データ活用の神髄」**を見ました。

本記事では、自作データソースの構築から実装時のトラブルシューティング、そして実体験として理解した「企業がMCPを導入すべき理由」についてまとめます。


1. データソースの構築:気象庁データを「AIが読める形」にする

MCPサーバを作る上で最初にして最大の壁は、**「AIに渡すためのクリーンなデータを作る」**ことです。詳細は前回のポスト「MCPサーバー自作に挑戦_vol1」にて。

1.1. データソースの選定

チュートリアルとしての安定性を重視し、外部APIではなくローカルファイルをデータソースとしました。

  • 使用データ: 気象庁「日別平年値(1991~2020年)」
  • 課題: 気象庁のデータ(CSV)は、メタデータが多く含まれる「ワイド形式」であり、そのままではデータベースとして扱いにくい形式でした。

1.2. Pythonによるデータ前処理(Google Colab)

約1300地点、過去30年分のデータを統合するため、Python(Pandas)で以下の処理を行いました。

  1. ロバストな読み込み: cp932エンコーディングのエラーを無視してバイナリ読み込みを行う関数を実装。
  2. ワイドからロングへ変換: 横に長いデータをmelt関数で「日付・地点・要素・値」の縦持ち形式に変換。
  3. 要素のマッピング: 平均気温(0510)や降水量(4000)などの要素コードを、avg_tempなどの分かりやすいカラム名に変換。

最終的に、**約48万行+**のクリーンなCSVファイル(mcp_weather_data_all.csv)を作成することに成功しました。


2.**「日本の気象庁データをデータソースとした、日本版Weather MCPサーバ」**の自作に挑戦_実装編

2.1.構築の参考情報

ModelContextProtocol 公式ドキュメント「MCPサーバを構築する」

を参考に、構築しました。
また以下の情報をLLMに渡して構築の際力を借りました。

  • MCP公式ドキュメントのLLM向けマークダウン形式テキスト:(llms-full.txt)
  • MCPサーバの開発に用いたい言語用のMCP SDK の READMEファイル:この場合はPython
  • MCPサーバ要件定義

2.2.環境を設定する

公式ドキュメントのクイックスタートの流れに沿って実装を進めます。
まず、uvPython プロジェクトと環境をインストールして設定しましょう。

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

コマンドが確実に実行されるように、後でターミナルを再起動してください。
それでは、プロジェクトを作成して設定しましょう。
コチラのコマンドは "nord.js command prompt" で実行しました。
ファイル名もそのまま"weather" にしました。これが後ほど泥沼に嵌るきっかけの一つに…。

# Create a new directory for our project
uv init weather
cd weather

# Create virtual environment and activate it
uv venv
.venv\Scripts\activate

# Install dependencies
uv add mcp[cli] httpx

# Create our server file
new-item weather.py

それでは、サーバーの構築に取り掛かりましょう。

2.3. サーバーの構築

  • パッケージのインポートとインスタンスのセットアップ:
    FastMCP クラスは、Python の型ヒントとドキュメント文字列を使用してツール定義を自動的に生成し、MCP ツールの作成と保守を容易にします。
  • ヘルパー関数:
    データをクエリしてフォーマットするためのヘルパー関数を追加
  • ツール実行の実装:
    ツール実行ハンドラーは、各ツールのロジックを実際に実行する役割を担います。
  • サーバーの実行:
    を一つのファイルにまとめています。
    このコードはLLMに参考資料を渡して丸投げで作成しました。
    人力では、"DATA_SOURCE_FILE"の参照先やエラーの対処で"# ServerSession は削除"などの操作をしたくらいです。
weather.py
import pandas as pd
import sys
from typing import Annotated, Any, AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from pydantic import BaseModel, Field

# MCP SDKのインポート
from mcp.server.fastmcp import Context, FastMCP # ServerSession は削除
# --- 1. 定数とデータモデルの定義 ---

# 統合されたデータソースのファイル名
DATA_SOURCE_FILE = "C:\\Users\\user\\Desktop\\hiroakikody\\作業ファイル\\purogrum練習用\\学習まとめフォルダ\\ZENN\\handsonMCPserver\\weather\\mcp_weather_data_all.csv"
# MCPツールの出力スキーマをPydanticモデルで定義
class PastWeather(BaseModel):
    """MCPツールが返す過去の気候データ構造"""
    location_name: str = Field(description="観測地点名。例: 熊本")
    latitude: float = Field(description="観測地点の緯度(10進数)")
    longitude: float = Field(description="観測地点の経度(10進数)")
    amedas_id: int = Field(description="アメダス地点番号(5桁コード)")
    month: int = Field(description="検索された月")
    day: int = Field(description="検索された日")
    
    # 観測データ (0.1倍補正済み)
    avg_temp: float = Field(description="日平均気温(摂氏℃, 0.1℃単位から補正済み)")
    max_temp: float = Field(description="日最高気温(摂氏℃, 0.1℃単位から補正済み)")
    min_temp: float = Field(description="日最低気温(摂氏℃, 0.1℃単位から補正済み)")
    avg_precipitation: float = Field(description="日平均降水量(mm)")

# --- 2. ライフサイクル管理とデータ読み込み ---

@dataclass
class AppContext:
    """アプリケーション全体で共有されるコンテキスト(データフレームを格納)"""
    weather_data: pd.DataFrame

@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
    """サーバー起動時にデータソースをメモリに読み込む"""
    try:
        print(f"INFO: データソース '{DATA_SOURCE_FILE}' を読み込みます。")
        
        # 巨大なCSVファイルをPandasで読み込み
        # NOTE: データ処理ロジックが正しいため、ここでは読み込むだけ
        df_weather = pd.read_csv(DATA_SOURCE_FILE)
        
        # 検索キーとなるカラムの型を確定 (結合キー)
        df_weather['amedas_id'] = df_weather['amedas_id'].astype(int)
        df_weather['month'] = df_weather['month'].astype(int)
        df_weather['day'] = df_weather['day'].astype(int)
        
        yield AppContext(weather_data=df_weather)
        
    except FileNotFoundError:
        print(f"ERROR: データソースファイルが見つかりません: {DATA_SOURCE_FILE}")
        sys.exit(1)
    except Exception as e:
        print(f"FATAL ERROR: データ読み込み中に予期せぬエラーが発生しました: {e}")
        sys.exit(1)
        
# ライフスパンコンテキストをFastMCPに渡し、サーバーを初期化
mcp = FastMCP("jp-climate", lifespan=app_lifespan)

# --- 3. MCPツールの定義 ---

@mcp.tool()
async def get_jp_past_weather(
    amedas_id: Annotated[int, Field(description="検索するアメダス地点番号 (5桁)")], 
    month: Annotated[int, Field(description="検索する月 (1-12)")], 
    day: Annotated[int, Field(description="検索する日 (1-31)")],
    ctx: Context[Any, AppContext]) -> PastWeather | None:
    """
    指定されたアメダス地点・日付の過去の気候情報(平年値データ)を取得します。
    """
    
    # 1. Lifespan Contextからデータフレームを取得
    df = ctx.request_context.lifespan_context.weather_data
    
    # 2. クエリの実行 (Pandasを使った高速なフィルタリング)
    result = df[
        (df['amedas_id'] == amedas_id) &
        (df['month'] == month) &
        (df['day'] == day)
    ]
    
    if result.empty:
        # データが見つからない場合はNoneを返却
        return None
        
    # 3. 結果の整形と返却 (最初の行を使用)
    data = result.iloc[0].to_dict()
    
    # Pydanticモデルに必要な項目をマッピング
    return PastWeather(
        location_name=data['location_name'],
        latitude=data['latitude'],
        longitude=data['longitude'],
        amedas_id=data['amedas_id'],
        month=data['month'],
        day=data['day'],
        avg_temp=data['avg_temp'],
        max_temp=data['max_temp'],
        min_temp=data['min_temp'],
        avg_precipitation=data['avg_precipitation']
    )

# --- 4. サーバーの実行 ---

def main():
    """エントリーポイント"""
    # stdioトランスポートでMCPサーバーを実行
    mcp.run(transport='stdio')

if __name__ == "__main__":
    main()

2.4.Claude for Desktop でサーバーをテストする

ご利用になるMCPサーバーに合わせてClaude for Desktopを設定する必要があります。設定するには、~/Library/Application Support/Claude/claude_desktop_config.jsonテキストエディタでClaude for Desktopアプリの設定ファイルを開いてください。ファイルが存在しない場合は作成してください。
キーにサーバーを追加しますmcpServers。MCP UI 要素は、少なくとも 1 台のサーバーが適切に構成されている場合にのみ、Claude for Desktop に表示されます。
今回の例では、次のように"filesystem", "weather-jp"サーバーを追加します。
"filesystem"サーバ, "weather-jp"サーバの間の"}"に",:カンマ"を忘れないように注意!

mcpServers_claude_desktop_config.json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:\\Users\\user\\Desktop",
        "C:\\Users\\user\\Downloads"
      ]
    },
    "weather-jp": { 
      "command": "uv",
          "args": [
        "--directory",
        "C:\\Users\\user\\Desktop\\hiroakikody\\作業ファイル\\purogrum練習用\\学習まとめフォルダ\\ZENN\\handsonMCPserver\\weather", 
        "run",
        "weather.py"
      ],
      "env": {
        "PYTHONIOENCODING": "utf-8"
      }
    }
  }
}

以上のコードを任意のファイルに配置して、Claude Desktopを起動するとMCPサーバが読み込まれます。
成功すると、


写真のように"running"や"検索とツール"のリストから自作した"weather-jp"がツールとして表示されます。

3.実装の壁:uv と環境設定の落とし穴

さて、ここからは実装でつまずいたところを備忘録。
データは用意できましたが、それを配信するMCPサーバ(Pythonスクリプト)をClaude Desktopに認識させる工程で、数多のエラーに見舞われました。

3.1.ハマりポイント①:依存関係の欠如

サーバ起動時に以下のエラーが多発しました。

ModuleNotFoundError: No module named 'pandas'

原因と対策:
Claude DesktopはJSONで指定されたuvコマンドを使ってサーバを起動しますが、その環境(仮想環境)にpandasがインストールされていませんでした。
プロジェクトフォルダで uv add pandas mcp[cli] httpx を実行し、依存関係を明示的にインストールすることで解決しました。

3.2.ハマりポイント②:JSON設定と引数の順序

uv runコマンドの引数指定もシビアでした。

error: unrecognized subcommand ...

JSONの args 配列において、--directory オプションと run コマンドの順序が間違っていると、uvがパスをコマンドとして誤認してしまいます。
試行錯誤の末にたどり着いたこの設定は、以下のロジックに基づいた「正解」の構成です。

場所を決める (--directory ...): まず uv にプロジェクトの場所を教え、仮想環境とデータファイルへのパスを通す。

命令する (run): その環境下で実行モードに入る。

実行する (weather.py): 肝心のスクリプトを動かす。

この順序を守ることで、外部ツール(Claude Desktop)からでも、ローカル開発環境と同じ条件でPythonスクリプトを安定して動作させることができます。

成功したJSON設定例:

 "weather-jp": { 
      "command": "uv",
          "args": [
        "--directory",
        "C:\\Users\\user\\Desktop\\hiroakikody\\作業ファイル\\purogrum練習用\\学習まとめフォルダ\\ZENN\\handsonMCPserver\\weather", 
        "run",
        "weather.py"
      ],
      "env": {
        "PYTHONIOENCODING": "utf-8"
      }
    }

3.3. ハマりポイント③:単純なタイポ

お恥ずかしい話ですが、weather_jp.py を whether_jp.py と記述していたことによる program not found エラーでも時間を浪費しました。
"wheather"と"_jp"の間に”スペース”が意図せずまぎれていたのです。
JSON設定は一文字のミスも許されない厳密さが求められるのだという経験になりました。

また、コードブロックではweather.pyですが、
いざMCPサーバ実装のときに、ファイル名を"weather_jp"に変えたところ、
エラーが発生したり、ファイル名が原因かと考えても見分けがつかなくなり苦戦しました。

  • ファイル名が"weather.py"だったりMCPサーバ名が"weather-jp"だったりと自分でつけたファイル名・サーバー名のせいで混乱してしまったり、
  • タイポしてしまったり、
  • ファイル名がよくわからなくなったり、
  • プログラムを実行するときにファイルを保存している場所のパスが違っていたり、etc…、
    プログラミング経験が少ないので、MCPサーバの設定で詰まったというよりは、環境設定やパス、ファイル名の命名などの基礎的な部分で泥沼に引っ掛かりました。
    反省点は多々ありますが、
    MCPサーバの自作と実装成功したことを記憶してまた次の挑戦に取り掛かりたいと思います。

4. 結論:MCPは「AIエージェントのUSB-C」以上の存在だった

苦労の末、サーバが Running 状態になり、Claudeに「weather-jpの使い方を教えてください。」と聞いた瞬間、これまでの苦労が報われました。

4.1.実感した「データ活用の革命」

以前のブログ(参考ポスト:戦略的ツールとなるMCP)で「MCPは企業データの活用に役立つ」と書きましたが、実際に自前のデータを繋いでみて、その意味が腹落ちしました。

単に天気を答えるだけではありません。
以下のテストの結果を見てもらえれば納得してもらえるでしょう!

4.2.自作MCPサーバテスト

4.2.1.気温と降水量の相関グラフ作成

  • 「"weather-jp" を使って「このデータから、気温と降水量の相関グラフを描いて」
    とClaude Desktop にオーダーしてみると、


    というやり取りのあと、次のようにあっという間に可視化されました。


4.2.2.緯度経度からGeoTiff形式でデータを出力

「熊本、大分のアメダス地点のデータと緯度経度からGeoTiff形式でデータを出力してください。」とCloude Desktopにオーダーすると、

直接GeoTiff形式へのファイル変換はできませんでしたが、
csvから抽出したデータをGeoTiff形式へ変換するプログラムを作成してくれました。
変換したファイルをQGIS上で表示すると以下のように。

QGISでのデータ分析提案もしてくれます。

Claude Desktopにデータソースを繋ぐだけで、データの問い合わせ、加工、分析、可視化といったタスクが、驚くほどスムーズに完結しました。
またローカルファイルを分析してもらうだけであればClaude Desktopのクレジットの減りも少ないことも発見でした。以前Neo4jのMCPサーバを実装してテストして(参考ポスト:Neo4j MCPサーバーを動かし、AIエージェントに知識グラフを統合する)みましたが、そのときは2回ほどClaude Desktop とやりとりするとクレジットが枯渇したので、その時の経験と比較すると経済的ですね。

まとめ

MCPサーバは、単なる接続コネクタ(USB-C)ではありません。それは、「眠っている社内データ」を「AIが思考するための燃料」に変えるためのパイプラインです。

環境構築のハードルは高いですが、一度繋がってしまえば、そこにはデータ活用の無限の可能性が広がっています。この「実装の苦しみ」と「活用の喜び」の両方を知れたことが、今回の最大の収穫でした。

参考文献

https://www.shuwasystem.co.jp/book/9784798075730.html
https://modelcontextprotocol.io/docs/develop/build-server#implementing-tool-execution

Discussion