ofac-match
OfacMatch
OFAC sanctions name screening for .NET that tells you why a name matched.

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 OfacMatchusing 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 alikeHow 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.CSVOFAC 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-matchClaude Code and other clients (needs the .NET 10 SDK):
claude mcp add ofac-match -- dnx OfacMatch.Mcp --yesTools: 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 1951Results 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.mdDocumentation
Guide | What it covers |
Benefits and comparison with other options | |
Every step, with a worked example | |
Types, options and defaults | |
Thresholds, review, record keeping, limits | |
Full results by variation and setting | |
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 toolpackage
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
Available Tools
5 toolscompare_namesExplain how two names scoreARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | The searched name. | |
| companies | No | Treat the names as company names, ignoring legal forms such as LLC or LTD. | |
| otherName | Yes | The name to compare with, e.g. a listed name or alias. |
TDQS
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.
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.
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.
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.
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.
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 statusARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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 entryARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| entryId | Yes | The SDN entry number (ent_num) shown in screening results. |
TDQS
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.
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.
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.
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.
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.
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 listARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | The name to screen, in any order, e.g. "John Doe" or "DOE, John". | |
| type | No | Only screen this kind of entry: individual, entity, vessel or aircraft. Omit to screen all. | |
| country | No | The party's country, if known. Matches whose listed addresses are all in other countries are flagged. | |
| minScore | No | Lowest score to return, 0-100. Default 85. | |
| maxResults | No | Maximum number of matches to return, 1-50. Default 10. | |
| yearOfBirth | No | The party's year of birth, if known. Matches whose listed dates of birth differ by more than a year are flagged. |
TDQS
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.
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.
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.
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.
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.
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 namesARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| names | Yes | The names to screen (at most 100). | |
| minScore | No | Lowest score to count as a potential match, 0-100. Default 85. |
TDQS
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.
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.
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.
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.
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.
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.
5 tool updates
v0.1.0- First observed
compare_names - First observed
get_list_info - First observed
get_sanctions_entry - First observed
screen_name - First observed
screen_names
TDQS
Scored across 5 tools
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.
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.
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.
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
Related MCP Connectors
Fuzzy-screen people and company names against OFAC SDN, EU, UK and UN sanctions lists.
Screen a name against the U.S. Treasury OFAC SDN list. Information, not a compliance determination.
Screen a name against OFAC's SDN and Consolidated Sanctions lists before a transaction.
Checks names against OFAC, UK, EU, UN and more sanctions lists; returns scored possible matches.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server for OFAC SDN sanctions screening. Check names against US Treasury sanctions lists with automatic cache refresh. No API key needed for the check itself.MIT
- AlicenseNot gradedqualityBmaintenanceScreens names against the US Consolidated Screening List including OFAC SDN and BIS Entity List, keyless.207 npmMIT
- FlicenseNot gradedqualityDmaintenanceScreens names and companies against OFAC, EU, UK, and UN sanctions lists with fuzzy-match scores, supporting bulk lookups for AML/KYC checks. Data is sourced directly from official government lists and cached for fast repeat checks.-
- AlicenseNot gradedqualityAmaintenanceScreen names against consolidated OFAC, EU, UK, and UN sanctions lists and resolve legal entities via GLEIF, with offline fuzzy matching.342 npm3Apache 2.0