@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.
Files changed (59) hide show
  1. package/AGENTS.md +272 -0
  2. package/CLAUDE.md +2 -2
  3. package/README.md +2 -2
  4. package/dist/mcp-server/resources/definitions/bill.resource.d.ts.map +1 -1
  5. package/dist/mcp-server/resources/definitions/bill.resource.js +1 -1
  6. package/dist/mcp-server/resources/definitions/bill.resource.js.map +1 -1
  7. package/dist/mcp-server/resources/definitions/committee.resource.js +1 -1
  8. package/dist/mcp-server/resources/definitions/committee.resource.js.map +1 -1
  9. package/dist/mcp-server/resources/definitions/current-congress.resource.js +1 -1
  10. package/dist/mcp-server/resources/definitions/current-congress.resource.js.map +1 -1
  11. package/dist/mcp-server/resources/definitions/member.resource.js +1 -1
  12. package/dist/mcp-server/resources/definitions/member.resource.js.map +1 -1
  13. package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +18 -1
  14. package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +50 -16
  16. package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
  17. package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts +7 -1
  18. package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts.map +1 -1
  19. package/dist/mcp-server/tools/definitions/bill-summaries.tool.js +25 -13
  20. package/dist/mcp-server/tools/definitions/bill-summaries.tool.js.map +1 -1
  21. package/dist/mcp-server/tools/definitions/committee-lookup.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/committee-lookup.tool.js +4 -8
  23. package/dist/mcp-server/tools/definitions/committee-lookup.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts.map +1 -1
  25. package/dist/mcp-server/tools/definitions/committee-reports.tool.js +4 -11
  26. package/dist/mcp-server/tools/definitions/committee-reports.tool.js.map +1 -1
  27. package/dist/mcp-server/tools/definitions/crs-reports.tool.d.ts +13 -1
  28. package/dist/mcp-server/tools/definitions/crs-reports.tool.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/definitions/crs-reports.tool.js +39 -8
  30. package/dist/mcp-server/tools/definitions/crs-reports.tool.js.map +1 -1
  31. package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts.map +1 -1
  32. package/dist/mcp-server/tools/definitions/daily-record.tool.js +4 -6
  33. package/dist/mcp-server/tools/definitions/daily-record.tool.js.map +1 -1
  34. package/dist/mcp-server/tools/definitions/enacted-laws.tool.js +3 -9
  35. package/dist/mcp-server/tools/definitions/enacted-laws.tool.js.map +1 -1
  36. package/dist/mcp-server/tools/definitions/member-lookup.tool.d.ts +8 -1
  37. package/dist/mcp-server/tools/definitions/member-lookup.tool.d.ts.map +1 -1
  38. package/dist/mcp-server/tools/definitions/member-lookup.tool.js +24 -13
  39. package/dist/mcp-server/tools/definitions/member-lookup.tool.js.map +1 -1
  40. package/dist/mcp-server/tools/definitions/roll-votes.tool.d.ts.map +1 -1
  41. package/dist/mcp-server/tools/definitions/roll-votes.tool.js +4 -8
  42. package/dist/mcp-server/tools/definitions/roll-votes.tool.js.map +1 -1
  43. package/dist/mcp-server/tools/definitions/senate-nominations.tool.d.ts.map +1 -1
  44. package/dist/mcp-server/tools/definitions/senate-nominations.tool.js +5 -9
  45. package/dist/mcp-server/tools/definitions/senate-nominations.tool.js.map +1 -1
  46. package/dist/mcp-server/tools/format-helpers.d.ts +3 -3
  47. package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
  48. package/dist/mcp-server/tools/format-helpers.js +198 -29
  49. package/dist/mcp-server/tools/format-helpers.js.map +1 -1
  50. package/dist/mcp-server/tools/tool-helpers.d.ts +13 -0
  51. package/dist/mcp-server/tools/tool-helpers.d.ts.map +1 -0
  52. package/dist/mcp-server/tools/tool-helpers.js +23 -0
  53. package/dist/mcp-server/tools/tool-helpers.js.map +1 -0
  54. package/dist/services/congress-api/congress-api-service.d.ts +41 -47
  55. package/dist/services/congress-api/congress-api-service.d.ts.map +1 -1
  56. package/dist/services/congress-api/congress-api-service.js +233 -133
  57. package/dist/services/congress-api/congress-api-service.js.map +1 -1
  58. package/package.json +7 -11
  59. 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
- bill: unknown;
20
- }>;
21
- getBillSubResource(params: BillSubResourceParams): Promise<FetchListResult>;
22
- listLaws(params: ListLawsParams): Promise<FetchListResult>;
23
- getLaw(params: GetLawParams): Promise<{
24
- law: unknown;
25
- }>;
26
- listMembers(params: ListMembersParams): Promise<FetchListResult>;
27
- getMember(bioguideId: string): Promise<{
28
- member: unknown;
29
- }>;
30
- getMemberLegislation(params: GetMemberLegislationParams): Promise<FetchListResult>;
31
- listCommittees(params: ListCommitteesParams): Promise<FetchListResult>;
32
- getCommittee(chamber: string, committeeCode: string): Promise<{
33
- committee: unknown;
34
- }>;
35
- getCommitteeSubResource(params: CommitteeSubResourceParams): Promise<FetchListResult>;
36
- listVotes(params: ListVotesParams): Promise<FetchListResult>;
37
- getVote(params: GetVoteParams): Promise<{
38
- vote: unknown;
39
- }>;
40
- getVoteMembers(params: GetVoteParams): Promise<{
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":"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"}
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
- const MAX_RETRIES = 3;
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
- const data = await this.get(`/law/${params.congress}/${params.lawType}/${params.lawNumber}`);
46
- return { law: data.law };
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
- const query = {};
59
- if (params.currentMember !== undefined)
60
- query.currentMember = params.currentMember ? 'true' : 'false';
61
- return this.fetchList(path, 'members', params, query);
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 (err) {
139
- // Upstream API returns 500 instead of 404 for nonexistent report IDs
140
- if (err instanceof Error && err.message.includes('HTTP 500')) {
141
- throw notFound('CRS report not found', { reportNumber: params.reportNumber });
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 err;
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 query = { ...extraQuery };
194
- if (params?.limit)
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
- async get(path, query) {
239
+ get(path, ctx, query) {
222
240
  this.checkRateLimit();
223
- const url = new URL(`${this.baseUrl}${path}`);
224
- url.searchParams.set('format', 'json');
225
- url.searchParams.set('api_key', this.apiKey);
226
- if (query) {
227
- for (const [k, v] of Object.entries(query)) {
228
- if (v !== undefined && v !== '')
229
- url.searchParams.set(k, v);
230
- }
231
- }
232
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
233
- const res = await fetch(url.toString(), {
234
- headers: { Accept: 'application/json' },
235
- });
236
- if (res.status === 429) {
237
- throw rateLimited('Congress.gov API rate limit reached (5,000 requests/hour). Wait before retrying — the limit resets hourly.');
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 res.json();
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
- function sleep(ms) {
291
- return new Promise((resolve) => setTimeout(resolve, ms));
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() {