sanctions-screening-mcp-server

v0.6.0 pre-1.0

Screen eleven OFAC, EU, UK, UN, BIS, and State sanctions and export-control lists and resolve GLEIF entities offline. A screening aid, not a compliance determination. This product uses the International Trade Administration’s Data API but is not endorsed or certified by the International Trade Administration.

sanctions-screening.caseyjhand.com/mcp
claude mcp add --transport http sanctions-screening-mcp-server https://sanctions-screening.caseyjhand.com/mcp
codex mcp add sanctions-screening-mcp-server --url https://sanctions-screening.caseyjhand.com/mcp
{
  "mcpServers": {
    "sanctions-screening-mcp-server": {
      "url": "https://sanctions-screening.caseyjhand.com/mcp"
    }
  }
}
gemini mcp add --transport http sanctions-screening-mcp-server https://sanctions-screening.caseyjhand.com/mcp
{
  "mcpServers": {
    "sanctions-screening-mcp-server": {
      "command": "bunx",
      "args": [
        "mcp-remote",
        "https://sanctions-screening.caseyjhand.com/mcp"
      ]
    }
  }
}
{
  "mcpServers": {
    "sanctions-screening-mcp-server": {
      "type": "http",
      "url": "https://sanctions-screening.caseyjhand.com/mcp"
    }
  }
}
curl -X POST https://sanctions-screening.caseyjhand.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'

Tools

7

sanctions_screen_name

Screen a name (person, company, vessel, aircraft) against loaded sanctions and export-control watchlists — OFAC SDN + Consolidated, EU, UK, UN, and the six BIS/State CSL lists — alias- and fuzzy-aware. Check sanctions_list_sources for coverage before interpreting an empty result; zero-record CSL lists are not loaded. Returns scored potential matches with the source list, sanctioning program, designation date, and the matched alias; an OFAC party both OFAC lists publish under one entry ID is one hit, its sources naming both lists. Strict mode (default) matches exact-normalized then all-tokens-present, then runs a fuzzy pass over each selected list strict finds nothing on: a full pass when strict finds nothing on any list, otherwise one that adds only candidates covering every word of the name other than legal forms, articles and other function words, and the jurisdiction codes uk, usa, uae, and rf, ranked after the strict hits. Fuzzy mode runs the fuzzy pass over every selected list. It adds Jaro-Winkler and phonetic matching and labels hits approximate with a raw 0–1 similarity score plus the count of query tokens the candidate covers, which orders candidates that tie on score; fuzzySources names the lists it searched. Results are paged: totalAvailable and hasMore report matches beyond the returned page, and nextOffset retrieves them. This is a screening AID for a human/compliance review, NOT a compliance determination: a hit means "review this candidate against the official source," and an empty result never means "cleared."

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sanctions_screen_name",
    "arguments": {
      "name": "<name>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "description": "The name to screen (person, organization, vessel, or aircraft), in any script. It must contain at least one letter or digit, and at most 64 words and 1024 characters."
    },
    "entityType": {
      "default": "any",
      "description": "Restrict to one entity class, or \"any\" (default) to screen across all.",
      "type": "string",
      "enum": [
        "any",
        "person",
        "organization",
        "vessel",
        "aircraft"
      ]
    },
    "matchMode": {
      "default": "strict",
      "description": "strict (default): exact-normalized then all-tokens-present, then a fuzzy pass over each selected list strict finds nothing on — a full pass when strict finds nothing on any list, otherwise adding only candidates that cover every word of the name other than legal forms, articles and other function words, and the jurisdiction codes uk, usa, uae, and rf. fuzzy: a scored Jaro-Winkler + phonetic pass over every selected list.",
      "type": "string",
      "enum": [
        "strict",
        "fuzzy"
      ]
    },
    "minScore": {
      "description": "Score floor for approximate hits (0–1), applied uniformly to every fuzzy candidate regardless of how it was matched (Jaro-Winkler, token, or phonetic); a query token counts toward queryTokenCoverage only when its match clears it too. It governs every fuzzy pass: fuzzy mode, and in strict mode the pass over the lists strict found nothing on, so raising it can remove approximate hits from a strict screen as well. Exact and strong hits are unaffected. Defaults to the server's configured floor.",
      "type": "number",
      "minimum": 0,
      "maximum": 1
    },
    "sources": {
      "description": "Restrict to specific source lists. Omit to screen all loaded lists.",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "ofac_sdn",
          "ofac_consolidated",
          "eu",
          "uk",
          "un",
          "bis_el",
          "bis_dpl",
          "bis_uvl",
          "bis_meu",
          "state_dtc",
          "state_acn"
        ]
      }
    },
    "limit": {
      "default": 25,
      "description": "Maximum number of potential matches to return in one page.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "offset": {
      "default": 0,
      "description": "Zero-based index of the first potential match to return. Re-call with the returned nextOffset to page through every match when hasMore is true; an offset past the end returns an empty page, not an error.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "name",
    "entityType",
    "matchMode",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
view source ↗

sanctions_screen_identifier

Look up an identifier — a vessel IMO number, a SWIFT/BIC code, a digital-currency wallet address, a passport or national ID number, or any other identifier a list publishes — against all loaded sanctions watchlists at once: OFAC SDN + Consolidated, EU, UK, and UN. Exact match after normalization, with no fuzzy or partial matching and no score: spacing, letter case, and the separators - . / are ignored, an IMO number matches with or without its IMO prefix, a SWIFT/BIC code compares on its first eight characters so a branch code matches its institution, and a wallet address folds case only where its encoding is case-insensitive (hex, bech32, cashaddr — never base58). Returns every designation that publishes a matching identifier, one per designation — an OFAC party both OFAC lists publish under one entry ID is one hit, its sources naming both lists — with the identifiers that matched as published; sanctions_get_designation pulls the full record. This is a screening AID for a human/compliance review, NOT a compliance determination: a hit means "review this candidate against the official source," and an empty result never means "cleared" — an identifier a list prints only in free-text remarks, or bundled with other numbers in one field, does not match.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sanctions_screen_identifier",
    "arguments": {
      "value": "<value>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "value": {
      "type": "string",
      "minLength": 1,
      "description": "The identifier to look up, as you hold it (e.g. \"IMO 7406784\", \"DCBKKPPY\", a wallet address, a passport number). Must contain at least one character other than whitespace and - . /"
    },
    "type": {
      "default": "any",
      "description": "Restrict to one identifier category, matched on the label each list publishes, or \"any\" (default) to search every published identifier, including categories with no name here (MMSI, call signs, tail numbers, tax and registration numbers, email, websites).",
      "type": "string",
      "enum": [
        "any",
        "imo",
        "swift_bic",
        "digital_currency_address",
        "passport",
        "national_id"
      ]
    },
    "sources": {
      "description": "Restrict to specific source lists. Omit to search all loaded lists.",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "ofac_sdn",
          "ofac_consolidated",
          "eu",
          "uk",
          "un",
          "bis_el",
          "bis_dpl",
          "bis_uvl",
          "bis_meu",
          "state_dtc",
          "state_acn"
        ]
      }
    }
  },
  "required": [
    "value",
    "type"
  ],
  "additionalProperties": false
}
view source ↗

sanctions_get_designation

Fetch the full record for one sanctions designation by source list + entry ID or the list's published reference number — the drill-in after sanctions_screen_name or sanctions_screen_identifier surfaces a candidate, or the lookup for a reference a notice cites (UN QDe.004, EU EU.27.28, UK OFSI Group ID). Returns all published aliases, identifiers (passport, national ID, tax and registration numbers, SWIFT/BIC codes, digital-currency addresses, vessel call signs, aircraft tail and serial numbers, phone numbers, email addresses, websites), addresses, dates and places of birth at the precision the source published, nationalities, descriptive features (a vessel's flag, type, and tonnage; an aircraft's model and operator; a title or gender; sanctions notes), sanctioning program, legal basis, and designation date. The record reflects exactly what the source published; missing fields mean the source omitted them. This is a screening aid — the designation record supports a compliance review, it is not itself a determination.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sanctions_get_designation",
    "arguments": {
      "source": "<source>",
      "entryId": "<entryId>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "source": {
      "type": "string",
      "enum": [
        "ofac_sdn",
        "ofac_consolidated",
        "eu",
        "uk",
        "un",
        "bis_el",
        "bis_dpl",
        "bis_uvl",
        "bis_meu",
        "state_dtc",
        "state_acn"
      ],
      "description": "Which source list the entry belongs to."
    },
    "entryId": {
      "type": "string",
      "minLength": 1,
      "description": "The source list's own entry ID (the sourceEntryId from sanctions_screen_name), or the reference number the list publishes for the entry (UN QDe.004, EU EU.27.28, UK OFSI Group ID 14196). Matched trimmed and case-insensitive, entry ID first."
    }
  },
  "required": [
    "source",
    "entryId"
  ],
  "additionalProperties": false
}
view source ↗

sanctions_list_sources

List supported watchlists (OFAC SDN + Consolidated, EU, UK, UN, BIS Entity/Denied Persons/Unverified/Military End User, State ITAR Debarred/Nonproliferation) and GLEIF datasets with current mirrored counts, source URLs, licenses, readiness, and as-of timestamps. coverageNotice names unloaded CSL lists and how to refresh them; prior mirror readiness does not establish coverage of newly added lists. For GLEIF, reports Level 2 ownership relationships, reporting-exception coverage, and alternate-name index readiness. Use this for provenance, freshness, and coverage on any result. Attribution: UK data is under the Open Government Licence v3.0; BIS and State CSL data is supplied by the International Trade Administration under its published terms. This product uses the International Trade Administration’s Data API but is not endorsed or certified by the International Trade Administration.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sanctions_list_sources",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {},
  "additionalProperties": false
}
view source ↗

sanctions_resolve_entity

Resolve a company or organization name (with an optional jurisdiction: a country code, which includes its subdivisions, or an ISO 3166-2 subdivision code) to candidate GLEIF Legal Entity Identifiers (LEIs), ranked. This turns a free-text counterparty name into a stable global identifier that sanctions_get_entity and sanctions_trace_ownership key off. Every name GLEIF publishes takes part: the legal name, previous legal names, trading names, alternative-language legal names, and ASCII transliterations of a legal name in another script — each candidate reports the name it matched on and that name's type, one candidate per LEI. Strict mode (default) matches exact-normalized then all-tokens-present; fuzzy mode (or auto when strict is empty) adds Jaro-Winkler scoring labeled approximate with a raw 0–1 score plus the count of query tokens the matched name covers, which orders candidates that tie on score. Results are paged: totalAvailable and hasMore report candidates beyond the returned page, and nextOffset retrieves them. Returns potential matches to confirm against the GLEIF record — name resolution is a candidate ranking, not an authoritative identification.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sanctions_resolve_entity",
    "arguments": {
      "name": "<name>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "description": "The company / organization name to resolve to an LEI, in any script. It must contain at least one letter or digit, and at most 64 words and 1024 characters."
    },
    "jurisdiction": {
      "description": "Optional legal-jurisdiction filter. A country code (\"US\") matches that country and every subdivision under it (US-DE, US-CA); a subdivision code (\"US-DE\") matches exactly. Case-insensitive. Empty string disables it.",
      "anyOf": [
        {
          "type": "string",
          "const": ""
        },
        {
          "type": "string",
          "pattern": "^[A-Za-z]{2}(-[A-Za-z0-9]{1,3})?$",
          "description": "ISO 3166-1 alpha-2 country code (e.g. US) or ISO 3166-2 subdivision code (e.g. US-DE), case-insensitive."
        }
      ]
    },
    "matchMode": {
      "default": "strict",
      "description": "strict (default): exact then all-tokens-present. fuzzy: also scored Jaro-Winkler.",
      "type": "string",
      "enum": [
        "strict",
        "fuzzy"
      ]
    },
    "status": {
      "default": "issued",
      "description": "Registration status filter. issued (default) matches ISSUED; lapsed matches exactly LAPSED; any applies no filter and is the only value that reaches the other states (RETIRED, DUPLICATE, ANNULLED, PENDING_TRANSFER, PENDING_ARCHIVAL, MERGED) — each candidate's status field names its state.",
      "type": "string",
      "enum": [
        "any",
        "issued",
        "lapsed"
      ]
    },
    "minScore": {
      "description": "Jaro-Winkler floor for fuzzy hits (0–1); defaults to the server's configured floor.",
      "type": "number",
      "minimum": 0,
      "maximum": 1
    },
    "limit": {
      "default": 10,
      "description": "Maximum LEI candidates to return in one page.",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    },
    "offset": {
      "default": 0,
      "description": "Zero-based index of the first LEI candidate to return. Re-call with the returned nextOffset to page through every candidate when hasMore is true; an offset past the end returns an empty page, not an error.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "name",
    "matchMode",
    "status",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
view source ↗

sanctions_get_entity

Fetch the full GLEIF Level 1 record for one LEI: legal name, other/trading names, legal and headquarters addresses, registration status, jurisdiction, registration authority and ID, and last-update date — plus a sanctions cross-reference against all loaded watchlists. The cross-reference screens the legal name and every other and transliterated name strict (exact, then all tokens present — never fuzzy, unlike sanctions_screen_name), and looks up the LEI and the registration number as exact non-document identifiers, the registration number matching only an identifier published for the country of the entity's legal jurisdiction. Hits merge to one per designation, an OFAC party both OFAC lists publish to one hit whose sources names both: matchedOn names every input that produced each, and a hit only an identifier produced carries matchedIdentifiers and no matchedName. The screening cross-reference is a screening AID: a hit is a candidate to verify against the official source, and no hit is not a clearance. screeningStatus says whether that cross-reference actually ran — an empty sanctionsHits under not_ready means the sanctions mirror was unavailable, not that nothing matched. sanctionsScreen says whether the hit list is the whole set: it reports how many potential matches existed before the cap, so a capped cross-reference is distinguishable from a complete one. LEI must be a 20-character GLEIF identifier (18 alphanumerics + 2 check digits).

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sanctions_get_entity",
    "arguments": {
      "lei": "<lei>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "lei": {
      "type": "string",
      "pattern": "^[A-Z0-9]{18}[0-9]{2}$",
      "description": "The 20-character GLEIF Legal Entity Identifier to look up."
    }
  },
  "required": [
    "lei"
  ],
  "additionalProperties": false
}
view source ↗

sanctions_trace_ownership

Trace a paged GLEIF Level 2 ownership segment from a 20-character LEI (use sanctions_resolve_entity to obtain one). Direction both walks parents and children separately from the root, never sideways into siblings or co-parents; parents-side placement wins cycles. Ultimate-parent edges are shortcuts, never walked hops; ultimate-only nodes are flagged reachedVia: ultimate. Drain nextCursor with the same query, then re-root each boundary with its LEI, same-side direction and depth, including at depth five; deduplicate branch roots and edges across segments. Pages emit nodes, then edges, then boundaries, up to limit records (50 default, 100 maximum), stopping after an intact record reaches the soft 64-KiB target. Edges reference nodes across assembled pages. Cursors bind query and topology; changed topology requires restarting without a cursor. Entity and screening values read current mirrors, not an immutable snapshot. Set screenNodes to cross-reference only this page of nodes as sanctions_get_entity does: legal, other and transliterated names strict (exact then all tokens present, never fuzzy), LEI and country-matched registration number as exact non-document identifiers, with merged designation hits and matchedOn inputs. A node missing Level 1 is screened by LEI alone. Every hit is a candidate to verify; no hit is a clearance. Each eligible node retains direct and ultimate parentStatus: relationship, reporting exception and its reasons, none, or unknown when exceptions are not loaded. Read hasMore and truncationReasons separately from segment-wide missingEntityCount and page-local missingEntityLeis, screening counts and per-node ten-hit cap disclosures. A screening aid, not a compliance determination; a finished segment never proves full beneficial ownership.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sanctions_trace_ownership",
    "arguments": {
      "lei": "<lei>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "lei": {
      "type": "string",
      "pattern": "^[A-Z0-9]{18}[0-9]{2}$",
      "description": "The 20-character GLEIF LEI at the root of the ownership graph."
    },
    "direction": {
      "default": "both",
      "description": "Walk parents (who owns it), children (what it owns), or both (default): a parents walk plus a children walk from the root, each to depth, never into siblings or co-parents.",
      "type": "string",
      "enum": [
        "parents",
        "children",
        "both"
      ]
    },
    "depth": {
      "default": 3,
      "description": "Maximum traversal depth from the root entity (1–5), on each side of a both walk. Ultimate-parent edges do not count as hops.",
      "type": "integer",
      "minimum": 1,
      "maximum": 5
    },
    "screenNodes": {
      "default": false,
      "description": "When true, cross-reference every returned node on this page against all watchlists: legal, other and transliterated names strict, LEI and country-matched registration number as identifiers. A node missing Level 1 has only its LEI lookup. Edge/boundary-only pages screen zero nodes.",
      "type": "boolean"
    },
    "limit": {
      "default": 50,
      "description": "Maximum records across nodes, edges and boundaries per page (default 50, requests above 100 clamp with notice).",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "cursor": {
      "description": "nextCursor from the preceding page, with the same query parameters. Omit to start a new segment.",
      "type": "string"
    }
  },
  "required": [
    "lei",
    "direction",
    "depth",
    "screenNodes",
    "limit"
  ],
  "additionalProperties": false
}
view source ↗

Resources

3

Fetch one sanctions designation by source list + entry ID or the list's published reference number — a read-only URI mirror of sanctions_get_designation. The record is what the source published; a screening aid, not a determination.

uri sanctions://designation/{source}/{entryId} mime application/json

Fetch one GLEIF Level 1 legal-entity record by LEI — a read-only URI mirror of sanctions_get_entity's entity payload. The sanctions cross-reference is available only via the tool.

uri sanctions://entity/{lei} mime application/json

List the sanctions watchlists and GLEIF datasets loaded in the local mirror, each with its record count, source URL, and license, plus each mirror's readiness and as-of timestamp, the GLEIF Level 2 relationship count, whether GLEIF reporting exceptions are loaded and how many, and whether the GLEIF alternate-name index is built — the sanctions_list_sources payload, unchanged, as a read-only URI.

uri sanctions://sources mime application/json

Prompts

1

Structure a full counterparty due-diligence pass: resolve the name to an LEI, pull its GLEIF ownership graph, screen the named entity and every parent and subsidiary in that graph against all sanctions lists, and summarize hits with provenance and the decision-support caveat.

  • namerequired — The counterparty name to vet (person or organization).
  • jurisdiction — Optional jurisdiction to disambiguate the entity: an ISO 3166-1 alpha-2 country code, which also matches every subdivision under it ("US" matches US-DE and US-CA), or an ISO 3166-2 subdivision code ("US-DE").

Extensions

1

io.modelcontextprotocol/skills

{
  "directoryRead": true
}