@cyanheads/federal-regulations-mcp-server 0.5.0 → 0.5.2

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 (90) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/README.md +19 -16
  4. package/changelog/0.5.x/0.5.1.md +27 -0
  5. package/changelog/0.5.x/0.5.2.md +32 -0
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/mcp-server/resources/definitions/cfr-section.resource.d.ts +8 -2
  9. package/dist/mcp-server/resources/definitions/cfr-section.resource.d.ts.map +1 -1
  10. package/dist/mcp-server/resources/definitions/cfr-section.resource.js +21 -8
  11. package/dist/mcp-server/resources/definitions/cfr-section.resource.js.map +1 -1
  12. package/dist/mcp-server/resources/definitions/document.resource.js +1 -1
  13. package/dist/mcp-server/resources/definitions/document.resource.js.map +1 -1
  14. package/dist/mcp-server/resources/definitions/index.d.ts +5 -0
  15. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/browse-cfr.tool.d.ts +6 -4
  17. package/dist/mcp-server/tools/definitions/browse-cfr.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/browse-cfr.tool.js +46 -38
  19. package/dist/mcp-server/tools/definitions/browse-cfr.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/definitions/find-comments.tool.js +2 -2
  21. package/dist/mcp-server/tools/definitions/find-comments.tool.js.map +1 -1
  22. package/dist/mcp-server/tools/definitions/format-utils.d.ts +15 -0
  23. package/dist/mcp-server/tools/definitions/format-utils.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/format-utils.js +19 -0
  25. package/dist/mcp-server/tools/definitions/format-utils.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/fr-citation.d.ts +24 -0
  27. package/dist/mcp-server/tools/definitions/fr-citation.d.ts.map +1 -0
  28. package/dist/mcp-server/tools/definitions/fr-citation.js +29 -0
  29. package/dist/mcp-server/tools/definitions/fr-citation.js.map +1 -0
  30. package/dist/mcp-server/tools/definitions/get-cfr-section.tool.d.ts +15 -3
  31. package/dist/mcp-server/tools/definitions/get-cfr-section.tool.d.ts.map +1 -1
  32. package/dist/mcp-server/tools/definitions/get-cfr-section.tool.js +67 -13
  33. package/dist/mcp-server/tools/definitions/get-cfr-section.tool.js.map +1 -1
  34. package/dist/mcp-server/tools/definitions/get-docket.tool.d.ts +2 -1
  35. package/dist/mcp-server/tools/definitions/get-docket.tool.d.ts.map +1 -1
  36. package/dist/mcp-server/tools/definitions/get-docket.tool.js +9 -5
  37. package/dist/mcp-server/tools/definitions/get-docket.tool.js.map +1 -1
  38. package/dist/mcp-server/tools/definitions/get-document.tool.d.ts +11 -5
  39. package/dist/mcp-server/tools/definitions/get-document.tool.d.ts.map +1 -1
  40. package/dist/mcp-server/tools/definitions/get-document.tool.js +46 -14
  41. package/dist/mcp-server/tools/definitions/get-document.tool.js.map +1 -1
  42. package/dist/mcp-server/tools/definitions/index.d.ts +227 -178
  43. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  44. package/dist/mcp-server/tools/definitions/list-open-comments.tool.d.ts +6 -3
  45. package/dist/mcp-server/tools/definitions/list-open-comments.tool.d.ts.map +1 -1
  46. package/dist/mcp-server/tools/definitions/list-open-comments.tool.js +41 -31
  47. package/dist/mcp-server/tools/definitions/list-open-comments.tool.js.map +1 -1
  48. package/dist/mcp-server/tools/definitions/search-rules.tool.d.ts +34 -3
  49. package/dist/mcp-server/tools/definitions/search-rules.tool.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/definitions/search-rules.tool.js +206 -23
  51. package/dist/mcp-server/tools/definitions/search-rules.tool.js.map +1 -1
  52. package/dist/services/ecfr/cite.d.ts +26 -4
  53. package/dist/services/ecfr/cite.d.ts.map +1 -1
  54. package/dist/services/ecfr/cite.js +36 -4
  55. package/dist/services/ecfr/cite.js.map +1 -1
  56. package/dist/services/ecfr/ecfr-service.d.ts +42 -1
  57. package/dist/services/ecfr/ecfr-service.d.ts.map +1 -1
  58. package/dist/services/ecfr/ecfr-service.js +96 -16
  59. package/dist/services/ecfr/ecfr-service.js.map +1 -1
  60. package/dist/services/ecfr/read-section.d.ts +25 -10
  61. package/dist/services/ecfr/read-section.d.ts.map +1 -1
  62. package/dist/services/ecfr/read-section.js +57 -18
  63. package/dist/services/ecfr/read-section.js.map +1 -1
  64. package/dist/services/ecfr/types.d.ts +39 -1
  65. package/dist/services/ecfr/types.d.ts.map +1 -1
  66. package/dist/services/ecfr/xml.d.ts +22 -3
  67. package/dist/services/ecfr/xml.d.ts.map +1 -1
  68. package/dist/services/ecfr/xml.js +615 -89
  69. package/dist/services/ecfr/xml.js.map +1 -1
  70. package/dist/services/ecfr-mirror/ecfr-mirror.d.ts +16 -5
  71. package/dist/services/ecfr-mirror/ecfr-mirror.d.ts.map +1 -1
  72. package/dist/services/ecfr-mirror/ecfr-mirror.js +33 -17
  73. package/dist/services/ecfr-mirror/ecfr-mirror.js.map +1 -1
  74. package/dist/services/federal-register/comment-period.d.ts +17 -0
  75. package/dist/services/federal-register/comment-period.d.ts.map +1 -0
  76. package/dist/services/federal-register/comment-period.js +28 -0
  77. package/dist/services/federal-register/comment-period.js.map +1 -0
  78. package/dist/services/federal-register/federal-register-service.d.ts +20 -6
  79. package/dist/services/federal-register/federal-register-service.d.ts.map +1 -1
  80. package/dist/services/federal-register/federal-register-service.js +148 -35
  81. package/dist/services/federal-register/federal-register-service.js.map +1 -1
  82. package/dist/services/federal-register/types.d.ts +79 -15
  83. package/dist/services/federal-register/types.d.ts.map +1 -1
  84. package/dist/services/regulations-gov/regulations-gov-service.d.ts.map +1 -1
  85. package/dist/services/regulations-gov/regulations-gov-service.js +1 -0
  86. package/dist/services/regulations-gov/regulations-gov-service.js.map +1 -1
  87. package/dist/services/regulations-gov/types.d.ts +2 -0
  88. package/dist/services/regulations-gov/types.d.ts.map +1 -1
  89. package/package.json +2 -2
  90. 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.5.0
4
+ **Version:** 0.5.2
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.5.0
4
+ **Version:** 0.5.2
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
- [![Version](https://img.shields.io/badge/Version-0.5.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/)
10
+ [![Version](https://img.shields.io/badge/Version-0.5.2-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
 
@@ -35,8 +35,8 @@ US federal regulatory law from three official sources: the Federal Register (pro
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, and date range |
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 |
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) |
@@ -56,15 +56,16 @@ Both resources mirror a tool (`regulations_get_document`, `regulations_get_cfr_s
56
56
 
57
57
  ### `regulations_search_rules` <sub>tool</sub>
58
58
 
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), and `published_after` / `published_before`; `per_page` 2–100 (default 20), `page` 1–50
60
- - Each result carries `documentNumber`, `agencies[].slug`, `docketIds`, `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
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
61
62
 
62
63
  ---
63
64
 
64
65
  ### `regulations_get_document` <sub>tool</sub>
65
66
 
66
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
67
- - Returns metadata plus the handles other tools take: `docketId`, `regulationsGovDocumentId`, `commentCount`, and `cfrReferences`. With text, `fullTextLength` and `fullTextNextOffset` (present while text remains) page the body; a major final rule runs past a million characters
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
68
69
 
69
70
  ---
70
71
 
@@ -77,15 +78,17 @@ Both resources mirror a tool (`regulations_get_document`, `regulations_get_cfr_s
77
78
 
78
79
  ### `regulations_get_cfr_section` <sub>tool</sub>
79
80
 
80
- - 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
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 the `cfrCite` the server returns (`"40 CFR 141.61"`, `"14 CFR 241 § 25"`; a cite naming another title fails with `conflicting_title`), `part` takes `"Part 141"`, and `date` reads text from 2017-01-01 through the title's up-to-date date
81
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
83
+ - A whole-part read also returns the part's `heading`, `authority`, `sourceNote`, and `notes`, and each `sections[]` entry carries the Authority or Source its subpart or subject group states for it
84
+ - Superscripts read `^x` and subscripts `_x` (`3 × 10^−8`, `CO_{2}e`), footnote markers `[n]`, and diacritics and overlines are combining marks (`x̄`)
82
85
 
83
86
  ---
84
87
 
85
88
  ### `regulations_get_docket` <sub>tool</sub> · key required
86
89
 
87
- - `docket_id` (e.g. `EPA-HQ-OAR-2025-0194`); `document_types` filters to `Proposed Rule`, `Rule`, `Notice`, `Supporting & Related Material`, or `Other`; `per_page` 5–250 (default 25), `page` 1–40
88
- - Returns the docket's metadata, `documentCount`, and `documents[]`, each with an `objectId` for `regulations_find_comments` and a `frDocNum` back to `regulations_get_document`. `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)
90
+ - `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
91
+ - 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)
89
92
 
90
93
  ---
91
94
 
@@ -101,21 +104,21 @@ Both resources mirror a tool (`regulations_get_document`, `regulations_get_cfr_s
101
104
  ### `regulations_list_open_comments` <sub>tool</sub>
102
105
 
103
106
  - Optional `query`, `type` (`PRORULE`, `RULE`, `NOTICE`; default `["PRORULE", "RULE"]`), `agencies` (Federal Register slugs), and `closing_before`; `per_page` 1–100 (default 20)
104
- - Rows sort by closing date, soonest first, and carry `commentsCloseOn`, `daysRemaining`, `documentNumber`, and `docketIds`. `totalPages` and `nextPage` page the window; `truncated` means the Federal Register's 10,000-document limit was reached
105
- - Keyless. `keyed` reports whether `commentCount` was filled in, which happens only when `REGULATIONS_GOV_API_KEY` is set
107
+ - 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
108
+ - 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
106
109
 
107
110
  ---
108
111
 
109
112
  ### `regulations://document/{documentNumber}` <sub>resource</sub>
110
113
 
111
114
  - `documentNumber` in any Federal Register form (`2024-07773`, `98-1572`, `E9-25990`); the payload is `regulations_get_document`'s without the body text
112
- - `docketId`, `cfrReferences`, and `commentCount` chain into the comment and CFR tools
115
+ - `docketId`, `regulationsGovDocumentId`, the printed `docketIds`, `cfrReferences`, `citation`, `commentCount`, and `commentUrl` chain into the search, comment, and CFR tools
113
116
 
114
117
  ---
115
118
 
116
119
  ### `regulations://cfr/{title}/{part}/{section}` <sub>resource</sub>
117
120
 
118
- - 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"`)
121
+ - Sections only, at the current date and the tool's default 64,000-character window; the `part` and `section` segments resolve the way the tool resolves them (`"Part 141"`; `"61"`, `"§ 141.61"`, `"40 CFR 141.61"`)
119
122
  - `source` (`mirror` / `live`) reports provenance. `bodyTextNextOffset` marks a section longer than the window; read the rest through `regulations_get_cfr_section` with `offset`
120
123
 
121
124
  ## Features
@@ -127,7 +130,7 @@ Federal Register / eCFR / Regulations.gov-specific:
127
130
  - One workflow over three official sources: the agent calls regulatory verbs (`search_rules`, `get_cfr_section`, `find_comments`) rather than three API clients
128
131
  - 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
129
132
  - 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
130
- - 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
133
+ - 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, titles outside the mirror, and titles eCFR has re-issued since the mirror last synced them
131
134
  - 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
132
135
 
133
136
  Agent-friendly output:
@@ -222,7 +225,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
222
225
  ### Prerequisites
223
226
 
224
227
  - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
225
- - Optional: a free [api.data.gov key](https://api.data.gov/signup/) for the Regulations.gov tools and comment counts.
228
+ - Optional: a free [api.data.gov key](https://api.data.gov/signup/) for the Regulations.gov tools.
226
229
 
227
230
  ### Installation
228
231
 
@@ -255,7 +258,7 @@ cp .env.example .env
255
258
 
256
259
  | Variable | Description | Default |
257
260
  |:---|:---|:---|
258
- | `REGULATIONS_GOV_API_KEY` | api.data.gov key for `regulations_get_docket`, `regulations_find_comments`, and comment counts in `regulations_list_open_comments`. | none |
261
+ | `REGULATIONS_GOV_API_KEY` | api.data.gov key for `regulations_get_docket` and `regulations_find_comments`. | none |
259
262
  | `FEDERAL_REGISTER_BASE_URL` | Federal Register API v1 base URL. | `https://www.federalregister.gov/api/v1` |
260
263
  | `ECFR_BASE_URL` | eCFR API base URL. | `https://www.ecfr.gov/api` |
261
264
  | `REGULATIONS_GOV_BASE_URL` | Regulations.gov API v4 base URL. | `https://api.regulations.gov/v4` |
@@ -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).
@@ -0,0 +1,32 @@
1
+ ---
2
+ summary: "eCFR text renders superscripts, subscripts, footnote markers, and diacritics; whole-part reads return the part's heading, Authority, Source, and notes; the cfrCite the server returns reads back as section input; and a mirrored title behind eCFR's latest issue is read live. Existing mirrors need a mirror:refresh."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.5.2 — 2026-09-25
8
+
9
+ ## Added
10
+
11
+ - **`authority`, `sourceNote`, and `notes`** on whole-part `regulations_get_cfr_section` reads, and `authority` / `sourceNote` on each `sections[]` entry whose subpart or subject group states its own. Single-section and appendix reads carry none [#52](https://github.com/cyanheads/federal-regulations-mcp-server/issues/52).
12
+ - **Cite-form `section` and `part` inputs** on `regulations_get_cfr_section` and the `regulations://cfr/{title}/{part}/{section}` resource: `section` takes the `cfrCite` the server returns (`"40 CFR 141.61"`, `"40 C.F.R. 141.61"`, `"14 CFR 241 § 25"`) and `part` takes `"Part 141"`; `notice` names each rewrite [#53](https://github.com/cyanheads/federal-regulations-mcp-server/issues/53).
13
+ - **`conflicting_title`** (`ValidationError`) when a `section` cite names another title (`"21 CFR 141.61"` with `title: 40`), which answered `not_found` before.
14
+
15
+ ## Changed
16
+
17
+ - **`bodyText` renders inline markup**: superscripts `^x` and subscripts `_x` (`3 × 10^−8`, `CO_{2}e`), footnote markers `[n]`, `<AC>` diacritics and `<E T="7503">` overlines as combining marks (`x̄`, `R̅M̅`), no stray space after an inline element, and a section's own `<SECAUTH>` / `<APPRO>` kept [#51](https://github.com/cyanheads/federal-regulations-mcp-server/issues/51), [#60](https://github.com/cyanheads/federal-regulations-mcp-server/issues/60). `bodyTextLength`, `bodyTextNextOffset`, and `sections[].offset` values saved from 0.5.1 no longer line up.
18
+ - **Whole-part `heading`** is the part's own (`PART 141—NATIONAL PRIMARY DRINKING WATER REGULATIONS`), not `Part 141`.
19
+ - **Mirror `INGEST_VERSION` 3 → 4**: an existing mirror reads as stale and every read goes live until `bun run mirror:refresh` re-derives each held title.
20
+ - **Mirror freshness is per title**: current section reads and mirror searches use a title only while its `cfr_part_index.issue_date` is at eCFR's latest issue, and read it live otherwise. On a ready mirror these reads now need eCFR's titles document and fail `upstream_unavailable` when it can't be read [#55](https://github.com/cyanheads/federal-regulations-mcp-server/issues/55).
21
+ - **`regulations_browse_cfr` search** reports `date` on mirror results too: the titles document's index date.
22
+ - **`regulations_browse_cfr` structure mode** holds each title's parsed structure document by title and date (4 entries, 15 minutes), so paging a part costs one upstream read [#44](https://github.com/cyanheads/federal-regulations-mcp-server/issues/44).
23
+
24
+ ## Fixed
25
+
26
+ - **XML tag stripping, block extraction, and `isCompleteXmlDocument`** run in linear time on unclosed openers [#57](https://github.com/cyanheads/federal-regulations-mcp-server/issues/57), [#62](https://github.com/cyanheads/federal-regulations-mcp-server/issues/62). A literal `<` in a search excerpt now survives (`systems <500 service connections`).
27
+ - **Structure-mode `cfrCite` for dotless sections** names the part (`14 CFR 241 § 01`, not `14 CFR 01`) [#59](https://github.com/cyanheads/federal-regulations-mcp-server/issues/59).
28
+ - **Mirror ingest** writes a title's part index only after its section rows commit, so an aborted run never dates older rows as current.
29
+
30
+ ## Dependencies
31
+
32
+ - `@types/node` `26.6.1 → 26.6.2` (dev).
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ await createApp({
31
31
  tools: allToolDefinitions,
32
32
  resources: allResourceDefinitions,
33
33
  prompts: [],
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; regulations_list_open_comments runs keyless and only adds comment counts when the key is present. Trace a rule end to end: search_rules → get_document (yields docket ID + CFR parts) → find_comments → get_cfr_section.',
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,wpBAAwpB;IAC1pB,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"}
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"}
@@ -2,8 +2,9 @@
2
2
  * @fileoverview regulations://cfr/{title}/{part}/{section} — codified text of a
3
3
  * current CFR section, the same payload as regulations_get_cfr_section at the
4
4
  * current date and its default window, for clients that support injectable
5
- * context. Mirror-backed with a live eCFR fallback, and the section resolves the
6
- * way the tool resolves it ("61", "§ 141.61", "141.61(c)"). Every datum is also
5
+ * context. Mirror-backed with a live eCFR fallback, and the part and section
6
+ * resolve the way the tool resolves them ("Part 141"; "61", "§ 141.61",
7
+ * "141.61(c)", "40 CFR 141.61"). Every datum is also
7
8
  * reachable through the tool surface, which is where a section longer than one
8
9
  * window is paged: a resource read takes no offset.
9
10
  *
@@ -23,6 +24,11 @@ export declare const cfrSectionResource: import("@cyanheads/mcp-ts-core").Resour
23
24
  readonly code: JsonRpcErrorCode.NotFound;
24
25
  readonly when: "No such title/part/section in the current CFR, under the section as given or any form it resolves to.";
25
26
  readonly recovery: "Verify the cite with regulations_browse_cfr (structure mode) and pass the identifier it lists — a section is normally part.section (\"141.61\"). The part or section may not exist or may be reserved.";
27
+ }, {
28
+ readonly reason: "conflicting_title";
29
+ readonly code: JsonRpcErrorCode.ValidationError;
30
+ readonly when: "The section is written as a full cite (\"21 CFR 141.61\") naming a different title than the URI.";
31
+ readonly recovery: "Put the title the cite names in the URI, or pass the section without its title prefix (\"141.61\").";
26
32
  }, {
27
33
  readonly reason: "upstream_unavailable";
28
34
  readonly code: JsonRpcErrorCode.ServiceUnavailable;
@@ -1 +1 @@
1
- {"version":3,"file":"cfr-section.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/cfr-section.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAMjE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;GAqF7B,CAAC"}
1
+ {"version":3,"file":"cfr-section.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/cfr-section.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAMjE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;GAsG7B,CAAC"}
@@ -2,8 +2,9 @@
2
2
  * @fileoverview regulations://cfr/{title}/{part}/{section} — codified text of a
3
3
  * current CFR section, the same payload as regulations_get_cfr_section at the
4
4
  * current date and its default window, for clients that support injectable
5
- * context. Mirror-backed with a live eCFR fallback, and the section resolves the
6
- * way the tool resolves it ("61", "§ 141.61", "141.61(c)"). Every datum is also
5
+ * context. Mirror-backed with a live eCFR fallback, and the part and section
6
+ * resolve the way the tool resolves them ("Part 141"; "61", "§ 141.61",
7
+ * "141.61(c)", "40 CFR 141.61"). Every datum is also
7
8
  * reachable through the tool surface, which is where a section longer than one
8
9
  * window is paged: a resource read takes no offset.
9
10
  *
@@ -14,21 +15,21 @@
14
15
  */
15
16
  import { resource, z } from '@cyanheads/mcp-ts-core';
16
17
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
17
- import { sectionCite } from '../../../services/ecfr/cite.js';
18
+ import { describePartRewrite, normalizePart, sectionCite } from '../../../services/ecfr/cite.js';
18
19
  import { getEcfrService } from '../../../services/ecfr/ecfr-service.js';
19
- import { readSection } from '../../../services/ecfr/read-section.js';
20
+ import { citedTitle, readSection } from '../../../services/ecfr/read-section.js';
20
21
  import { DEFAULT_WINDOW_CHARS, windowText } from '../../../services/text-window.js';
21
22
  export const cfrSectionResource = resource('regulations://cfr/{title}/{part}/{section}', {
22
23
  name: 'cfr-section',
23
24
  title: 'CFR section (current)',
24
- description: 'Codified text of a current CFR section by title/part/section (e.g. regulations://cfr/40/50/50.1). Mirrors regulations_get_cfr_section at the current date and its default 64,000-character window — mirror-backed with a live eCFR fallback. The section resolves as the tool resolves it ("61", "§ 141.61", "141.61(c)"); `section` names the identifier read and `notice` says how. When bodyTextNextOffset is present the section runs past the window — read on through regulations_get_cfr_section with that offset. Sections only; read an appendix through regulations_get_cfr_section with its `appendix` input.',
25
+ description: 'Codified text of a current CFR section by title/part/section (e.g. regulations://cfr/40/50/50.1). Mirrors regulations_get_cfr_section at the current date and its default 64,000-character window — mirror-backed with a live eCFR fallback. The part and section resolve as the tool resolves them ("Part 141"; "61", "§ 141.61", "141.61(c)", "40 CFR 141.61"); `part` and `section` name what was read and `notice` says how. When bodyTextNextOffset is present the section runs past the window — read on through regulations_get_cfr_section with that offset. Sections only; read an appendix through regulations_get_cfr_section with its `appendix` input.',
25
26
  mimeType: 'application/json',
26
27
  params: z.object({
27
28
  title: z
28
29
  .string()
29
30
  .regex(/^\d{1,2}$/)
30
31
  .describe('CFR title number (1–50).'),
31
- part: z.string().describe('CFR part (e.g. "50").'),
32
+ part: z.string().describe('CFR part (e.g. "50"); a leading "Part" is dropped.'),
32
33
  section: z.string().describe('Section identifier (e.g. "50.1").'),
33
34
  }),
34
35
  errors: [
@@ -38,6 +39,12 @@ export const cfrSectionResource = resource('regulations://cfr/{title}/{part}/{se
38
39
  when: 'No such title/part/section in the current CFR, under the section as given or any form it resolves to.',
39
40
  recovery: 'Verify the cite with regulations_browse_cfr (structure mode) and pass the identifier it lists — a section is normally part.section ("141.61"). The part or section may not exist or may be reserved.',
40
41
  },
42
+ {
43
+ reason: 'conflicting_title',
44
+ code: JsonRpcErrorCode.ValidationError,
45
+ when: 'The section is written as a full cite ("21 CFR 141.61") naming a different title than the URI.',
46
+ recovery: 'Put the title the cite names in the URI, or pass the section without its title prefix ("141.61").',
47
+ },
41
48
  {
42
49
  reason: 'upstream_unavailable',
43
50
  code: JsonRpcErrorCode.ServiceUnavailable,
@@ -48,8 +55,13 @@ export const cfrSectionResource = resource('regulations://cfr/{title}/{part}/{se
48
55
  ],
49
56
  async handler(params, ctx) {
50
57
  const titleNum = Number.parseInt(params.title, 10);
51
- const part = decodeSegment(params.part);
58
+ const written = decodeSegment(params.part);
59
+ const part = normalizePart(written) ?? written;
52
60
  const section = decodeSegment(params.section);
61
+ const cited = citedTitle(section);
62
+ if (cited !== undefined && cited !== titleNum) {
63
+ throw ctx.fail('conflicting_title', `Section "${section}" cites title ${cited}, but title is ${titleNum}.`, { ...ctx.recoveryFor('conflicting_title') });
64
+ }
53
65
  const read = await readSection(titleNum, part, section, undefined, ctx);
54
66
  if (!read.found) {
55
67
  const tried = read.alsoTried.length > 0 ? ` (also tried ${read.alsoTried.join(', ')})` : '';
@@ -64,6 +76,7 @@ export const cfrSectionResource = resource('regulations://cfr/{title}/{part}/{se
64
76
  const resolved = read.result.section ?? section;
65
77
  const hierarchyPath = await getEcfrService().hierarchyPath(titleNum, { part, section: resolved }, read.result.date, ctx);
66
78
  const cut = windowText(read.result.bodyText, { offset: 0, maxChars: DEFAULT_WINDOW_CHARS });
79
+ const notice = [describePartRewrite(written, part), read.rewrite].filter(Boolean).join(' ');
67
80
  return {
68
81
  cfrCite: sectionCite(titleNum, part, resolved),
69
82
  title: titleNum,
@@ -78,7 +91,7 @@ export const cfrSectionResource = resource('regulations://cfr/{title}/{part}/{se
78
91
  bodyTextOffset: cut.offset,
79
92
  bodyTextLength: cut.length,
80
93
  ...(cut.nextOffset !== undefined && { bodyTextNextOffset: cut.nextOffset }),
81
- ...(read.rewrite && { notice: read.rewrite }),
94
+ ...(notice && { notice }),
82
95
  };
83
96
  },
84
97
  list: () => ({
@@ -1 +1 @@
1
- {"version":3,"file":"cfr-section.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/cfr-section.resource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cfr-section.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/cfr-section.resource.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@ import { getFederalRegisterService } from '../../../services/federal-register/fe
12
12
  export const documentResource = resource('regulations://document/{documentNumber}', {
13
13
  name: 'federal-register-document',
14
14
  title: 'Federal Register document',
15
- description: 'A single Federal Register document by FR number: metadata plus the cross-source handles (docket ID, affected CFR parts, comment count) that chain into the comment and codified-text tools. Mirrors regulations_get_document (full text omitted).',
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).',
16
16
  mimeType: 'application/json',
17
17
  /**
18
18
  * Validated as the template hands it over: the pattern admits only unreserved
@@ -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,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,mPAAmP;IACrP,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"}
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"}
@@ -23,6 +23,11 @@ export declare const allResourceDefinitions: (import("@cyanheads/mcp-ts-core").R
23
23
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
24
24
  readonly when: "No such title/part/section in the current CFR, under the section as given or any form it resolves to.";
25
25
  readonly recovery: "Verify the cite with regulations_browse_cfr (structure mode) and pass the identifier it lists — a section is normally part.section (\"141.61\"). The part or section may not exist or may be reserved.";
26
+ }, {
27
+ readonly reason: "conflicting_title";
28
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
29
+ readonly when: "The section is written as a full cite (\"21 CFR 141.61\") naming a different title than the URI.";
30
+ readonly recovery: "Put the title the cite names in the URI, or pass the section without its title prefix (\"141.61\").";
26
31
  }, {
27
32
  readonly reason: "upstream_unavailable";
28
33
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;MAAyC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAAyC,CAAC"}
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * @fileoverview regulations_browse_cfr — navigate the CFR hierarchy (structure
3
3
  * mode) or full-text-search the codified CFR (search mode) via eCFR. Search runs
4
- * against the local mirror's FTS5 index only when the mirror's title coverage can
5
- * answer the request; a scoped mirror, an unscoped (all-titles) query it cannot
6
- * answer completely, a historical date, or a cold deploy all route to the live
7
- * eCFR search API. Keyless. Feeds regulations_get_cfr_section.
4
+ * against the local mirror's FTS5 index only when the mirror holds every title
5
+ * the request covers at that title's latest eCFR issue; a title it does not
6
+ * hold, a title eCFR has re-issued since the mirror took it, an all-titles query
7
+ * against a partial mirror or one with any title behind, a historical date, or a
8
+ * cold deploy all route to the live eCFR search API. Keyless. Feeds
9
+ * regulations_get_cfr_section.
8
10
  *
9
11
  * `title` and `part` scope both modes; a part with no title is refused rather
10
12
  * than dropped, since part numbers repeat across titles and eCFR rejects the
@@ -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;AAmJjE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyXxB,CAAC"}
1
+ {"version":3,"file":"browse-cfr.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/browse-cfr.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAsB,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAwJjE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuXxB,CAAC"}
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * @fileoverview regulations_browse_cfr — navigate the CFR hierarchy (structure
3
3
  * mode) or full-text-search the codified CFR (search mode) via eCFR. Search runs
4
- * against the local mirror's FTS5 index only when the mirror's title coverage can
5
- * answer the request; a scoped mirror, an unscoped (all-titles) query it cannot
6
- * answer completely, a historical date, or a cold deploy all route to the live
7
- * eCFR search API. Keyless. Feeds regulations_get_cfr_section.
4
+ * against the local mirror's FTS5 index only when the mirror holds every title
5
+ * the request covers at that title's latest eCFR issue; a title it does not
6
+ * hold, a title eCFR has re-issued since the mirror took it, an all-titles query
7
+ * against a partial mirror or one with any title behind, a historical date, or a
8
+ * cold deploy all route to the live eCFR search API. Keyless. Feeds
9
+ * regulations_get_cfr_section.
8
10
  *
9
11
  * `title` and `part` scope both modes; a part with no title is refused rather
10
12
  * than dropped, since part numbers repeat across titles and eCFR rejects the
@@ -21,6 +23,7 @@
21
23
  */
22
24
  import { tool, z } from '@cyanheads/mcp-ts-core';
23
25
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
26
+ import { normalizePart } from '../../../services/ecfr/cite.js';
24
27
  import { ECFR_SEARCH_WINDOW, getEcfrService, outsideCoverageMessage, today, } from '../../../services/ecfr/ecfr-service.js';
25
28
  import { mirrorReady, mirrorScope, mirrorSearch, } from '../../../services/ecfr-mirror/ecfr-mirror.js';
26
29
  import { isoDate } from './date-input.js';
@@ -32,37 +35,41 @@ function filterClause(title, part) {
32
35
  ? `, filtered to title ${title}`
33
36
  : `, filtered to title ${title} part ${part}`;
34
37
  }
35
- /** Human-readable coverage of the mirror index, for the `sourceScope` field. */
36
- function describeMirrorScope(scope, title, part) {
38
+ /**
39
+ * Human-readable coverage of the mirror index, for the `sourceScope` field. The
40
+ * mirror answers only for titles at their latest issue, so its text is the text
41
+ * in effect on the day the live index serves as current — the same `date` a
42
+ * live search reports.
43
+ */
44
+ function describeMirrorScope(scope, date, title, part) {
37
45
  const held = scope.complete ? 'all CFR titles' : `CFR titles ${scope.titles.join(', ')}`;
38
46
  // The index holds section text alone, so an appendix match cannot come back
39
47
  // from it — which is indistinguishable from no such appendix unless said.
40
- return `Local mirror index — ${held}${filterClause(title, part)}, current section text only; appendices are not indexed, so no result here is evidence about them.`;
48
+ return `Local mirror index — ${held}${filterClause(title, part)}, section text in effect on ${date}; appendices are not indexed, so no result here is evidence about them.`;
49
+ }
50
+ /**
51
+ * Whether the mirror can answer a current search: it holds the title asked for
52
+ * at that title's latest eCFR issue, or, for an all-titles query, it holds every
53
+ * title and each of them is at its latest issue. A mirror built once and never
54
+ * refreshed keeps its rows, and a title eCFR has re-issued since would answer
55
+ * with superseded text.
56
+ */
57
+ async function mirrorAnswers(scope, title, ctx) {
58
+ const service = getEcfrService();
59
+ if (title !== undefined)
60
+ return service.isLatestIssue(title, scope.issueDates.get(title), ctx);
61
+ if (!scope.complete)
62
+ return false;
63
+ for (const held of scope.titles) {
64
+ if (!(await service.isLatestIssue(held, scope.issueDates.get(held), ctx)))
65
+ return false;
66
+ }
67
+ return true;
41
68
  }
42
69
  /** Human-readable coverage of the live eCFR search index, for `sourceScope`. */
43
70
  function describeLiveScope(date, title, part) {
44
71
  return `Live eCFR search — all CFR titles${filterClause(title, part)}, text in effect on ${date}.`;
45
72
  }
46
- /**
47
- * Canonicalize a caller-supplied part identifier, or `undefined` when nothing is
48
- * left of it. eCFR matches `hierarchy[part]` exactly, and the mirror matches its
49
- * `part` column exactly, so "Part 58" and "58 " both return zero rather than an
50
- * error — strip the spelled-out prefix and the surrounding whitespace that
51
- * produce that silent miss. A value that is blank to begin with, or blank once
52
- * stripped, is no filter at all: returning it as one would put "part " in
53
- * `sourceScope` and claim a restriction the query never carried.
54
- *
55
- * Deliberately conservative beyond that: case and leading zeros are left alone.
56
- * Real part identifiers include an uppercase one (26 CFR 16A) and lowercase
57
- * suffixes (14 CFR 1203a), and none begin with a zero, so folding either would
58
- * rewrite a caller's part into a different one.
59
- */
60
- function normalizePart(part) {
61
- return (part
62
- ?.trim()
63
- .replace(/^(?:parts?|pts?\.?)\s+/i, '')
64
- .trim() || undefined);
65
- }
66
73
  /** Largest `per_page` the tool accepts — a truncation notice only suggests raising it below this. */
67
74
  const MAX_PER_PAGE = 50;
68
75
  /** The "or raise per_page" clause, offered only while a larger page exists. */
@@ -81,7 +88,7 @@ const structureNode = z
81
88
  cfrCite: z
82
89
  .string()
83
90
  .nullable()
84
- .describe('Assembled cite → regulations_get_cfr_section: "40 CFR 50.1" for a section, "Appendix A-1 to Part 50, Title 40" for an appendix. Null on a level with no read path (title, subtitle, chapter).'),
91
+ .describe('Assembled cite → regulations_get_cfr_section: "40 CFR 50.1" for a section ("14 CFR 241 § 25" for one numbered without its part), "Appendix A-1 to Part 50, Title 40" for an appendix. Null on a level with no read path (title, subtitle, chapter).'),
85
92
  appendix: z
86
93
  .string()
87
94
  .nullable()
@@ -138,7 +145,7 @@ export const browseCfrTool = tool('regulations_browse_cfr', {
138
145
  part: z
139
146
  .string()
140
147
  .optional()
141
- .describe('CFR part within the title, in both modes — structure mode lists every section and appendix in the part, flattened and paged by page/per_page; search mode restricts matches to text inside that part. Requires title; a part on its own is rejected. Parts can be alphanumeric ("1203a", "16A") and are matched exactly, so pass the identifier as eCFR writes it — "58", not "Part 58" or "058".'),
148
+ .describe('CFR part within the title, in both modes — structure mode lists every section and appendix in the part, flattened and paged by page/per_page; search mode restricts matches to text inside that part. Requires title; a part on its own is rejected. Parts can be alphanumeric ("1203a", "16A") and are matched exactly once a leading "Part" or "Pt." is dropped, so pass the identifier as eCFR writes it — "58" ("Part 58" also reads as 58), not "058".'),
142
149
  query: z
143
150
  .union([z.literal(''), z.string().min(2).describe('Search phrase, at least 2 characters.')])
144
151
  .optional()
@@ -168,7 +175,7 @@ export const browseCfrTool = tool('regulations_browse_cfr', {
168
175
  date: z
169
176
  .string()
170
177
  .optional()
171
- .describe('Resolved point-in-time date — the hierarchy snapshot (structure mode), or the day whose section text was searched (search mode, live source only).'),
178
+ .describe('Resolved point-in-time date — the hierarchy snapshot (structure mode), or the day whose section text was searched (search mode).'),
172
179
  nodes: z
173
180
  .array(structureNode)
174
181
  .optional()
@@ -317,21 +324,22 @@ export const browseCfrTool = tool('regulations_browse_cfr', {
317
324
  if (!query) {
318
325
  throw ctx.fail('query_required', undefined, { ...ctx.recoveryFor('query_required') });
319
326
  }
320
- // The mirror answers only what it actually holds. A title outside its scope,
321
- // or an all-titles query against a scoped mirror, would come back empty from
322
- // a corpus that never contained the answer — so those go to live eCFR, as
323
- // section reads already do on a mirror miss.
327
+ // The mirror answers only what it actually holds, as of the issue eCFR serves
328
+ // now. A title outside its scope, or an all-titles query against a scoped
329
+ // mirror, would come back empty from a corpus that never contained the
330
+ // answer, and a title behind its latest issue would match superseded text —
331
+ // so those go to live eCFR, as section reads already do on a mirror miss.
324
332
  const scope = !input.date && (await mirrorReady()) ? await mirrorScope() : null;
325
- const answering = scope && (input.title === undefined ? scope.complete : scope.titles.includes(input.title))
326
- ? scope
327
- : null;
333
+ const answering = scope && (await mirrorAnswers(scope, input.title, ctx)) ? scope : null;
328
334
  const { page, per_page: perPage } = input;
329
335
  let provenance;
330
336
  let response;
331
337
  if (answering) {
338
+ const date = await service.currentDate(ctx);
332
339
  provenance = {
333
340
  source: 'mirror',
334
- sourceScope: describeMirrorScope(answering, input.title, part),
341
+ sourceScope: describeMirrorScope(answering, date, input.title, part),
342
+ date,
335
343
  };
336
344
  response = await mirrorSearch(query, input.title, part, perPage, (page - 1) * perPage);
337
345
  }