@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.
- package/AGENTS.md +19 -5
- package/CLAUDE.md +19 -5
- package/README.md +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/prompts/definitions/legislative-research.prompt.d.ts +9 -0
- package/dist/mcp-server/prompts/definitions/legislative-research.prompt.d.ts.map +1 -1
- package/dist/mcp-server/prompts/definitions/legislative-research.prompt.js +46 -15
- package/dist/mcp-server/prompts/definitions/legislative-research.prompt.js.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +38 -0
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +72 -4
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts +37 -0
- package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.js +66 -7
- package/dist/mcp-server/tools/definitions/committee-reports.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts +41 -3
- package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.js +61 -8
- package/dist/mcp-server/tools/definitions/daily-record.tool.js.map +1 -1
- package/dist/mcp-server/tools/format-helpers.d.ts +9 -5
- package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/format-helpers.js +397 -89
- package/dist/mcp-server/tools/format-helpers.js.map +1 -1
- package/dist/mcp-server/tools/tool-helpers.d.ts +67 -0
- package/dist/mcp-server/tools/tool-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-helpers.js +85 -0
- package/dist/mcp-server/tools/tool-helpers.js.map +1 -1
- package/dist/services/congress-documents/congress-documents-service.d.ts +71 -0
- package/dist/services/congress-documents/congress-documents-service.d.ts.map +1 -0
- package/dist/services/congress-documents/congress-documents-service.js +271 -0
- package/dist/services/congress-documents/congress-documents-service.js.map +1 -0
- package/dist/services/congress-documents/document-formats.d.ts +29 -0
- package/dist/services/congress-documents/document-formats.d.ts.map +1 -0
- package/dist/services/congress-documents/document-formats.js +51 -0
- package/dist/services/congress-documents/document-formats.js.map +1 -0
- package/dist/services/congress-documents/extract-text.d.ts +21 -0
- package/dist/services/congress-documents/extract-text.d.ts.map +1 -0
- package/dist/services/congress-documents/extract-text.js +81 -0
- package/dist/services/congress-documents/extract-text.js.map +1 -0
- package/dist/services/congress-documents/types.d.ts +45 -0
- package/dist/services/congress-documents/types.d.ts.map +1 -0
- package/dist/services/congress-documents/types.js +6 -0
- package/dist/services/congress-documents/types.js.map +1 -0
- package/package.json +1 -1
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE5C,0FAA0F;AAC1F,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAEzC,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,iFAAiF;AACjF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAEpD,sFAAsF;AACtF,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;AAElG;;;;;GAKG;AACH,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AACpD,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AACtD,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAChD,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAQ1C,SAAS,mBAAmB,CAAC,KAAc;IACzC,IACE,OAAO,WAAW,KAAK,UAAU;QACjC,OAAO,WAAW,CAAC,SAAS,CAAC,cAAc,KAAK,UAAU;QAC1D,CAAC,KAAK,EACN,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,WAAW,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,CAAC,KAAK,YAAY,SAAS,CAAC,CAAC;IACvC,CAAC;AACH,CAAC;AAED,MAAM,OAAO,wBAAwB;IACN,gBAAgB;IAA7C,YAA6B,gBAAgB,GAAW,kBAAkB;gCAA7C,gBAAgB;IAAgC,CAAC;IAE9E;;;;;;;OAOG;IACH,KAAK,CAAC,aAAa,CAAC,MAA2B,EAAE,GAAY;QAC3D,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAExC,MAAM,SAAS,GAAG,uCAAuC,CAAC;QAC1D,MAAM,cAAc,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAExC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE;YACxF,SAAS;YACT,OAAO,EAAE,cAAc;YACvB,WAAW,EAAE,eAAe;YAC5B,UAAU,EAAE,YAAY,GAAG,CAAC;YAC5B,WAAW,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC;YAC7D,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9B,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IACxC,CAAC;IAED,mFAAmF;IAC3E,MAAM,CAAC,IAAY,EAAE,MAA2B,EAAE,GAAY;QACpE,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC;QAEtC,sFAAsF;QACtF,IAAI,eAAe,GAAG,CAAC,IAAI,MAAM,IAAI,eAAe,EAAE,CAAC;YACrD,MAAM,aAAa,CACjB,mBAAmB,MAAM,gDAAgD,eAAe,cAAc,EACtG;gBACE,MAAM,EAAE,eAAe;gBACvB,GAAG,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC;gBACnC,eAAe;gBACf,MAAM;gBACN,SAAS,EAAE,KAAK;aACjB,CACF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAClC,MAAM,SAAS,GAAG,GAAG,GAAG,eAAe,CAAC;QACxC,OAAO;YACL,IAAI,EAAE,KAAK;YACX,eAAe;YACf,MAAM;YACN,SAAS;YACT,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;SACnC,CAAC;IACJ,CAAC;IAEO,iBAAiB,CAAC,GAAW,EAAE,GAAY;QACjD,IAAI,IAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO;gBAAE,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC1F,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACjE,MAAM,QAAQ,CACZ,gIAAgI,EAChI;gBACE,MAAM,EAAE,oBAAoB;gBAC5B,GAAG,GAAG,CAAC,WAAW,CAAC,oBAAoB,CAAC;gBACxC,SAAS,EAAE,KAAK;aACjB,CACF,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,OAAO,CACnB,GAAW,EACX,cAAkC,EAClC,GAAY,EACZ,MAAoB;QAEpB,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,kBAAkB,EAAE,cAAc,EAAE;gBACzE,OAAO,EAAE,EAAE,MAAM,EAAE,6CAA6C,EAAE;gBAClE,gBAAgB,EAAE,CAAC,GAAG,CAAC;gBACvB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACK,kBAAkB,CAAC,KAAc,EAAE,GAAY;QACrD,IAAI,CAAC,CAAC,KAAK,YAAY,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/C,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAC,QAAQ,EAAE,CAAC;YAC7C,OAAO,QAAQ,CACb,kEAAkE,EAClE;gBACE,MAAM,EAAE,oBAAoB;gBAC5B,GAAG,GAAG,CAAC,WAAW,CAAC,oBAAoB,CAAC;gBACxC,SAAS,EAAE,KAAK;aACjB,EACD,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;QACJ,CAAC;QACD,mFAAmF;QACnF,OAAO,kBAAkB,CACvB,oDAAoD,EACpD,EAAE,MAAM,EAAE,qBAAqB,EAAE,GAAG,GAAG,CAAC,WAAW,CAAC,qBAAqB,CAAC,EAAE,EAC5E,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;IAEO,iBAAiB,CAAC,QAAkB,EAAE,GAAY;QACxD,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QACvD,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;QAChE,IAAI,qBAAqB,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO;QACjD,MAAM,kBAAkB,CACtB,uCAAuC,SAAS,IAAI,SAAS,2CAA2C,EACxG;YACE,MAAM,EAAE,qBAAqB;YAC7B,GAAG,GAAG,CAAC,WAAW,CAAC,qBAAqB,CAAC;YACzC,WAAW,EAAE,SAAS;YACtB,kFAAkF;YAClF,SAAS,EAAE,KAAK;SACjB,CACF,CAAC;IACJ,CAAC;IAED,0FAA0F;IAClF,oBAAoB,CAAC,QAAkB,EAAE,GAAY;QAC3D,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACtD,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;QAC5B,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,IAAI,IAAI,CAAC,gBAAgB;YAAE,OAAO;QACtF,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,WAAW,CAAC,QAAkB,EAAE,GAAY;QACxD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,IAAI,MAAM,EAAE,CAAC;YACX,SAAS,CAAC;gBACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI;oBAAE,MAAM;gBAChB,IAAI,CAAC,KAAK;oBAAE,SAAS;gBACrB,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC;gBAC1B,IAAI,KAAK,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBAClC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;oBAC7C,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;gBACjD,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;QAED,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,MAAM,kBAAkB,CAAC,+CAA+C,EAAE;gBACxE,MAAM,EAAE,qBAAqB;gBAC7B,GAAG,GAAG,CAAC,WAAW,CAAC,qBAAqB,CAAC;gBACzC,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACtB,EAAE,IAAI,KAAK,CAAC,UAAU,CAAC;QACzB,CAAC;QACD,OAAO,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjD,CAAC;IAEO,QAAQ,CAAC,GAAY,EAAE,IAA6B;QAC1D,OAAO,aAAa,CAClB,6BAA6B,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,OAAO,CAAC,sCAAsC,EAChH;YACE,MAAM,EAAE,kBAAkB;YAC1B,GAAG,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC;YACtC,GAAG,IAAI;YACP,QAAQ,EAAE,IAAI,CAAC,gBAAgB;YAC/B,SAAS,EAAE,KAAK;SACjB,CACF,CAAC;IACJ,CAAC;IAEO,gBAAgB,CAAC,KAAc;QACrC,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC9B,IAAI,KAAK,CAAC,IAAI,EAAE,SAAS,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;YAClD,OAAO,CACL,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAC,kBAAkB;gBAClD,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAC,OAAO,CACxC,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,iBAAiB,CAAC,GAAY,EAAE,SAAiB;QACvD,MAAM,SAAS,GAAG,GAAyC,CAAC;QAC5D,MAAM,SAAS,GACb,OAAO,SAAS,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,4BAA4B,CAAC;QAC/F,MAAM,SAAS,GACb,OAAO,SAAS,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3F,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAC7C,CAAC;IAEO,cAAc,CAAC,GAAY;QACjC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QAC1B,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1D,CAAC;CACF;AAED,IAAI,QAA8C,CAAC;AAEnD,MAAM,UAAU,qBAAqB;IACnC,QAAQ,GAAG,IAAI,wBAAwB,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,oFAAoF,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -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 `&` in its own sweep would turn the
|
|
21
|
+
* literal `&lt;` into `<` instead of the `<` 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 @@
|
|
|
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.
|
|
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",
|