get_receipts
Retrieve itemized contributions received by a campaign committee. Filter by amount, contributor type, and sort by amount or date to analyze donation patterns.
Instructions
Retrieve itemized contributions (Schedule A) received by a campaign committee. Shows individual and organizational donors, amounts, and contributor details. Automatically classifies PAC contributions by type (Corporate, Labor, Trade, Leadership PAC) for deeper analysis. Supports filtering by amount threshold for researching significant contributions and campaign finance patterns.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| committee_id | Yes | FEC committee ID (e.g., "C00401224") | |
| min_amount | No | Minimum contribution amount to filter (default: $1,000) | |
| two_year_transaction_period | No | Two-year period (e.g., 2024 covers 2023-2024). | |
| cycle | No | Alias for two_year_transaction_period to align with finance cycle filters. | |
| contributor_type | No | Filter by contributor type: "individual" or "committee" (PAC) | |
| include_notable | No | Include flagged-first notable analysis block in output (default: true) | |
| fuzzy_threshold | No | Fuzzy match confidence threshold for reference list matching (default: 90) | |
| limit | No | Number of results to return (default: 20, max: 100) | |
| sort_by | No | Sort results by "amount" (descending) or "date" (most recent first) | amount |
Implementation Reference
- src/tools/get-receipts.ts:30-180 (handler)The main handler function `executeGetReceipts` that retrieves itemized contributions (Schedule A) with PAC classification. It fetches receipts from the FEC API, classifies PAC committees, enriches data, optionally runs notable receipt analysis, and formats the output.
export async function executeGetReceipts( client: FECClient, params: { committee_id: string; min_amount?: number; two_year_transaction_period?: number; cycle?: number; contributor_type?: 'individual' | 'committee'; include_notable?: boolean; fuzzy_threshold?: number; limit?: number; sort_by?: 'amount' | 'date'; } ): Promise<GetReceiptsResult> { try { const transactionPeriod = params.two_year_transaction_period ?? params.cycle; const includeNotable = params.include_notable ?? true; const fuzzyThreshold = params.fuzzy_threshold ?? 90; const response = await client.getScheduleA({ committee_id: params.committee_id, min_amount: params.min_amount ?? 1000, two_year_transaction_period: transactionPeriod, contributor_type: params.contributor_type, limit: params.limit ?? 20, sort_by: params.sort_by ?? 'amount', }); // Get unique PAC committee IDs for enrichment const pacCommitteeIds = [ ...new Set( response.results .filter(r => r.contributor_committee_id) .map(r => r.contributor_committee_id as string) ), ]; // Fetch PAC details in parallel (limit to 10 to avoid rate limiting) const pacDetailsMap = new Map<string, PACClassification>(); const pacIdsToFetch = pacCommitteeIds.slice(0, 10); if (pacIdsToFetch.length > 0) { const pacDetailsPromises = pacIdsToFetch.map(async (id) => { try { const committeeResponse = await client.getCommittee(id); if (committeeResponse.results.length > 0) { return { id, classification: classifyPAC(committeeResponse.results[0]) }; } return null; } catch { // If lookup fails, skip this PAC return null; } }); const pacResults = await Promise.all(pacDetailsPromises); for (const result of pacResults) { if (result) { pacDetailsMap.set(result.id, result.classification); } } } // Transform to enriched receipts with PAC classification const enrichedReceipts: EnrichedReceipt[] = response.results.map(record => { const base = transformScheduleA(record); const pacId = record.contributor_committee_id; return { ...base, contributor_committee_id: pacId, pac_classification: pacId ? (pacDetailsMap.get(pacId) || null) : null, }; }); // Get committee name from first result if available const committeeName = response.results[0]?.committee_name; // Build response text const lines: string[] = []; if (committeeName) { lines.push(`## Contributions to ${committeeName}`); } else { lines.push(`## Contributions to ${params.committee_id}`); } // Add filter info const filters: string[] = []; if (params.min_amount) { filters.push(`minimum $${params.min_amount.toLocaleString()}`); } if (params.contributor_type) { filters.push(`${params.contributor_type}s only`); } if (transactionPeriod) { filters.push( params.two_year_transaction_period ? `${transactionPeriod} cycle` : `${transactionPeriod} cycle (auto-aligned from cycle)` ); } if (filters.length > 0) { lines.push(`*Filters: ${filters.join(', ')}*`); } lines.push(`*Showing ${enrichedReceipts.length} of ${response.pagination.count} results*`); const committeeOrOrgReceipts = enrichedReceipts.filter( (receipt) => receipt.contributor_type !== 'Individual' ); if (committeeOrOrgReceipts.length > 0) { const classifiedCommitteeOrOrgReceipts = committeeOrOrgReceipts.filter( (receipt) => receipt.pac_classification !== null ).length; const unclassifiedCommitteeOrOrgReceipts = committeeOrOrgReceipts.length - classifiedCommitteeOrOrgReceipts; lines.push( `*Committee/organization receipts: ${committeeOrOrgReceipts.length} (PAC-classified: ${classifiedCommitteeOrOrgReceipts}, unclassified: ${unclassifiedCommitteeOrOrgReceipts})*` ); } lines.push(''); if (includeNotable) { const referenceData = loadReferenceData(); const notableItems = classifyNotableReceipts( enrichedReceipts, committeeName || params.committee_id, referenceData, fuzzyThreshold ); lines.push(formatNotableReceiptsText(notableItems, Math.min(params.limit ?? 20, 10))); lines.push(''); } // Format enriched receipts const receiptsText = formatEnrichedReceiptsText(enrichedReceipts, undefined); lines.push(receiptsText); return { content: [{ type: 'text', text: lines.join('\n') }], }; } catch (error) { return { content: [{ type: 'text', text: formatErrorForToolResponse(error) }], isError: true, }; } } - src/tools/get-receipts.ts:19-23 (handler)Tool definition object `GET_RECEIPTS_TOOL` with name 'get_receipts', description, and input schema reference.
export const GET_RECEIPTS_TOOL = { name: 'get_receipts', description: `Retrieve itemized contributions (Schedule A) received by a campaign committee. Shows individual and organizational donors, amounts, and contributor details. Automatically classifies PAC contributions by type (Corporate, Labor, Trade, Leadership PAC) for deeper analysis. Supports filtering by amount threshold for researching significant contributions and campaign finance patterns.`, inputSchema: getReceiptsInputSchema, }; - src/schemas/receipts.schema.ts:9-76 (schema)Input schema definition for get_receipts using Zod, defining all parameters: committee_id, min_amount, two_year_transaction_period, cycle, contributor_type, include_notable, fuzzy_threshold, limit, and sort_by.
export const getReceiptsInputSchema = { committee_id: z .string() .regex(committeeIdPattern, 'Committee ID must be in format C00000000') .describe('FEC committee ID (e.g., "C00401224")'), min_amount: z .number() .positive('Minimum amount must be positive') .optional() .default(1000) .describe('Minimum contribution amount to filter (default: $1,000)'), two_year_transaction_period: z .number() .int() .min(1980) .max(2030) .optional() .describe('Two-year period (e.g., 2024 covers 2023-2024).'), cycle: z .number() .int() .min(1980) .max(2030) .optional() .describe('Alias for two_year_transaction_period to align with finance cycle filters.'), contributor_type: z .enum(['individual', 'committee']) .optional() .describe('Filter by contributor type: "individual" or "committee" (PAC)'), include_notable: z .boolean() .optional() .default(true) .describe('Include flagged-first notable analysis block in output (default: true)'), fuzzy_threshold: z .number() .int() .min(80) .max(99) .optional() .default(90) .describe('Fuzzy match confidence threshold for reference list matching (default: 90)'), limit: z .number() .int() .min(1, 'Limit must be at least 1') .max(100, 'Limit cannot exceed 100') .optional() .default(20) .describe('Number of results to return (default: 20, max: 100)'), sort_by: z .enum(['amount', 'date']) .optional() .default('amount') .describe('Sort results by "amount" (descending) or "date" (most recent first)'), }; export const getReceiptsParamsSchema = z.object(getReceiptsInputSchema); export type GetReceiptsInput = z.infer<typeof getReceiptsParamsSchema>; - src/tools/index.ts:100-230 (registration)Registration of the get_receipts tool in `registerTools()`: maps GET_RECEIPTS_TOOL def, getReceiptsParamsSchema, and executeGetReceipts handler into the server via server.tool() (lines 130-144, 212-229).
export function registerTools(server: McpServer, config: Config): void { const client = new FECClient({ apiKey: config.fecApiKey, baseUrl: config.fecApiBaseUrl, timeout: config.fecApiTimeoutMs, }); const toolRegistrations: ToolRegistration[] = [ { def: SEARCH_CANDIDATES_TOOL, paramsSchema: searchCandidatesParamsSchema, execute: async (params) => executeSearchCandidates(client, params as { q: string; election_year?: number; office?: 'H' | 'S' | 'P'; state?: string; party?: string; }), }, { def: GET_COMMITTEE_FINANCES_TOOL, paramsSchema: getCommitteeFinancesParamsSchema, execute: async (params) => executeGetCommitteeFinances(client, params as { committee_id: string; cycle?: number; }), }, { def: GET_RECEIPTS_TOOL, paramsSchema: getReceiptsParamsSchema, execute: async (params) => executeGetReceipts(client, params as { committee_id: string; min_amount?: number; two_year_transaction_period?: number; cycle?: number; contributor_type?: 'individual' | 'committee'; include_notable?: boolean; fuzzy_threshold?: number; limit?: number; sort_by?: 'amount' | 'date'; }), }, { def: GET_DISBURSEMENTS_TOOL, paramsSchema: getDisbursementsParamsSchema, execute: async (params) => executeGetDisbursements(client, params as { committee_id: string; min_amount?: number; two_year_transaction_period?: number; cycle?: number; purpose?: string; include_notable?: boolean; fuzzy_threshold?: number; limit?: number; sort_by?: 'amount' | 'date'; }), }, { def: GET_INDEPENDENT_EXPENDITURES_TOOL, paramsSchema: getIndependentExpendituresParamsSchema, execute: async (params) => executeGetIndependentExpenditures(client, params as { candidate_id?: string; committee_id?: string; support_oppose?: 'support' | 'oppose'; min_amount?: number; cycle?: number; limit?: number; }), }, { def: GET_COMMITTEE_FLAGS_TOOL, paramsSchema: getCommitteeFlagsParamsSchema, execute: async (params) => executeGetCommitteeFlags(client, params as { committee_id: string; cycle?: number; }), }, { def: SEARCH_DONORS_TOOL, paramsSchema: searchDonorsParamsSchema, execute: async (params) => executeSearchDonors(client, params as { contributor_name?: string; contributor_employer?: string; contributor_occupation?: string; contributor_state?: string; min_amount?: number; cycle?: number; limit?: number; }), }, { def: SEARCH_SPENDING_TOOL, paramsSchema: searchSpendingParamsSchema, execute: async (params) => executeSearchSpending(client, params as { description?: string; recipient_name?: string; recipient_state?: string; min_amount?: number; cycle?: number; limit?: number; }), }, ]; for (const { def, paramsSchema, execute } of toolRegistrations) { server.tool( def.name, def.description, def.inputSchema, async (params): Promise<ToolResult> => { try { const validatedParams = await paramsSchema.parseAsync(params); const result = await execute(validatedParams); return { ...result } as ToolResult; } catch (error) { return { content: [{ type: 'text', text: formatErrorForToolResponse(error) }], isError: true, }; } } ); } - src/tools/index.ts:54-71 (helper)Re-export of GET_RECEIPTS_TOOL and executeGetReceipts from the tools index for use by other modules.
export { SEARCH_CANDIDATES_TOOL, executeSearchCandidates, GET_COMMITTEE_FINANCES_TOOL, executeGetCommitteeFinances, GET_RECEIPTS_TOOL, executeGetReceipts, GET_DISBURSEMENTS_TOOL, executeGetDisbursements, GET_INDEPENDENT_EXPENDITURES_TOOL, executeGetIndependentExpenditures, GET_COMMITTEE_FLAGS_TOOL, executeGetCommitteeFlags, SEARCH_DONORS_TOOL, executeSearchDonors, SEARCH_SPENDING_TOOL, executeSearchSpending, };