unicode-le-mcp

An MCP server that finds the Unicode risks
in a document — the characters that hide meaning: bidirectional controls,
invisibles, homoglyphs, words that mix scripts, text that is not in NFC — the
detection engine behind the Unicode-LE
editor extension, exposed as a tool an agent can call.
It reports codepoints as U+XXXX and never returns the characters
themselves. A model that pasted a document into its own reasoning has already
been handed the bidi controls in it; what comes back from here cannot carry them
on into a commit message or a review comment. Every non-ASCII character in a
reply leaves as a \uXXXX escape, including the key paths, which are text from
the document.
No dependencies, no network calls, no filesystem access. Content goes in,
structured results come out.
Use it
Point any MCP host at npx unicode-le-mcp.
Claude Code
claude mcp add unicode-le -- npx -y unicode-le-mcp
Anything with a JSON config — Cursor, Windsurf, Claude Desktop:
{
"mcpServers": {
"unicode-le": {
"command": "npx",
"args": ["-y", "unicode-le-mcp"]
}
}
}
VS Code needs nothing here. Install the extension instead — it
carries this server and registers it for you:
VS Code Marketplace
· Open VSX
No Node? The same detect_unicode_risks tool ships in a static Rust
binary: cargo install unicode-le, then unicode-le mcp
(crates.io). The two servers answer
identically — one corpus runs against both, a differential test feeds both
thousands of generated documents and compares every answer, and both read the
same Unicode tables, written out by the crate, rather than whatever version the
host's JavaScript engine carries. The binary additionally offers
unicode_le_scan, which walks a tree; this server reads no files.
Prefer a global install to npx on every launch:
npm install -g unicode-le-mcp
{
"mcpServers": {
"unicode-le": { "command": "unicode-le-mcp" }
}
}
No environment variables, no API key, no configuration of its own. To check it
before wiring it into anything:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | npx -y unicode-le-mcp
If that prints the tool name, the server works.
The tool
detect_unicode_risks
content | string | required. The document text to scan. |
kinds | string[] | Report only these: bidi, invisible, confusable, mixed-script, non-nfc, whitespace, unassigned. Omit for all of them — the only setting under which an empty result means clean. |
scripts | string[] | Non-Latin scripts the document is expected to be written in, by Unicode name or ISO 15924 tag: Han, Cyrillic, Hira. Naming them lets the homoglyph check run on a translated document. |
format | string | json, yaml, toml, ini, env, csv, tsv or text, so each finding can carry the key path it sits under. |
filename | string | An alternative to format; only its extension is read. |
maxResults | number | Default 500, ceiling 5000. |
Each finding carries its kind, severity, 1-based line and column (UTF-16, as an
editor counts), byte offset, the codepoints, the script and, where the format
allows, the key path:
{
"ok": true,
"data": {
"findings": [
{
"kind": "confusable",
"severity": "high",
"line": 2,
"column": 8,
"offset": 70,
"key": "auth.provider",
"codepoints": ["U+0430"],
"scripts": ["Cyrillic"],
"resembles": ["U+0061"],
"detail": "a Cyrillic character in a word that is not Cyrillic, and it reduces to the codepoint under `resembles`: the two are indistinguishable on screen"
}
],
"refusals": []
},
"diagnostics": [],
"meta": { "tool": "detect_unicode_risks", "count": 1, "truncated": false }
}
A document plainly written in a script nobody declared is refused for the
homoglyph and mixed-script checks rather than judged by them. The refusal comes
back twice — in data.refusals and as a warning diagnostic — so an empty
finding list is never mistaken for a clean document. ok means the check ran,
not that the answer was yes.
Also in the MCP registry
io.github.nolindnaidoo/unicode-le —
registry.modelcontextprotocol.io
Ten more like it
One tool each, same shape: content in, structured data out, no network and no
filesystem. Every one is on npm as <name>-mcp and in the MCP registry as
io.github.nolindnaidoo/<name>.
Every tool in the family, one page: letools.dev
Built by
Nolin Naidoo — Chief Engineer, AI/ML & Platform
Architecture. nolindnaidoo.com ·
GitHub ·
LinkedIn
Also from the same workshop
Twelve Rust tools built the same way: small, single-purpose, and driven by a
machine rather than a person. pixelcoords and pixelactions make up one loop —
pixelcoords answers where, pixelactions acts there. The ten LE crates are
the terminal half of the extensions they sit in: the same detection, held to
the extension's own corpus, and an exit code instead of a results editor.
Licence
MIT © Nolin Naidoo