Screen names against OFAC, EU, UK, UN sanctions lists; resolve entities via GLEIF. Screening aid.
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
Entity screening and resolution over the consolidated OFAC, EU, UK, and UN sanctions lists plus the GLEIF legal-entity registry, matched offline against a local mirror. Screen a name for potential watchlist hits, resolve a company to its LEI, and trace its beneficial-ownership chain from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| 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, with source provenance on every hit. |
sanctions_get_designation | Fetch the full record for one sanctions designation by source list + entry ID — aliases, identifiers, addresses, dates/places of birth, nationalities, program, and legal basis. |
sanctions_resolve_entity | Resolve a company / organization name (+ optional jurisdiction) to ranked candidate GLEIF LEIs. |
sanctions_get_entity | Fetch the full GLEIF Level 1 record for one LEI, 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), optionally screening every node for beneficial ownership. |
sanctions_list_sources | List the loaded watchlists and GLEIF datasets with record counts, source URLs, licenses, and mirror readiness/freshness. |
| Resource | Description |
|---|---|
sanctions://designation/{source}/{entryId} | One sanctions designation by source + entry ID (URI mirror of sanctions_get_designation). |
sanctions://entity/{lei} | One GLEIF Level 1 entity by LEI (URI mirror of sanctions_get_entity's entity payload, without the screening cross-reference). |
sanctions://sources | Loaded lists + GLEIF datasets with counts and refresh timestamps (URI mirror of sanctions_list_sources). |
All resource data is also reachable via the tools, which are the primary path for tool-only MCP clients.
| Prompt | Description |
|---|---|
sanctions_vet_counterparty | Sequence 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. |
sanctions_screen_name toolsources, entityType, and minScorematchMode: "strict" (default) is exact-normalized then all-tokens-present via FTS5; "fuzzy" adds Jaro-Winkler + Double-Metaphone and auto-triggers when strict finds nothingmatchType (exact / strong / approximate); approximate hits add the raw Jaro-Winkler score (0–1) and queryTokenCoverage for tie-breakinglimit), totalAvailable / hasMore / nextOffset, with totalAvailableBasis marking the count exact or a scanned lower boundcaveat — a hit is a candidate to verify, never a clearancesanctions_get_designation toolsource + entryId (the sourceEntryId from a sanctions_screen_name hit)designation_not_found, mirror_not_readysanctions_resolve_entity tooljurisdiction) to ranked GLEIF LEI candidatesstatus filter: issued (default), lapsed, or any; matches against legal and other/trading namessanctions_screen_name, with the same matchType, score, and queryTokenCoverage fieldslimit), same totalAvailable / totalAvailableBasis / hasMore / nextOffset contractsanctions_get_entity toolscreeningStatus (screened / not_ready) says whether the cross-reference ran at all — an empty hit list under not_ready is not a clean screensanctionsScreen.hasMore flags a capped cross-reference; re-screen the legal name with sanctions_screen_name for the full setlei_not_found, mirror_not_readysanctions_trace_ownership tooldirection (parents / children / both, default both), depth 1–5 (default 3)role and depth) and directed edges with relationshipTypescreenNodes: true screens every node's legal name against all watchlists, strict-only, capped at 10 hits per nodecomplete is true only when nothing was truncated by depth AND every node resolved to a GLEIF Level 1 record; truncated and missingEntityLeis say which is falsescreeningStatus (screened / not_requested / not_ready) plus screenedNodeCount / flaggedNodeCount report per-node screening coveragelei_not_found, mirror_not_readysanctions_list_sources toolsanctionsReady / sanctionsAsOf and leiReady / leiAsOf report mirror readiness and last-sync timestamp for freshness checkssanctions://designation/{source}/{entryId} resourcesanctions_get_designation's payloadsource is one of ofac_sdn / ofac_consolidated / eu / uk / un; entryId is the source list's own entry IDdesignation_not_found, mirror_not_readysanctions://entity/{lei} resourcesanctions_get_entity's GLEIF Level 1 payload, without the screening cross-reference (tool-only)lei is regex-validated: 20 chars, 18 alphanumerics + 2 check digitslei_not_found, mirror_not_readysanctions://sources resourcesanctions_list_sources — loaded lists + GLEIF dataset with counts, URL, license, and readiness timestampsttlMs: 0 — never cached, since mirror readiness and the as-of timestamps are the payload itselfsanctions_vet_counterparty promptname required; jurisdiction optional (ISO 3166-1 alpha-2) to disambiguatescreenNodes: true, pull the full designation record for any hit, then summarize with provenance and the decision-support caveatThe 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:
bun run mirror:init
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: 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.
Sanctions-screening-specific:
MirrorService — offline, keyless, no per-request rate limitAgent-friendly output:
primary / aka / fka / low-quality-aka)sanctions_list_sources — each source's record count and the mirror's as-of timestampA 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:
{
"mcpServers": {
"sanctions-screening-mcp-server": {
"type": "streamable-http",
"url": "https://sanctions-screening.caseyjhand.com/mcp"
}
}
}
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).
{
"mcpServers": {
"sanctions-screening-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/sanctions-screening-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"sanctions-screening-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/sanctions-screening-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/sanctions-screening-mcp-server.git
cd sanctions-screening-mcp-server
bun install
cp .env.example .env
# edit .env if you need to override defaults (all optional)
bun run mirror:init
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: stateful, stateless, or auto (the schema default, which resolves to stateful). createApp() declares stateless in src/index.ts, and the shipped .env.example and Docker image set it too — no tool here needs a multi-round-trip input. Setting the variable overrides the declaration. | 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:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security, changelog sync
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t sanctions-screening-mcp-server .
docker run --rm -p 3010:3010 -v sanctions-data:/usr/src/app/data sanctions-screening-mcp-server
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.ts[!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.
This server redistributes open data from the following sources, cited here per their terms:
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/sanctions-screening-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-sanctions-screening-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/sanctions-screening-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/sanctions-screening-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.