@cyanheads/congressgov-mcp-server 0.4.3 → 0.5.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 (47) hide show
  1. package/AGENTS.md +19 -5
  2. package/CLAUDE.md +19 -5
  3. package/README.md +3 -1
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/mcp-server/prompts/definitions/legislative-research.prompt.d.ts +9 -0
  7. package/dist/mcp-server/prompts/definitions/legislative-research.prompt.d.ts.map +1 -1
  8. package/dist/mcp-server/prompts/definitions/legislative-research.prompt.js +46 -15
  9. package/dist/mcp-server/prompts/definitions/legislative-research.prompt.js.map +1 -1
  10. package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +38 -0
  11. package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +72 -4
  13. package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts +37 -0
  15. package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/committee-reports.tool.js +66 -7
  17. package/dist/mcp-server/tools/definitions/committee-reports.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts +41 -3
  19. package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/daily-record.tool.js +61 -8
  21. package/dist/mcp-server/tools/definitions/daily-record.tool.js.map +1 -1
  22. package/dist/mcp-server/tools/format-helpers.d.ts +9 -5
  23. package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/format-helpers.js +397 -89
  25. package/dist/mcp-server/tools/format-helpers.js.map +1 -1
  26. package/dist/mcp-server/tools/tool-helpers.d.ts +67 -0
  27. package/dist/mcp-server/tools/tool-helpers.d.ts.map +1 -1
  28. package/dist/mcp-server/tools/tool-helpers.js +85 -0
  29. package/dist/mcp-server/tools/tool-helpers.js.map +1 -1
  30. package/dist/services/congress-documents/congress-documents-service.d.ts +71 -0
  31. package/dist/services/congress-documents/congress-documents-service.d.ts.map +1 -0
  32. package/dist/services/congress-documents/congress-documents-service.js +271 -0
  33. package/dist/services/congress-documents/congress-documents-service.js.map +1 -0
  34. package/dist/services/congress-documents/document-formats.d.ts +29 -0
  35. package/dist/services/congress-documents/document-formats.d.ts.map +1 -0
  36. package/dist/services/congress-documents/document-formats.js +51 -0
  37. package/dist/services/congress-documents/document-formats.js.map +1 -0
  38. package/dist/services/congress-documents/extract-text.d.ts +21 -0
  39. package/dist/services/congress-documents/extract-text.d.ts.map +1 -0
  40. package/dist/services/congress-documents/extract-text.js +81 -0
  41. package/dist/services/congress-documents/extract-text.js.map +1 -0
  42. package/dist/services/congress-documents/types.d.ts +45 -0
  43. package/dist/services/congress-documents/types.d.ts.map +1 -0
  44. package/dist/services/congress-documents/types.js +6 -0
  45. package/dist/services/congress-documents/types.js.map +1 -0
  46. package/package.json +1 -1
  47. package/server.json +3 -3
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @fileoverview Bounded retrieval of legislative document text from www.congress.gov.
3
+ *
4
+ * The document bodies behind Congress.gov's format URLs live on `www.congress.gov`
5
+ * — a different host from the `api.congress.gov` that `CongressApiService` is
6
+ * scoped to, with no API key, no JSON, and documents that run past a megabyte.
7
+ * That is a second upstream, not a second method on the API client, so it gets
8
+ * its own service the way `SenateVoteService` does for the Senate LIS feed.
9
+ *
10
+ * Two bounds, deliberately separate:
11
+ * - **The fetch** is bounded by {@link MAX_DOCUMENT_BYTES}, a request timeout, and
12
+ * a content-type allowlist. An oversized document is refused — before the body
13
+ * is read when Content-Length says so, mid-stream when it does not — rather
14
+ * than silently truncated or pulled whole into memory.
15
+ * - **The response** is bounded by an exact character offset/limit window. Offsets
16
+ * index into the extracted plain text and are never snapped to section or
17
+ * paragraph breaks, so feeding `nextOffset` back walks a document with every
18
+ * character returned exactly once — no overlap, no gap.
19
+ *
20
+ * @module services/congress-documents/congress-documents-service
21
+ */
22
+ import type { Context } from '@cyanheads/mcp-ts-core';
23
+ import type { DocumentContent, FetchDocumentParams } from './types.js';
24
+ /**
25
+ * Ceiling on a single document. The largest body observed across bills,
26
+ * committee reports, and the Daily Record is the 119th Congress H.R. 1 USLM XML
27
+ * at ~2.7 MB (its Formatted Text is ~1.0 MB), so this clears real documents with
28
+ * room to spare while still refusing a pathological one.
29
+ */
30
+ export declare const MAX_DOCUMENT_BYTES = 5000000;
31
+ /** Bounds the whole exchange, headers and body — a megabyte-scale body needs the room. */
32
+ export declare const REQUEST_TIMEOUT_MS = 30000;
33
+ export declare class CongressDocumentsService {
34
+ private readonly maxDocumentBytes;
35
+ constructor(maxDocumentBytes?: number);
36
+ /**
37
+ * Fetch one document and return the requested character window of its text.
38
+ *
39
+ * `url` comes from Congress.gov's own format metadata — a caller never supplies
40
+ * one — and is still checked against {@link ALLOWED_HOSTS} before any request
41
+ * goes out, so a redirected or malformed upstream link cannot turn this into a
42
+ * general-purpose fetcher.
43
+ */
44
+ fetchDocument(params: FetchDocumentParams, ctx: Context): Promise<DocumentContent>;
45
+ /** Slice the exact `[offset, offset + limit)` window and describe what remains. */
46
+ private window;
47
+ private assertAllowedHost;
48
+ private doFetch;
49
+ /**
50
+ * A 404 means Congress.gov's metadata named a document its own file host does
51
+ * not hold — a missing document, not a transport failure. Everything else
52
+ * (5xx, timeout, socket error) is the transport, and stays retryable.
53
+ */
54
+ private classifyFetchError;
55
+ private assertContentType;
56
+ /** Refuse an oversized document on its advertised length, before reading a byte of it. */
57
+ private assertAdvertisedSize;
58
+ /**
59
+ * Read the body under a hard byte budget, cancelling the stream the moment it
60
+ * is exceeded. Chunks are decoded once at the end so a multi-byte character
61
+ * split across a chunk boundary still decodes correctly.
62
+ */
63
+ private readBounded;
64
+ private tooLarge;
65
+ private isRetryableError;
66
+ private getRequestContext;
67
+ private getAbortSignal;
68
+ }
69
+ export declare function initCongressDocuments(): void;
70
+ export declare function getCongressDocuments(): CongressDocumentsService;
71
+ //# sourceMappingURL=congress-documents-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"congress-documents-service.d.ts","sourceRoot":"","sources":["../../../src/services/congress-documents/congress-documents-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAUtD,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,UAAY,CAAC;AAE5C,0FAA0F;AAC1F,eAAO,MAAM,kBAAkB,QAAS,CAAC;AA4CzC,qBAAa,wBAAwB;IACvB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAA7C,YAA6B,gBAAgB,GAAE,MAA2B,EAAI;IAE9E;;;;;;;OAOG;IACG,aAAa,CAAC,MAAM,EAAE,mBAAmB,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAkBvF;IAED,mFAAmF;IACnF,OAAO,CAAC,MAAM;IA8Bd,OAAO,CAAC,iBAAiB;YAoBX,OAAO;IAsBrB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAqB1B,OAAO,CAAC,iBAAiB;IAgBzB,0FAA0F;IAC1F,OAAO,CAAC,oBAAoB;IAQ5B;;;;OAIG;YACW,WAAW;IAoCzB,OAAO,CAAC,QAAQ;IAahB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,cAAc;CAIvB;AAID,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,oBAAoB,IAAI,wBAAwB,CAO/D"}
@@ -0,0 +1,271 @@
1
+ /**
2
+ * @fileoverview Bounded retrieval of legislative document text from www.congress.gov.
3
+ *
4
+ * The document bodies behind Congress.gov's format URLs live on `www.congress.gov`
5
+ * — a different host from the `api.congress.gov` that `CongressApiService` is
6
+ * scoped to, with no API key, no JSON, and documents that run past a megabyte.
7
+ * That is a second upstream, not a second method on the API client, so it gets
8
+ * its own service the way `SenateVoteService` does for the Senate LIS feed.
9
+ *
10
+ * Two bounds, deliberately separate:
11
+ * - **The fetch** is bounded by {@link MAX_DOCUMENT_BYTES}, a request timeout, and
12
+ * a content-type allowlist. An oversized document is refused — before the body
13
+ * is read when Content-Length says so, mid-stream when it does not — rather
14
+ * than silently truncated or pulled whole into memory.
15
+ * - **The response** is bounded by an exact character offset/limit window. Offsets
16
+ * index into the extracted plain text and are never snapped to section or
17
+ * paragraph breaks, so feeding `nextOffset` back walks a document with every
18
+ * character returned exactly once — no overlap, no gap.
19
+ *
20
+ * @module services/congress-documents/congress-documents-service
21
+ */
22
+ import { invalidParams, JsonRpcErrorCode, McpError, notFound, serviceUnavailable, } from '@cyanheads/mcp-ts-core/errors';
23
+ import { fetchWithTimeout, withRetry } from '@cyanheads/mcp-ts-core/utils';
24
+ import { extractDocumentText } from './extract-text.js';
25
+ /**
26
+ * Ceiling on a single document. The largest body observed across bills,
27
+ * committee reports, and the Daily Record is the 119th Congress H.R. 1 USLM XML
28
+ * at ~2.7 MB (its Formatted Text is ~1.0 MB), so this clears real documents with
29
+ * room to spare while still refusing a pathological one.
30
+ */
31
+ export const MAX_DOCUMENT_BYTES = 5_000_000;
32
+ /** Bounds the whole exchange, headers and body — a megabyte-scale body needs the room. */
33
+ export const REQUEST_TIMEOUT_MS = 30_000;
34
+ const MAX_ATTEMPTS = 3;
35
+ const BASE_BACKOFF_MS = 500;
36
+ /** Every format URL these tools resolve points here; nothing else is fetched. */
37
+ const ALLOWED_HOSTS = new Set(['www.congress.gov']);
38
+ /** Content types the text extractor can read. PDF and everything else are refused. */
39
+ const ALLOWED_CONTENT_TYPES = new Set(['application/xml', 'text/html', 'text/plain', 'text/xml']);
40
+ /**
41
+ * Contract reasons this service raises. The matching `recovery` hints live once
42
+ * in `documentErrorContracts` (tool-helpers) and reach the wire via
43
+ * `ctx.recoveryFor` — services have no `ctx.fail`, but carrying `reason` in
44
+ * `data` plus the resolved hint gives clients the same shape.
45
+ */
46
+ const DOCUMENT_UNAVAILABLE = 'document_unavailable';
47
+ const DOCUMENT_FETCH_FAILED = 'document_fetch_failed';
48
+ const DOCUMENT_TOO_LARGE = 'document_too_large';
49
+ const OFFSET_PAST_END = 'offset_past_end';
50
+ function isNativeAbortSignal(value) {
51
+ if (typeof AbortSignal !== 'function' ||
52
+ typeof AbortSignal.prototype.throwIfAborted !== 'function' ||
53
+ !value) {
54
+ return false;
55
+ }
56
+ try {
57
+ AbortSignal.prototype.throwIfAborted.call(value);
58
+ return true;
59
+ }
60
+ catch (error) {
61
+ return !(error instanceof TypeError);
62
+ }
63
+ }
64
+ export class CongressDocumentsService {
65
+ maxDocumentBytes;
66
+ constructor(maxDocumentBytes = MAX_DOCUMENT_BYTES) {
67
+ this.maxDocumentBytes = maxDocumentBytes;
68
+ }
69
+ /**
70
+ * Fetch one document and return the requested character window of its text.
71
+ *
72
+ * `url` comes from Congress.gov's own format metadata — a caller never supplies
73
+ * one — and is still checked against {@link ALLOWED_HOSTS} before any request
74
+ * goes out, so a redirected or malformed upstream link cannot turn this into a
75
+ * general-purpose fetcher.
76
+ */
77
+ async fetchDocument(params, ctx) {
78
+ this.assertAllowedHost(params.url, ctx);
79
+ const operation = 'CongressDocumentsService GET document';
80
+ const requestContext = this.getRequestContext(ctx, operation);
81
+ const signal = this.getAbortSignal(ctx);
82
+ const body = await withRetry(() => this.doFetch(params.url, requestContext, ctx, signal), {
83
+ operation,
84
+ context: requestContext,
85
+ baseDelayMs: BASE_BACKOFF_MS,
86
+ maxRetries: MAX_ATTEMPTS - 1,
87
+ isTransient: (error) => this.isRetryableError(error),
88
+ ...(signal ? { signal } : {}),
89
+ });
90
+ const text = extractDocumentText(body);
91
+ return this.window(text, params, ctx);
92
+ }
93
+ /** Slice the exact `[offset, offset + limit)` window and describe what remains. */
94
+ window(text, params, ctx) {
95
+ const totalCharacters = text.length;
96
+ const offset = params.characterOffset;
97
+ /** An empty document is a real (if unusual) answer at offset 0 — not a bad offset. */
98
+ if (totalCharacters > 0 && offset >= totalCharacters) {
99
+ throw invalidParams(`characterOffset ${offset} is past the end of this document, which has ${totalCharacters} characters.`, {
100
+ reason: OFFSET_PAST_END,
101
+ ...ctx.recoveryFor(OFFSET_PAST_END),
102
+ totalCharacters,
103
+ offset,
104
+ retryable: false,
105
+ });
106
+ }
107
+ const slice = text.slice(offset, offset + params.characterLimit);
108
+ const end = offset + slice.length;
109
+ const truncated = end < totalCharacters;
110
+ return {
111
+ text: slice,
112
+ totalCharacters,
113
+ offset,
114
+ truncated,
115
+ nextOffset: truncated ? end : null,
116
+ };
117
+ }
118
+ assertAllowedHost(url, ctx) {
119
+ let host;
120
+ try {
121
+ const parsed = new URL(url);
122
+ if (parsed.protocol === 'https:' || parsed.protocol === 'http:')
123
+ host = parsed.hostname;
124
+ }
125
+ catch {
126
+ host = undefined;
127
+ }
128
+ if (host === undefined || !ALLOWED_HOSTS.has(host.toLowerCase())) {
129
+ throw notFound('The upstream metadata points at a document this server does not retrieve — only documents hosted on www.congress.gov are read.', {
130
+ reason: DOCUMENT_UNAVAILABLE,
131
+ ...ctx.recoveryFor(DOCUMENT_UNAVAILABLE),
132
+ retryable: false,
133
+ });
134
+ }
135
+ }
136
+ async doFetch(url, requestContext, ctx, signal) {
137
+ let response;
138
+ try {
139
+ response = await fetchWithTimeout(url, REQUEST_TIMEOUT_MS, requestContext, {
140
+ headers: { Accept: 'text/html, application/xml, text/plain, */*' },
141
+ expectedStatuses: [404],
142
+ ...(signal ? { signal } : {}),
143
+ });
144
+ }
145
+ catch (error) {
146
+ throw this.classifyFetchError(error, ctx);
147
+ }
148
+ this.assertContentType(response, ctx);
149
+ this.assertAdvertisedSize(response, ctx);
150
+ return this.readBounded(response, ctx);
151
+ }
152
+ /**
153
+ * A 404 means Congress.gov's metadata named a document its own file host does
154
+ * not hold — a missing document, not a transport failure. Everything else
155
+ * (5xx, timeout, socket error) is the transport, and stays retryable.
156
+ */
157
+ classifyFetchError(error, ctx) {
158
+ if (!(error instanceof McpError))
159
+ return error;
160
+ if (error.code === JsonRpcErrorCode.NotFound) {
161
+ return notFound('Congress.gov does not hold the document its metadata pointed to.', {
162
+ reason: DOCUMENT_UNAVAILABLE,
163
+ ...ctx.recoveryFor(DOCUMENT_UNAVAILABLE),
164
+ retryable: false,
165
+ }, { cause: error });
166
+ }
167
+ /** The framework message embeds the request URL; re-message without echoing it. */
168
+ return serviceUnavailable('Unable to retrieve the document from Congress.gov.', { reason: DOCUMENT_FETCH_FAILED, ...ctx.recoveryFor(DOCUMENT_FETCH_FAILED) }, { cause: error });
169
+ }
170
+ assertContentType(response, ctx) {
171
+ const raw = response.headers.get('content-type') ?? '';
172
+ const mediaType = raw.split(';')[0]?.trim().toLowerCase() ?? '';
173
+ if (ALLOWED_CONTENT_TYPES.has(mediaType))
174
+ return;
175
+ throw serviceUnavailable(`Congress.gov returned content type '${mediaType || 'unknown'}', which this server cannot read as text.`, {
176
+ reason: DOCUMENT_FETCH_FAILED,
177
+ ...ctx.recoveryFor(DOCUMENT_FETCH_FAILED),
178
+ contentType: mediaType,
179
+ /** A wrong media type is deterministic — retrying re-downloads the same bytes. */
180
+ retryable: false,
181
+ });
182
+ }
183
+ /** Refuse an oversized document on its advertised length, before reading a byte of it. */
184
+ assertAdvertisedSize(response, ctx) {
185
+ const header = response.headers.get('content-length');
186
+ if (header === null)
187
+ return;
188
+ const contentLength = Number(header);
189
+ if (!Number.isFinite(contentLength) || contentLength <= this.maxDocumentBytes)
190
+ return;
191
+ throw this.tooLarge(ctx, { contentLength });
192
+ }
193
+ /**
194
+ * Read the body under a hard byte budget, cancelling the stream the moment it
195
+ * is exceeded. Chunks are decoded once at the end so a multi-byte character
196
+ * split across a chunk boundary still decodes correctly.
197
+ */
198
+ async readBounded(response, ctx) {
199
+ const reader = response.body?.getReader();
200
+ const chunks = [];
201
+ let bytes = 0;
202
+ if (reader) {
203
+ for (;;) {
204
+ const { done, value } = await reader.read();
205
+ if (done)
206
+ break;
207
+ if (!value)
208
+ continue;
209
+ bytes += value.byteLength;
210
+ if (bytes > this.maxDocumentBytes) {
211
+ await reader.cancel().catch(() => undefined);
212
+ throw this.tooLarge(ctx, { bytesRead: bytes });
213
+ }
214
+ chunks.push(value);
215
+ }
216
+ }
217
+ if (bytes === 0) {
218
+ throw serviceUnavailable('Congress.gov returned an empty document body.', {
219
+ reason: DOCUMENT_FETCH_FAILED,
220
+ ...ctx.recoveryFor(DOCUMENT_FETCH_FAILED),
221
+ retryable: false,
222
+ });
223
+ }
224
+ const merged = new Uint8Array(bytes);
225
+ let at = 0;
226
+ for (const chunk of chunks) {
227
+ merged.set(chunk, at);
228
+ at += chunk.byteLength;
229
+ }
230
+ return new TextDecoder('utf-8').decode(merged);
231
+ }
232
+ tooLarge(ctx, data) {
233
+ return invalidParams(`This document exceeds the ${this.maxDocumentBytes.toLocaleString('en-US')}-byte ceiling this server retrieves.`, {
234
+ reason: DOCUMENT_TOO_LARGE,
235
+ ...ctx.recoveryFor(DOCUMENT_TOO_LARGE),
236
+ ...data,
237
+ maxBytes: this.maxDocumentBytes,
238
+ retryable: false,
239
+ });
240
+ }
241
+ isRetryableError(error) {
242
+ if (error instanceof McpError) {
243
+ if (error.data?.retryable === false)
244
+ return false;
245
+ return (error.code === JsonRpcErrorCode.ServiceUnavailable ||
246
+ error.code === JsonRpcErrorCode.Timeout);
247
+ }
248
+ return true;
249
+ }
250
+ getRequestContext(ctx, operation) {
251
+ const ctxRecord = ctx;
252
+ const requestId = typeof ctxRecord.requestId === 'string' ? ctxRecord.requestId : 'congress-documents-service';
253
+ const timestamp = typeof ctxRecord.timestamp === 'string' ? ctxRecord.timestamp : new Date().toISOString();
254
+ return { operation, requestId, timestamp };
255
+ }
256
+ getAbortSignal(ctx) {
257
+ const signal = ctx.signal;
258
+ return isNativeAbortSignal(signal) ? signal : undefined;
259
+ }
260
+ }
261
+ let _service;
262
+ export function initCongressDocuments() {
263
+ _service = new CongressDocumentsService();
264
+ }
265
+ export function getCongressDocuments() {
266
+ if (!_service) {
267
+ throw new Error('CongressDocumentsService not initialized — call initCongressDocuments() in setup()');
268
+ }
269
+ return _service;
270
+ }
271
+ //# sourceMappingURL=congress-documents-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"congress-documents-service.js","sourceRoot":"","sources":["../../../src/services/congress-documents/congress-documents-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @fileoverview Resolve a requested document format to its upstream URL.
3
+ * @module services/congress-documents/document-formats
4
+ */
5
+ import type { DocumentFormat } from './types.js';
6
+ export declare const DOCUMENT_FORMATS: readonly ['text', 'xml'];
7
+ /**
8
+ * Upstream `type` labels for each supported format, in preference order.
9
+ *
10
+ * `Formatted Text` is GPO's `<pre>`-wrapped print output and is the only format
11
+ * present on every document checked — bills back to 1999, committee reports, and
12
+ * Daily Record articles. XML is inconsistent: `United States Legislative Markup`
13
+ * ships on some newer bill versions, the legacy `Formatted XML` on many more, and
14
+ * neither on committee reports or the Daily Record. PDF is not offered.
15
+ */
16
+ export declare const DOCUMENT_FORMAT_LABELS: Record<DocumentFormat, readonly string[]>;
17
+ /**
18
+ * Pick the URL for `format` from an upstream format list.
19
+ *
20
+ * Labels are tried in preference order, and within one label a non-errata entry
21
+ * wins over an errata reprint — committee reports publish both under the same
22
+ * `Formatted Text` label and the errata is the correction, not the report.
23
+ * Returns `undefined` when the document publishes nothing in this format, which
24
+ * callers surface as the `format_unavailable` contract failure.
25
+ */
26
+ export declare function selectDocumentUrl(entries: unknown, format: DocumentFormat): string | undefined;
27
+ /** The upstream labels a format resolves to, for error messages that name what was searched. */
28
+ export declare function describeFormat(format: DocumentFormat): string;
29
+ //# sourceMappingURL=document-formats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-formats.d.ts","sourceRoot":"","sources":["../../../src/services/congress-documents/document-formats.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAEpE,eAAO,MAAM,gBAAgB,YAAI,MAAM,EAAE,KAAK,CAAU,CAAC;AAEzD;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAG5E,CAAC;AAUF;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAa9F;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAE7D"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @fileoverview Resolve a requested document format to its upstream URL.
3
+ * @module services/congress-documents/document-formats
4
+ */
5
+ export const DOCUMENT_FORMATS = ['text', 'xml'];
6
+ /**
7
+ * Upstream `type` labels for each supported format, in preference order.
8
+ *
9
+ * `Formatted Text` is GPO's `<pre>`-wrapped print output and is the only format
10
+ * present on every document checked — bills back to 1999, committee reports, and
11
+ * Daily Record articles. XML is inconsistent: `United States Legislative Markup`
12
+ * ships on some newer bill versions, the legacy `Formatted XML` on many more, and
13
+ * neither on committee reports or the Daily Record. PDF is not offered.
14
+ */
15
+ export const DOCUMENT_FORMAT_LABELS = {
16
+ text: ['Formatted Text'],
17
+ xml: ['United States Legislative Markup', 'Formatted XML'],
18
+ };
19
+ function refUrl(ref) {
20
+ return typeof ref.url === 'string' && ref.url.trim() !== '' ? ref.url.trim() : undefined;
21
+ }
22
+ function matchesLabel(ref, label) {
23
+ return typeof ref.type === 'string' && ref.type.trim().toLowerCase() === label.toLowerCase();
24
+ }
25
+ /**
26
+ * Pick the URL for `format` from an upstream format list.
27
+ *
28
+ * Labels are tried in preference order, and within one label a non-errata entry
29
+ * wins over an errata reprint — committee reports publish both under the same
30
+ * `Formatted Text` label and the errata is the correction, not the report.
31
+ * Returns `undefined` when the document publishes nothing in this format, which
32
+ * callers surface as the `format_unavailable` contract failure.
33
+ */
34
+ export function selectDocumentUrl(entries, format) {
35
+ if (!Array.isArray(entries))
36
+ return;
37
+ const refs = entries.filter((entry) => typeof entry === 'object' && entry !== null);
38
+ for (const label of DOCUMENT_FORMAT_LABELS[format]) {
39
+ const matched = refs.filter((ref) => matchesLabel(ref, label) && refUrl(ref) !== undefined);
40
+ const preferred = matched.find((ref) => ref.isErrata !== 'Y') ?? matched[0];
41
+ const url = preferred ? refUrl(preferred) : undefined;
42
+ if (url)
43
+ return url;
44
+ }
45
+ return;
46
+ }
47
+ /** The upstream labels a format resolves to, for error messages that name what was searched. */
48
+ export function describeFormat(format) {
49
+ return DOCUMENT_FORMAT_LABELS[format].join(' / ');
50
+ }
51
+ //# sourceMappingURL=document-formats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-formats.js","sourceRoot":"","sources":["../../../src/services/congress-documents/document-formats.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,KAAK,CAAU,CAAC;AAEzD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAA8C;IAC/E,IAAI,EAAE,CAAC,gBAAgB,CAAC;IACxB,GAAG,EAAE,CAAC,kCAAkC,EAAE,eAAe,CAAC;CAC3D,CAAC;AAEF,SAAS,MAAM,CAAC,GAAsB;IACpC,OAAO,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3F,CAAC;AAED,SAAS,YAAY,CAAC,GAAsB,EAAE,KAAa;IACzD,OAAO,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC;AAC/F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB,EAAE,MAAsB;IACxE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO;IACpC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CACzB,CAAC,KAAK,EAA8B,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CACnF,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC;QAC5F,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;QAC5E,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtD,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;IACtB,CAAC;IACD,OAAO;AACT,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,OAAO,sBAAsB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @fileoverview Turn a Congress.gov document body into the plain text offsets index into.
3
+ * @module services/congress-documents/extract-text
4
+ */
5
+ /**
6
+ * Extract the readable plain text of a Congress.gov document body.
7
+ *
8
+ * "Formatted Text" is not semantic HTML — it is GPO's monospace print output
9
+ * wrapped in `<pre>`, sometimes inside `<html><body>` and sometimes bare, with
10
+ * the occasional inline anchor and HTML-escaped entities. The pre-formatted
11
+ * layout (column alignment, indentation, blank-line structure) is the document's
12
+ * real structure, so whitespace is preserved verbatim rather than collapsed the
13
+ * way `htmlToMarkdown` collapses narrative prose. XML bodies run through the same
14
+ * pipeline minus the `<pre>` unwrap.
15
+ *
16
+ * The result is the string every character offset indexes into, so it has to be
17
+ * deterministic: line endings are normalized to `\n` before anything else, and
18
+ * the transform is applied once per fetch.
19
+ */
20
+ export declare function extractDocumentText(body: string): string;
21
+ //# sourceMappingURL=extract-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-text.d.ts","sourceRoot":"","sources":["../../../src/services/congress-documents/extract-text.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA4DH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKxD"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @fileoverview Turn a Congress.gov document body into the plain text offsets index into.
3
+ * @module services/congress-documents/extract-text
4
+ */
5
+ /** Entities GPO emits by name. Numeric references are decoded generically. */
6
+ const NAMED_ENTITIES = {
7
+ amp: '&',
8
+ apos: "'",
9
+ gt: '>',
10
+ lt: '<',
11
+ nbsp: ' ',
12
+ quot: '"',
13
+ };
14
+ const COMMENT_RE = /<!--[\s\S]*?-->/g;
15
+ const TAG_RE = /<[^>]*>/g;
16
+ const ENTITY_RE = /&(#[xX][0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g;
17
+ /**
18
+ * Decode HTML/XML character references in one pass.
19
+ *
20
+ * A single pass is the point: decoding `&amp;` in its own sweep would turn the
21
+ * literal `&amp;lt;` into `<` instead of the `&lt;` the document actually says.
22
+ * An unrecognized reference is left verbatim rather than guessed at.
23
+ */
24
+ function decodeEntities(text) {
25
+ return text.replace(ENTITY_RE, (match, ref) => {
26
+ if (ref.startsWith('#')) {
27
+ const hex = ref[1] === 'x' || ref[1] === 'X';
28
+ const codePoint = Number.parseInt(hex ? ref.slice(2) : ref.slice(1), hex ? 16 : 10);
29
+ if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > 0x10ffff)
30
+ return match;
31
+ return String.fromCodePoint(codePoint);
32
+ }
33
+ return NAMED_ENTITIES[ref.toLowerCase()] ?? match;
34
+ });
35
+ }
36
+ /**
37
+ * The tag name has to end at the `>` or at whitespace. A loose `<\s*pre[^>]*>`
38
+ * also matches USLM's `<preamble>` — which truncated a 2.6 MB bill to its
39
+ * 223-character preamble, and reported that as the document's full length.
40
+ */
41
+ const PRE_OPEN_RE = /<\s*pre(?:\s[^>]*)?\/?>/i;
42
+ const PRE_CLOSE_RE = /<\s*\/\s*pre\s*>/gi;
43
+ /**
44
+ * The content between the first `<pre>` and the last `</pre>`, or `undefined`
45
+ * when the body carries no `<pre>` block at all (every XML body, and any future
46
+ * shape that is not GPO print output).
47
+ */
48
+ function unwrapPre(body) {
49
+ const open = PRE_OPEN_RE.exec(body);
50
+ if (!open)
51
+ return;
52
+ const start = open.index + open[0].length;
53
+ let closeAt = -1;
54
+ for (const close of body.matchAll(PRE_CLOSE_RE)) {
55
+ if (close.index > start)
56
+ closeAt = close.index;
57
+ }
58
+ return closeAt > start ? body.slice(start, closeAt) : body.slice(start);
59
+ }
60
+ /**
61
+ * Extract the readable plain text of a Congress.gov document body.
62
+ *
63
+ * "Formatted Text" is not semantic HTML — it is GPO's monospace print output
64
+ * wrapped in `<pre>`, sometimes inside `<html><body>` and sometimes bare, with
65
+ * the occasional inline anchor and HTML-escaped entities. The pre-formatted
66
+ * layout (column alignment, indentation, blank-line structure) is the document's
67
+ * real structure, so whitespace is preserved verbatim rather than collapsed the
68
+ * way `htmlToMarkdown` collapses narrative prose. XML bodies run through the same
69
+ * pipeline minus the `<pre>` unwrap.
70
+ *
71
+ * The result is the string every character offset indexes into, so it has to be
72
+ * deterministic: line endings are normalized to `\n` before anything else, and
73
+ * the transform is applied once per fetch.
74
+ */
75
+ export function extractDocumentText(body) {
76
+ const normalized = body.replace(/\r\n?/g, '\n');
77
+ const stripped = normalized.replace(COMMENT_RE, '');
78
+ const inner = unwrapPre(stripped) ?? stripped;
79
+ return decodeEntities(inner.replace(TAG_RE, '')).trim();
80
+ }
81
+ //# sourceMappingURL=extract-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-text.js","sourceRoot":"","sources":["../../../src/services/congress-documents/extract-text.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,8EAA8E;AAC9E,MAAM,cAAc,GAA2B;IAC7C,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,GAAG;IACT,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;CACV,CAAC;AAEF,MAAM,UAAU,GAAG,kBAAkB,CAAC;AACtC,MAAM,MAAM,GAAG,UAAU,CAAC;AAC1B,MAAM,SAAS,GAAG,uCAAuC,CAAC;AAE1D;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE;QACpD,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;YAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACpF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,QAAQ;gBAAE,OAAO,KAAK,CAAC;YACxF,OAAO,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,cAAc,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,KAAK,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAC/C,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAE1C;;;;GAIG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAE1C,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC;IACjB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAChD,IAAI,KAAK,CAAC,KAAK,GAAG,KAAK;YAAE,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC;IACjD,CAAC;IACD,OAAO,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC;IAC9C,OAAO,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Domain types for bounded retrieval of Congress.gov document text.
3
+ * @module services/congress-documents/types
4
+ */
5
+ /** The document formats this server retrieves. PDF is deliberately absent — extraction cost. */
6
+ export type DocumentFormat = 'text' | 'xml';
7
+ /**
8
+ * One entry of an upstream format list. Bill text versions and committee reports
9
+ * carry these under `formats[]`; Daily Congressional Record articles carry the
10
+ * same `{type, url}` pairs under `text[]`. Every field is optional — upstream
11
+ * rows are sparse and untyped at this boundary.
12
+ */
13
+ export interface DocumentFormatRef {
14
+ isErrata?: unknown;
15
+ type?: unknown;
16
+ url?: unknown;
17
+ }
18
+ /** Parameters for a single bounded document read. */
19
+ export interface FetchDocumentParams {
20
+ /** Maximum characters to return. */
21
+ characterLimit: number;
22
+ /** First character of the document to return, 0-based. */
23
+ characterOffset: number;
24
+ /** Absolute URL resolved from upstream metadata — never caller-supplied. */
25
+ url: string;
26
+ }
27
+ /**
28
+ * A bounded window onto one document's plain text.
29
+ *
30
+ * `offset` + `text.length` is exactly `nextOffset` when more remains, so feeding
31
+ * `nextOffset` back walks the document with no overlap and no gap.
32
+ */
33
+ export interface DocumentContent {
34
+ /** Offset to pass next, or null at the end of the document. */
35
+ nextOffset: number | null;
36
+ /** Echo of the requested start offset. */
37
+ offset: number;
38
+ /** The requested character window, verbatim. */
39
+ text: string;
40
+ /** Characters in the whole document, not this window. */
41
+ totalCharacters: number;
42
+ /** True when characters remain past this window. */
43
+ truncated: boolean;
44
+ }
45
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/congress-documents/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,gGAAgG;AAChG,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qDAAqD;AACrD,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,eAAe,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;CACpB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @fileoverview Domain types for bounded retrieval of Congress.gov document text.
3
+ * @module services/congress-documents/types
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/congress-documents/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/congressgov-mcp-server",
3
- "version": "0.4.3",
3
+ "version": "0.5.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",