Skip to main content
Glama
2b3pro

propublica-npo-mcp

by 2b3pro

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

TableJSON Schema
NameRequiredDescriptionDefault
einsYesA list of 2-5 nonprofit EINs, each with or without punctuation.

Implementation Reference

  • 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;
    }
  • 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,
      };
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the full burden. It mentions 'key financial metrics' and 'most recent filing' but does not disclose what happens if filings are missing, whether the tool is read-only, or any rate limits or error conditions. This is insufficient for behavioral transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence of 15 words. It is front-loaded with the main action and provides all necessary information without any extraneous text. Every word contributes meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has one parameter and no output schema, the description provides a basic understanding of input and purpose but fails to describe the output format or behavior for edge cases (e.g., missing data). It is minimally adequate but not thorough.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema covers the parameter 'eins' fully (100% coverage) with minItems/maxItems and description. The tool description adds little beyond '2-5 nonprofits' which is already in the schema. Baseline 3 is appropriate as the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Compare'), the resource ('nonprofits'), the scope ('2-5', 'side by side'), and the content ('key financial metrics from their most recent filing'). It effectively distinguishes from sibling tools like 'get_organization' (single org) and 'search_nonprofits' (search).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implicitly tells when to use (comparing multiple nonprofits) but does not provide explicit when-not-to-use guidance or mention alternative tools. It lacks exclusions like needing exactly 2-5 EINs or what to do for more than 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/2b3pro/propublica-npo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server