e-Gov法令XMLをMCP向けJSONへ変換する:条・項・号と出典を壊さない設計
e-Gov法令APIのXMLを、AIが引用しやすいMCPレスポンスへ変換する設計を解説。条・項・号の順序、施行時点、改正履歴、原文参照を失わない型と実装手順を示します。
- mcp
- egov
- japanese-law
- xml
- json
- typescript
- legal-tech
- 情報確認
- 参考リンク
- 5件
- 更新性
- 定期更新
- 読了目安
- 約6分
仕様・料金・提供範囲が変わりやすいテーマは、公開日・更新日・情報確認日を分けて管理します。 導入前には必ず記事末尾の一次情報と公式ドキュメントで最新状況を確認してください。
結論:XMLを「本文1本」に潰さず、引用できる小さなJSONへ変換する
e-Gov法令XMLをMCPで扱うときの要点は、条・項・号の階層、取得時点、改正リビジョン、原文URLを同じレスポンスに残すことです。AIに渡す文字数を減らすために本文を平坦化しても、検証に必要な位置情報まで捨ててはいけません。
この記事では、e-Govの法令標準XMLを入力にして、MCPツールが返す安定したJSONを設計します。法令の解釈や個別事案への当てはめは扱いません。最終判断には、対象時点の原文と必要に応じて専門家の確認が必要です。
対象読者
- e-Gov法令APIをTypeScriptから利用したい人
- MCPサーバーに法令検索・条文取得ツールを追加したい人
- AIの回答から「どの法令の何条何項か」を追跡できるようにしたい人
- XMLをJSONへ変換した後の欠落や順序崩れをテストしたい人
まず固定する4つの識別情報
変換コードを書く前に、MCPレスポンスで必ず返す値を決めます。
| 値 | 役割 | 欠けたときの問題 |
|---|---|---|
lawId | 法令を機械的に識別 | 同名・改題・略称に弱くなる |
revisionId | 取得した改正状態を識別 | 後日、同じ内容を再現できない |
asOf | どの時点を求めたかを記録 | 現行条文と過去条文を取り違える |
sourceUrl | 人間が原文へ戻る入口 | AIの引用を照合できない |
法令IDは法令番号と一対一になるよう設計された15文字の識別子です。改題後も名称だけで追わず、IDを軸にする理由がここにあります(e-Gov「法令種別と法令ID」)。
法令API v2の law_data は、法令IDまたは法令番号を指定した場合は該当する版を返し、asof で指定日以前の改正履歴を選べます。リビジョンIDを直接指定した場合は asof が使われないため、呼び出し側で両者を混同しない設計が必要です(法令API v2仕様:law_data)。
法令XMLのどこをJSONへ写すか
法令標準XMLのルート Law は LawNum と LawBody を持ち、LawBody の下に法令名、本文、附則、別表などが並びます(法令標準XMLスキーマの解説)。MCPで条文を答えるだけでも、次の要素は残します。
| XML要素 | JSON側 | 変換上の注意 |
|---|---|---|
LawTitle | lawTitle | 略称ではなく取得本文の題名 |
Article | articles[] | MainProvision、章・節などの階層、附則を区別して列挙する |
Paragraph | paragraphs[] | 表示上番号がない第1項も要素として存在する |
Item | items[] | 号の下にイ・ロ等の下位要素があり得る |
Sentence | sentences[] | 本文・ただし書を Function で区別できる |
SupplProvision | supplementaryProvisions[] | 本則と混ぜず、施行関係の文脈を残す |
法令の表示では番号が省略される1項だけの条でも、XML上は Paragraph が置かれます。また「第二十一条の二」のような枝番号付きの条は第二十一条の子要素ではなく、独立した Article です(e-Gov「法令の構造と法令標準XML」)。DOMの深さだけで条番号を推測せず、要素名と属性を読みます。
MCPレスポンスの型を先に決める
XMLパーサー固有の形をそのまま公開すると、ライブラリ交換でMCPの出力が変わります。公開境界には、自分で管理できる小さなDTOを置きます。
type SourceRef = { provider: "e-Gov法令検索"; lawId: string; revisionId: string | null; asOf: string | null; // YYYY-MM-DD retrievedAt: string; // ISO 8601 sourceUrl: string;};
type SentenceDto = { text: string; function: "main" | "proviso" | null;};
type ItemDto = { kind: "Item" | `Subitem${number}`; number: string | null; title: string | null; sentences: SentenceDto[]; children: ItemDto[]; warnings: string[];};
type ParagraphDto = { number: number | null; caption: string | null; sentences: SentenceDto[]; items: ItemDto[];};
type ArticleDto = { number: string; caption: string | null; title: string | null; paragraphs: ParagraphDto[];};
type LawExcerptDto = { schemaVersion: "law-excerpt/1"; lawTitle: string; lawNumber: string; articles: ArticleDto[]; supplementaryProvisions: ArticleDto[][]; source: SourceRef; warnings: string[];};schemaVersion はe-Gov側の版ではなく、MCPが公開するJSON契約の版です。null と空配列を使い分け、存在しない番号をAIに推測させないことも重要です。
順序を壊さない変換手順
XMLからの変換は、次の順番にすると責務を分けられます。
- APIレスポンスから
law_info、revision_info、法令本文を分離する。 - XMLを名前空間対応のパーサーで解析し、要素順を保持する。
MainProvisionから章・節などの構造要素をたどり、直下の出現順を保ってArticleを列挙する。- 各条の
Paragraph、各項のItem、各号のSubitem1〜Subitem10を子要素順に写す。 Sentenceの文字列とFunction属性を保持する。SupplProvisionは本則と別配列へ変換する。- APIメタデータから
SourceRefを作り、本文DTOへ付加する。 - 要求された条が見つからない場合は空の成功値ではなく、機械可読なエラーを返す。
DOM風のAPIを持つXMLパーサーなら、核になる変換は次の形です。実運用では実行環境に合うXMLパーサーを選び、外部エンティティを解決しない設定にしてください。
const directChildren = (parent: Element, tag: string): Element[] => Array.from(parent.children).filter((node) => node.localName === tag);
const directText = (parent: Element, tag: string): string | null => { const node = directChildren(parent, tag)[0]; const value = node?.textContent?.trim(); return value ? value : null;};
const sentence = (node: Element): SentenceDto => { const raw = node.getAttribute("Function"); return { text: node.textContent?.replace(/\s+/g, " ").trim() ?? "", function: raw === "main" ? "main" : raw === "proviso" ? "proviso" : null, };};
const item = (node: Element): ItemDto => ({ kind: node.localName as ItemDto["kind"], number: node.getAttribute("Num"), title: directText(node, node.localName === "Item" ? "ItemTitle" : `${node.localName}Title`), sentences: directChildren(node, node.localName === "Item" ? "ItemSentence" : `${node.localName}Sentence`) .flatMap((group) => directChildren(group, "Sentence")) .map(sentence), children: Array.from(node.children) .filter((child) => /^Subitem(?:10|[1-9])$/.test(child.localName)) .map(item), warnings: Array.from(node.children) .filter((child) => ["Column", "Table", "List", "TableStruct"].includes(child.localName)) .map((child) => `未対応要素: ${child.localName}`),});
const paragraph = (node: Element): ParagraphDto => { const raw = node.getAttribute("Num"); const parsed = raw === null ? null : Number.parseInt(raw, 10); return { number: Number.isNaN(parsed) ? null : parsed, caption: directText(node, "ParagraphCaption"), sentences: directChildren(node, "ParagraphSentence") .flatMap((group) => directChildren(group, "Sentence")) .map(sentence), items: directChildren(node, "Item").map(item), };};
const article = (node: Element): ArticleDto => ({ number: node.getAttribute("Num") ?? "", caption: directText(node, "ArticleCaption"), title: directText(node, "ArticleTitle"), paragraphs: directChildren(node, "Paragraph").map(paragraph),});
const articleContainers = new Set([ "MainProvision", "Part", "Chapter", "Section", "Subsection", "Division",]);
const collectArticles = (node: Element): ArticleDto[] => Array.from(node.children).flatMap((child) => { if (child.localName === "Article") return [article(child)]; return articleContainers.has(child.localName) ? collectArticles(child) : []; });この抜粋は条・項・号の変換核です。実際の入口では MainProvision の Part、Chapter、Section、Subsection、Division を再帰的にたどって Article を出現順に集め、SupplProvision は supplementaryProvisions へ分離します。querySelectorAll("Article") で全子孫を一括取得すると本則と附則の境界を失うため、どのコンテナから読んだかを必ず保持します。表、様式、別表まで扱う場合は、別DTOとして段階的に追加してください。
APIの形式指定と変換境界
法令API v2には、外側のレスポンス形式を選ぶ response_format と、法令本文形式を選ぶ law_full_text_format があります。組み合わせによって本文がBase64表現になる場合もあるため、形式を暗黙の既定値に任せず固定します(法令API v2仕様)。
また、v2の法令本文JSONや法令ファイルJSONなどには試行版で仕様変更の可能性がある旨が表示されています(法令API v2仕様の注意事項)。そのため、MCPの出力をe-GovのJSONと同型にせず、次の境界を置きます。
e-Gov API ├─ APIメタデータ ──> 出典・時点の検証 └─ 法令標準XML ────> XMLパーサー ──> LawExcerptDto └─ MCP tool result法令ファイル取得エンドポイントはXML、JSON、HTML、RTF、DOCXを選べます(法令API v2:law_file)。XMLを正規入力とするなら、取得層でメディア型とサイズ上限を検証してからパーサーへ渡します。
MCPツールは用途別に小さくする
全文を毎回返す1ツールより、検索と取得を分けた方が、コンテキスト消費と誤引用を抑えられます。
| ツール | 入力 | 返すもの | 返さないもの |
|---|---|---|---|
search_laws | 正式名・法令番号 | 候補のID、題名、番号 | 全文 |
get_law_article | lawId、条番号、asOf | 指定条のDTOと出典 | 他の条 |
get_law_revisions | lawId | 改正履歴と施行日 | 法的評価 |
get_law_source | revisionId | 原文URLと取得情報 | AI要約 |
既存MCPへ新しい取得層を組み込む前に、e-Gov法令MCPの構成と改善方針で責務分割を確認できます。時点指定の挙動は法令API v2のasof実測も参考になります。ただし、どちらの記事も特定パッケージの未統合変更が公開済みであることを意味しません。
エラーを「空の回答」にしない
AIは空配列を見ると、名称違いや通信失敗を内容不存在と解釈しがちです。ツール呼び出しがプロトコル上は完了したものの指定条が見つからない場合は、たとえば次のToolResultを返します。
{ "resultType": "complete", "content": [ { "type": "text", "text": "指定した条番号は取得したリビジョンにありません" } ], "structuredContent": { "errorCode": "ARTICLE_NOT_FOUND", "message": "指定した条番号は取得したリビジョンにありません", "lawId": "000000000000000", "revisionId": "取得できた場合のみ入れる", "requestedArticle": "21_2", "retryable": false }, "isError": true}これはSDKから返す内側のToolResult相当で、MCPのJSON-RPC error オブジェクトではありません。ツール名不明やプロトコル不正などJSON-RPC層の失敗とは分けます。区別したいアプリケーションエラーは、LAW_NOT_FOUND、REVISION_NOT_FOUND、ARTICLE_NOT_FOUND、UPSTREAM_TIMEOUT、INVALID_XML、UNSUPPORTED_SCHEMA です。上流レスポンスの一部をそのままモデルへ見せず、ログには機密情報を除いた相関IDを残します。
回帰テストに入れる法令構造
「有名な第1条が取れた」だけでは変換の正しさを証明できません。次の構造を、固定したXMLフィクスチャでテストします。
- 1項だけで表示番号が省略される条
- 2項以上を持つ条
- 号、イ・ロ、(1)など多段の列挙
- 本文とただし書が同じ項にある条
- 「第二十一条の二」のような枝番号
- 附則内の条
- 表、別表、様式、ルビや画像参照を含む文
- 改正前後で題名または条番号が変わる法令
検証では、XMLへ戻したときの完全一致より、入力要素数、順序、番号、文文字列、出典識別子が期待値と一致することを確かめます。スキーマ自体は公開XSDで機械検証できます(XMLSchemaForJapaneseLaw v3)。
公開前チェックリスト
-
lawId、revisionId、asOf、sourceUrlを出力する - 本則・章節構造・附則を区別し、
Article、Paragraph、Item、Subitemを出現順に読む -
Sentence Functionの本文・ただし書を区別する - 枝番号を数値化して欠落させない
- 附則と本則を同じ配列へ無条件に混ぜない
- 不明な要素を黙って捨てず、警告または未対応エラーにする
- XMLサイズ、タイムアウト、メディア型を取得層で制限する
- AI要約とは別に、原文の引用位置を返す
- 対象時点が重要な回答は人間確認へ送る
よくある質問
e-Gov法令API v2がJSONを返すなら、XML変換は不要ですか?
用途次第です。v2にはJSON形式がありますが、法令本文JSONの一部は試行版として仕様変更の可能性が明記されています。MCPの公開契約を安定させたい場合は、法令標準XMLを入力として自前の小さなDTOへ変換する選択肢があります。
条文を単純なプレーンテキストにしてはいけませんか?
全文検索だけなら使えますが、引用・差分・条項指定では条、項、号、ただし書、附則の境界が必要です。本文テキストと構造化位置情報を併存させると、AIの回答を原文へ戻して検証できます。
この記事のコードは公開中のegov-law-mcpがAPI v2対応済みであることを示しますか?
示しません。この記事は変換境界の設計例です。特定パッケージの実装状況や公開状態は、対象バージョンのソースと配布情報を別途確認してください。
まとめ
e-Gov法令XMLをMCP向けJSONへ変換するときは、本文の短縮より先に検証可能性を設計します。
- 法令ID、改正リビジョン、基準日、原文URLを固定する
- 条・項・号を階層のままDTOへ写し、表示省略や枝番号を推測しない
- e-Govのレスポンス形式とMCPの公開JSON契約を分離する
- 不存在、通信失敗、未対応構造を別エラーとして返す
- 代表的な条だけでなく、附則・枝番・ただし書まで回帰テストする
変換後のJSONは「AIが読みやすい」だけでなく、人間が原文へ戻って確かめられることが完成条件です。接続後の切り分けにはMCPサーバー接続トラブルシューティングも利用できます。
一次情報・参考リンク
- e-Gov 法令API Version 2 API仕様 https://laws.e-gov.go.jp/api/2/redoc/
- 法令標準XMLスキーマ https://laws.e-gov.go.jp/docs/law-data-basic/419a603-xml-schema-for-japanese-law/
- 法令の構造と法令標準XML https://laws.e-gov.go.jp/docs/law-data-basic/8ebd8bc-law-structure-and-xml/
- XMLSchemaForJapaneseLaw v3 https://laws.e-gov.go.jp/file/XMLSchemaForJapaneseLaw_v3.xsd
- 法令APIバージョン2リリースのお知らせ https://laws.e-gov.go.jp/file/%E6%B3%95%E4%BB%A4API%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B32%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%AE%E3%81%8A%E7%9F%A5%E3%82%89%E3%81%9B.pdf 公開
関連して読む
- · 参考リンク 4件
法令をAIで扱うときの安全境界:出典・施行日・改正履歴の確認チェックリスト
法令調査をAIやMCPで補助するとき、何を自動化し、どこで人が確認するかを整理。法令ID、公布日、施行日、基準日、改正履歴、引用位置を残す実務チェックリストです。
- · 参考リンク 5件
その条文は「現在」のものです|e-Gov法令API v2の時点指定(asof)と通称検索を実測
e-Gov法令API v2はasofパラメータで過去時点の条文を返し、法令名検索が通称(abbrev)にもマッチします。v1しか叩いていないegov-law-mcp 0.1.0では何が引けないのかを、下請法の改題を題材に実測ログで確認します。
- · 参考リンク 6件
Claude Codeでe-Gov法令を検索する|egov-law-mcpの設定と使い方
e-Gov法令MCPをClaude Code、Claude Desktop、Cursorへ追加し、法令検索・条文取得・関連法令確認を出典URL付きで行う手順を解説します。