law-mcp-server 0.1.7 → 0.3.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
@@ -16,13 +16,12 @@ This repository will host an MCP server that uses **法令API Version 2** (e-Gov
16
16
  - `GET /lawsearch/{keyword}` – search laws by keyword.
17
17
  - Response format: JSON (includes meta, LawName, Articles, etc.). Respect official rate limits; treat 429/503 as retryable with backoff.
18
18
 
19
- ## MCP Capabilities (planned tools)
19
+ ## MCP Capabilities
20
20
 
21
- - `fetch_law` – Input: `lawId` (string), optional `revisionDate`. Output: normalized law JSON plus source URL.
22
- - `search_laws` – Input: `keyword` (string), optional `lawType` filter. Output: list of LawID, title, promulgation date, and API URL.
23
- - `list_revisions` – Input: `lawId`. Output: known revision dates/IDs when the API supplies them.
24
- - `check_consistency` – Input: `documentText`, optional `lawIds`, optional `articleHints`, `strictness` (low/medium/high). Output: matched citations, conflicting passages, and a traceable reasoning summary.
25
- - `summarize_law` – Input: `lawId`, optional `articles` list. Output: concise bullet summary suitable for grounding model responses.
21
+ - `search_laws` – Input: `keyword` (string). Output: list of LawID, title, and promulgation date.
22
+ - `fetch_law` – Input: `lawId` (string), optional `revisionDate`. Output: normalized law JSON.
23
+ - `check_consistency` – Input: `documentText`, `lawIds` (required). Output: matched citations, conflicting passages, and similarity scores.
24
+ - `summarize_law` – Input: `lawId`, optional `articles` list. Output: concise article summary with paragraph text.
26
25
 
27
26
  ## Consistency Check Workflow
28
27
 
@@ -62,7 +61,7 @@ This repository will host an MCP server that uses **法令API Version 2** (e-Gov
62
61
  - Build: `npm run build`.
63
62
  - Copy `.env.example` to `.env` and adjust if needed.
64
63
  - Run server over stdio (JSON-RPC): `npm start` (or `npm run dev` for ts-node).
65
- - Configure via environment variables in `.env` (see Configuration section). The server registers tools `fetch_law`, `search_laws`, `list_revisions`, `check_consistency`, and `summarize_law`.
64
+ - Configure via environment variables in `.env` (see Configuration section). The server registers tools `search_laws`, `fetch_law`, `check_consistency`, and `summarize_law`.
66
65
  - Quality: `npm run lint` (ESLint) / `npm run format` (Prettier).
67
66
 
68
67
  ### Claude Desktop configuration
@@ -87,7 +86,54 @@ package, run `npm install && npm run build` and then `npm link` so the
87
86
  ## Usage Examples (conceptual)
88
87
 
89
88
  - Search and fetch: “Search for 個人情報保護 and show the latest articles.” → calls `search_laws` then `fetch_law`.
90
- - Consistency check: Check this draft against 労働基準法 Articles 24 and 37; highlight mismatches.” → calls `check_consistency` with `lawIds=[...]` and article hints.
89
+ - Consistency check: "Check this draft against 労働基準法 Articles 24 and 37; highlight mismatches." → calls `search_laws` to get LawID, then `check_consistency` with `lawIds=[...]`.
90
+
91
+ ## Skills
92
+
93
+ This repository includes domain-specific skills that demonstrate effective usage patterns for law-mcp-server tools. Skills provide comprehensive guides on how to leverage the server's capabilities for specific use cases.
94
+
95
+ ### Available Skills
96
+
97
+ #### Digital Marketing Law Skill (`skills/digital-marketing-law/`)
98
+
99
+ A comprehensive guide for using law-mcp-server to reference and verify compliance with laws related to digital marketing activities in Japan. This skill covers:
100
+
101
+ - **Display Regulations**: Misleading Representation Prevention Act (景品表示法), Specified Commercial Transactions Act (特商法), Consumer Contract Act
102
+ - **Personal Information & Tracking**: Personal Information Protection Act (個人情報保護法), Telecommunications Business Act (電気通信事業法), Specified Electronic Mail Act
103
+ - **Platform Regulations**: Digital Platform Transparency Act, Provider Liability Act
104
+ - **Industry-Specific Laws**: Pharmaceutical Affairs Act (薬機法), Financial Instruments and Exchange Act (金商法)
105
+ - **Intellectual Property**: Copyright Act, Trademark Act, Unfair Competition Prevention Act
106
+ - **Competition Law**: Antimonopoly Act (独占禁止法)
107
+
108
+ **Key Features**:
109
+
110
+ - Search patterns for formal names, abbreviations, and article numbers
111
+ - 5 practical workflows (privacy policy creation, ad review, email marketing, platform transactions, amendment tracking)
112
+ - Real-world use cases for JIAA/APTI activities, client proposals, and compliance checks
113
+ - Common Q&A (Cookie consent, influencer marketing, comparative advertising, AI-generated content, retargeting)
114
+
115
+ **Usage**:
116
+
117
+ 1. Read the skill file: `skills/digital-marketing-law/digital-marketing-law-SKILL.md`
118
+ 2. Reference the appropriate workflow for your task
119
+ 3. Use the provided search keywords and tool sequences
120
+ 4. Follow the best practices for law searches and consistency checks
121
+
122
+ ### Using Skills with Claude
123
+
124
+ To enable Claude to use these skills effectively:
125
+
126
+ 1. **With Claude Desktop**: Skills in this repository are automatically available when the law-mcp-server is configured
127
+ 2. **With Claude API**: Include the skill content in your system prompts or as reference documentation
128
+ 3. **Custom Integration**: Point Claude to the skills directory in your MCP server configuration
129
+
130
+ Skills enhance Claude's ability to:
131
+
132
+ - Choose the right tools for specific legal queries
133
+ - Use appropriate search keywords (formal names vs. abbreviations)
134
+ - Apply domain knowledge for effective law searches
135
+ - Structure multi-step legal compliance checks
136
+ - Provide context-aware recommendations
91
137
 
92
138
  ## Validation Plan (to implement)
93
139
 
package/dist/src/cache.js CHANGED
@@ -1,3 +1,4 @@
1
+ const MAX_ENTRIES = 500;
1
2
  export class MemoryCache {
2
3
  store = new Map();
3
4
  get(key) {
@@ -11,8 +12,24 @@ export class MemoryCache {
11
12
  return entry.value;
12
13
  }
13
14
  set(key, value, ttlSeconds) {
15
+ if (this.store.size >= MAX_ENTRIES) {
16
+ this.evictExpired();
17
+ }
18
+ if (this.store.size >= MAX_ENTRIES) {
19
+ const oldest = this.store.keys().next().value;
20
+ if (oldest !== undefined)
21
+ this.store.delete(oldest);
22
+ }
14
23
  const expiresAt = Date.now() + ttlSeconds * 1000;
15
24
  this.store.set(key, { value, expiresAt });
16
25
  }
26
+ evictExpired() {
27
+ const now = Date.now();
28
+ for (const [key, entry] of this.store) {
29
+ if (now > entry.expiresAt) {
30
+ this.store.delete(key);
31
+ }
32
+ }
33
+ }
17
34
  }
18
35
  export const cache = new MemoryCache();
@@ -1,3 +1,8 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ const pkg = require("../../package.json");
4
+ export const name = pkg.name;
5
+ export const version = pkg.version;
1
6
  const toNumber = (value, fallback) => {
2
7
  const parsed = Number(value);
3
8
  return Number.isFinite(parsed) ? parsed : fallback;
@@ -6,5 +11,5 @@ export const config = {
6
11
  apiBase: process.env.LAW_API_BASE?.trim() || "https://laws.e-gov.go.jp/api/2/",
7
12
  httpTimeoutMs: toNumber(process.env.HTTP_TIMEOUT_MS, 15000),
8
13
  cacheTtlSeconds: toNumber(process.env.CACHE_TTL_SECONDS, 900),
9
- userAgent: "law-mcp-server/0.1.0",
14
+ userAgent: `${pkg.name}/${pkg.version}`,
10
15
  };
package/dist/src/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioJsonRpcServer } from "./mcp.js";
3
3
  import { tools, resolveTool, usageInstructions } from "./tools.js";
4
+ import { name, version } from "./config.js";
4
5
  const server = new StdioJsonRpcServer();
5
- const serverInfo = { name: "law-mcp-server", version: "0.1.6" };
6
+ const serverInfo = { name, version };
6
7
  server.register("initialize", async (params) => {
7
8
  const payload = (params ?? {});
8
9
  const protocolVersion = typeof payload.protocolVersion === "string"
@@ -13,20 +14,17 @@ server.register("initialize", async (params) => {
13
14
  serverInfo,
14
15
  instructions: usageInstructions,
15
16
  capabilities: {
16
- tools: {
17
- list: true,
18
- call: true,
19
- },
17
+ tools: {},
20
18
  },
21
19
  };
22
20
  });
23
- server.register("ping", async () => ({ ok: true }));
21
+ server.register("notifications/initialized", async () => { });
22
+ server.register("ping", async () => ({}));
24
23
  server.register("tools/list", async () => ({
25
24
  tools: tools.map((tool) => ({
26
25
  name: tool.name,
27
26
  description: tool.description,
28
27
  inputSchema: tool.inputSchema,
29
- outputSchema: tool.outputSchema,
30
28
  })),
31
29
  }));
32
30
  server.register("tools/call", async (params) => {
@@ -41,7 +39,16 @@ server.register("tools/call", async (params) => {
41
39
  const args = payload.arguments && typeof payload.arguments === "object"
42
40
  ? payload.arguments
43
41
  : {};
44
- const result = await tool.handler(args);
45
- return { content: result };
42
+ try {
43
+ const result = await tool.handler(args);
44
+ return { content: result };
45
+ }
46
+ catch (error) {
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ return {
49
+ content: [{ type: "text", text: message }],
50
+ isError: true,
51
+ };
52
+ }
46
53
  });
47
54
  server.start();
@@ -10,23 +10,22 @@ class HttpError extends Error {
10
10
  this.body = body;
11
11
  }
12
12
  }
13
- const withTimeout = async (promise, ms) => {
13
+ const request = async (url, context) => {
14
14
  const controller = new AbortController();
15
- const timer = setTimeout(() => controller.abort(), ms);
15
+ const timer = setTimeout(() => controller.abort(), config.httpTimeoutMs);
16
+ let res;
16
17
  try {
17
- return await promise;
18
+ res = await fetch(url, {
19
+ headers: {
20
+ "User-Agent": config.userAgent,
21
+ Accept: "application/json",
22
+ },
23
+ signal: controller.signal,
24
+ });
18
25
  }
19
26
  finally {
20
27
  clearTimeout(timer);
21
28
  }
22
- };
23
- const request = async (url, context) => {
24
- const res = await withTimeout(fetch(url, {
25
- headers: {
26
- "User-Agent": config.userAgent,
27
- Accept: "application/json",
28
- },
29
- }), config.httpTimeoutMs);
30
29
  if (!res.ok) {
31
30
  const body = await res.text();
32
31
  if (res.status === 404 && context?.lawId) {
@@ -53,28 +52,37 @@ export const fetchLawData = async (lawId, revisionDate) => {
53
52
  return data;
54
53
  };
55
54
  export const searchLaws = async (keyword) => {
55
+ const cacheKey = `search:${keyword}`;
56
+ const cached = cache.get(cacheKey);
57
+ if (cached)
58
+ return cached;
56
59
  const base = config.apiBase.endsWith("/")
57
60
  ? config.apiBase
58
61
  : `${config.apiBase}/`;
59
62
  const queryUrl = new URL(`lawsearch`, base);
60
63
  queryUrl.searchParams.set("keyword", keyword);
64
+ const attempts = [];
61
65
  try {
62
- return await request(queryUrl.toString());
66
+ const data = await request(queryUrl.toString());
67
+ cache.set(cacheKey, data, config.cacheTtlSeconds);
68
+ return data;
63
69
  }
64
70
  catch (error) {
65
- const is404 = error instanceof HttpError && error.status === 404;
66
- // Some deployments return 404 for no hits; normalize to empty result.
67
- if (is404) {
68
- const pathUrl = new URL(`lawsearch/${encodeURIComponent(keyword)}`, base);
69
- try {
70
- return await request(pathUrl.toString());
71
- }
72
- catch (err) {
73
- const path404 = err instanceof HttpError && err.status === 404;
74
- if (path404) {
75
- return { numberOfHits: 0, referencelaw: [] };
71
+ if (error instanceof HttpError) {
72
+ attempts.push(`query style (${queryUrl.toString()}): ${error.status} ${error.body}`);
73
+ if (error.status === 404) {
74
+ const pathUrl = new URL(`lawsearch/${encodeURIComponent(keyword)}`, base);
75
+ try {
76
+ const data = await request(pathUrl.toString());
77
+ cache.set(cacheKey, data, config.cacheTtlSeconds);
78
+ return data;
79
+ }
80
+ catch (err) {
81
+ if (err instanceof HttpError) {
82
+ attempts.push(`path style (${pathUrl.toString()}): ${err.status} ${err.body}`);
83
+ }
84
+ throw new Error(`Law search failed for keyword "${keyword}". Attempts: ${attempts.join(" | ")}. Ensure LAW_API_BASE is reachable and keyword is valid.`);
76
85
  }
77
- throw err;
78
86
  }
79
87
  }
80
88
  throw error;
package/dist/src/tools.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { fetchLawData, searchLaws } from "./lawApi.js";
2
2
  import { checkConsistency } from "./consistency.js";
3
- export const usageInstructions = `Usage guidelines:\n\n- To find laws by Japanese name/keyword, call search_laws first. It returns canonical e-Gov LawID values (e.g., 個人情報保護法 -> H15HO57).\n- Always pass the canonical LawID to fetch_law, list_revisions, and check_consistency (lawIds). Do not pass the Japanese title string.\n- fetch_law accepts optional revisionDate when you need a specific revision.\n- check_consistency requires at least one LawID in lawIds. Use search_laws to discover the IDs before calling.\n- summarize_law can take an optional articles array of article numbers to limit the summary.`;
3
+ export const usageInstructions = `Usage guidelines:\n\n- To find laws by Japanese name/keyword, call search_laws first. It returns canonical e-Gov LawID values (e.g., 個人情報保護法 -> H15HO57).\n- Always pass the canonical LawID to fetch_law and check_consistency (lawIds). Do not pass the Japanese title string.\n- fetch_law accepts optional revisionDate when you need a specific revision.\n- check_consistency requires at least one LawID in lawIds. Use search_laws to discover the IDs before calling.\n- summarize_law can take an optional articles array of article numbers to limit the summary.`;
4
4
  const requireString = (value, field) => {
5
5
  if (typeof value !== "string" || value.trim().length === 0) {
6
6
  throw new Error(`Field ${field} is required`);
@@ -28,7 +28,7 @@ const fetchLaw = {
28
28
  const lawId = requireString(input.lawId, "lawId");
29
29
  const revisionDate = typeof input.revisionDate === "string" ? input.revisionDate : undefined;
30
30
  const data = await fetchLawData(lawId, revisionDate);
31
- return [{ type: "json", data }];
31
+ return [{ type: "text", text: JSON.stringify(data, null, 2) }];
32
32
  },
33
33
  };
34
34
  const search = {
@@ -44,7 +44,7 @@ const search = {
44
44
  handler: async (input) => {
45
45
  const keyword = requireString(input.keyword, "keyword");
46
46
  const results = await searchLaws(keyword);
47
- return [{ type: "json", data: results }];
47
+ return [{ type: "text", text: JSON.stringify(results, null, 2) }];
48
48
  },
49
49
  };
50
50
  const summarize = {
@@ -73,32 +73,24 @@ const summarize = {
73
73
  ? articles.filter((article) => article.ArticleNumber &&
74
74
  articlesFilter.includes(article.ArticleNumber))
75
75
  : articles;
76
+ const toArray = (v) => !v ? [] : Array.isArray(v) ? v : [v];
76
77
  const body = filtered
77
- .map((article) => `${article.ArticleNumber || ""} ${article.ArticleTitle || ""}`.trim())
78
78
  .slice(0, 10)
79
- .join("\n");
79
+ .map((article) => {
80
+ const heading = `${article.ArticleNumber || ""} ${article.ArticleTitle || ""}`.trim();
81
+ const paragraphs = toArray(article.Paragraph)
82
+ .map((p) => {
83
+ const sentences = toArray(p.ParagraphSentence);
84
+ return sentences.join("");
85
+ })
86
+ .filter(Boolean)
87
+ .join("\n");
88
+ return [heading, paragraphs].filter(Boolean).join("\n");
89
+ })
90
+ .join("\n\n");
80
91
  return [{ type: "text", text: body || "No articles available" }];
81
92
  },
82
93
  };
83
- const listRevisions = {
84
- name: "list_revisions",
85
- description: "List known revisions for a law (requires canonical LawID from search_laws).",
86
- inputSchema: {
87
- type: "object",
88
- properties: {
89
- lawId: { type: "string" },
90
- },
91
- required: ["lawId"],
92
- },
93
- handler: async (input) => {
94
- const lawId = requireString(input.lawId, "lawId");
95
- const data = await fetchLawData(lawId);
96
- const revisions = Array.isArray(data.revisions)
97
- ? data.revisions
98
- : [];
99
- return [{ type: "json", data: { lawId, revisions } }];
100
- },
101
- };
102
94
  const check = {
103
95
  name: "check_consistency",
104
96
  description: "Check a document against one or more laws (lawIds must be canonical LawIDs from search_laws).",
@@ -107,8 +99,6 @@ const check = {
107
99
  properties: {
108
100
  documentText: { type: "string" },
109
101
  lawIds: { type: "array", items: { type: "string" } },
110
- articleHints: { type: "array", items: { type: "string" } },
111
- strictness: { type: "string", enum: ["low", "medium", "high"] },
112
102
  },
113
103
  required: ["documentText"],
114
104
  },
@@ -122,14 +112,8 @@ const check = {
122
112
  }
123
113
  const laws = await Promise.all(lawIds.map((lawId) => fetchLawData(lawId)));
124
114
  const output = checkConsistency(documentText, laws);
125
- return [{ type: "json", data: output }];
115
+ return [{ type: "text", text: JSON.stringify(output, null, 2) }];
126
116
  },
127
117
  };
128
- export const tools = [
129
- fetchLaw,
130
- search,
131
- listRevisions,
132
- check,
133
- summarize,
134
- ];
118
+ export const tools = [fetchLaw, search, check, summarize];
135
119
  export const resolveTool = (name) => tools.find((tool) => tool.name === name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "law-mcp-server",
3
- "version": "0.1.7",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for e-Gov law API consistency checks",
6
6
  "files": [
@@ -16,14 +16,13 @@
16
16
  "scripts": {
17
17
  "build": "tsc",
18
18
  "prepare": "npm run build",
19
- "start": "node dist/index.js",
19
+ "start": "node dist/src/index.js",
20
20
  "dev": "ts-node src/index.ts",
21
21
  "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
22
22
  "format": "prettier --write .",
23
23
  "test": "npm run build && node dist/test/integration.test.js"
24
24
  },
25
25
  "dependencies": {
26
- "stripe": "^20.3.1",
27
26
  "undici": "^6.13.0"
28
27
  },
29
28
  "devDependencies": {