phraseflip-mcp
A privacy-first MCP Apps server that renders an interactive phrase-flipped
translation card inline in an AI chat (Claude Desktop, etc.). A subset of
sentences is translated into a target language entirely on your machine using
OPUS-MT (int8 ONNX) via transformers.js
on onnxruntime-node. No text ever leaves your device.
The card renders a clean phrase-flipped paragraph with hover-to-reveal for
English originals. Only a minimal acknowledgment is returned to the model
context (e.g. Rendered flipped view: 4/12 sentences, 25% → Spanish.).
Features
phrase_flip(text, target?, intensity?) — split into sentences, translate all
locally with OPUS-MT, return an interactive ui:// card embedding both the
original and translated text. Defaults to 25% intensity when omitted.
set_preferences({ auto?, defaultTarget? }) — persist an auto-flip toggle and a
default target language to a small JSON file in your OS config dir.
- Lazy, per-pair model download with progress surfaced as MCP progress
notifications. Models cache to your OS cache dir; subsequent calls are instant.
- 10 languages (+ experimental Arabic): Spanish, French, Greek, German, Italian,
Portuguese, Japanese, Korean, Chinese, Dutch.
Install / run
No global install needed — run via npx:
npx phraseflip-mcp
The first translation into a given language downloads that language's OPUS-MT
model (~75–300 MB) to your OS cache dir. onnxruntime-node ships prebuilt
binaries, so there is no native build step.
Official MCP Registry name: io.github.oneill9/phraseflip-mcp.
Claude Desktop config
Add to claude_desktop_config.json
(macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"phraseflip": {
"command": "npx",
"args": ["-y", "phraseflip-mcp"]
}
}
}
Then ask Claude something like: "phrase-flip this into Spanish: …" and an
interactive card appears in the conversation.
Preferences
Stored as JSON in your OS config dir (path is printed when you call
set_preferences). On macOS: ~/Library/Preferences/phraseflip-mcp/preferences.json.
auto | boolean | true | If false, phrase_flip returns a graceful opt-in card ("Flip anyway" / "Re-enable") instead of auto-rendering. |
defaultTarget | string | es | Default target language code when none is given. |
Development
npm install
npm run build
npm test
npm start
Project layout (src/):
index.ts — MCP server (stdio), registers both tools.
engine.ts — transformers.js OPUS-MT pipeline, instance + disk cache, progress.
widget.ts — builds the ui:// HTML card (hover + postMessage), inlines content.css.
sentences.ts — sentence splitter (ported from the extension's tokenise()).
flip.ts — flip order + intensity levels (ported from the demo).
languages.ts — language table + per-language model specs + resolveModel() (the one place to apply coverage fixes).
prefs.ts — JSON preferences store.
Translation engine coverage
Each language declares a model spec in languages.ts with one of three engine
kinds. The engine dispatches on kind automatically.
opus-direct | es, fr, de, it, zh, nl, ja, ar | direct Xenova/opus-mt-en-XX Marian pair |
nllb | el, pt, ko | Xenova/nllb-200-distilled-600M with src_lang/tgt_lang (~600 MB) |
opus-multi | (none active) | prepend a >>xx<< target token (wired but not currently used) |
Notes:
- Japanese uses
Xenova/opus-mt-en-jap (not en-ja).
- Greek and Portuguese have no direct Xenova OPUS-MT pair. The spike's
multi-target fallback (
en-mul / en-ROMANCE + >>xx<< prefix) does not
work through transformers.js 4.x: the MarianTokenizer language-token stripping
is not applied, so >>xx<< is sub-word-split, the model ignores it and the
token leaks into the output. en-mul also doesn't list Greek as a target. Both
are therefore routed through NLLB-200 (Greek ell_Grek, Portuguese
por_Latn) — the same reliable code-based path as Korean. The opus-multi
kind remains wired in case upstream tokenizer handling is fixed.
- NLLB-200 is a single ~600 MB model shared by el/pt/ko (downloaded once,
cached). It was not downloaded during validation per the spike's guidance; the
call uses the documented
pipe(text, { src_lang, tgt_lang }) signature.
Notes / TODO
- The interactive card uses the
@mcp-ui/server MCP Apps adapter. Button
callbacks in the disabled state post type: 'tool' UIActionResult
messages to the host, which the host turns into a tools/call. Verify this
round-trip in your target host.