本文へスキップ

公共データAPIをMCP化する設計パターン:認証キー・識別子・出典・レート制限の共通解

e-Gov法令・国会会議録・EDINET・e-Stat・法人番号の公共APIをMCPにする共通設計。認証キーの環境変数化、識別子の正規化、出典と時点の保持、レート制限とキャッシュ、見つからない時に止まる設計、stdio/HTTPの選択をegov-law-mcpの実装(2026-09-14確認)で例示。

SHAYOUWORLD 更新 約9分

公共データAPIをMCPにするとき、データ源が違っても設計の争点は6つに収束します。 認証キーの置き場、識別子の正規化、出典と取得時点、レート制限とキャッシュ、失敗の形、そしてトランスポートです。この6つを先に決めておけば、法令から統計、法人情報へサーバーを増やしても、エージェント側の扱いが変わりません。

日本の公共データAPI 5選は「どのAPIを選ぶか」の記事でした。この記事は「選んだ後に共通で決めること」を扱います。実例として、筆者が公開している @codeagentjp/egov-law-mcp 0.1.0 のソース(bin/egov-law-mcp.mjs、539行)を2026年9月14日に読み直し、できていることとできていないことを分けて引用します。

  1. キーは環境変数から読み、レスポンス・ログ・ツール入力に出さない。 キーレスAPIでも取得先ドメインの固定とタイムアウトは要る。
  2. 名前検索と本文取得を分け、本文はIDで引く。 法令ID・docID・法人番号・statsDataIdは桁と形式で検証する。
  3. 出典には取得日時とデータの時点を別々に持たせる。 リビジョンID・latestwithdrawalStatusUPDATED_DATE のような時点情報を落とさない。
  4. 上限・待機・分割取得・キャッシュはサーバーの内側に置く。 エージェントが limit を上げられない形にする。
  5. 0件・無効キー・取得失敗・部分取得を別の結果として返す。 HTTP 200のエラーを成功にしない。
  6. キーレス個人利用はstdio、キーを共有するならStreamable HTTPで1箇所に。

5つのAPIを同じ軸で並べる

各公式仕様を2026年9月14日に確認した範囲で、設計に効く項目だけを並べます。

API認証主な識別子時点を表す項目1回の上限形式
e-Gov 法令API v2不要law_id(15桁)、law_revision_idasofamendment_enforcement_datelawslimit 既定100JSON / XML
国会会議録検索API手続不要issueID(21桁)、speechID会議日発言・会議一覧100件、会議単位10件XML / JSON
EDINET API v2Subscription-Key(クエリ)docID(8桁)、edinetCodeJCNsubmitDateTimewithdrawalStatusdocInfoEditStatusファイル日付単位JSON、ZIP / PDF / CSV
e-Stat API 3.0appId(クエリ)statsDataId、分類 codeSURVEY_DATEUPDATED_DATE10万件、NEXT_KEY で継続XML / JSON / CSV
法人番号Web-API 4.0id(13桁、クエリ)corporateNumber(13桁)updateDatechangeDatelatestnum 10件、diff 50日、2,000件で分割CSV / XML

EDINETの書類一覧には提出者の法人番号が JCN として入り、法人番号Web-APIと同じ13桁で結べます。名称の文字列一致ではなく、この共通識別子で結ぶのが横断の前提です。

キーレス(e-Gov・国会会議録)
キー必須(EDINET・e-Stat・法人番号)
秘密の置き場
なし。取得先ドメインの固定だけ
環境変数。inputSchema にキーを含めない
URLのログ
そのまま残してよい
クエリにキーが乗るのでマスクが必須
配布
npx で誰でも起動できる
利用者ごとにキーが要る。共有は HTTP で1箇所に
無効時の見え方
該当なし
EDINET 401、e-Stat は HTTP 200 で STATUS 100、法人番号は 404 HTML
規約上の義務
負荷への配慮
譲渡・貸与・開示の禁止、出典明示の文言
各公式仕様・利用規約と2026-09-14の実行結果より

パターン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-mcpfetchText は、パスが /api/1/ で始まらない、..// を含む場合に例外を投げ、解決後のURLの originhttps://laws.e-gov.go.jp と一致しなければ拒否します。redirect: "error" でリダイレクトも追いません。タイムアウトは環境変数 EGOV_LAW_MCP_TIMEOUT_MS(既定15,000ms)で変えられ、User-Agentegov-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-mcpsearch_laws で候補(lawIdlawNamelawNoscore)を返し、get_law / get_articlelawId または lawNum で引く二段構えです。search_lawsscoreLaw は完全一致100、前方一致90、部分一致80、番号・ID一致70、複数語すべて含む60という単純な採点で、通称辞書は持っていません。法令API v1の一覧に abbrev がないためで、この弱点は法令名が見つからない理由で扱いました。v2の lawslaw_title が略称にもマッチするので、移行すれば辞書の大半は不要になります。

設計としての要点は「名前→IDの変換で自動確定しない」ことです。候補が0件でも複数でも、ツールは候補一覧を返して止まり、IDの確定は人(またはエージェントの明示的な選択)に委ねます。入力側では、IDの桁と文字種を inputSchemapattern で検証し、名前と番号を同じ引数に混ぜません。

パターン3:出典と取得時点を構造で返す

@codeagentjp/egov-law-mcpsourceInfo は、条文取得ごとに次のオブジェクトを返します。

{
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_datarevision_info.law_revision_idamendment_enforcement_date がそのまま時点になります。

他のデータ源にも、それぞれ時点を表す項目があります。

データ源取得日時とは別に持つべき時点
e-Gov 法令law_revision_idamendment_enforcement_date、指定した asof
国会会議録会議の開催日、issueID
EDINETsubmitDateTimewithdrawalStatus(取下)、docInfoEditStatus(修正)
e-StatSURVEY_DATEUPDATED_DATE、使用した絞り込みコード
法人番号lastUpdateDateupdateDatechangeDatelatest

これらを retrievedAt と同じフィールドに潰さないことが、後から「その値は当時正しかったか」を検証できる条件です。出典を outputSchema で必須化する具体的なスキーマはMCPの出典情報を欠落させないoutputSchema設計にあります。

パターン4:レート制限とキャッシュはサーバー側で

公式の要請は控えめな表現ですが、全データ源に共通しています。国会会議録は「多重リクエストを避け、取得後に数秒程度空ける」、e-Statは規約第8条で短時間の大量アクセスを禁止(FAQではアクセス回数の制限は現在なし)、法人番号は規約第9条で同じ禁止と第8条で集中時の制限、EDINETは仕様書に 429 Too Many Requests が定義されています。

100件
国会会議録 発言単位の上限
会議単位は10件。取得後は数秒空ける
10万件
e-Stat 1回の上限
超過は NEXT_KEY を startPosition に
2,000件
法人番号 分割の閾値
diff / name は divide で分割取得
429
EDINET のレート制限応答
十分な時間を空けて再試行、と仕様書
各公式仕様より(2026-09-14確認)

上限と待機はエージェントに任せると守られません。「もっと取って」と言われれば limit を上げるからです。@codeagentjp/egov-law-mcpclampNumberlimit を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つです。

  1. 0件:正常な応答で該当がない。count: 0 を返し、エラーにしない
  2. 無効キー・認証失敗:設定の問題。ツール実行エラーとして返し、再試行させない
  3. 取得失敗:タイムアウト、5xx、パース失敗。エラーとして返す
  4. 部分取得NEXT_KEYdivideSize > 1 が残っている。データは返すが warnings に未取得範囲を書く

@codeagentjp/egov-law-mcpfetchTextresponse.ok でなければ例外を投げ、handleRequest がそれをJSON-RPCの error(code -32000message に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 に書き、logstderr[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・latestwithdrawalStatusUPDATED_DATE を落とさない
  • 上限・待機・キャッシュはエージェントが動かせない場所に置く
  • 0件・無効キー・取得失敗・部分取得を区別し、HTTP 200のエラーを成功にしない

@codeagentjp/egov-law-mcp 0.1.0で満たせているのは、取得先の固定、タイムアウト、stderr分離、retrievedAt 付き出典、limit のclampまでです。時点の保持、TTL付きキャッシュ、待機は次の版で埋める項目として、この記事に残しておきます。

この記事の情報・検証メモ
Tags
公開日
情報確認
参考リンク
9件
更新性
長く使える
更新管理

仕様・料金・提供範囲が変わりやすいテーマは、公開日・更新日・情報確認日を分けて管理します。 導入前には必ず記事末尾の一次情報と公式ドキュメントで最新状況を確認してください。

検証メモ
e-Gov 法令API v2 — curl 実行日 2026-09-14 e-Stat API 3.0 — appIdなしのエラー応答のみ確認 2026-09-14 法人番号Web-API — IDなしの応答のみ確認 2026-09-14
図解を保存・共有

記事の要点を1枚にまとめました。画像は新しいタブで開いて保存できます。

公共データAPIをMCP化する設計パターン:認証キー・識別子・出典・レート制限の共通解 データ源が違っても争点は6つ。キー・識別子・出典と時点・上限とキャッシュ・失敗の形・トランスポートを先に決める キーと識別子:キーは環境変数から読み、レスポンスとログに出さない。名前検索は候補を返して止まり、本文取得はIDで引く。法令ID・docID・法人番号・statsDataIdを桁と形式で検証する。 出典と上限:取得日時とデータの時点を別フィールドで返す。limitはサーバー側でclampし、待機と分割取得も内側で持つ。キャッシュのTTLはデータ源ごとに変える。 失敗の形:0件・無効キー・取得失敗・部分取得を区別して返す。HTTP 200のエラーを成功にしない。キーレス個人利用はstdio、キー共有はHTTPで1箇所に。
公共データAPIをMCP化する設計パターン:認証キー・識別子・出典・レート制限の共通解 記事の要約 2026.09.14 設計・ワークフロー
Primary sources

一次情報・参考リンク

About the author
SHAYOUWORLD

日本の公共データAPIを使うMCPサーバーを作って公開し、ローカルLLMを自分のGPUで測った記録を、一次情報・検証ログ・失敗例とあわせて整理します。