compare_nonprofits
Compare 2 to 5 nonprofits side by side on key financial metrics from their most recent IRS filings to evaluate performance.
Instructions
Compare 2-5 nonprofits side by side on key financial metrics from their most recent filing with data.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| eins | Yes | A list of 2-5 nonprofit EINs, each with or without punctuation. |
Implementation Reference
- src/tools/compare-nonprofits.ts:28-84 (handler)The main handler function that compares 2-5 nonprofits by fetching each organization, sorting filings to get the most recent one with data, and extracting key financial metrics (revenue, expenses, assets, CEO compensation, employees). Returns a comparison array or an error result.
export async function handler( client: ProPublicaClient, args: z.infer<z.ZodObject<typeof inputSchema>>, ) { try { const results = await Promise.all( args.eins.map(async (ein) => { try { const organization = await client.getOrganization(ein); const latestFiling = [...organization.filings_with_data].sort( (a, b) => filingTaxPeriod(b) - filingTaxPeriod(a), )[0]; if (!latestFiling) { return { ein: String(organization.organization.ein).replace(/\D/g, ""), error: "No filings with data are available for this organization.", }; } const summary = summarizeFiling(latestFiling); return { ein: String(organization.organization.ein).replace(/\D/g, ""), name: organization.organization.name, latest_filing_year: summary.tax_period, total_revenue: summary.total_revenue, total_expenses: summary.total_expenses, total_assets: summary.total_assets, net_assets: summary.net_assets, ceo_compensation: summary.ceo_compensation, employees: summary.employees, ntee_code: organization.organization.ntee_code ?? null, }; } catch (error) { if (error?.constructor?.name === "ProPublicaNotFoundError") { return { ein: String(ein).replace(/\D/g, ""), error: "EIN not found in ProPublica's database. Verify the EIN or search by name first.", }; } throw error; } }), ); const successful = results.filter((row) => !("error" in row)); if (successful.length === 0) { throw new Error("None of the requested EINs could be compared."); } return jsonResult({ comparison: results }); } catch (error) { return errorResult(error); } } - Input schema using Zod: accepts an array of 2-5 EIN strings (each min length 1) for comparison.
export const inputSchema = { eins: z .array(z.string().min(1)) .min(2) .max(5) .describe("A list of 2-5 nonprofit EINs, each with or without punctuation."), }; - src/index.ts:53-60 (registration)Registration of the 'compare_nonprofits' tool with the MCP server, linking its name, description, inputSchema, and handler.
server.registerTool( compareNonprofits.name, { description: compareNonprofits.description, inputSchema: compareNonprofits.inputSchema, }, (args) => compareNonprofits.handler(client, args), ); - Helper function 'filingTaxPeriod' that extracts the tax period year from a filing, with fallback.
function filingTaxPeriod(filing: FilingWithData): number { return filing.tax_prd ?? filing.tax_prd_yr ?? 0; } - src/tools/shared.ts:9-48 (helper)Shared helper 'jsonResult' used by the handler to format successful JSON responses.
export function jsonResult(value: unknown): CallToolResult { return { content: [ { type: "text", text: JSON.stringify(value, null, 2), }, ], structuredContent: value as Record<string, unknown>, }; } export function errorResult(error: unknown): CallToolResult { if (error instanceof ProPublicaInputError) { return { content: [{ type: "text", text: error.message }], isError: true, }; } if (error instanceof ProPublicaNotFoundError) { return { content: [{ type: "text", text: error.message }], isError: true, }; } if (error instanceof ProPublicaApiError) { return { content: [{ type: "text", text: error.message }], isError: true, }; } const message = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: message }], isError: true, }; }