@cyanheads/fdic-banks-mcp-server
Search FDIC-insured banks, Call Report financials, peer rankings, failures, mergers, deposit share via MCP. STDIO or Streamable HTTP.
11 Tools
Overview
Every FDIC-insured bank and savings institution, open, merged, or failed, from the FDIC BankFind Suite API. Read quarterly Call Report financials back to 1984, rank a bank against peers, screen many at once, search failures since 1934, trace a bank's mergers, renames, and charter changes, and map branch deposits and market share. Credit unions are NCUA-insured and not included. No API key required.
Tools
Dataframe workflow
fdic_query_financials and fdic_get_deposits return one page of rows at a time (limit, offset); while rows remain, next_offset names the next page. At offset 0, when the full result is larger than limit, they also stage it as a df_XXXXX_XXXXX table and name it in the dataset field; later pages never stage.
- Pass that name to
fdic_dataframe_describe for the table's columns, units, and basis.
- Query it with
fdic_dataframe_query: joins, aggregates, window functions, and CTEs across every staged table. register_as saves a result as a new table.
- Tables expire after
FDIC_DATASET_TTL_SECONDS (default 24 hours). The live tables hold 1,000,000 rows together, and the oldest are dropped past that.
Staging runs on DuckDB and is on by default; CANVAS_PROVIDER_TYPE=none turns it off. The Claude Desktop .mcpb bundle ships without DuckDB's native binding and sets CANVAS_PROVIDER_TYPE=none, so there the two producers return their rows a page at a time, continued with offset and next_offset, the dataframe tools report that dataframes are off, and every other tool works normally.
Capability reference
fdic_search_institutions tool
- Filters:
name (up to 100 characters; every word must match a current, former, or trade name), certs (first 50 unique), state, city, bank_classes, min_assets / max_assets (thousands of US dollars), holding_company_rssd
status is active, inactive, or any; it defaults to any when name or certs is given and active otherwise, and status_filter reports the value applied
- Up to 100 per page (default 20), paged with
offset / next_offset. Records carry active, successor_cert for merged or failed banks, holding_company.rssd, and matched_on; CERTs with no record land in missing_certs
fdic_get_institution_financials tool
cert required; up to 25 metrics (default: a 15-metric health set); quarters accepts 1–10,000 and queries at most 200 (default 8), bounded by from_date / to_date as 2025-03-31, 20250331, or 2025Q1
- Rows run most recent first, each a
values map of metric → number, null when not reported; metric_definitions gives each metric's FDIC field, unit, and basis
quarters_available counts the quarters in the window; an unknown CERT fails as cert_not_found
fdic_get_institution_history tool
cert required; event_types (omitted = every type but branch) selects from merger, failure_or_assistance, branch_purchase, name_change, relocation, charter_change, opening, closing, other, and branch (branch-level openings, closings, sales, and relocations); from_date / to_date (YYYY-MM-DD, inclusive) bound the effective date
- Events run newest first. Each carries
effective_date, event_type, change_code, change_description, and role (subject, outgoing, survivor, buyer, seller), plus counterparty (CERT and name of the other institution), changes (former and new values of name, address, city, state, class, chartering agency, regulator), branch (the office, on branch events), and transaction_number (shared by the rows of one deal) where FDIC recorded them. A merger or failure appears once, whichever side the CERT was on
limit (default 50, no maximum) and offset page the list; total, next_offset, and event_types_filter report the match; data_as_of is the history index build time. Mergers start in 1969 and failures in 1970; an unknown CERT fails as cert_not_found
fdic_compare_peers tool
cert required; report_date defaults to the latest published quarter (report_date_defaulted); up to 20 metrics
- Peer group:
peer_asset_band (same by default, any, under_100m, 100m_1b, 1b_10b, 10b_250b, over_250b) plus an optional peer_state, or peer_certs (first 200 unique); peer_certs can't be combined with the band or state (conflicting_peer_filters)
- Each comparison returns
value, peer_median, peer_p25 / peer_p75, peer_min / peer_max, percentile, and rank of rank_of, over the peer_count_with_value peers that reported the metric; peer_cohort_incomplete marks a peer group only partly processed
fdic_query_financials tool
- Filters: up to 100
certs, state, min_assets / max_assets, up to 5 metric_filters (metric with min and/or max), and up to 30 metrics; from_date / to_date default to the latest published quarter
limit (default 50) rows per page, ordered by sort_by / sort_order, continued with offset / next_offset; at offset 0 a larger panel is also staged whole as a dataframe
- The panel is capped at
FDIC_PANEL_MAX_ROWS (default 50,000), keeping the newest quarters; total_matching, rows_fetched, and panel_truncated report what was left out
fdic_search_failures tool
- Filters:
name, certs (first 50 unique; most events before 1977 carry none), state, from_date / to_date (YYYY-MM-DD), methods, min_assets; resolution is failure by default, or assistance or all
summary totals every matching event with a by_method breakdown; group_by (year, state, method, insurance_fund) adds groups
total_assets_missing_count, total_deposits_missing_count, and estimated_loss_missing_count count unreported amounts, and a total is null when no match has a recorded amount. sort by date, loss, or assets; up to 200 per page (default 25)
fdic_get_deposits tool
cert alone returns the institution's branches and deposit share in each state (mode: "institution"); a geography (state, county, city, zip, msa_code) ranks every institution in that market with hhi (market); both together give institution_in_market. county and city require state
year from 1994 on, defaulting to the latest survey (year_defaulted); deposits are domestic branch deposits as of June 30, in thousands of US dollars
limit (default 25) rows per page, continued with offset / next_offset; at offset 0 a larger set is also staged as a dataframe, and total_rows gives the full count
fdic_list_reference tool
- One
topic per call: metrics, bank_classes, failure_methods, change_codes, insurance_funds, peer_asset_bands, or coverage
metrics lists all 49 metric names with FDIC field, unit, basis, caveat, and in_default_set; change_codes lists the 41 FDIC structure-change codes with their event_type and level (institution, branch, or both); served from built-in tables with no request to FDIC
fdic_dataframe_describe tool
name (a df_XXXXX_XXXXX from a dataset field) returns that table's source tool, parameters, row count, expiry, column_schema, and column_units
- Without
name, lists live tables newest first, 50 per page, paged with offset / next_offset; over HTTP with MCP_AUTH_MODE=none listing is off and fails as listing_unavailable
- Fails as
canvas_unavailable when dataframe staging is off
fdic_dataframe_query tool
- One
SELECT per call, up to 20,000 characters; writes, DDL, file-reading functions, and system catalogs are rejected with typed reasons (non_select_statement, denied_function, system_catalog_access, and others)
row_limit accepts 1–1,000,000 (default 1,000) and executes at most 10,000; row_count_capped reports when more rows matched. preview sets the inline row count
register_as saves the result as a new dataframe, or fails as register_as_too_large past 1,000,000 rows; BIGINT results such as COUNT(*) come back as strings
fdic_dataframe_drop tool
name of a staged dataframe; returns dropped: true, or dropped: false when nothing by that name was staged
- Disabled unless
FDIC_DATAFRAME_DROP_ENABLED=true; otherwise tables expire on their TTL
Features
Built on @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.
FDIC-specific:
- Reads five BankFind datasets: institutions (every insured charter, active and inactive), quarterly Call Report financials from 1984, failures and assistance transactions from 1934, structure-change history (mergers from 1969, failures from 1970), and the annual Summary of Deposits from 1994
- Paced requests (
FDIC_RATE_LIMIT_RPS, at most four in flight) with a response cache and a cooldown after an FDIC 429, retried honoring Retry-After; each call has a 45-second budget (55 seconds for a panel, or a history read that includes branch)
- A curated catalog of 49 Call Report metrics with FDIC field code, unit, and basis;
_ytd metrics accumulate from January 1, and unsuffixed income and return metrics cover a single quarter
- Handles BankFind's quirks: case-sensitive code values, string-typed report dates, absence sentinels, and ratios reported as
0 when a bank did not report them
- Array inputs are deduplicated in order and capped at 1,000 items; empty
certs, peer_certs, bank_classes, methods, and event_types are rejected
Agent-friendly output:
- Every data response carries
data_as_of, metric_definitions, and report_date_defaulted / year_defaulted when the server picked the period
- Unreported metrics come back
null; nothing is filled in
- Typed errors with recovery text that names the next call (
cert_not_found, conflicting_peer_filters, no_report_for_period); rate-limit errors (pacer_shed, upstream_rate_limited) carry retryAfter
processed_certs / deferred_certs and processed_metrics / deferred_metrics separate what was queried from what to pass in a new call
- Publishes the
fdic-banks-guide agent skill (root skills/: identifier chains, gates, multi-step workflows), served to MCP clients as skill://fdic-banks-guide/SKILL.md and shipped in the npm package, Docker image, and .mcpb bundle
Getting started
Connect to the hosted instance, no install:
Every caller of the hosted instance shares one FDIC request pacer and one dataframe workspace, so fdic_dataframe_describe takes a table's exact name there; listing is off.
Or run it locally:
For other clients, add this to the MCP config file:
No API key or account is needed: the FDIC BankFind Suite API is public. For Docker or a source build, see Running the server.
Configuration
See .env.example for the full list of optional overrides.
Running the server
Local development
Requires Bun v1.4.0 or higher (or Node.js v24+).
Docker
The image defaults to HTTP transport on port 3010, stateless session mode, and logs to /var/log/fdic-banks-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/fdic-banks-mcp-server:latest"].
Project structure
Development guide
See 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 logging, ctx.state for storage
- Register new tools in
buildToolDefinitions() in src/mcp-server/tools/definitions/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:
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.