Skip to main content
Glama
Murzav

paraglide-i18n-mcp

by Murzav
README.md
# paraglide-i18n-mcp

MCP server for [Paraglide JS](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) / [inlang](https://inlang.com) i18n catalogs. Lets an MCP-aware client (Claude Code, Codex, etc.) manage translations, variants, and plural forms without parsing catalog files itself.

This is a pre-release scaffold at v0.1.0. The package is not on npm yet; install from source. Sister project of [`xcstrings-mcp`](https://github.com/Murzav/xcstrings-mcp), which does the same job for Apple `.xcstrings`.

## What it does

Exposes 24 tools and 4 MCP resources over stdio. The client calls them, the server does the catalog I/O.

The interesting part is what it refuses to do. Translation work belongs to the LLM. The server does not generate text. It validates placeholders, plural completeness against CLDR, markup balance, schema integrity. Writes go through an atomic protocol: temp file, fsync, rename, fsync parent, per-file mutex. Path traversal, OOM via oversized inputs, and ReDoS regexes all get rejected at the boundary, not at the handler.

Tools group by area:

| Area              | Tools                                                                                  |
| ----------------- | -------------------------------------------------------------------------------------- |
| Project lifecycle | `discover_files`, `parse_paraglide`, `list_files`, `health_check`                      |
| Translation       | `get_untranslated`, `get_coverage`, `validate_translations`, `submit_translations`     |
| Keys              | `add_keys`, `delete_keys`, `rename_key`, `search_keys`, `get_key`, `get_context`       |
| Variants, plurals | `get_variants`, `add_variant`, `add_variant_arm`, `get_plurals`, `submit_plural_forms` |
| Locale            | `list_locales`, `add_locale`, `remove_locale`                                          |
| Diff, build       | `get_diff`, `compile_messages`                                                         |

Every tool description carries a structured shape (`Use after`, `Errors`, `Capabilities`, `Response`, `Limits`, `Pair with`) so the LLM knows when to call it and what to expect back without round-tripping through the input schema.

## Install (local, pre-npm)

Clone and build:

```bash
git clone https://github.com/Murzav/paraglide-i18n-mcp.git
cd paraglide-i18n-mcp
bun install --frozen-lockfile
bun run build
```

This produces `dist/bin/cli.mjs` (ESM). Runs on Node ≥22.17 or Bun ≥1.3.

If you don't have bun installed, get it from [bun.sh](https://bun.sh) (one-line installer) or use npm equivalents at your own risk (the lockfile is `bun.lock`, so install order may differ).

## Wire it into your MCP client

### Claude Code

Add to your MCP settings (the file path varies by platform; check Claude Code docs):

```json
{
  "mcpServers": {
    "paraglide": {
      "command": "node",
      "args": ["/abs/path/to/paraglide-i18n-mcp/dist/bin/cli.mjs"]
    }
  }
}
```

### Codex CLI

```toml
# ~/.codex/config.toml
[[mcp_servers]]
name = "paraglide"
command = "node"
args = ["/abs/path/to/paraglide-i18n-mcp/dist/bin/cli.mjs"]
```

### Any MCP client

The server speaks JSON-RPC over stdio. Spawn `node dist/bin/cli.mjs` and hold stdin/stdout open. The CLI takes no runtime flags; configure it via the env vars below.

To verify the binary works at all:

```bash
node dist/bin/cli.mjs --version
node dist/bin/cli.mjs --help
```

## Configuration

Environment variables:

| Var                            | Default  | What it does                                                                                                                                                         |
| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PARAGLIDE_MCP_LOG_LEVEL`      | `info`   | `trace` / `debug` / `info` / `warn` / `error` / `silent`. All logs go to stderr (stdout is reserved for JSON-RPC).                                                   |
| `PARAGLIDE_MCP_LOG_FORMAT`     | `pretty` | `pretty` for terminals, `json` for log aggregators.                                                                                                                  |
| `PARAGLIDE_MCP_WORKSPACE_ROOT` | `$PWD`   | `discover_files` refuses any root outside this directory. Defaults to the server's cwd.                                                                              |
| `PARAGLIDE_MCP_INCLUDE_STACK`  | unset    | Set to `1` to include stack frames in JSON-RPC error payloads. Off by default so absolute paths don't leak to clients.                                               |
| `PARAGLIDE_MCP_IDLE_TTL_MS`    | `300000` | After this many ms idle, a cached project is evicted (handle closed, ~200 MB RSS released). Next access re-parses from disk. Set to `0` to disable the idle sweeper. |

## Quick workflow

A typical session looks like:

1. `discover_files` to find inlang projects under a directory, or skip straight to step 2 if you already know the path.
2. `parse_paraglide` to load and cache it.
3. `get_untranslated` (or `get_coverage`) to see what's missing.
4. `submit_translations` for flat bundles, or `add_variant_arm` / `submit_plural_forms` for variant bundles, to write.
5. `validate_translations` to catch placeholder, plural, or markup issues before they reach production.
6. `compile_messages` to run paraglide-js codegen.

`get_diff` between snapshots tells you what changed since the last `parse_paraglide`. `health_check` reports server version, installed `@inlang/sdk` version, and cached projects. Use it as a smoke test when wiring the server into a new client.

## Status & quality

- 3 ultrareview rounds across the codebase. 50 unique findings, 49 fixed, 1 deferred with documented rationale.
- 610 tests across 92 files. Coverage: statements 97%, branches 90%, functions 99%, lines 97%.
- v0.1.0 supports `plugin-message-format` v4 only. Other plugins resolve to a read-only stub. Full multi-plugin write support lands in v0.1.x.
- `@inlang/sdk` pinned to exact `2.9.3`. `health_check` warns on an installed-version mismatch.

See [`docs/spec.md`](./docs/spec.md) for design decisions, [`CHANGELOG.md`](./CHANGELOG.md) for release notes.

## Development

```bash
bun run test          # vitest one-shot
bun run test:watch    # vitest watch
bun run coverage      # v8 coverage report
bun run lint          # oxlint
bun run format:check  # oxfmt
bun run typecheck     # tsc --noEmit
bun run dev           # run the server with --watch
```

The pre-commit hook runs lint + format + typecheck on staged files. Don't bypass it.

## License

Dual-licensed under either of:

- Apache License 2.0 ([LICENSE-APACHE](./LICENSE-APACHE))
- MIT License ([LICENSE-MIT](./LICENSE-MIT))

at your option.