@cyanheads/congressgov-mcp-server 0.3.32 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/AGENTS.md +24 -3
  2. package/CLAUDE.md +24 -3
  3. package/Dockerfile +13 -0
  4. package/README.md +15 -4
  5. package/dist/config/server-config.d.ts +15 -0
  6. package/dist/config/server-config.d.ts.map +1 -1
  7. package/dist/config/server-config.js +65 -0
  8. package/dist/config/server-config.js.map +1 -1
  9. package/dist/index.js +30 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp-server/tools/definitions/search-bills.tool.d.ts +47 -0
  12. package/dist/mcp-server/tools/definitions/search-bills.tool.d.ts.map +1 -0
  13. package/dist/mcp-server/tools/definitions/search-bills.tool.js +99 -0
  14. package/dist/mcp-server/tools/definitions/search-bills.tool.js.map +1 -0
  15. package/dist/mcp-server/tools/format-helpers.d.ts +2 -0
  16. package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
  17. package/dist/mcp-server/tools/format-helpers.js +31 -0
  18. package/dist/mcp-server/tools/format-helpers.js.map +1 -1
  19. package/dist/services/congress-mirror/congress-mirror-service.d.ts +45 -0
  20. package/dist/services/congress-mirror/congress-mirror-service.d.ts.map +1 -0
  21. package/dist/services/congress-mirror/congress-mirror-service.js +116 -0
  22. package/dist/services/congress-mirror/congress-mirror-service.js.map +1 -0
  23. package/dist/services/congress-mirror/ingest.d.ts +33 -0
  24. package/dist/services/congress-mirror/ingest.d.ts.map +1 -0
  25. package/dist/services/congress-mirror/ingest.js +179 -0
  26. package/dist/services/congress-mirror/ingest.js.map +1 -0
  27. package/dist/services/congress-mirror/normalize.d.ts +25 -0
  28. package/dist/services/congress-mirror/normalize.d.ts.map +1 -0
  29. package/dist/services/congress-mirror/normalize.js +47 -0
  30. package/dist/services/congress-mirror/normalize.js.map +1 -0
  31. package/dist/services/congress-mirror/schema.d.ts +19 -0
  32. package/dist/services/congress-mirror/schema.d.ts.map +1 -0
  33. package/dist/services/congress-mirror/schema.js +44 -0
  34. package/dist/services/congress-mirror/schema.js.map +1 -0
  35. package/dist/services/congress-mirror/types.d.ts +55 -0
  36. package/dist/services/congress-mirror/types.d.ts.map +1 -0
  37. package/dist/services/congress-mirror/types.js +9 -0
  38. package/dist/services/congress-mirror/types.js.map +1 -0
  39. package/package.json +30 -4
  40. package/scripts/_mirror-context.ts +30 -0
  41. package/scripts/congress-mirror-init.ts +23 -0
  42. package/scripts/congress-mirror-refresh.ts +24 -0
  43. package/scripts/congress-mirror-verify.ts +36 -0
  44. package/server.json +3 -3
@@ -0,0 +1,179 @@
1
+ /**
2
+ * @fileoverview The Congress bill ingester — the one irreducibly per-source part
3
+ * of the mirror. Merges two upstream Congress.gov sources into one row per bill:
4
+ * the bill list (`/bill/{congress}`, sorted by updateDate) supplies the skeleton
5
+ * (title, chamber, latest action, update date); the CRS summaries list
6
+ * (`/summaries/{congress}`) supplies the summary text. Both reuse the existing
7
+ * `CongressApiService` — no new HTTP-calling code, only merge/orchestration.
8
+ *
9
+ * Merge order matters: the framework upsert writes every declared column for each
10
+ * record, so a partial (summary-only) upsert would null the title. The summaries
11
+ * are therefore loaded into a per-congress map first, then attached as each full
12
+ * bill row is emitted. Summaries are fully re-walked every sync (not date-filtered)
13
+ * so a re-emitted bill never loses a previously-stored summary. The durable
14
+ * checkpoint is the bills' `updateDate` high-water mark; refresh re-scans bills
15
+ * from it inclusively (`sort=updateDate asc`), and the upsert makes the overlap
16
+ * harmless.
17
+ * @module services/congress-mirror/ingest
18
+ */
19
+ import { logger } from '@cyanheads/mcp-ts-core/utils';
20
+ import { htmlToPlainText } from './normalize.js';
21
+ /** Congress.gov list endpoints cap the page size at 250 (confirmed live). */
22
+ const PAGE_SIZE = 250;
23
+ function isRecord(value) {
24
+ return typeof value === 'object' && value !== null;
25
+ }
26
+ /** Read a non-empty string (or a stringified number) from a raw field. */
27
+ function str(value) {
28
+ if (typeof value === 'string') {
29
+ const trimmed = value.trim();
30
+ return trimmed === '' ? undefined : trimmed;
31
+ }
32
+ if (typeof value === 'number' && Number.isFinite(value))
33
+ return String(value);
34
+ return;
35
+ }
36
+ /** Read an integer from a raw field (number or numeric string). */
37
+ function int(value) {
38
+ if (typeof value === 'number' && Number.isFinite(value))
39
+ return Math.trunc(value);
40
+ if (typeof value === 'string' && value.trim() !== '') {
41
+ const parsed = Number.parseInt(value.trim(), 10);
42
+ return Number.isNaN(parsed) ? undefined : parsed;
43
+ }
44
+ return;
45
+ }
46
+ /**
47
+ * Derive the composite primary key. Matches the existing
48
+ * `congress://bill/{congress}/{billType}/{billNumber}` resource addressing; the
49
+ * bill type is lowercased so both upstream sources key identically.
50
+ */
51
+ function deriveBillId(congress, billType, billNumber) {
52
+ return `${congress}/${billType.toLowerCase()}/${billNumber}`;
53
+ }
54
+ /**
55
+ * The bill list emits date-only `updateDate` ("2026-06-30"); the API's
56
+ * `fromDateTime` filter wants full ISO 8601. Widen a date-only checkpoint to the
57
+ * start of that day so the inclusive re-scan catches same-day updates.
58
+ */
59
+ function toFromDateTime(checkpoint) {
60
+ return /^\d{4}-\d{2}-\d{2}$/.test(checkpoint) ? `${checkpoint}T00:00:00Z` : checkpoint;
61
+ }
62
+ /**
63
+ * Page the CRS summaries list for one congress into a `billId → plain-text
64
+ * summary` map, keeping the latest version per bill (by summary update/action
65
+ * stamp). Summaries ship as HTML and are stripped to plain text before storage.
66
+ */
67
+ async function buildSummaryMap(api, congress, signal) {
68
+ const latest = new Map();
69
+ let offset = 0;
70
+ while (!signal.aborted) {
71
+ const result = await api.listSummaries({ congress, limit: PAGE_SIZE, offset });
72
+ const rows = result.data;
73
+ if (rows.length === 0)
74
+ break;
75
+ for (const raw of rows) {
76
+ if (!isRecord(raw) || !isRecord(raw.bill))
77
+ continue;
78
+ const bill = raw.bill;
79
+ const c = int(bill.congress);
80
+ const billType = str(bill.type)?.toLowerCase();
81
+ const billNumber = int(bill.number);
82
+ if (c === undefined || billType === undefined || billNumber === undefined)
83
+ continue;
84
+ const text = typeof raw.text === 'string' ? htmlToPlainText(raw.text) : '';
85
+ if (!text)
86
+ continue;
87
+ const id = deriveBillId(c, billType, billNumber);
88
+ const stamp = str(raw.updateDate) ?? str(raw.actionDate) ?? '';
89
+ const existing = latest.get(id);
90
+ if (!existing || stamp >= existing.stamp)
91
+ latest.set(id, { text, stamp });
92
+ }
93
+ if (rows.length < PAGE_SIZE || result.pagination.nextOffset == null)
94
+ break;
95
+ offset += PAGE_SIZE;
96
+ }
97
+ const map = new Map();
98
+ for (const [id, value] of latest)
99
+ map.set(id, value.text);
100
+ return map;
101
+ }
102
+ /** Map one raw bill-list record to a full mirror row, joining the summary map. */
103
+ function toBillRow(raw, fallbackCongress, summaries) {
104
+ const billType = str(raw.type)?.toLowerCase();
105
+ const billNumber = int(raw.number);
106
+ if (billType === undefined || billNumber === undefined)
107
+ return null;
108
+ const congress = int(raw.congress) ?? fallbackCongress;
109
+ const id = deriveBillId(congress, billType, billNumber);
110
+ const latestAction = isRecord(raw.latestAction) ? raw.latestAction : undefined;
111
+ return {
112
+ billId: id,
113
+ congress,
114
+ billType,
115
+ billNumber,
116
+ title: str(raw.title) ?? null,
117
+ summary: summaries.get(id) ?? null,
118
+ originChamber: str(raw.originChamber) ?? null,
119
+ latestActionDate: latestAction ? (str(latestAction.actionDate) ?? null) : null,
120
+ latestActionText: latestAction ? (str(latestAction.text) ?? null) : null,
121
+ updateDate: str(raw.updateDate) ?? null,
122
+ };
123
+ }
124
+ /**
125
+ * Build the Congress bill ingester. The returned generator walks the configured
126
+ * congresses; for each, it loads the summary map, then streams bill pages
127
+ * (`sort=updateDate asc`) as full merged rows, advancing the durable checkpoint
128
+ * to the max `updateDate` seen.
129
+ *
130
+ * @param getApi - Accessor for the Congress API service (injectable for tests).
131
+ * @param congresses - Congress numbers to mirror.
132
+ */
133
+ export function createCongressBillIngester(getApi, congresses) {
134
+ return async function* sync({ mode, checkpoint, signal }) {
135
+ const api = getApi();
136
+ const fromDateTime = mode === 'refresh' && checkpoint ? toFromDateTime(checkpoint) : undefined;
137
+ let maxUpdate = checkpoint;
138
+ logger.info(`Congress bill mirror sync starting (mode=${mode}, congresses=${congresses.join(',')}${fromDateTime ? `, since=${fromDateTime}` : ''})`);
139
+ for (const congress of congresses) {
140
+ if (signal.aborted)
141
+ return;
142
+ const summaries = await buildSummaryMap(api, congress, signal);
143
+ logger.info(`Congress ${congress}: ${summaries.size} summaries loaded`);
144
+ let offset = 0;
145
+ let billsMirrored = 0;
146
+ while (!signal.aborted) {
147
+ const result = await api.listBills({
148
+ congress,
149
+ sort: 'updateDate asc',
150
+ limit: PAGE_SIZE,
151
+ offset,
152
+ ...(fromDateTime ? { fromDateTime } : {}),
153
+ });
154
+ const rows = result.data;
155
+ if (rows.length === 0)
156
+ break;
157
+ const records = [];
158
+ for (const raw of rows) {
159
+ if (!isRecord(raw))
160
+ continue;
161
+ const record = toBillRow(raw, congress, summaries);
162
+ if (!record)
163
+ continue;
164
+ records.push(record);
165
+ const updated = typeof record.updateDate === 'string' ? record.updateDate : undefined;
166
+ if (updated && (maxUpdate === undefined || updated > maxUpdate))
167
+ maxUpdate = updated;
168
+ }
169
+ billsMirrored += records.length;
170
+ yield { records, ...(maxUpdate ? { checkpoint: maxUpdate } : {}) };
171
+ if (rows.length < PAGE_SIZE || result.pagination.nextOffset == null)
172
+ break;
173
+ offset += PAGE_SIZE;
174
+ }
175
+ logger.info(`Congress ${congress}: ${billsMirrored} bills mirrored`);
176
+ }
177
+ };
178
+ }
179
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../../src/services/congress-mirror/ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @fileoverview Pure normalization helpers for the bill mirror — HTML→plain-text
3
+ * stripping (summaries ship as HTML; the FTS index and snippet surface want plain
4
+ * text) and FTS5 `MATCH` escaping (tokenize + individually quote so a stray `-`,
5
+ * an unbalanced quote, or an FTS5 reserved word can't break the query). No I/O.
6
+ * @module services/congress-mirror/normalize
7
+ */
8
+ /**
9
+ * Strip HTML tags to plain text and decode the handful of entities Congress.gov
10
+ * emits in summary bodies. Block-ish tags collapse to spaces so adjacent words
11
+ * don't fuse (`</p><p>` → space), and whitespace is collapsed to single spaces —
12
+ * a discovery/snippet surface doesn't need paragraph structure, and flat text
13
+ * tokenizes more cleanly for FTS5.
14
+ */
15
+ export declare function htmlToPlainText(html: string): string;
16
+ /**
17
+ * Build an FTS5 `MATCH` expression from free-text input. Each token is stripped
18
+ * of embedded quotes and wrapped in double quotes, which neutralizes every FTS5
19
+ * operator (`-`, `*`, `:`, `NEAR`, `AND`, `OR`, `NOT`) so punctuation or a
20
+ * reserved word can't raise a SQLite syntax error. Tokens are AND-combined
21
+ * (implicit-AND across terms). Returns an empty string when the input has no
22
+ * searchable tokens — callers treat that as "no match", not a thrown query.
23
+ */
24
+ export declare function toFtsMatch(input: string): string;
25
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../src/services/congress-mirror/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcpD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @fileoverview Pure normalization helpers for the bill mirror — HTML→plain-text
3
+ * stripping (summaries ship as HTML; the FTS index and snippet surface want plain
4
+ * text) and FTS5 `MATCH` escaping (tokenize + individually quote so a stray `-`,
5
+ * an unbalanced quote, or an FTS5 reserved word can't break the query). No I/O.
6
+ * @module services/congress-mirror/normalize
7
+ */
8
+ /**
9
+ * Strip HTML tags to plain text and decode the handful of entities Congress.gov
10
+ * emits in summary bodies. Block-ish tags collapse to spaces so adjacent words
11
+ * don't fuse (`</p><p>` → space), and whitespace is collapsed to single spaces —
12
+ * a discovery/snippet surface doesn't need paragraph structure, and flat text
13
+ * tokenizes more cleanly for FTS5.
14
+ */
15
+ export function htmlToPlainText(html) {
16
+ return html
17
+ .replace(/<\s*br\s*\/?\s*>/gi, ' ')
18
+ .replace(/<\s*\/?(p|div|li|ul|ol|h[1-6]|tr|table)\b[^>]*>/gi, ' ')
19
+ .replace(/<[^>]*>/g, '')
20
+ .replace(/&nbsp;/g, ' ')
21
+ .replace(/&#039;/g, "'")
22
+ .replace(/&#39;/g, "'")
23
+ .replace(/&amp;/g, '&')
24
+ .replace(/&lt;/g, '<')
25
+ .replace(/&gt;/g, '>')
26
+ .replace(/&quot;/g, '"')
27
+ .replace(/\s+/g, ' ')
28
+ .trim();
29
+ }
30
+ /**
31
+ * Build an FTS5 `MATCH` expression from free-text input. Each token is stripped
32
+ * of embedded quotes and wrapped in double quotes, which neutralizes every FTS5
33
+ * operator (`-`, `*`, `:`, `NEAR`, `AND`, `OR`, `NOT`) so punctuation or a
34
+ * reserved word can't raise a SQLite syntax error. Tokens are AND-combined
35
+ * (implicit-AND across terms). Returns an empty string when the input has no
36
+ * searchable tokens — callers treat that as "no match", not a thrown query.
37
+ */
38
+ export function toFtsMatch(input) {
39
+ return input
40
+ .trim()
41
+ .split(/\s+/)
42
+ .map((t) => t.replace(/"/g, '').trim())
43
+ .filter((t) => t.length > 0)
44
+ .map((t) => `"${t}"`)
45
+ .join(' AND ');
46
+ }
47
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../src/services/congress-mirror/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI;SACR,OAAO,CAAC,oBAAoB,EAAE,GAAG,CAAC;SAClC,OAAO,CAAC,mDAAmD,EAAE,GAAG,CAAC;SACjE,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK;SACT,IAAI,EAAE;SACN,KAAK,CAAC,KAAK,CAAC;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;SACtC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;SACpB,IAAI,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @fileoverview Mirror store schema for the Congress bill FTS index. A single
3
+ * MirrorService-owned table (`bills`) with an FTS5 index over `title` + `summary`
4
+ * and secondary indexes on the filterable columns. The primary key is the derived
5
+ * `billId` (`{congress}/{billType}/{billNumber}`) — a single column, matching the
6
+ * `congress://bill/{congress}/{billType}/{billNumber}` resource addressing.
7
+ * @module services/congress-mirror/schema
8
+ */
9
+ import type { SqliteMirrorStoreSpec } from '@cyanheads/mcp-ts-core/mirror';
10
+ /** Primary table name (the MirrorService-owned table). */
11
+ export declare const BILLS_TABLE = "bills";
12
+ /**
13
+ * The MirrorService primary-table spec. One row per bill, merged from the bill
14
+ * list (title, chamber, latest action, update date) and the CRS summaries list
15
+ * (summary text). `title` + `summary` are FTS-indexed; `congress`, `billType`,
16
+ * and `originChamber` back the exact-match search filters.
17
+ */
18
+ export declare function billStoreSpec(path: string): SqliteMirrorStoreSpec;
19
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/services/congress-mirror/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,0DAA0D;AAC1D,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CA0BjE"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @fileoverview Mirror store schema for the Congress bill FTS index. A single
3
+ * MirrorService-owned table (`bills`) with an FTS5 index over `title` + `summary`
4
+ * and secondary indexes on the filterable columns. The primary key is the derived
5
+ * `billId` (`{congress}/{billType}/{billNumber}`) — a single column, matching the
6
+ * `congress://bill/{congress}/{billType}/{billNumber}` resource addressing.
7
+ * @module services/congress-mirror/schema
8
+ */
9
+ /** Primary table name (the MirrorService-owned table). */
10
+ export const BILLS_TABLE = 'bills';
11
+ /**
12
+ * The MirrorService primary-table spec. One row per bill, merged from the bill
13
+ * list (title, chamber, latest action, update date) and the CRS summaries list
14
+ * (summary text). `title` + `summary` are FTS-indexed; `congress`, `billType`,
15
+ * and `originChamber` back the exact-match search filters.
16
+ */
17
+ export function billStoreSpec(path) {
18
+ return {
19
+ path,
20
+ table: BILLS_TABLE,
21
+ primaryKey: 'billId',
22
+ version: 1,
23
+ columns: {
24
+ billId: 'TEXT',
25
+ congress: 'INTEGER',
26
+ billType: 'TEXT',
27
+ billNumber: 'INTEGER',
28
+ title: 'TEXT',
29
+ summary: 'TEXT',
30
+ originChamber: 'TEXT',
31
+ latestActionDate: 'TEXT',
32
+ latestActionText: 'TEXT',
33
+ updateDate: 'TEXT',
34
+ },
35
+ fts: ['title', 'summary'],
36
+ indexes: [
37
+ { columns: ['congress'] },
38
+ { columns: ['billType'] },
39
+ { columns: ['originChamber'] },
40
+ { columns: ['updateDate'] },
41
+ ],
42
+ };
43
+ }
44
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/services/congress-mirror/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,WAAW;QAClB,UAAU,EAAE,QAAQ;QACpB,OAAO,EAAE,CAAC;QACV,OAAO,EAAE;YACP,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,MAAM;YAChB,UAAU,EAAE,SAAS;YACrB,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,MAAM;YACf,aAAa,EAAE,MAAM;YACrB,gBAAgB,EAAE,MAAM;YACxB,gBAAgB,EAAE,MAAM;YACxB,UAAU,EAAE,MAAM;SACnB;QACD,GAAG,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;QACzB,OAAO,EAAE;YACP,EAAE,OAAO,EAAE,CAAC,UAAU,CAAC,EAAE;YACzB,EAAE,OAAO,EAAE,CAAC,UAAU,CAAC,EAAE;YACzB,EAAE,OAAO,EAAE,CAAC,eAAe,CAAC,EAAE;YAC9B,EAAE,OAAO,EAAE,CAAC,YAAY,CAAC,EAAE;SAC5B;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Domain types for the Congress bill FTS mirror — the merged row
3
+ * shape, the search filters, and the search page envelope. The mirror row is the
4
+ * single-table projection of two upstream Congress.gov sources (the bill list and
5
+ * the CRS summaries list) keyed by a derived `billId`.
6
+ * @module services/congress-mirror/types
7
+ */
8
+ import type { BillType } from '../../services/congress-api/types.js';
9
+ /**
10
+ * One mirrored bill — a flat row keyed by the derived `billId`
11
+ * (`{congress}/{billType}/{billNumber}`). `title` comes from the bill list;
12
+ * `summary` from the CRS summaries list (HTML-stripped, latest version), nullable
13
+ * because CRS summarizes only a fraction of bills.
14
+ */
15
+ export interface BillMirrorRow {
16
+ billId: string;
17
+ billNumber: number;
18
+ billType: string;
19
+ congress: number;
20
+ latestActionDate: string | null;
21
+ latestActionText: string | null;
22
+ originChamber: string | null;
23
+ summary: string | null;
24
+ title: string | null;
25
+ updateDate: string | null;
26
+ }
27
+ /** Filters accepted by the keyword search. */
28
+ export interface SearchBillsFilters {
29
+ billType?: BillType | undefined;
30
+ congress?: number | undefined;
31
+ limit: number;
32
+ offset: number;
33
+ originChamber?: string | undefined;
34
+ query: string;
35
+ }
36
+ /** A single search result — a merged mirror row projected for display. */
37
+ export interface SearchBillResult {
38
+ billId: string;
39
+ billNumber: number;
40
+ billType: string;
41
+ congress: number;
42
+ latestActionDate?: string;
43
+ latestActionText?: string;
44
+ originChamber?: string;
45
+ summaryPreview?: string;
46
+ title: string;
47
+ }
48
+ /** Search page returned by the mirror service. */
49
+ export interface SearchBillsPage {
50
+ items: SearchBillResult[];
51
+ limit: number;
52
+ offset: number;
53
+ total: number;
54
+ }
55
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/congress-mirror/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAEjE;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Domain types for the Congress bill FTS mirror — the merged row
3
+ * shape, the search filters, and the search page envelope. The mirror row is the
4
+ * single-table projection of two upstream Congress.gov sources (the bill list and
5
+ * the CRS summaries list) keyed by a derived `billId`.
6
+ * @module services/congress-mirror/types
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/congress-mirror/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/congressgov-mcp-server",
3
- "version": "0.3.32",
3
+ "version": "0.4.0",
4
4
  "description": "Access U.S. congressional data - bills, votes, members, committees - through MCP. STDIO & Streamable HTTP.",
5
5
  "mcpName": "io.github.cyanheads/congressgov-mcp-server",
6
6
  "type": "module",
@@ -9,7 +9,19 @@
9
9
  "bin": {
10
10
  "congressgov-mcp-server": "dist/index.js"
11
11
  },
12
- "files": ["dist/", "README.md", "LICENSE", "CLAUDE.md", "AGENTS.md", "Dockerfile", "server.json"],
12
+ "files": [
13
+ "dist/",
14
+ "scripts/_mirror-context.ts",
15
+ "scripts/congress-mirror-init.ts",
16
+ "scripts/congress-mirror-refresh.ts",
17
+ "scripts/congress-mirror-verify.ts",
18
+ "README.md",
19
+ "LICENSE",
20
+ "CLAUDE.md",
21
+ "AGENTS.md",
22
+ "Dockerfile",
23
+ "server.json"
24
+ ],
13
25
  "scripts": {
14
26
  "build": "bun scripts/build.ts",
15
27
  "rebuild": "bun scripts/clean.ts && bun scripts/build.ts",
@@ -31,7 +43,10 @@
31
43
  "dev:stdio": "MCP_TRANSPORT_TYPE=stdio bun --watch src/index.ts",
32
44
  "dev:http": "MCP_TRANSPORT_TYPE=http bun --watch src/index.ts",
33
45
  "start:stdio": "MCP_TRANSPORT_TYPE=stdio node dist/index.js",
34
- "start:http": "MCP_TRANSPORT_TYPE=http node dist/index.js"
46
+ "start:http": "MCP_TRANSPORT_TYPE=http node dist/index.js",
47
+ "mirror:init": "bun run scripts/congress-mirror-init.ts",
48
+ "mirror:refresh": "bun run scripts/congress-mirror-refresh.ts",
49
+ "mirror:verify": "bun run scripts/congress-mirror-verify.ts"
35
50
  },
36
51
  "keywords": [
37
52
  "mcp",
@@ -79,16 +94,27 @@
79
94
  "dependencies": {
80
95
  "@cyanheads/mcp-ts-core": "^0.10.10",
81
96
  "fast-xml-parser": "^5.9.3",
97
+ "node-cron": "^4.4.1",
82
98
  "pino-pretty": "^13.1.3",
83
99
  "zod": "^4.4.3"
84
100
  },
101
+ "peerDependencies": {
102
+ "better-sqlite3": "^12.0.0"
103
+ },
104
+ "peerDependenciesMeta": {
105
+ "better-sqlite3": {
106
+ "optional": true
107
+ }
108
+ },
85
109
  "devDependencies": {
86
110
  "@biomejs/biome": "^2.5.1",
87
111
  "@types/node": "^26.0.1",
112
+ "better-sqlite3": "^12.11.1",
88
113
  "depcheck": "^1.4.7",
89
114
  "ignore": "^7.0.5",
90
115
  "tsc-alias": "^1.8.17",
91
116
  "typescript": "^6.0.3",
92
117
  "vitest": "^4.1.9"
93
- }
118
+ },
119
+ "trustedDependencies": ["better-sqlite3"]
94
120
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @fileoverview Shared bootstrap for the mirror lifecycle CLI scripts
3
+ * (congress-mirror-init / congress-mirror-refresh / congress-mirror-verify).
4
+ * Builds the Congress API client and the mirror service from the environment and
5
+ * hands back the underlying mirror — no MCP transport, no tool registration.
6
+ * Imported by the three named scripts, so it must travel with them in the npm
7
+ * tarball and the Docker image.
8
+ * @module scripts/_mirror-context
9
+ */
10
+
11
+ import type { Mirror } from '@cyanheads/mcp-ts-core/mirror';
12
+ import { getServerConfig } from '@/config/server-config.js';
13
+ import { initCongressApi } from '@/services/congress-api/congress-api-service.js';
14
+ import { initCongressMirror } from '@/services/congress-mirror/congress-mirror-service.js';
15
+
16
+ /**
17
+ * Build the mirror from env config and return its instance. The CLI builds the
18
+ * mirror regardless of `CONGRESS_MIRROR_ENABLED` (that flag gates the tool and the
19
+ * in-process scheduler, not out-of-band index construction); the ingester pages
20
+ * the live Congress.gov API via `CongressApiService`, so init it here.
21
+ */
22
+ export function getMirror(): Mirror {
23
+ const config = getServerConfig();
24
+ initCongressApi();
25
+ const service = initCongressMirror({
26
+ mirrorPath: config.mirrorPath,
27
+ congresses: config.congresses,
28
+ });
29
+ return service.mirrorInstance;
30
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * @fileoverview mirror:init — full out-of-band build of the Congress bill FTS
4
+ * mirror. Pages the bill list and CRS summaries for the configured congress
5
+ * window, merges them into one row per bill, and writes the SQLite index.
6
+ * Idempotent (the framework upserts by primary key) — safe to re-run after an
7
+ * interrupt. Run as a one-shot job or at Docker build, never on server startup.
8
+ * @module scripts/congress-mirror-init
9
+ */
10
+
11
+ import { logger } from '@cyanheads/mcp-ts-core/utils';
12
+ import { getMirror } from './_mirror-context.js';
13
+
14
+ const mirror = getMirror();
15
+ logger.info('Starting Congress bill mirror init (full build)');
16
+
17
+ const result = await mirror.runSync({ mode: 'init', signal: AbortSignal.timeout(3_600_000) });
18
+
19
+ logger.info(
20
+ `Congress bill mirror init complete: ${result.recordsApplied} records applied across ${result.pagesFetched} pages (total ${result.total}).`,
21
+ );
22
+ await mirror.close();
23
+ process.exit(0);
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * @fileoverview mirror:refresh — incremental refresh of the Congress bill FTS
4
+ * mirror. Re-scans bills updated since the durable checkpoint and re-walks the
5
+ * CRS summaries, merging them into the existing index; the index stays
6
+ * transactionally queryable throughout. Run out-of-band for stdio deployments
7
+ * (the HTTP server schedules this on a cron when CONGRESS_MIRROR_REFRESH_CRON is
8
+ * set).
9
+ * @module scripts/congress-mirror-refresh
10
+ */
11
+
12
+ import { logger } from '@cyanheads/mcp-ts-core/utils';
13
+ import { getMirror } from './_mirror-context.js';
14
+
15
+ const mirror = getMirror();
16
+ logger.info('Starting Congress bill mirror refresh');
17
+
18
+ const result = await mirror.runSync({ mode: 'refresh', signal: AbortSignal.timeout(3_600_000) });
19
+
20
+ logger.info(
21
+ `Congress bill mirror refresh complete: ${result.recordsApplied} records applied (total ${result.total}).`,
22
+ );
23
+ await mirror.close();
24
+ process.exit(0);
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * @fileoverview mirror:verify — report the Congress bill mirror health: readiness,
4
+ * sync status, last completion, record count, and a SQLite integrity check. Exits
5
+ * non-zero when the mirror is not ready or integrity fails, so it can gate a
6
+ * deployment or a post-build smoke check.
7
+ * @module scripts/congress-mirror-verify
8
+ */
9
+
10
+ import { logger } from '@cyanheads/mcp-ts-core/utils';
11
+ import { getMirror } from './_mirror-context.js';
12
+
13
+ const mirror = getMirror();
14
+ const status = await mirror.status();
15
+
16
+ logger.info(
17
+ `Congress bill mirror status: ready=${status.ready}, status=${status.status}, total=${status.total ?? 0}, completedAt=${status.completedAt ?? 'never'}`,
18
+ );
19
+ if (status.error) logger.warning(`Last sync error: ${status.error}`);
20
+
21
+ if (!status.ready) {
22
+ logger.error('Mirror is NOT ready — run mirror:init to build the index.');
23
+ await mirror.close();
24
+ process.exit(1);
25
+ }
26
+
27
+ const integrity = await mirror.store.integrityCheck();
28
+ if (!integrity.ok) {
29
+ logger.error(`SQLite integrity check FAILED: ${integrity.results.join('; ')}`);
30
+ await mirror.close();
31
+ process.exit(1);
32
+ }
33
+
34
+ logger.info('Congress bill mirror integrity check passed.');
35
+ await mirror.close();
36
+ process.exit(0);
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/cyanheads/congressgov-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.3.32",
9
+ "version": "0.4.0",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",
@@ -19,7 +19,7 @@
19
19
  "registryBaseUrl": "https://registry.npmjs.org",
20
20
  "identifier": "@cyanheads/congressgov-mcp-server",
21
21
  "runtimeHint": "bun",
22
- "version": "0.3.32",
22
+ "version": "0.4.0",
23
23
  "packageArguments": [
24
24
  {
25
25
  "type": "positional",
@@ -54,7 +54,7 @@
54
54
  "registryBaseUrl": "https://registry.npmjs.org",
55
55
  "identifier": "@cyanheads/congressgov-mcp-server",
56
56
  "runtimeHint": "bun",
57
- "version": "0.3.32",
57
+ "version": "0.4.0",
58
58
  "packageArguments": [
59
59
  {
60
60
  "type": "positional",