@pipeworx/mcp-congress 0.1.1 → 0.1.3
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/README.md +2 -2
- package/package.json +3 -3
- package/server.json +1 -1
- package/src/committee-index-data.ts +67 -0
- package/src/index.ts +340 -1
- package/src/server.ts +1 -1
package/src/index.ts
CHANGED
|
@@ -452,7 +452,42 @@ async function fetchWithTimeout(
|
|
|
452
452
|
),
|
|
453
453
|
);
|
|
454
454
|
}
|
|
455
|
-
|
|
455
|
+
// Fleet #2382. Everything that isn't a timeout/abort here is a genuine
|
|
456
|
+
// NETWORK-LEVEL failure — DNS resolution, connection refused, TLS handshake,
|
|
457
|
+
// Cloudflare's own "Network connection lost." — meaning `fetch()` itself
|
|
458
|
+
// threw and no HTTP response of any kind was ever received. Until this fix
|
|
459
|
+
// that raw exception was rethrown VERBATIM: a bare `TypeError: fetch failed`
|
|
460
|
+
// (or the Workers-runtime equivalent) names no upstream, carries no class
|
|
461
|
+
// token, and reads exactly like a defect in OUR code — because it says
|
|
462
|
+
// nothing about the call at all. It landed in `error`, the tier that means
|
|
463
|
+
// "Pipeworx has a defect", for every one of the (at the time of writing)
|
|
464
|
+
// ~470 packs that call this helper directly with no wrapper of their own.
|
|
465
|
+
//
|
|
466
|
+
// `dexscreener` hit this independently (fleet #1579) and fixed it with a
|
|
467
|
+
// bespoke per-pack try/catch around `fetchWithTimeout`. That fix is correct
|
|
468
|
+
// but only covers one pack; every other caller of this shared helper still
|
|
469
|
+
// leaked the raw exception. Moving the same fix HERE — the one place that
|
|
470
|
+
// already carries the timeout case — covers every pack that uses
|
|
471
|
+
// `fetchWithTimeout` without a wrapper, for free, and without widening
|
|
472
|
+
// `classifyToolError`'s regex list: the fix is giving the message a proper
|
|
473
|
+
// `upstream_down:` token at the point the two facts (no response was ever
|
|
474
|
+
// received, and which host we were trying to reach) are actually in hand,
|
|
475
|
+
// not teaching the classifier to guess from prose after the fact.
|
|
476
|
+
//
|
|
477
|
+
// Safe on the same grounds as the timeout branch above: no argument a
|
|
478
|
+
// caller passes can make `fetch()` itself throw a connection-level error,
|
|
479
|
+
// so this is always an availability failure, never a caller mistake. Same
|
|
480
|
+
// `markInternalOrigin` treatment — an origin we run that never answered is
|
|
481
|
+
// still ours, not a third party's outage.
|
|
482
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
483
|
+
throw new Error(
|
|
484
|
+
markInternalOrigin(
|
|
485
|
+
`upstream_down: could not reach ${name} at all (${raw.slice(0, 160)}). ` +
|
|
486
|
+
`No request reached ${name}, so this says NOTHING about whether the arguments you passed ` +
|
|
487
|
+
'are valid — do not re-check them on the strength of this error. Retry shortly.',
|
|
488
|
+
url,
|
|
489
|
+
),
|
|
490
|
+
);
|
|
456
491
|
}
|
|
457
492
|
}
|
|
458
493
|
|
|
@@ -637,6 +672,7 @@ function collapse(s: string): string {
|
|
|
637
672
|
}
|
|
638
673
|
/**
|
|
639
674
|
* Congress MCP — US Congress data via GovTrack API (free, no auth required)
|
|
675
|
+
* plus committee rosters/leadership baked from a second, public-domain source.
|
|
640
676
|
*
|
|
641
677
|
* Tools:
|
|
642
678
|
* - search_bills: Search congressional bills by name, subject or keyword, ranked locally by title match
|
|
@@ -644,8 +680,32 @@ function collapse(s: string): string {
|
|
|
644
680
|
* - get_members: Get current members of Congress
|
|
645
681
|
* - get_votes: Get recent congressional votes (newest first)
|
|
646
682
|
* - get_recent_bills: Most recently introduced / acted-on bills
|
|
683
|
+
* - committee_leadership: every standing committee in a chamber with its chair + ranking member
|
|
684
|
+
* - committee_members: one committee's full roster (party, rank, chair/ranking title)
|
|
685
|
+
* - member_committees: one member's committee assignments by bioguide id
|
|
686
|
+
*
|
|
687
|
+
* COMMITTEE DATA (fleet #2843): GovTrack's API has no committee-membership or
|
|
688
|
+
* committee-leadership endpoint at all, so committee_leadership/
|
|
689
|
+
* committee_members/member_committees are baked from a SEPARATE source —
|
|
690
|
+
* github.com/unitedstates/congress-legislators (committees-current.yaml,
|
|
691
|
+
* committee-membership-current.yaml, legislators-current.yaml), CC0 1.0
|
|
692
|
+
* Universal ("no rights reserved" per that repo's own LICENSE, verified
|
|
693
|
+
* live). Refreshed by scripts/refresh-congress-committees.mjs into
|
|
694
|
+
* ./committee-index-data.ts, which is NEVER value-imported here (fleet #2754
|
|
695
|
+
* — a baked table as a static import blew the gateway bundle 24.4->37.8MiB
|
|
696
|
+
* and caused edge 1102s on ~1/4 of requests) — it is registered in
|
|
697
|
+
* workers/gateway/src/pack-baked-indexes.json, uploaded to KV at deploy, and
|
|
698
|
+
* injected as `args._bakedIndex` on every call into this pack. This data
|
|
699
|
+
* changes on a committee-reshuffle cadence, not daily; `data_as_of` on every
|
|
700
|
+
* response is the source files' own last-commit date, not "now".
|
|
647
701
|
*/
|
|
648
702
|
|
|
703
|
+
import type {
|
|
704
|
+
CommitteeRow,
|
|
705
|
+
CommitteeMemberRow,
|
|
706
|
+
MemberCommitteeAssignment,
|
|
707
|
+
MemberProfile,
|
|
708
|
+
} from './committee-index-data.js';
|
|
649
709
|
|
|
650
710
|
// Bound every fetch() in this pack to a fixed timeout — an upstream that
|
|
651
711
|
// degrades without erroring would otherwise hold the Worker in `await fetch()`
|
|
@@ -795,6 +855,243 @@ function formatVote(v: RawVote) {
|
|
|
795
855
|
};
|
|
796
856
|
}
|
|
797
857
|
|
|
858
|
+
// --- Baked committee index (fleet #2843) ---
|
|
859
|
+
//
|
|
860
|
+
// NEVER value-import ./committee-index-data.js here — the type-only import
|
|
861
|
+
// above is erased from the bundle; a value import is not (same trap as
|
|
862
|
+
// mcps/arkansas-code/src/index.ts's AR_SECTIONS, fleet #2754).
|
|
863
|
+
let COMMITTEES: CommitteeRow[] = [];
|
|
864
|
+
let COMMITTEE_MEMBERSHIP: Record<string, CommitteeMemberRow[]> = {};
|
|
865
|
+
let MEMBER_COMMITTEES: Record<string, MemberCommitteeAssignment[]> = {};
|
|
866
|
+
let MEMBER_PROFILES: Record<string, MemberProfile> = {};
|
|
867
|
+
let CONGRESS_DATA_AS_OF = '';
|
|
868
|
+
let COMMITTEE_INDEX_LOADED = false;
|
|
869
|
+
|
|
870
|
+
function ensureCommitteeIndex(args: Record<string, unknown>): void {
|
|
871
|
+
if (COMMITTEE_INDEX_LOADED) return;
|
|
872
|
+
const idx = args._bakedIndex as Record<string, unknown> | null | undefined;
|
|
873
|
+
const committees = idx?.COMMITTEES;
|
|
874
|
+
const membership = idx?.COMMITTEE_MEMBERSHIP;
|
|
875
|
+
const memberCommittees = idx?.MEMBER_COMMITTEES;
|
|
876
|
+
const memberProfiles = idx?.MEMBER_PROFILES;
|
|
877
|
+
const dataAsOf = idx?.CONGRESS_DATA_AS_OF;
|
|
878
|
+
const ok =
|
|
879
|
+
Array.isArray(committees) &&
|
|
880
|
+
committees.length > 0 &&
|
|
881
|
+
typeof membership === 'object' &&
|
|
882
|
+
membership !== null &&
|
|
883
|
+
typeof memberCommittees === 'object' &&
|
|
884
|
+
memberCommittees !== null &&
|
|
885
|
+
typeof memberProfiles === 'object' &&
|
|
886
|
+
memberProfiles !== null &&
|
|
887
|
+
typeof dataAsOf === 'string' &&
|
|
888
|
+
dataAsOf.length > 0;
|
|
889
|
+
if (!ok) {
|
|
890
|
+
// Loud on purpose, same posture as ar-code's ensureIndex: a missing or
|
|
891
|
+
// malformed baked index is a server-side KV fault, not "no committees
|
|
892
|
+
// exist" — collapsing the two would be exactly the silent-empty-array
|
|
893
|
+
// trap docs/silent-zero-policy.md holds every pack to.
|
|
894
|
+
throw new Error(
|
|
895
|
+
`congress: the baked committee index is unavailable (${idx == null ? 'not injected' : 'malformed'}). ` +
|
|
896
|
+
'The gateway loads it from KV on the first call into committee_leadership/committee_members/member_committees; ' +
|
|
897
|
+
'this is a server-side fault, not an empty roster — retry shortly. search_bills/get_bill/get_members/get_votes/get_recent_bills are unaffected (they call GovTrack live and never touch this index).',
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
COMMITTEES = committees as CommitteeRow[];
|
|
901
|
+
COMMITTEE_MEMBERSHIP = membership as Record<string, CommitteeMemberRow[]>;
|
|
902
|
+
MEMBER_COMMITTEES = memberCommittees as Record<string, MemberCommitteeAssignment[]>;
|
|
903
|
+
MEMBER_PROFILES = memberProfiles as Record<string, MemberProfile>;
|
|
904
|
+
CONGRESS_DATA_AS_OF = dataAsOf;
|
|
905
|
+
COMMITTEE_INDEX_LOADED = true;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const VALID_CHAMBERS = new Set(['house', 'senate', 'joint']);
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Chair/ranking member resolution: prefer the explicit title (handles
|
|
912
|
+
* "Chair"/"Chairman"/"Chairwoman" and "Ranking Member"); fall back to
|
|
913
|
+
* majority/minority rank 1 for the handful of caucuses and select
|
|
914
|
+
* committees (e.g. the Senate Intelligence/Ethics committees, the Joint
|
|
915
|
+
* Commission on Security and Cooperation in Europe) that the source data
|
|
916
|
+
* does not label with an explicit title.
|
|
917
|
+
*/
|
|
918
|
+
function resolveLeadership(members: CommitteeMemberRow[]): {
|
|
919
|
+
chair: CommitteeMemberRow | null;
|
|
920
|
+
ranking_member: CommitteeMemberRow | null;
|
|
921
|
+
} {
|
|
922
|
+
const chair =
|
|
923
|
+
members.find((m) => typeof m.title === 'string' && /^Chair/i.test(m.title)) ??
|
|
924
|
+
members.find((m) => m.party_role === 'majority' && m.rank === 1) ??
|
|
925
|
+
null;
|
|
926
|
+
const ranking =
|
|
927
|
+
members.find((m) => m.title === 'Ranking Member') ??
|
|
928
|
+
members.find((m) => m.party_role === 'minority' && m.rank === 1) ??
|
|
929
|
+
null;
|
|
930
|
+
return { chair, ranking_member: ranking };
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/** Words that appear in nearly every committee name and carry no identifying signal. */
|
|
934
|
+
const COMMITTEE_NAME_STOPWORDS = new Set([
|
|
935
|
+
'house', 'senate', 'joint', 'committee', 'committees', 'on', 'the', 'select',
|
|
936
|
+
'permanent', 'special', 'of', 'congress', 'united', 'states', 'us', 'subcommittee',
|
|
937
|
+
'and', 'for',
|
|
938
|
+
]);
|
|
939
|
+
|
|
940
|
+
function normalizeCommitteeName(s: string): string[] {
|
|
941
|
+
return s
|
|
942
|
+
.toLowerCase()
|
|
943
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
944
|
+
.trim()
|
|
945
|
+
.split(' ')
|
|
946
|
+
.filter((w) => w && !COMMITTEE_NAME_STOPWORDS.has(w));
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/** Full-committee (not subcommittee) name search — every query word must appear in (or be a prefix/suffix of) a candidate word. */
|
|
950
|
+
function findCommitteesByName(name: string, chamber: string | undefined): CommitteeRow[] {
|
|
951
|
+
const qWords = normalizeCommitteeName(name);
|
|
952
|
+
if (qWords.length === 0) return [];
|
|
953
|
+
return COMMITTEES.filter((c) => {
|
|
954
|
+
if (chamber && c.chamber !== chamber) return false;
|
|
955
|
+
const cWords = normalizeCommitteeName(c.name);
|
|
956
|
+
return qWords.every((w) => cWords.some((cw) => cw === w || cw.startsWith(w) || w.startsWith(cw)));
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function findCommitteeById(id: string): CommitteeRow | undefined {
|
|
961
|
+
const norm = id.trim().toUpperCase();
|
|
962
|
+
return COMMITTEES.find((c) => c.id.toUpperCase() === norm);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function buildFullCommittee(c: CommitteeRow) {
|
|
966
|
+
const members = COMMITTEE_MEMBERSHIP[c.id] ?? [];
|
|
967
|
+
const { chair, ranking_member } = resolveLeadership(members);
|
|
968
|
+
return {
|
|
969
|
+
id: c.id,
|
|
970
|
+
name: c.name,
|
|
971
|
+
chamber: c.chamber,
|
|
972
|
+
url: c.url,
|
|
973
|
+
minority_url: c.minority_url,
|
|
974
|
+
jurisdiction: c.jurisdiction,
|
|
975
|
+
chair,
|
|
976
|
+
ranking_member,
|
|
977
|
+
member_count: members.length,
|
|
978
|
+
members,
|
|
979
|
+
subcommittees: c.subcommittees,
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function committeeLeadership(args: Record<string, unknown>) {
|
|
984
|
+
ensureCommitteeIndex(args);
|
|
985
|
+
const chamberArg = typeof args.chamber === 'string' ? args.chamber.trim().toLowerCase() : undefined;
|
|
986
|
+
if (chamberArg && !VALID_CHAMBERS.has(chamberArg)) {
|
|
987
|
+
return { error: `chamber must be "house", "senate" or "joint" — got ${JSON.stringify(args.chamber)}`, chamber: args.chamber ?? null };
|
|
988
|
+
}
|
|
989
|
+
const committees = COMMITTEES.filter((c) => !chamberArg || c.chamber === chamberArg);
|
|
990
|
+
return {
|
|
991
|
+
chamber: chamberArg ?? 'all',
|
|
992
|
+
congress: 119,
|
|
993
|
+
data_as_of: CONGRESS_DATA_AS_OF,
|
|
994
|
+
source: 'github.com/unitedstates/congress-legislators (public domain, CC0 1.0 Universal)',
|
|
995
|
+
total: committees.length,
|
|
996
|
+
committees: committees.map((c) => {
|
|
997
|
+
const members = COMMITTEE_MEMBERSHIP[c.id] ?? [];
|
|
998
|
+
const { chair, ranking_member } = resolveLeadership(members);
|
|
999
|
+
return {
|
|
1000
|
+
id: c.id,
|
|
1001
|
+
name: c.name,
|
|
1002
|
+
chamber: c.chamber,
|
|
1003
|
+
url: c.url,
|
|
1004
|
+
chair,
|
|
1005
|
+
ranking_member,
|
|
1006
|
+
member_count: members.length,
|
|
1007
|
+
};
|
|
1008
|
+
}),
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function committeeMembers(args: Record<string, unknown>) {
|
|
1013
|
+
ensureCommitteeIndex(args);
|
|
1014
|
+
const chamberArg = typeof args.chamber === 'string' ? args.chamber.trim().toLowerCase() : undefined;
|
|
1015
|
+
if (chamberArg && !VALID_CHAMBERS.has(chamberArg)) {
|
|
1016
|
+
return { error: `chamber must be "house", "senate" or "joint" — got ${JSON.stringify(args.chamber)}`, chamber: args.chamber ?? null };
|
|
1017
|
+
}
|
|
1018
|
+
const idArg = typeof args.committee_id === 'string' && args.committee_id.trim() ? args.committee_id.trim() : undefined;
|
|
1019
|
+
const nameArg = typeof args.name === 'string' && args.name.trim() ? args.name.trim() : undefined;
|
|
1020
|
+
if (!idArg && !nameArg) {
|
|
1021
|
+
return { error: 'Pass committee_id (e.g. "HSWM") or name (e.g. "Ways and Means", "Appropriations") — call committee_leadership first to see every valid id/name.' };
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (idArg) {
|
|
1025
|
+
const top = findCommitteeById(idArg);
|
|
1026
|
+
if (top) return { data_as_of: CONGRESS_DATA_AS_OF, ...buildFullCommittee(top) };
|
|
1027
|
+
for (const c of COMMITTEES) {
|
|
1028
|
+
const sub = c.subcommittees.find((s) => s.id.toUpperCase() === idArg.toUpperCase());
|
|
1029
|
+
if (sub) {
|
|
1030
|
+
const members = COMMITTEE_MEMBERSHIP[sub.id] ?? [];
|
|
1031
|
+
const { chair, ranking_member } = resolveLeadership(members);
|
|
1032
|
+
return {
|
|
1033
|
+
data_as_of: CONGRESS_DATA_AS_OF,
|
|
1034
|
+
id: sub.id,
|
|
1035
|
+
name: `${c.name} Subcommittee on ${sub.name}`,
|
|
1036
|
+
chamber: c.chamber,
|
|
1037
|
+
parent_committee_id: c.id,
|
|
1038
|
+
parent_committee_name: c.name,
|
|
1039
|
+
chair,
|
|
1040
|
+
ranking_member,
|
|
1041
|
+
member_count: members.length,
|
|
1042
|
+
members,
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
found: false,
|
|
1048
|
+
error: `No committee or subcommittee with id "${idArg}". Call committee_leadership to list valid ids.`,
|
|
1049
|
+
committee_id: idArg,
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const matches = findCommitteesByName(nameArg!, chamberArg);
|
|
1054
|
+
if (matches.length === 0) {
|
|
1055
|
+
return {
|
|
1056
|
+
found: false,
|
|
1057
|
+
error: `No committee name matched "${nameArg}"${chamberArg ? ` in the ${chamberArg}` : ''}. Try a shorter name (e.g. "Appropriations", "Judiciary", "Ways and Means"), or pass chamber to disambiguate.`,
|
|
1058
|
+
name: nameArg,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
if (matches.length === 1) {
|
|
1062
|
+
return { data_as_of: CONGRESS_DATA_AS_OF, ...buildFullCommittee(matches[0]) };
|
|
1063
|
+
}
|
|
1064
|
+
return {
|
|
1065
|
+
data_as_of: CONGRESS_DATA_AS_OF,
|
|
1066
|
+
ambiguous: true,
|
|
1067
|
+
note: `"${nameArg}" matched ${matches.length} committees (one per chamber, usually) — pass chamber: "house" or "senate" to pick one.`,
|
|
1068
|
+
matches: matches.map(buildFullCommittee),
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function memberCommittees(args: Record<string, unknown>) {
|
|
1073
|
+
ensureCommitteeIndex(args);
|
|
1074
|
+
const bioguide = typeof args.bioguide_id === 'string' ? args.bioguide_id.trim().toUpperCase() : undefined;
|
|
1075
|
+
if (!bioguide) {
|
|
1076
|
+
return { error: 'Pass bioguide_id — a Congress.gov/GovInfo "bioguide" id, e.g. "N000015" for Richard Neal, "G000386" for Chuck Grassley.' };
|
|
1077
|
+
}
|
|
1078
|
+
const assignments = MEMBER_COMMITTEES[bioguide];
|
|
1079
|
+
if (!assignments || assignments.length === 0) {
|
|
1080
|
+
return {
|
|
1081
|
+
found: false,
|
|
1082
|
+
error: `No committee assignments found for bioguide "${bioguide}". This covers the current (119th) Congress only; check the id is correct and the member currently serves.`,
|
|
1083
|
+
bioguide_id: bioguide,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
data_as_of: CONGRESS_DATA_AS_OF,
|
|
1088
|
+
bioguide_id: bioguide,
|
|
1089
|
+
profile: MEMBER_PROFILES[bioguide] ?? null,
|
|
1090
|
+
total: assignments.length,
|
|
1091
|
+
committees: assignments,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
798
1095
|
// --- Tool definitions ---
|
|
799
1096
|
|
|
800
1097
|
const tools: McpToolExport['tools'] = [
|
|
@@ -881,6 +1178,42 @@ const tools: McpToolExport['tools'] = [
|
|
|
881
1178
|
},
|
|
882
1179
|
},
|
|
883
1180
|
},
|
|
1181
|
+
{
|
|
1182
|
+
name: 'committee_leadership',
|
|
1183
|
+
description:
|
|
1184
|
+
'ANSWERS "who chairs X committee" / "who are the ranking members of every House committee" / "list of committee chairs in Congress" — returns EVERY standing committee in one chamber (or all three chambers) of the 119th Congress with its chair and ranking member (name, party, bioguide id). Pass chamber to scope to "house", "senate" or "joint"; omit it for all three. For one committee\'s FULL roster (every member, not just the chair/ranking pair) use committee_members instead. SOURCE: github.com/unitedstates/congress-legislators (public domain), baked periodically — NOT GovTrack, which has no committee-leadership endpoint at all. data_as_of in the response is the source data\'s own last-update date.',
|
|
1185
|
+
inputSchema: {
|
|
1186
|
+
type: 'object',
|
|
1187
|
+
properties: {
|
|
1188
|
+
chamber: { type: 'string', description: '"house", "senate" or "joint". Omit to return every chamber.' },
|
|
1189
|
+
},
|
|
1190
|
+
},
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
name: 'committee_members',
|
|
1194
|
+
description:
|
|
1195
|
+
'ANSWERS "who is on the House Appropriations committee" / "roster of the Senate Judiciary committee" / "Democratic members of Ways and Means" — returns one committee\'s full membership with each member\'s party, rank and title (Chair / Ranking Member / plain member), plus the committee\'s chair, ranking member and list of subcommittees. Identify the committee by committee_id (the exact id from committee_leadership, e.g. "HSWM" for House Ways and Means, or a subcommittee id like "HSWM05") OR by name (e.g. "Appropriations", "Ways and Means", "Judiciary") — a name with no chamber that matches committees in BOTH the House and Senate (e.g. "Appropriations") returns both as `matches` rather than guessing; pass chamber to pick one directly. For the chair/ranking member of EVERY committee at once, use committee_leadership instead — it is cheaper when you do not need the full roster. SOURCE: github.com/unitedstates/congress-legislators (public domain), baked periodically, current (119th) Congress only.',
|
|
1196
|
+
inputSchema: {
|
|
1197
|
+
type: 'object',
|
|
1198
|
+
properties: {
|
|
1199
|
+
committee_id: { type: 'string', description: 'Exact committee or subcommittee id, e.g. "HSWM" (House Ways and Means) or "SSJU" (Senate Judiciary). Takes priority over name.' },
|
|
1200
|
+
name: { type: 'string', description: 'Committee name or partial name to search for, e.g. "Appropriations", "Judiciary", "Foreign Affairs".' },
|
|
1201
|
+
chamber: { type: 'string', description: 'Optional: "house", "senate" or "joint" — disambiguates a name that matches more than one chamber.' },
|
|
1202
|
+
},
|
|
1203
|
+
},
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
name: 'member_committees',
|
|
1207
|
+
description:
|
|
1208
|
+
'ANSWERS "what committees is X on" / "committee assignments for Senator X" — returns every committee and subcommittee assignment (committee name, chamber, rank, title) for one member of the 119th Congress, identified by their bioguide id (the standard Congress.gov/GovInfo member identifier, e.g. "N000015" for Richard Neal — committee_leadership and committee_members both return bioguide ids for the members they list, so look a member up there first if you only have a name). SOURCE: github.com/unitedstates/congress-legislators (public domain), baked periodically, current Congress only.',
|
|
1209
|
+
inputSchema: {
|
|
1210
|
+
type: 'object',
|
|
1211
|
+
properties: {
|
|
1212
|
+
bioguide_id: { type: 'string', description: 'The member\'s bioguide id, e.g. "N000015".' },
|
|
1213
|
+
},
|
|
1214
|
+
required: ['bioguide_id'],
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
884
1217
|
];
|
|
885
1218
|
|
|
886
1219
|
// --- callTool dispatcher ---
|
|
@@ -901,6 +1234,12 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
|
|
|
901
1234
|
args.congress as number | undefined,
|
|
902
1235
|
(args.limit as number) ?? 20,
|
|
903
1236
|
);
|
|
1237
|
+
case 'committee_leadership':
|
|
1238
|
+
return committeeLeadership(args);
|
|
1239
|
+
case 'committee_members':
|
|
1240
|
+
return committeeMembers(args);
|
|
1241
|
+
case 'member_committees':
|
|
1242
|
+
return memberCommittees(args);
|
|
904
1243
|
default:
|
|
905
1244
|
throw new Error(`Unknown tool: ${name}`);
|
|
906
1245
|
}
|
package/src/server.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
|
|
|
9
9
|
import pack from './index.js';
|
|
10
10
|
|
|
11
11
|
const server = new Server(
|
|
12
|
-
{ name: '@pipeworx/mcp-congress', version: '0.1.
|
|
12
|
+
{ name: '@pipeworx/mcp-congress', version: '0.1.3' },
|
|
13
13
|
{ capabilities: { tools: {} } },
|
|
14
14
|
);
|
|
15
15
|
|