The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Sanctions Screening MCP Server listing page.
Screen names against the consolidated OFAC, EU, UK, and UN sanctions lists and resolve legal entities against GLEIF, fuzzy-matched offline over a local SQLite + FTS5 mirror. A screening aid, not a compliance determination.
Public Hosted Server: https://sanctions-screening.caseyjhand.com/mcp
[!IMPORTANT] This is a screening aid, not legal or compliance certification. Every tool returns potential matches with a transparent score and source provenance — never a verdict. A hit means "review this candidate against the official source"; an empty result never means "cleared." Real sanctions compliance is a legal process — it requires human review and a qualified compliance determination. This server feeds that process; it does not perform it, and its output is not a compliance record.
sanctions-screening-mcp-server turns the world's open sanctions data plus the global legal-entity registry into one screening-and-resolution workflow, answered offline and fuzzy-matched. It screens a name against the consolidated US (OFAC), EU, UK, and UN sanctions lists at once, and resolves legal entities against the GLEIF Legal Entity Identifier (LEI) database with corporate-ownership tracing.
All sources are bulk-downloadable, keyless, and clear for redistribution. The server mirrors them to a local SQLite + FTS5 index and serves matches from that mirror — no live API key, no per-request rate limit on the hot path. The agent sees screening verbs (screen_name, resolve_entity, trace_ownership); which list answered a query surfaces only as provenance on each hit.
The matching model is transparent by design: strict token matching first (exact-normalized, then all-tokens-present via FTS5), with a scored Jaro-Winkler + phonetic fuzzy fallback. Approximate hits carry the raw Jaro-Winkler similarity (0–1) — a real measurement, never a fabricated "confidence percentage."
Six tools organized around two workflows — screen a name against the watchlists, and resolve a legal entity to its global identifier and ownership graph:
| Tool | Description |
|---|---|
sanctions_screen_name | Screen a name (person, company, vessel, aircraft) against all loaded watchlists at once — OFAC SDN + Consolidated, EU, UK, UN — alias- and fuzzy-aware. Returns scored potential matches with source list, program, designation date, and the matched alias. |
sanctions_get_designation | Fetch the full record for one sanctions designation by source list + entry ID: all aliases, identifiers, addresses, dates/places of birth, nationalities, program, legal basis, and designation date. |
sanctions_resolve_entity | Resolve a company / organization name (+ optional jurisdiction) to ranked candidate GLEIF LEIs. Turns a free-text counterparty name into a stable global identifier. |
sanctions_get_entity | Fetch the full GLEIF Level 1 record for one LEI — legal name, trading names, addresses, registration status, jurisdiction — plus a sanctions cross-reference screened on the legal name. |
sanctions_trace_ownership | Trace the GLEIF Level 2 corporate-ownership graph for an LEI (parents and/or children, BFS to a bounded depth), optionally screening every node for beneficial-ownership screening. |
sanctions_list_sources | List the loaded watchlists and GLEIF datasets with record counts, source URLs, licenses, and the mirror's readiness and as-of timestamps. |
sanctions_screen_nameThe 80% entry point — "is this entity on a watchlist?"
exact / strong / approximate; approximate hits carry the raw Jaro-Winkler score (0–1) plus queryTokenCoverage — how many query tokens the candidate explains, which ranks candidates that one shared exact token pins at the same scoreminScore), and result limittotalAvailable and hasMore report matches beyond the returned page and nextOffset retrieves them, with totalAvailableBasis marking that count exact (strict) or a scanned-set floor (fuzzy)sanctions_get_designationThe drill-in after sanctions_screen_name surfaces a candidate.
source + entryId (the sourceEntryId from a screen hit)sanctions_resolve_entityThe bridge from a free-text counterparty name to a stable LEI that the entity tools key off.
issued default, lapsed, or any)queryTokenCoverage countsanctions_screen_name — totalAvailable, totalAvailableBasis, hasMore, nextOffsetsanctions_get_entityWho is this legal entity — plus a watchlist cross-reference in the same call.
screeningStatus says whether that cross-reference actually ran: an empty hit list under not_ready means the sanctions mirror was unavailable, not that nothing matchedsanctionsScreen — totalAvailable, totalAvailableBasis, hasMore — since the hit list is capped at twenty-five; re-screen the legal name with sanctions_screen_name for the full setsanctions_trace_ownershipBeneficial-ownership screening — the cross-source workflow that single-list tools can't do.
direction: walk parents (who owns it), children (what it owns), or bothscreenNodes: true screens every entity in the graph against all watchlists — "is anyone in this ownership chain sanctioned?"screenedNodeCount / flaggedNodeCount so a caller can see coverage at a glancecomplete, truncated (further relationships exist past the requested depth), and missingEntityLeis (nodes with no GLEIF Level 1 record, which carry their LEI where a legal name would be)screeningStatus separates a completed node screen from one never requested and one the sanctions mirror could not run; each screened node carries sanctionsScreen — totalAvailable, totalAvailableBasis, hasMore — since its hit list is capped at ten| Type | Name | Description |
|---|---|---|
| Resource | sanctions://designation/{source}/{entryId} | One sanctions designation by source + entry ID (URI mirror of sanctions_get_designation). |
| Resource | sanctions://entity/{lei} | One GLEIF Level 1 entity by LEI (URI mirror of sanctions_get_entity's entity payload, without the screening cross-reference). |
| Resource | sanctions://sources | Loaded lists + GLEIF datasets with counts and refresh timestamps (URI mirror of sanctions_list_sources). |
| Prompt | sanctions_vet_counterparty | Sequences the tools into a full counterparty due-diligence pass: resolve → trace ownership → screen the entity and every beneficial owner → summarize with provenance and the decision-support caveat. |
All resource data is also reachable via the tools, which are the primary path for tool-only MCP clients. The resources are a convenience for resource-capable clients only.
The server aggregates five upstream sources behind the screening surface. All are bulk, keyless, and clear for redistribution.
| Source | Role | License |
|---|---|---|
| OFAC SDN + Consolidated (US Treasury) | Primary US sanctions/watchlist — individuals, entities, vessels, aircraft, with a.k.a. aliases | US Government public domain |
| EU Consolidated Financial Sanctions List | EU-designated persons and entities | Freely redistributable |
| UK Sanctions List (UKSL, FCDO) | UK sanctions targets — persons, entities, ships | Open Government Licence v3.0 |
| UN Security Council Consolidated List | UN-designated individuals and entities across all regimes | Freely redistributable |
| GLEIF LEI (Level 1 + Level 2) | Who-is-who (entity reference) and who-owns-whom (corporate ownership) | CC0 1.0 Universal |
The UK source is the UK Sanctions List (UKSL), the single authoritative UK source since the OFSI Consolidated List closed on 28 January 2026.
The mirror is not bundled — the sanctions lists and the GLEIF golden copy are downloaded and normalized on first run. Run the init lifecycle script out-of-band before screening:
This streams all five sanctions lists in full, rebuilds the per-alias name index, then streams the GLEIF golden copy (Level 1 entities + Level 2 ownership relationships). It is resumable and intended to run once, off the request path.
| Script | Purpose |
|---|---|
bun run mirror:init | Full initial load of all sources (sanctions lists + GLEIF golden copy). |
bun run mirror:refresh | Re-harvest the sanctions lists and apply GLEIF deltas. The sanctions half (lists + name index) also runs on a cron under HTTP transport; GLEIF deltas are manual. |
bun run mirror:verify | Report mirror readiness and per-source record counts. |
bun run mirror:seed | Load a small synthetic fixture for local smoke tests (no downloads). |
Set SANCTIONS_INIT_SKIP_GLEIF=1 on mirror:init to load the sanctions lists only and skip GLEIF.
Memory note: every leg of
mirror:initstreams. The sanctions documents total roughly 172 MB, of which OFACSDN_ADVANCED.XMLis about 120 MB on its own; the GLEIF Level 1 golden copy is roughly 3.3M LEI records (~892 MB compressed, several GB decompressed). Each source is scanned one record at a time and ingested in bounded batches, so peak resident memory tracks the batch size rather than the size of any source document. Size disk for the mirror accordingly — GLEIF dominates there — or skip GLEIF withSANCTIONS_INIT_SKIP_GLEIF=1if you only need watchlist screening.
Built on @cyanheads/mcp-ts-core:
mirror_not_ready, designation_not_found, lei_not_found)none, jwt, oauth (defaults to none — all data is public)Sanctions-specific:
MirrorService — offline, no live API key, no per-request rate limitAgent-friendly output:
matchType (exact / strong), never a fabricated percentageprimary / aka / fka / low-quality-aka)sanctions_list_sources — each source's record count and the mirror's as-of timestamp, so an agent can judge stalenessA public instance is available at https://sanctions-screening.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP, with this client config:
Add the following to your MCP client configuration file. The server is offline-first — populate the mirror with bun run mirror:init before screening (see Source lists).
Or with npx (no Bun required):
For Streamable HTTP, set the transport and start the server:
All sources are keyless — there is no required API key. Every variable below is optional with a sensible default.
| Variable | Description | Default |
|---|---|---|
SANCTIONS_MIRROR_PATH | Filesystem path for the SQLite mirror; a persistent volume on a hosted deployment. | ./data/sanctions.db |
SANCTIONS_REFRESH_CRON | Cron for the scheduled refresh of the sanctions lists + name index (HTTP transport only). GLEIF deltas are refreshed manually via mirror:refresh. | 0 4 * * * |
SANCTIONS_FUZZY_MIN_SCORE | Default Jaro-Winkler similarity floor for fuzzy matches when minScore is omitted. | 0.85 |
SANCTIONS_FUZZY_MAX_RESULTS | Hard cap on fuzzy candidates scored per query, to bound work on short queries. | 50 |
OFAC_SDN_URL | Override for the OFAC SDN advanced-XML file. | official SLS URL |
OFAC_CONSOLIDATED_URL | Override for the OFAC Consolidated advanced-XML file. | official SLS URL |
EU_FSF_URL | Override for the EU consolidated XML file (includes the static public token path component). | official EU URL |
UK_SANCTIONS_URL | Override for the UK Sanctions List (UKSL) XML file. | official FCDO URL |
UN_SC_URL | Override for the UN Security Council consolidated XML file. | official UN URL |
GLEIF_GOLDEN_COPY_BASE_URL | Override for the GLEIF golden-copy / delta download API. | https://goldencopy.gleif.org |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_SESSION_MODE | Session mode: auto (resolves to stateful), stateful, or stateless. The shipped .env.example and Docker image pin stateless — no tool here needs a multi-round-trip input. | stateless |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
Source URLs default to the verified official endpoints; overrides exist for testing and for pinning a mirror in restricted environments. The EU "token" is a static public path component, not a credential.
See .env.example for the full list of optional overrides.
Build and run:
Run checks and tests:
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/sanctions-screening-mcp-server. The image runs under Bun, so the mirror uses bun:sqlite (no native build). Mount a volume at the mirror path (/usr/src/app/data by default) so the populated mirror survives container restarts, and run bun run mirror:init inside the container (docker exec) to populate it. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources/prompts, inits the screening service, schedules the HTTP refresh. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts) — the six screening/resolution tools. |
src/mcp-server/resources | Resource definitions (*.resource.ts) — the three URI mirrors. |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts) — the counterparty vetting prompt. |
src/services/screening | The screening service — local mirror, normalized schema, source ingesters (OFAC/EU/UK/UN/GLEIF), and the strict/fuzzy matching engine. |
scripts/mirror-*.ts | Mirror lifecycle CLI — init, refresh, verify, seed. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.tsThis server redistributes open data from the following sources, cited here per their terms:
Issues and pull requests are welcome. Run checks and tests before submitting:
Apache-2.0 — see LICENSE for details.