公共データAPIをMCP化する設計パターン:認証キー・識別子・出典・レート制限の共通解
e-Gov法令・国会会議録・EDINET・e-Stat・法人番号の公共APIをMCPにする共通設計。認証キーの環境変数化、識別子の正規化、出典と時点の保持、レート制限とキャッシュ、見つからない時に止まる設計、stdio/HTTPの選択をegov-law-mcpの実装(2026-09-14確認)で例示。
公共データAPIをMCPにするとき、データ源が違っても設計の争点は6つに収束します。 認証キーの置き場、識別子の正規化、出典と取得時点、レート制限とキャッシュ、失敗の形、そしてトランスポートです。この6つを先に決めておけば、法令から統計、法人情報へサーバーを増やしても、エージェント側の扱いが変わりません。
日本の公共データAPI 5選は「どのAPIを選ぶか」の記事でした。この記事は「選んだ後に共通で決めること」を扱います。実例として、筆者が公開している @codeagentjp/egov-law-mcp 0.1.0 のソース(bin/egov-law-mcp.mjs、539行)を2026年9月14日に読み直し、できていることとできていないことを分けて引用します。
- キーは環境変数から読み、レスポンス・ログ・ツール入力に出さない。 キーレスAPIでも取得先ドメインの固定とタイムアウトは要る。
- 名前検索と本文取得を分け、本文はIDで引く。 法令ID・docID・法人番号・statsDataIdは桁と形式で検証する。
- 出典には取得日時とデータの時点を別々に持たせる。 リビジョンID・
latest・withdrawalStatus・UPDATED_DATEのような時点情報を落とさない。 - 上限・待機・分割取得・キャッシュはサーバーの内側に置く。 エージェントが
limitを上げられない形にする。 - 0件・無効キー・取得失敗・部分取得を別の結果として返す。 HTTP 200のエラーを成功にしない。
- キーレス個人利用はstdio、キーを共有するならStreamable HTTPで1箇所に。
5つのAPIを同じ軸で並べる
各公式仕様を2026年9月14日に確認した範囲で、設計に効く項目だけを並べます。
| API | 認証 | 主な識別子 | 時点を表す項目 | 1回の上限 | 形式 |
|---|---|---|---|---|---|
| e-Gov 法令API v2 | 不要 | law_id(15桁)、law_revision_id | asof、amendment_enforcement_date | laws の limit 既定100 | JSON / XML |
| 国会会議録検索API | 手続不要 | issueID(21桁)、speechID | 会議日 | 発言・会議一覧100件、会議単位10件 | XML / JSON |
| EDINET API v2 | Subscription-Key(クエリ) | docID(8桁)、edinetCode、JCN | submitDateTime、withdrawalStatus、docInfoEditStatus | ファイル日付単位 | JSON、ZIP / PDF / CSV |
| e-Stat API 3.0 | appId(クエリ) | statsDataId、分類 code | SURVEY_DATE、UPDATED_DATE | 10万件、NEXT_KEY で継続 | XML / JSON / CSV |
| 法人番号Web-API 4.0 | id(13桁、クエリ) | corporateNumber(13桁) | updateDate、changeDate、latest | num 10件、diff 50日、2,000件で分割 | CSV / XML |
EDINETの書類一覧には提出者の法人番号が JCN として入り、法人番号Web-APIと同じ13桁で結べます。名称の文字列一致ではなく、この共通識別子で結ぶのが横断の前提です。
パターン1:認証キーは環境変数、レスポンスとログには出さない
EDINETは書類一覧APIのリクエストURLに &Subscription-Key=APIキー を付ける仕様です。e-Statも appId=、法人番号も id= がクエリに乗ります。つまりリクエストURLを丸ごとログに残す実装は、キーをログに残す実装です。
規約側の要求も明確です。e-Stat利用規約第3条はIDを第三者に譲渡・貸与しないこと、法人番号Web-API利用規約第9条はIDの譲渡・貸与・開示を禁止しています。MCPのツール結果にキー入りのURLを返すと、エージェントの会話ログ経由で「開示」が起きます。
実装で守る形は3つです。
// 起動時に読む。無ければ落とす(黙って動かない)const apiKey = process.env.EDINET_API_KEY;if (!apiKey) { throw new Error("EDINET_API_KEY is not set");}
// 出典に返す URL はキーを除いたものにするconst sourceUrl = new URL(requestUrl);sourceUrl.searchParams.delete("Subscription-Key");- ツールの
inputSchemaにキーを入れない。エージェントに渡させない - 出典として返す
apiUrlからキー付きクエリを除く - stderrのログにもURL全体を書かない
キーレスでも保護はゼロではありません。@codeagentjp/egov-law-mcp の fetchText は、パスが /api/1/ で始まらない、.. や // を含む場合に例外を投げ、解決後のURLの origin が https://laws.e-gov.go.jp と一致しなければ拒否します。redirect: "error" でリダイレクトも追いません。タイムアウトは環境変数 EGOV_LAW_MCP_TIMEOUT_MS(既定15,000ms)で変えられ、User-Agent に egov-law-mcp/0.1.0 (https://codeagent.jp/) を名乗ります。キーがない代わりに「どこにしか行かないか」を固定するのが、キーレスAPIの認証設計です。
パターン2:識別子を正規化し、名前検索と本文取得を分ける
各データ源の安定識別子は桁と形式が決まっています。法令IDは15桁(331AC0000000120)、法人番号は13桁の数字、docIDは8桁(S1000001 の形)、issueIDは21桁の英数字です。
法令IDは改題でも変わりません。旧・下請代金支払遅延等防止法は2026年1月1日に改題されましたが、laws?law_title=下請代金 を今日引いても 331AC0000000120 は同じで、abbrev が 取適法,中小受託取引適正化法 に更新されているだけでした。名前は変わる、IDは変わらないので、本文取得はIDで引きます。
@codeagentjp/egov-law-mcp は search_laws で候補(lawId、lawName、lawNo、score)を返し、get_law / get_article は lawId または lawNum で引く二段構えです。search_laws の scoreLaw は完全一致100、前方一致90、部分一致80、番号・ID一致70、複数語すべて含む60という単純な採点で、通称辞書は持っていません。法令API v1の一覧に abbrev がないためで、この弱点は法令名が見つからない理由で扱いました。v2の laws は law_title が略称にもマッチするので、移行すれば辞書の大半は不要になります。
設計としての要点は「名前→IDの変換で自動確定しない」ことです。候補が0件でも複数でも、ツールは候補一覧を返して止まり、IDの確定は人(またはエージェントの明示的な選択)に委ねます。入力側では、IDの桁と文字種を inputSchema の pattern で検証し、名前と番号を同じ引数に混ぜません。
パターン3:出典と取得時点を構造で返す
@codeagentjp/egov-law-mcp の sourceInfo は、条文取得ごとに次のオブジェクトを返します。
{ name: "e-Gov法令検索", url: "https://laws.e-gov.go.jp/law/<lawId>", apiUrl: "https://laws.e-gov.go.jp/api/1/...", attribution: "出典: e-Gov法令検索(https://laws.e-gov.go.jp/)", retrievedAt: "<ISO 8601>"}retrievedAt(取得日時)はあります。足りないのはデータ自体の時点です。v1を使っている以上リビジョンIDがなく、「いつ施行の条文か」を返せません。v2なら law_data の revision_info.law_revision_id と amendment_enforcement_date がそのまま時点になります。
他のデータ源にも、それぞれ時点を表す項目があります。
| データ源 | 取得日時とは別に持つべき時点 |
|---|---|
| e-Gov 法令 | law_revision_id、amendment_enforcement_date、指定した asof |
| 国会会議録 | 会議の開催日、issueID |
| EDINET | submitDateTime、withdrawalStatus(取下)、docInfoEditStatus(修正) |
| e-Stat | SURVEY_DATE、UPDATED_DATE、使用した絞り込みコード |
| 法人番号 | lastUpdateDate、updateDate、changeDate、latest |
これらを retrievedAt と同じフィールドに潰さないことが、後から「その値は当時正しかったか」を検証できる条件です。出典を outputSchema で必須化する具体的なスキーマはMCPの出典情報を欠落させないoutputSchema設計にあります。
パターン4:レート制限とキャッシュはサーバー側で
公式の要請は控えめな表現ですが、全データ源に共通しています。国会会議録は「多重リクエストを避け、取得後に数秒程度空ける」、e-Statは規約第8条で短時間の大量アクセスを禁止(FAQではアクセス回数の制限は現在なし)、法人番号は規約第9条で同じ禁止と第8条で集中時の制限、EDINETは仕様書に 429 Too Many Requests が定義されています。
上限と待機はエージェントに任せると守られません。「もっと取って」と言われれば limit を上げるからです。@codeagentjp/egov-law-mcp は clampNumber で limit を1〜50、previewChars を500〜20,000に丸めており、スキーマの maximum を超えた値を渡されても内側で切ります。この「エージェントが上限を動かせない」形が基本です。
キャッシュも同じサーバーの中に置きますが、TTLはデータ源ごとに変えます。@codeagentjp/egov-law-mcp は法令一覧(v1の lawlists)をプロセス内の lawListCache に持ち、cacheFetchedAt を結果に返します。ただしTTLがなく、プロセスが生きている限り更新されません。 法令一覧は改題や新規制定で変わるので、日次程度の失効が要ります。法人番号の diff は日次で確定、EDINETの書類一覧はその日のうちに増える、e-Statのメタ情報は長めに持てる、というように、失効の粒度をデータの更新単位に合わせます。
不足している点も書いておきます。現行の実装には直列化・待機・バックオフがありません。連続呼び出しがそのままe-Govへ流れる構造で、複数エージェントから同時に使う用途には足りません。
パターン5:エラーを埋めない
今回の実行で、失敗の見え方がデータ源ごとにばらばらだと確認できました。
e-Gov 法令API v2 存在しない法令ID -> HTTP 404 {"code":"404004","message":"指定のパラメータで取得できる法令本文ファイルは存在しません。"} 該当なしの検索 -> HTTP 200 {"total_count":0,"count":0,"laws":[]}
e-Stat API 3.0 appId なし / 無効 -> HTTP 200 {"RESULT":{"STATUS":100,"ERROR_MSG":"認証に失敗しました。..."}}
法人番号Web-API 4.0 ID なし / 無効 -> HTTP 404 の HTML(Not Found)e-Statは認証失敗でもHTTP 200です。HTTPステータスだけを見る実装は、キーが無効でも「成功したが0件」と誤読します。逆に法人番号は404のHTMLで、本文をJSONとしてパースすると別の例外になります。
MCPの結果として区別すべき状態は4つです。
- 0件:正常な応答で該当がない。
count: 0を返し、エラーにしない - 無効キー・認証失敗:設定の問題。ツール実行エラーとして返し、再試行させない
- 取得失敗:タイムアウト、5xx、パース失敗。エラーとして返す
- 部分取得:
NEXT_KEYやdivideSize > 1が残っている。データは返すがwarningsに未取得範囲を書く
@codeagentjp/egov-law-mcp は fetchText で response.ok でなければ例外を投げ、handleRequest がそれをJSON-RPCの error(code -32000、message にHTTPステータス)にします。一方 search_laws の0件は count: 0 の正常結果です。1と3をエラー、0件を正常として分けている点は満たしています。4の部分取得は、v1に継続キーがないため実装されていません。
パターン6:stdioかStreamable HTTPか
@codeagentjp/egov-law-mcp はstdioサーバーで、npx -y @codeagentjp/egov-law-mcp で起動します。MCP 2026-07-28のstdio仕様は、サーバーが stdout に有効なMCPメッセージ以外を書いてはならず(MUST NOT)、ログは stderr に書いてよい(MAY)と定めています。実装では writeJson だけが stdout に書き、log は stderr に [egov-law-mcp] ... を出す形で分離しています。
トランスポートの選択は、キーの有無でほぼ決まります。
- キーレスで個人が使う:stdio。配布は
npx、秘密なし、ネットワーク面なし - キーが要るが個人利用:stdio。キーは利用者自身の環境変数に置く
- キーが要り、複数人で共有:Streamable HTTP。キーはサーバーに1つだけ置き、利用者ごとの認可はHTTP側で設計する
キーを利用者全員の端末に配るくらいなら、HTTPで1箇所に閉じ込める方が規約(譲渡・貸与の禁止)にも沿います。判断基準の詳細はMCPのstdioとStreamable HTTPの選び方にまとめています。
実装前チェックリスト
- キーは環境変数から読み、無ければ起動時に落ちる
-
inputSchema、ツール結果、stderrのどこにもキーが出ない - 取得先のoriginを固定し、リダイレクトを追わない
- 名前検索は候補を返して止まる。本文取得はIDのみ
- IDの桁と文字種を
patternで検証している - 出典に
retrievedAtと、データ源固有の時点項目を別々に持つ -
limitと本文サイズをサーバー側でclampしている - 待機・直列化・バックオフがサーバー側にある
- キャッシュのTTLをデータ源の更新単位に合わせた
- 0件・無効キー・取得失敗・部分取得を別の結果として返す
- HTTP 200の本文エラーを検知している
- stdioなら
stdoutにMCPメッセージ以外を書いていない
まとめ
- 公共データMCPの争点は、キー・識別子・出典と時点・上限とキャッシュ・失敗の形・トランスポートの6つ
- キーはクエリに乗る仕様が多い。環境変数から読み、URLごとログや出典に出さない。キーレスでも取得先の固定は要る
- 名前は変わりIDは変わらない。名前検索は候補で止め、本文はIDで引く
- 取得日時とデータの時点を別に持つ。リビジョンID・
latest・withdrawalStatus・UPDATED_DATEを落とさない - 上限・待機・キャッシュはエージェントが動かせない場所に置く
- 0件・無効キー・取得失敗・部分取得を区別し、HTTP 200のエラーを成功にしない
@codeagentjp/egov-law-mcp 0.1.0で満たせているのは、取得先の固定、タイムアウト、stderr分離、retrievedAt 付き出典、limit のclampまでです。時点の保持、TTL付きキャッシュ、待機は次の版で埋める項目として、この記事に残しておきます。
この記事の情報・検証メモ
- 公開日
- 情報確認
- 参考リンク
- 9件
- 更新性
- 長く使える
仕様・料金・提供範囲が変わりやすいテーマは、公開日・更新日・情報確認日を分けて管理します。 導入前には必ず記事末尾の一次情報と公式ドキュメントで最新状況を確認してください。
一次情報・参考リンク
- GitHub: SHAYOUWORLD/egov-law-mcp(bin/egov-law-mcp.mjs 0.1.0) https://github.com/SHAYOUWORLD/egov-law-mcp
- e-Gov 法令API Version 2 OpenAPI定義(2.1.139) https://laws.e-gov.go.jp/api/2/swagger-ui/lawapi-v2.yaml
- 国会会議録検索システム 検索用API https://kokkai.ndl.go.jp/api.html
- EDINET API仕様書 Version 2 https://disclosure2dl.edinet-fsa.go.jp/guide/static/disclosure/download/ESE140206.pdf
- e-Stat API仕様 3.0 https://www.e-stat.go.jp/api/api-info/e-stat-manual3-0
- e-Stat API機能 利用規約 https://www.e-stat.go.jp/api/terms-of-use
- 法人番号システムWeb-API 概要編 https://www.houjin-bangou.nta.go.jp/pc/webapi/images/k-web-api-kinou-gaiyo.pdf
- 法人番号システムWeb-API機能利用規約 https://www.houjin-bangou.nta.go.jp/webapi/riyokiyaku.html
- MCP 2026-07-28: stdio transport https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio