@cyanheads/congressgov-mcp-server 0.3.7 → 0.3.9
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 +272 -0
- package/CLAUDE.md +2 -2
- package/README.md +2 -2
- package/dist/mcp-server/resources/definitions/bill.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/bill.resource.js +1 -1
- package/dist/mcp-server/resources/definitions/bill.resource.js.map +1 -1
- package/dist/mcp-server/resources/definitions/committee.resource.js +1 -1
- package/dist/mcp-server/resources/definitions/committee.resource.js.map +1 -1
- package/dist/mcp-server/resources/definitions/current-congress.resource.js +1 -1
- package/dist/mcp-server/resources/definitions/current-congress.resource.js.map +1 -1
- package/dist/mcp-server/resources/definitions/member.resource.js +1 -1
- package/dist/mcp-server/resources/definitions/member.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +18 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +50 -16
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts +7 -1
- package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-summaries.tool.js +25 -13
- package/dist/mcp-server/tools/definitions/bill-summaries.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.js +4 -8
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.js +4 -11
- package/dist/mcp-server/tools/definitions/committee-reports.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/crs-reports.tool.d.ts +13 -1
- package/dist/mcp-server/tools/definitions/crs-reports.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/crs-reports.tool.js +39 -8
- package/dist/mcp-server/tools/definitions/crs-reports.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.js +4 -6
- package/dist/mcp-server/tools/definitions/daily-record.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/enacted-laws.tool.js +3 -9
- package/dist/mcp-server/tools/definitions/enacted-laws.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/member-lookup.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/member-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/member-lookup.tool.js +24 -13
- package/dist/mcp-server/tools/definitions/member-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/roll-votes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/roll-votes.tool.js +4 -8
- package/dist/mcp-server/tools/definitions/roll-votes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.js +5 -9
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.js.map +1 -1
- package/dist/mcp-server/tools/format-helpers.d.ts +3 -3
- package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/format-helpers.js +198 -29
- package/dist/mcp-server/tools/format-helpers.js.map +1 -1
- package/dist/mcp-server/tools/tool-helpers.d.ts +13 -0
- package/dist/mcp-server/tools/tool-helpers.d.ts.map +1 -0
- package/dist/mcp-server/tools/tool-helpers.js +23 -0
- package/dist/mcp-server/tools/tool-helpers.js.map +1 -0
- package/dist/services/congress-api/congress-api-service.d.ts +41 -47
- package/dist/services/congress-api/congress-api-service.d.ts.map +1 -1
- package/dist/services/congress-api/congress-api-service.js +233 -133
- package/dist/services/congress-api/congress-api-service.js.map +1 -1
- package/package.json +7 -11
- package/server.json +3 -3
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* @fileoverview Congress.gov API v3 client — auth, pagination, rate limiting, response normalization.
|
|
3
3
|
* @module services/congress-api/congress-api-service
|
|
4
4
|
*/
|
|
5
|
+
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
5
6
|
import type { BillSubResourceParams, CommitteeSubResourceParams, CongressDetail, GetBillParams, GetCommitteeReportParams, GetCrsReportParams, GetDailyArticlesParams, GetDailyIssuesParams, GetLawParams, GetMemberLegislationParams, GetVoteParams, ListBillsParams, ListCommitteeReportsParams, ListCommitteesParams, ListDailyRecordParams, ListLawsParams, ListMembersParams, ListNominationsParams, ListSummariesParams, ListVotesParams, NominationSubResourceParams, Pagination, PaginationParams } from './types.js';
|
|
7
|
+
type ApiRecord = Record<string, unknown>;
|
|
8
|
+
type EntityResult<TKey extends string> = Record<TKey, ApiRecord>;
|
|
6
9
|
interface FetchListResult {
|
|
7
10
|
data: unknown[];
|
|
8
11
|
pagination: Pagination;
|
|
@@ -14,60 +17,51 @@ export declare class CongressApiService {
|
|
|
14
17
|
private requestCount;
|
|
15
18
|
private windowStart;
|
|
16
19
|
constructor();
|
|
17
|
-
listBills(params: ListBillsParams): Promise<FetchListResult>;
|
|
18
|
-
getBill(params: GetBillParams): Promise<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
vote: unknown;
|
|
42
|
-
}>;
|
|
43
|
-
listNominations(params: ListNominationsParams): Promise<FetchListResult>;
|
|
44
|
-
getNomination(congress: number, nominationNumber: string): Promise<{
|
|
45
|
-
nomination: unknown;
|
|
46
|
-
}>;
|
|
47
|
-
getNominee(congress: number, nominationNumber: string, ordinal: number, params?: PaginationParams): Promise<FetchListResult>;
|
|
48
|
-
getNominationSubResource(params: NominationSubResourceParams): Promise<FetchListResult>;
|
|
49
|
-
listSummaries(params: ListSummariesParams): Promise<FetchListResult>;
|
|
50
|
-
listCrsReports(params?: PaginationParams): Promise<FetchListResult>;
|
|
51
|
-
getCrsReport(params: GetCrsReportParams): Promise<{
|
|
52
|
-
report: unknown;
|
|
53
|
-
}>;
|
|
54
|
-
listCommitteeReports(params: ListCommitteeReportsParams): Promise<FetchListResult>;
|
|
55
|
-
getCommitteeReport(params: GetCommitteeReportParams): Promise<{
|
|
56
|
-
report: unknown;
|
|
57
|
-
}>;
|
|
58
|
-
getCommitteeReportText(params: GetCommitteeReportParams): Promise<{
|
|
20
|
+
listBills(params: ListBillsParams, ctx?: Context): Promise<FetchListResult>;
|
|
21
|
+
getBill(params: GetBillParams, ctx?: Context): Promise<EntityResult<'bill'>>;
|
|
22
|
+
getBillSubResource(params: BillSubResourceParams, ctx?: Context): Promise<FetchListResult>;
|
|
23
|
+
listLaws(params: ListLawsParams, ctx?: Context): Promise<FetchListResult>;
|
|
24
|
+
getLaw(params: GetLawParams, ctx?: Context): Promise<EntityResult<'law'>>;
|
|
25
|
+
listMembers(params: ListMembersParams, ctx?: Context): Promise<FetchListResult>;
|
|
26
|
+
getMember(bioguideId: string, ctx?: Context): Promise<EntityResult<'member'>>;
|
|
27
|
+
getMemberLegislation(params: GetMemberLegislationParams, ctx?: Context): Promise<FetchListResult>;
|
|
28
|
+
listCommittees(params: ListCommitteesParams, ctx?: Context): Promise<FetchListResult>;
|
|
29
|
+
getCommittee(chamber: string, committeeCode: string, ctx?: Context): Promise<EntityResult<'committee'>>;
|
|
30
|
+
getCommitteeSubResource(params: CommitteeSubResourceParams, ctx?: Context): Promise<FetchListResult>;
|
|
31
|
+
listVotes(params: ListVotesParams, ctx?: Context): Promise<FetchListResult>;
|
|
32
|
+
getVote(params: GetVoteParams, ctx?: Context): Promise<EntityResult<'vote'>>;
|
|
33
|
+
getVoteMembers(params: GetVoteParams, ctx?: Context): Promise<EntityResult<'vote'>>;
|
|
34
|
+
listNominations(params: ListNominationsParams, ctx?: Context): Promise<FetchListResult>;
|
|
35
|
+
getNomination(congress: number, nominationNumber: string, ctx?: Context): Promise<EntityResult<'nomination'>>;
|
|
36
|
+
getNominee(congress: number, nominationNumber: string, ordinal: number, params?: PaginationParams, ctx?: Context): Promise<FetchListResult>;
|
|
37
|
+
getNominationSubResource(params: NominationSubResourceParams, ctx?: Context): Promise<FetchListResult>;
|
|
38
|
+
listSummaries(params: ListSummariesParams, ctx?: Context): Promise<FetchListResult>;
|
|
39
|
+
listCrsReports(params?: PaginationParams, ctx?: Context): Promise<FetchListResult>;
|
|
40
|
+
getCrsReport(params: GetCrsReportParams, ctx?: Context): Promise<EntityResult<'report'>>;
|
|
41
|
+
listCommitteeReports(params: ListCommitteeReportsParams, ctx?: Context): Promise<FetchListResult>;
|
|
42
|
+
getCommitteeReport(params: GetCommitteeReportParams, ctx?: Context): Promise<EntityResult<'report'>>;
|
|
43
|
+
getCommitteeReportText(params: GetCommitteeReportParams, ctx?: Context): Promise<{
|
|
59
44
|
text: unknown;
|
|
60
45
|
}>;
|
|
61
|
-
listDailyRecord(params?: ListDailyRecordParams): Promise<FetchListResult>;
|
|
62
|
-
getDailyIssues(params: GetDailyIssuesParams): Promise<FetchListResult>;
|
|
63
|
-
getDailyArticles(params: GetDailyArticlesParams): Promise<FetchListResult>;
|
|
64
|
-
getCurrentCongress(): Promise<CongressDetail>;
|
|
65
|
-
getCongress(congress: number): Promise<CongressDetail>;
|
|
46
|
+
listDailyRecord(params?: ListDailyRecordParams, ctx?: Context): Promise<FetchListResult>;
|
|
47
|
+
getDailyIssues(params: GetDailyIssuesParams, ctx?: Context): Promise<FetchListResult>;
|
|
48
|
+
getDailyArticles(params: GetDailyArticlesParams, ctx?: Context): Promise<FetchListResult>;
|
|
49
|
+
getCurrentCongress(ctx?: Context): Promise<CongressDetail>;
|
|
50
|
+
getCongress(congress: number, ctx?: Context): Promise<CongressDetail>;
|
|
66
51
|
private fetchList;
|
|
67
52
|
private extractPagination;
|
|
68
53
|
private get;
|
|
69
54
|
private checkRateLimit;
|
|
70
55
|
private inferListKey;
|
|
56
|
+
private buildUrl;
|
|
57
|
+
private buildQuery;
|
|
58
|
+
private extractListItems;
|
|
59
|
+
private getRequestContext;
|
|
60
|
+
private getAbortSignal;
|
|
61
|
+
private fetchResponse;
|
|
62
|
+
private isRetryableError;
|
|
63
|
+
private parseJsonResponse;
|
|
64
|
+
private isMissingEntityErrorBody;
|
|
71
65
|
}
|
|
72
66
|
export declare function initCongressApi(): void;
|
|
73
67
|
export declare function getCongressApi(): CongressApiService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"congress-api-service.d.ts","sourceRoot":"","sources":["../../../src/services/congress-api/congress-api-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"congress-api-service.d.ts","sourceRoot":"","sources":["../../../src/services/congress-api/congress-api-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,32 @@
|
|
|
2
2
|
* @fileoverview Congress.gov API v3 client — auth, pagination, rate limiting, response normalization.
|
|
3
3
|
* @module services/congress-api/congress-api-service
|
|
4
4
|
*/
|
|
5
|
-
import { notFound, rateLimited, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
5
|
+
import { JsonRpcErrorCode, McpError, notFound, rateLimited, serviceUnavailable, validationError, } from '@cyanheads/mcp-ts-core/errors';
|
|
6
|
+
import { fetchWithTimeout, withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
6
7
|
import { getServerConfig } from '../../config/server-config.js';
|
|
7
|
-
|
|
8
|
+
function isApiRecord(value) {
|
|
9
|
+
return typeof value === 'object' && value !== null;
|
|
10
|
+
}
|
|
11
|
+
function isNativeAbortSignal(value) {
|
|
12
|
+
if (typeof AbortSignal !== 'function' ||
|
|
13
|
+
typeof AbortSignal.prototype.throwIfAborted !== 'function' ||
|
|
14
|
+
!value) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
AbortSignal.prototype.throwIfAborted.call(value);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return !(error instanceof TypeError);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const MAX_ATTEMPTS = 3;
|
|
8
26
|
const BASE_BACKOFF_MS = 1000;
|
|
9
27
|
const RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000;
|
|
10
28
|
const RATE_LIMIT_MAX = 5000;
|
|
29
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
30
|
+
const HTML_RESPONSE_RE = /^\s*<(!DOCTYPE\s+html|html[\s>])/i;
|
|
11
31
|
export class CongressApiService {
|
|
12
32
|
apiKey;
|
|
13
33
|
baseUrl;
|
|
@@ -19,58 +39,67 @@ export class CongressApiService {
|
|
|
19
39
|
this.baseUrl = config.baseUrl.replace(/\/$/, '');
|
|
20
40
|
}
|
|
21
41
|
// --- Bills ---
|
|
22
|
-
listBills(params) {
|
|
42
|
+
listBills(params, ctx) {
|
|
23
43
|
const path = params.billType
|
|
24
44
|
? `/bill/${params.congress}/${params.billType}`
|
|
25
45
|
: `/bill/${params.congress}`;
|
|
26
|
-
return this.fetchList(path, 'bills', params);
|
|
46
|
+
return this.fetchList(path, 'bills', params, ctx);
|
|
27
47
|
}
|
|
28
|
-
async getBill(params) {
|
|
29
|
-
const data = await this.get(`/bill/${params.congress}/${params.billType}/${params.billNumber}
|
|
48
|
+
async getBill(params, ctx) {
|
|
49
|
+
const data = await this.get(`/bill/${params.congress}/${params.billType}/${params.billNumber}`, ctx);
|
|
30
50
|
return { bill: data.bill };
|
|
31
51
|
}
|
|
32
|
-
getBillSubResource(params) {
|
|
52
|
+
getBillSubResource(params, ctx) {
|
|
33
53
|
const path = `/bill/${params.congress}/${params.billType}/${params.billNumber}/${params.subResource}`;
|
|
34
54
|
const key = this.inferListKey(params.subResource);
|
|
35
|
-
return this.fetchList(path, key, params);
|
|
55
|
+
return this.fetchList(path, key, params, ctx);
|
|
36
56
|
}
|
|
37
57
|
// --- Laws ---
|
|
38
|
-
listLaws(params) {
|
|
58
|
+
listLaws(params, ctx) {
|
|
39
59
|
const path = params.lawType
|
|
40
60
|
? `/law/${params.congress}/${params.lawType}`
|
|
41
61
|
: `/law/${params.congress}`;
|
|
42
|
-
return this.fetchList(path, 'bills', params);
|
|
62
|
+
return this.fetchList(path, 'bills', params, ctx);
|
|
43
63
|
}
|
|
44
|
-
async getLaw(params) {
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
async getLaw(params, ctx) {
|
|
65
|
+
/** Congress.gov returns the law endpoint payload under `bill` — a law is a bill that became law. */
|
|
66
|
+
const data = await this.get(`/law/${params.congress}/${params.lawType}/${params.lawNumber}`, ctx);
|
|
67
|
+
return { law: data.bill };
|
|
47
68
|
}
|
|
48
69
|
// --- Members ---
|
|
49
|
-
listMembers(params) {
|
|
70
|
+
listMembers(params, ctx) {
|
|
71
|
+
if (params.congress !== undefined && (params.stateCode || params.district !== undefined)) {
|
|
72
|
+
throw validationError('Congress.gov does not support combining congress with stateCode or district for member lookups. Use congress-only or stateCode/district-only filters.', {
|
|
73
|
+
congress: params.congress,
|
|
74
|
+
stateCode: params.stateCode,
|
|
75
|
+
district: params.district,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
50
78
|
let path = '/member';
|
|
51
|
-
if (params.congress)
|
|
52
|
-
path = `/member/congress/${params.congress}`;
|
|
53
79
|
if (params.stateCode) {
|
|
54
80
|
path = `/member/${params.stateCode}`;
|
|
55
81
|
if (params.district !== undefined)
|
|
56
82
|
path += `/${params.district}`;
|
|
57
83
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
else if (params.congress) {
|
|
85
|
+
path = `/member/congress/${params.congress}`;
|
|
86
|
+
}
|
|
87
|
+
const extraQuery = params.currentMember === undefined
|
|
88
|
+
? undefined
|
|
89
|
+
: { currentMember: params.currentMember ? 'true' : 'false' };
|
|
90
|
+
return this.fetchList(path, 'members', params, ctx, extraQuery);
|
|
62
91
|
}
|
|
63
|
-
async getMember(bioguideId) {
|
|
64
|
-
const data = await this.get(`/member/${bioguideId}
|
|
92
|
+
async getMember(bioguideId, ctx) {
|
|
93
|
+
const data = await this.get(`/member/${bioguideId}`, ctx);
|
|
65
94
|
return { member: data.member };
|
|
66
95
|
}
|
|
67
|
-
getMemberLegislation(params) {
|
|
96
|
+
getMemberLegislation(params, ctx) {
|
|
68
97
|
const path = `/member/${params.bioguideId}/${params.type}`;
|
|
69
98
|
const key = params.type.replace('-legislation', 'Legislation');
|
|
70
|
-
return this.fetchList(path, key, params);
|
|
99
|
+
return this.fetchList(path, key, params, ctx);
|
|
71
100
|
}
|
|
72
101
|
// --- Committees ---
|
|
73
|
-
listCommittees(params) {
|
|
102
|
+
listCommittees(params, ctx) {
|
|
74
103
|
let path = '/committee';
|
|
75
104
|
if (params.congress && params.chamber)
|
|
76
105
|
path = `/committee/${params.congress}/${params.chamber}`;
|
|
@@ -78,81 +107,85 @@ export class CongressApiService {
|
|
|
78
107
|
path = `/committee/${params.congress}`;
|
|
79
108
|
else if (params.chamber)
|
|
80
109
|
path = `/committee/${params.chamber}`;
|
|
81
|
-
return this.fetchList(path, 'committees', params);
|
|
110
|
+
return this.fetchList(path, 'committees', params, ctx);
|
|
82
111
|
}
|
|
83
|
-
async getCommittee(chamber, committeeCode) {
|
|
84
|
-
const data = await this.get(`/committee/${chamber}/${committeeCode}
|
|
112
|
+
async getCommittee(chamber, committeeCode, ctx) {
|
|
113
|
+
const data = await this.get(`/committee/${chamber}/${committeeCode}`, ctx);
|
|
85
114
|
return { committee: data.committee };
|
|
86
115
|
}
|
|
87
|
-
getCommitteeSubResource(params) {
|
|
116
|
+
getCommitteeSubResource(params, ctx) {
|
|
88
117
|
const path = `/committee/${params.chamber}/${params.committeeCode}/${params.subResource}`;
|
|
89
118
|
const key = this.inferListKey(params.subResource);
|
|
90
|
-
return this.fetchList(path, key, params);
|
|
119
|
+
return this.fetchList(path, key, params, ctx);
|
|
91
120
|
}
|
|
92
121
|
// --- Votes ---
|
|
93
|
-
listVotes(params) {
|
|
94
|
-
return this.fetchList(`/house-vote/${params.congress}/${params.session}`, 'houseRollCallVotes', params);
|
|
122
|
+
listVotes(params, ctx) {
|
|
123
|
+
return this.fetchList(`/house-vote/${params.congress}/${params.session}`, 'houseRollCallVotes', params, ctx);
|
|
95
124
|
}
|
|
96
|
-
async getVote(params) {
|
|
97
|
-
const data = await this.get(`/house-vote/${params.congress}/${params.session}/${params.voteNumber}
|
|
98
|
-
return { vote: data.houseRollCallVote ?? data };
|
|
125
|
+
async getVote(params, ctx) {
|
|
126
|
+
const data = await this.get(`/house-vote/${params.congress}/${params.session}/${params.voteNumber}`, ctx);
|
|
127
|
+
return { vote: (data.houseRollCallVote ?? data) };
|
|
99
128
|
}
|
|
100
|
-
async getVoteMembers(params) {
|
|
101
|
-
const data = await this.get(`/house-vote/${params.congress}/${params.session}/${params.voteNumber}/members
|
|
102
|
-
return { vote: data.houseRollCallVoteMemberVotes ?? data };
|
|
129
|
+
async getVoteMembers(params, ctx) {
|
|
130
|
+
const data = await this.get(`/house-vote/${params.congress}/${params.session}/${params.voteNumber}/members`, ctx);
|
|
131
|
+
return { vote: (data.houseRollCallVoteMemberVotes ?? data) };
|
|
103
132
|
}
|
|
104
133
|
// --- Nominations ---
|
|
105
|
-
listNominations(params) {
|
|
106
|
-
return this.fetchList(`/nomination/${params.congress}`, 'nominations', params);
|
|
134
|
+
listNominations(params, ctx) {
|
|
135
|
+
return this.fetchList(`/nomination/${params.congress}`, 'nominations', params, ctx);
|
|
107
136
|
}
|
|
108
|
-
async getNomination(congress, nominationNumber) {
|
|
109
|
-
const data = await this.get(`/nomination/${congress}/${nominationNumber}
|
|
137
|
+
async getNomination(congress, nominationNumber, ctx) {
|
|
138
|
+
const data = await this.get(`/nomination/${congress}/${nominationNumber}`, ctx);
|
|
110
139
|
return { nomination: data.nomination };
|
|
111
140
|
}
|
|
112
|
-
getNominee(congress, nominationNumber, ordinal, params) {
|
|
113
|
-
return this.fetchList(`/nomination/${congress}/${nominationNumber}/${ordinal}`, 'nominees', params);
|
|
141
|
+
getNominee(congress, nominationNumber, ordinal, params, ctx) {
|
|
142
|
+
return this.fetchList(`/nomination/${congress}/${nominationNumber}/${ordinal}`, 'nominees', params, ctx);
|
|
114
143
|
}
|
|
115
|
-
getNominationSubResource(params) {
|
|
144
|
+
getNominationSubResource(params, ctx) {
|
|
116
145
|
const path = `/nomination/${params.congress}/${params.nominationNumber}/${params.subResource}`;
|
|
117
146
|
const key = this.inferListKey(params.subResource);
|
|
118
|
-
return this.fetchList(path, key, params);
|
|
147
|
+
return this.fetchList(path, key, params, ctx);
|
|
119
148
|
}
|
|
120
149
|
// --- Summaries ---
|
|
121
|
-
listSummaries(params) {
|
|
150
|
+
listSummaries(params, ctx) {
|
|
122
151
|
let path = '/summaries';
|
|
123
152
|
if (params.congress && params.billType)
|
|
124
153
|
path = `/summaries/${params.congress}/${params.billType}`;
|
|
125
154
|
else if (params.congress)
|
|
126
155
|
path = `/summaries/${params.congress}`;
|
|
127
|
-
return this.fetchList(path, 'summaries', params);
|
|
156
|
+
return this.fetchList(path, 'summaries', params, ctx);
|
|
128
157
|
}
|
|
129
158
|
// --- CRS Reports ---
|
|
130
|
-
listCrsReports(params) {
|
|
131
|
-
return this.fetchList('/crsreport', 'CRSReports', params);
|
|
159
|
+
listCrsReports(params, ctx) {
|
|
160
|
+
return this.fetchList('/crsreport', 'CRSReports', params, ctx);
|
|
132
161
|
}
|
|
133
|
-
async getCrsReport(params) {
|
|
134
|
-
let data;
|
|
162
|
+
async getCrsReport(params, ctx) {
|
|
135
163
|
try {
|
|
136
|
-
data = await this.get(`/crsreport/${params.reportNumber}
|
|
164
|
+
const data = await this.get(`/crsreport/${params.reportNumber}`, ctx);
|
|
165
|
+
return { report: (data.CRSReport ?? data) };
|
|
137
166
|
}
|
|
138
|
-
catch (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
catch (error) {
|
|
168
|
+
const statusCode = error instanceof McpError && typeof error.data?.statusCode === 'number'
|
|
169
|
+
? error.data.statusCode
|
|
170
|
+
: undefined;
|
|
171
|
+
const responseBody = error instanceof McpError && typeof error.data?.responseBody === 'string'
|
|
172
|
+
? error.data.responseBody
|
|
173
|
+
: '';
|
|
174
|
+
if (statusCode === 500 && this.isMissingEntityErrorBody(responseBody)) {
|
|
175
|
+
throw notFound('CRS report not found', { reportNumber: params.reportNumber }, { cause: error });
|
|
142
176
|
}
|
|
143
|
-
throw
|
|
177
|
+
throw error;
|
|
144
178
|
}
|
|
145
|
-
return { report: data.CRSReport ?? data };
|
|
146
179
|
}
|
|
147
180
|
// --- Committee Reports ---
|
|
148
|
-
listCommitteeReports(params) {
|
|
181
|
+
listCommitteeReports(params, ctx) {
|
|
149
182
|
const path = params.reportType
|
|
150
183
|
? `/committee-report/${params.congress}/${params.reportType}`
|
|
151
184
|
: `/committee-report/${params.congress}`;
|
|
152
|
-
return this.fetchList(path, 'reports', params);
|
|
185
|
+
return this.fetchList(path, 'reports', params, ctx);
|
|
153
186
|
}
|
|
154
|
-
async getCommitteeReport(params) {
|
|
155
|
-
const data = await this.get(`/committee-report/${params.congress}/${params.reportType}/${params.reportNumber}
|
|
187
|
+
async getCommitteeReport(params, ctx) {
|
|
188
|
+
const data = await this.get(`/committee-report/${params.congress}/${params.reportType}/${params.reportNumber}`, ctx);
|
|
156
189
|
const reports = data.committeeReports;
|
|
157
190
|
const report = Array.isArray(reports) ? reports[0] : (reports ?? data);
|
|
158
191
|
if (!report || (typeof report === 'object' && Object.keys(report).length === 0)) {
|
|
@@ -162,51 +195,36 @@ export class CongressApiService {
|
|
|
162
195
|
reportNumber: params.reportNumber,
|
|
163
196
|
});
|
|
164
197
|
}
|
|
165
|
-
return { report };
|
|
198
|
+
return { report: report };
|
|
166
199
|
}
|
|
167
|
-
async getCommitteeReportText(params) {
|
|
168
|
-
const data = await this.get(`/committee-report/${params.congress}/${params.reportType}/${params.reportNumber}/text
|
|
200
|
+
async getCommitteeReportText(params, ctx) {
|
|
201
|
+
const data = await this.get(`/committee-report/${params.congress}/${params.reportType}/${params.reportNumber}/text`, ctx);
|
|
169
202
|
return { text: data.text ?? data['text-versions'] ?? data };
|
|
170
203
|
}
|
|
171
204
|
// --- Daily Congressional Record ---
|
|
172
|
-
listDailyRecord(params) {
|
|
173
|
-
return this.fetchList('/daily-congressional-record', 'dailyCongressionalRecord', params);
|
|
205
|
+
listDailyRecord(params, ctx) {
|
|
206
|
+
return this.fetchList('/daily-congressional-record', 'dailyCongressionalRecord', params, ctx);
|
|
174
207
|
}
|
|
175
|
-
getDailyIssues(params) {
|
|
176
|
-
return this.fetchList(`/daily-congressional-record/${params.volumeNumber}`, 'dailyCongressionalRecord', params);
|
|
208
|
+
getDailyIssues(params, ctx) {
|
|
209
|
+
return this.fetchList(`/daily-congressional-record/${params.volumeNumber}`, 'dailyCongressionalRecord', params, ctx);
|
|
177
210
|
}
|
|
178
|
-
getDailyArticles(params) {
|
|
211
|
+
getDailyArticles(params, ctx) {
|
|
179
212
|
const path = `/daily-congressional-record/${params.volumeNumber}/${params.issueNumber}/articles`;
|
|
180
|
-
return this.fetchList(path, 'articles', params);
|
|
213
|
+
return this.fetchList(path, 'articles', params, ctx);
|
|
181
214
|
}
|
|
182
215
|
// --- Congress metadata ---
|
|
183
|
-
async getCurrentCongress() {
|
|
184
|
-
const data = await this.get('/congress/current');
|
|
216
|
+
async getCurrentCongress(ctx) {
|
|
217
|
+
const data = await this.get('/congress/current', ctx);
|
|
185
218
|
return data.congress;
|
|
186
219
|
}
|
|
187
|
-
async getCongress(congress) {
|
|
188
|
-
const data = await this.get(`/congress/${congress}
|
|
220
|
+
async getCongress(congress, ctx) {
|
|
221
|
+
const data = await this.get(`/congress/${congress}`, ctx);
|
|
189
222
|
return data.congress;
|
|
190
223
|
}
|
|
191
224
|
// --- Internal ---
|
|
192
|
-
async fetchList(path, listKey, params, extraQuery) {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
query.limit = String(params.limit);
|
|
196
|
-
if (params?.offset)
|
|
197
|
-
query.offset = String(params.offset);
|
|
198
|
-
if (params?.fromDateTime)
|
|
199
|
-
query.fromDateTime = params.fromDateTime;
|
|
200
|
-
if (params?.toDateTime)
|
|
201
|
-
query.toDateTime = params.toDateTime;
|
|
202
|
-
const data = await this.get(path, query);
|
|
203
|
-
const raw = data[listKey];
|
|
204
|
-
// Some endpoints (e.g. committee-bills) nest the array inside an object — unwrap it.
|
|
205
|
-
const items = Array.isArray(raw)
|
|
206
|
-
? raw
|
|
207
|
-
: raw && typeof raw === 'object'
|
|
208
|
-
? (Object.values(raw).find(Array.isArray) ?? [])
|
|
209
|
-
: [];
|
|
225
|
+
async fetchList(path, listKey, params, ctx, extraQuery) {
|
|
226
|
+
const data = await this.get(path, ctx, this.buildQuery(params, extraQuery));
|
|
227
|
+
const items = this.extractListItems(data[listKey]);
|
|
210
228
|
const pagination = this.extractPagination(data.pagination, items.length, params);
|
|
211
229
|
return { data: items, pagination };
|
|
212
230
|
}
|
|
@@ -218,44 +236,26 @@ export class CongressApiService {
|
|
|
218
236
|
return { count, nextOffset };
|
|
219
237
|
}
|
|
220
238
|
// biome-ignore lint/suspicious/noExplicitAny: API responses are dynamic JSON
|
|
221
|
-
|
|
239
|
+
get(path, ctx, query) {
|
|
222
240
|
this.checkRateLimit();
|
|
223
|
-
const url =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
if (res.status >= 500) {
|
|
240
|
-
const body = await res.text().catch(() => '');
|
|
241
|
-
// The API returns 500 instead of 404 for some nonexistent entities —
|
|
242
|
-
// these have structured JSON error bodies vs. empty/HTML for real outages
|
|
243
|
-
if (body.includes('"error"')) {
|
|
244
|
-
throw new Error(`Congress.gov API returned HTTP ${res.status}: ${body}`);
|
|
245
|
-
}
|
|
246
|
-
if (attempt < MAX_RETRIES - 1) {
|
|
247
|
-
await sleep(BASE_BACKOFF_MS * 2 ** attempt);
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
throw serviceUnavailable(`Congress.gov API returned HTTP ${res.status} after ${MAX_RETRIES} attempts.`, { status: res.status });
|
|
251
|
-
}
|
|
252
|
-
if (!res.ok) {
|
|
253
|
-
const body = await res.text().catch(() => '');
|
|
254
|
-
throw new Error(`Congress.gov API returned HTTP ${res.status}: ${body || res.statusText}`);
|
|
255
|
-
}
|
|
241
|
+
const url = this.buildUrl(path, query);
|
|
242
|
+
const operation = `CongressApiService GET ${path}`;
|
|
243
|
+
const requestContext = this.getRequestContext(ctx, operation);
|
|
244
|
+
const signal = this.getAbortSignal(ctx);
|
|
245
|
+
const retryOptions = {
|
|
246
|
+
operation,
|
|
247
|
+
context: requestContext,
|
|
248
|
+
baseDelayMs: BASE_BACKOFF_MS,
|
|
249
|
+
maxRetries: MAX_ATTEMPTS - 1,
|
|
250
|
+
isTransient: (error) => this.isRetryableError(error),
|
|
251
|
+
...(signal ? { signal } : {}),
|
|
252
|
+
};
|
|
253
|
+
return withRetry(async () => {
|
|
254
|
+
const response = await this.fetchResponse(url, path, requestContext, signal);
|
|
255
|
+
const data = this.parseJsonResponse(await response.text(), path);
|
|
256
256
|
this.requestCount++;
|
|
257
|
-
return
|
|
258
|
-
}
|
|
257
|
+
return data;
|
|
258
|
+
}, retryOptions);
|
|
259
259
|
}
|
|
260
260
|
checkRateLimit() {
|
|
261
261
|
const now = Date.now();
|
|
@@ -286,9 +286,109 @@ export class CongressApiService {
|
|
|
286
286
|
};
|
|
287
287
|
return mapping[subResource] ?? subResource;
|
|
288
288
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
buildUrl(path, query) {
|
|
290
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
291
|
+
url.searchParams.set('format', 'json');
|
|
292
|
+
url.searchParams.set('api_key', this.apiKey);
|
|
293
|
+
if (query) {
|
|
294
|
+
for (const [k, v] of Object.entries(query)) {
|
|
295
|
+
if (v !== undefined && v !== '')
|
|
296
|
+
url.searchParams.set(k, v);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return url;
|
|
300
|
+
}
|
|
301
|
+
buildQuery(params, extraQuery) {
|
|
302
|
+
const query = { ...extraQuery };
|
|
303
|
+
if (params?.limit != null)
|
|
304
|
+
query.limit = String(params.limit);
|
|
305
|
+
if (params?.offset != null)
|
|
306
|
+
query.offset = String(params.offset);
|
|
307
|
+
if (params?.fromDateTime)
|
|
308
|
+
query.fromDateTime = params.fromDateTime;
|
|
309
|
+
if (params?.toDateTime)
|
|
310
|
+
query.toDateTime = params.toDateTime;
|
|
311
|
+
return query;
|
|
312
|
+
}
|
|
313
|
+
extractListItems(raw) {
|
|
314
|
+
if (Array.isArray(raw))
|
|
315
|
+
return raw;
|
|
316
|
+
if (!isApiRecord(raw))
|
|
317
|
+
return [];
|
|
318
|
+
const nestedItems = Object.values(raw).find(Array.isArray);
|
|
319
|
+
return Array.isArray(nestedItems) ? nestedItems : [];
|
|
320
|
+
}
|
|
321
|
+
getRequestContext(ctx, operation) {
|
|
322
|
+
const ctxRecord = ctx;
|
|
323
|
+
const requestId = typeof ctxRecord?.requestId === 'string' ? ctxRecord.requestId : 'congress-api-service';
|
|
324
|
+
const timestamp = typeof ctxRecord?.timestamp === 'string' ? ctxRecord.timestamp : new Date().toISOString();
|
|
325
|
+
return { operation, requestId, timestamp };
|
|
326
|
+
}
|
|
327
|
+
getAbortSignal(ctx) {
|
|
328
|
+
const signal = ctx?.signal;
|
|
329
|
+
return isNativeAbortSignal(signal) ? signal : undefined;
|
|
330
|
+
}
|
|
331
|
+
async fetchResponse(url, path, requestContext, signal) {
|
|
332
|
+
try {
|
|
333
|
+
return await fetchWithTimeout(url, REQUEST_TIMEOUT_MS, requestContext, {
|
|
334
|
+
headers: { Accept: 'application/json' },
|
|
335
|
+
...(signal ? { signal } : {}),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
if (error instanceof McpError && error.code === JsonRpcErrorCode.ServiceUnavailable) {
|
|
340
|
+
const statusCode = typeof error.data?.statusCode === 'number' ? error.data.statusCode : undefined;
|
|
341
|
+
if (statusCode === 404) {
|
|
342
|
+
throw notFound('Congress.gov resource not found', { path }, { cause: error });
|
|
343
|
+
}
|
|
344
|
+
if (statusCode === 429) {
|
|
345
|
+
throw rateLimited('Congress.gov API rate limit reached (5,000 requests/hour). Wait before retrying — the limit resets hourly.', { path }, { cause: error });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
isRetryableError(error) {
|
|
352
|
+
if (error instanceof McpError) {
|
|
353
|
+
return (error.code === JsonRpcErrorCode.ServiceUnavailable ||
|
|
354
|
+
error.code === JsonRpcErrorCode.Timeout);
|
|
355
|
+
}
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
parseJsonResponse(text, path) {
|
|
359
|
+
const trimmed = text.trim();
|
|
360
|
+
if (!trimmed) {
|
|
361
|
+
throw serviceUnavailable('Congress.gov API returned an empty response body.', { path });
|
|
362
|
+
}
|
|
363
|
+
if (HTML_RESPONSE_RE.test(trimmed)) {
|
|
364
|
+
throw serviceUnavailable('Congress.gov API returned HTML instead of JSON.', {
|
|
365
|
+
path,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
return JSON.parse(trimmed);
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
throw serviceUnavailable('Congress.gov API returned invalid JSON.', { path }, { cause: error });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
isMissingEntityErrorBody(body) {
|
|
376
|
+
const trimmed = body.trim();
|
|
377
|
+
if (!trimmed || HTML_RESPONSE_RE.test(trimmed))
|
|
378
|
+
return false;
|
|
379
|
+
try {
|
|
380
|
+
const parsed = JSON.parse(trimmed);
|
|
381
|
+
const message = typeof parsed.error === 'string'
|
|
382
|
+
? parsed.error
|
|
383
|
+
: typeof parsed.message === 'string'
|
|
384
|
+
? parsed.message
|
|
385
|
+
: '';
|
|
386
|
+
return /not found|no data/i.test(message);
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
292
392
|
}
|
|
293
393
|
let _service;
|
|
294
394
|
export function initCongressApi() {
|