@cyanheads/federal-regulations-mcp-server 0.2.4 → 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.
Files changed (71) hide show
  1. package/AGENTS.md +21 -14
  2. package/CLAUDE.md +21 -14
  3. package/README.md +100 -131
  4. package/changelog/0.2.x/0.2.5.md +23 -0
  5. package/changelog/0.3.x/0.3.0.md +27 -0
  6. package/changelog/template.md +9 -26
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +56 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp-server/resources/definitions/cfr-section.resource.d.ts +1 -0
  12. package/dist/mcp-server/resources/definitions/cfr-section.resource.d.ts.map +1 -1
  13. package/dist/mcp-server/resources/definitions/cfr-section.resource.js +1 -0
  14. package/dist/mcp-server/resources/definitions/cfr-section.resource.js.map +1 -1
  15. package/dist/mcp-server/resources/definitions/document.resource.js +1 -1
  16. package/dist/mcp-server/resources/definitions/document.resource.js.map +1 -1
  17. package/dist/mcp-server/resources/definitions/index.d.ts +1 -0
  18. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -1
  19. package/dist/mcp-server/tools/definitions/browse-cfr.tool.d.ts +3 -0
  20. package/dist/mcp-server/tools/definitions/browse-cfr.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/browse-cfr.tool.js +3 -0
  22. package/dist/mcp-server/tools/definitions/browse-cfr.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/definitions/date-input.d.ts +14 -0
  24. package/dist/mcp-server/tools/definitions/date-input.d.ts.map +1 -0
  25. package/dist/mcp-server/tools/definitions/date-input.js +27 -0
  26. package/dist/mcp-server/tools/definitions/date-input.js.map +1 -0
  27. package/dist/mcp-server/tools/definitions/find-comments.tool.d.ts +2 -0
  28. package/dist/mcp-server/tools/definitions/find-comments.tool.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/definitions/find-comments.tool.js +2 -0
  30. package/dist/mcp-server/tools/definitions/find-comments.tool.js.map +1 -1
  31. package/dist/mcp-server/tools/definitions/format-utils.d.ts +20 -1
  32. package/dist/mcp-server/tools/definitions/format-utils.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/definitions/format-utils.js +22 -2
  34. package/dist/mcp-server/tools/definitions/format-utils.js.map +1 -1
  35. package/dist/mcp-server/tools/definitions/get-cfr-section.tool.d.ts +1 -0
  36. package/dist/mcp-server/tools/definitions/get-cfr-section.tool.d.ts.map +1 -1
  37. package/dist/mcp-server/tools/definitions/get-cfr-section.tool.js +1 -0
  38. package/dist/mcp-server/tools/definitions/get-cfr-section.tool.js.map +1 -1
  39. package/dist/mcp-server/tools/definitions/get-docket.tool.d.ts +3 -0
  40. package/dist/mcp-server/tools/definitions/get-docket.tool.d.ts.map +1 -1
  41. package/dist/mcp-server/tools/definitions/get-docket.tool.js +3 -0
  42. package/dist/mcp-server/tools/definitions/get-docket.tool.js.map +1 -1
  43. package/dist/mcp-server/tools/definitions/get-document.tool.d.ts +23 -4
  44. package/dist/mcp-server/tools/definitions/get-document.tool.d.ts.map +1 -1
  45. package/dist/mcp-server/tools/definitions/get-document.tool.js +88 -11
  46. package/dist/mcp-server/tools/definitions/get-document.tool.js.map +1 -1
  47. package/dist/mcp-server/tools/definitions/index.d.ts +142 -83
  48. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  49. package/dist/mcp-server/tools/definitions/list-open-comments.tool.d.ts +26 -6
  50. package/dist/mcp-server/tools/definitions/list-open-comments.tool.d.ts.map +1 -1
  51. package/dist/mcp-server/tools/definitions/list-open-comments.tool.js +107 -56
  52. package/dist/mcp-server/tools/definitions/list-open-comments.tool.js.map +1 -1
  53. package/dist/mcp-server/tools/definitions/search-rules.tool.d.ts +18 -3
  54. package/dist/mcp-server/tools/definitions/search-rules.tool.d.ts.map +1 -1
  55. package/dist/mcp-server/tools/definitions/search-rules.tool.js +38 -26
  56. package/dist/mcp-server/tools/definitions/search-rules.tool.js.map +1 -1
  57. package/dist/services/ecfr/ecfr-service.js +2 -2
  58. package/dist/services/ecfr-mirror/ecfr-mirror.d.ts +10 -6
  59. package/dist/services/ecfr-mirror/ecfr-mirror.d.ts.map +1 -1
  60. package/dist/services/ecfr-mirror/ecfr-mirror.js +17 -12
  61. package/dist/services/ecfr-mirror/ecfr-mirror.js.map +1 -1
  62. package/dist/services/federal-register/federal-register-service.d.ts +16 -6
  63. package/dist/services/federal-register/federal-register-service.d.ts.map +1 -1
  64. package/dist/services/federal-register/federal-register-service.js +233 -35
  65. package/dist/services/federal-register/federal-register-service.js.map +1 -1
  66. package/dist/services/federal-register/types.d.ts +38 -8
  67. package/dist/services/federal-register/types.d.ts.map +1 -1
  68. package/dist/services/regulations-gov/regulations-gov-service.js +2 -2
  69. package/dist/services/upstream-failure.d.ts +1 -1
  70. package/package.json +19 -9
  71. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** federal-regulations-mcp-server
4
- **Version:** 0.2.4
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.12.3`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 0.3.0
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.13.6`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
8
- **Zod:** ^4.4.3
8
+ **Zod:** ^4.6.5
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -165,7 +165,7 @@ Handlers receive a unified `ctx` object. Key properties:
165
165
 
166
166
  Handlers throw — the framework catches, classifies, and formats.
167
167
 
168
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
168
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text unless the message already contains it verbatim); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Forwarding it is lint-enforced per throw site (`error-contract-recovery-unforwarded`). Mark an entry the service layer throws with `thrownBy: 'service'` so `error-contract-unthrown` skips it — lint-only metadata, nothing at runtime reads it. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring.
169
169
 
170
170
  ```ts
171
171
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
@@ -247,9 +247,9 @@ No prompts in v1 — `createApp({ prompts: [] })`.
247
247
 
248
248
  ## Skills
249
249
 
250
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
250
+ Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
251
251
 
252
- **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
252
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
253
253
 
254
254
  Available skills:
255
255
 
@@ -268,8 +268,9 @@ Available skills:
268
268
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
269
269
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
270
270
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
271
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
272
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
271
+ | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
272
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
273
+ | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
273
274
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
274
275
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
275
276
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
@@ -288,7 +289,7 @@ Available skills:
288
289
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
289
290
  | `api-workers` | Cloudflare Workers runtime |
290
291
 
291
- **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
292
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
292
293
 
293
294
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
294
295
 
@@ -325,11 +326,11 @@ When you complete a skill's checklist, check the boxes and add a completion time
325
326
 
326
327
  ## Bundling
327
328
 
328
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
329
+ `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
329
330
 
330
- **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
331
+ **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`.
331
332
 
332
- **README install badges** (Claude Desktop `.mcpb`, Cursor, VS Code) and the `base64` / `encodeURIComponent` config-generation commands are ship-time concerns — run the `polish-docs-meta` skill, which carries the badge format, layout, and generation snippets in `skills/polish-docs-meta/references/readme.md`.
333
+ **README install badges** (Claude Desktop `.mcpb`, Cursor, VS Code) and the `base64` / `encodeURIComponent` config-generation commands are ship-time concerns — run the `polish-docs-meta` skill, which carries the badge format, layout, and generation snippets in `framework-skills/polish-docs-meta/references/readme.md`.
333
334
 
334
335
  ---
335
336
 
@@ -354,12 +355,18 @@ security: false # optional — true ONLY for a source
354
355
 
355
356
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
356
357
 
357
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
358
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries — don't ship empty headers.
358
359
 
359
360
  **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
360
361
 
361
362
  ---
362
363
 
364
+ ## Publishing
365
+
366
+ **Every release goes through a release PR, straight-through** — `git-wrapup`'s "Release PR mode", mode `straight-through`. One run: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-and-publish` then fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. A caller's brief may run a given release as `gated` instead — a `release-pr-review` pass on the open PR before `release-and-publish`. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history.
367
+
368
+ ---
369
+
363
370
  ## Imports
364
371
 
365
372
  ```ts
package/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** federal-regulations-mcp-server
4
- **Version:** 0.2.4
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.12.3`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 0.3.0
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.13.6`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
8
- **Zod:** ^4.4.3
8
+ **Zod:** ^4.6.5
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -165,7 +165,7 @@ Handlers receive a unified `ctx` object. Key properties:
165
165
 
166
166
  Handlers throw — the framework catches, classifies, and formats.
167
167
 
168
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
168
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text unless the message already contains it verbatim); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Forwarding it is lint-enforced per throw site (`error-contract-recovery-unforwarded`). Mark an entry the service layer throws with `thrownBy: 'service'` so `error-contract-unthrown` skips it — lint-only metadata, nothing at runtime reads it. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring.
169
169
 
170
170
  ```ts
171
171
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
@@ -247,9 +247,9 @@ No prompts in v1 — `createApp({ prompts: [] })`.
247
247
 
248
248
  ## Skills
249
249
 
250
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
250
+ Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
251
251
 
252
- **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
252
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
253
253
 
254
254
  Available skills:
255
255
 
@@ -268,8 +268,9 @@ Available skills:
268
268
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
269
269
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
270
270
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
271
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
272
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
271
+ | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
272
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
273
+ | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
273
274
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
274
275
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
275
276
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
@@ -288,7 +289,7 @@ Available skills:
288
289
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
289
290
  | `api-workers` | Cloudflare Workers runtime |
290
291
 
291
- **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
292
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
292
293
 
293
294
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
294
295
 
@@ -325,11 +326,11 @@ When you complete a skill's checklist, check the boxes and add a completion time
325
326
 
326
327
  ## Bundling
327
328
 
328
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
329
+ `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
329
330
 
330
- **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
331
+ **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`.
331
332
 
332
- **README install badges** (Claude Desktop `.mcpb`, Cursor, VS Code) and the `base64` / `encodeURIComponent` config-generation commands are ship-time concerns — run the `polish-docs-meta` skill, which carries the badge format, layout, and generation snippets in `skills/polish-docs-meta/references/readme.md`.
333
+ **README install badges** (Claude Desktop `.mcpb`, Cursor, VS Code) and the `base64` / `encodeURIComponent` config-generation commands are ship-time concerns — run the `polish-docs-meta` skill, which carries the badge format, layout, and generation snippets in `framework-skills/polish-docs-meta/references/readme.md`.
333
334
 
334
335
  ---
335
336
 
@@ -354,12 +355,18 @@ security: false # optional — true ONLY for a source
354
355
 
355
356
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
356
357
 
357
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
358
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries — don't ship empty headers.
358
359
 
359
360
  **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
360
361
 
361
362
  ---
362
363
 
364
+ ## Publishing
365
+
366
+ **Every release goes through a release PR, straight-through** — `git-wrapup`'s "Release PR mode", mode `straight-through`. One run: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-and-publish` then fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. A caller's brief may run a given release as `gated` instead — a `release-pr-review` pass on the open PR before `release-and-publish`. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history.
367
+
368
+ ---
369
+
363
370
  ## Imports
364
371
 
365
372
  ```ts
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.2.4-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/federal-regulations-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/federal-regulations-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/federal-regulations-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.3.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/federal-regulations-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/federal-regulations-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/federal-regulations-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -27,145 +27,138 @@
27
27
 
28
28
  ---
29
29
 
30
- US administrative law is where agencies turn statutes into binding rules — and it spans three official sources that don't talk to each other. This server stitches them into one workflow: the **Federal Register** (the daily journal of proposed/final rules and notices), the **eCFR** (the codified Code of Federal Regulations, full text, point-in-time), and **Regulations.gov** (rulemaking dockets and the public comments filed on them). Every Federal Register document carries the docket ID and CFR-part handles that chain across sources, so a rule can be traced **proposal → comments → final → codified text** in one sequence.
30
+ ## Overview
31
31
 
32
- The agent sees regulatory verbs — `search_rules`, `get_cfr_section`, `find_comments` — not three API clients. The Federal Register and eCFR tools are **keyless**; the two Regulations.gov tools need a free `api.data.gov` key and return an actionable error without it.
32
+ US federal regulatory law across three official sources: the Federal Register (proposed/final rules and notices), the eCFR (codified, point-in-time CFR text), and Regulations.gov (rulemaking dockets and public comments). Search rules, trace a document from proposal through comments to codified text, and read CFR sections from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
33
33
 
34
- ## Tools
34
+ ### Tools
35
35
 
36
- Seven tools across the regulatory workflow. The **Federal Register** and **eCFR** tools (1–4, 7) are keyless. The two **Regulations.gov** tools (5–6) need `REGULATIONS_GOV_API_KEY`; `regulations_list_open_comments` runs keyless and only adds comment counts when the key is present.
36
+ | Tool | Description |
37
+ |:---|:---|
38
+ | `regulations_search_rules` | Search Federal Register proposed rules, final rules, notices, and presidential documents by query, type, agency, and date range, ranked by relevance or date |
39
+ | `regulations_get_document` | Fetch one Federal Register document by number, with the docket ID, CFR parts, and comment count that chain into other tools |
40
+ | `regulations_browse_cfr` | Walk the CFR hierarchy or full-text-search the codified CFR |
41
+ | `regulations_get_cfr_section` | Read codified CFR text for a section, whole part, or appendix, current or as of a past date |
42
+ | `regulations_get_docket` | Pull a rulemaking docket and its filed documents from Regulations.gov (key required) |
43
+ | `regulations_find_comments` | Fetch public comments on a document or docket, or one comment's full body and attachments (key required) |
44
+ | `regulations_list_open_comments` | List proposed rules, comment-requesting final rules, and optionally notices currently open for public comment, soonest closing first |
37
45
 
38
- | Tool | Source | Key | Description |
39
- |:---|:---|:---|:---|
40
- | `regulations_search_rules` | Federal Register | — | Search proposed rules, final rules, notices, and presidential documents by query, type, agency, date range, and open-for-comment status. The primary discovery entry point. |
41
- | `regulations_get_document` | Federal Register | — | Fetch one FR document by number — full metadata plus the cross-source handles (docket ID, CFR parts, comment count) that chain into the comment and codified-text tools. |
42
- | `regulations_browse_cfr` | eCFR | — | Walk the CFR hierarchy (structure mode) or full-text-search the codified CFR (search mode). Both feed `regulations_get_cfr_section`. |
43
- | `regulations_get_cfr_section` | eCFR | — | Read the codified text at a CFR location — a section, a whole part, or an appendix — current or as of a past date. |
44
- | `regulations_get_docket` | Regulations.gov | ✔ | Pull a rulemaking docket and the documents filed in it (NPRM, final rule, supporting materials). |
45
- | `regulations_find_comments` | Regulations.gov | ✔ | Fetch public comments on a document or docket, or one comment's full body and attachments. Flags attachment-only submissions. |
46
- | `regulations_list_open_comments` | Federal Register (+ Reg.gov enrich) | optional | Rules currently open for public comment, closing soonest first. Fully functional keyless; enriches comment counts when keyed. |
46
+ ### Resources
47
47
 
48
- ---
48
+ | Resource | Description |
49
+ |:---|:---|
50
+ | `regulations://document/{documentNumber}` | A single Federal Register document — metadata plus cross-source handles (mirrors `regulations_get_document`) |
51
+ | `regulations://cfr/{title}/{part}/{section}` | Codified text of a current CFR section (mirrors `regulations_get_cfr_section`) |
49
52
 
50
- ### `regulations_search_rules`
53
+ All resource data is also reachable via the tool surface — tool-only MCP clients lose nothing.
51
54
 
52
- Search the Federal Register — the daily journal of US proposed rules, final rules, notices, and presidential documents (1994–present). Keyless.
55
+ ## Capability reference
53
56
 
54
- - Full-text `query` across title and body, or browse by filters alone
55
- - Filter by document `type` (`PRORULE`, `RULE`, `NOTICE`, `PRESDOCU`), `agencies` (Federal Register agency slug), and publication date window (`published_after` / `published_before`)
56
- - Each result carries the `documentNumber`, `docketIds`, `regulationIdNumbers` (RIN), and `cfrReferences` that chain into the document, comment, and codified-text tools
57
- - Pagination up to 100 per page, 50 pages; the Federal Register caps navigation at 5,000 records and the match count at 10,000 — when a result set is larger, the tool flags it and steers you to date-windowing
57
+ ### `regulations_search_rules` <sub>tool</sub>
58
58
 
59
- ---
59
+ - Keyless; full-text `query` optional, or browse by `type` (`PRORULE`/`RULE`/`NOTICE`/`PRESDOCU`), `agencies` (Federal Register slug, e.g. `environmental-protection-agency`), and `published_after`/`published_before` (real calendar days, `YYYY-MM-DD`)
60
+ - `order` is `relevance`, `newest`, or `oldest`; omitted, it is `relevance` with a `query` and `newest` without one
61
+ - `per_page` 2–100 (default 20), `page` 1–50 — the Federal Register caps navigation at 50 pages / 5,000 records and total matches at 10,000; narrow the date window rather than paging deeper
62
+ - Each result carries `documentNumber` (→ `regulations_get_document`), `agencies` as `{ name, slug }` (the slug feeds back into `agencies`; null when the Federal Register lists an agency by raw name only), `docketIds` (→ `regulations_get_docket` / `find_comments`), `regulationIdNumbers`, and `cfrReferences` (→ `regulations_get_cfr_section`)
63
+ - `invalid_filter` when the Federal Register rejects a filter value (an agency name or acronym instead of a slug), naming the parameter; `upstream_unavailable` on a 5xx/timeout, retryable after a brief wait
60
64
 
61
- ### `regulations_get_document`
65
+ ---
62
66
 
63
- Fetch one Federal Register document by its FR number — the stitching tool of this server. Keyless.
67
+ ### `regulations_get_document` <sub>tool</sub>
64
68
 
65
- - Full metadata: title, type, agencies, abstract, action, effective/comment dates, RINs
66
- - The output renders a **cross-source handles** block — the docket ID (→ `regulations_get_docket` / `regulations_find_comments`), affected CFR parts (→ `regulations_get_cfr_section`), and Regulations.gov document ID and comment count — each next to the tool name that consumes it
67
- - `include_full_text` inlines the document body as plain text; default false returns the body URLs only (final rules can run tens of thousands of words)
69
+ - Keyless; fetch one document by `document_number` (format `\d{4}-\d+`, e.g. `2025-14555`)
70
+ - Full metadata (title, type, agencies as `{ name, slug }`, abstract, action, effective/comment dates, RINs) plus cross-source handles: `docketId`, `regulationsGovDocumentId`, `commentCount`, and `cfrReferences`
71
+ - `include_full_text` inlines the plain-text body as a character window: `max_chars` (default 64,000, up to 200,000) from `offset` (default 0). `fullTextLength` reports the whole body's length and `fullTextNextOffset` the offset to resume from while text remains; documents of about ten printed pages or fewer come back whole, while a major final rule runs past a million characters
72
+ - Passing `offset` or `max_chars` implies `include_full_text`; pairing either with an explicit `include_full_text: false` fails as `full_text_disabled`. An offset at or past the end returns empty `fullText` with the length and a notice
73
+ - `fullText` is plain text: links reduce to their text, and email addresses the published body obfuscates are decoded
74
+ - `not_found` when the FR number doesn't exist; `upstream_unavailable` on a 5xx/timeout
68
75
 
69
76
  ---
70
77
 
71
- ### `regulations_browse_cfr`
78
+ ### `regulations_browse_cfr` <sub>tool</sub>
72
79
 
73
- Explore the codified Code of Federal Regulations via eCFR in two modes. Keyless.
74
-
75
- - `structure` mode walks the CFR tree (all 50 titles, or one title's chapters → parts → sections) to discover a cite when the exact citation is unknown
76
- - `search` mode runs a full-text query across the codified CFR and returns matching sections with their hierarchy path and a snippet
77
- - `title` and `part` scope both modes, so a part surfaced by `structure` can be searched directly instead of filtering a whole title's hits by eye. A part needs its title in either mode — part numbers repeat across the Code — and is matched exactly, so pass it as eCFR writes it (`58`, not `Part 58` or `058`)
78
- - A live search hit's `hierarchyPath` names the part it sits in (`Part 51 — Requirements for Preparation, Adoption, and Submittal of Implementation Plans`), so the subject matter is readable without a second call; a mirror hit's path stays structural, because the index stores no level names
79
- - Search is served from the synced local mirror (FTS5) only when the mirror's title coverage can answer the request — a title it does not hold, an all-titles query against a scoped mirror, a `date`, or a cold deploy all go to the live eCFR search API instead
80
- - Every search result reports `source` (`mirror` or `live`) and `sourceScope` — which corpus answered and what it covers, so an empty result is never mistaken for "no such regulation"
81
- - `date` searches the section text in effect that day; eCFR indexes 2017-01-03 onward, and an undated search is pinned to eCFR's current index date rather than spanning every historical version
82
- - Both modes assemble a `cfrCite` that feeds `regulations_get_cfr_section`, and both hand appendices back the same way: an `appendix` field with eCFR's verbatim identifier, and a cite that names the appendix rather than the part around it
83
- - The mirror indexes section text only, so it never matches an appendix — its `sourceScope` says so, since otherwise an appendix that exists and one that does not both read as zero matches
80
+ - Keyless; `mode: "structure"` walks the CFR tree (all 50 titles, or one title's chapters → parts → sections); `mode: "search"` full-text-searches the codified CFR
81
+ - `title` (1–50) and `part` scope both modes; a `part` without `title` is rejected (`title_required_for_part`) since part numbers repeat across titles
82
+ - Search accepts `date` (point-in-time; eCFR indexes 2017-01-03 onward) and `per_page` (1–50, default 20)
83
+ - Every search result reports `source` (`mirror`/`live`) and `sourceScope` — the mirror only answers a title it holds, never an all-titles query when scoped, so anything it can't answer falls through to the live eCFR API
84
+ - `query_required` when `mode="search"` has no query; `title_not_found` / `date_out_of_range` / `upstream_unavailable` round out the errors
84
85
 
85
86
  ---
86
87
 
87
- ### `regulations_get_cfr_section`
88
-
89
- Read the codified text at a CFR location — one section, a whole part, or one appendix — via eCFR. Keyless.
88
+ ### `regulations_get_cfr_section` <sub>tool</sub>
90
89
 
91
- - Answers "what does 40 CFR 50.1 say today?" and "...as of 2019-01-01?" — pass `date` for point-in-time text
92
- - Provide `title` + `part` + `section` for one section, or omit `section` to fetch the whole part
93
- - Provide `appendix` to read an appendix, passing the identifier verbatim as `regulations_browse_cfr` emits it (`Appendix A-1 to Part 50`, `Schedule I to Part 789`, `Special Federal Aviation Regulation No. 88`) — the identifiers are prose, not letters, and a short form matches nothing
94
- - A whole-part read names the part's appendices and their headings but does not inline their text; appendices routinely run several times the length of the sections around them, so reading one is a deliberate second call
95
- - Current single-section reads are served from the local mirror when ready (the `source` is reported); historical dates, whole-part fetches, appendix reads, and a cold mirror fall back to the live eCFR versioner
96
- - The text carries what the XML carries: paragraphs, subheadings, editorial notes, tables (one pipe-delimited line per row), figure references, and the trailing source citation — the bracketed Federal Register history (`[36 FR 22384, Nov. 25, 1971, as amended at 81 FR 68276, Oct. 3, 2016]`) that leads back to the rulemakings behind the text
97
- - A cite that names nothing returns the tool's not-found error with a recovery hint pointing at `regulations_browse_cfr` structure mode, not a bare upstream status
98
- - eCFR retains historical versions back to roughly 2017; a date before coverage is rejected with guidance
90
+ - Keyless; reads one section (`title`+`part`+`section`), a whole part (`title`+`part`, `section` omitted), or one appendix (`title`(+`part`)+`appendix`) — `section` and `appendix` are mutually exclusive (`conflicting_target`)
91
+ - `date` for point-in-time text; eCFR retains history back to ~2017-01-03, rejected earlier as `date_out_of_range`
92
+ - `appendix` must be passed verbatim as eCFR / `regulations_browse_cfr` emits it (e.g. `Appendix A-1 to Part 50`), not a short form
93
+ - A whole-part fetch lists its appendices' identifiers and headings without inlining their text — call again with `appendix` to read one
94
+ - `source` (`mirror`/`live`) reports provenance; current single-section reads are mirror-served when ready, everything else (historical dates, whole-part, appendix reads) falls back to the live eCFR versioner
95
+ - `not_found` / `location_required` / `upstream_unavailable` round out the errors
99
96
 
100
97
  ---
101
98
 
102
- ### `regulations_get_docket` · key required
103
-
104
- Pull a rulemaking docket from Regulations.gov by docket ID (e.g. `EPA-HQ-OAR-2025-0194`). Requires `REGULATIONS_GOV_API_KEY`.
99
+ ### `regulations_get_docket` <sub>tool</sub> · key required
105
100
 
106
- - Docket metadata (title, agency, RIN, abstract) plus the documents filed in it (NPRM, final rule, supporting materials)
107
- - Each returned document's `objectId` feeds `regulations_find_comments(document_object_id)`; `frDocNum` chains back to `regulations_get_document`
108
- - A docket often holds hundreds of supporting materials — filter `document_types` to `Proposed Rule` / `Rule` to find the rule documents themselves
109
- - An unknown docket ID returns the tool's not-found error with a recovery hint, not a raw upstream status
110
- - Regulations.gov requires a page size of 5–250 and caps a query at 20 pages (5,000 records)
101
+ - Requires `REGULATIONS_GOV_API_KEY`; fetch a docket by `docket_id` (e.g. `EPA-HQ-OAR-2025-0194`)
102
+ - `document_types` filters to `Proposed Rule` / `Rule` / `Notice` / `Supporting & Related Material` / `Other` — a docket often holds hundreds of supporting materials
103
+ - `per_page` 5–250 (default 25), `page` 1–20 — Regulations.gov caps a query at 5,000 records
104
+ - Each document's `objectId` chains into `regulations_find_comments`; `frDocNum` chains back to `regulations_get_document`
105
+ - `auth_required` (missing/rejected key) names the env var and signup URL; `not_found` / `rate_limited` (429, 1,000 req/hr) / `upstream_unavailable` round out the errors
111
106
 
112
107
  ---
113
108
 
114
- ### `regulations_find_comments` · key required
109
+ ### `regulations_find_comments` <sub>tool</sub> · key required
115
110
 
116
- Fetch public comments — the unique corpus of what citizens and organizations actually submitted. Requires `REGULATIONS_GOV_API_KEY`.
117
-
118
- - Target with exactly one of: `docket_id` (all comments in a docket), `document_object_id` (comments on one document), `fr_document_number` (convenience — resolves the FR number to its Regulations.gov document internally), or `comment_id` (one comment's full detail). Supplying two is rejected with an actionable error rather than resolved by precedence
119
- - An ID Regulations.gov cannot resolve comes back as the tool's not-found error with a recovery hint, whether the API reports it as a 404 or as the 400 it uses for an ID it cannot parse
120
- - The list endpoint returns no body text or attachment info — call with `comment_id` to read a comment's body
121
- - When a comment's real content is a PDF/DOCX attachment, the body is a stub and `attachmentOnly` is `true`; the attachment download URLs are returned so the agent knows where the substance lives. The flag reaches both the structured output and the rendered text
122
- - Regulations.gov caps a query at 20 pages (5,000 records); a high-volume docket surfaces a sample, flagged as truncated
111
+ - Requires `REGULATIONS_GOV_API_KEY`; exactly one of `docket_id`, `document_object_id`, `fr_document_number`, or `comment_id` — zero or two is rejected (`target_required` / `multiple_targets`), never resolved by precedence
112
+ - `comment_id` returns one comment's full body and attachments; the other three list a set — the list endpoint carries no body text, so read a comment's substance via `comment_id`
113
+ - When a comment's substance is a PDF/DOCX attachment, `bodyText` is a stub and `attachmentOnly` is `true`, with the attachment download URLs
114
+ - `per_page` 5–250 (default 25), `page` 1–20 — Regulations.gov caps a query at 5,000 records; narrow a high-volume docket with `document_object_id`
115
+ - `auth_required` / `not_found` / `rate_limited` (429, 1,000 req/hr) / `upstream_unavailable` round out the errors
123
116
 
124
117
  ---
125
118
 
126
- ### `regulations_list_open_comments`
119
+ ### `regulations_list_open_comments` <sub>tool</sub>
120
+
121
+ - Keyless; lists documents currently open for public comment, sorted by closing date soonest first (same-day closes by document number) across the whole open window — filter by `type`, `query`, `agencies` (Federal Register slug), and `closing_before` (a real calendar day, `YYYY-MM-DD`)
122
+ - `type` takes `PRORULE`, `RULE`, and `NOTICE`; the default (also used for an empty list) is `["PRORULE", "RULE"]` — proposed rules plus the direct final and interim final rules that take comment. `NOTICE` adds several hundred information-collection and other notices
123
+ - `per_page` 1–100 (default 20), `page` from 1; `totalCount`, `totalPages`, and `nextPage` describe the window. The Federal Register can't sort by comment date, so the window is fetched whole (one request up to 2,000 documents, then 2,000 per request) and paged locally; past the Federal Register's 10,000-document limit the response is `truncated` and holds the 10,000 most recently published matches
124
+ - Each row carries `daysRemaining`, `documentNumber` (→ `regulations_get_document`), `agencies` as `{ name, slug }`, and `docketIds` (→ `regulations_find_comments`)
125
+ - Fully functional keyless; when `REGULATIONS_GOV_API_KEY` is set, `commentCount` is enriched from the Federal Register document's own embedded Regulations.gov info (no extra call) — `keyed` reports which
126
+ - `invalid_filter` when the Federal Register rejects a filter value, naming the parameter; `upstream_unavailable` on a 5xx/timeout
127
127
 
128
- Rules currently open for public comment, sorted by closing date (soonest first) — "what can I still weigh in on?" Keyless, degrades gracefully.
128
+ ---
129
129
 
130
- - Filter by `agencies` (Federal Register slug) and `query`; `closing_before` finds deadlines you need to act on soon
131
- - Each row carries `daysRemaining`, the `documentNumber` (→ `regulations_get_document`), and `docketIds` (→ `regulations_find_comments`)
132
- - Fully functional without a key; when `REGULATIONS_GOV_API_KEY` is configured, each row is enriched with the comment count read from the Federal Register document's own embedded Regulations.gov info (no extra rate-limited call)
130
+ ### `regulations://document/{documentNumber}` <sub>resource</sub>
133
131
 
134
- ## Resources
132
+ - Same payload as `regulations_get_document` without full text — metadata plus cross-source handles, the body never inlined
133
+ - `documentNumber` format `\d{4}-\d+` (e.g. `2025-14555`)
134
+ - `not_found` / `upstream_unavailable` mirror the tool's errors
135
135
 
136
- | Type | Name | Description |
137
- |:---|:---|:---|
138
- | Resource | `regulations://document/{documentNumber}` | A single Federal Register document — metadata plus cross-source handles (mirrors `regulations_get_document`, full text omitted). |
139
- | Resource | `regulations://cfr/{title}/{part}/{section}` | Codified text of a current CFR section (mirrors `regulations_get_cfr_section` at the current date, mirror-backed with a live eCFR fallback). |
136
+ ---
140
137
 
141
- All resource data is also reachable via the tool surface — tool-only MCP clients lose nothing. Collections (rule search, comment lists) are not exposed as resources; use `regulations_search_rules` and `regulations_find_comments` instead.
138
+ ### `regulations://cfr/{title}/{part}/{section}` <sub>resource</sub>
142
139
 
143
- ## Features
140
+ - Same payload as `regulations_get_cfr_section` at the current date — sections only; read an appendix via the tool's `appendix` input instead
141
+ - Mirror-backed with a live eCFR fallback; `source` (`mirror`/`live`) reports provenance
142
+ - `not_found` / `upstream_unavailable` mirror the tool's errors
144
143
 
145
- Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):
144
+ ## Features
146
145
 
147
- - Declarative tool and resource definitions — single file per primitive, framework handles registration and validation
148
- - Unified error handling — handlers throw, framework catches, classifies, and formats; typed error contracts with recovery hints
149
- - Pluggable auth: `none`, `jwt`, `oauth`
150
- - Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
151
- - Structured logging with optional OpenTelemetry tracing
152
- - STDIO and Streamable HTTP transports
146
+ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
153
147
 
154
- Federal-regulations-specific:
148
+ Federal Register / eCFR / Regulations.gov-specific:
155
149
 
156
- - One workflow over three official sources — the agent sees regulatory verbs, not three API clients; which source is hit is a service-layer detail
150
+ - One workflow over three official sources — the agent sees regulatory verbs (`search_rules`, `get_cfr_section`, `find_comments`), not three API clients
157
151
  - Cross-source stitching — every Federal Register document surfaces its docket ID and CFR-part handles next to the tools that consume them, priming the proposal → comments → final → codified-text trace
158
- - Keyless core — Federal Register + eCFR (5 of 7 tools) is a complete deployment with no API key; the Regulations.gov leg layers on top
159
- - Locally mirrored codified CFR — the eCFR is synced once into embedded SQLite + FTS5 and queried by exact cite or full text, with a live-API fallback whenever the mirror's title coverage cannot answer the request
160
- - Three independent service clients, each with its own base URL, retry/backoff, and rate-limit handling (Regulations.gov honors `Retry-After` on 429)
161
- - A 45-second wall-clock budget per request, shared across every upstream call, retry, and backoff a tool makes — a stalled source answers with an actionable `upstream_unavailable` well inside a client's request timeout, rather than leaving the caller to time out with nothing on the wire
152
+ - Keyless core — the Federal Register + eCFR tools (5 of 7) are a complete deployment with no API key; the Regulations.gov leg (`REGULATIONS_GOV_API_KEY`) layers on top
153
+ - Locally mirrored codified CFR — the eCFR is synced once into embedded SQLite + FTS5 and queried by exact cite or full text, falling back to the live API whenever the mirror's title coverage can't answer
154
+ - A 45-second wall-clock budget per request, shared across every upstream call, retry, and backoff a tool makes — a stalled source answers with an actionable `upstream_unavailable` well inside a client's request timeout
162
155
 
163
156
  Agent-friendly output:
164
157
 
165
- - Provenance on every CFR read — `source: "mirror" | "live"` so agents know whether text came from the synced index or the live API, and on search a `sourceScope` line naming what that corpus covers
166
- - Honest truncation — Federal Register (50-page / 5,000-record) and Regulations.gov (20-page / 5,000-record) ceilings are surfaced with guidance to narrow, never silently dropped
158
+ - Provenance — `source: "mirror" | "live"` on every CFR read, and a `sourceScope` line on search naming what that corpus covers
159
+ - Honest truncation — Federal Register (50-page/5,000-record) and Regulations.gov (20-page/5,000-record) ceilings are surfaced via `truncated`/`notice` enrichment, never silently dropped
167
160
  - Attachment-aware comments — `attachmentOnly` flags when a comment's substance is a file rather than inline text, with the download URLs, on both the structured and text surfaces
168
- - Actionable `auth_required` errors — the two keyed tools name the env var and the free signup URL rather than passing through a raw 401/403
161
+ - Actionable `auth_required` errors — the two keyed tools name the env var and free signup URL rather than passing through a raw 401/403
169
162
 
170
163
  ## Getting started
171
164
 
@@ -306,36 +299,6 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
306
299
 
307
300
  See [`.env.example`](./.env.example) for the full list of optional overrides.
308
301
 
309
- ## The eCFR mirror
310
-
311
- The codified CFR is large (~50 titles, hundreds of MB of XML — Title 40 alone is ~150 MB) but changes far less often than it is queried. This server mirrors it once into an embedded SQLite + FTS5 index and serves `regulations_get_cfr_section` (current single sections) and `regulations_browse_cfr` (full-text search) from it, falling back to the live eCFR API when the mirror has not yet completed an init or a historical/whole-part read is requested.
312
-
313
- **The mirror answers only what it holds.** A scoped mirror (`ECFR_MIRROR_TITLES`) is consulted for a search only when the requested title is in the set, and never for an all-titles search — a three-title index reporting zero matches for the whole CFR is worse than no index at all. Those requests go to live eCFR instead, and every search result names the corpus that answered it in `sourceScope`.
314
-
315
- **The mirror is populated out-of-band, never on startup** — a full title sweep takes a while and must not block the server. Build it with:
316
-
317
- ```sh
318
- # Mirror all 50 CFR titles (idempotent, resumable from the persisted cursor)
319
- bun run mirror:init
320
-
321
- # Or scope to specific titles to build a smaller, faster mirror
322
- ECFR_MIRROR_TITLES=21,40 bun run mirror:init
323
-
324
- # Incremental refresh against the latest eCFR issues
325
- bun run mirror:refresh
326
-
327
- # Report row counts, the last-synced issue date, and whether the index is stale
328
- bun run mirror:verify
329
- ```
330
-
331
- **A sync pass deletes only what it read in full.** Each title's pass removes the rows the pass did not rewrite — how a withdrawn section leaves the index. A response that arrives cut short parses to a prefix of the title, which is indistinguishable from a title that shrank, so the pass first checks that the document it read is whole (its root element is closed) and leaves the title untouched when it is not, rather than deleting everything past the cut and reporting the run complete.
332
-
333
- **An index written by an older ingester is not served.** The ingester stamps a version into the mirror once a run has re-derived every title the index holds; when a release changes how rows are derived, an index carrying an older stamp is treated exactly like a cold one — every read falls back to live eCFR until `bun run mirror:refresh` re-derives it. `mirror:verify` says so explicitly, and a refresh removes the rows the previous ingester filed under keys the current one no longer produces. A run that skips a title — a failed fetch, or an `ECFR_MIRROR_TITLES` scope narrower than the index — names it in the log and leaves the stamp unwritten, because those untouched titles are exactly the ones a stamp would certify wrongly.
334
-
335
- Until the mirror has completed an init, the codified-text tools run against the live eCFR API — so the server is useful immediately on a fresh deploy; the mirror is a latency/throughput optimization, not a hard dependency. On the HTTP transport a weekly refresh is scheduled automatically (`ECFR_MIRROR_REFRESH_CRON`); stdio operators run the lifecycle scripts above.
336
-
337
- The mirror database (`./data/ecfr-mirror.sqlite`) is a local artifact — it is git-ignored and excluded from the `.mcpb` bundle, never shipped in the package.
338
-
339
302
  ## Running the server
340
303
 
341
304
  ### Local development
@@ -352,6 +315,14 @@ The mirror database (`./data/ecfr-mirror.sqlite`) is a local artifact — it is
352
315
  bun run start:http
353
316
  ```
354
317
 
318
+ - **Populate/refresh the eCFR mirror** (out-of-band, idempotent — the codified-text tools work against the live eCFR API until this completes, so it's a latency optimization, not a hard dependency):
319
+
320
+ ```sh
321
+ bun run mirror:init # full build across all 50 titles, resumable (scope with ECFR_MIRROR_TITLES)
322
+ bun run mirror:refresh # incremental refresh against the latest eCFR issues
323
+ bun run mirror:verify # report row counts and the last-synced issue date
324
+ ```
325
+
355
326
  - **Run checks and tests:**
356
327
 
357
328
  ```sh
@@ -393,21 +364,19 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
393
364
  - Register new tools and resources via the barrels in `src/mcp-server/*/definitions/index.ts`
394
365
  - Wrap external API calls: validate raw → normalize to the domain type → return the output schema; never fabricate missing upstream fields
395
366
 
367
+ ## Data disclaimer
368
+
369
+ eCFR content is "authoritative but unofficial" per the Office of the Federal Register — not the official legal edition of the Code of Federal Regulations; verify against [govinfo.gov](https://www.govinfo.gov/app/collection/cfr) for legal research. This server is not affiliated with or endorsed by the Office of the Federal Register, the Government Publishing Office, or the General Services Administration.
370
+
396
371
  ## Contributing
397
372
 
398
- Issues and pull requests are welcome. Run checks and tests before submitting:
373
+ Issues are welcome. Run checks and tests before submitting:
399
374
 
400
375
  ```sh
401
376
  bun run devcheck
402
377
  bun run test
403
378
  ```
404
379
 
405
- ## Data disclaimer
406
-
407
- eCFR content is "authoritative but unofficial" per the Office of the Federal Register — it is not the official legal edition of the Code of Federal Regulations. The official CFR is published annually in print and digitally at [govinfo.gov](https://www.govinfo.gov/app/collection/cfr). For legal research requiring the official text, verify against govinfo.gov or the annual printed CFR.
408
-
409
- This server is not affiliated with or endorsed by the Office of the Federal Register (OFR), the Government Publishing Office (GPO), or the General Services Administration (GSA).
410
-
411
380
  ## License
412
381
 
413
382
  Apache-2.0 — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,23 @@
1
+ ---
2
+ summary: "HTTP deployments now declare stateless session mode explicitly, and markdown table cells with a backslash render correctly."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.2.5 — 2026-09-20
8
+
9
+ ## Changed
10
+
11
+ - **Skill tree** moved `skills/` → `framework-skills/`, so a plugin host no longer auto-loads these development skills into every installing agent.
12
+ - A handler that unwinds after its request's abort signal fired now classifies `RequestCancelled` (`-32011`) instead of `InternalError` (`-32603`), picked up from the framework bump below.
13
+
14
+ ## Fixed
15
+
16
+ - **Session mode** — the server now declares `sessionMode: 'stateless'` explicitly instead of relying on the schema default (`auto`, which resolves to `stateful`). An HTTP deployment with no `MCP_SESSION_MODE` set previously resolved `stateful` and issued an `Mcp-Session-Id` header; it now resolves `stateless` and does not. `teardown()` also drains an in-flight mirror refresh before closing the store.
17
+ - **Markdown table cells** — `escapePipes` now escapes backslashes before pipes, so a value carrying its own backslash (e.g. `x\|y`) no longer produces a malformed row or an eaten character. Also resolves the open CodeQL alert `js/incomplete-sanitization`.
18
+
19
+ ## Dependencies
20
+
21
+ - `@cyanheads/mcp-ts-core` ^0.12.3 → ^0.13.6 (error-contract `severity`/`thrownBy` fields, plugin-manifest `userConfig` wiring checks, README version-badge parity check)
22
+ - `zod` ^4.4.3 → ^4.6.5, `vitest` ^4.1.11 → ^5.0.1
23
+ - dev: `@biomejs/biome`, `@types/node`, `ignore`, `tsc-alias` refreshed; bun engine floor raised to `>=1.4.0`