building-standards-act-mcp 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,9 +18,71 @@ AI の建築法規回答に含まれるハルシネーションを、条文原
18
18
  - **主要法令プリセット** — 建築基準法・消防法・都市計画法ほか 115 法令の law_id をプリセット登録済み
19
19
  - **プリセット検証** — `validate_presets` ツールでプリセットの有効性をランタイム検証
20
20
  - **略称対応** — 「建基法」「都計法」「消防法」等の略称で指定可能
21
- - **キャッシュ** — 検索 30 分・法令データ 24 時間の TTL キャッシュ
21
+ - **キャッシュ** — 検索 30 分・法令データ 24 時間の TTL キャッシュ(永続化オプションあり)
22
+ - **API 耐障害性** — 自動リトライ(exponential backoff)+ サーキットブレーカー
23
+ - **運用可視化** — 構造化 JSON ログ + 使用量メトリクス(`get_metrics` ツール)
22
24
 
23
- ## セットアップ
25
+ ## Web で使う(Vercel デプロイ)
26
+
27
+ Vercel にデプロイすると、ChatGPT・Claude.ai・Gemini CLI からすぐに使えます。環境変数の設定は不要です(e-Gov API は公開 API)。
28
+
29
+ [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FSora-bluesky%2Fbuilding-standards-act-mcp)
30
+
31
+ ### カスタム GPT でのセットアップ(ChatGPT Plus/Pro/Team/Enterprise)
32
+
33
+ 1. 上の「Deploy with Vercel」ボタンでデプロイ(ワンクリック)
34
+ 2. [chatgpt.com/create](https://chatgpt.com/create) にアクセス
35
+ 3. 「Configure」タブで名前を入力(例: `建築法規アシスタント`)
36
+ 4. Instructions に以下を貼り付け:
37
+ ```
38
+ 建築確認申請の法規に関する質問に回答してください。
39
+ 回答には必ず条文番号を引用し、get_law で原文と照合してください。
40
+ ```
41
+ 5. 「Add Action」→「Import from URL」をクリック
42
+ 6. URL に `https://your-app.vercel.app/api/openapi.json`(your-app をデプロイ先に置換)を入力
43
+ 7. 「Save」→ 公開設定を選択
44
+
45
+ > 参考: [Building and publishing a GPT](https://help.openai.com/en/articles/8798878-building-and-publishing-a-gpt)
46
+
47
+ ### Claude.ai でのセットアップ(Free/Pro/Max/Team/Enterprise)
48
+
49
+ > **注意**: Custom Connectors はベータ版のため、接続が不安定な場合があります。安定してご利用いただくには [npx(ローカル)でのセットアップ](#npx推奨)もご検討ください。
50
+
51
+ 1. 上の「Deploy with Vercel」ボタンでデプロイ(ワンクリック)
52
+ 2. [claude.ai](https://claude.ai) → プロフィールアイコン → Settings → Connectors
53
+ 3. 「Add custom connector」をクリック
54
+ 4. URL に `https://your-app.vercel.app/api/mcp`(your-app をデプロイ先に置換)を入力
55
+ 5. チャットで「+」→「Connectors」→ building-law を有効化
56
+ 6. 「建築基準法第20条を教えて」と質問
57
+
58
+ > 参考: [Get started with custom connectors using remote MCP](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp)
59
+
60
+ ### Gemini CLI でのセットアップ(開発者向け)
61
+
62
+ 1. 上の「Deploy with Vercel」ボタンでデプロイ(ワンクリック)
63
+ 2. `~/.gemini/settings.json` に以下を追加:
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "building-law": {
68
+ "httpUrl": "https://your-app.vercel.app/api/mcp"
69
+ }
70
+ }
71
+ }
72
+ ```
73
+ 3. `gemini` コマンドで「建築基準法第20条を取得して」と質問
74
+
75
+ > 参考: [MCP servers with the Gemini CLI](https://geminicli.com/docs/tools/mcp-server/)
76
+
77
+ ### Vercel Free tier の制限事項
78
+
79
+ - 関数の実行時間が **10 秒** に制限されます(Pro プランでは 60 秒)
80
+ - `get_kokuji`(告示取得)は PDF ダウンロード + パースが重いため、タイムアウトする場合があります
81
+ - `BUILDING_LAW_REQUEST_TIMEOUT=8000` を設定してタイムアウトを短縮できます
82
+
83
+ ---
84
+
85
+ ## ローカルで使う(stdio)
24
86
 
25
87
  ### npx(推奨)
26
88
 
@@ -73,18 +135,19 @@ npm run build
73
135
 
74
136
  ## MCP ツール
75
137
 
76
- | ツール | 説明 |
77
- | ------------------- | -------------------------------------------------------------------------------- |
78
- | `get_law` | 条番号を指定して法令の条文を取得する。略称・正式名称のいずれでも指定可能 |
79
- | `get_full_law` | 法令の全文を取得する。条番号を指定せず法令全体のテキストを返す |
80
- | `search_law` | キーワードで建築関連法令を横断検索する。プリセット法令と e-Gov API の両方を検索 |
81
- | `get_kokuji` | 建築基準法が技術基準を委任している告示の全文を取得する |
82
- | `validate_presets` | プリセット法令の law_id が e-Gov API で有効かを一括検証する |
83
- | `check_law_updates` | 法令の改正状況を e-Gov API で確認する(単体/バッチ/改正履歴表示に対応) |
84
- | `get_laws_batch` | 複数の法令・条文を一括取得する(最大20件、同一法令は1回のAPI呼び出しで効率処理) |
85
- | `verify_citation` | AIの回答に含まれる法令引用を検証する(条文存在確認・テキスト照合、最大10件) |
86
- | `suggest_related` | 指定した条文の関連法令・委任先・同法令内参照を自動抽出して提案する |
87
- | `analyze_article` | 条文の構造解析メタデータ(項数・号数・参照統計・プレビュー)をJSON形式で返す |
138
+ | ツール | 説明 |
139
+ | ------------------- | ------------------------------------------------------------------------------------------- |
140
+ | `get_law` | 条番号を指定して法令の条文を取得する。略称・正式名称のいずれでも指定可能 |
141
+ | `get_full_law` | 法令の全文を取得する。条番号を指定せず法令全体のテキストを返す |
142
+ | `search_law` | キーワードで建築関連法令を横断検索する。プリセット法令と e-Gov API の両方を検索 |
143
+ | `get_kokuji` | 建築基準法が技術基準を委任している告示の全文を取得する |
144
+ | `validate_presets` | プリセット法令の law_id が e-Gov API で有効かを一括検証する |
145
+ | `check_law_updates` | 法令の改正状況を e-Gov API で確認する(単体/バッチ/改正履歴表示に対応) |
146
+ | `get_laws_batch` | 複数の法令・条文を一括取得する(最大20件、同一法令は1回のAPI呼び出しで効率処理) |
147
+ | `verify_citation` | AIの回答に含まれる法令引用を検証する(条文存在確認・テキスト照合、最大10件) |
148
+ | `suggest_related` | 指定した条文の関連法令・委任先・同法令内参照を自動抽出して提案する |
149
+ | `analyze_article` | 条文の構造解析メタデータ(項数・号数・参照統計・プレビュー)をJSON形式で返す |
150
+ | `get_metrics` | サーバーの使用量メトリクスを返す(ツール呼び出し回数・APIリクエスト数・キャッシュヒット率) |
88
151
 
89
152
  ### パラメータ
90
153
 
@@ -154,6 +217,10 @@ npm run build
154
217
  | `law_name` | 法令名(正式名称または略称) | `建築基準法` |
155
218
  | `article_number` | 条文番号 | `第20条` |
156
219
 
220
+ #### `get_metrics`
221
+
222
+ パラメータなし。サーバー起動後の稼働時間・ツール呼び出し回数・API リクエスト数・キャッシュヒット率を返す。
223
+
157
224
  ## 使用例
158
225
 
159
226
  ### 条文取得
@@ -193,12 +260,48 @@ npm run build
193
260
 
194
261
  ## ハルシネーション防止ワークフロー
195
262
 
196
- 本ツールを使って AI の建築法規回答の正確性を担保するワークフローです。
263
+ AI が建築法規について回答する際、条文の引用ミスや存在しない条文の捏造(ハルシネーション)が発生することがあります。本ツールは **4 段階の防止メカニズム** でこれを解決します。
264
+
265
+ ### 基本ワークフロー(自動実行)
197
266
 
198
- 1. Claude が建築法規の質問に仮回答を作成
199
- 2. 引用した条文・告示番号を `get_law` / `get_kokuji` で取得
200
- 3. 実際の原文と仮回答を照合し、誤りがあれば修正
267
+ ```
268
+ ユーザーの質問
269
+ ↓
270
+ 1. AI が仮回答を作成
271
+ ↓
272
+ 2. get_law / get_kokuji で引用した条文・告示の原文を取得
273
+ ↓
274
+ 3. 原文と仮回答を照合 → 誤りがあれば修正
275
+ ↓
201
276
  4. 2〜3 を収束するまで繰り返す(最大 4 ラウンド)
277
+ ↓
278
+ 照合済みの条文番号・告示番号を引用元として明示した最終回答
279
+ ```
280
+
281
+ このワークフローは MCP サーバーの [システムプロンプト](src/server.ts) に組み込まれており、AI が自動的に実行します。
282
+
283
+ ### 追加の検証ツール
284
+
285
+ 基本ワークフローに加えて、以下の専用ツールを使用することでさらに精度を高められます。
286
+
287
+ | ツール | 用途 | 使い方 |
288
+ | ----------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
289
+ | `verify_citation` | AI の回答に含まれる **法令引用を自動検証** する | 回答テキストから条文番号・法令名を自動抽出し、e-Gov API から原文を取得して照合。不一致があれば指摘し、正しい条文を提示する(最大 10 件同時検証) |
290
+ | `suggest_related` | 指定条文の **関連法令を自動提案** する | 条文間の参照(委任先・同法令内参照)を解析し、見落としがちな関連規定を提示。施行令への委任や別の章への参照を漏れなく把握できる |
291
+ | `analyze_article` | 条文の **構造を解析** する | 項数・号数・参照先の統計情報をメタデータとして返す。複雑な条文の全体像を把握し、読み落としを防ぐ |
292
+ | `get_laws_batch` | **複数条文を一括取得** する | 関連する複数の条文をまとめて取得し、条文間の整合性を確認する(最大 20 件) |
293
+
294
+ ### 使用例:引用検証
295
+
296
+ ```
297
+ 「建築基準法第20条で木造建築物の構造計算が必要な条件を教えて」
298
+
299
+ → AI が回答を作成
300
+ → verify_citation で回答内の引用を自動検証
301
+ → 「第20条第1項第四号の引用が正確であることを確認しました」
302
+ または
303
+ → 「第20条第1項第三号の記載に誤りがあります。正しくは…」
304
+ ```
202
305
 
203
306
  ## 対応法令一覧(115 法令)
204
307
 
@@ -398,6 +501,31 @@ e-Gov API は告示を収録していないため、`get_kokuji` は国土交通
398
501
  - `check_law_updates` ツールで法令の改正状況を確認できます(e-Gov API のリビジョン履歴を参照)
399
502
  - 法令改正による `law_id` の無効化リスクは低い(`law_id` は法令番号ベースの固有識別子)
400
503
 
504
+ ## 環境変数
505
+
506
+ | 変数 | 説明 | デフォルト |
507
+ | ------------------------------ | ------------------------------------------------- | ---------------------------- |
508
+ | `BUILDING_LAW_CACHE` | キャッシュモード(`memory` / `file`) | `memory` |
509
+ | `BUILDING_LAW_CACHE_DIR` | ファイルキャッシュの保存先ディレクトリ | `~/.cache/building-law-mcp/` |
510
+ | `BUILDING_LAW_LOG_LEVEL` | ログレベル(`debug` / `info` / `warn` / `error`) | `info` |
511
+ | `BUILDING_LAW_REQUEST_TIMEOUT` | API リクエストタイムアウト(ミリ秒) | `30000` |
512
+
513
+ ### 永続キャッシュの有効化
514
+
515
+ ```json
516
+ {
517
+ "mcpServers": {
518
+ "building-law": {
519
+ "command": "npx",
520
+ "args": ["-y", "building-law-mcp"],
521
+ "env": {
522
+ "BUILDING_LAW_CACHE": "file"
523
+ }
524
+ }
525
+ }
526
+ }
527
+ ```
528
+
401
529
  ## 技術仕様
402
530
 
403
531
  | 項目 | 値 |
@@ -410,7 +538,8 @@ e-Gov API は告示を収録していないため、`get_kokuji` は国土交通
410
538
  | 告示 PDF テキストキャッシュ | 24 時間 |
411
539
  | プリセット検証キャッシュ TTL | 7 日間 |
412
540
  | リビジョン履歴キャッシュ TTL | 1 時間 |
413
- | リクエストタイムアウト | 30 秒 |
541
+ | リクエストタイムアウト | 30 秒(環境変数で変更可能) |
542
+ | Vercel デプロイ | 対応(MCP-over-HTTP + REST API) |
414
543
  | Node.js | >= 20.0.0 |
415
544
 
416
545
  ## データソース
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { createServer } from "./server.js";
4
+ import { logger } from "./lib/logger.js";
4
5
  const server = createServer();
5
6
  async function main() {
6
7
  const transport = new StdioServerTransport();
7
8
  await server.connect(transport);
8
- console.error("building-law-mcp Server running on stdio");
9
+ logger.info("server started", { transport: "stdio" });
9
10
  }
10
11
  main().catch((error) => {
11
12
  console.error("Fatal error:", error);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;AAE9B,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;AAC5D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;AAE9B,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -1,4 +1,18 @@
1
- export declare class TTLCache<T> {
1
+ /**
2
+ * Common cache interface shared by all cache implementations.
3
+ */
4
+ export interface ICache<T> {
5
+ get(key: string): T | undefined;
6
+ set(key: string, value: T, ttl_ms?: number): void;
7
+ has(key: string): boolean;
8
+ delete(key: string): boolean;
9
+ clear(): void;
10
+ readonly size: number;
11
+ }
12
+ /**
13
+ * In-memory TTL cache. Entries expire after a configurable duration.
14
+ */
15
+ export declare class TTLCache<T> implements ICache<T> {
2
16
  private readonly store;
3
17
  private readonly default_ttl_ms;
4
18
  constructor(default_ttl_ms?: number);
@@ -9,3 +23,28 @@ export declare class TTLCache<T> {
9
23
  clear(): void;
10
24
  get size(): number;
11
25
  }
26
+ /**
27
+ * File-based TTL cache. Entries persist across server restarts.
28
+ * Each key is stored as an individual JSON file under
29
+ * `~/.cache/building-law-mcp/{name}/{hash}.json`.
30
+ */
31
+ export declare class FileCache<T> implements ICache<T> {
32
+ private readonly dir;
33
+ private readonly default_ttl_ms;
34
+ constructor(name: string, default_ttl_ms?: number);
35
+ private keyToPath;
36
+ get(key: string): T | undefined;
37
+ set(key: string, value: T, ttl_ms?: number): void;
38
+ has(key: string): boolean;
39
+ delete(key: string): boolean;
40
+ clear(): void;
41
+ get size(): number;
42
+ }
43
+ /**
44
+ * Factory function to create a cache instance based on the
45
+ * `BUILDING_LAW_CACHE` environment variable.
46
+ *
47
+ * - `"memory"` (default): in-memory TTLCache
48
+ * - `"file"`: file-based FileCache that persists across restarts
49
+ */
50
+ export declare function createCache<T>(name: string, ttl_ms: number): ICache<T>;
package/dist/lib/cache.js CHANGED
@@ -1,3 +1,10 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ import * as crypto from "node:crypto";
5
+ /**
6
+ * In-memory TTL cache. Entries expire after a configurable duration.
7
+ */
1
8
  export class TTLCache {
2
9
  store = new Map();
3
10
  default_ttl_ms;
@@ -41,4 +48,124 @@ export class TTLCache {
41
48
  return this.store.size;
42
49
  }
43
50
  }
51
+ const DEFAULT_CACHE_DIR = path.join(os.homedir(), ".cache", "building-law-mcp");
52
+ /**
53
+ * File-based TTL cache. Entries persist across server restarts.
54
+ * Each key is stored as an individual JSON file under
55
+ * `~/.cache/building-law-mcp/{name}/{hash}.json`.
56
+ */
57
+ export class FileCache {
58
+ dir;
59
+ default_ttl_ms;
60
+ constructor(name, default_ttl_ms = 30 * 60 * 1000) {
61
+ this.default_ttl_ms = default_ttl_ms;
62
+ const cacheBase = process.env.BUILDING_LAW_CACHE_DIR ?? DEFAULT_CACHE_DIR;
63
+ this.dir = path.join(cacheBase, name);
64
+ fs.mkdirSync(this.dir, { recursive: true });
65
+ }
66
+ keyToPath(key) {
67
+ const hash = crypto.createHash("md5").update(key).digest("hex");
68
+ return path.join(this.dir, `${hash}.json`);
69
+ }
70
+ get(key) {
71
+ const filePath = this.keyToPath(key);
72
+ try {
73
+ const raw = fs.readFileSync(filePath, "utf-8");
74
+ const entry = JSON.parse(raw);
75
+ if (Date.now() > entry.expires_at) {
76
+ try {
77
+ fs.unlinkSync(filePath);
78
+ }
79
+ catch {
80
+ // ignore cleanup failure
81
+ }
82
+ return undefined;
83
+ }
84
+ return entry.value;
85
+ }
86
+ catch {
87
+ return undefined;
88
+ }
89
+ }
90
+ set(key, value, ttl_ms) {
91
+ const ttl = ttl_ms ?? this.default_ttl_ms;
92
+ const entry = { value, expires_at: Date.now() + ttl };
93
+ fs.writeFileSync(this.keyToPath(key), JSON.stringify(entry), "utf-8");
94
+ }
95
+ has(key) {
96
+ return this.get(key) !== undefined;
97
+ }
98
+ delete(key) {
99
+ try {
100
+ fs.unlinkSync(this.keyToPath(key));
101
+ return true;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ clear() {
108
+ try {
109
+ const files = fs.readdirSync(this.dir);
110
+ for (const file of files) {
111
+ if (file.endsWith(".json")) {
112
+ try {
113
+ fs.unlinkSync(path.join(this.dir, file));
114
+ }
115
+ catch {
116
+ // ignore individual file cleanup failure
117
+ }
118
+ }
119
+ }
120
+ }
121
+ catch {
122
+ // directory might not exist
123
+ }
124
+ }
125
+ get size() {
126
+ try {
127
+ const files = fs.readdirSync(this.dir).filter((f) => f.endsWith(".json"));
128
+ let count = 0;
129
+ for (const file of files) {
130
+ const filePath = path.join(this.dir, file);
131
+ try {
132
+ const raw = fs.readFileSync(filePath, "utf-8");
133
+ const entry = JSON.parse(raw);
134
+ if (Date.now() <= entry.expires_at) {
135
+ count++;
136
+ }
137
+ else {
138
+ try {
139
+ fs.unlinkSync(filePath);
140
+ }
141
+ catch {
142
+ // ignore cleanup failure
143
+ }
144
+ }
145
+ }
146
+ catch {
147
+ // corrupted file, skip
148
+ }
149
+ }
150
+ return count;
151
+ }
152
+ catch {
153
+ return 0;
154
+ }
155
+ }
156
+ }
157
+ /**
158
+ * Factory function to create a cache instance based on the
159
+ * `BUILDING_LAW_CACHE` environment variable.
160
+ *
161
+ * - `"memory"` (default): in-memory TTLCache
162
+ * - `"file"`: file-based FileCache that persists across restarts
163
+ */
164
+ export function createCache(name, ttl_ms) {
165
+ const mode = process.env.BUILDING_LAW_CACHE ?? "memory";
166
+ if (mode === "file") {
167
+ return new FileCache(name, ttl_ms);
168
+ }
169
+ return new TTLCache(ttl_ms);
170
+ }
44
171
  //# sourceMappingURL=cache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/lib/cache.ts"],"names":[],"mappings":"AAKA,MAAM,OAAO,QAAQ;IACF,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IACzC,cAAc,CAAS;IAExC,YAAY,iBAAyB,EAAE,GAAG,EAAE,GAAG,IAAI;QACjD,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAQ,EAAE,MAAe;QACxC,MAAM,GAAG,GAAG,MAAM,IAAI,IAAI,CAAC,cAAc,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK;YACL,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG;SAC7B,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;IACrC,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,IAAI,IAAI;QACN,8BAA8B;QAC9B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC;gBAClC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/lib/cache.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,8 @@
1
1
  import type { EgovLawSearchResponse, EgovLawDataResponse, EgovLawRevisionsResponse } from "./types.js";
2
+ /** Configure retry behavior (for testing only). */
3
+ export declare function _setRetryOptions(opts: Partial<import("./resilience.js").RetryOptions>): void;
4
+ /** Reset circuit breaker state (for testing only). */
5
+ export declare function _resetCircuitBreaker(): void;
2
6
  /**
3
7
  * Search laws by title keyword.
4
8
  * Uses GET /api/2/laws?law_title={keyword}
@@ -1,14 +1,28 @@
1
- import { TTLCache } from "./cache.js";
1
+ import { createCache } from "./cache.js";
2
2
  import { EgovApiError } from "./errors.js";
3
+ import { logger } from "./logger.js";
4
+ import { recordApiCall, recordCacheHit } from "./metrics.js";
5
+ import { withRetry, CircuitBreaker } from "./resilience.js";
3
6
  const BASE_URL = "https://laws.e-gov.go.jp/api/2";
4
7
  const SEARCH_CACHE_TTL = 30 * 60 * 1000; // 30 minutes
5
8
  const LAW_DATA_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
6
9
  const REVISIONS_CACHE_TTL = 60 * 60 * 1000; // 1 hour
7
- const REQUEST_TIMEOUT = 30_000; // 30 seconds
8
- const searchCache = new TTLCache(SEARCH_CACHE_TTL);
9
- const lawDataCache = new TTLCache(LAW_DATA_CACHE_TTL);
10
- const revisionsCache = new TTLCache(REVISIONS_CACHE_TTL);
11
- async function fetchJson(url) {
10
+ const REQUEST_TIMEOUT = Number(process.env.BUILDING_LAW_REQUEST_TIMEOUT) || 30_000; // 30 seconds
11
+ const searchCache = createCache("search", SEARCH_CACHE_TTL);
12
+ const lawDataCache = createCache("law-data", LAW_DATA_CACHE_TTL);
13
+ const revisionsCache = createCache("revisions", REVISIONS_CACHE_TTL);
14
+ const circuitBreaker = new CircuitBreaker();
15
+ // Retry options (configurable for testing)
16
+ let retryOptions = {};
17
+ /** Configure retry behavior (for testing only). */
18
+ export function _setRetryOptions(opts) {
19
+ retryOptions = opts;
20
+ }
21
+ /** Reset circuit breaker state (for testing only). */
22
+ export function _resetCircuitBreaker() {
23
+ circuitBreaker.reset();
24
+ }
25
+ async function fetchJsonRaw(url) {
12
26
  const controller = new AbortController();
13
27
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
14
28
  try {
@@ -36,6 +50,29 @@ async function fetchJson(url) {
36
50
  clearTimeout(timeout);
37
51
  }
38
52
  }
53
+ /**
54
+ * Fetch JSON with retry and circuit breaker.
55
+ */
56
+ async function fetchJson(url) {
57
+ const endpoint = url.replace(BASE_URL, "").split("?")[0];
58
+ const start = Date.now();
59
+ try {
60
+ const result = await circuitBreaker.execute(() => withRetry(() => fetchJsonRaw(url), retryOptions));
61
+ recordApiCall(endpoint, Date.now() - start, 200);
62
+ logger.debug("api call", { endpoint, ms: Date.now() - start });
63
+ return result;
64
+ }
65
+ catch (error) {
66
+ const status = error instanceof EgovApiError ? error.statusCode : undefined;
67
+ recordApiCall(endpoint, Date.now() - start, status);
68
+ logger.warn("api call failed", {
69
+ endpoint,
70
+ status,
71
+ ms: Date.now() - start,
72
+ });
73
+ throw error;
74
+ }
75
+ }
39
76
  /**
40
77
  * Search laws by title keyword.
41
78
  * Uses GET /api/2/laws?law_title={keyword}
@@ -44,8 +81,10 @@ export async function searchLaws(lawTitle) {
44
81
  const cacheKey = `search:${lawTitle}`;
45
82
  const cached = searchCache.get(cacheKey);
46
83
  if (cached) {
84
+ recordCacheHit("search", true);
47
85
  return cached;
48
86
  }
87
+ recordCacheHit("search", false);
49
88
  const url = `${BASE_URL}/laws?law_title=${encodeURIComponent(lawTitle)}`;
50
89
  const result = await fetchJson(url);
51
90
  searchCache.set(cacheKey, result);
@@ -59,8 +98,10 @@ export async function getLawData(lawId) {
59
98
  const cacheKey = `law:${lawId}`;
60
99
  const cached = lawDataCache.get(cacheKey);
61
100
  if (cached) {
101
+ recordCacheHit("law-data", true);
62
102
  return cached;
63
103
  }
104
+ recordCacheHit("law-data", false);
64
105
  const url = `${BASE_URL}/law_data/${encodeURIComponent(lawId)}`;
65
106
  const result = await fetchJson(url);
66
107
  lawDataCache.set(cacheKey, result);
@@ -74,8 +115,10 @@ export async function getLawRevisions(lawId) {
74
115
  const cacheKey = `revisions:${lawId}`;
75
116
  const cached = revisionsCache.get(cacheKey);
76
117
  if (cached) {
118
+ recordCacheHit("revisions", true);
77
119
  return cached;
78
120
  }
121
+ recordCacheHit("revisions", false);
79
122
  const url = `${BASE_URL}/law_revisions/${encodeURIComponent(lawId)}`;
80
123
  const result = await fetchJson(url);
81
124
  revisionsCache.set(cacheKey, result);
@@ -1 +1 @@
1
- {"version":3,"file":"egov-client.js","sourceRoot":"","sources":["../../src/lib/egov-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"egov-client.js","sourceRoot":"","sources":["../../src/lib/egov-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Structured JSON logger for MCP server.
3
+ * Outputs to stderr to avoid interfering with stdio MCP transport.
4
+ */
5
+ export declare const logger: {
6
+ debug: (msg: string, data?: Record<string, unknown>) => void;
7
+ info: (msg: string, data?: Record<string, unknown>) => void;
8
+ warn: (msg: string, data?: Record<string, unknown>) => void;
9
+ error: (msg: string, data?: Record<string, unknown>) => void;
10
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Structured JSON logger for MCP server.
3
+ * Outputs to stderr to avoid interfering with stdio MCP transport.
4
+ */
5
+ const LEVEL_ORDER = {
6
+ debug: 0,
7
+ info: 1,
8
+ warn: 2,
9
+ error: 3,
10
+ };
11
+ function getMinLevel() {
12
+ const env = process.env.BUILDING_LAW_LOG_LEVEL?.toLowerCase();
13
+ if (env && env in LEVEL_ORDER) {
14
+ return env;
15
+ }
16
+ return "info";
17
+ }
18
+ function shouldLog(level) {
19
+ return LEVEL_ORDER[level] >= LEVEL_ORDER[getMinLevel()];
20
+ }
21
+ function emit(level, msg, data) {
22
+ if (!shouldLog(level)) {
23
+ return;
24
+ }
25
+ const entry = {
26
+ ts: new Date().toISOString(),
27
+ level,
28
+ msg,
29
+ };
30
+ if (data !== undefined) {
31
+ entry.data = data;
32
+ }
33
+ process.stderr.write(JSON.stringify(entry) + "\n");
34
+ }
35
+ export const logger = {
36
+ debug: (msg, data) => emit("debug", msg, data),
37
+ info: (msg, data) => emit("info", msg, data),
38
+ warn: (msg, data) => emit("warn", msg, data),
39
+ error: (msg, data) => emit("error", msg, data),
40
+ };
41
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,GAA6B;IAC5C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,SAAS,WAAW;IAClB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,WAAW,EAAE,CAAC;IAC9D,IAAI,GAAG,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,OAAO,GAAe,CAAC;IACzB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,SAAS,CAAC,KAAe;IAChC,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,IAAI,CACX,KAAe,EACf,GAAW,EACX,IAA8B;IAE9B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAA4B;QACrC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,KAAK;QACL,GAAG;KACJ,CAAC;IACF,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CACrD,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CACpD,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IACzB,IAAI,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CACpD,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IACzB,KAAK,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CACrD,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;CAC3B,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * In-memory metrics collector for tool calls, API requests, and cache hits.
3
+ * Resets on server restart (intentional — lightweight, no external deps).
4
+ */
5
+ /** Record a tool call (duration in ms, success/failure). */
6
+ export declare function recordToolCall(toolName: string, durationMs: number, success: boolean): void;
7
+ /** Record an API call (duration in ms, HTTP status code). */
8
+ export declare function recordApiCall(endpoint: string, durationMs: number, statusCode?: number): void;
9
+ /** Record a cache hit or miss. */
10
+ export declare function recordCacheHit(cacheName: string, hit: boolean): void;
11
+ /** Return aggregated metrics snapshot. */
12
+ export declare function getMetrics(): Record<string, unknown>;
13
+ /** Reset all metrics (for testing). */
14
+ export declare function _resetMetrics(): void;