localization-mcp-server
v1.3.14
Published
MCP server for the localization system — controlled AI access to translations
Maintainers
Readme
localization-mcp-server
MCP server for the localization backend. Provides controlled AI access to translations: reading, editing, quality checks, sandbox workflow, project management, webhooks, and more.
Installation
npm install -g localization-mcp-serverConfiguration
| Variable | Required | Description |
|----------|----------|-------------|
| MCP_TOKEN | Yes | API token for backend authentication |
| BACKEND_URL | Yes | Base URL of the localization backend (e.g. http://79.76.35.167:8080) |
| ADMIN_UI_URL | No | Admin UI URL for links in tool responses |
| NODE_ENV | No | In non-production mode, loads .env from the package directory |
| AUDIT_LOG_PATH | No | Path for write-operation audit log (default: ./mcp-audit.log) |
Copy .env.example as a starting point.
Environment safety (mandatory)
Dynamic environment resolution is a core requirement, not optional.
The developer configuring this MCP server is responsible for wiring BACKEND_URL to the correct environment. The server does not auto-detect which environment it should target.
Rules:
BACKEND_URLmust always be set explicitly. If missing, the server logs a loud warning and refuses to start. Always point to the production backend.Never hardcode production URLs in MCP config that is also used during development. Use environment-specific
.envfiles or injectBACKEND_URLfrom your CI/runtime config.Development agents should work with sandbox. All MCP write tools target sandbox only — production is read-only. But
BACKEND_URLstill determines which server's sandbox you hit.If environment is unknown, fail safely. Do not silently guess production. The server will show
(NOT SET)in diagnostic output ifBACKEND_URLis missing, making misconfiguration visible.Each environment needs its own
MCP_TOKEN. Tokens are server-specific — a dev token won't work on production and vice versa.
Correct setup:
BACKEND_URL=http://79.76.35.167:8080
ADMIN_UI_URL=http://79.76.35.167:3010URL disambiguation for agents
| Purpose | URL | Notes |
|---------|-----|-------|
| Backend API (for all MCP/agent requests) | http://79.76.35.167:8080 | All tool calls go here |
| Admin UI (browser only, not for API calls) | http://79.76.35.167:3010 | Human-facing UI, do NOT send API requests here |
| Swagger docs | http://79.76.35.167:8080/api-docs | API reference |
Agents must:
- Use the configured
BACKEND_URLfor all API requests - Never send API requests to
ADMIN_UI_URL(port 3010) — it serves static HTML, not the API - Never guess ports — use the exact URL from environment config
- If
BACKEND_URLis not configured, surface the error instead of improvising
Usage
Start the MCP server over stdio:
localization-mcp-serverRun the interactive setup helper:
localization-mcp-server setupClaude Code / Cursor integration
Add to your MCP config (.mcp.json or ~/.claude.json):
{
"mcpServers": {
"localization": {
"type": "stdio",
"command": "npx",
"args": ["-y", "localization-mcp-server"],
"env": {
"MCP_TOKEN": "<your-token>",
"BACKEND_URL": "http://79.76.35.167:8080"
}
}
}
}CLI registration (recommended)
claude mcp add -s user localization \
-e MCP_TOKEN=your-token \
-e BACKEND_URL=http://79.76.35.167:8080 \
-- npx -y localization-mcp-serverWarning: Always register globally with
-s user. Per-project registration (without-s user) creates a.mcp.jsonin the current directory that overrides the global config. This causes 401 errors in every other project because they pick up the override without the correct token. If you see unexpected 401s, run:claude mcp remove localization && claude mcp add -s user localization -e MCP_TOKEN=<token> -e BACKEND_URL=<url> -- npx -y localization-mcp-server
Available Tools (38 total)
Discovery
| Tool | Description | Parameters |
|------|-------------|------------|
| assess_integration_state | Full integration assessment with agent guide, URL patterns, and project details | projectSlug? |
| list_projects | List all projects with sandbox state | — |
| get_project_details | Namespaces, locales, sandbox status for a project | projectSlug |
Project Structure
Projects, namespaces, locales, and webhooks — structural setup, not translation content.
| Tool | Description | Key Parameters |
|------|-------------|----------------|
| create_project | Create a new project | slug, name? |
| create_namespace | Create a namespace (requires reason justification) | projectSlug, namespace, reason |
| rename_namespace | Rename an existing namespace | projectSlug, currentSlug, newSlug |
| delete_namespace | Delete namespace with all keys (requires confirmed: true) | projectSlug, namespace, confirmed |
| create_locale | Add a locale to a project | projectSlug, code, isDefault? |
| update_locale | Update locale aliases | projectSlug, code, aliases |
| delete_locale | Remove locale and all its values (requires confirmed: true) | projectSlug, code, confirmed |
| list_webhooks | List webhook configs for a project | projectSlug |
| create_webhook | Register outgoing webhook | projectSlug, url, events, description?, secret? |
| update_webhook | Update webhook config | projectSlug, webhookId, url?, events?, enabled? |
| delete_webhook | Delete a webhook | projectSlug, webhookId |
| list_webhook_events | List supported event types | — |
Translations — Read
| Tool | Description | Key Parameters |
|------|-------------|----------------|
| list_translations | Browse entries with search, pagination, quality/locale filters | projectSlug, namespace, env, search?, missingLocale?, qualityLevel? |
| get_translations_needing_attention | Get non-green translations that need quality improvement | projectSlug, namespace, qualityLevels?, includeUnchecked? |
| export_namespace | Full JSON export per locale | projectSlug, namespace, env?, locale? |
| get_namespace_coverage | Per-locale fill percentage with missing key samples | projectSlug, namespace, env? |
| compare_local_vs_server | Diff local JSON against server entries | projectSlug, namespace, translations?, filePath?, env? |
| analyze_entries | Preflight analysis of planned keys — duplicates, conflicts, reuse signals | projectSlug, namespace, entries, sourceLocale? |
| check_keys_exist | Check if a list of keys exist in a namespace | projectSlug, namespace, keys, env? |
Translations — Write
All writes go to the sandbox. Production is read-only from MCP.
| Tool | Description | Key Parameters |
|------|-------------|----------------|
| set_translation | Create or update one key (upsert). Only passed locales are changed. | projectSlug, namespace, key, values, context? |
| delete_translation | Soft delete a key in sandbox | projectSlug, namespace, key |
| rename_key | Rename a key preserving all values | projectSlug, namespace, oldKey, newKey |
| mark_expected | Mark a locale translation as manually accepted (suppresses quality warnings) | projectSlug, namespace, key, locale |
| unmark_expected | Remove manual acceptance from a locale translation | projectSlug, namespace, key, locale |
| revert_sandbox_entry | Revert a single key to its production value | projectSlug, namespace, key |
Translations — Bulk Write
| Tool | Description | Key Parameters |
|------|-------------|----------------|
| bulk_set_locale | Bulk upsert many keys for a single locale | projectSlug, namespace, locale, entries, dryRun? |
| bulk_import | Multi-locale bulk upsert from inline JSON or file path | projectSlug, namespace, translations?, filePath?, contexts?, dryRun? |
AI
| Tool | Description | Key Parameters |
|------|-------------|----------------|
| ai_translate | Translate one text to project locales via Gemini (does not save) | projectSlug, text, context?, targetLocales? |
| bulk_ai_translate | Translate N keys in one call — batches of 10 (does not save) | projectSlug, entries, targetLocales? |
| bulk_translate_and_save | Translate N keys, save to sandbox, and quality-check in one step | projectSlug, namespace, entries, targetLocales?, skipQuality? |
| ai_quality_check | Stateless multi-locale quality check — score, level, comment (not persisted) | projectSlug, source, translations: {locale: string}, context? |
| check_entry_quality | Quality check all locales of a key and persist results to DB | projectSlug, namespace, key |
| get_ai_usage | AI token usage statistics for a project | projectSlug |
Sandbox & Promotion
| Tool | Description | Parameters |
|------|-------------|------------|
| reset_sandbox | Discard all sandbox changes (requires confirmed: true) | projectSlug, confirmed |
| get_translation_diff | Full diff between sandbox and production | projectSlug, namespace?, locale?, statusFilter? |
| validate_translations | Analyze diff for empty values, partial translations, deletions | projectSlug, namespace? |
| preview_push_to_production | Read-only preview of what would change on promote | projectSlug |
| list_snapshots | List available production snapshots | projectSlug |
Note: Promoting sandbox to production is intentionally not available via MCP. Use the Admin UI.
Workflows
Starting a session
Before writing to any project:
1. list_projects → confirm project exists
2. get_project_details(projectSlug) → get namespaces, locales, sandbox state
→ sandbox auto-initializes on project creation; use reset_sandbox if re-sync needed
3. [start writing]Adding translations for a new locale
1. get_project_details("my-app") → confirm locale doesn't exist
2. create_locale("my-app", "nb-NO") → add the locale
3. get_namespace_coverage("my-app", "common") → see fill gaps
4. list_translations("my-app", "common", { missingLocale: "nb-NO" })
5. bulk_set_locale("my-app", "common", "nb-NO", entries)
6. validate_translations("my-app") → check for issues
7. preview_push_to_production("my-app") → review before promotingImporting translations from a local file
1. compare_local_vs_server("my-app", "common", { filePath: "/path/to/en.json" })
→ see what's new, matching, or conflicting
2. bulk_import("my-app", "common", { filePath: "/path/to/translations.json", dryRun: true })
→ preview what would be imported
3. bulk_import("my-app", "common", { filePath: "/path/to/translations.json" })
→ actually import
4. get_translation_diff("my-app") → verify changesBulk translate and save new keys
1. get_project_details("my-app") → confirm namespace + locales
→ sandbox auto-initializes on project creation; use reset_sandbox to re-sync if needed
2. bulk_translate_and_save("my-app", "common", entries)
→ translates, saves to sandbox, and returns inline quality scores
3. Fix any red translations via set_translation
4. preview_push_to_production("my-app") → review before promotingQuality improvement workflow
1. get_translations_needing_attention("my-app", "common")
→ find yellow/red translations
2. For each problematic key:
- Review context and values
- set_translation to fix the value, OR
- mark_expected if the translation is actually correct
3. check_entry_quality("my-app", "common", "button.save")
→ re-check and persist updated quality scoresIntegrating a new project (from a consumer app)
1. assess_integration_state("my-app") → get full diagnosis
→ returns: URL patterns, classification guide, project state
2. Follow the guide for your integration state (S1-S6)
3. Update local app config to point to the backendSafety Rules
Destructive operations (require explicit user instruction)
| Tool | Risk |
|------|------|
| reset_sandbox | Wipes all pending sandbox changes |
| delete_translation | Removes a key and all its values |
| delete_locale | Removes a locale across ALL namespaces |
| delete_namespace | Removes a namespace and ALL its keys |
| bulk_import with overwrite | Can silently overwrite existing translations |
Locale code rules
- Always use exact BCP 47 codes from
get_project_details - Never guess or remap:
nb-NOis notno,da-DKis notda - Invalid codes are rejected with a clear error
Namespace policy
- Always check existing namespaces before creating a new one
create_namespacerequires areasonexplaining why no existing namespace fits- If the target namespace is ambiguous, ask the user — don't guess
Key naming
Keys must match: /^[a-zA-Z0-9._-]+$/
Valid: button.save, error-message, form_field
Invalid: button/save, button save, button:save
Production Model
- All MCP writes go to sandbox only
- Production is read-only from MCP
- Promoting to production is a manual step via Admin UI
preview_push_to_productionshows what would change but does NOT push
Published Files
The package publishes dist/, flows/, .env.example, and this README.
