DEV Community

Cover image for Giving an AI agent a spell checker with MCP
Seppe Gadeyne
Seppe Gadeyne

Posted on

Giving an AI agent a spell checker with MCP

A writing agent can produce a fluent paragraph and still choose the wrong spelling convention. A word can pass a dictionary check and still conflict with a style guide. US English makes a useful example: spelling and regional usage are separate checks.

I keep those decisions outside the model in language-mcp: a small TypeScript MCP server with local spelling checks. It currently supports US English and Dutch, with a separate Dutch word lookup. The examples below use US English; the same design can be extended to other languages with suitable dictionaries, rules, and tools. The agent submits text, reads the findings, and decides which changes make sense. The server does not rewrite files.

The useful part of this project is the boundary between a dictionary, a style rule, and an editorial decision. They answer different questions, even when all three produce something that looks like a correction.

A local dictionary first, a network lookup when needed

Four tools use the Hunspell executable with bundled dictionaries. Dutch uses OpenTaal; US English uses the SCOWL/LibreOffice dictionary assets. Spelling checks do not send the submitted text to a spelling API.

That privacy statement applies to this server, not to the whole agent session. If your MCP client uses a hosted language model, its conversation and tool results may still reach that provider. I explain this distinction in more detail in our guide to what stays local in a hybrid AI setup.

The fifth tool, get_dutch_word_details, queries Woordenlijst.org, maintained by the Instituut voor de Nederlandse Taal for the Taalunie. It provides lexical details such as pronunciation, word forms, and hyphenation. This is a separate network operation, useful for an individual word that needs investigation.

The Dutch dictionary source is OpenTaal's Hunspell repository. Keep the dictionary attribution and license files when redistributing the assets; a server's code license does not replace them.

Why I kept the Hunspell executable

The project's architecture decision record considers replacing the executable with nspell, a JavaScript implementation. Removing a native dependency would simplify deployment. I kept the executable because changing the implementation also means proving behavioral compatibility.

A particularly easy mistake sits in Hunspell's -a pipe protocol. It does not promise one response line for every submitted token. A hyphenated input can produce a response for each part, followed by an empty separator line.

If the wrapper zips individual response lines with input words, one compound can shift the remaining results onto the wrong words. The current implementation groups output into blocks separated by empty lines, then associates each block with one unique input token. It rejects a response-count mismatch instead of returning misaligned findings.

Within a block, the wrapper accepts * and + responses, collects suggestions from & responses, and treats unknown responses as failures. It removes duplicate suggestions and returns at most eight per token. It also limits the subprocess to 20 seconds.

That explains the dependency better than a speed claim. The ADR discusses performance, but I am not presenting a Hunspell-versus-nspell benchmark here. A replacement should pass the same compound and suggestion tests before deployment gets simpler at the expense of correctness.

Install the pinned version

You need Git, Node.js 20 or newer, npm, and hunspell available on the MCP host's PATH. The Bash commands below target Linux, macOS, or WSL. Native Windows setup is not covered here; with WSL, install the dependencies and run the client inside that environment.

Install Hunspell through your operating system's package manager first. The repository README gives pacman -S hunspell for Arch Linux. Package installation may require administrator privileges. Check the actual executable before building:

node --version
npm --version
hunspell --version

git clone https://github.com/seppegadeyne/language-mcp.git
cd language-mcp
git checkout --detach a5787562260bead2e19939db862e2aa07b78781d
npm ci --ignore-scripts
npm test
npm run build
Enter fullscreen mode Exit fullscreen mode

The checkout pins the source discussed here rather than silently following future changes. Run the locked dependency install, tests, and build on your MCP host before configuring your client. A successful run on one machine is not proof that every Node version or operating system has been exercised.

In an existing Hermes installation, merge this entry into mcp_servers in your configuration. Replace the example path with your checkout's absolute path; do not replace unrelated server entries.

mcp_servers:
  language:
    command: node
    args: ["/absolute/path/to/language-mcp/dist/cli.js"]
    connect_timeout: 60
    enabled: true
    timeout: 120
Enter fullscreen mode Exit fullscreen mode

Then test discovery:

hermes mcp test language
Enter fullscreen mode Exit fullscreen mode

The server speaks MCP over stdio, so node dist/cli.js is not an interactive spelling prompt. Your client launches it and exchanges protocol messages. Other stdio-capable MCP clients need their own configuration syntax, and their tool names may include a server prefix.

Try the US English tools

These JSON objects are tool arguments, not shell commands. Select the named tool through your MCP client or ask your agent to call it.

For a paragraph, use check_us_english_text:

{"text":"While moving toward the center, we organized everything."}
Enter fullscreen mode Exit fullscreen mode

The verified response is:

US English check (hunspell en_US + britticism scan)
8 words checked, 0 unknown, 0 British forms.
OK: no typos, no British forms found.
Enter fullscreen mode Exit fullscreen mode

For one word, use validate_us_english_word:

{"word":"color"}
Enter fullscreen mode Exit fullscreen mode

The verified response is "color" is correct US English.

To see the regional style check in action, replace While with the intentionally British Whilst, and toward with towards. These are test inputs, not recommended US English. The dictionary accepts them, but the separate style rules flag both and suggest the US forms. A dictionary check alone would miss that distinction.

The server also provides check_dutch_text, validate_dutch_word, and get_dutch_word_details. The first two use the local Dutch dictionary; the last uses the network for lexical details. You do not need the network lookup for the US English examples.

Extend the pattern to another language

MCP does not tie this design to English or Dutch. The current server implements those two languages; adding another one requires code and dictionary assets, not just a language name in a request.

Start with a compatible Hunspell dictionary from a trustworthy source. Include its dictionary and affix files, preserve its license and attribution, and add it to the dictionary resolver. Then register language-specific text-checking and word-validation tools with the same result structure.

Add regional or editorial rules only when the dictionary cannot express the distinction you need. A lexical lookup is optional and requires a suitable source for that language; the Dutch lookup is not a universal dictionary API. Finally, test valid words, misspellings, compounds, suggestions, and any language-specific rules before exposing the new tools to an agent.

The reusable idea is the separation of responsibilities: the dictionary checks spelling, explicit rules check selected conventions, and the agent reviews the findings in context. Language coverage depends on the available dictionaries and the integration work, not on MCP itself.

Treat the style rules as review suggestions

The britticism detector is a static list of regular expressions and suggested replacements. Some rules cover spelling variants. Others encode editorial preferences, such as using custom rather than bespoke.

I would not automatically apply every match. The list also includes context-sensitive vocabulary: queue is perfectly ordinary in an American programming article, and lift does not always mean an elevator. Rules can flag valid text or generate an unsuitable inflection. This implementation is not a grammar parser.

There is no measured coverage percentage behind the rule list. It is inspectable and testable, which makes it useful for enforcing a specific style contract, but it cannot certify that a paragraph is idiomatic US English.

The two checking paths also treat Markdown differently. The spelling tokenizer removes URLs, email addresses, fenced code, and inline code. The britticism scan receives the original text. A quoted example or identifier can therefore appear in style findings even when spelling ignores it. Preserve source quotations and code; review the surrounding prose instead.

The limits belong in the workflow

The tokenizer stops after 2,000 checkable tokens, skips one-character tokens, and skips short all-uppercase acronyms. For a longer document, split it into sections and keep track of what you submitted. A clean result is not evidence that every character in the original document was checked.

Reported positions are JavaScript string indexes, not editor-ready line and column locations. The implementation uses the first occurrence of the token in the original string. Repeated words and excluded material make automatic patching risky.

The network lookup has its own boundaries. It uses an unofficial XML service, serializes calls with a minimum 1.2-second interval, and caches results in memory for 24 hours. This is not a bulk dictionary download API or an availability guarantee. Its parser also takes the first matching descendant fields; in the verified pizza lookup, the top-level hyphenation reflected a diminutive. Check ambiguous lexical details on the source website rather than treating every rendered field as authoritative.

My preferred workflow is simple: submit a section, inspect the findings, make deliberate edits, and check the edited section again. Keep proper nouns and technical identifiers unless there is a specific reason to change them. Read the whole paragraph afterward. A dictionary cannot tell whether a sentence says what you meant.

The Glama listing is another way to discover the server; the pinned repository is the reference for the tool names and behavior described here. A directory listing does not remove the Hunspell runtime requirement.

Top comments (0)