get_independent_expenditures
Retrieve independent expenditures by PACs and Super PACs, filtered by candidate, committee, support/oppose indicator, or amount. Track outside money influencing federal elections.
Instructions
Retrieve independent expenditures (Schedule E) - money spent by PACs and Super PACs to support or oppose candidates without coordinating with campaigns. Critical for understanding outside money influence in elections. Can filter by candidate targeted, committee spending, or support/oppose indicator.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| candidate_id | No | FEC candidate ID to find expenditures targeting this candidate | |
| committee_id | No | FEC committee ID to find expenditures made by this committee | |
| support_oppose | No | Filter by support or oppose indicator | |
| min_amount | No | Minimum expenditure amount to include | |
| cycle | No | Two-year election cycle (e.g., 2024) | |
| limit | No | Maximum number of results to return (default: 20) |
Implementation Reference
- Main handler function that executes the get_independent_expenditures tool logic. Calls the FEC API's getScheduleE, formats results using formatIndependentExpenditureText, and returns a formatted response.
export async function executeGetIndependentExpenditures( client: FECClient, params: { candidate_id?: string; committee_id?: string; support_oppose?: 'support' | 'oppose'; min_amount?: number; cycle?: number; limit?: number; } ): Promise<GetIndependentExpendituresResult> { try { // Map support/oppose to FEC indicator let supportOpposeIndicator: 'S' | 'O' | undefined; if (params.support_oppose === 'support') { supportOpposeIndicator = 'S'; } else if (params.support_oppose === 'oppose') { supportOpposeIndicator = 'O'; } const response = await client.getScheduleE({ candidate_id: params.candidate_id, committee_id: params.committee_id, support_oppose_indicator: supportOpposeIndicator, min_amount: params.min_amount, two_year_transaction_period: params.cycle, limit: params.limit ?? 20, }); // Build header based on search type let targetCandidate: string | undefined; if (params.candidate_id && response.results.length > 0) { targetCandidate = response.results[0].candidate_name || params.candidate_id; } // Format response const lines: string[] = []; // Add context header if (params.candidate_id) { lines.push(`## Independent Expenditures Targeting ${targetCandidate || params.candidate_id}`); } else if (params.committee_id) { const committeeName = response.results[0]?.committee_name || params.committee_id; lines.push(`## Independent Expenditures by ${committeeName}`); } // Add filter info const filters: string[] = []; if (params.support_oppose) { filters.push(`${params.support_oppose} only`); } if (params.min_amount) { filters.push(`minimum $${params.min_amount.toLocaleString()}`); } if (params.cycle) { filters.push(`${params.cycle} cycle`); } if (filters.length > 0) { lines.push(`*Filters: ${filters.join(', ')}*`); } lines.push(`*Showing ${response.results.length} of ${response.pagination.count} results*`); lines.push(''); // Format the expenditures const expendituresText = formatIndependentExpenditureText(response.results, targetCandidate); lines.push(expendituresText); return { content: [{ type: 'text', text: lines.join('\n') }], }; } catch (error) { return { content: [{ type: 'text', text: formatErrorForToolResponse(error) }], isError: true, }; } } - Input schema definition using Zod for validation. Defines fields: candidate_id, committee_id, support_oppose, min_amount, cycle, limit. Uses superRefine to require at least one of candidate_id or committee_id.
export const getIndependentExpendituresInputSchema = { candidate_id: z .string() .regex( candidateIdPattern, 'Candidate ID must be in format like H8CA15053 or P00009423' ) .optional() .describe('FEC candidate ID to find expenditures targeting this candidate'), committee_id: z .string() .regex(committeeIdPattern, 'Committee ID must be in format like C00123456') .optional() .describe('FEC committee ID to find expenditures made by this committee'), support_oppose: z .enum(['support', 'oppose']) .optional() .describe('Filter by support or oppose indicator'), min_amount: z .number() .min(0) .optional() .describe('Minimum expenditure amount to include'), cycle: z .number() .int() .min(2000) .max(2030) .optional() .describe('Two-year election cycle (e.g., 2024)'), limit: z .number() .int() .min(1) .max(100) .optional() .describe('Maximum number of results to return (default: 20)'), }; export const getIndependentExpendituresParamsSchema = z .object(getIndependentExpendituresInputSchema) .superRefine((value, ctx) => { if (!value.candidate_id && !value.committee_id) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Please provide either a candidate_id or committee_id to search for independent expenditures.', }); } }); export type GetIndependentExpendituresInput = z.infer< typeof getIndependentExpendituresParamsSchema >; - src/tools/index.ts:162-231 (registration)Registration of the GET_INDEPENDENT_EXPENDITURES_TOOL in the tools index. Maps the tool definition, paramsSchema, and execute function together for server registration.
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/get-independent-expenditures.ts:11-15 (registration)Tool definition object with name 'get_independent_expenditures', description, and inputSchema reference.
export const GET_INDEPENDENT_EXPENDITURES_TOOL = { name: 'get_independent_expenditures', description: `Retrieve independent expenditures (Schedule E) - money spent by PACs and Super PACs to support or oppose candidates without coordinating with campaigns. Critical for understanding outside money influence in elections. Can filter by candidate targeted, committee spending, or support/oppose indicator.`, inputSchema: getIndependentExpendituresInputSchema, }; - src/utils/formatters.ts:372-417 (helper)Helper function to format independent expenditure results for display. Groups by support/oppose, shows totals, and lists each expenditure with committee name, amount, candidate, date, purpose, and payee.
export function formatIndependentExpenditureText( expenditures: FECScheduleE[], targetCandidate?: string ): string { if (expenditures.length === 0) { return 'No independent expenditures found matching the criteria.'; } const lines: string[] = []; if (targetCandidate) { lines.push(`## Independent Expenditures Targeting ${targetCandidate}`); } else { lines.push('## Independent Expenditures'); } lines.push(''); // Group by support/oppose const supporting = expenditures.filter(e => e.support_oppose_indicator === 'S'); const opposing = expenditures.filter(e => e.support_oppose_indicator === 'O'); const totalSupport = supporting.reduce((sum, e) => sum + e.expenditure_amount, 0); const totalOppose = opposing.reduce((sum, e) => sum + e.expenditure_amount, 0); lines.push(`**Total Supporting:** ${formatCurrency(totalSupport)} (${supporting.length} expenditures)`); lines.push(`**Total Opposing:** ${formatCurrency(totalOppose)} (${opposing.length} expenditures)`); lines.push(''); expenditures.forEach((exp, index) => { const indicator = exp.support_oppose_indicator === 'S' ? '✓ SUPPORT' : '✗ OPPOSE'; lines.push(`${index + 1}. **${exp.committee_name}** - ${formatCurrency(exp.expenditure_amount)} [${indicator}]`); if (exp.candidate_name) { lines.push(` - Candidate: ${exp.candidate_name} (${exp.candidate_party || 'Unknown party'})`); } lines.push(` - Date: ${formatDate(exp.expenditure_date)}`); if (exp.expenditure_description) { lines.push(` - Purpose: ${exp.expenditure_description}`); } if (exp.payee_name) { lines.push(` - Paid to: ${exp.payee_name}`); } lines.push(''); }); return lines.join('\n'); }