Skip to main content
Glama

OfacMatch

OFAC sanctions name screening for .NET that tells you why a name matched.

OfacMatch screening a misspelled name, flagging a birth-year conflict and clearing a common name

Try it in your browser: no install, and the names you type never leave your browser.

Why I built this

I spent ten years building title and mortgage systems at First American, including OFAC screening that stops a loan from going to someone on the Treasury's sanctions list. Name matching sounds simple until a compliance analyst asks why a name was flagged, or why a misspelled one wasn't.

Many teams still search OFAC's website by hand or rely on screening logic they can't see inside. I wanted something small and open: load the official list, score names the way OFAC describes in its own FAQ, and show the reasoning behind every score.

Related MCP server: mcp-sanctions-screening

What it does

OfacMatch downloads the Specially Designated Nationals (SDN) list from OFAC, screens names against it, and gives each potential match a score from 0 to 100, the same scale OFAC's search tool uses. Every match comes with an explanation: which name or alias it hit, and how each part of the name paired up.

It handles the messy parts of real names: accents, LAST, First ordering, apostrophes and hyphens, titles like "Dr.", company endings like LLC or LTD (even misspelled), and aliases. If you pass a year of birth or country that disagrees with the listed record, the match is flagged rather than hidden, because that call belongs to a person.

It has no dependencies, runs inside your own process (names never leave your systems), and works on modern .NET as well as .NET Framework 4.7.2+.

I measured it on the real list rather than guessing: it finds 99.8% of 4,255 realistic misspellings and variations of listed names at a threshold of 85. The evaluation has the details, including where it still misses.

This is an early release (0.1). Feedback, bug reports and name variations that score badly are very welcome.

OfacMatch helps you screen; it doesn't make compliance decisions and isn't legal advice. A score says how alike two names are written, not whether two records are the same person. Pick your threshold from your own risk assessment and have someone review every potential match. More in Using OfacMatch responsibly.

Quick start

dotnet add package OfacMatch
using OfacMatch.Data;
using OfacMatch.Screening;

using var http = new HttpClient();
var list = await SanctionsList.DownloadAsync(http, saveToDirectory: "ofac-data");

var screener = new SanctionsScreener(list, new ScreeningOptions { MinScore = 85 });

foreach (var match in screener.Screen("Aymen Zawahri"))
{
    Console.WriteLine(match.Explain());
}

Output against the SDN list published 27 October 2026:

Score 95.8 via NameParts against Primary name "AL ZAWAHIRI, Dr. Ayman"
  Entry 2676: AL ZAWAHIRI, Dr. Ayman (Individual; programs: SDGT)
  Full-name score 84.5; name-parts score 95.8
  AYMEN -> AYMAN: similarity 0.907, sounds alike
  ZAWAHRI -> ZAWAHIRI: similarity 0.946, sounds alike

How scoring works walks through this example step by step.

Screening with more context

var request = new ScreeningRequest("John Michael Doe")
{
    Type = EntryType.Individual, // a borrower is a person, so skip companies and vessels
    YearOfBirth = 1971,          // flags matches whose listed dates of birth disagree
    Country = "United States",   // flags matches whose listed addresses are all elsewhere
};

var matches = screener.Screen(request);

Offline use

var list = SanctionsList.LoadFromDirectory("ofac-data"); // SDN.CSV, ALT.CSV, ADD.CSV

OFAC updates the list often. Reload it at least daily, and screen against the current list before every decision.

Use it from an AI assistant (MCP server)

OfacMatch.Mcp lets Claude, GitHub Copilot and other MCP clients screen names, look up SDN entries and explain scores. It runs locally and needs no API key.

Claude Desktop: download the extension for your computer from the latest release (ofac-match-windows.mcpb or ofac-match-mac-*.mcpb) and double-click it. No .NET or config file needed.

Claude Code plugin (adds the server plus a skill for reporting results responsibly; needs the .NET 10 SDK):

/plugin install ofac-match --marketplace krunalsolanki/ofac-match

Claude Code and other clients (needs the .NET 10 SDK):

claude mcp add ofac-match -- dnx OfacMatch.Mcp --yes

Tools: screen_name, screen_names, get_sanctions_entry, compare_names, get_list_info. See the MCP server guide for other clients and settings.

Command line

dotnet run --project samples/OfacMatch.Cli -- "Aymen Zawahri" --min 85 --yob 1951

Results on the real SDN list

From the evaluation: 600 listed parties (400 individuals, 200 companies) and 4,255 realistic variations of their names, plus 2,000 ordinary names, against the SDN list published 27 October 2026.

Threshold

Listed parties found

Ordinary names that alert

80

99.9%

83.4%

85

99.8%

47.9%

90

99.8%

15.9%

95

99.5%

3.8%

Variations tested: exact, surname first, lower case without punctuation, one wrong letter, two letters swapped, one letter missing, different transliteration, joined particles ("ALZAWAHIRI"), middle name left out, and changed company suffix.

Two things surprised me in these numbers. First, screening on a name alone alerts a lot: about half of ordinary two-part names look like something on the list, and a few are spelled exactly like a listed person. Date of birth and country are what let a reviewer clear those quickly. Second, moving the threshold from 85 to 90 cut alerts by two thirds without losing any of these variations, so the threshold deserves a deliberate choice rather than a default.

You can re-run it yourself:

dotnet run -c Release --project eval/OfacMatch.Evaluation -- --data ofac-data --out docs/evaluation.md

Documentation

Guide

What it covers

Why OfacMatch

Benefits and comparison with other options

How scoring works

Every step, with a worked example

API and configuration reference

Types, options and defaults

Using OfacMatch responsibly

Thresholds, review, record keeping, limits

Evaluation

Full results by variation and setting

MCP server

Using OfacMatch from AI assistants

What's next

Things I'd like to work on, roughly in order:

  • Evaluation on the real list

  • Give rare words more weight ("CUBA" should count more than "BANK"), to cut alerts on common names

  • Catch typos at the start of company endings ("LTMITED")

  • Support OFAC's Consolidated (non-SDN) list and the Advanced XML format

  • Faster batch screening

  • An ASP.NET Core sample and a dotnet tool package

If you'd like to help with any of these, or have a case that scores badly, open an issue. CONTRIBUTING.md has the details.

Credits

Built by Krunal Solanki. The idea and the compliance background come from my work on OFAC screening in mortgage lending.

Privacy Policy

OfacMatch collects nothing. Names you screen are processed on your own computer, or in your browser for the web page, and are never sent anywhere. The only network request is the download of the public SDN list from OFAC, which is cached locally. Full details: PRIVACY.md.

Data

The SDN list is published by the U.S. Department of the Treasury's Office of Foreign Assets Control. OfacMatch downloads it from OFAC's Sanctions List Service and does not redistribute it. The unit tests use fictional names only.

License

Apache License 2.0

Available Tools

5 tools
compare_namesExplain how two names scoreA
Read-onlyIdempotent

Scores two names against each other with the same method used for screening, and shows the whole-name score, the name-parts score and how each part paired. Useful to understand or explain a score.

NameRequiredDescriptionDefault
nameYesThe searched name.
companiesNoTreat the names as company names, ignoring legal forms such as LLC or LTD.
otherNameYesThe name to compare with, e.g. a listed name or alias.

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint and idempotentHint, so the description needn't cover safety. It adds genuinely new behavioral context by disclosing what comes back: a whole-name score, per-part scores, and how parts were paired, which is exactly what an explanation tool needs to promise.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One compact sentence that front-loads the action and then enumerates the outputs, followed by a short use note. No filler, though the two clauses could be tightened slightly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the return-value burden and does so by naming the three outputs. A three-parameter, read-only tool is otherwise fully covered; only the companies flag's effect on comparison is left unmentioned.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so name, otherName, and the companies flag are already documented in the schema. The description adds nothing about the companies mode or the searched-name vs alias distinction, which is acceptable at full coverage but yields only baseline credit.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: scores two names against each other and reports whole-name score, name-parts score, and pairings. This is clearly a comparison/explanation tool, distinguishably different from the screening siblings, though it never names those siblings explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implies usage with 'useful to understand or explain a score' and ties the method to screening, giving the agent enough context to infer this is a diagnostic follow-up to screening. It never states when to prefer screen_name/screen_names instead or any preconditions, so the guidance stays implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_list_infoSDN list statusA
Read-onlyIdempotent

Reports which copy of the OFAC SDN list the server is using: number of entries, where it came from and when it was downloaded.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint and idempotentHint, so the safety profile is covered. The description adds useful substance beyond that by disclosing exactly what state is reported — dataset size, provenance, and download timestamp — which tells the agent this reflects server-side list version rather than any query result.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence with the key fact (which list copy) front-loaded and the three returned attributes appended after a colon. No filler, no restatement of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description carries the burden of describing return content, and it does so by naming the three fields. It stops short of format details (e.g., timestamp timezone or entry-count type), but for a zero-parameter status tool this is close to sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes no parameters, so there is no schema semantics to add; the baseline applies. The description appropriately spends its words on the response content instead of inventing inputs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Reports which copy of the OFAC SDN list the server is using') and enumerates the returned facts (entry count, origin, download time). This is plainly distinct from the name-screening siblings, which query entries rather than describe the loaded dataset.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: an agent can infer this is a metadata/freshness check, but the description never says when to call it (e.g., before screening, to verify staleness) or that it is not a screening tool. No alternatives or exclusions are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_sanctions_entryLook up an SDN entryA
Read-onlyIdempotent

Returns the full published record of one OFAC SDN entry by its entry number: names, aliases, type, programs, dates of birth, addresses and remarks. Use it to review a potential match.

NameRequiredDescriptionDefault
entryIdYesThe SDN entry number (ent_num) shown in screening results.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnlyHint, idempotentHint), so the description's value is in disclosing the payload — it enumerates exactly which record fields come back, which matters since there is no output schema. The word "published" usefully signals this is the official published snapshot rather than live data. It does not say what happens on an unknown entryId, which keeps it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero filler: the return contents lead, and the single usage cue follows. Nothing is repeated unnecessarily and the most decision-relevant information is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter read tool with full schema coverage and no output schema, the description covers purpose, payload, and trigger well. The only meaningful omission is failure behavior for a nonexistent or malformed entry number, which an agent would want to know before calling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single parameter is already documented as the SDN entry number (ent_num) from screening results. The description restates but adds no format, range, or error semantics beyond the schema, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Returns the full published record of one OFAC SDN entry") and scopes it by key ("by its entry number"). The field enumeration (names, aliases, type, programs, DOBs, addresses, remarks) makes it unmistakably distinct from the screening siblings screen_name/screen_names and from compare_names.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Use it to review a potential match" gives a clear workflow context: call this after a screening result surfaces a candidate entry. It does not explicitly name the alternatives (e.g. "to search by name use screen_name") or state what to do if no entry exists, so it falls short of full when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

screen_nameScreen a name against the OFAC SDN listA
Read-onlyIdempotent

Screens one person, company, vessel or aircraft name against the U.S. Treasury OFAC SDN sanctions list and returns potential matches with scores (0-100, 100 = identical) and an explanation of each. Give year of birth and country when known so conflicts can be flagged.

NameRequiredDescriptionDefault
nameYesThe name to screen, in any order, e.g. "John Doe" or "DOE, John".
typeNoOnly screen this kind of entry: individual, entity, vessel or aircraft. Omit to screen all.
countryNoThe party's country, if known. Matches whose listed addresses are all in other countries are flagged.
minScoreNoLowest score to return, 0-100. Default 85.
maxResultsNoMaximum number of matches to return, 1-50. Default 10.
yearOfBirthNoThe party's year of birth, if known. Matches whose listed dates of birth differ by more than a year are flagged.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint and idempotentHint, so the safety profile is covered. The description adds real value beyond that: it discloses the return shape (potential matches with 0-100 scores where 100 is identical, plus an explanation per match) and the flagging behavior when year of birth or country conflict.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero preamble: the primary action and return format come first, followed by one sentence of actionable input advice. Nothing is wasted or redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description still conveys what comes back (scored matches plus explanations) and how to improve result quality via year of birth and country. Fully sufficient for a read-only, single-parameter-required screening call, though match/flag structure is described only in prose.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so every parameter is already documented, including that minScore defaults to 85 and yearOfBirth/country trigger flagging. The description restates the yearOfBirth/country advice and adds the rationale ('so conflicts can be flagged'), which is a marginal increment over the schema rather than new information.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb (screens), a specific resource (one person/company/vessel/aircraft name) and a specific reference list (U.S. Treasury OFAC SDN). The word 'one' implicitly separates it from the batch sibling screen_names, but no sibling is named explicitly and compare_names/get_sanctions_entry are never referenced.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage guidance is implied rather than stated: an agent can infer this is the single-name screening entry point against OFAC SDN. There is no explicit when-to-use vs compare_names or get_sanctions_entry, and no stated exclusions or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

screen_namesScreen several namesA
Read-onlyIdempotent

Screens up to 100 names against the OFAC SDN list and returns, for each, the number of potential matches and the best one. Use screen_name for full details on any name that has matches.

NameRequiredDescriptionDefault
namesYesThe names to screen (at most 100).
minScoreNoLowest score to count as a potential match, 0-100. Default 85.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the read-only and idempotent profile, and the description adds the screening target (OFAC SDN only), the batch ceiling, and the summarized nature of the response. It does not state whether the screen is local or remote, latency, or what the 'best match' object contains, so it stops short of full disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with capability and return shape, followed by the sibling fallback. No filler or restatement of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter read tool with no output schema, the definition explains what comes back in summary form and where to go for depth. A note on whether lists other than OFAC SDN exist (the presence of get_list_info hints at that) would close the remaining gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both the names array and the minScore threshold are already fully documented in the schema. The description's 'up to 100' and 'number of potential matches' loosely reinforce those parameters but add no new semantics or formatting guidance, which meets the baseline-3 case.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb (screens), a specific resource (OFAC SDN list), a bounded scope (up to 100 names), and the return shape (match count plus best match per name). It distinguishes itself cleanly from the sibling screen_name, which handles a single name.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly routes the agent to screen_name when full details are needed for a name that has matches, which is a real when-to-use split. It does not discuss when to prefer a single-name call outright or what happens past the 100-name cap, but the primary alternative is named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedcompare_names
    • First observedget_list_info
    • First observedget_sanctions_entry
    • First observedscreen_name
    • First observedscreen_names

TDQS

A4.1/5.0

Scored across 5 tools

Disambiguation4/5

The five tools occupy distinct roles: single screening, batch screening, score explanation, list provenance, and entry lookup. The only near-collision is screen_name vs screen_names, but the descriptions explicitly state the plural is a bulk summary and directs agents to the singular for detail, which resolves the overlap.

Naming Consistency5/5

All names follow a strict snake_case verb_noun pattern (screen_name, screen_names, compare_names, get_list_info, get_sanctions_entry). The singular/plural pair is a predictable and readable convention rather than a deviation.

Tool Count5/5

Five tools is tightly scoped to the screening domain, with each tool earning its place: screen, batch screen, explain, list metadata, and entry retrieval. Nothing feels extraneous or missing at the surface level.

Completeness4/5

The core lifecycle — screen a name, screen many, explain a score, inspect the underlying SDN record, and verify list freshness — is fully covered with no dead ends. Minor gaps exist, such as no tool to browse or search entries by program/country without a name, but agents can work around this.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers