@cyanheads/congressgov-mcp-server 0.7.1 → 0.7.3

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 (47) hide show
  1. package/AGENTS.md +20 -15
  2. package/CLAUDE.md +20 -15
  3. package/Dockerfile +23 -10
  4. package/README.md +90 -93
  5. package/changelog/0.7.x/0.7.2.md +20 -0
  6. package/changelog/0.7.x/0.7.3.md +36 -0
  7. package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +5 -4
  8. package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
  9. package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +93 -2
  10. package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
  11. package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/bill-summaries.tool.js +32 -4
  13. package/dist/mcp-server/tools/definitions/bill-summaries.tool.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts +2 -2
  15. package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts +2 -2
  16. package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
  17. package/dist/mcp-server/tools/format-helpers.js +85 -30
  18. package/dist/mcp-server/tools/format-helpers.js.map +1 -1
  19. package/dist/mcp-server/tools/summary-window.d.ts +112 -0
  20. package/dist/mcp-server/tools/summary-window.d.ts.map +1 -0
  21. package/dist/mcp-server/tools/summary-window.js +182 -0
  22. package/dist/mcp-server/tools/summary-window.js.map +1 -0
  23. package/dist/mcp-server/tools/tool-helpers.d.ts +21 -4
  24. package/dist/mcp-server/tools/tool-helpers.d.ts.map +1 -1
  25. package/dist/mcp-server/tools/tool-helpers.js +21 -4
  26. package/dist/mcp-server/tools/tool-helpers.js.map +1 -1
  27. package/dist/services/congress-documents/extract-text-stream.js +1 -1
  28. package/dist/services/congress-documents/extract-text-stream.js.map +1 -1
  29. package/dist/services/congress-documents/extract-text.d.ts +0 -6
  30. package/dist/services/congress-documents/extract-text.d.ts.map +1 -1
  31. package/dist/services/congress-documents/extract-text.js +4 -37
  32. package/dist/services/congress-documents/extract-text.js.map +1 -1
  33. package/dist/services/congress-mirror/normalize.d.ts +4 -1
  34. package/dist/services/congress-mirror/normalize.d.ts.map +1 -1
  35. package/dist/services/congress-mirror/normalize.js +8 -12
  36. package/dist/services/congress-mirror/normalize.js.map +1 -1
  37. package/dist/services/senate-lis/parse.d.ts.map +1 -1
  38. package/dist/services/senate-lis/parse.js +74 -2
  39. package/dist/services/senate-lis/parse.js.map +1 -1
  40. package/dist/services/senate-lis/types.d.ts +8 -1
  41. package/dist/services/senate-lis/types.d.ts.map +1 -1
  42. package/dist/utils/character-references.d.ts +27 -0
  43. package/dist/utils/character-references.d.ts.map +1 -0
  44. package/dist/utils/character-references.js +60 -0
  45. package/dist/utils/character-references.js.map +1 -0
  46. package/package.json +7 -6
  47. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** congressgov-mcp-server
4
- **Version:** 0.7.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
4
+ **Version:** 0.7.3
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.9`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` + `@modelcontextprotocol/client` ^2.0.0
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.1.0
8
8
  **Zod:** ^4.6.5
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -53,7 +53,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
53
53
 
54
54
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
55
55
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
56
- - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
56
+ - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly. Values round-trip as JSON; a `Date` reads back as an ISO string.
57
57
  - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
58
58
  - **Secrets in env vars only** — never hardcoded.
59
59
  - **All tools are read-only.** Every tool gets `annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }`.
@@ -68,18 +68,20 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
68
68
 
69
69
  | Name | Description |
70
70
  |:-----|:------------|
71
- | `congressgov_bill_lookup` | Browse, filter, and retrieve bill data (actions, sponsors, summaries, text, related bills) + bounded document text via `content` |
71
+ | `congressgov_bill_lookup` | Browse, filter, and retrieve bill data (actions, sponsors, summaries, text, related bills) + bounded document text via `content`; `summaries` takes `versionCode` and the shared `characterOffset`/`characterLimit` window |
72
72
  | `congressgov_enacted_laws` | Browse enacted public and private laws by congress |
73
73
  | `congressgov_member_lookup` | Discover members by state/district/congress, retrieve legislative portfolios |
74
74
  | `congressgov_committee_lookup` | Browse committees and retrieve legislation, reports, nominations |
75
75
  | `congressgov_roll_votes` | Retrieve House and Senate roll call votes and member voting positions |
76
76
  | `congressgov_senate_nominations` | Browse presidential nominations, track Senate confirmation pipeline |
77
- | `congressgov_bill_summaries` | Browse recent CRS bill summaries — the "what's happening" feed |
77
+ | `congressgov_bill_summaries` | Browse recent CRS bill summaries — the "what's happening" feed, bounded by the shared summary page budget and per-row text window |
78
78
  | `congressgov_crs_reports` | Browse and retrieve nonpartisan CRS policy analysis reports |
79
79
  | `congressgov_committee_reports` | Browse and retrieve committee reports accompanying legislation + bounded report text via `content` |
80
80
  | `congressgov_daily_record` | Browse daily Congressional Record — floor speeches, debates, proceedings + bounded article text via `content` |
81
81
  | `congressgov_search_bills` | Keyword-search bill titles/summaries via the opt-in local FTS mirror — off by default |
82
82
 
83
+ **Summary bounding** (`src/mcp-server/tools/summary-window.ts`, constants in `tool-helpers.ts`). Both summary surfaces enforce two limits. `SUMMARY_PAGE_CHARACTERS` stops a page once its serialized rows reach the budget — the first row always returns, `pagination.nextOffset` counts the rows actually returned, and an enrichment notice discloses the stop. `DEFAULT_SUMMARY_TEXT_CHARACTERS` bounds one row's upstream `text`; a windowed row gains `textTotalCharacters` / `textTruncated` / `textNextOffset`, and its window end is retracted off any HTML tag or character reference it would otherwise split (unless the construct is wider than the whole window, where the requested limit wins). A row inside both limits is returned untouched, so an unbounded page stays byte-identical. `congressgov_bill_lookup` `summaries` overrides the row window with `characterOffset`/`characterLimit` and selects one version with `versionCode` — Congress.gov publishes no version filter, so selection reads the bill's whole summary list and matches client-side.
84
+
83
85
  ### Resources (5)
84
86
 
85
87
  | URI Template | Description |
@@ -114,6 +116,7 @@ src/
114
116
  congress-documents-service.ts # www.congress.gov document fetch — host allowlist, byte ceiling, character window
115
117
  document-formats.ts # Format label → URL resolution over upstream format lists
116
118
  extract-text.ts # GPO `<pre>` / XML body → deterministic plain text
119
+ extract-text-stream.ts # Incremental twin of extract-text.ts — one character window, constant memory
117
120
  types.ts # Document format + content window types
118
121
  congress-mirror/
119
122
  congress-mirror-service.ts # Mirror read path — FTS5 search, ready(), sync accessor
@@ -147,6 +150,8 @@ src/
147
150
  prompts/definitions/
148
151
  bill-analysis.prompt.ts # congressgov_bill_analysis
149
152
  legislative-research.prompt.ts # congressgov_legislative_research
153
+ utils/
154
+ character-references.ts # Single-pass HTML/XML character-reference decoding
150
155
  scripts/
151
156
  _mirror-context.ts # Shared bootstrap for the mirror lifecycle CLI scripts
152
157
  congress-mirror-init.ts # mirror:init — full out-of-band mirror build
@@ -179,7 +184,7 @@ Four services. `CongressApiService` backs nine tools and the House branch of `co
179
184
  - The fetch is bounded: a 25 MB ceiling (refused on `Content-Length`, else mid-stream), a 30s deadline, and a content-type allowlist
180
185
  - The body is extracted as it streams and only the requested window is retained, so a read's live set is flat in the size of the document (transient allocation is not — peak RSS runs above a buffered read); the ceiling bounds how long a response may run, not how much of one fits in a buffer
181
186
  - The structured response is bounded by an exact character window — `structuredContent.content.text` and its offsets index the extracted plain text and are never snapped to section breaks, so feeding `nextOffset` back walks a document with no overlap and no gap; `content[]` wraps that unchanged window in a dynamically safe Markdown fence for presentation
182
- - `extract-text.ts` unwraps GPO's `<pre>` print output verbatim (whitespace is the document's structure), while no-`<pre>` XML gains one space only at an otherwise-unseparated sibling close→open element boundary; both readings decode entities in one pass, and `extract-text-stream.ts` is their incremental twin, pinned byte-for-byte against the whole-string extractor
187
+ - `extract-text.ts` unwraps GPO's `<pre>` print output verbatim (whitespace is the document's structure), while no-`<pre>` XML gains one space only at an otherwise-unseparated sibling close→open element boundary; both readings decode character references through `src/utils/character-references.ts` — the one decoder every HTML/XML → text path in the server shares, single-pass so an escaped reference stays escaped — and `extract-text-stream.ts` is their incremental twin, pinned byte-for-byte against the whole-string extractor
183
188
 
184
189
  **`CongressMirrorService`** — local SQLite FTS5 mirror of bill title + CRS summary text, backing `congressgov_search_bills` only:
185
190
  - Opt-in via `CONGRESS_MIRROR_ENABLED` (off by default); built out-of-band via the `mirror:init`/`mirror:refresh` scripts, never on server startup
@@ -212,7 +217,7 @@ All tools share these patterns. The service layer handles them uniformly:
212
217
  | Invalid params | `validationError('...', { field })` |
213
218
  | Network error | `serviceUnavailable('Unable to reach the Congress.gov API.')` |
214
219
 
215
- The `content` operation adds `documentErrorContracts` (tool-helpers) on top: `document_unavailable`, `format_unavailable`, `document_fetch_failed`, `document_too_large`, and `offset_past_end`. `CongressDocumentsService` raises each with a matching `data.reason` and resolves the hint via `ctx.recoveryFor` — note that `ctx.fail` does **not** auto-populate `recovery`, so handler-side `ctx.fail` calls must spread `ctx.recoveryFor(reason)` into their data or the hint never reaches the wire.
220
+ The `content` operation adds `documentErrorContracts` (tool-helpers) on top: `document_unavailable`, `format_unavailable`, `document_fetch_failed`, `document_too_large`, and `offset_past_end` — the last of which `congressgov_bill_lookup` also raises from its `summaries` branch when `characterOffset` is past the end of every returned row's text. `CongressDocumentsService` raises each with a matching `data.reason` and resolves the hint via `ctx.recoveryFor` — note that `ctx.fail` does **not** auto-populate `recovery`, so handler-side `ctx.fail` calls must spread `ctx.recoveryFor(reason)` into their data or the hint never reaches the wire.
216
221
 
217
222
  Service-only contract reasons carry `thrownBy: 'service'`; handler-local reasons remain checked by `error-contract-unthrown`. `RequestCancelled` is a baseline code and needs no contract entry. Retry predicates compose `defaultIsTransient` with the existing exclusion of `RateLimited` codes and preserve upstream `retryable: false`.
218
223
 
@@ -267,7 +272,7 @@ Available skills:
267
272
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
268
273
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
269
274
  | `git-wrapup` | Land the commit stack, version bump, and changelog on a release branch; open the release PR |
270
- | `release-pr-review` | Review the release PR and land fixes as ordinary commits; keep its body in sync |
275
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
271
276
  | `release-and-publish` | Fast-forward main, tag, push, and publish to npm, MCP Registry, GitHub Releases, and Docker |
272
277
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
273
278
  | `report-issue-framework` | File bugs/features against `@cyanheads/mcp-ts-core` |
@@ -276,9 +281,9 @@ Available skills:
276
281
  | `api-config` | AppConfig, parseConfig, env vars |
277
282
  | `api-context` | Context interface, RequestContext, logger, state, multi-round-trip input |
278
283
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
279
- | `api-linter` | MCP definition linter rules reference — look up `format-parity`, `schema-*`, `name-*`, `server-json-*` diagnostics |
284
+ | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
280
285
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper — Tier 3 opt-in |
281
- | `api-mirror` | MirrorService: persistent local SQLite mirror of bulk upstream datasets — Tier 3 opt-in |
286
+ | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
282
287
  | `api-services` | LLM, Speech, Graph services |
283
288
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
284
289
  | `api-testing` | createMockContext, test patterns |
@@ -310,8 +315,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
310
315
  | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
311
316
  | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
312
317
  | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
313
- | `bun run release:github` | Create GitHub Release from the current annotated tag |
314
- | `bun run test` | Run tests |
318
+ | `bun run release:github` | Validate the current annotated tag and create its GitHub Release; `-- --check` validates only |
319
+ | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
315
320
  | `bun run dev:stdio` | Dev mode (stdio) |
316
321
  | `bun run dev:http` | Dev mode (HTTP) |
317
322
  | `bun run start:stdio` | Production mode (stdio) |
@@ -387,6 +392,6 @@ Remind the user to run these after completing a release flow.
387
392
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
388
393
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
389
394
  - [ ] `.codex-plugin/plugin.json` populated — `name` and `interface.displayName` = unscoped repo name; `version`, `description`, `repository`, `license` and `interface.shortDescription` from `package.json`
390
- - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
391
- - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys
395
+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable is listed in `env_vars`. Never write `"KEY": ""` into `env` — it overrides the user's exported key
396
+ - [ ] `.claude-plugin/plugin.json` populated — unscoped `name`, `version`, `description`, `author`, `repository`, `license`, `keywords`; inline `mcpServers` entry keyed by the unscoped repo name. User-supplied variables use `userConfig` and `"KEY": "${user_config.<option>}"` env references, matching `manifest.json`
392
397
  - [ ] `bun run devcheck` passes
package/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** congressgov-mcp-server
4
- **Version:** 0.7.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
4
+ **Version:** 0.7.3
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.9`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` + `@modelcontextprotocol/client` ^2.0.0
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.1.0
8
8
  **Zod:** ^4.6.5
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -53,7 +53,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
53
53
 
54
54
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
55
55
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
56
- - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
56
+ - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly. Values round-trip as JSON; a `Date` reads back as an ISO string.
57
57
  - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
58
58
  - **Secrets in env vars only** — never hardcoded.
59
59
  - **All tools are read-only.** Every tool gets `annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }`.
@@ -68,18 +68,20 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
68
68
 
69
69
  | Name | Description |
70
70
  |:-----|:------------|
71
- | `congressgov_bill_lookup` | Browse, filter, and retrieve bill data (actions, sponsors, summaries, text, related bills) + bounded document text via `content` |
71
+ | `congressgov_bill_lookup` | Browse, filter, and retrieve bill data (actions, sponsors, summaries, text, related bills) + bounded document text via `content`; `summaries` takes `versionCode` and the shared `characterOffset`/`characterLimit` window |
72
72
  | `congressgov_enacted_laws` | Browse enacted public and private laws by congress |
73
73
  | `congressgov_member_lookup` | Discover members by state/district/congress, retrieve legislative portfolios |
74
74
  | `congressgov_committee_lookup` | Browse committees and retrieve legislation, reports, nominations |
75
75
  | `congressgov_roll_votes` | Retrieve House and Senate roll call votes and member voting positions |
76
76
  | `congressgov_senate_nominations` | Browse presidential nominations, track Senate confirmation pipeline |
77
- | `congressgov_bill_summaries` | Browse recent CRS bill summaries — the "what's happening" feed |
77
+ | `congressgov_bill_summaries` | Browse recent CRS bill summaries — the "what's happening" feed, bounded by the shared summary page budget and per-row text window |
78
78
  | `congressgov_crs_reports` | Browse and retrieve nonpartisan CRS policy analysis reports |
79
79
  | `congressgov_committee_reports` | Browse and retrieve committee reports accompanying legislation + bounded report text via `content` |
80
80
  | `congressgov_daily_record` | Browse daily Congressional Record — floor speeches, debates, proceedings + bounded article text via `content` |
81
81
  | `congressgov_search_bills` | Keyword-search bill titles/summaries via the opt-in local FTS mirror — off by default |
82
82
 
83
+ **Summary bounding** (`src/mcp-server/tools/summary-window.ts`, constants in `tool-helpers.ts`). Both summary surfaces enforce two limits. `SUMMARY_PAGE_CHARACTERS` stops a page once its serialized rows reach the budget — the first row always returns, `pagination.nextOffset` counts the rows actually returned, and an enrichment notice discloses the stop. `DEFAULT_SUMMARY_TEXT_CHARACTERS` bounds one row's upstream `text`; a windowed row gains `textTotalCharacters` / `textTruncated` / `textNextOffset`, and its window end is retracted off any HTML tag or character reference it would otherwise split (unless the construct is wider than the whole window, where the requested limit wins). A row inside both limits is returned untouched, so an unbounded page stays byte-identical. `congressgov_bill_lookup` `summaries` overrides the row window with `characterOffset`/`characterLimit` and selects one version with `versionCode` — Congress.gov publishes no version filter, so selection reads the bill's whole summary list and matches client-side.
84
+
83
85
  ### Resources (5)
84
86
 
85
87
  | URI Template | Description |
@@ -114,6 +116,7 @@ src/
114
116
  congress-documents-service.ts # www.congress.gov document fetch — host allowlist, byte ceiling, character window
115
117
  document-formats.ts # Format label → URL resolution over upstream format lists
116
118
  extract-text.ts # GPO `<pre>` / XML body → deterministic plain text
119
+ extract-text-stream.ts # Incremental twin of extract-text.ts — one character window, constant memory
117
120
  types.ts # Document format + content window types
118
121
  congress-mirror/
119
122
  congress-mirror-service.ts # Mirror read path — FTS5 search, ready(), sync accessor
@@ -147,6 +150,8 @@ src/
147
150
  prompts/definitions/
148
151
  bill-analysis.prompt.ts # congressgov_bill_analysis
149
152
  legislative-research.prompt.ts # congressgov_legislative_research
153
+ utils/
154
+ character-references.ts # Single-pass HTML/XML character-reference decoding
150
155
  scripts/
151
156
  _mirror-context.ts # Shared bootstrap for the mirror lifecycle CLI scripts
152
157
  congress-mirror-init.ts # mirror:init — full out-of-band mirror build
@@ -179,7 +184,7 @@ Four services. `CongressApiService` backs nine tools and the House branch of `co
179
184
  - The fetch is bounded: a 25 MB ceiling (refused on `Content-Length`, else mid-stream), a 30s deadline, and a content-type allowlist
180
185
  - The body is extracted as it streams and only the requested window is retained, so a read's live set is flat in the size of the document (transient allocation is not — peak RSS runs above a buffered read); the ceiling bounds how long a response may run, not how much of one fits in a buffer
181
186
  - The structured response is bounded by an exact character window — `structuredContent.content.text` and its offsets index the extracted plain text and are never snapped to section breaks, so feeding `nextOffset` back walks a document with no overlap and no gap; `content[]` wraps that unchanged window in a dynamically safe Markdown fence for presentation
182
- - `extract-text.ts` unwraps GPO's `<pre>` print output verbatim (whitespace is the document's structure), while no-`<pre>` XML gains one space only at an otherwise-unseparated sibling close→open element boundary; both readings decode entities in one pass, and `extract-text-stream.ts` is their incremental twin, pinned byte-for-byte against the whole-string extractor
187
+ - `extract-text.ts` unwraps GPO's `<pre>` print output verbatim (whitespace is the document's structure), while no-`<pre>` XML gains one space only at an otherwise-unseparated sibling close→open element boundary; both readings decode character references through `src/utils/character-references.ts` — the one decoder every HTML/XML → text path in the server shares, single-pass so an escaped reference stays escaped — and `extract-text-stream.ts` is their incremental twin, pinned byte-for-byte against the whole-string extractor
183
188
 
184
189
  **`CongressMirrorService`** — local SQLite FTS5 mirror of bill title + CRS summary text, backing `congressgov_search_bills` only:
185
190
  - Opt-in via `CONGRESS_MIRROR_ENABLED` (off by default); built out-of-band via the `mirror:init`/`mirror:refresh` scripts, never on server startup
@@ -212,7 +217,7 @@ All tools share these patterns. The service layer handles them uniformly:
212
217
  | Invalid params | `validationError('...', { field })` |
213
218
  | Network error | `serviceUnavailable('Unable to reach the Congress.gov API.')` |
214
219
 
215
- The `content` operation adds `documentErrorContracts` (tool-helpers) on top: `document_unavailable`, `format_unavailable`, `document_fetch_failed`, `document_too_large`, and `offset_past_end`. `CongressDocumentsService` raises each with a matching `data.reason` and resolves the hint via `ctx.recoveryFor` — note that `ctx.fail` does **not** auto-populate `recovery`, so handler-side `ctx.fail` calls must spread `ctx.recoveryFor(reason)` into their data or the hint never reaches the wire.
220
+ The `content` operation adds `documentErrorContracts` (tool-helpers) on top: `document_unavailable`, `format_unavailable`, `document_fetch_failed`, `document_too_large`, and `offset_past_end` — the last of which `congressgov_bill_lookup` also raises from its `summaries` branch when `characterOffset` is past the end of every returned row's text. `CongressDocumentsService` raises each with a matching `data.reason` and resolves the hint via `ctx.recoveryFor` — note that `ctx.fail` does **not** auto-populate `recovery`, so handler-side `ctx.fail` calls must spread `ctx.recoveryFor(reason)` into their data or the hint never reaches the wire.
216
221
 
217
222
  Service-only contract reasons carry `thrownBy: 'service'`; handler-local reasons remain checked by `error-contract-unthrown`. `RequestCancelled` is a baseline code and needs no contract entry. Retry predicates compose `defaultIsTransient` with the existing exclusion of `RateLimited` codes and preserve upstream `retryable: false`.
218
223
 
@@ -267,7 +272,7 @@ Available skills:
267
272
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
268
273
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
269
274
  | `git-wrapup` | Land the commit stack, version bump, and changelog on a release branch; open the release PR |
270
- | `release-pr-review` | Review the release PR and land fixes as ordinary commits; keep its body in sync |
275
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
271
276
  | `release-and-publish` | Fast-forward main, tag, push, and publish to npm, MCP Registry, GitHub Releases, and Docker |
272
277
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
273
278
  | `report-issue-framework` | File bugs/features against `@cyanheads/mcp-ts-core` |
@@ -276,9 +281,9 @@ Available skills:
276
281
  | `api-config` | AppConfig, parseConfig, env vars |
277
282
  | `api-context` | Context interface, RequestContext, logger, state, multi-round-trip input |
278
283
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
279
- | `api-linter` | MCP definition linter rules reference — look up `format-parity`, `schema-*`, `name-*`, `server-json-*` diagnostics |
284
+ | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
280
285
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper — Tier 3 opt-in |
281
- | `api-mirror` | MirrorService: persistent local SQLite mirror of bulk upstream datasets — Tier 3 opt-in |
286
+ | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
282
287
  | `api-services` | LLM, Speech, Graph services |
283
288
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
284
289
  | `api-testing` | createMockContext, test patterns |
@@ -310,8 +315,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
310
315
  | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
311
316
  | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
312
317
  | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
313
- | `bun run release:github` | Create GitHub Release from the current annotated tag |
314
- | `bun run test` | Run tests |
318
+ | `bun run release:github` | Validate the current annotated tag and create its GitHub Release; `-- --check` validates only |
319
+ | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
315
320
  | `bun run dev:stdio` | Dev mode (stdio) |
316
321
  | `bun run dev:http` | Dev mode (HTTP) |
317
322
  | `bun run start:stdio` | Production mode (stdio) |
@@ -387,6 +392,6 @@ Remind the user to run these after completing a release flow.
387
392
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
388
393
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
389
394
  - [ ] `.codex-plugin/plugin.json` populated — `name` and `interface.displayName` = unscoped repo name; `version`, `description`, `repository`, `license` and `interface.shortDescription` from `package.json`
390
- - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
391
- - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys
395
+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable is listed in `env_vars`. Never write `"KEY": ""` into `env` — it overrides the user's exported key
396
+ - [ ] `.claude-plugin/plugin.json` populated — unscoped `name`, `version`, `description`, `author`, `repository`, `license`, `keywords`; inline `mcpServers` entry keyed by the unscoped repo name. User-supplied variables use `userConfig` and `"KEY": "${user_config.<option>}"` env references, matching `manifest.json`
392
397
  - [ ] `bun run devcheck` passes
package/Dockerfile CHANGED
@@ -9,7 +9,7 @@
9
9
  # multi-arch buildx run on an arm64 host. This stage only runs tsc and emits
10
10
  # architecture-independent JavaScript, and the production stage installs its own
11
11
  # dependencies natively, so building it on the host architecture is safe.
12
- FROM --platform=$BUILDPLATFORM oven/bun:1.4.0 AS build
12
+ FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS build
13
13
 
14
14
  WORKDIR /usr/src/app
15
15
 
@@ -39,7 +39,7 @@ RUN bun run build
39
39
  # application. It uses a slim base image and only includes production
40
40
  # dependencies and build artifacts.
41
41
  # ==============================================================================
42
- FROM oven/bun:1.4.0-slim AS production
42
+ FROM oven/bun:1.4.2-slim AS production
43
43
 
44
44
  WORKDIR /usr/src/app
45
45
 
@@ -55,8 +55,11 @@ LABEL org.opencontainers.image.source="https://github.com/cyanheads/congressgov-
55
55
  LABEL org.opencontainers.image.licenses="Apache-2.0"
56
56
  LABEL org.opencontainers.image.version="${APP_VERSION}"
57
57
 
58
- # Copy dependency manifests
59
- COPY package.json bun.lock ./
58
+ # Copy dependency manifests and preserve the release-age gate and scanner.
59
+ COPY package.json bun.lock bunfig.toml ./
60
+
61
+ # Seed the scanner because the production install omits this devDependency.
62
+ COPY --from=build /usr/src/app/node_modules/@socketsecurity/bun-security-scanner ./node_modules/@socketsecurity/bun-security-scanner
60
63
 
61
64
  # Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
62
65
  # that are not needed in the final production image.
@@ -69,21 +72,32 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
69
72
  bun install --production --omit=peer --frozen-lockfile --ignore-scripts
70
73
 
71
74
  # Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
72
- # These are installed by default. Omit them for a smaller image with:
73
- # docker build --build-arg OTEL_ENABLED=false
75
+ # Installed by default; omit with --build-arg OTEL_ENABLED=false.
76
+ # Resolve each package inside the installed framework's tested peer range.
74
77
  ARG OTEL_ENABLED=true
75
78
  RUN --mount=type=cache,target=/root/.bun/install/cache \
76
79
  if [ "$OTEL_ENABLED" = "true" ]; then \
77
- bun add --omit=dev --omit=peer --ignore-scripts @hono/otel \
78
- @opentelemetry/instrumentation-http \
80
+ specs=$(bun -e ' \
81
+ const { peerDependencies: peers } = await Bun.file("node_modules/@cyanheads/mcp-ts-core/package.json").json(); \
82
+ const names = process.argv.slice(1); \
83
+ const missing = names.filter((name) => !peers?.[name]); \
84
+ if (missing.length > 0) throw new Error(`no peerDependencies range for ${missing.join(", ")}`); \
85
+ console.log(names.map((name) => `${name}@${peers[name]}`).join(" ")); \
86
+ ' \
87
+ @hono/otel \
88
+ @opentelemetry/api-logs \
89
+ @opentelemetry/exporter-logs-otlp-http \
79
90
  @opentelemetry/exporter-metrics-otlp-http \
80
91
  @opentelemetry/exporter-trace-otlp-http \
92
+ @opentelemetry/instrumentation-http \
81
93
  @opentelemetry/instrumentation-pino \
82
94
  @opentelemetry/resources \
95
+ @opentelemetry/sdk-logs \
83
96
  @opentelemetry/sdk-metrics \
84
97
  @opentelemetry/sdk-node \
85
98
  @opentelemetry/sdk-trace-node \
86
- @opentelemetry/semantic-conventions; \
99
+ @opentelemetry/semantic-conventions) \
100
+ && bun add --omit=dev --omit=peer --ignore-scripts $specs; \
87
101
  fi
88
102
 
89
103
  # Copy the compiled application code from the build stage
@@ -128,7 +142,6 @@ ENV MCP_TRANSPORT_TYPE="http"
128
142
  ENV MCP_SESSION_MODE="stateless"
129
143
  ENV MCP_LOG_LEVEL="info"
130
144
  ENV LOGS_DIR="/var/log/congressgov-mcp-server"
131
- ENV MCP_FORCE_CONSOLE_LOGGING="true"
132
145
 
133
146
  # Expose the port the server listens on
134
147
  EXPOSE ${MCP_HTTP_PORT}