mcp server
language-mcp
Local MCP server for Dutch and US English spelling checks, dictionary lookups, and US English style suggestions for AI agents.
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 28, 2026language-mcp
A local MCP server for Dutch and US English language checks, built for AI agents (Hermes, Claude, and others) that write resumes, cover letters, blog articles, and website copy.
Currently supported: Dutch and US English. Additional languages can be implemented by adding dictionaries, language-specific rules, and MCP tools; they are not supported automatically by selecting an arbitrary language.
Tools
| Tool | Source | Network | Purpose |
|---|---|---|---|
check_us_english_text | hunspell en_US + British form detection (local) | No | Check text for typos and British spelling or vocabulary, with US replacements |
check_us_english_b1_text | hunspell en_US -m + rule layers (local) | No | B1 plain-language proxies: readability (Flesch Reading Ease, Flesch-Kincaid, ARI), sentence/paragraph length, passive voice, jargon, wordy phrases, hidden verbs, filler, idioms, nominalization density, "you" address |
validate_us_english_word | hunspell en_US + British form detection (local) | No | Check a single English word for correct US English spelling and suggest replacements |
check_dutch_text | OpenTaal/hunspell + selected spelling rules (local) | No | Check words and selected compounds, hyphens, and calendar capitalization; return suggestions, positions, rule IDs, explanations, confidence, and source links |
check_dutch_b1_text | OpenTaal/hunspell -m + rule layers (local) | No | B1 simplicity proxies: readability (Flesch-Douma, ARI, LIX, Brouwer Leesindex), sentence/paragraph length, passive voice, officialese jargon, filler, idioms, rare-word flags from a Zipf frequency table with plain-synonym suggestions (Open Dutch WordNet), nominalization density, je/u voice |
validate_dutch_word | OpenTaal/hunspell (local) | No | Check a single Dutch word locally for correct spelling, with suggestions |
get_dutch_word_details | woordenlijst.org (MolexServe) | Yes | Retrieve lemma details: part of speech, pronunciation, hyphenation, paradigm, and diminutive forms |
The local tools use bundled hunspell dictionaries: OpenTaal for Dutch (v2.20.23)
and SCOWL/LibreOffice en_US for US English. The detector for British forms
catches terms that hunspell does not flag (whilst, towards, bespoke,
webshop, sole trader, findability, etc.), including categories from the
Straffe Sites US English guidelines, and provides a US replacement for each match.
get_dutch_word_details queries the internal XML service at woordenlijst.org
(INT/Taalunie). This unofficial integration uses caching and rate limiting
(1.2 seconds between calls); use it only for individual important words.
Tool descriptions and status messages are in US English; Dutch words and
upstream dictionary labels retain their original language.
Dutch spelling rules
check_dutch_text also checks selected patterns that a word dictionary misses:
btw nummer → btw-nummer, keuken tafel → keukentafel, auto ongeluk →
auto-ongeluk, and 3D printer → 3D-printer. Calendar cues such as op Maandag
and in Januari produce medium-confidence suggestions for lowercase spelling.
Each rule finding includes its ID, explanation, original source positions,
confidence, and a precise Team Taaladvies source link.
The rule layer protects code, marked headings, quotations, and other selected
non-prose spans. Pass optional protected_terms for exact case-sensitive names
or unmarked titles; horizontal whitespace may vary. This protection does not
whitelist Hunspell dictionary words.
Accepted variants such as rode kool / rodekool, optional clarity hyphens, and
meaning-dependent word groups are not automatically joined or rewritten.
This is a small, local spelling layer, not a general grammar checker. See the rule catalog, exceptions, limits, and attribution. B1 readability remains a separate check.
B1 simplicity checks
Both B1 tools report deterministic proxies with the same structure: a readability block, passive voice, rule hits with plain alternatives and positions in the source text, nominalization density, and a reader-address check. Thresholds are tool parameters because conventions differ by sector.
Dutch
check_dutch_b1_text reports deterministic B1 proxies, not a validated B1
verdict: "B1" applied to texts is an editorial convention (Rijksoverheid
guideline: write new material at B1, aiming for A2), not an empirically
validated text norm. What the tool measures:
- Readability: Flesch-Douma reading ease (NL-adapted Flesch; practical B1 band ~60-70), ARI as a second, language-neutral indication, LIX (Björnsson 1968; Swedish scale, not validated for Dutch — compounds inflate it), and Brouwer Leesindex A (1963 Dutch Flesch recalibration that weighs sentence length twice as heavily as Flesch-Douma). Formulas are suppressed below 50 words / 3 sentences (short-sample guard; the guard is intentional, see the evaluation baseline).
- Word frequency: rare-word flags from a bundled Zipf table of the top
50,000 Dutch word forms (derived from FrequencyWords/OpenSubtitles2018 —
spoken-language bias, documented in
assets/frequency/README.md). Words the table knows but scores rare (default below Zipf 3, configurable viarare_word_zipf_max) are flagged with value, count, and position. Unknown words are never flagged: Dutch compounds and proper names dominate that set. When Open Dutch WordNet lists a clearly more frequent single-word synonym (at least half a Zipf point), the flag includes concrete suggestions ("staken → try: ophouden, stoppen"); suggestions are review hints, never automatic replacements. - Sentence and paragraph length against configurable thresholds (defaults: warn >15, flag >20 words per sentence; 150 words per paragraph, matching common editorial guidelines).
- Passive voice:
hunspell -mmorphology batch plus an auxiliary window (wordt/worden/werd + past participle = high confidence; is/zijn + past participle = low-confidence zijn-passive; "door" agent noted). These are review flags: context-free morphology cannot tell a genuine passive from a copula construction, so editors judge each hit. - Officialese jargon with plain alternatives (betreffende → over, thans → nu, in het kader van → voor...), filler words, and idioms to avoid for NT2 readers, each with the match position in the source text.
- Nominalization density (-ing/-tie/-heid/-iteit words per 100 words) and je/u voice consistency (default je, configurable to u).
All checks are local and offline: the same bundled OpenTaal assets and hunspell binary as the spelling tools, plus pure TypeScript rule layers in the spirit of the British-form detector.
The Dutch B1 proxies are grounded in an evaluation baseline: the bundled
City of Amsterdam complex–simple corpus (1,311 sentence pairs, EUPL-1.2,
evaluation-only — nothing from it runs at check time) with a harness that
measures how much more often each proxy flags the complex side of a pair
(tests/eval/b1-amsterdam.ts, committed baseline in
tests/eval/baseline-v1.json, protocol in docs/b1-eval.md). On that set,
passive voice separates +11.8 percentage points and sentence length +10.4;
the rare-word signal separates +3.8; nominalization density and the
long-word share barely separate, and the readability formulas have 0%
coverage on single sentences because of the short-sample guard.
US English
check_us_english_b1_text applies the same approach to US English, with
English formulas, thresholds, and rule sources:
- Readability: Flesch Reading Ease (practical B1 band 60-70, "plain English" in Flesch's table), Flesch-Kincaid grade level (default target 9 or lower), and ARI. A zero-dependency syllable counter agrees with the CMU Pronouncing Dictionary on about 94% of the 10,000 most common US English words. The same short-sample guard applies.
- Sentence and paragraph length: defaults warn above 20 and flag above 25 words per sentence (plainlanguage.gov average and the GOV.UK limit), and the longest flagged sentences are listed with positions.
- Passive voice:
hunspell -mwith the bundled en_US dictionary marks regular -ed forms (affix flag D); a closed list covers irregular participles (written, built, sold). Detects be-passives, get-passives, and pronoun contractions ("it's built"), notes a "by" agent, skips "used to", and marks adjectival uses ("is located") as low confidence. - Formal jargon and buzzwords (utilize → use, prior to → before, robust,
leverage), wordy phrases (in order to → to, due to the fact that →
because), hidden verbs (make a decision → decide, provide assistance →
help), filler words, and idioms, based on the Federal Plain Language
Guidelines and the GOV.UK "words to avoid" list. British spellings stay
in
check_us_english_text. - Nominalization density (-tion/-sion/-ment/-ance/-ence/-ity per 100 words, everyday nouns excluded) and reader address: third-person references to the reader ("users", "the customer") are reported when the expected address is "you" (default).
Checking a file: path, ranges, and hashes
The four check tools (check_dutch_text, check_dutch_b1_text,
check_us_english_text, check_us_english_b1_text) accept either text or
path. With path, the server reads the local UTF-8 file itself, so an agent
never has to retype a long document into a tool call. Every report includes an
Input: line with the SHA-256 of the exact text that was checked:
Input: file /abs/cv.md, range [0, 5401) of 5401 code points, 5401 code points checked, input_sha256 9f2c…, file_sha256 9f2c…
| Argument | Meaning |
|---|---|
path | Absolute path of a UTF-8 text file (max 2 MiB; binary and invalid UTF-8 are refused). |
start, end | Optional half-open range [start, end) in Unicode code points of the file. |
expected_sha256 | Optional hex SHA-256 the checked text must have. On a mismatch nothing is checked and the tool returns an error. Works with text too. |
Hashes cover the exact UTF-8 bytes without normalization, so for a whole file
input_sha256 equals sha256sum <file>. With a range, finding positions are
relative to the range start. To limit which folders the server may read, set
LANGUAGE_MCP_ROOTS to one or more folders (separated by :, or ; on
Windows); without it, any file readable by the server process is allowed. A set
variable that names no folder refuses every path.
Spell checks stop after 20,000 words. When that happens the report starts with
INCOMPLETE: and the tool returns an error, so a partial check never counts as
full coverage: split the file into ranges instead.
This supports full-coverage review: a lead agent records the file hash or chunk
hashes in its manifest, delegates the checks per chunk, and verifies that each
report's input_sha256 matches.
Example: check US English copy
Call check_us_english_text with:
{
"text": "We build custom online stores with colorful designs and clear navigation."
}
The tool reports the number of words checked, unknown words, and British forms.
It provides suggestions and positions for flagged words. Review suggestions in
context: proper names and technical terms may be valid even when flagged.
For plain-language checks on the same copy, call check_us_english_b1_text
with the same text. For Dutch copy, use check_dutch_text and
check_dutch_b1_text instead.
Installation
Requirements: Node 20+ and hunspell on PATH (pacman -S hunspell).
npm install
npm run build # Compile TypeScript into dist/
Tests
npm test # Run node:test through tsx
Hermes configuration (stdio)
mcp_servers:
language:
command: node
args: [/absolute/path/to/language-mcp/dist/cli.js]
connect_timeout: 60
enabled: true
timeout: 120
License and attribution
Code: MIT.
Dictionary assets:
assets/nl.dic/assets/nl.aff: © OpenTaal — Revised BSD License and/or CC BY 3.0. Full terms are inassets/LICENSE.txt; source: https://github.com/OpenTaal/opentaal-hunspell. Reusing these assets requires attribution to OpenTaal.assets/en_US.dic/assets/en_US.aff: based on the SCOWL word list (Kevin Atkinson, LGPL), with an affix file from Geoff Kuenning's Ispell (BSD), distributed through LibreOffice/dictionaries. Seeassets/en_US-LICENSE.txtandassets/en_US-README.txt; source: https://github.com/LibreOffice/dictionaries/tree/master/en.
Data assets (each bundled with its own license and attribution file, kept separate from the MIT code):
assets/frequency/nl-zipf.tsv: Zipf frequencies for the top 50,000 Dutch word forms, derived from hermitdave/FrequencyWordsnl_50k.txt(OpenSubtitles2018). Content CC BY-SA 4.0; seeassets/frequency/README.md(includes the spoken-language bias caveat).assets/synonyms/nl-synonyms.tsv: Dutch synonym table (31,199 lemmas), derived from Open Dutch WordNet release 1.4 (VU Amsterdam, CLTL). CC BY-SA 4.0; seeassets/synonyms/LICENSEandassets/synonyms/README.md. When using this data, cite: Postma, Van Miltenburg, Segers, Schoen & Vossen (2016), "Open Dutch WordNet", Proceedings of the 8th Global WordNet Conference. CC BY-SA obligations apply to distributing the derived data, not to texts checked with the tool.assets/eval/amsterdam-complex-simple/: City of Amsterdam complex–simple sentence pairs (EUPL-1.2) and the Hobo 2022 lexical-simplification set, bundled as evaluation-only assets for the B1 harness; never used at runtime. See the README in that directory.
Evaluation baselines (tests/eval/) are committed artifacts of the runs
documented in docs/b1-eval.md.
get_dutch_word_details data: woordenlijst.org (Instituut voor de Nederlandse
Taal / Taalunie), accessed through its public web service without a separate
API agreement.