@cyanheads/federal-regulations-mcp-server 0.4.0 → 0.5.1
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/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +61 -94
- package/changelog/0.5.x/0.5.0.md +44 -0
- package/changelog/0.5.x/0.5.1.md +27 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/resources/definitions/document.resource.d.ts +1 -1
- package/dist/mcp-server/resources/definitions/document.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/document.resource.js +10 -7
- package/dist/mcp-server/resources/definitions/document.resource.js.map +1 -1
- package/dist/mcp-server/resources/definitions/index.d.ts +12 -12
- package/dist/mcp-server/tools/definitions/browse-cfr.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/browse-cfr.tool.js +1 -20
- package/dist/mcp-server/tools/definitions/browse-cfr.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/cfr-part.d.ts +22 -0
- package/dist/mcp-server/tools/definitions/cfr-part.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/cfr-part.js +27 -0
- package/dist/mcp-server/tools/definitions/cfr-part.js.map +1 -0
- package/dist/mcp-server/tools/definitions/document-number.d.ts +24 -0
- package/dist/mcp-server/tools/definitions/document-number.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/document-number.js +28 -0
- package/dist/mcp-server/tools/definitions/document-number.js.map +1 -0
- package/dist/mcp-server/tools/definitions/find-comments.tool.d.ts +26 -4
- package/dist/mcp-server/tools/definitions/find-comments.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/find-comments.tool.js +230 -47
- package/dist/mcp-server/tools/definitions/find-comments.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/format-utils.d.ts +18 -1
- package/dist/mcp-server/tools/definitions/format-utils.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/format-utils.js +24 -1
- package/dist/mcp-server/tools/definitions/format-utils.js.map +1 -1
- package/dist/mcp-server/tools/definitions/fr-citation.d.ts +24 -0
- package/dist/mcp-server/tools/definitions/fr-citation.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/fr-citation.js +29 -0
- package/dist/mcp-server/tools/definitions/fr-citation.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-cfr-section.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/get-cfr-section.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-docket.tool.d.ts +5 -1
- package/dist/mcp-server/tools/definitions/get-docket.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-docket.tool.js +67 -17
- package/dist/mcp-server/tools/definitions/get-docket.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-document.tool.d.ts +12 -6
- package/dist/mcp-server/tools/definitions/get-document.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-document.tool.js +51 -21
- package/dist/mcp-server/tools/definitions/get-document.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +240 -170
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/list-open-comments.tool.d.ts +6 -3
- package/dist/mcp-server/tools/definitions/list-open-comments.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/list-open-comments.tool.js +41 -31
- package/dist/mcp-server/tools/definitions/list-open-comments.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/paging.d.ts +48 -0
- package/dist/mcp-server/tools/definitions/paging.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/paging.js +48 -0
- package/dist/mcp-server/tools/definitions/paging.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-rules.tool.d.ts +43 -3
- package/dist/mcp-server/tools/definitions/search-rules.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-rules.tool.js +274 -33
- package/dist/mcp-server/tools/definitions/search-rules.tool.js.map +1 -1
- package/dist/services/character-references.d.ts +29 -0
- package/dist/services/character-references.d.ts.map +1 -0
- package/dist/services/character-references.js +49 -0
- package/dist/services/character-references.js.map +1 -0
- package/dist/services/ecfr/ecfr-service.d.ts.map +1 -1
- package/dist/services/ecfr/ecfr-service.js +8 -28
- package/dist/services/ecfr/ecfr-service.js.map +1 -1
- package/dist/services/ecfr/xml.d.ts +2 -2
- package/dist/services/ecfr/xml.d.ts.map +1 -1
- package/dist/services/ecfr/xml.js +6 -37
- package/dist/services/ecfr/xml.js.map +1 -1
- package/dist/services/federal-register/comment-period.d.ts +17 -0
- package/dist/services/federal-register/comment-period.d.ts.map +1 -0
- package/dist/services/federal-register/comment-period.js +28 -0
- package/dist/services/federal-register/comment-period.js.map +1 -0
- package/dist/services/federal-register/federal-register-service.d.ts +20 -6
- package/dist/services/federal-register/federal-register-service.d.ts.map +1 -1
- package/dist/services/federal-register/federal-register-service.js +164 -49
- package/dist/services/federal-register/federal-register-service.js.map +1 -1
- package/dist/services/federal-register/types.d.ts +79 -15
- package/dist/services/federal-register/types.d.ts.map +1 -1
- package/dist/services/regulations-gov/regulations-gov-service.d.ts +22 -6
- package/dist/services/regulations-gov/regulations-gov-service.d.ts.map +1 -1
- package/dist/services/regulations-gov/regulations-gov-service.js +71 -42
- package/dist/services/regulations-gov/regulations-gov-service.js.map +1 -1
- package/dist/services/regulations-gov/types.d.ts +21 -2
- package/dist/services/regulations-gov/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/server.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** federal-regulations-mcp-server
|
|
4
|
-
**Version:** 0.
|
|
4
|
+
**Version:** 0.5.1
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.13.6`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** federal-regulations-mcp-server
|
|
4
|
-
**Version:** 0.
|
|
4
|
+
**Version:** 0.5.1
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.13.6`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/federal-regulations-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/federal-regulations-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -29,123 +29,95 @@
|
|
|
29
29
|
|
|
30
30
|
## Overview
|
|
31
31
|
|
|
32
|
-
US federal regulatory law
|
|
32
|
+
US federal regulatory law from three official sources: the Federal Register (proposed rules, final rules, and notices), the eCFR (codified CFR text, current or as of a past date), and Regulations.gov (rulemaking dockets and public comments). Search rules, trace a document from proposal through its comments to the codified text, and read CFR sections. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
|
|
33
33
|
|
|
34
34
|
### Tools
|
|
35
35
|
|
|
36
36
|
| Tool | Description |
|
|
37
37
|
|:---|:---|
|
|
38
|
-
| `regulations_search_rules` | Search Federal Register proposed rules, final rules, notices, and presidential documents by query, type, agency,
|
|
39
|
-
| `regulations_get_document` | Fetch one Federal Register document by number, with the docket
|
|
38
|
+
| `regulations_search_rules` | Search Federal Register proposed rules, final rules, notices, and presidential documents by query, type, agency, date range, CFR part, docket number, and RIN, or resolve a page cite to its document |
|
|
39
|
+
| `regulations_get_document` | Fetch one Federal Register document by number, with the docket IDs, CFR parts, citation, comment count, and comment URL that chain into other tools |
|
|
40
40
|
| `regulations_browse_cfr` | List CFR titles, a title's chapters, or every section and appendix in a part, or full-text-search the codified CFR |
|
|
41
41
|
| `regulations_get_cfr_section` | Read codified CFR text for a section, whole part, or appendix, current or as of a past date |
|
|
42
42
|
| `regulations_get_docket` | Pull a rulemaking docket and its filed documents from Regulations.gov (key required) |
|
|
43
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
|
|
44
|
+
| `regulations_list_open_comments` | List documents currently open for public comment, soonest closing first |
|
|
45
45
|
|
|
46
46
|
### Resources
|
|
47
47
|
|
|
48
48
|
| Resource | Description |
|
|
49
49
|
|:---|:---|
|
|
50
|
-
| `regulations://document/{documentNumber}` |
|
|
51
|
-
| `regulations://cfr/{title}/{part}/{section}` | Codified text of a current CFR section
|
|
50
|
+
| `regulations://document/{documentNumber}` | One Federal Register document's metadata and cross-source handles |
|
|
51
|
+
| `regulations://cfr/{title}/{part}/{section}` | Codified text of a current CFR section |
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Both resources mirror a tool (`regulations_get_document`, `regulations_get_cfr_section`), so tool-only clients lose nothing.
|
|
54
54
|
|
|
55
55
|
## Capability reference
|
|
56
56
|
|
|
57
57
|
### `regulations_search_rules` <sub>tool</sub>
|
|
58
58
|
|
|
59
|
-
-
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
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
|
|
59
|
+
- Optional full-text `query`, filtered by `type` (`PRORULE` / `RULE` / `NOTICE` / `PRESDOCU`), `agencies` (Federal Register slugs such as `environmental-protection-agency`, not names or acronyms), `published_after` / `published_before`, `cfr_title` + `cfr_part` (a part number or range such as `140-143`; `cfr_part` needs `cfr_title`, else `title_required_for_part`), `docket_id` (a docket number as the Federal Register prints it), and `rin`, all combined; `per_page` 2–100 (default 20), `page` 1–50
|
|
60
|
+
- `citation` (`"89 FR 49102"`) with `citation_date` (the date its source note prints beside it) returns the documents printed on that page, in page order, volume 59 (1994) onward. A cite missing its date, or combined with the date window, fails with `citation_incomplete`; a volume before 59 or a date from another year fails with `citation_out_of_range`. When no document spans the page, the notice gives that day's page range
|
|
61
|
+
- Each result carries `documentNumber`, `citation` / `startPage` / `endPage`, `agencies[].slug`, the printed `docketIds`, `regulationsGovDocketId` and `regulationsGovDocumentId`, `commentCount`, `commentUrl`, `commentsCloseOn` with `commentPeriodOpen` (open through 11:59 PM Eastern on the close date), `regulationIdNumbers`, and `cfrReferences` for the follow-up tools. `totalPages` and `nextPage` page the results. The Federal Register serves 50 pages, so `truncated` marks a set larger than 50 × `per_page` (1,000 at the default, 5,000 at 100); narrow the date window rather than paging deeper. `totalCount` stops at 10,000, which means at least that many. A window whose start falls after its end fails with `date_range_inverted`, and `invalid_filter` names a rejected parameter
|
|
64
62
|
|
|
65
63
|
---
|
|
66
64
|
|
|
67
65
|
### `regulations_get_document` <sub>tool</sub>
|
|
68
66
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
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
|
|
67
|
+
- `document_number` in any form the Federal Register issues: `2024-07773` from 2010 on, and older and correction numbers such as `98-1572`, `E9-25990`, or `C1-2009-30484`; `include_full_text` adds the plain-text body as one window of `max_chars` (default 64,000, max 200,000) starting at `offset`, and passing either of those implies it
|
|
68
|
+
- Returns metadata plus the handles other tools take: the Regulations.gov `docketId` and `regulationsGovDocumentId`, the printed `docketIds`, `citation` / `startPage` / `endPage`, `commentCount`, `commentUrl`, `commentPeriodOpen`, and `cfrReferences`. With text, `fullTextLength` and `fullTextNextOffset` (present while text remains) page the body; a major final rule runs past a million characters
|
|
75
69
|
|
|
76
70
|
---
|
|
77
71
|
|
|
78
72
|
### `regulations_browse_cfr` <sub>tool</sub>
|
|
79
73
|
|
|
80
|
-
-
|
|
81
|
-
- `
|
|
82
|
-
- `date` is point-in-time in both modes and must be a real calendar day; structure mode rejects a date past the title's up-to-date date (`date_out_of_range`), and search indexes 2017-01-03 onward
|
|
83
|
-
- `page` (1-based, default 1) and `per_page` (1–50, default 20) page search results and a part's listing; `page` and `totalCount` come back with a notice naming the next page, and a page past the end returns no rows plus the last page. Labels are plain text (eCFR's inline markup stripped)
|
|
84
|
-
- Search returns one row per section: eCFR's live index answers one hit per section *version*, so repeats are collapsed. `countBasis` says what `totalCount` counts — `sections`, or eCFR's own `section_versions` count until the whole hit list has been read. Live search pages through eCFR's first 10,000 hits only; a page past them fails as `page_out_of_window`
|
|
85
|
-
- 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
|
|
86
|
-
- `query_required` when `mode="search"` has no query; `title_not_found` / `date_out_of_range` / `page_out_of_window` / `upstream_unavailable` round out the errors
|
|
74
|
+
- `mode: "structure"` lists the 50 titles, a title's top-level divisions, or, with `title` + `part`, every section and appendix in the part with its `subpart` and `subjectGroup`; `mode: "search"` full-text-searches the codified CFR and requires `query`. `title`, `part` (needs `title`), and a point-in-time `date` scope both modes, and `per_page` 1–50 (default 20) pages search results and part listings
|
|
75
|
+
- Search rows carry `cfrCite`, `heading`, `hierarchyPath`, and `excerpt`, one per section. `source` (`mirror` / `live`) and `sourceScope` name the corpus that answered and what it covers, and `countBasis` says whether `totalCount` counts `sections` or `section_versions`; live search reaches eCFR's first 10,000 hits only (`page_out_of_window` past them)
|
|
87
76
|
|
|
88
77
|
---
|
|
89
78
|
|
|
90
79
|
### `regulations_get_cfr_section` <sub>tool</sub>
|
|
91
80
|
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
- Text is one window of `bodyText`: `offset` (default 0) and `max_chars` (default 64,000, max 200,000) select it, and `bodyTextOffset` / `bodyTextLength` / `bodyTextNextOffset` (present while text remains) page through it — the same contract as `regulations_get_document`'s full text. An offset past the end is an empty window with a `notice`
|
|
95
|
-
- A whole-part fetch adds `sections[]`, a text-free index of the sections in the window (`section`, `heading`, `cfrCite`, and the `offset` each starts at in the part's text — pass it as `offset` to jump there)
|
|
96
|
-
- `date` for point-in-time text, 2017-01-01 through the title's up-to-date date; anything outside that is `date_out_of_range`, checked before any text request, and a date that is not a real calendar day is rejected at input validation
|
|
97
|
-
- `appendix` must be passed verbatim as eCFR / `regulations_browse_cfr` emits it (e.g. `Appendix A-1 to Part 50`), not a short form
|
|
98
|
-
- A whole-part fetch lists its appendices' identifiers and headings without inlining their text — call again with `appendix` to read one
|
|
99
|
-
- `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
|
|
100
|
-
- `not_found` / `location_required` / `upstream_unavailable` round out the errors
|
|
81
|
+
- One section (`title` + `part` + `section`), a whole part (`section` omitted), or one appendix (`appendix`, verbatim as `regulations_browse_cfr` emits it, e.g. `Appendix A-1 to Part 50`). `section` also takes cites as people write them (`"61"`, `"§ 141.61"`, `"141.61(c)"`), and `date` reads text from 2017-01-01 through the title's up-to-date date
|
|
82
|
+
- Text comes back as one `bodyText` window (`max_chars` default 64,000, max 200,000, from `offset`), paged by `bodyTextLength` and `bodyTextNextOffset`. A whole-part read adds a `sections[]` index with each section's `offset` and names its `appendices` without their text; `source` (`mirror` / `live`) reports provenance
|
|
101
83
|
|
|
102
84
|
---
|
|
103
85
|
|
|
104
86
|
### `regulations_get_docket` <sub>tool</sub> · key required
|
|
105
87
|
|
|
106
|
-
-
|
|
107
|
-
- `
|
|
108
|
-
- `per_page` 5–250 (default 25), `page` 1–20 — Regulations.gov caps a query at 5,000 records
|
|
109
|
-
- Each document's `objectId` chains into `regulations_find_comments`; `frDocNum` chains back to `regulations_get_document`
|
|
110
|
-
- `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
|
|
88
|
+
- `docket_id`, the Regulations.gov docket ID (e.g. `EPA-HQ-OAR-2025-0194`) — `regulationsGovDocketId` on a search or open-comments row, `docketId` from `regulations_get_document`, not a printed `docketIds` entry; `document_types` filters to `Proposed Rule`, `Rule`, `Notice`, `Supporting & Related Material`, or `Other`; `per_page` 5–250 (default 25), `page` 1–40
|
|
89
|
+
- Returns the docket's metadata, `documentCount`, and `documents[]`, each with an `objectId` for `regulations_find_comments`, a `frDocNum` back to `regulations_get_document`, and `commentPeriodOpen` as Regulations.gov reports it. `totalPages` and `nextPage` page the documents; Regulations.gov serves 40 pages, so `truncated` marks a docket larger than 40 × `per_page` (10,000 at 250)
|
|
111
90
|
|
|
112
91
|
---
|
|
113
92
|
|
|
114
93
|
### `regulations_find_comments` <sub>tool</sub> · key required
|
|
115
94
|
|
|
116
|
-
-
|
|
117
|
-
- `
|
|
118
|
-
-
|
|
119
|
-
- `
|
|
120
|
-
- `auth_required` / `not_found` / `rate_limited` (429, 1,000 req/hr) / `upstream_unavailable` round out the errors
|
|
95
|
+
- Exactly one of `docket_id`, `document_object_id`, `fr_document_number`, or `comment_id`; list scopes take `per_page` 5–250 (default 25) and `page` 1–40
|
|
96
|
+
- `document_object_id` takes an object ID (`0900006485883ec6`) or a document ID (`EPA-HQ-OW-2022-0114-0027`). `fr_document_number` resolves to the Regulations.gov document carrying that exact number and answers `not_found` when none does
|
|
97
|
+
- Lists narrow by comment text with `search_term`, and each hit then carries `highlightedContent`, the matched passages. `posted_after` / `posted_before` set an inclusive posted-date window. A backwards window fails with `date_range_inverted`, and filters passed with `comment_id` fail with `filter_requires_list_mode`
|
|
98
|
+
- `mode` is `list` or `detail`. Lists return comment summaries without body text. `comment_id` returns the body, submitter, `receivedDate`, `postmarkDate`, `duplicateComments` (above 1 marks a mass-mail campaign record), and `attachments`, with `attachmentOnly: true` when the substance is in a PDF/DOCX file. Lists carry `totalPages` and `nextPage`; `truncated` marks a set larger than the 40 pages Regulations.gov serves reach (40 × `per_page`, 10,000 at 250), and posted-date windows take it one slice at a time
|
|
121
99
|
|
|
122
100
|
---
|
|
123
101
|
|
|
124
102
|
### `regulations_list_open_comments` <sub>tool</sub>
|
|
125
103
|
|
|
126
|
-
-
|
|
127
|
-
- `
|
|
128
|
-
-
|
|
129
|
-
- Each row carries `daysRemaining`, `documentNumber` (→ `regulations_get_document`), `agencies` as `{ name, slug }`, and `docketIds` (→ `regulations_find_comments`)
|
|
130
|
-
- 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
|
|
131
|
-
- `invalid_filter` when the Federal Register rejects a filter value, naming the parameter; `upstream_unavailable` on a 5xx/timeout
|
|
104
|
+
- Optional `query`, `type` (`PRORULE`, `RULE`, `NOTICE`; default `["PRORULE", "RULE"]`), `agencies` (Federal Register slugs), and `closing_before`; `per_page` 1–100 (default 20)
|
|
105
|
+
- Rows sort by closing date, soonest first, and carry `commentsCloseOn`, `daysRemaining`, `documentNumber`, the printed `docketIds`, `regulationsGovDocketId` and `regulationsGovDocumentId`, `commentCount`, and `commentUrl`. `asOf` is today's date in Eastern time, so a document stays listed through its close day. `totalPages` and `nextPage` page the window; `truncated` means the Federal Register's 10,000-document limit was reached
|
|
106
|
+
- Keyless. `keyed` reports whether `REGULATIONS_GOV_API_KEY` is set, which `regulations_get_docket` and `regulations_find_comments` need to follow up on a row
|
|
132
107
|
|
|
133
108
|
---
|
|
134
109
|
|
|
135
110
|
### `regulations://document/{documentNumber}` <sub>resource</sub>
|
|
136
111
|
|
|
137
|
-
-
|
|
138
|
-
- `
|
|
139
|
-
- `not_found` / `upstream_unavailable` mirror the tool's errors
|
|
112
|
+
- `documentNumber` in any Federal Register form (`2024-07773`, `98-1572`, `E9-25990`); the payload is `regulations_get_document`'s without the body text
|
|
113
|
+
- `docketId`, `regulationsGovDocumentId`, the printed `docketIds`, `cfrReferences`, `citation`, `commentCount`, and `commentUrl` chain into the search, comment, and CFR tools
|
|
140
114
|
|
|
141
115
|
---
|
|
142
116
|
|
|
143
117
|
### `regulations://cfr/{title}/{part}/{section}` <sub>resource</sub>
|
|
144
118
|
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
- Mirror-backed with a live eCFR fallback; `source` (`mirror`/`live`) reports provenance
|
|
148
|
-
- `not_found` / `upstream_unavailable` mirror the tool's errors
|
|
119
|
+
- Sections only, at the current date and the tool's default 64,000-character window; the `section` segment resolves the way the tool resolves it (`"61"`, `"§ 141.61"`)
|
|
120
|
+
- `source` (`mirror` / `live`) reports provenance. `bodyTextNextOffset` marks a section longer than the window; read the rest through `regulations_get_cfr_section` with `offset`
|
|
149
121
|
|
|
150
122
|
## Features
|
|
151
123
|
|
|
@@ -153,18 +125,17 @@ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): s
|
|
|
153
125
|
|
|
154
126
|
Federal Register / eCFR / Regulations.gov-specific:
|
|
155
127
|
|
|
156
|
-
- One workflow over three official sources
|
|
157
|
-
- Cross-source stitching
|
|
158
|
-
- Keyless core
|
|
159
|
-
- Locally mirrored codified CFR
|
|
160
|
-
- A 45-second
|
|
128
|
+
- One workflow over three official sources: the agent calls regulatory verbs (`search_rules`, `get_cfr_section`, `find_comments`) rather than three API clients
|
|
129
|
+
- Cross-source stitching: every Federal Register document surfaces its docket ID and CFR-part handles, which feed the proposal → comments → final rule → codified text trace
|
|
130
|
+
- Keyless core: the five Federal Register and eCFR tools need no key. `regulations_get_docket` and `regulations_find_comments` need `REGULATIONS_GOV_API_KEY` (free at [api.data.gov/signup](https://api.data.gov/signup/), 1,000 requests/hour) and fail with `auth_required`, naming the variable and signup URL, when it is missing or rejected
|
|
131
|
+
- Locally mirrored codified CFR: the eCFR syncs into embedded SQLite + FTS5 for exact-cite reads and full-text search, and falls back to the live API for historical dates, whole parts, appendices, and titles outside the mirror
|
|
132
|
+
- A 45-second budget per request, shared by every upstream call, retry, and backoff, so a stalled source answers with `upstream_unavailable` inside a client's timeout
|
|
161
133
|
|
|
162
134
|
Agent-friendly output:
|
|
163
135
|
|
|
164
|
-
- Provenance
|
|
165
|
-
- Honest
|
|
166
|
-
- Attachment-aware comments
|
|
167
|
-
- Actionable `auth_required` errors — the two keyed tools name the env var and free signup URL rather than passing through a raw 401/403
|
|
136
|
+
- Provenance: `source: "mirror" | "live"` on every CFR read, plus a `sourceScope` line on search naming what the corpus covers
|
|
137
|
+
- Honest paging: `totalPages` and `nextPage` say how far a list goes, a page past the end names the last page instead of reading as "nothing matched", and matches beyond the Federal Register's 50 pages or Regulations.gov's 40 surface through `truncated` and `notice`, never as silently dropped rows
|
|
138
|
+
- Attachment-aware comments: `attachmentOnly` flags a comment whose substance is a file, with the download URLs
|
|
168
139
|
|
|
169
140
|
## Getting started
|
|
170
141
|
|
|
@@ -185,7 +156,7 @@ A public instance is available at `https://federal-regulations.caseyjhand.com/mc
|
|
|
185
156
|
|
|
186
157
|
### Self-Hosted / Local
|
|
187
158
|
|
|
188
|
-
Add the following to your MCP client configuration file.
|
|
159
|
+
Add the following to your MCP client configuration file. `REGULATIONS_GOV_API_KEY` enables the docket and comment tools; omit it to run the keyless Federal Register and eCFR tools alone.
|
|
189
160
|
|
|
190
161
|
```json
|
|
191
162
|
{
|
|
@@ -242,8 +213,6 @@ Or with Docker:
|
|
|
242
213
|
}
|
|
243
214
|
```
|
|
244
215
|
|
|
245
|
-
> Omit the `REGULATIONS_GOV_API_KEY` line entirely to run the keyless core (Federal Register + eCFR). The two Regulations.gov tools then return an actionable `auth_required` error, and `regulations_list_open_comments` runs without comment counts.
|
|
246
|
-
|
|
247
216
|
For Streamable HTTP, set the transport and start the server:
|
|
248
217
|
|
|
249
218
|
```sh
|
|
@@ -253,8 +222,8 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
|
|
|
253
222
|
|
|
254
223
|
### Prerequisites
|
|
255
224
|
|
|
256
|
-
- [Bun v1.
|
|
257
|
-
- Optional: a free [api.data.gov key](https://api.data.gov/signup/) for the Regulations.gov tools
|
|
225
|
+
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
226
|
+
- Optional: a free [api.data.gov key](https://api.data.gov/signup/) for the Regulations.gov tools.
|
|
258
227
|
|
|
259
228
|
### Installation
|
|
260
229
|
|
|
@@ -285,23 +254,21 @@ cp .env.example .env
|
|
|
285
254
|
|
|
286
255
|
## Configuration
|
|
287
256
|
|
|
288
|
-
All configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:
|
|
289
|
-
|
|
290
257
|
| Variable | Description | Default |
|
|
291
258
|
|:---|:---|:---|
|
|
292
|
-
| `REGULATIONS_GOV_API_KEY` |
|
|
259
|
+
| `REGULATIONS_GOV_API_KEY` | api.data.gov key for `regulations_get_docket` and `regulations_find_comments`. | none |
|
|
293
260
|
| `FEDERAL_REGISTER_BASE_URL` | Federal Register API v1 base URL. | `https://www.federalregister.gov/api/v1` |
|
|
294
261
|
| `ECFR_BASE_URL` | eCFR API base URL. | `https://www.ecfr.gov/api` |
|
|
295
262
|
| `REGULATIONS_GOV_BASE_URL` | Regulations.gov API v4 base URL. | `https://api.regulations.gov/v4` |
|
|
296
|
-
| `ECFR_MIRROR_PATH` |
|
|
297
|
-
| `ECFR_MIRROR_REFRESH_CRON` | Cron expression for an in-process mirror refresh (HTTP transport only), e.g. `0 4 * * 0`.
|
|
298
|
-
| `ECFR_MIRROR_TITLES` | Comma-separated CFR
|
|
263
|
+
| `ECFR_MIRROR_PATH` | Path to the eCFR SQLite mirror database. | `./data/ecfr-mirror.sqlite` |
|
|
264
|
+
| `ECFR_MIRROR_REFRESH_CRON` | Cron expression for an in-process mirror refresh (HTTP transport only), e.g. `0 4 * * 0`. Each run re-harvests every configured title in full. | none (no job) |
|
|
265
|
+
| `ECFR_MIRROR_TITLES` | Comma-separated CFR titles to mirror (e.g. `21,40`). Reads and searches outside the set, and all-titles searches, go to the live eCFR API. | all 50 titles |
|
|
299
266
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
300
|
-
| `MCP_HTTP_PORT` |
|
|
301
|
-
| `MCP_AUTH_MODE` |
|
|
302
|
-
| `MCP_LOG_LEVEL` | Log level (
|
|
303
|
-
| `STORAGE_PROVIDER_TYPE` | Storage backend
|
|
304
|
-
| `OTEL_ENABLED` | Enable [OpenTelemetry
|
|
267
|
+
| `MCP_HTTP_PORT` | HTTP server port. | `3010` |
|
|
268
|
+
| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
|
|
269
|
+
| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.). | `info` |
|
|
270
|
+
| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1`. | `in-memory` |
|
|
271
|
+
| `OTEL_ENABLED` | Enable [OpenTelemetry](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
|
|
305
272
|
|
|
306
273
|
See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
307
274
|
|
|
@@ -321,10 +288,10 @@ See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
|
321
288
|
bun run start:http
|
|
322
289
|
```
|
|
323
290
|
|
|
324
|
-
- **
|
|
291
|
+
- **Build or refresh the eCFR mirror** (out of band and idempotent; the CFR tools use the live eCFR API until the first build completes):
|
|
325
292
|
|
|
326
293
|
```sh
|
|
327
|
-
bun run mirror:init # full build
|
|
294
|
+
bun run mirror:init # full build, resumable (scope with ECFR_MIRROR_TITLES)
|
|
328
295
|
bun run mirror:refresh # re-harvest every configured title against the latest eCFR issues
|
|
329
296
|
bun run mirror:verify # report row counts and the last-synced issue date
|
|
330
297
|
```
|
|
@@ -334,7 +301,6 @@ See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
|
334
301
|
```sh
|
|
335
302
|
bun run devcheck # Lint, format, typecheck, security, changelog sync
|
|
336
303
|
bun run test # Vitest test suite
|
|
337
|
-
bun run lint:mcp # Validate MCP definitions against the linter rules
|
|
338
304
|
```
|
|
339
305
|
|
|
340
306
|
### Docker
|
|
@@ -344,20 +310,21 @@ docker build -t federal-regulations-mcp-server .
|
|
|
344
310
|
docker run --rm -e REGULATIONS_GOV_API_KEY=your-key -p 3010:3010 federal-regulations-mcp-server
|
|
345
311
|
```
|
|
346
312
|
|
|
347
|
-
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/federal-regulations-mcp-server`.
|
|
313
|
+
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/federal-regulations-mcp-server`. Build the mirror inside a running container with `docker exec <container> bun run mirror:init`, and mount a volume over `/usr/src/app/data` so it survives recreation. OpenTelemetry peer dependencies are installed by default; build with `--build-arg OTEL_ENABLED=false` to omit them.
|
|
348
314
|
|
|
349
315
|
## Project structure
|
|
350
316
|
|
|
351
317
|
| Directory | Purpose |
|
|
352
318
|
|:---|:---|
|
|
353
|
-
| `src/index.ts` | `createApp()` entry point
|
|
319
|
+
| `src/index.ts` | `createApp()` entry point: registers tools and resources, inits the three services, and schedules the mirror refresh when `ECFR_MIRROR_REFRESH_CRON` is set. |
|
|
354
320
|
| `src/config` | Server-specific environment variable parsing and validation with Zod. |
|
|
355
|
-
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`)
|
|
356
|
-
| `src/mcp-server/resources` | Resource definitions (`*.resource.ts`)
|
|
321
|
+
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`), the seven `regulations_*` tools. |
|
|
322
|
+
| `src/mcp-server/resources` | Resource definitions (`*.resource.ts`), the document and CFR-section resources. |
|
|
357
323
|
| `src/services/federal-register` | Federal Register API v1 client (keyless). |
|
|
358
|
-
| `src/services/ecfr` | eCFR API client (keyless)
|
|
359
|
-
| `src/services/ecfr-mirror` | eCFR codified-text mirror (
|
|
360
|
-
| `src/services/regulations-gov` | Regulations.gov v4 client (`X-Api-Key`)
|
|
324
|
+
| `src/services/ecfr` | eCFR API client (keyless): versioner, structure, search, section XML parsing, and cite resolution. |
|
|
325
|
+
| `src/services/ecfr-mirror` | eCFR codified-text mirror (SQLite + FTS5) and the opt-in refresh job. |
|
|
326
|
+
| `src/services/regulations-gov` | Regulations.gov v4 client (`X-Api-Key`): dockets and comments. |
|
|
327
|
+
| `src/services` | Shared request budget, text windowing, character-reference decoding, and upstream-failure handling. |
|
|
361
328
|
| `scripts/ecfr-mirror-*.ts` | Out-of-band mirror lifecycle: `init`, `refresh`, `verify`. |
|
|
362
329
|
| `tests/` | Unit and integration tests mirroring `src/`. |
|
|
363
330
|
|
|
@@ -372,7 +339,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
|
|
|
372
339
|
|
|
373
340
|
## Data disclaimer
|
|
374
341
|
|
|
375
|
-
eCFR content is "authoritative but unofficial" per the Office of the Federal Register
|
|
342
|
+
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.
|
|
376
343
|
|
|
377
344
|
## Contributing
|
|
378
345
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "regulations_find_comments gains text and posted-date filters and resolves documents by exact FR number or document ID; list tools share one paging contract to each upstream's real page ceiling; older FR document numbers are accepted."
|
|
3
|
+
breaking: true
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.5.0 — 2026-09-25
|
|
8
|
+
|
|
9
|
+
Breaking: five inputs that previously succeeded now fail.
|
|
10
|
+
|
|
11
|
+
1. `regulations_search_rules` with `published_after` later than `published_before` is `date_range_inverted` (previously an empty success).
|
|
12
|
+
2. `regulations_find_comments` `fr_document_number` must match the Federal Register number pattern. A Regulations.gov document ID, which previously worked through a text-search fallback, belongs in `document_object_id`.
|
|
13
|
+
3. An `fr_document_number` that no Regulations.gov document carries is `not_found` (previously resolved to the first document whose text quoted the number, and listed that document's comments).
|
|
14
|
+
4. A `document_object_id` that is neither a 16-hex object ID nor a resolvable Regulations.gov document ID is `not_found` (previously a 0-comment success).
|
|
15
|
+
5. A `docket_id` with a character no Regulations.gov ID contains (anything but letters, digits, `-`, and `_`) is `not_found` (previously a 0-comment success).
|
|
16
|
+
|
|
17
|
+
## Added
|
|
18
|
+
|
|
19
|
+
- **`regulations_find_comments` list filters**: `search_term` (`filter[searchTerm]`, stemmed) and inclusive `posted_after` / `posted_before` (`filter[postedDate][ge|le]`). Search hits carry `highlightedContent`, the matched passages as plain text [#39](https://github.com/cyanheads/federal-regulations-mcp-server/issues/39).
|
|
20
|
+
- **`filter_requires_list_mode`** and **`date_range_inverted`** on `regulations_find_comments`: a filter alongside `comment_id`, or an inverted posted window, fails before any request [#39](https://github.com/cyanheads/federal-regulations-mcp-server/issues/39).
|
|
21
|
+
- **`regulations_find_comments` detail mode** adds `postmarkDate` and `duplicateComments`; a count above 1 renders as a mass-mail campaign record in `content[]` [#33](https://github.com/cyanheads/federal-regulations-mcp-server/issues/33).
|
|
22
|
+
- **`totalPages`, `nextPage`, and `cap`** enrichment on `regulations_search_rules`, `regulations_get_docket`, and `regulations_find_comments` list mode, matching `regulations_list_open_comments` [#48](https://github.com/cyanheads/federal-regulations-mcp-server/issues/48).
|
|
23
|
+
- **`date_range_inverted`** on `regulations_search_rules` for an inverted `published_after` / `published_before` window [#48](https://github.com/cyanheads/federal-regulations-mcp-server/issues/48).
|
|
24
|
+
|
|
25
|
+
## Changed
|
|
26
|
+
|
|
27
|
+
- **`page`** on `regulations_get_docket` and `regulations_find_comments` accepts 1–40, the last page Regulations.gov serves (was 1–20) [#43](https://github.com/cyanheads/federal-regulations-mcp-server/issues/43).
|
|
28
|
+
- **`truncated`** now means matches lie past the last reachable page (`50 × per_page` on the Federal Register, `40 × per_page` on Regulations.gov), set on every page of such a set; the notice names a `per_page` that reaches them when one exists [#48](https://github.com/cyanheads/federal-regulations-mcp-server/issues/48).
|
|
29
|
+
- **A page past the end** returns a notice naming the last page instead of "nothing matched" advice [#48](https://github.com/cyanheads/federal-regulations-mcp-server/issues/48).
|
|
30
|
+
- **`regulations_find_comments` `document_object_id`** takes a document ID (`EPA-HQ-OW-2022-0114-0027`) as well as an object ID, resolving it through `/documents/{id}`; an object ID is matched in either case and sent lowercase [#34](https://github.com/cyanheads/federal-regulations-mcp-server/issues/34).
|
|
31
|
+
- **`regulations_find_comments` `fr_document_number`** resolves through the exact-match `filter[frDocNum]` and answers `not_found` when no document carries the number; an empty result on a resolved document names its docket to widen to [#34](https://github.com/cyanheads/federal-regulations-mcp-server/issues/34).
|
|
32
|
+
- **`regulations_find_comments` targets** are trimmed, and a value with characters no Regulations.gov ID contains answers `not_found` without a request [#34](https://github.com/cyanheads/federal-regulations-mcp-server/issues/34).
|
|
33
|
+
|
|
34
|
+
## Fixed
|
|
35
|
+
|
|
36
|
+
- **`regulations_find_comments` `receivedDate`** reads Regulations.gov's `receiveDate` and is no longer always null [#33](https://github.com/cyanheads/federal-regulations-mcp-server/issues/33).
|
|
37
|
+
- **`regulations_get_document` and `regulations://document/{documentNumber}`** accept every Federal Register number shape (`98-1572`, `94-31556-2`, `E9-25990`, `C1-2009-30484`), uppercasing a letter prefix before the request [#40](https://github.com/cyanheads/federal-regulations-mcp-server/issues/40).
|
|
38
|
+
- **Character references in comment bodies and eCFR text** decode through one shared decoder: every HTML-standard name, numeric references outside Unicode scalar values left as written instead of throwing `RangeError`, and `&constructor;`-style names kept literal [#54](https://github.com/cyanheads/federal-regulations-mcp-server/issues/54), [#56](https://github.com/cyanheads/federal-regulations-mcp-server/issues/56).
|
|
39
|
+
- **Comment HTML stripping** removes only what HTML reads as a tag (`<` then a letter, `/`, `!`, or `?`, closed before the next `<`), so text like "levels < 4 ppt and > 2 ppt" is kept instead of eaten up to the next `>`; a body of unclosed `<` runs in linear time; non-breaking spaces collapse like other spaces [#54](https://github.com/cyanheads/federal-regulations-mcp-server/issues/54).
|
|
40
|
+
|
|
41
|
+
## Dependencies
|
|
42
|
+
|
|
43
|
+
- `entities` added: `^8.1.0` (character-reference decoding).
|
|
44
|
+
- `@socketsecurity/bun-security-scanner` `^1.1.2 → ^1.1.3` (dev).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "regulations_search_rules filters by CFR part, printed docket number, and RIN and resolves a CFR source-note cite to its document; Federal Register rows carry keyless Regulations.gov IDs, comment counts, and comment URLs, plus whether the comment period is open."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.5.1 — 2026-09-25
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`regulations_search_rules` filters `cfr_title`, `cfr_part`, `docket_id`, and `rin`**, ANDed with the rest. `cfr_part` takes a part or a range (`"140-143"`) and needs `cfr_title` (`title_required_for_part`); `docket_id` matches the docket numbers the Federal Register prints [#47](https://github.com/cyanheads/federal-regulations-mcp-server/issues/47).
|
|
12
|
+
- **`citation` + `citation_date` on `regulations_search_rules`** resolve a page cite (`"89 FR 49102"`, `2024-06-11`) to the documents printed on that page, volume 59 (1994) on. `citation_incomplete` and `citation_out_of_range` reject a bad pairing before any request; an empty result reports that day's page span [#46](https://github.com/cyanheads/federal-regulations-mcp-server/issues/46).
|
|
13
|
+
- **`citation`, `startPage`, `endPage`** on `regulations_search_rules` rows and `regulations_get_document`, null where the Federal Register records no pages [#46](https://github.com/cyanheads/federal-regulations-mcp-server/issues/46).
|
|
14
|
+
- **`regulationsGovDocketId`, `regulationsGovDocumentId`, `commentCount`, `commentUrl`** on `regulations_search_rules` and `regulations_list_open_comments` rows, read from the Federal Register with no key; `regulations_get_document` adds `commentUrl` and the printed `docketIds` [#49](https://github.com/cyanheads/federal-regulations-mcp-server/issues/49).
|
|
15
|
+
- **`commentPeriodOpen`** on `regulations_search_rules` rows and `regulations_get_document` (true through 11:59 PM Eastern on `commentsCloseOn`, null with no close date), and on `regulations_get_docket` documents, relayed from Regulations.gov's `openForComment` [#50](https://github.com/cyanheads/federal-regulations-mcp-server/issues/50).
|
|
16
|
+
|
|
17
|
+
## Changed
|
|
18
|
+
|
|
19
|
+
- **`regulations_list_open_comments`** fills `commentCount` without `REGULATIONS_GOV_API_KEY` and drops the "counts unavailable" notice; `keyed` now reports whether `regulations_get_docket` / `regulations_find_comments` can follow up on a row [#49](https://github.com/cyanheads/federal-regulations-mcp-server/issues/49).
|
|
20
|
+
- **`docketIds`** is described as the Federal Register's printed docket numbers. `regulations_get_docket`, `regulations_find_comments`, and the `find_comments` empty-docket notice point at `regulationsGovDocketId` instead [#49](https://github.com/cyanheads/federal-regulations-mcp-server/issues/49).
|
|
21
|
+
- **`regulations_get_cfr_section` `bodyText`** names `citation` + `citation_date` as the path from a source-note cite to its rulemaking [#46](https://github.com/cyanheads/federal-regulations-mcp-server/issues/46).
|
|
22
|
+
|
|
23
|
+
## Fixed
|
|
24
|
+
|
|
25
|
+
- **`commentsCloseOn` and `commentEndDate`** no longer read as "still open for comment": the first is the inclusive Eastern close date, the second the UTC instant the period ends [#50](https://github.com/cyanheads/federal-regulations-mcp-server/issues/50).
|
|
26
|
+
- **`regulations_list_open_comments` `asOf`** is today's Eastern date, so documents closing today stay listed after 8 PM EDT [#50](https://github.com/cyanheads/federal-regulations-mcp-server/issues/50).
|
|
27
|
+
- **`cfrReferences`** keeps parts the Federal Register serves as numbers, which most documents before 2020 do; they were dropped [#47](https://github.com/cyanheads/federal-regulations-mcp-server/issues/47).
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ await createApp({
|
|
|
31
31
|
tools: allToolDefinitions,
|
|
32
32
|
resources: allResourceDefinitions,
|
|
33
33
|
prompts: [],
|
|
34
|
-
instructions:
|
|
34
|
+
instructions: "US federal regulatory law over three official sources. The Federal Register (regulations_search_rules, regulations_get_document, regulations_list_open_comments) and eCFR (regulations_browse_cfr, regulations_get_cfr_section) tools are keyless. The Regulations.gov tools (regulations_get_docket, regulations_find_comments) need REGULATIONS_GOV_API_KEY (free at https://api.data.gov/signup/) and return an actionable auth_required error without it; comment counts and Regulations.gov IDs on the Federal Register tools need no key. Trace a rule end to end: search_rules → get_document (yields docket ID + CFR parts) → find_comments → get_cfr_section, and back from a section's source-note cite with search_rules citation + citation_date.",
|
|
35
35
|
setup(core) {
|
|
36
36
|
initFederalRegisterService(core.config, core.storage);
|
|
37
37
|
initEcfrService(core.config, core.storage);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AACjG,OAAO,EAAE,0BAA0B,EAAE,MAAM,yDAAyD,CAAC;AACrG,OAAO,EAAE,yBAAyB,EAAE,MAAM,uDAAuD,CAAC;AAElG,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,gCAAgC;IACtC,KAAK,EAAE,gCAAgC;IACvC,+EAA+E;IAC/E,wEAAwE;IACxE,yEAAyE;IACzE,2EAA2E;IAC3E,wEAAwE;IACxE,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE,kBAAkB;IACzB,SAAS,EAAE,sBAAsB;IACjC,OAAO,EAAE,EAAE;IACX,YAAY,EACV
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AACjG,OAAO,EAAE,0BAA0B,EAAE,MAAM,yDAAyD,CAAC;AACrG,OAAO,EAAE,yBAAyB,EAAE,MAAM,uDAAuD,CAAC;AAElG,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,gCAAgC;IACtC,KAAK,EAAE,gCAAgC;IACvC,+EAA+E;IAC/E,wEAAwE;IACxE,yEAAyE;IACzE,2EAA2E;IAC3E,wEAAwE;IACxE,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE,kBAAkB;IACzB,SAAS,EAAE,sBAAsB;IACjC,OAAO,EAAE,EAAE;IACX,YAAY,EACV,+tBAA+tB;IACjuB,KAAK,CAAC,IAAI;QACR,0BAA0B,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACtD,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,yBAAyB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACrD,uEAAuE;QACvE,KAAK,qBAAqB,CACxB,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAC5B,eAAe,EAAE,CAAC,qBAAqB,CACxC,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,QAAQ;QACZ,MAAM,iBAAiB,EAAE,CAAC;QAC1B,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -13,7 +13,7 @@ export declare const documentResource: import("@cyanheads/mcp-ts-core").Resource
|
|
|
13
13
|
readonly reason: "not_found";
|
|
14
14
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
15
15
|
readonly when: "No Federal Register document exists with that number.";
|
|
16
|
-
readonly recovery: "Verify the number via regulations_search_rules; FR numbers look like \"
|
|
16
|
+
readonly recovery: "Verify the number via regulations_search_rules; FR numbers look like \"2024-07773\", or \"98-1572\" and \"E9-25990\" before 2010.";
|
|
17
17
|
}, {
|
|
18
18
|
readonly reason: "upstream_unavailable";
|
|
19
19
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/document.resource.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"document.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/document.resource.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAOjE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;GAqD3B,CAAC"}
|
|
@@ -7,24 +7,27 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
9
9
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
10
|
+
import { frDocumentNumber, normalizeFrDocumentNumber, } from '../../tools/definitions/document-number.js';
|
|
10
11
|
import { getFederalRegisterService } from '../../../services/federal-register/federal-register-service.js';
|
|
11
12
|
export const documentResource = resource('regulations://document/{documentNumber}', {
|
|
12
13
|
name: 'federal-register-document',
|
|
13
14
|
title: 'Federal Register document',
|
|
14
|
-
description: 'A single Federal Register document by FR number: metadata
|
|
15
|
+
description: 'A single Federal Register document by FR number: metadata, citation and pages, and the cross-source handles (Regulations.gov docket and document IDs, printed docket numbers, affected CFR parts, comment count and comment URL) that chain into the search, comment, and codified-text tools. Mirrors regulations_get_document (full text omitted).',
|
|
15
16
|
mimeType: 'application/json',
|
|
17
|
+
/**
|
|
18
|
+
* Validated as the template hands it over: the pattern admits only unreserved
|
|
19
|
+
* URI characters, which RFC 6570 expansion never percent-encodes, so a valid
|
|
20
|
+
* number arrives exactly as written.
|
|
21
|
+
*/
|
|
16
22
|
params: z.object({
|
|
17
|
-
documentNumber:
|
|
18
|
-
.string()
|
|
19
|
-
.regex(/^[0-9]{4}-[0-9]+$/)
|
|
20
|
-
.describe('Federal Register document number (e.g. "2025-14555").'),
|
|
23
|
+
documentNumber: frDocumentNumber().describe('Federal Register document number: "2024-07773" from 2010 on, older and correction numbers like "98-1572", "E9-25990", or "C1-2009-30484".'),
|
|
21
24
|
}),
|
|
22
25
|
errors: [
|
|
23
26
|
{
|
|
24
27
|
reason: 'not_found',
|
|
25
28
|
code: JsonRpcErrorCode.NotFound,
|
|
26
29
|
when: 'No Federal Register document exists with that number.',
|
|
27
|
-
recovery: 'Verify the number via regulations_search_rules; FR numbers look like "
|
|
30
|
+
recovery: 'Verify the number via regulations_search_rules; FR numbers look like "2024-07773", or "98-1572" and "E9-25990" before 2010.',
|
|
28
31
|
},
|
|
29
32
|
{
|
|
30
33
|
reason: 'upstream_unavailable',
|
|
@@ -35,7 +38,7 @@ export const documentResource = resource('regulations://document/{documentNumber
|
|
|
35
38
|
],
|
|
36
39
|
async handler(params, ctx) {
|
|
37
40
|
const service = getFederalRegisterService();
|
|
38
|
-
const detail = await service.getDocument(params.documentNumber, undefined, ctx);
|
|
41
|
+
const detail = await service.getDocument(normalizeFrDocumentNumber(params.documentNumber), undefined, ctx);
|
|
39
42
|
// Drop the (absent) fullText field; the resource omits the body by contract.
|
|
40
43
|
const { fullText: _omit, ...rest } = detail;
|
|
41
44
|
return rest;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/document.resource.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yDAAyD,CAAC;AAEpG,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CAAC,yCAAyC,EAAE;IAClF,IAAI,EAAE,2BAA2B;IACjC,KAAK,EAAE,2BAA2B;IAClC,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"document.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/document.resource.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,yDAAyD,CAAC;AAEpG,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CAAC,yCAAyC,EAAE;IAClF,IAAI,EAAE,2BAA2B;IACjC,KAAK,EAAE,2BAA2B;IAClC,WAAW,EACT,sVAAsV;IACxV,QAAQ,EAAE,kBAAkB;IAC5B;;;;OAIG;IACH,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,cAAc,EAAE,gBAAgB,EAAE,CAAC,QAAQ,CACzC,2IAA2I,CAC5I;KACF,CAAC;IACF,MAAM,EAAE;QACN;YACE,MAAM,EAAE,WAAW;YACnB,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,uDAAuD;YAC7D,QAAQ,EACN,6HAA6H;SAChI;QACD;YACE,MAAM,EAAE,sBAAsB;YAC9B,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;YACzC,IAAI,EAAE,2EAA2E;YACjF,QAAQ,EAAE,6EAA6E;SACxF;KACF;IAED,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG;QACvB,MAAM,OAAO,GAAG,yBAAyB,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CACtC,yBAAyB,CAAC,MAAM,CAAC,cAAc,CAAC,EAChD,SAAS,EACT,GAAG,CACJ,CAAC;QACF,6EAA6E;QAC7E,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,SAAS,EAAE;YACT;gBACE,GAAG,EAAE,mCAAmC;gBACxC,IAAI,EAAE,mCAAmC;gBACzC,QAAQ,EAAE,kBAAkB;aAC7B;SACF;KACF,CAAC;CACH,CAAC,CAAC"}
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
* @module mcp-server/resources/definitions/index
|
|
4
4
|
*/
|
|
5
5
|
export declare const allResourceDefinitions: (import("@cyanheads/mcp-ts-core").ResourceDefinition<import("zod").ZodObject<{
|
|
6
|
+
documentNumber: import("zod").ZodString;
|
|
7
|
+
}, import("zod/v4/core").$strip>, undefined, readonly [{
|
|
8
|
+
readonly reason: "not_found";
|
|
9
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
10
|
+
readonly when: "No Federal Register document exists with that number.";
|
|
11
|
+
readonly recovery: "Verify the number via regulations_search_rules; FR numbers look like \"2024-07773\", or \"98-1572\" and \"E9-25990\" before 2010.";
|
|
12
|
+
}, {
|
|
13
|
+
readonly reason: "upstream_unavailable";
|
|
14
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
|
|
15
|
+
readonly when: "Federal Register returned a 5xx, timed out, or served an HTML error page.";
|
|
16
|
+
readonly recovery: "Retry after a brief wait; the Federal Register API may be momentarily down.";
|
|
17
|
+
}]> | import("@cyanheads/mcp-ts-core").ResourceDefinition<import("zod").ZodObject<{
|
|
6
18
|
title: import("zod").ZodString;
|
|
7
19
|
part: import("zod").ZodString;
|
|
8
20
|
section: import("zod").ZodString;
|
|
@@ -17,17 +29,5 @@ export declare const allResourceDefinitions: (import("@cyanheads/mcp-ts-core").R
|
|
|
17
29
|
readonly when: "eCFR returned a 5xx, timed out, or served an HTML error page (live path, mirror not ready or missing the section).";
|
|
18
30
|
readonly recovery: "Retry after a brief wait; the eCFR API may be momentarily unavailable.";
|
|
19
31
|
readonly thrownBy: "service";
|
|
20
|
-
}]> | import("@cyanheads/mcp-ts-core").ResourceDefinition<import("zod").ZodObject<{
|
|
21
|
-
documentNumber: import("zod").ZodString;
|
|
22
|
-
}, import("zod/v4/core").$strip>, undefined, readonly [{
|
|
23
|
-
readonly reason: "not_found";
|
|
24
|
-
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
25
|
-
readonly when: "No Federal Register document exists with that number.";
|
|
26
|
-
readonly recovery: "Verify the number via regulations_search_rules; FR numbers look like \"2025-14555\".";
|
|
27
|
-
}, {
|
|
28
|
-
readonly reason: "upstream_unavailable";
|
|
29
|
-
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
|
|
30
|
-
readonly when: "Federal Register returned a 5xx, timed out, or served an HTML error page.";
|
|
31
|
-
readonly recovery: "Retry after a brief wait; the Federal Register API may be momentarily down.";
|
|
32
32
|
}]>)[];
|
|
33
33
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browse-cfr.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/browse-cfr.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"browse-cfr.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/browse-cfr.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AA6HjE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyXxB,CAAC"}
|