Skip to main content
Glama
README.md
# mcp-lab-jma

気象庁の JSON API をラップする、**ステートレスなリモート MCP サーバー**。
天気予報・気象警報・アメダス実況・エリアコード検索の4ツールを、正規化済みの構造で提供する。

> [!WARNING]
> **これは試作(プロトタイプ)です。**
> - **気象庁の非公式 API に依存**しています。`bosai` 配下の JSON は気象庁サイト自身のための
>   非公式なエンドポイントで、無保証・予告なく構造が変わり、災害時には通常と異なるデータ構造に
>   なる可能性が報告されています。
> - **防災上の判断には使用しないこと。** 本サーバーはデータの正確性・可用性を一切保証しません。
>   避難などの判断は、気象庁の公式サイトや自治体の情報に従ってください。
> - 出典はすべて気象庁です(各レスポンスの `source` フィールドに明記)。

---

## 特徴

- **完全ステートレス** — MCP 2026-07-28 のステートレス core に準拠。`createMcpHandler` を使い、
  セッションハンドシェイクや `Mcp-Session-Id` を持ちません。Durable Object / KV / D1 / R2 を使わず、
  キャッシュは Cache API のみ。
- **シークレット不要** — 気象庁 API はキー不要。認証情報を一切持ちません。
- **入力を外部に出さない** — 地名の解決はビルド時に同梱したエリア索引で行うため、ユーザーが入力した
  地名文字列は気象庁側に送信されません(外部に出るのは6桁コード等のみ)。
  アプリケーションログは出力せず(ユーザー入力を `console` に出しません)、有効化しているのは
  プラットフォーム側の稼働メトリクスのみです。ユーザー入力は POST ボディで扱い URL のクエリ
  パラメータには載せないため、リクエスト URL のログに入力値が残ることもありません。
- **生 JSON を素通ししない** — `timeSeries` の多重ネストやアメダスの `[値, 品質フラグ]` 組を
  サーバー側で正規化し、欠測は `null` で返します。

## ツール

| ツール | 役割 |
|---|---|
| `get_forecast` | 天気予報(`range="3days"` で3日 / `range="week"` で週間) |
| `get_warnings` | 発表中の気象警報・注意報(種別で束ね、市町村を畳む) |
| `get_observation` | アメダス観測所の最新実況(1地点。観測所は名称・かなの前方一致で解決) |
| `list_areas` | 地名からエリアコード候補を検索(`supports` で利用可能ツールを提示) |

- 地域は地名でもコードでも指定できます(例: `"愛知県"` / `"名古屋市"` / `"230000"`)。
  ただし解決先はツールごとに異なり、`get_forecast` / `get_warnings` は予報区・市町村、`get_observation` は
  アメダス観測所のプールから探します。予報用のエリアコードを `get_observation` に渡すと `not_found` です。
  どのコードがどのツールで使えるかは `list_areas` の `supports` で確認できます。
- 曖昧・未発見・上流障害は**エラーにせず**、正常応答として `status` と `retryable` を返します
  (`isError` が立つのは引数スキーマ違反のときだけ)。

## 技術スタック

- Cloudflare Workers + Hono + TypeScript
- `@cloudflare/agents`(`createMcpHandler` ステートレスハンドラ) / `@modelcontextprotocol/server` v2
- zod v4(入出力スキーマ) / vitest(テスト) / pnpm

## セットアップ

```bash
pnpm install
```

### エリア索引の生成(ビルド時のみ)

`src/data/areas.json` は気象庁の `area.json` / `amedastable.json` を縮約した同梱データです。
**実行時には気象庁へ取りに行きません。** 再生成するときのみ実行します(生成物はコミット済み)。

```bash
pnpm build:area-index
```

### 開発サーバー

```bash
pnpm dev
```

公開パスは `/lab/jma-weather` 配下です。MCP エンドポイントは `POST /lab/jma-weather/mcp`
(開発サーバーなら <http://localhost:8787/lab/jma-weather/mcp>)。MCP Inspector 等から4ツールが見え、
呼び出せます。

`GET /lab/jma-weather` はブラウザ向けの仕様説明ページ(接続情報・ツール一覧・異常系・キャッシュ・
取り扱うデータ)を HTML で返します。`Accept` に `application/json` のみを指定したリクエスト(`text/html` を
含まないもの)には従来どおりヘルスチェック用の JSON(`name` / `description` / `mcpEndpoint`)を返すため、
監視用途はそのまま使えます。`Accept: */*`(curl の既定)は HTML になります。

### テスト・型チェック

```bash
pnpm test        # 正規化・解決ロジックの単体テスト(ネットワーク非依存、fixtures 固定)
pnpm typecheck   # Worker コード + ビルドスクリプトの型チェック
```

> テストは `test/fixtures/` に固定した実レスポンスに対して検証し、気象庁 API へネットワークアクセス
> しません。テストランナーはプロジェクトパスの都合で Node 上の vitest を用います(下記メモ参照)。

## 設計上の判断(要点)

- **ツール粒度**: 目的別4ツール + エリア解決をサーバー内に隠蔽。汎用クエリDSLも過度な細粒度も採らない。
- **レスポンスサイズ制御**: 警報は種別で束ね市町村を畳む(最大20種別 / 各10市町村、超過は `truncated`)。
  アメダスは全国一括を取得しても**返すのは1地点**。
- **異常系**: `isError` にせず正常応答 + `status` / `retryable`。クライアントの無限リトライを防ぐため
  `retryable` をブール値で明示。
- **レート制限**: 気象庁の `bosai` API に公表されたレート制限は無く、クライアントに返すべきレート制限
  エラーを定義できません。そのため上流の 429 を前提とした設計は行わず、**Cache API による上流アクセス
  抑制**(予報 600s / 警報 120s / アメダス 60s)を代替手段としています。仮に上流が 429 を返した場合は
  404 以外の非 2xx として `upstream_error` / `retryable: true` に落ちます。
- **トランスポート**: 2026-07-28 のステートレス core に準拠。`tools/list` は `ttlMs` / `cacheScope`
  (24時間・共有可)を持つ。
- 3days と week はマージしない。17時発表で当日の最低気温が落ちるのは正常系として `null` を返す。

### 補足メモ

- キャッシュ TTL(Cache API): 予報 600s / 警報 120s / アメダス 60s。気象庁サイトへの負荷を作らないため。
- 本リポジトリのパスに非 ASCII 文字が含まれるため、`@cloudflare/vitest-pool-workers` の workerd
  モジュール解決が失敗します。テスト対象は Worker API 非依存の純関数(正規化・解決)なので、
  plain vitest(Node 環境)で検証しています。Cache API や `tools/list` の挙動は `pnpm dev` で確認できます。

## v1 でやらないこと

MRTR / `input_required` による確認、フィールド選択、地震・津波・台風、欠測と非観測の区別、
過去データ遡及、認証・レート制限の実装。

## 出典・ライセンス

- データ出典: 気象庁(<https://www.jma.go.jp/>)。政府標準利用規約に基づき出典を明記しています。
- 本プロジェクトは学習・検証目的の試作です。