@cyanheads/nonprofit-explorer-mcp-server

Search and explore 1.8M+ US nonprofits, fetch Form 990 financials, and access IRS filing history via MCP. STDIO or Streamable HTTP.

3 Tools

[![Version](https://img.shields.io/badge/Version-0.3.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/nonprofit-explorer-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.4.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/nonprofit-explorer-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/nonprofit-explorer-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.4-blueviolet.svg?style=flat-square)](https://bun.sh/)
[![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/nonprofit-explorer-mcp-server/releases/latest/download/nonprofit-explorer-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=nonprofit-explorer-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvbm9ucHJvZml0LWV4cGxvcmVyLW1jcC1zZXJ2ZXIiXX0=) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22nonprofit-explorer-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fnonprofit-explorer-mcp-server%22%5D%7D) [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
**Public Hosted Server:** [https://nonprofit-explorer.caseyjhand.com/mcp](https://nonprofit-explorer.caseyjhand.com/mcp)
--- ## Overview IRS Form 990 data for 1.8M+ tax-exempt organizations, via the ProPublica Nonprofit Explorer API. Search by name, fetch an org's financial snapshot, and pull its full filing history. ### Tools | Tool | Description | |:---|:---| | `nonprofit_search_organizations` | Search 1.8M+ IRS-recognized tax-exempt organizations by name, keyword, city, or phrase with optional state, NTEE sector, and 501(c) filters | | `nonprofit_get_organization` | Full profile by EIN: legal identity, IRS classification, filing requirements, newest-return metadata, and latest extracted financial snapshot | | `nonprofit_get_filings` | Form 990 history by EIN, optionally filtered by fiscal year: financials, fundraising figures, executive compensation, and source PDF links | ## Capability reference ### `nonprofit_search_organizations` tool - Full-text query — supports quoted phrases (`"Red Cross"`), required terms (`+evanston`), excluded terms (`-dental`) - Optional filters: state/territory name or postal code (including military codes and `ZZ` for foreign entities), NTEE major sector (`1`–`10` or a case-insensitive major letter), and 501(c) subsection code (`3` covers both public charities and private foundations — see `foundation_type` on `nonprofit_get_organization` to tell them apart). Names and letters normalize to the upstream codes shown in `active_filters`; unrecognized states are rejected before a request - `sub_name` is the upstream legal name with an optional secondary line; it can repeat `name` or be null - Paginated at 25 per page (`page`, zero-indexed); `num_pages`, `per_page`, and `page_offset` track position - API caps total results at 10,000 — `total_results === 10000` means the actual count may be higher, and a page whose offset reaches that ceiling is refused (`pagination_ceiling`) - Zero matches and a page past the last one both succeed with an empty `organizations` array and a `notice` explaining which case it is - Returns EINs — pass to `nonprofit_get_organization` or `nonprofit_get_filings` for details --- ### `nonprofit_get_organization` tool - Accepts EIN as integer (`530196605`) or string with or without hyphen (`"53-0196605"`); use `nonprofit_search_organizations` first if you only have an org name - Returns legal identity, address, NTEE code, 501(c) type, and IRS ruling date - IRS Business Master File standing: deductibility (including the deductible-by-treaty case), exemption status, and public-charity vs. private-foundation classification — each decoded from its IRS code with the raw code retained - `latest_filing` is the latest extracted financial snapshot: revenue, expenses, assets, liabilities, net assets, and source PDF link. `latest_return` separately identifies the newest return across extracted and PDF-only histories; a newer return need not have extracted figures - Filing requirements, private-foundation filing requirements, affiliation, and group exemption number come from the IRS Business Master File. Empty-history guidance uses these codes without claiming why a particular filing is absent - `filing_count` counts filings with extracted data; `filing_count_pdf_only` separately counts PDFs without financial extracts. Use `nonprofit_get_filings` for those links; PDF-only histories have no financial snapshot - Data lags 1–2 years — `tax_prd_yr` in the snapshot is the fiscal year of the filing, not the current year --- ### `nonprofit_get_filings` tool - All Form 990 filings with extracted data, sorted by full tax period newest first — per filing: revenue, expenses, assets, liabilities, net assets, revenue breakdown (contributions, program service, investment income), and the source PDF link - Optional inclusive `from_year` / `to_year` bounds apply to both extracted and PDF-only histories. Totals remain unfiltered; an empty selected interval does not mean the organization has no history - Financial fields follow each return's form type. For 990-PF, investment income is savings interest plus dividends and securities interest (Part I column (a), lines 3–4); both components must be extracted. It excludes capital gains, rents, and net investment income - 990-PF filings include `grants_paid`, `distributable_amount`, `fair_market_value_assets`, and `net_investment_income`. The distributable amount is a payout requirement, not actual payments or a compliance measure - `program_expense_ratio` is always `null` — ProPublica returns no Form 990 Part IX program-service expense total, so the program/management/fundraising split is only in the source PDF - Form 990 includes other salaries and wages, payroll taxes, professional fundraising fees, and fundraising event gross, direct expenses, and net income. For 990-EZ, event gross is separate from combined gaming/fundraising expenses and net income; the combined figures cannot establish event-only results - Executive compensation includes the source field for 990 and 990-PF. The 990-EZ extract has no compensation source: amount and field name are null, with guidance to Part IV of the PDF - `filings_pdf_only` retains fiscal years and tax periods for returns without extracted data, including returns newer than the financial snapshots - An EIN that resolves to a real org with no 990 on record returns an empty `filings` array plus a `notice`, not an error - Data lags 1–2 years — always cite `tax_prd_yr` (fiscal year) when presenting figures ## Features Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing. ProPublica Nonprofit Explorer / IRS-specific: - Keyless access — ProPublica Nonprofit Explorer API requires no API key - Covers 1.8M+ IRS-recognized tax-exempt organizations - IRS Form 990 data: annual filings for public charities (990), small orgs (990-EZ), and private foundations (990-PF) - Source filing PDF link on every filing record, except where IRS PDF processing lags the extracted data and `pdf_url` is still null - Data sourced from ProPublica Nonprofit Explorer, derived from IRS Form 990 filings; data lags 1–2 years Agent-friendly output: - Provenance on every response — `data_source` attribution on all tool outputs, ProPublica URL for direct verification - Filing-year clarity — `tax_prd_yr` prominently labeled as fiscal year with explicit lag caveat in every financial response - Null values are rendered, not dropped — the formatted text names why a value is absent (never recorded by the IRS, not extracted from the filing, not carried by that form type), and `structuredContent` keeps an explicit `null` rather than omitting the key - Field-name transparency on compensation — `field_name` and `form_type` exposed so agents know exactly which IRS field was read The published [nonprofit-explorer-guide](./skills/nonprofit-explorer-guide/SKILL.md) covers diligence, financial comparisons, and when to inspect the source return. It is included in npm, Docker, and desktop bundles and served through MCP skill resources. ## Getting started ### Public Hosted Instance A public instance is available at `https://nonprofit-explorer.caseyjhand.com/mcp` — no installation required. Point any MCP client at it via Streamable HTTP, with this client config: ```json { "mcpServers": { "nonprofit-explorer-mcp-server": { "type": "streamable-http", "url": "https://nonprofit-explorer.caseyjhand.com/mcp" } } } ``` ### Self-Hosted / Local Add the following to your MCP client configuration file. ```json { "mcpServers": { "nonprofit-explorer-mcp-server": { "type": "stdio", "command": "bunx", "args": ["@cyanheads/nonprofit-explorer-mcp-server@latest"], "env": { "MCP_TRANSPORT_TYPE": "stdio", "MCP_LOG_LEVEL": "info" } } } } ``` Or with npx (no Bun required): ```json { "mcpServers": { "nonprofit-explorer-mcp-server": { "type": "stdio", "command": "npx", "args": ["-y", "@cyanheads/nonprofit-explorer-mcp-server@latest"], "env": { "MCP_TRANSPORT_TYPE": "stdio", "MCP_LOG_LEVEL": "info" } } } } ``` Or with Docker: ```json { "mcpServers": { "nonprofit-explorer-mcp-server": { "type": "stdio", "command": "docker", "args": [ "run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/nonprofit-explorer-mcp-server:latest" ] } } } ``` For Streamable HTTP, set the transport and start the server: ```sh MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http # Server listens at http://localhost:3010/mcp ``` ### Prerequisites - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+). - No API key required — ProPublica Nonprofit Explorer is a keyless public API. ### Installation 1. **Clone the repository:** ```sh git clone https://github.com/cyanheads/nonprofit-explorer-mcp-server.git ``` 2. **Navigate into the directory:** ```sh cd nonprofit-explorer-mcp-server ``` 3. **Install dependencies:** ```sh bun install ``` 4. **Configure environment:** ```sh cp .env.example .env # edit .env as needed (no required vars — server works out of the box) ``` ## Configuration All configuration is validated at startup. Key environment variables: | Variable | Description | Default | |:---------|:------------|:--------| | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` | | `MCP_HTTP_PORT` | HTTP server port | `3010` | | `MCP_HTTP_HOST` | HTTP server hostname | `127.0.0.1` | | `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path | `/mcp` | | `MCP_PUBLIC_URL` | Public origin override for TLS-terminating reverse-proxy deployments | — | | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth` | `none` | | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.) | `info` | | `LOGS_DIR` | Directory for log files (Node.js only) | `/logs` | | `LOG_TOOL_FAILURE_PAYLOADS` | Log each failed tool call's arguments and result, redacted by key name and capped at `LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES` (default `16384`). A secret inside a free-form value is not redacted. | `false` | | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1` | `in-memory` | | `OTEL_ENABLED` | Enable [OpenTelemetry instrumentation](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry) | `false` | No server-specific required variables. ProPublica Nonprofit Explorer is a keyless public API. See [`.env.example`](./.env.example) for the full list of optional overrides. ## Running the server ### Local development - **Build and run:** ```sh # One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http ``` - **Run checks and tests:** ```sh bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec ``` ### Docker ```sh docker build -t nonprofit-explorer-mcp-server . docker run --rm -p 3010:3010 nonprofit-explorer-mcp-server ``` The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/nonprofit-explorer-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them. ## Project structure | Directory | Purpose | |:----------|:--------| | `src/index.ts` | `createApp()` entry point — registers tools and inits the service. | | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Three tools for search and financial data. | | `src/services/nonprofit-explorer` | ProPublica Nonprofit Explorer API client and domain types. | | `skills/` | Published agent guidance for diligence and financial interpretation. | | `tests/` | Unit and integration tests mirroring `src/`. | ## Development guide See [`CLAUDE.md`/`AGENTS.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version: - Handlers throw, framework catches — no `try/catch` in tool logic - Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage - Register new tools via the arrays in `createApp()` in `src/index.ts` - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields ## Contributing Issues are welcome. Run checks and tests before submitting: ```sh bun run devcheck bun run test ``` ## License Apache-2.0 — see [LICENSE](LICENSE) for details.