localization-mcp-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/AGENT_GUIDE.md +556 -0
- package/AUDIT_REPORT.md +244 -0
- package/PROJECT_OVERVIEW.md +140 -0
- package/dist/api-client.d.ts +11 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +67 -0
- package/dist/api-client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/locale-aliases.d.ts +46 -0
- package/dist/locale-aliases.d.ts.map +1 -0
- package/dist/locale-aliases.js +71 -0
- package/dist/locale-aliases.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +19 -0
- package/dist/logger.js.map +1 -0
- package/dist/permissions.d.ts +127 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +75 -0
- package/dist/permissions.js.map +1 -0
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +129 -0
- package/dist/prompts.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +25 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/diff.d.ts +3 -0
- package/dist/tools/diff.d.ts.map +1 -0
- package/dist/tools/diff.js +166 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/environment.d.ts +3 -0
- package/dist/tools/environment.d.ts.map +1 -0
- package/dist/tools/environment.js +113 -0
- package/dist/tools/environment.js.map +1 -0
- package/dist/tools/production.d.ts +3 -0
- package/dist/tools/production.d.ts.map +1 -0
- package/dist/tools/production.js +145 -0
- package/dist/tools/production.js.map +1 -0
- package/dist/tools/project-management.d.ts +3 -0
- package/dist/tools/project-management.d.ts.map +1 -0
- package/dist/tools/project-management.js +416 -0
- package/dist/tools/project-management.js.map +1 -0
- package/dist/tools/sandbox-writes.d.ts +3 -0
- package/dist/tools/sandbox-writes.d.ts.map +1 -0
- package/dist/tools/sandbox-writes.js +260 -0
- package/dist/tools/sandbox-writes.js.map +1 -0
- package/dist/tools/snapshots.d.ts +3 -0
- package/dist/tools/snapshots.d.ts.map +1 -0
- package/dist/tools/snapshots.js +50 -0
- package/dist/tools/snapshots.js.map +1 -0
- package/dist/tools/translations.d.ts +3 -0
- package/dist/tools/translations.d.ts.map +1 -0
- package/dist/tools/translations.js +135 -0
- package/dist/tools/translations.js.map +1 -0
- package/migrate-expenses.cjs +120 -0
- package/package.json +26 -0
- package/src/api-client.ts +68 -0
- package/src/index.ts +29 -0
- package/src/logger.ts +31 -0
- package/src/permissions.ts +89 -0
- package/src/prompts.ts +159 -0
- package/src/server.ts +27 -0
- package/src/tools/diff.ts +225 -0
- package/src/tools/environment.ts +175 -0
- package/src/tools/production.ts +196 -0
- package/src/tools/project-management.ts +517 -0
- package/src/tools/sandbox-writes.ts +321 -0
- package/src/tools/snapshots.ts +68 -0
- package/src/tools/translations.ts +167 -0
- package/tsconfig.json +17 -0
package/.env.example
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# URL of the localization backend API
|
|
2
|
+
BACKEND_URL=http://localhost:3000
|
|
3
|
+
|
|
4
|
+
# Service account credentials — MCP server logs in automatically and refreshes the token
|
|
5
|
+
BACKEND_EMAIL=admin@test.com
|
|
6
|
+
BACKEND_PASSWORD=Admin123!
|
|
7
|
+
|
|
8
|
+
# Path for the write-operation audit log (optional, defaults to ./mcp-audit.log)
|
|
9
|
+
# AUDIT_LOG_PATH=/var/log/localization-mcp-audit.log
|
package/AGENT_GUIDE.md
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
# Localization MCP Server — Agent Guide
|
|
2
|
+
|
|
3
|
+
> This document is written for AI agents. Read it fully before making any changes.
|
|
4
|
+
> See also: [PROJECT_OVERVIEW.md](PROJECT_OVERVIEW.md) — env rules, URL mapping, deployment behavior
|
|
5
|
+
> See also: [AUDIT_REPORT.md](AUDIT_REPORT.md) — known data quality issues in current translation data
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## System overview
|
|
10
|
+
|
|
11
|
+
This MCP server wraps the Localization backend API. It lets agents manage translation keys through a **sandbox/production** workflow. All writes go to sandbox. Production is read-only. Nothing reaches production until a human promotes the sandbox via the Admin UI.
|
|
12
|
+
|
|
13
|
+
**Backend URL (local):** `http://localhost:8080`
|
|
14
|
+
**Admin UI (local):** `http://localhost:3001`
|
|
15
|
+
**Auth:** `MCP_TOKEN` in `mcp-server/.env` (an `lmcp_` prefixed MCP token)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Environment rules
|
|
20
|
+
|
|
21
|
+
| Operation | Sandbox | Production |
|
|
22
|
+
|-----------|---------|------------|
|
|
23
|
+
| Read keys | ✅ | ✅ |
|
|
24
|
+
| Create key | ✅ | ❌ not possible |
|
|
25
|
+
| Update key | ✅ | ❌ not possible |
|
|
26
|
+
| Delete key | ✅ (soft delete) | ❌ not possible |
|
|
27
|
+
| Promote changes | ❌ manual only | via Admin UI |
|
|
28
|
+
|
|
29
|
+
**There is no MCP tool that writes to production.** This is architectural, not a permission check.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Locale codes — critical
|
|
34
|
+
|
|
35
|
+
Locale codes are **case-sensitive** and must exactly match what `get_project_details` returns. This is the only authoritative source.
|
|
36
|
+
|
|
37
|
+
**Always call `get_project_details` before any write operation.** The response now includes:
|
|
38
|
+
- `code` — the exact string to use in all tool calls
|
|
39
|
+
- `isDefault` — which locale is the fallback
|
|
40
|
+
|
|
41
|
+
Example response for `travis`:
|
|
42
|
+
```json
|
|
43
|
+
"locales": [
|
|
44
|
+
{ "code": "en", "isDefault": true },
|
|
45
|
+
{ "code": "da-DK", "isDefault": false },
|
|
46
|
+
{ "code": "nb-NO", "isDefault": false },
|
|
47
|
+
{ "code": "sv", "isDefault": false },
|
|
48
|
+
{ "code": "uk", "isDefault": false }
|
|
49
|
+
]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Do not guess, remap, or normalize locale codes.** If your source data uses different codes (e.g. `no`, `da`), you must resolve the mapping before calling any write tool — using the project metadata as the source of truth, not assumptions.
|
|
53
|
+
|
|
54
|
+
If a locale code from your source data does not appear in `get_project_details`, that is a blocker: do not proceed until it is resolved (create the locale or correct the source data).
|
|
55
|
+
|
|
56
|
+
**Known API gap — no alias information:** The API does not expose locale aliases (e.g. that `nb-NO` is also known as `no`). This mapping must come from the source data context or explicit human input — it is not derivable from the API alone. If you encounter this ambiguity, report it as a blocker rather than guessing.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Namespace selection rules
|
|
61
|
+
|
|
62
|
+
**Default: reuse, do not create.**
|
|
63
|
+
|
|
64
|
+
Before writing anything, resolve the target namespace from project metadata and request context. Creating a new namespace is a last resort, not a default action.
|
|
65
|
+
|
|
66
|
+
### Decision order
|
|
67
|
+
|
|
68
|
+
1. **Explicit namespace in the request** — use it directly, no questions.
|
|
69
|
+
2. **Clear namespace from context** — if the request clearly relates to an existing namespace (frontend → `frontend`, mobile → `mobile`, backoffice → `backoffice-translations`), use that namespace.
|
|
70
|
+
3. **Project uses one namespace for that area** — if there is only one obvious match, use it without asking.
|
|
71
|
+
4. **Multiple namespaces, target is ambiguous** — ask the user which namespace to use. Do not guess.
|
|
72
|
+
5. **No matching namespace exists** — ask the user whether to create one or use an existing namespace. Do not create silently.
|
|
73
|
+
|
|
74
|
+
### Rules
|
|
75
|
+
|
|
76
|
+
- Always call `get_project_details` first to see the current namespace list.
|
|
77
|
+
- Treat the existing namespace structure as intentional. If the project separates frontend, backend, and mobile into different namespaces, reuse that structure.
|
|
78
|
+
- Never create a namespace just because the request did not explicitly name one.
|
|
79
|
+
- If a request covers work that spans multiple existing namespaces, ask the user how to split it — do not invent a new combined namespace.
|
|
80
|
+
- Do not assume that "new feature" → "new namespace". A new feature's keys almost always belong in an existing namespace.
|
|
81
|
+
|
|
82
|
+
### Example: what to do
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Request: "Add expense report keys"
|
|
86
|
+
Project namespaces: ["backoffice-translations", "mobile"]
|
|
87
|
+
|
|
88
|
+
→ Context: expense report is a backoffice feature
|
|
89
|
+
→ Use: backoffice-translations
|
|
90
|
+
→ Do not create: expenses, expense-report, etc.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Request: "Add push notification copy"
|
|
95
|
+
Project namespaces: ["backoffice-translations", "mobile"]
|
|
96
|
+
|
|
97
|
+
→ Context: push notifications are mobile-specific
|
|
98
|
+
→ Use: mobile
|
|
99
|
+
→ Do not create: notifications, push, etc.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Request: "Add shared error messages"
|
|
104
|
+
Project namespaces: ["frontend", "backend", "mobile"]
|
|
105
|
+
|
|
106
|
+
→ Context: ambiguous — could belong to any namespace
|
|
107
|
+
→ Action: ask the user which namespace should receive these keys
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Key naming rules
|
|
113
|
+
|
|
114
|
+
Keys must match: `/^[a-zA-Z0-9._-]+$/`
|
|
115
|
+
|
|
116
|
+
Valid: `button.save`, `error-message`, `form_field`, `accessControl`
|
|
117
|
+
Invalid: `button/save`, `button save`, `button:save`
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Tool reference
|
|
122
|
+
|
|
123
|
+
### `list_projects`
|
|
124
|
+
Lists all accessible projects with sandbox state.
|
|
125
|
+
|
|
126
|
+
**Returns:** slug, name, sandbox initialized (yes/no), has pending changes (yes/no)
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
list_projects()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### `get_project_details`
|
|
135
|
+
Gets namespaces, locales, and IDs for a project. **Call this first** to get valid locale codes before writing.
|
|
136
|
+
|
|
137
|
+
**Params:** `projectSlug` (required)
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
get_project_details({ projectSlug: "travis" })
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Response example:**
|
|
144
|
+
```
|
|
145
|
+
Project: travis — "TRAVIS"
|
|
146
|
+
Locales (5): en, da-DK (default), nb-NO, sv, uk
|
|
147
|
+
Namespaces (2): backoffice-translations, mobile
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### `get_environment_status`
|
|
153
|
+
Shows sandbox state: initialized, has pending changes, snapshot count.
|
|
154
|
+
|
|
155
|
+
**Params:** `projectSlug` (required)
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### `init_sandbox`
|
|
160
|
+
Copies current production into sandbox. Safe to call if already initialized (returns early unless `force: true`).
|
|
161
|
+
|
|
162
|
+
**Params:** `projectSlug`, `force` (optional, default false)
|
|
163
|
+
|
|
164
|
+
⚠️ `force: true` wipes existing sandbox changes. Do not use unless intentional.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### `list_translations`
|
|
169
|
+
Reads translation entries from a namespace. **Defaults to sandbox view.**
|
|
170
|
+
|
|
171
|
+
**Params:**
|
|
172
|
+
- `projectSlug` (required)
|
|
173
|
+
- `namespace` (required) — e.g. `backoffice-translations`
|
|
174
|
+
- `env` — `sandbox` (default) or `production`
|
|
175
|
+
- `page`, `limit` (default 1, 50)
|
|
176
|
+
- `search` — searches keys and values (min 2 chars)
|
|
177
|
+
- `searchLocale` — restricts value search to one locale. **Only works when `search` is also provided.**
|
|
178
|
+
- `sortBy` — `key` (default) or `createdAt`
|
|
179
|
+
- `sortOrder` — `asc` (default) or `desc`
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
list_translations({ projectSlug: "travis", namespace: "backoffice-translations", env: "sandbox", search: "button" })
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Known limitation:** `searchLocale` without `search` has no effect.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### `set_translation`
|
|
190
|
+
Creates or updates a translation key in sandbox (upsert). Calls PATCH first; falls back to POST if key does not exist.
|
|
191
|
+
|
|
192
|
+
**Always writes to sandbox. Never touches production.**
|
|
193
|
+
|
|
194
|
+
**Params:**
|
|
195
|
+
- `projectSlug` (required)
|
|
196
|
+
- `namespace` (required)
|
|
197
|
+
- `key` (required) — must match `/^[a-zA-Z0-9._-]+$/`
|
|
198
|
+
- `values` (required) — `{ "en": "Save", "nb-NO": "Lagre" }`
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
set_translation({
|
|
202
|
+
projectSlug: "travis",
|
|
203
|
+
namespace: "backoffice-translations",
|
|
204
|
+
key: "button.save",
|
|
205
|
+
values: { "en": "Save", "nb-NO": "Lagre", "da-DK": "Gem", "sv": "Spara" }
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Important:** You do not have to provide all locales at once. Omitted locales keep their existing value.
|
|
210
|
+
|
|
211
|
+
**Warning:** If you pass unknown locale codes, the tool will list them explicitly. Fix them before moving on.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### `delete_translation`
|
|
216
|
+
Soft-deletes a key in sandbox across all locales. The key remains in production until promote.
|
|
217
|
+
|
|
218
|
+
**Params:** `projectSlug`, `namespace`, `key`
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### `create_namespace`
|
|
223
|
+
Creates a new namespace in a project. Required before using `set_translation` or `bulk_import` for a new module.
|
|
224
|
+
|
|
225
|
+
**Params:**
|
|
226
|
+
- `projectSlug` (required)
|
|
227
|
+
- `namespace` (required) — lowercase alphanumeric with dashes: `expenses`, `mobile-v2`
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
create_namespace({ projectSlug: "travis", namespace: "expenses" })
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Returns an error if the namespace already exists. After creating, call `init_sandbox` if you plan to use the sandbox workflow.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### `create_locale`
|
|
238
|
+
Adds a locale to a project. Must be done before any translations can be written for that locale.
|
|
239
|
+
|
|
240
|
+
**Params:**
|
|
241
|
+
- `projectSlug` (required)
|
|
242
|
+
- `code` (required) — **full BCP 47 code**: `nb-NO`, `da-DK`, `sv`, `en`, `uk`. Do NOT use `no`, `da`.
|
|
243
|
+
- `isDefault` (optional, default false)
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
create_locale({ projectSlug: "travis", code: "pl", isDefault: false })
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### `export_namespace`
|
|
252
|
+
Exports all translation keys for a namespace as a flat JSON map, per locale. Fetches all pages automatically.
|
|
253
|
+
|
|
254
|
+
Use this to:
|
|
255
|
+
- See the full content of a namespace
|
|
256
|
+
- Get the data format needed for `bulk_import`
|
|
257
|
+
- Compare before/after a migration
|
|
258
|
+
|
|
259
|
+
**Params:**
|
|
260
|
+
- `projectSlug` (required)
|
|
261
|
+
- `namespace` (required)
|
|
262
|
+
- `env` — `production` (default) or `sandbox`
|
|
263
|
+
- `locale` — restrict to one locale; omit for all locales
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
export_namespace({ projectSlug: "travis", namespace: "backoffice-translations", env: "production" })
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### `bulk_import`
|
|
272
|
+
Imports multiple translation keys into sandbox at once from a JSON map. Much faster than calling `set_translation` one key at a time.
|
|
273
|
+
|
|
274
|
+
**Always writes to sandbox. Production is unchanged.**
|
|
275
|
+
|
|
276
|
+
**Params:**
|
|
277
|
+
- `projectSlug` (required)
|
|
278
|
+
- `namespace` (required) — must already exist (use `create_namespace` first)
|
|
279
|
+
- `translations` (required) — locale → key → value map:
|
|
280
|
+
```json
|
|
281
|
+
{ "en": { "save": "Save", "cancel": "Cancel" }, "nb-NO": { "save": "Lagre", "cancel": "Avbryt" } }
|
|
282
|
+
```
|
|
283
|
+
- `dryRun` (optional, default false) — preview what would be imported without writing
|
|
284
|
+
|
|
285
|
+
**Strategy:** For each key, tries PATCH first (update existing); falls back to POST if key does not exist (404). This is safe to run multiple times.
|
|
286
|
+
|
|
287
|
+
**Validation:** Aborts immediately if any locale code is not in the project. Fix the codes and retry.
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
bulk_import({
|
|
291
|
+
projectSlug: "travis",
|
|
292
|
+
namespace: "expenses",
|
|
293
|
+
translations: {
|
|
294
|
+
"en": { "page.title": "Expenses", "button.addExpense": "Add expense" },
|
|
295
|
+
"nb-NO": { "page.title": "Utgifter", "button.addExpense": "Legg til utgift" }
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Dry run first** for large imports:
|
|
301
|
+
```
|
|
302
|
+
bulk_import({ ..., dryRun: true })
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### `get_translation_diff`
|
|
308
|
+
Shows all differences between sandbox and production: added, changed, deleted entries.
|
|
309
|
+
|
|
310
|
+
**Params:** `projectSlug`, optional `namespace`, `locale`, `statusFilter` (`added`/`changed`/`deleted`/`all`)
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### `validate_translations`
|
|
315
|
+
Analyzes the sandbox diff for problems: empty values, partially translated keys, keys being fully deleted.
|
|
316
|
+
|
|
317
|
+
**Params:** `projectSlug`, optional `namespace`
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### `list_snapshots`
|
|
322
|
+
Lists production snapshots available for revert (max 5, created automatically before each promote).
|
|
323
|
+
|
|
324
|
+
**Params:** `projectSlug`
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
### `preview_push_to_production`
|
|
329
|
+
Read-only preview of what would happen if sandbox were promoted to production right now.
|
|
330
|
+
|
|
331
|
+
**Does not push anything.** Pushing is manual, via Admin UI.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Module migration workflow
|
|
336
|
+
|
|
337
|
+
Use this when moving a frontend module's local translation files to the server for the first time.
|
|
338
|
+
|
|
339
|
+
### Step 1 — Create a namespace for the module
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
create_namespace({ projectSlug: "travis", namespace: "expenses" })
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Use the module name as the namespace slug (lowercase, dashes only).
|
|
346
|
+
|
|
347
|
+
### Step 2 — Initialize sandbox
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
init_sandbox({ projectSlug: "travis" })
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Safe to call if already initialized.
|
|
354
|
+
|
|
355
|
+
### Step 3 — Dry-run the import
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
bulk_import({
|
|
359
|
+
projectSlug: "travis",
|
|
360
|
+
namespace: "expenses",
|
|
361
|
+
translations: { "en": { "page.title": "Expenses" }, "nb-NO": { "page.title": "Utgifter" } },
|
|
362
|
+
dryRun: true
|
|
363
|
+
})
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Verify key counts look correct before writing.
|
|
367
|
+
|
|
368
|
+
### Step 4 — Run the real import
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
bulk_import({ projectSlug: "travis", namespace: "expenses", translations: { ... } })
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Step 5 — Verify and review
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
list_translations({ projectSlug: "travis", namespace: "expenses", env: "sandbox" })
|
|
378
|
+
get_translation_diff({ projectSlug: "travis", namespace: "expenses" })
|
|
379
|
+
validate_translations({ projectSlug: "travis", namespace: "expenses" })
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Fix any partial translations before handing off to the developer.
|
|
383
|
+
|
|
384
|
+
### Step 6 — Developer tests against sandbox HTTP endpoint
|
|
385
|
+
|
|
386
|
+
The developer can test their code against the sandbox without promoting to production:
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
GET http://localhost:8080/translations/travis/expenses/en?env=sandbox
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
This returns sandbox values (production base + sandbox overrides). No caching. Safe for local dev.
|
|
393
|
+
|
|
394
|
+
### Step 7 — Human promotes via Admin UI
|
|
395
|
+
|
|
396
|
+
Admin UI → Translations → Sandbox tab → Push to Production.
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## The real workflow
|
|
401
|
+
|
|
402
|
+
### Step 1 — Understand the project
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
get_project_details({ projectSlug: "travis" })
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Note the exact locale codes. You will use them in every `set_translation` call.
|
|
409
|
+
|
|
410
|
+
### Step 2 — Ensure sandbox is initialized
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
get_environment_status({ projectSlug: "travis" })
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
If `initialized: false`, call `init_sandbox({ projectSlug: "travis" })`.
|
|
417
|
+
|
|
418
|
+
### Step 3 — Add or edit keys
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
set_translation({
|
|
422
|
+
projectSlug: "travis",
|
|
423
|
+
namespace: "backoffice-translations",
|
|
424
|
+
key: "feature.newButton",
|
|
425
|
+
values: { "en": "New button", "nb-NO": "Ny knapp", "da-DK": "Ny knap", "sv": "Ny knapp" }
|
|
426
|
+
})
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Step 4 — Verify in sandbox
|
|
430
|
+
|
|
431
|
+
```
|
|
432
|
+
list_translations({ projectSlug: "travis", namespace: "backoffice-translations", env: "sandbox", search: "feature.new" })
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
The new key should appear with your values.
|
|
436
|
+
|
|
437
|
+
### Step 5 — Check the diff
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
get_translation_diff({ projectSlug: "travis" })
|
|
441
|
+
validate_translations({ projectSlug: "travis" })
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
If `validate_translations` reports issues (empty values, partial translations), fix them before proceeding.
|
|
445
|
+
|
|
446
|
+
### Step 6 — Preview
|
|
447
|
+
|
|
448
|
+
```
|
|
449
|
+
preview_push_to_production({ projectSlug: "travis" })
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Review what will change in production.
|
|
453
|
+
|
|
454
|
+
### Step 7 — Human review and local testing
|
|
455
|
+
|
|
456
|
+
**Stop here.** The developer must:
|
|
457
|
+
1. Pull the latest code (translations are fetched live from the API, no code change needed for new keys)
|
|
458
|
+
2. Test the feature locally against `http://localhost:8080/translations/travis/backoffice-translations/en`
|
|
459
|
+
3. Review the diff in Admin UI → Translations → Sandbox tab
|
|
460
|
+
4. Click "Push to Production" in the Admin UI when satisfied
|
|
461
|
+
|
|
462
|
+
### Step 8 — After production push
|
|
463
|
+
|
|
464
|
+
Production endpoint now serves the new key. The sandbox resets automatically to match production (a snapshot is saved for revert).
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Deployment behavior
|
|
469
|
+
|
|
470
|
+
Translation keys are **decoupled from code deployments**. They are fetched at runtime from the API.
|
|
471
|
+
|
|
472
|
+
| Change type | Deployment needed? |
|
|
473
|
+
|-------------|-------------------|
|
|
474
|
+
| New translation key | No — takes effect after Admin UI "Push to Production" |
|
|
475
|
+
| Updated translation value | No |
|
|
476
|
+
| New feature that *uses* a translation key | Yes — code must ship the new key reference |
|
|
477
|
+
| New locale for the project | No — agents can add values, push from Admin UI |
|
|
478
|
+
|
|
479
|
+
**If your code references a key before it exists in production, you will get `undefined` at runtime.** For key-dependent features, add the key to sandbox, push to production, then deploy the code.
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Common problems and fixes
|
|
484
|
+
|
|
485
|
+
### "Key created with empty values"
|
|
486
|
+
|
|
487
|
+
**Cause:** All locale codes were invalid (e.g., `NO` instead of `nb-NO`).
|
|
488
|
+
**Fix:** Call `get_project_details` to see exact locale codes, then retry with correct codes.
|
|
489
|
+
|
|
490
|
+
### "Key not found after create"
|
|
491
|
+
|
|
492
|
+
**Cause:** Searching production (`env: "production"`) for a key that exists only in sandbox.
|
|
493
|
+
**Fix:** Search with `env: "sandbox"` until the key is promoted.
|
|
494
|
+
|
|
495
|
+
### "get_translation_diff shows key as deleted with productionValue: null"
|
|
496
|
+
|
|
497
|
+
**Cause:** You created then deleted a sandbox-only key (it was never in production). This is a diff entry from sandbox to itself.
|
|
498
|
+
**Fix:** Ignore it — the key will be cleaned up on promote. Or reset the sandbox if the deletion was unintended.
|
|
499
|
+
|
|
500
|
+
### "Diff shows changes but I didn't make any"
|
|
501
|
+
|
|
502
|
+
**Cause:** Another agent or developer has sandbox changes in progress.
|
|
503
|
+
**Fix:** Review with `get_translation_diff` and coordinate with the team.
|
|
504
|
+
|
|
505
|
+
### "searchLocale does not filter results"
|
|
506
|
+
|
|
507
|
+
**Cause:** `searchLocale` only works when `search` is also provided.
|
|
508
|
+
**Fix:** Add `search` param to activate locale filtering.
|
|
509
|
+
|
|
510
|
+
### "MCP server returns Error 0 or Unauthorized"
|
|
511
|
+
|
|
512
|
+
**Cause:** `MCP_TOKEN` in `mcp-server/.env` is missing or revoked.
|
|
513
|
+
**Fix:** Generate a new token in Admin UI → API Tokens, update `.env`, restart Claude.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Verified working calls (tested 2026-03-28)
|
|
518
|
+
|
|
519
|
+
| Tool | Status | Notes |
|
|
520
|
+
|------|--------|-------|
|
|
521
|
+
| `list_projects` | ✅ | |
|
|
522
|
+
| `get_project_details` | ✅ | Returns locale codes as string array, not objects |
|
|
523
|
+
| `get_environment_status` | ✅ | |
|
|
524
|
+
| `init_sandbox` | ✅ | |
|
|
525
|
+
| `list_translations` (sandbox) | ✅ | Default env |
|
|
526
|
+
| `list_translations` (production) | ✅ | Pass `env: "production"` |
|
|
527
|
+
| `set_translation` (create) | ✅ | |
|
|
528
|
+
| `set_translation` (update) | ✅ | |
|
|
529
|
+
| `delete_translation` | ✅ | Soft delete, 204 response |
|
|
530
|
+
| `get_translation_diff` | ✅ | |
|
|
531
|
+
| `validate_translations` | ✅ | |
|
|
532
|
+
| `preview_push_to_production` | ✅ | Read-only |
|
|
533
|
+
| `list_snapshots` | ✅ | |
|
|
534
|
+
| `reset_sandbox` | ✅ | Requires `confirmed: true` |
|
|
535
|
+
| `create_namespace` | ✅ | |
|
|
536
|
+
| `create_locale` | ✅ | Must use BCP 47 codes |
|
|
537
|
+
| `export_namespace` | ✅ | Auto-paginates |
|
|
538
|
+
| `bulk_import` | ✅ | PATCH→POST upsert; locale validation |
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Known gaps and limitations
|
|
543
|
+
|
|
544
|
+
| Issue | Status |
|
|
545
|
+
|-------|--------|
|
|
546
|
+
| `searchLocale` without `search` is a no-op | Known limitation — both params required |
|
|
547
|
+
| Invalid locale codes previously silently dropped | Fixed: `set_translation` and `bulk_import` now reject unknown codes with a clear error |
|
|
548
|
+
| Sandbox-only keys leaked into production endpoint | Fixed: production endpoint requires `translation_values` to exist |
|
|
549
|
+
| `uk` locale exists but has 0 translations | Not a bug — no content added yet |
|
|
550
|
+
| Sandbox had 19 test-artifact ghost keys | Fixed: sandbox reset 2026-03-28 |
|
|
551
|
+
| No `create_namespace` / `bulk_import` tools | Fixed: all 4 project management tools implemented |
|
|
552
|
+
| Sandbox data not testable without promoting | Fixed: `?env=sandbox` on public endpoint |
|
|
553
|
+
| **API gap: no locale alias information** | **Open** — API returns canonical codes only (e.g. `nb-NO`), no information about aliases (e.g. `no`, `nb`). Agent cannot resolve source-to-server locale mapping without external context. Report as blocker if ambiguous. |
|
|
554
|
+
| **API gap: no `isDefault` in locales list** | Fixed 2026-03-28 — locales now return `{ code, isDefault }` |
|
|
555
|
+
| MCP has no `rename_key` / `bulk_delete` | By design — not implemented |
|
|
556
|
+
| Production push via MCP | By design — manual only via Admin UI |
|