Content
# zhtw-mcp
A linguistic linter for Traditional Chinese (zh-TW) that enforces Taiwan Ministry of Education (MoE) standards on vocabulary, punctuation, and character shapes. It plugs into AI coding assistants through the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) and catches Mainland Chinese (zh-CN) regional drift before it reaches the user.
The tool enforces three official Taiwan standards:
- [Revised Handbook of Punctuation](https://language.moe.gov.tw/001/upload/files/site_content/m0001/hau/c2.htm) (《重訂標點符號手冊》修訂版) -- punctuation marks
- [Standard Form of National Characters](https://language.moe.gov.tw/001/Upload/files/SITE_CONTENT/M0001/STD/F4.HTML) (《國字標準字體》) -- character shapes
- Cross-strait vocabulary normalization, grounded in [OpenCC](https://github.com/BYVoid/OpenCC)'s TWPhrases/TWVariants datasets -- word choices
Over 1100 vocabulary rules and 15 casing rules are compiled into the binary. For ambiguous terms, the server asks the AI assistant it runs inside for help deciding -- no extra API keys required.
## Why this exists
### Modern Chinese is an inadequately standardized language
In the late Qing dynasty, scholars had to express Western concepts in a writing system with no native vocabulary for them. Whether coining new words or importing translations via Japanese (和製漢語), they assembled a literary system under enormous time pressure. Many translated terms were inconsistent, ambiguous, or contradictory. The Chinese-speaking world has lived with these deficiencies for over a century.
### Simplified Chinese made it worse
The PRC simplification effort reduced not just stroke counts but vocabulary precision. Terms that should vary by domain got flattened into single catch-all translations. Many PRC translations were coined hastily: if a term worked in one context, it spread uncritically to others.
### AI models amplify the problem
AI language models learn from web text where Simplified Chinese vastly outweighs Traditional Chinese (roughly 2.6:1 in [CC-100](https://data.statmt.org/cc-100/)). Major datasets like [CulturaX](https://huggingface.co/datasets/uonlp/CulturaX) do not even track Traditional Chinese separately. A [FAccT 2025 study](https://arxiv.org/abs/2505.22645) confirmed that most models favor zh-CN terminology when asked to write zh-TW. The output looks plausible but is not how people in Taiwan actually write.
This goes beyond character conversion. The same word often means different things across the strait:
| English | zh-CN | zh-TW | Why it matters |
|---------|-------|-------|----------------|
| concurrency | 並發 | 並行 | In zh-CN, 並行 means "parallel" -- a different concept entirely |
| parallel | 並行 | 平行 | zh-CN 並行 = "parallel"; in Taiwan, 並行 = "concurrent" |
| process (OS) | 進程 | 行程 | 進程 in Taiwan means "progress," not an OS process |
| file / document | 文件 / 文檔 | 檔案 / 文件 | 文件 in China = "file"; in Taiwan = "document" |
| render | 渲染 | 算繪 | 渲染 in Taiwan = "exaggerate" (a painting technique) |
| traverse | 遍歷 | 走訪 | 遍歷 in Taiwan is reserved for Ergodic theory (遍歷理論) |
### What this project does
Automatically check and correct zh-TW text produced by AI, catching cross-strait terminology leaks:
- Half-width punctuation (`,` `.` `:`) that should be full-width (`,` `。` `:`)
- Mainland-style `""` curly quotes replaced with Taiwan-style `「」` corner brackets
- Missing or extra CJK-Latin/digit spacing
- Mainland vocabulary -- 軟件→軟體, 內存→記憶體, 默認→預設, etc.
- Non-standard character variants -- 裏→裡, 着→著 per MoE standard forms
- Politically colored terms -- 祖國, 內地
- Casing -- JavaScript, GitHub, macOS
These standards are enforced through two profiles on the strictness axis, plus orthogonal capability flags:
| Profile | Purpose |
|---------|---------|
| `base` | Cross-strait vocabulary, punctuation, casing, grammar, politically colored terms |
| `strict` | Full MoE enforcement: character variants (裏→裡), grammar (臺/台), all punctuation |
| Flag | Purpose |
|------|---------|
| `relaxed` | Relaxed for software UI: disables colon/dunhao enforcement and grammar checks; uses en-dash for ranges |
| `detect_ai` | AI writing review: filler phrase detection, semantic safety words, copula/passive voice checks, density-based pattern detection |
Profiles control how strict the zh-TW norm enforcement is. Flags are orthogonal -- `detect_ai` works with either profile, `relaxed` can combine with `strict` if you want variant normalization but lenient punctuation.
See [docs/rules.md](docs/rules.md) for the full rule reference.
## Naming convention: cn and tw
This project follows [BCP 47](https://www.rfc-editor.org/info/bcp47). The region subtag comes from [ISO 3166-1 alpha-2](https://www.iso.org/iso-3166-country-codes.html), where "region" can denote a sovereign state, territory, or economic area -- not necessarily a "country."
- `zh-CN`: Chinese as written in the CN region (Simplified)
- `zh-TW`: Chinese as written in the TW region (Traditional)
Throughout the codebase, `cn` and `tw` denote regional writing conventions, not a political statement.
## Getting started
### Pre-built binaries
Every successful push to `main` refreshes the rolling [`latest`](https://github.com/sysprog21/zhtw-mcp/releases/tag/latest) release, which is what GitHub reports as the latest release. No version tag is involved. Each archive holds the binary, `LICENSE`, and `README.md`, and `SHA256SUMS` ships next to them.
| Platform | Asset |
| --- | --- |
| Linux x86_64 (glibc 2.39 or newer) | `zhtw-mcp-x86_64-unknown-linux-gnu.tar.gz` |
| Linux arm64 (glibc 2.39 or newer) | `zhtw-mcp-aarch64-unknown-linux-gnu.tar.gz` |
| macOS arm64 | `zhtw-mcp-aarch64-apple-darwin.tar.gz` |
| Windows x86_64 | `zhtw-mcp-x86_64-pc-windows-msvc.tar.gz` |
On any other platform, use Nix below or build from source.
The browser extension is packaged onto the same release as
`zhtw-mcp-extension.zip`. Unpack it and load it through `chrome://extensions`
with developer mode on.
#### macOS / Linux
```bash
base=https://github.com/sysprog21/zhtw-mcp/releases/download/latest
case "$(uname -sm)" in
"Darwin arm64") asset=zhtw-mcp-aarch64-apple-darwin.tar.gz ;;
"Linux x86_64") asset=zhtw-mcp-x86_64-unknown-linux-gnu.tar.gz ;;
"Linux aarch64") asset=zhtw-mcp-aarch64-unknown-linux-gnu.tar.gz ;;
*) asset=""; echo "no pre-built binary for $(uname -sm)" >&2 ;;
esac
[ -n "$asset" ] &&
curl -fsSLO "$base/$asset" -O "$base/SHA256SUMS" &&
shasum -a 256 --ignore-missing -c SHA256SUMS &&
tar -xzf "$asset" zhtw-mcp
```
On Linux without `shasum`, use `sha256sum --ignore-missing -c SHA256SUMS` instead.
#### Windows (PowerShell)
```powershell
$base = "https://github.com/sysprog21/zhtw-mcp/releases/download/latest"
$asset = "zhtw-mcp-x86_64-pc-windows-msvc.tar.gz"
irm "$base/$asset" -OutFile $asset
irm "$base/SHA256SUMS" -OutFile SHA256SUMS
$want = ((Select-String -Path SHA256SUMS -SimpleMatch $asset).Line -split '\s+')[0]
if ((Get-FileHash -Algorithm SHA256 $asset).Hash -ine $want) { throw "checksum mismatch" }
tar -xzf $asset zhtw-mcp.exe
```
Both snippets leave the binary in the current directory; move it somewhere on your `PATH` to run it by name.
### Nix
On any system with Nix and flakes enabled:
```bash
# builds and drops into a temporary shell with `zhtw-mcp` on `$PATH`
nix shell "github:sysprog21/zhtw-mcp"
# builds and runs zhtw-mcp (you can use this command to register with an MCP
# client as shown in the Installing section)
nix run "github:sysprog21/zhtw-mcp"
```
> **Note:** The first run compiles the project from source, which can take
several minutes. Subsequent runs reuse the Nix store cache and start instantly.
To speed up the initial build, run `nix build --cores 0 "github:sysprog21/zhtw-mcp"`
first. And `--cores 0` tells Nix to use all available CPU cores.
### Building from source
Requires stable Rust 1.91+.
```bash
make
```
The binary is at `target/release/zhtw-mcp`.
### Installing
The quickest way to build, install to `$XDG_BIN_HOME` (or `~/.local/bin`),
stop older server processes, and register with detected MCP clients
(Claude Code and/or Codex):
```bash
make install # build release, install binary, register detected MCP clients
make uninstall # remove binary and detected MCP registrations
make status # check binary freshness, process, and registration state
```
For manual setup or other MCP clients:
```bash
# Claude Code
claude mcp add zhtw-mcp -- /path/to/zhtw-mcp
# Codex CLI
codex mcp add zhtw -- /path/to/zhtw-mcp
# OpenCode
opencode mcp add zhtw-mcp /path/to/zhtw-mcp
```
Other MCP clients may use `.mcp.json` in your project root:
```json
{
"mcpServers": {
"zhtw-mcp": {
"command": "/path/to/zhtw-mcp",
"args": []
}
}
}
```
Replace `/path/to/zhtw-mcp` with the actual binary path (e.g., `target/release/zhtw-mcp`).
### CLI quick start
```bash
zhtw-mcp lint README.md # lint a file
zhtw-mcp lint file.md --fix # auto-fix in place
zhtw-mcp lint file.md --fix --dry-run # preview fixes
zhtw-mcp lint file.md --telemetry # print stderr summary counters
zhtw-mcp cache clear # clear persistent judgment cache
```
See [docs/cli.md](docs/cli.md) for the full CLI reference and [docs/mcp.md](docs/mcp.md) for MCP tool/resource/prompt details.
### Common prompts
When running as an MCP server, you interact through natural language. The assistant translates your intent into `zhtw` tool calls:
| Intent | Say | Maps to | What happens |
|--------|-----|---------|--------------|
| Lint text | *"Check this paragraph for mainland terms"* | `zhtw({ "text": "..." })` | Returns issues with line/column, suggestions, and rule type |
| Auto-fix | *"Fix the zh-TW issues in this document"* | `zhtw({ "text": "...", "fix_mode": "lexical_safe" })` | Deterministic fixes applied; corrected text returned |
| Quality gate | *"Reject if more than 3 zh-TW errors"* | `zhtw({ "text": "...", "max_errors": 3 })` | `accepted: true/false` verdict based on error count |
| Strict MoE | *"Check this with strict MoE rules"* | `zhtw({ "text": "...", "profile": "strict" })` | Adds character variant (裏→裡) and full punctuation enforcement |
| UI strings | *"Lint this UI string, skip grammar"* | `zhtw({ "text": "...", "relaxed": true })` | Disables colon/dunhao/grammar enforcement; uses en-dash for ranges |
| AI writing review | *"Review this for AI writing artifacts"* | `zhtw({ "text": "...", "detect_ai": true })` | Flags filler phrases, semantic safety words, copula/passive overuse |
| Markdown-aware | *"Lint this markdown, skip code blocks"* | `zhtw({ "text": "...", "content_type": "markdown" })` | Fenced code, inline code, and HTML blocks excluded from scanning |
| Cost telemetry | *"Lint this and include telemetry"* | `zhtw({ "text": "...", "include_telemetry": true })` | Returns estimated token/caching metrics for the call |
Each `zhtw` call is stateless -- parameters like `profile` are per-call, not session state. Omitting `profile` defaults to `base`.
The server also exposes two read-only resources for assistants to consult: `zh-tw://style-guide/moe` (MoE standards) and `zh-tw://dictionary/ambiguous` (cross-strait term disambiguation). See [docs/mcp.md](docs/mcp.md) for the full prompt catalog.
## Further reading
- [docs/cli.md](docs/cli.md) -- full CLI reference, config files, CI/CD integration, S2T conversion
- [docs/mcp.md](docs/mcp.md) -- MCP tool parameters, resources, prompts, sampling, usage examples
- [docs/internals.md](docs/internals.md) -- processing pipeline, script detection, design decisions, testing
- [docs/rules.md](docs/rules.md) -- rule type reference, extending the ruleset, runtime overrides
## License
`zhtw-mcp` is available under a permissive MIT-style license.
Use of this source code is governed by a MIT license that can be found in the [LICENSE](LICENSE) file.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers
Time
A Model Context Protocol server for time and timezone conversions.