fi-lookup-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@fi-lookup-mcpReconcile 'Mtn America FCU, Sandy UT'"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
fi-lookup-mcp
A personal portfolio project demonstrating a tool-use, reconciliation, and lineage-tracing pattern over public regulatory data, implemented as a local MCP (Model Context Protocol) server. It speaks stdio, so it works with any MCP host — Claude Code (CLI) and Claude Desktop are both supported.
Built by Nelson Anievas, with development assisted by Claude Code. Public data only — no proprietary or employer systems involved.
What It Does
This server exposes 11 tools that allow an AI agent to resolve, enrich, and track the history of US financial institution records using canonical regulatory identifiers from FDIC, NCUA, and FFIEC public datasets.
The server handles three distinct patterns:
Reconciliation: given a dirty external record (e.g.
"Mtn America FCU, Sandy UT"), return ranked candidate matches with confidence scores and match reasonsLineage tracing: given an RSSD ID, return the full merger, acquisition, rebrand, and consolidation history — predecessors, successors, parent company, and subsidiaries — with real names resolved across 223,750 active and historical institutions
Change feed: return all transformation events (mergers, failures, rebrands, splits) within a configurable lookback window, filterable by institution type, event type, and state — for dataset maintenance and regulatory monitoring
It also ships with FI Explorer, a local web dashboard over the same data — no MCP client required:

The Overview tab. Charts are dependency-free inline SVG; provider and state bars are click-to-filter. See Web dashboard (FI Explorer).
Related MCP server: lenderwiki
Tools
search_institutions
Free-text name search across all FDIC banks and NCUA credit unions. Supports filtering by institution type and state. Returns ranked candidates with fuzzy match scores.
get_institution_profile
Full regulatory profile lookup by any identifier — FDIC cert, NCUA charter number, or RSSD ID. Returns all available metadata including regulator, charter type, ABA routing number, deposit account count, and web address.
reconcile_institution
The centerpiece reconciliation tool. Takes a messy external record (name, optional city/state/identifier) and returns ranked candidate matches, each with a confidence score (0–1) and human-readable match reasons.
Scoring blends:
Name similarity (0.6 weight): token-set ratio + Jaro-Winkler, with abbreviation expansion (FCU → federal credit union, Mtn → mountain, N.A. → national association)
Geographic agreement (0.4 weight): state match (0.6) + city match (0.4)
Exact identifier override: if a cert, charter, or RSSD is provided and matches, confidence is set to 1.0
crosswalk_identifiers
Translates between FDIC cert, NCUA charter number, and RSSD ID. Explains regulatory boundaries (e.g. why a credit union has no FDIC cert).
get_institution_history
Returns the full merger, acquisition, and rebrand lineage for any institution by RSSD ID. Resolves real names for both active and defunct predecessor/successor institutions using a 223,750-record historical name lookup built from FFIEC NIC active and closed attributes files. Includes parent company and subsidiary relationships.
Example output for JPMorgan Chase (RSSD 852218): 52 predecessors including Washington Mutual (FDIC-assisted, 2008), Bank One (merger, 2004), and Bear Stearns entities — all with resolved names and dates.
get_recent_changes
A configurable regulatory change feed built from FFIEC NIC Transformations data. Returns mergers, failures, rebrands, splits, and other structural events within a lookback window. Useful for identifying institutions that have changed status and may need dataset updates.
Each event carries the full metadata of both the predecessor and successor (name, type, regulator, city/state, FDIC cert / NCUA charter, ABA routing, deposit accounts, web address). For every predecessor with a portal on record, the tool also fetches its home/login URL and classifies whether it is still operating independently or has been consumed by the acquirer:
independent_portal_live— still served on its own domainconsumed_by_acquirer— redirects to the acquirer's domainredirects_elsewhere— redirects to a third domain (rebrand/division site)unreachable— portal did not respond (likely retired)
Portal checks run concurrently and are reported in a portal_summary tally. Lookups use a one-time RSSD index (O(1)), so the data-only path is near-instant; portal checks are the only network cost and can be tuned or disabled.
Parameters:
days: lookback window (default 365, max 3650)institution_type:"bank","cu", or"all"event_type:"merger","failure","split","rebrand", or"all"state: optional 2-letter state filtercheck_portals: fetch and classify predecessor portals (defaulttrue; setfalsefor an instant data-only feed)max_portal_checks: cap on portals fetched, most-recent first (default 50)
get_top_institutions
Returns the top N institutions ranked by deposit account count, with individual and cumulative market share percentages. Supports filtering by institution type.
export_institutions
Exports the full institution dataset to a CSV file with configurable filters, sorting, and market share calculations.
list_institutions
General-purpose browse/query tool over the complete FDIC + NCUA dataset, exposing all 39 metadata fields per institution (every other tool returns a trimmed projection). One tool that is searchable, filterable, sortable, and exportable:
Search: case-insensitive substring across any subset of fields (
search_fields, or"all")Filter: institution type; state (input accepts
UTorUtah; output is always the canonical 2-letter code); min/max deposit accounts;has_routing,has_rssd,has_history,has_divisions; and the business/provider signalsbusiness_banking,business_lending,sba_lender,website_business,website_small_business,business_login,service_provider,connection_method,oauth_networkSort: any field, ascending or descending (numeric fields sort numerically)
Page:
limit/offsetwithhas_more/next_offsetfor inline browsing;fieldsprojects a subsetExport: set
export_pathto write all matched rows (not just the page) tocsvorjson; bare filenames default under~/Desktop, written atomically
The 39 fields span: identity (name, city, state, regulator, cert/charter, RSSD, routing, deposits, web address), NIC lineage counts, divisions (division_count, trade_names, trade_name_urls — distinctly-branded banks/divisions under one charter, from regulatory trade-name data; credit-union brands surface as name-only divisions since NCUA publishes no URL for them), business coverage (business_banking, business_lending, sba_lender, website_business, website_small_business, business_login_portal), and inferred provider / open-finance signals (service_provider, likely_connection_method, oauth_networks, connection_basis).
business_banking is the best determination of whether an institution serves business customers, trusting the deterministic lending data over the homepage scrape — a confirmed C&I/MBL/SBA lender reads yes even if its (JS-rendered or bot-walled) site scraped no; business_basis discloses lending-data vs website. It is broader than website_business (it counts lenders), so use website_business for the narrow "advertises a business deposit account" question.
The lending data is authoritative; the website + provider signals are directional: best-effort scrapes (JS-only login widgets read as unknown), and OAuth rails reflect the provider's public FDX/Akoya/PCX capability, not a per-institution guarantee. See Data quality & validation for how these are measured and monitored.
refresh_cache
Rebuilds the local data snapshot from scratch — re-fetches FDIC data from the BankFind API (latest quarter auto-discovered), auto-downloads the newest NCUA quarterly ZIP, and re-reads the local FFIEC ZIPs. Runs the full NIC enrichment pipeline. Reports the data_as_of date for each source.
refresh_if_changed
Cost-effective conditional refresh: fingerprints all sources (FFIEC ZIP content hashes + latest FDIC/NCUA quarter) and rebuilds only when something actually changed, otherwise skips the expensive reprocessing and returns changed: false. This is the tool the monthly scheduler runs — see Scheduled updates.
Data Sources
All data is public regulatory data. No licensed or proprietary sources.
Source | Data | Refresh |
FDIC BankFind API | ~4,269 active banks: name, location, cert, RSSD, web address | API call |
FDIC Financials API | Deposit account counts + business lending (LNCI/LNCOMRE), most recent quarter | API call |
NCUA Quarterly ZIP | ~4,336 active credit unions; deposits (FS220A); web (FS220D); member-business loans (FS220/FS220L) | Auto-download |
SBA 7(a)/504 FOIA | Small-business lenders, joined by FDIC cert / NCUA charter (7a) or name (504) |
|
Institution websites | Advertised business / small-business accounts + separate business login portals |
|
FFIEC NIC Active Attributes | ABA primary routing numbers; joined via RSSD/cert/charter | Manual download |
FFIEC NIC Closed Attributes | Historical institution names for 161,950 defunct entities | Manual download |
FFIEC NIC Transformations | 59,071 merger/acquisition/rebrand/failure events | Manual download |
FFIEC NIC Relationships | Parent/subsidiary/branch ownership structure | Manual download |
Total universe: 8,605 active institutions + 223,750 name-resolved historical records
Data quality & validation
The regulatory fields (identity, lineage, lending, SBA) are deterministic. The website/inferred fields (website_business, business_login_portal, service_provider) are best-effort scrapes that fail in predictable ways — JS-rendered homepages read as "no", bot walls as "unreachable", a corporate/global web_address gets scraped instead of the consumer site. Rather than hand-wave this, the repo ships tooling to measure and monitor scraper accuracy:
Script | Purpose |
| Cross-checks the website signal against the deterministic lending data (no labels needed) — flags contradictions (lends but site says no, login-without-business, unreachable-but-large, …), deposit-ranked. A free correctness check. |
| Scores the scraper against a hand-labeled gold set → precision/recall/F1, split by reachable vs unreachable. Current baseline: |
| Optional headless-Chromium (Playwright) re-render of the flagged set; reports the flip rate = the JS-induced error estimate, and repairs the cache. |
| Ranks likely corporate/global |
| Stress-tests every division URL (and its redirect target) against the quality rules (social/dup-parent/login/redirect/error/unreachable); exits non-zero on any leak. |
| Continuous monitoring: appends one metrics record per run to |
The guiding principle is honesty over coverage: an unreachable site is reported as unknown, never no; business_banking only upgrades recall from deterministic data and never flips a website yes to no; and inferred provider patterns deliberately exclude embedded loan/account-opening widgets (MeridianLink, Blend, MANTL, …) that aren't the bank's banking platform.
Architecture
Claude Code / Claude Desktop (any MCP host)
|
| MCP stdio transport
v
server.py (FastMCP 3.4.2)
|
+-- search_institutions
+-- get_institution_profile
+-- reconcile_institution --> reconciler.py
+-- crosswalk_identifiers
+-- get_institution_history --> nic_names lookup (223,750 records)
+-- get_recent_changes --> CSV_TRANSFORMATIONS.zip
+-- get_top_institutions
+-- export_institutions
+-- list_institutions --> full dataset: search / filter / sort / export
+-- refresh_cache --> full rebuild (FDIC live + NCUA auto-dl + FFIEC)
+-- refresh_if_changed --> conditional rebuild (monthly launchd job)
|
v
data_loader.py + nic_loader.py + sba_loader.py + business_classifier.py
+ division_loader.py + js_loader.py (optional Playwright tier)
|
+-- cache/fdic_institutions.json (NIC-enriched)
+-- cache/ncua_institutions.json (NIC-enriched)
+-- cache/business_coverage.json (website / provider scrape)
+-- cache/division_coverage.json (per-division scrape)
+-- cache/sba_lenders.json (SBA 7(a)/504 index)
+-- cache/accuracy_history.jsonl (metrics_snapshot trend log)
+-- cache/call-report-data-*.zip
+-- cache/CSV_ATTRIBUTES_ACTIVE.zip
+-- cache/CSV_ATTRIBUTES_CLOSED.zip
+-- cache/CSV_TRANSFORMATIONS.zip
+-- cache/CSV_RELATIONSHIPS.zipAlso reading the same snapshot: web_app.py (the FI Explorer web dashboard),
build_release.py (the CSV / SQLite / Parquet release export), and the
data-quality tooling (audit_coverage.py, score_coverage.py, audit_divisions.py,
metrics_snapshot.py — see Data quality & validation).
Key design decisions:
Local cache first: runs fully offline after initial build; warm start skips live API calls
NIC enrichment at save time: predecessor/successor/parent/subsidiary fields are written into the JSON cache so subsequent warm starts load enriched data instantly
Atomic cache writes:
.tmprename pattern prevents corruption on interrupted writesStderr-only logging: never pollutes the MCP stdio JSON channel
Abbreviation-aware normalization: improves recall on dirty external records
Local Data Setup
The cache/ directory is not committed to Git — populate it manually before first run.
Required downloads
File | Source |
| FFIEC NIC Data Download — Active Attributes |
| FFIEC NIC Data Download — Closed Attributes |
| FFIEC NIC Data Download — Transformations |
| FFIEC NIC Data Download — Relationships |
FDIC is fetched live from the FDIC BankFind API (latest quarter auto-discovered) and NCUA quarterly ZIPs are now auto-downloaded — neither needs a manual download. Only the four FFIEC NIC ZIPs above must be placed in cache/ by hand, because FFIEC's bulk download is gated against scripted requests.
Scheduled updates
Each record carries a data_as_of date, and the snapshot keeps itself current with a cost-aware refresh strategy:
FDIC / NCUA — auto-fetch the newest published quarter on every refresh.
FFIEC — refreshed by dropping new ZIPs into
cache/(the bulk download is 403-gated to scripts, so it can't be auto-pulled). A content hash detects the change.refresh_if_changedrebuilds only when a source actually changed; a no-op run does cheap fingerprint checks (~0.3s CPU) and skips the expensive NIC reprocessing.
A monthly launchd job runs scheduled_refresh.py (which calls refresh_if_changed) at 03:00 on the 1st, logging to cache/refresh.log.
launchd agents
Templates live in launchd/ with a __FI_LOOKUP_DIR__ path placeholder. Install by substituting the absolute repo path (run these from the repo root):
# Monthly conditional refresh (runs at 03:00 on the 1st)
sed "s#__FI_LOOKUP_DIR__#$(pwd)#g" launchd/com.fi-lookup.monthly-refresh.plist \
> ~/Library/LaunchAgents/com.fi-lookup.monthly-refresh.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.fi-lookup.monthly-refresh.plist
# FI Explorer dashboard as a service (auto-start on login, relaunch on crash/sleep)
sed "s#__FI_LOOKUP_DIR__#$(pwd)#g" launchd/com.fi-lookup.dashboard.plist \
> ~/Library/LaunchAgents/com.fi-lookup.dashboard.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.fi-lookup.dashboard.plistManage either agent (<label> = com.fi-lookup.monthly-refresh or com.fi-lookup.dashboard):
launchctl kickstart -k gui/$(id -u)/<label> # run / restart now
launchctl bootout gui/$(id -u)/<label> # stop + unloadRecommended refresh cadence: monthly (bump to weekly only if you depend on the merger change-feed being current within days). The guard makes extra runs nearly free, so erring toward more frequent checks costs little.
Setup
Prerequisites
Python 3.11+
An MCP host — Claude Code (CLI) or Claude Desktop
Install
git clone https://github.com/nlsnnvas/fi-lookup-mcp.git
cd fi-lookup-mcp
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtDownload manual data files
Download the five ZIPs listed in the table above and place them in cache/. FFIEC files are available at ffiec.gov/npw/FinancialReport/DataDownload.
Build the data snapshot
python -c "import asyncio; from data_loader import build_snapshot; asyncio.run(build_snapshot())"This fetches FDIC data live, reads all local ZIPs, runs NIC enrichment, and writes the JSON cache. Expect 2–3 minutes on first run.
Run the tests (optional)
pip install -r requirements-dev.txt
python -m pytest -qThe suite (tests/) is hermetic — it covers the deterministic core (reconciliation scoring, state canonicalization, provider classification incl. the MeridianLink false-positive guard, division/trade-name ingestion, the business_banking composite, corporate-URL overrides, and the metrics helpers) and two convention guards (no-stdout, tools-don't-throw-on-empty-snapshot), so it needs no snapshot, network, or data ZIPs. CI runs it on every push. The snapshot-dependent validators (score_coverage.py, audit_*.py, metrics_snapshot.py) run separately against a built snapshot — see Data quality & validation.
Connect to an MCP host
Claude Code (CLI) — register the server with the venv interpreter:
claude mcp add fi-lookup -- "$(pwd)/.venv/bin/python" "$(pwd)/server.py"Verify it loaded with claude mcp list, then the tools are available in any claude session in that scope.
Claude Desktop:
fastmcp install claude-desktop server.py --name "fi-lookup"Then restart Claude Desktop.
Web dashboard (FI Explorer)
A local web UI over the same dataset and tools — no MCP client required. Built with Starlette + uvicorn (both ship with FastMCP, so no extra dependencies).
python web_app.py # serves http://127.0.0.1:8765
python web_app.py --port 9000 # custom portFive tabs:
Overview — headline metrics plus dependency-free inline-SVG charts (composition donut, likely-connection-method donut, business-coverage bars, top service providers, institutions-by-state), and a top-N market-share table (wraps
get_top_institutions). Provider and state bars are click-to-filter into Browse.Browse — searchable / filterable / sortable table over all institutions with every metadata field, plus CSV/JSON export (wraps
list_institutions). Filters includebusiness lending,SBA,website business,website small biz,service provider, andbusiness login(institutions with a separate business sign-in — multiple aggregation entry points)Profile & Lineage — enter an RSSD ID for merger/acquisition lineage: predecessors, successors, parent, subsidiaries (wraps
get_institution_history)Recent Changes — merger/failure/rebrand/split feed with optional portal verification, independent-vs-consumed (wraps
get_recent_changes)Reconcile — paste a messy record for ranked candidate matches with confidence scores (wraps
reconcile_institution)
The active tab and Browse filters encode into the URL, so a specific view (e.g. business login = yes) is shareable by link.
It is read-only and bound to 127.0.0.1 (localhost only) by default.
Sharing it safely (LAN demo)
Opt-in hardening via environment variables — all off by default for local use:
FI_AUTH_USER=demo FI_AUTH_PASS=s3cret \
FI_DISABLE_PORTAL_CHECKS=1 \
FI_RATE_LIMIT_PER_MIN=240 \
python web_app.py --host 0.0.0.0 # reachable at http://<this-machine-ip>:8765FI_AUTH_USER/FI_AUTH_PASS— require HTTP basic auth on all routes (constant-time check);/healthzstays open.FI_RATE_LIMIT_PER_MIN— per-IP request cap (default 240; 0 disables).FI_DISABLE_PORTAL_CHECKS— turn off the outbound portal-verification fan-out (otherwise hard-capped byFI_MAX_PORTAL_CHECKS, default 60) so an exposed instance can't be used to spray third-party requests.
The server prints its security posture on startup and warns if bound to a non-localhost interface with no auth. For internet exposure (not just a trusted LAN), additionally put it behind HTTPS/a reverse proxy.
Sharing publicly for free (occasional)
To let a few people reach it from their own devices without hosting anything, share.sh brings up an auth-protected instance on a separate port and opens a free Cloudflare quick tunnel — a temporary public HTTPS URL, no account or domain required. It prints the URL + credentials to hand out, and tears everything down on Ctrl-C (so the URL dies when you stop sharing). Your normal local instance on :8765 is left untouched.
brew install cloudflared # one-time prerequisite
./share.sh # random password, port 8766
FI_AUTH_USER=team FI_AUTH_PASS=hunter2 ./share.sh # fixed credentialsHardening is forced on for the shared instance (basic auth, rate limit, portal checks off), and the instance stays bound to localhost — only the local cloudflared process bridges it out. Account-less tunnels have no uptime guarantee and your Mac must stay awake while sharing; for an always-on public deployment, host it on a small VPS/PaaS behind HTTPS instead.
Example Interactions
Reconciliation:
"I have a vendor row that says 'Mtn America FCU, Sandy UT' — what is it?"
reconcile_institution scores ~8,605 institutions and returns Mountain America Credit Union (NCUA #24692) at 0.984 confidence, with ABA routing, deposit account count, and charter type.
Lineage tracing:
"What is the full acquisition history of Bank of America?"
get_institution_history returns 117 predecessor institutions going back to 1960, including the 1998 BankAmerica merger, the 2008 Countrywide acquisition, and the 2009 Merrill Lynch absorption — all with resolved names and dates.
Change feed:
"What bank failures and mergers happened in the last 90 days?"
get_recent_changes returns 108 events grouped by type: 1 FDIC-assisted failure, 107 mergers — including Meadows Bank absorbed by AMERICA FIRST Credit Union and two bank-to-credit-union conversions.
Why This Pattern Matters
Financial institution data is notoriously messy. The patterns here are directly applicable to:
Matching vendor/counterparty records to a canonical institution master
Tracing merger lineage for compliance, KYC, or data governance
Building regulatory change feeds for dataset maintenance automation
Enriching internal datasets with public regulatory metadata
Onboarding automation that maps free-text institution names to stable IDs
This project re-expresses reconciliation and lineage patterns from production AI agent work, using only public data.
Stack
Python 3.11
FastMCP 3.4.2
rapidfuzz (fuzzy string matching)
httpx (async HTTP)
Claude Code / Claude Desktop (MCP host)
Framing Note
This is a tool-use, reconciliation, and lineage-tracing pattern — not RAG. The model calls structured tools that execute deterministic scoring and lookup logic against a pre-built regulatory snapshot and return ranked, explainable results. The NIC enrichment pipeline runs at startup and writes enriched data to the JSON cache, so subsequent tool calls are fast in-memory lookups.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/nlsnnvas/fi-lookup-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server