@sonenta/mcp 0.34.0 → 0.35.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/README.md +9 -74
- package/bin/sonenta-mcp.js +20053 -121
- package/bin/sonenta-mcp.js.map +1 -0
- package/package.json +34 -14
- package/python/README.md +0 -114
- package/python/dist/sonenta_mcp-0.34.0-py3-none-any.whl +0 -0
- package/python/pyproject.toml +0 -58
- package/python/verbumia_mcp/__init__.py +0 -3
- package/python/verbumia_mcp/__main__.py +0 -23
- package/python/verbumia_mcp/client.py +0 -85
- package/python/verbumia_mcp/config.py +0 -157
- package/python/verbumia_mcp/server.py +0 -88
- package/python/verbumia_mcp/tools.py +0 -3300
|
@@ -1,3300 +0,0 @@
|
|
|
1
|
-
"""V1 MCP tool registrations.
|
|
2
|
-
|
|
3
|
-
Tools (V1 MCP surface; glossary_* added 2026-05-24; reports + trash added 2026-05-25):
|
|
4
|
-
- list_projects — list projects accessible to the API key
|
|
5
|
-
- get_project_info — project metadata: source_language, languages, namespaces
|
|
6
|
-
- list_keys — keys + source value + per-language translations (bulk-translate)
|
|
7
|
-
- list_untranslated_keys — keys WITHOUT a valid translation in a target language
|
|
8
|
-
- list_missing_keys — pending missing-key events (cursor paginated)
|
|
9
|
-
- missing_keys_stats — count-only summary of missing events (drives pagination)
|
|
10
|
-
- acknowledge_missing_keys— batch-resolve missing events (cleanup the dashboard)
|
|
11
|
-
- create_namespace — idempotent namespace creation (slug validated, slug_invalid on bad input)
|
|
12
|
-
- create_key — idempotent key creation; optionally seeds source value + sets semantic `type`
|
|
13
|
-
- create_keys_bulk — batch create_key (up to 500/call, per-item results)
|
|
14
|
-
- update_key — edit a key's SOURCE value / `type` / description in place (PATCH); demotes reviewed/approved targets to needs-review
|
|
15
|
-
- update_keys_bulk — batch update_key (up to 500/call, per-item results)
|
|
16
|
-
- propose_translation — submit a translation value (auto-creates the key if missing)
|
|
17
|
-
- propose_translations_bulk— batch propose_translation (up to 500/call, per-item results)
|
|
18
|
-
- publish_cdn — trigger a CDN release (closes the dogfood loop)
|
|
19
|
-
- validate_translations — lint a JSON i18next blob server-side
|
|
20
|
-
- project_context_get — read the project's markdown context document
|
|
21
|
-
- project_context_set — upsert the project's markdown context document
|
|
22
|
-
- glossary_list — list glossary entries (filters rule_type/q/limit)
|
|
23
|
-
- glossary_create — create a glossary entry (409 on duplicate rule_type+term)
|
|
24
|
-
- glossary_update — update an entry by uuid (term/translations/case_sensitive/note)
|
|
25
|
-
- glossary_delete — delete an entry by uuid
|
|
26
|
-
- health_report — read-only project source-health report (#752)
|
|
27
|
-
- coverage_report — read-only project translation-coverage report (#752)
|
|
28
|
-
- list_source_duplicates — read-only list of duplicate-source-value groups (#1116)
|
|
29
|
-
- set_duplicate_status — triage a duplicate group: to_fix | allowed | resolved (#1116)
|
|
30
|
-
- delete_keys_bulk — soft-delete (trash) keys by key_uuid; restorable
|
|
31
|
-
- restore_keys_bulk — restore trashed keys by key_uuid
|
|
32
|
-
- list_trash — list trashed (soft-deleted) keys (cursor paginated)
|
|
33
|
-
- a11y_estimate — preview the credit cost of an a11y generate/translate run (#a11y V1)
|
|
34
|
-
- generate_a11y_variant — AI-generate source-lang a11y variants (aria/alt/screen_reader/plain_language)
|
|
35
|
-
- translate_a11y_variants — translate existing source a11y overrides into target locales
|
|
36
|
-
- a11y_report — read-only WCAG a11y gap report (per key/surface/locale)
|
|
37
|
-
- list_a11y_gaps — actionable a11y gaps (filtered items[] of a11y_report)
|
|
38
|
-
- set_a11y_variant — upsert one a11y variant override (key, language, surface)
|
|
39
|
-
- delete_a11y_variant — delete one a11y variant override (cell re-inherits base value)
|
|
40
|
-
- list_surfaces — list the project's configurable surfaces (device + a11y; #928)
|
|
41
|
-
- create_surface — create a custom DEVICE surface (a11y is a fixed set)
|
|
42
|
-
- update_surface — rename / toggle-active a surface (deactivate is soft)
|
|
43
|
-
- delete_surface — delete a custom surface (builtins: deactivate only)
|
|
44
|
-
- get_variants — full variant matrix for one key (every active surface x language; #928)
|
|
45
|
-
- set_variant — upsert one variant override for ANY active surface (device or a11y)
|
|
46
|
-
- delete_variant — delete one variant override for any surface (cell re-inherits base)
|
|
47
|
-
- list_cognitive_candidates — text keys eligible for plain-language (cognitive) scoring
|
|
48
|
-
- set_cognitive_score — record a key's cognitive score (0-100) + plain-language suggestion (0 credit)
|
|
49
|
-
- analyze_cognitive — server AI cognitive analysis (billed; opt-in fallback to local scoring)
|
|
50
|
-
|
|
51
|
-
A11y (accessibility) tools (a11y V1) extend the variant engine with four SEMANTIC
|
|
52
|
-
surfaces — aria_label / alt_text / screen_reader / plain_language — orthogonal to
|
|
53
|
-
the visible text. The report hits `/v1/mcp/projects/{id}/a11y-report`. Variant
|
|
54
|
-
writes (set_variant / delete_variant, and the a11y-named set_a11y_variant /
|
|
55
|
-
delete_a11y_variant) use the mcp:* code-addressed GENERIC route
|
|
56
|
-
`/v1/mcp/projects/{id}/keys/{key_uuid}/variants/{language_code}/{surface}`, which
|
|
57
|
-
accepts any surface ACTIVE for the project (device or a11y; #928) — locale
|
|
58
|
-
reinjected straight from a report/matrix item. estimate/generate/translate are
|
|
59
|
-
billed in credits. All are thin HTTP wrappers surfaced verbatim. Every tool is
|
|
60
|
-
reachable with the same `mcp:*` key as the rest of the MCP surface.
|
|
61
|
-
|
|
62
|
-
list_keys and list_untranslated_keys accept `include_glossary_hints=true` to
|
|
63
|
-
attach backend-matched `glossary_hints[]` per key. All glossary matching is
|
|
64
|
-
backend-side; these tools carry ZERO glossary logic client-side.
|
|
65
|
-
|
|
66
|
-
Trash is UUID-addressed (key_uuids[]) — get key_uuid from list_keys (active) or
|
|
67
|
-
list_trash (trashed). No hard-delete/purge over MCP (human-only JWT). reports +
|
|
68
|
-
trash tools are thin HTTP wrappers, surfaced verbatim.
|
|
69
|
-
|
|
70
|
-
The tool surface mirrors the canonical contract published in
|
|
71
|
-
`docs/mcp/setup`. Argument names use the LLM-friendly `key` / `namespace` /
|
|
72
|
-
`language_code` rather than uuids — the backend resolves uuids server-side.
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
from __future__ import annotations
|
|
76
|
-
|
|
77
|
-
import json
|
|
78
|
-
import uuid
|
|
79
|
-
from typing import Any
|
|
80
|
-
|
|
81
|
-
from mcp.server import Server
|
|
82
|
-
from mcp.types import TextContent, Tool
|
|
83
|
-
|
|
84
|
-
from .client import VerbumiaApiError, VerbumiaClient
|
|
85
|
-
|
|
86
|
-
# The four accessibility (a11y) variant surfaces (task 989). Distinct from the
|
|
87
|
-
# device surfaces (desktop/mobile/tablet); these map to semantic attributes
|
|
88
|
-
# (aria-label, <img alt>, sr-only text, clear-language rendering), not the
|
|
89
|
-
# visible text. `surfaceKinds` in the variants matrix tags each as a11y.
|
|
90
|
-
A11Y_SURFACES = ("aria_label", "alt_text", "screen_reader", "plain_language")
|
|
91
|
-
|
|
92
|
-
# RS1 (1367) + FM1 (1390) + FF1 (1397): audit trail of t() repoints an agent made
|
|
93
|
-
# while resolving a duplicate group — accepted on ALL FOUR resolution paths
|
|
94
|
-
# (set_source_duplicate_merge_plan, set_duplicate_status, apply_new_key_cluster,
|
|
95
|
-
# mark_cluster_applied), persisted + surfaced in resolved_duplicates[]. UNION-MERGE
|
|
96
|
-
# (accumulate + dedup by normalized entry). FF1 split key_name into found_key +
|
|
97
|
-
# replacement_key (key_name kept, deprecated).
|
|
98
|
-
_FILES_MODIFIED_SCHEMA = {
|
|
99
|
-
"type": "array",
|
|
100
|
-
"description": (
|
|
101
|
-
"Optional audit trail of the t() repoints you made while resolving this group: "
|
|
102
|
-
"each {path (required), line?, found_key, replacement_key, occurrences?}. "
|
|
103
|
-
"`found_key` = the key string that WAS in the code at this spot (the old, "
|
|
104
|
-
"redundant one); `replacement_key` = the canonical you repointed it to (an "
|
|
105
|
-
"existing survivor, OR the new_key from apply_new_key_cluster). Accepted on "
|
|
106
|
-
"every resolution path and MERGED (union + dedup by the full normalized entry) "
|
|
107
|
-
"— record edits at whatever step you make them and they accumulate; omit to "
|
|
108
|
-
"preserve the existing list. Always pass the STRUCTURED field, never just a note."
|
|
109
|
-
),
|
|
110
|
-
"items": {
|
|
111
|
-
"type": "object",
|
|
112
|
-
"properties": {
|
|
113
|
-
"path": {"type": "string", "description": "Repo-relative file path that was edited."},
|
|
114
|
-
"line": {"type": "integer", "description": "Optional 1-based line number of the edit."},
|
|
115
|
-
"found_key": {"type": "string", "description": "The OLD key found in the code at this spot (the redundant one being repointed away)."},
|
|
116
|
-
"replacement_key": {"type": "string", "description": "The CANONICAL key you repointed to (an existing survivor, or the created new_key)."},
|
|
117
|
-
"occurrences": {"type": "integer", "description": "Optional count of how many usages you replaced here."},
|
|
118
|
-
"key_name": {"type": "string", "description": "DEPRECATED (pre-FF1 single field) — prefer found_key + replacement_key."},
|
|
119
|
-
},
|
|
120
|
-
"required": ["path"],
|
|
121
|
-
"additionalProperties": False,
|
|
122
|
-
},
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
# Key semantic types (a11y key.type pivot). Default "text". The type decides
|
|
126
|
-
# which a11y treatments a key offers (e.g. an `image` key's base value IS its
|
|
127
|
-
# alt text; a `button` offers aria_label) — the engine gates variant/a11y tools
|
|
128
|
-
# by type server-side, so listing these here is just for create/update_key.
|
|
129
|
-
KEY_TYPES = (
|
|
130
|
-
"text",
|
|
131
|
-
"heading",
|
|
132
|
-
"label",
|
|
133
|
-
"caption",
|
|
134
|
-
"badge",
|
|
135
|
-
"tooltip",
|
|
136
|
-
"error_message",
|
|
137
|
-
"toast",
|
|
138
|
-
"button",
|
|
139
|
-
"link",
|
|
140
|
-
"menu_item",
|
|
141
|
-
"input_placeholder",
|
|
142
|
-
"input_label",
|
|
143
|
-
"image",
|
|
144
|
-
"icon",
|
|
145
|
-
"meta_title",
|
|
146
|
-
"meta_description",
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
# Human/LLM guidance for choosing a key's `type`. Embedded in the create_key /
|
|
150
|
-
# update_key tool descriptions so an agent types keys correctly instead of
|
|
151
|
-
# leaving everything as the "text" default. The type DRIVES which a11y
|
|
152
|
-
# treatments + cognitive (plain-language) scoring a key gets, so it is the input
|
|
153
|
-
# that makes the a11y tooling work.
|
|
154
|
-
KEY_TYPE_GUIDANCE = (
|
|
155
|
-
"Set this at CREATION — do NOT leave everything as 'text'. The type drives "
|
|
156
|
-
"which a11y treatments the key offers (and whether it gets cognitive / "
|
|
157
|
-
"plain-language scoring), so correct typing is what lets the a11y tooling "
|
|
158
|
-
"work. Pick by the string's UI role:\n"
|
|
159
|
-
"- text — body / paragraph copy (offers plain_language, screen_reader)\n"
|
|
160
|
-
"- heading — a title or section heading (plain_language, screen_reader)\n"
|
|
161
|
-
"- label — a standalone short label (plain_language, screen_reader)\n"
|
|
162
|
-
"- caption — image caption / helper / secondary text (plain_language, screen_reader)\n"
|
|
163
|
-
"- button — a button's label (offers aria_label, screen_reader)\n"
|
|
164
|
-
"- link — a hyperlink / anchor label (aria_label, screen_reader)\n"
|
|
165
|
-
"- menu_item — a navigation / menu entry (aria_label, screen_reader)\n"
|
|
166
|
-
"- input_placeholder — a form input placeholder (aria_label)\n"
|
|
167
|
-
"- input_label — a form field's label (aria_label)\n"
|
|
168
|
-
"- tooltip — tooltip text (screen_reader)\n"
|
|
169
|
-
"- badge — a small status / count badge (screen_reader)\n"
|
|
170
|
-
"- error_message — a validation / error message (screen_reader)\n"
|
|
171
|
-
"- toast — a transient toast / snackbar notification (screen_reader)\n"
|
|
172
|
-
"- image — an image; THIS key's value IS the alt text\n"
|
|
173
|
-
"- icon — an icon; THIS key's value IS the accessible name (aria)\n"
|
|
174
|
-
"- meta_title — an SEO page <title> (no a11y treatment)\n"
|
|
175
|
-
"- meta_description — an SEO meta description (no a11y treatment)\n"
|
|
176
|
-
"Default 'text'. Reclassify existing keys later with update_key / "
|
|
177
|
-
"update_keys_bulk (type-only, no source_value needed). The current type is "
|
|
178
|
-
"returned by list_keys so you can audit and fix mis-typed keys."
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def _project_uuid(arguments: dict | None, client: VerbumiaClient) -> str:
|
|
183
|
-
"""Resolve project_uuid from per-call args or the configured env scope.
|
|
184
|
-
|
|
185
|
-
Rules (multi-project aware, 0.11+):
|
|
186
|
-
1. Explicit `project_uuid` in args wins. If `SONENTA_PROJECTS` is set
|
|
187
|
-
and the value isn't in that allowlist, raise early — better UX than
|
|
188
|
-
a backend 404/403 after a round-trip.
|
|
189
|
-
2. No explicit arg + exactly one configured project → use that one.
|
|
190
|
-
3. No explicit arg + multiple configured projects → raise asking the
|
|
191
|
-
caller to pass `project_uuid` and listing the allowed UUIDs.
|
|
192
|
-
4. No explicit arg + nothing configured → raise asking the caller to
|
|
193
|
-
pass `project_uuid` or set SONENTA_PROJECTS / SONENTA_PROJECT.
|
|
194
|
-
"""
|
|
195
|
-
allowed = client.config.allowed_project_uuids
|
|
196
|
-
if arguments and arguments.get("project_uuid"):
|
|
197
|
-
requested = str(arguments["project_uuid"])
|
|
198
|
-
if allowed and requested not in allowed:
|
|
199
|
-
raise ValueError(
|
|
200
|
-
f"project_uuid {requested!r} is not in SONENTA_PROJECTS allowlist "
|
|
201
|
-
f"({', '.join(allowed)})"
|
|
202
|
-
)
|
|
203
|
-
return requested
|
|
204
|
-
if client.config.default_project_uuid:
|
|
205
|
-
return client.config.default_project_uuid
|
|
206
|
-
if allowed:
|
|
207
|
-
raise ValueError(
|
|
208
|
-
"project_uuid is required when multiple projects are configured "
|
|
209
|
-
"via SONENTA_PROJECTS (or the legacy SONENTA_PROJECT singular). "
|
|
210
|
-
f"Pass one of: {', '.join(allowed)}"
|
|
211
|
-
)
|
|
212
|
-
raise ValueError(
|
|
213
|
-
"project_uuid is required (pass it explicitly or set SONENTA_PROJECTS / SONENTA_PROJECT)"
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def _filter_projects_by_allowlist(data: Any, allowed: tuple[str, ...]) -> Any:
|
|
218
|
-
"""Restrict a list_projects response to the SONENTA_PROJECTS allowlist so
|
|
219
|
-
`see == touch`: the backend returns everything the API key can reach, but
|
|
220
|
-
when the human has set an allowlist we only surface those projects — the
|
|
221
|
-
same scope `_project_uuid` enforces for get_project_info / writes. No
|
|
222
|
-
allowlist set → return `data` unchanged.
|
|
223
|
-
"""
|
|
224
|
-
if allowed and isinstance(data, dict) and isinstance(data.get("items"), list):
|
|
225
|
-
return {**data, "items": [p for p in data["items"] if p.get("uuid") in allowed]}
|
|
226
|
-
return data
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def _text(payload: Any) -> list[TextContent]:
|
|
230
|
-
return [TextContent(type="text", text=json.dumps(payload, indent=2, default=str))]
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _err(msg: str) -> list[TextContent]:
|
|
234
|
-
return [TextContent(type="text", text=f"error: {msg}")]
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
|
|
238
|
-
@server.list_tools()
|
|
239
|
-
async def _list_tools() -> list[Tool]:
|
|
240
|
-
return [
|
|
241
|
-
Tool(
|
|
242
|
-
name="list_projects",
|
|
243
|
-
description=(
|
|
244
|
-
"List projects accessible to the configured Sonenta API key. "
|
|
245
|
-
"Project-scoped keys return a single project; org-scoped keys "
|
|
246
|
-
"return all projects in the org."
|
|
247
|
-
),
|
|
248
|
-
inputSchema={
|
|
249
|
-
"type": "object",
|
|
250
|
-
"properties": {
|
|
251
|
-
"limit": {"type": "integer", "minimum": 1, "maximum": 200, "default": 50}
|
|
252
|
-
},
|
|
253
|
-
"additionalProperties": False,
|
|
254
|
-
},
|
|
255
|
-
),
|
|
256
|
-
Tool(
|
|
257
|
-
name="get_project_info",
|
|
258
|
-
description=(
|
|
259
|
-
"Fetch a project's metadata: source language, all configured "
|
|
260
|
-
"languages, namespaces, key count. Use before list_missing_keys "
|
|
261
|
-
"or propose_translation to learn what namespace + language codes "
|
|
262
|
-
"are valid."
|
|
263
|
-
),
|
|
264
|
-
inputSchema={
|
|
265
|
-
"type": "object",
|
|
266
|
-
"properties": {
|
|
267
|
-
"project_uuid": {
|
|
268
|
-
"type": "string",
|
|
269
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
"additionalProperties": False,
|
|
273
|
-
},
|
|
274
|
-
),
|
|
275
|
-
Tool(
|
|
276
|
-
name="distribution_info",
|
|
277
|
-
description=(
|
|
278
|
-
"Get the project's CDN distribution status: the public CDN base "
|
|
279
|
-
"URL + bundle URL template, the production version, which "
|
|
280
|
-
"(language, namespace) bundles are currently published, and any "
|
|
281
|
-
"gaps (bundles not yet published). Use to see what client SDKs "
|
|
282
|
-
"can fetch right now."
|
|
283
|
-
),
|
|
284
|
-
inputSchema={
|
|
285
|
-
"type": "object",
|
|
286
|
-
"properties": {
|
|
287
|
-
"project_uuid": {
|
|
288
|
-
"type": "string",
|
|
289
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
"additionalProperties": False,
|
|
293
|
-
},
|
|
294
|
-
),
|
|
295
|
-
Tool(
|
|
296
|
-
name="sonenta_guide",
|
|
297
|
-
description=(
|
|
298
|
-
"Get the Sonenta usage guide: a concise, up-to-date overview of "
|
|
299
|
-
"how this Sonenta instance works — core concepts, the translation "
|
|
300
|
-
"workflow, how to publish to the CDN, and the available MCP tools. "
|
|
301
|
-
"Takes no arguments. Call this first when unsure how to use Sonenta."
|
|
302
|
-
),
|
|
303
|
-
inputSchema={
|
|
304
|
-
"type": "object",
|
|
305
|
-
"properties": {},
|
|
306
|
-
"additionalProperties": False,
|
|
307
|
-
},
|
|
308
|
-
),
|
|
309
|
-
Tool(
|
|
310
|
-
name="list_keys",
|
|
311
|
-
description=(
|
|
312
|
-
"List translation keys with their source-language value, their "
|
|
313
|
-
"semantic `type`, and per-language translations. Designed for "
|
|
314
|
-
"bulk translation: Claude can scan the result and propose "
|
|
315
|
-
"translations for every key that's still missing in a target "
|
|
316
|
-
"language. Each key's `type` lets you AUDIT typing and reclassify "
|
|
317
|
-
"mis-typed keys (e.g. a button left as 'text') via update_key / "
|
|
318
|
-
"update_keys_bulk so the a11y treatments apply. Filters: "
|
|
319
|
-
"namespace slug, status_filter (only keys with at least one "
|
|
320
|
-
"translation in that status), language_code (restrict "
|
|
321
|
-
"translations[] to a single language)."
|
|
322
|
-
),
|
|
323
|
-
inputSchema={
|
|
324
|
-
"type": "object",
|
|
325
|
-
"properties": {
|
|
326
|
-
"project_uuid": {
|
|
327
|
-
"type": "string",
|
|
328
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
329
|
-
},
|
|
330
|
-
"namespace": {"type": "string", "description": "Namespace slug filter"},
|
|
331
|
-
"language_code": {
|
|
332
|
-
"type": "string",
|
|
333
|
-
"description": "Restrict translations[] to one BCP-47 code (e.g. fr).",
|
|
334
|
-
},
|
|
335
|
-
"status_filter": {
|
|
336
|
-
"type": "string",
|
|
337
|
-
"enum": ["missing", "draft", "translated", "reviewed", "approved"],
|
|
338
|
-
"description": "Include only keys with at least one translation in this status.",
|
|
339
|
-
},
|
|
340
|
-
"include_glossary_hints": {
|
|
341
|
-
"type": "boolean",
|
|
342
|
-
"default": False,
|
|
343
|
-
"description": "When true, attach matched glossary_hints[] to each key (backend-matched against the source string). Default false.",
|
|
344
|
-
},
|
|
345
|
-
"cursor": {"type": "string"},
|
|
346
|
-
"limit": {"type": "integer", "minimum": 1, "maximum": 200, "default": 50},
|
|
347
|
-
},
|
|
348
|
-
"additionalProperties": False,
|
|
349
|
-
},
|
|
350
|
-
),
|
|
351
|
-
Tool(
|
|
352
|
-
name="list_untranslated_keys",
|
|
353
|
-
description=(
|
|
354
|
-
"List keys that DON'T yet have a valid translation in the "
|
|
355
|
-
"target language. A key is untranslated when ANY of: no "
|
|
356
|
-
"translation row exists, value is empty, or status is "
|
|
357
|
-
"missing. Compact response (no translations[]) so Claude "
|
|
358
|
-
"can iterate a long list and propose translations from "
|
|
359
|
-
"source_value."
|
|
360
|
-
),
|
|
361
|
-
inputSchema={
|
|
362
|
-
"type": "object",
|
|
363
|
-
"properties": {
|
|
364
|
-
"project_uuid": {
|
|
365
|
-
"type": "string",
|
|
366
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
367
|
-
},
|
|
368
|
-
"language_code": {
|
|
369
|
-
"type": "string",
|
|
370
|
-
"description": "Required: target BCP-47 language code (e.g. fr).",
|
|
371
|
-
},
|
|
372
|
-
"namespace": {"type": "string", "description": "Namespace slug filter"},
|
|
373
|
-
"include_glossary_hints": {
|
|
374
|
-
"type": "boolean",
|
|
375
|
-
"default": False,
|
|
376
|
-
"description": "When true, attach matched glossary_hints[] to each key (term, rule_type, target_translation, matched_text) — backend-matched against the source string. Default false.",
|
|
377
|
-
},
|
|
378
|
-
"cursor": {"type": "string"},
|
|
379
|
-
"limit": {"type": "integer", "minimum": 1, "maximum": 200, "default": 50},
|
|
380
|
-
},
|
|
381
|
-
"required": ["language_code"],
|
|
382
|
-
"additionalProperties": False,
|
|
383
|
-
},
|
|
384
|
-
),
|
|
385
|
-
Tool(
|
|
386
|
-
name="list_missing_keys",
|
|
387
|
-
description=(
|
|
388
|
-
"List pending missing-key events captured from the SDK runtime. "
|
|
389
|
-
"Cursor paginated (default page = 50). Filters: namespace slug, "
|
|
390
|
-
"language_code, status. CALL missing_keys_stats FIRST to learn how "
|
|
391
|
-
"many events exist so you know whether to keep paginating."
|
|
392
|
-
),
|
|
393
|
-
inputSchema={
|
|
394
|
-
"type": "object",
|
|
395
|
-
"properties": {
|
|
396
|
-
"project_uuid": {
|
|
397
|
-
"type": "string",
|
|
398
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
399
|
-
},
|
|
400
|
-
"namespace": {"type": "string"},
|
|
401
|
-
"language_code": {"type": "string"},
|
|
402
|
-
"status": {
|
|
403
|
-
"type": "string",
|
|
404
|
-
"enum": ["open", "acknowledged", "resolved"],
|
|
405
|
-
},
|
|
406
|
-
"cursor": {"type": "string"},
|
|
407
|
-
"limit": {"type": "integer", "minimum": 1, "maximum": 200, "default": 50},
|
|
408
|
-
},
|
|
409
|
-
"additionalProperties": False,
|
|
410
|
-
},
|
|
411
|
-
),
|
|
412
|
-
Tool(
|
|
413
|
-
name="missing_keys_stats",
|
|
414
|
-
description=(
|
|
415
|
-
"Count-only summary of missing-key events for a project. Returns "
|
|
416
|
-
"{total, by_status, by_language, by_namespace}. Lightweight (no row "
|
|
417
|
-
"payloads). Use this BEFORE list_missing_keys so you know how many "
|
|
418
|
-
"pages remain and whether to keep paginating. Filters mirror "
|
|
419
|
-
"list_missing_keys; status defaults to `open` (pass `all` to count "
|
|
420
|
-
"every status)."
|
|
421
|
-
),
|
|
422
|
-
inputSchema={
|
|
423
|
-
"type": "object",
|
|
424
|
-
"properties": {
|
|
425
|
-
"project_uuid": {
|
|
426
|
-
"type": "string",
|
|
427
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
428
|
-
},
|
|
429
|
-
"namespace": {"type": "string"},
|
|
430
|
-
"language_code": {"type": "string"},
|
|
431
|
-
"status": {
|
|
432
|
-
"type": "string",
|
|
433
|
-
"description": "Defaults to `open`. Pass `all` to count every status.",
|
|
434
|
-
},
|
|
435
|
-
},
|
|
436
|
-
"additionalProperties": False,
|
|
437
|
-
},
|
|
438
|
-
),
|
|
439
|
-
Tool(
|
|
440
|
-
name="acknowledge_missing_keys",
|
|
441
|
-
description=(
|
|
442
|
-
"Batch-resolve a set of missing-event uuids. Use this to clean up "
|
|
443
|
-
"the dashboard after deciding events are noise (typo, dead code, "
|
|
444
|
-
"locale mismatch) — the project's `missing_keys_stats` total drops "
|
|
445
|
-
"by the count returned. Idempotent: rows already resolved are "
|
|
446
|
-
"counted under `already_resolved`; unknown uuids under `not_found`. "
|
|
447
|
-
"Auto-resolution on key creation already covers the happy path; "
|
|
448
|
-
"this is for the events you want to dismiss without creating a key."
|
|
449
|
-
),
|
|
450
|
-
inputSchema={
|
|
451
|
-
"type": "object",
|
|
452
|
-
"properties": {
|
|
453
|
-
"project_uuid": {
|
|
454
|
-
"type": "string",
|
|
455
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
456
|
-
},
|
|
457
|
-
"uuids": {
|
|
458
|
-
"type": "array",
|
|
459
|
-
"items": {"type": "string"},
|
|
460
|
-
"minItems": 1,
|
|
461
|
-
"maxItems": 500,
|
|
462
|
-
"description": "Missing-event uuids to mark resolved.",
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
"required": ["uuids"],
|
|
466
|
-
"additionalProperties": False,
|
|
467
|
-
},
|
|
468
|
-
),
|
|
469
|
-
Tool(
|
|
470
|
-
name="create_namespace",
|
|
471
|
-
description=(
|
|
472
|
-
"Create a namespace (the container for keys). Idempotent on "
|
|
473
|
-
"(project, slug): returns created=false if it already exists. "
|
|
474
|
-
"slug must be lowercase letters/digits with internal hyphens "
|
|
475
|
-
"(no leading/trailing hyphen, no underscore); an invalid slug "
|
|
476
|
-
"returns a slug_invalid error rather than 'namespace not found'. "
|
|
477
|
-
"Use this before create_key/propose_translation when the target "
|
|
478
|
-
"namespace does not exist yet."
|
|
479
|
-
),
|
|
480
|
-
inputSchema={
|
|
481
|
-
"type": "object",
|
|
482
|
-
"properties": {
|
|
483
|
-
"project_uuid": {
|
|
484
|
-
"type": "string",
|
|
485
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
486
|
-
},
|
|
487
|
-
"slug": {
|
|
488
|
-
"type": "string",
|
|
489
|
-
"description": "Namespace slug: lowercase letters/digits + internal hyphens (e.g. common, marketing-site).",
|
|
490
|
-
},
|
|
491
|
-
"name": {"type": "string", "description": "Human-readable namespace name."},
|
|
492
|
-
"description": {"type": "string"},
|
|
493
|
-
},
|
|
494
|
-
"required": ["slug", "name"],
|
|
495
|
-
"additionalProperties": False,
|
|
496
|
-
},
|
|
497
|
-
),
|
|
498
|
-
Tool(
|
|
499
|
-
name="create_key",
|
|
500
|
-
description=(
|
|
501
|
-
"Create a translation key, idempotent on (namespace, name). "
|
|
502
|
-
"Returns created=false if the key already exists. When "
|
|
503
|
-
"source_value is supplied AND the project has a source "
|
|
504
|
-
"language, the value is also written as a source-language "
|
|
505
|
-
"translation in the same call. ALWAYS set `type` to the key's "
|
|
506
|
-
"semantic role (button / link / heading / image / icon / "
|
|
507
|
-
"input_label / …) — do NOT leave it as the default 'text'; the "
|
|
508
|
-
"type drives the key's a11y treatments. See the `type` field for "
|
|
509
|
-
"the full 17-type taxonomy and when to use each."
|
|
510
|
-
),
|
|
511
|
-
inputSchema={
|
|
512
|
-
"type": "object",
|
|
513
|
-
"properties": {
|
|
514
|
-
"project_uuid": {
|
|
515
|
-
"type": "string",
|
|
516
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
517
|
-
},
|
|
518
|
-
"namespace": {
|
|
519
|
-
"type": "string",
|
|
520
|
-
"description": "Namespace slug (LLM-friendly).",
|
|
521
|
-
},
|
|
522
|
-
"name": {
|
|
523
|
-
"type": "string",
|
|
524
|
-
"description": "Dot-notation key name (e.g. hero.title).",
|
|
525
|
-
},
|
|
526
|
-
"source_value": {
|
|
527
|
-
"type": "string",
|
|
528
|
-
"description": "Optional source-language value to seed.",
|
|
529
|
-
},
|
|
530
|
-
"description": {"type": "string"},
|
|
531
|
-
"plurals": {"type": "boolean", "default": False},
|
|
532
|
-
"max_length": {"type": "integer", "minimum": 1},
|
|
533
|
-
"type": {
|
|
534
|
-
"type": "string",
|
|
535
|
-
"enum": list(KEY_TYPES),
|
|
536
|
-
"default": "text",
|
|
537
|
-
"description": "Semantic key type. " + KEY_TYPE_GUIDANCE,
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
"required": ["namespace", "name"],
|
|
541
|
-
"additionalProperties": False,
|
|
542
|
-
},
|
|
543
|
-
),
|
|
544
|
-
Tool(
|
|
545
|
-
name="propose_translation",
|
|
546
|
-
description=(
|
|
547
|
-
"Submit a translation value. Auto-creates the key if it "
|
|
548
|
-
"doesn't exist (write-through). Status is set to draft (or "
|
|
549
|
-
"translated if explicitly requested); reviewer/approved "
|
|
550
|
-
"promotion requires a human reviewer. Identifies the key "
|
|
551
|
-
"by namespace + key name + language_code — no uuids needed. "
|
|
552
|
-
"The response carries `variable_warnings: {missing, extra}` "
|
|
553
|
-
"(null when interpolation variables match the source) — a "
|
|
554
|
-
"non-blocking flag for placeholder drift; check it and fix the "
|
|
555
|
-
"value if set."
|
|
556
|
-
),
|
|
557
|
-
inputSchema={
|
|
558
|
-
"type": "object",
|
|
559
|
-
"properties": {
|
|
560
|
-
"project_uuid": {
|
|
561
|
-
"type": "string",
|
|
562
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
563
|
-
},
|
|
564
|
-
"namespace": {"type": "string"},
|
|
565
|
-
"key": {"type": "string"},
|
|
566
|
-
"language_code": {
|
|
567
|
-
"type": "string",
|
|
568
|
-
"description": "BCP-47 code (e.g. fr, fr-CA, es).",
|
|
569
|
-
},
|
|
570
|
-
"value": {"type": "string"},
|
|
571
|
-
"status": {
|
|
572
|
-
"type": "string",
|
|
573
|
-
"enum": ["draft", "translated"],
|
|
574
|
-
"default": "draft",
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
"required": ["namespace", "key", "language_code", "value"],
|
|
578
|
-
"additionalProperties": False,
|
|
579
|
-
},
|
|
580
|
-
),
|
|
581
|
-
Tool(
|
|
582
|
-
name="create_keys_bulk",
|
|
583
|
-
description=(
|
|
584
|
-
"Batch version of create_key: create up to 500 keys in one "
|
|
585
|
-
"call, idempotent per item (existing keys return created=false). "
|
|
586
|
-
"Returns a per-item results array + a summary roll-up; valid "
|
|
587
|
-
"items commit even if siblings fail. Intra-batch duplicates "
|
|
588
|
-
"(same namespace+name twice) error per item; the whole call is "
|
|
589
|
-
"rejected if the new keys would exceed the org plan cap. NO "
|
|
590
|
-
"server-side translation — this only batches the writes. Use "
|
|
591
|
-
"this instead of N create_key calls for a backlog."
|
|
592
|
-
),
|
|
593
|
-
inputSchema={
|
|
594
|
-
"type": "object",
|
|
595
|
-
"properties": {
|
|
596
|
-
"project_uuid": {
|
|
597
|
-
"type": "string",
|
|
598
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
599
|
-
},
|
|
600
|
-
"items": {
|
|
601
|
-
"type": "array",
|
|
602
|
-
"minItems": 1,
|
|
603
|
-
"maxItems": 500,
|
|
604
|
-
"items": {
|
|
605
|
-
"type": "object",
|
|
606
|
-
"properties": {
|
|
607
|
-
"namespace": {"type": "string", "description": "Namespace slug."},
|
|
608
|
-
"name": {"type": "string", "description": "Dot-notation key name (e.g. hero.title)."},
|
|
609
|
-
"source_value": {"type": "string", "description": "Optional source-language value to seed."},
|
|
610
|
-
"description": {"type": "string"},
|
|
611
|
-
"plurals": {"type": "boolean", "default": False},
|
|
612
|
-
"max_length": {"type": "integer", "minimum": 1},
|
|
613
|
-
"type": {"type": "string", "enum": list(KEY_TYPES), "default": "text", "description": "Semantic key type — set per item by the string's UI role (button / image / heading / …); do NOT default everything to 'text'. See create_key's `type` field for the full taxonomy and why it matters for a11y."},
|
|
614
|
-
},
|
|
615
|
-
"required": ["namespace", "name"],
|
|
616
|
-
"additionalProperties": False,
|
|
617
|
-
},
|
|
618
|
-
},
|
|
619
|
-
},
|
|
620
|
-
"required": ["items"],
|
|
621
|
-
"additionalProperties": False,
|
|
622
|
-
},
|
|
623
|
-
),
|
|
624
|
-
Tool(
|
|
625
|
-
name="update_key",
|
|
626
|
-
description=(
|
|
627
|
-
"Edit a key in place — its SOURCE-language value, its `type`, "
|
|
628
|
-
"and/or its description (pass at least one). Preserves the "
|
|
629
|
-
"key and its history (version bump + history revision + audit). "
|
|
630
|
-
"Address the key by EITHER key_uuid (preferred; from list_keys) "
|
|
631
|
-
"OR namespace + name — exactly one of the two. When the source "
|
|
632
|
-
"value ACTUALLY changes, the key's reviewed/approved target "
|
|
633
|
-
"translations are demoted to needs-review (\"translated\"); "
|
|
634
|
-
"missing/draft/translated targets are untouched, and "
|
|
635
|
-
"stale_flagged reports how many were demoted. Submitting the "
|
|
636
|
-
"current value is a no-op (result=unchanged, not billed). A "
|
|
637
|
-
"real source change (or a description-only change) bills 1 unit."
|
|
638
|
-
),
|
|
639
|
-
inputSchema={
|
|
640
|
-
"type": "object",
|
|
641
|
-
"properties": {
|
|
642
|
-
"project_uuid": {
|
|
643
|
-
"type": "string",
|
|
644
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
645
|
-
},
|
|
646
|
-
"key_uuid": {
|
|
647
|
-
"type": "string",
|
|
648
|
-
"description": "Key uuid to update (preferred addressing; get it from list_keys). Mutually exclusive with namespace+name.",
|
|
649
|
-
},
|
|
650
|
-
"namespace": {
|
|
651
|
-
"type": "string",
|
|
652
|
-
"description": "Namespace slug (use with name as an alternative to key_uuid).",
|
|
653
|
-
},
|
|
654
|
-
"name": {
|
|
655
|
-
"type": "string",
|
|
656
|
-
"description": "Dot-notation key name (use with namespace as an alternative to key_uuid).",
|
|
657
|
-
},
|
|
658
|
-
"source_value": {
|
|
659
|
-
"type": "string",
|
|
660
|
-
"description": "New source-language value to write.",
|
|
661
|
-
},
|
|
662
|
-
"description": {
|
|
663
|
-
"type": "string",
|
|
664
|
-
"description": "Optional: also update the key's description.",
|
|
665
|
-
},
|
|
666
|
-
"type": {
|
|
667
|
-
"type": "string",
|
|
668
|
-
"enum": list(KEY_TYPES),
|
|
669
|
-
"description": "Optional: reclassify the key's semantic type (see create_key). Changing the type re-gates which a11y treatments the key offers.",
|
|
670
|
-
},
|
|
671
|
-
},
|
|
672
|
-
"required": [],
|
|
673
|
-
"additionalProperties": False,
|
|
674
|
-
},
|
|
675
|
-
),
|
|
676
|
-
Tool(
|
|
677
|
-
name="update_keys_bulk",
|
|
678
|
-
description=(
|
|
679
|
-
"Batch version of update_key: edit up to 500 keys' SOURCE "
|
|
680
|
-
"values in one call. Returns a per-item results array + a "
|
|
681
|
-
"summary roll-up; valid items commit even if siblings fail "
|
|
682
|
-
"(status ok | unchanged | error per item). Each item addresses "
|
|
683
|
-
"its key by EITHER key_uuid OR namespace + name (exactly one). "
|
|
684
|
-
"Same staleness, no-op and billing semantics as update_key "
|
|
685
|
-
"(1 unit per key actually changed; no-ops and errors are free). "
|
|
686
|
-
"Use this instead of N update_key calls for a backlog."
|
|
687
|
-
),
|
|
688
|
-
inputSchema={
|
|
689
|
-
"type": "object",
|
|
690
|
-
"properties": {
|
|
691
|
-
"project_uuid": {
|
|
692
|
-
"type": "string",
|
|
693
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
694
|
-
},
|
|
695
|
-
"items": {
|
|
696
|
-
"type": "array",
|
|
697
|
-
"minItems": 1,
|
|
698
|
-
"maxItems": 500,
|
|
699
|
-
"items": {
|
|
700
|
-
"type": "object",
|
|
701
|
-
"properties": {
|
|
702
|
-
"key_uuid": {"type": "string", "description": "Key uuid (preferred). Mutually exclusive with namespace+name."},
|
|
703
|
-
"namespace": {"type": "string", "description": "Namespace slug (use with name)."},
|
|
704
|
-
"name": {"type": "string", "description": "Dot-notation key name (use with namespace)."},
|
|
705
|
-
"source_value": {"type": "string", "description": "New source-language value to write."},
|
|
706
|
-
"description": {"type": "string", "description": "Optional: also update the key's description."},
|
|
707
|
-
"type": {"type": "string", "enum": list(KEY_TYPES), "description": "Optional: reclassify the key's semantic type (see create_key)."},
|
|
708
|
-
},
|
|
709
|
-
"required": [],
|
|
710
|
-
"additionalProperties": False,
|
|
711
|
-
},
|
|
712
|
-
},
|
|
713
|
-
},
|
|
714
|
-
"required": ["items"],
|
|
715
|
-
"additionalProperties": False,
|
|
716
|
-
},
|
|
717
|
-
),
|
|
718
|
-
Tool(
|
|
719
|
-
name="propose_translations_bulk",
|
|
720
|
-
description=(
|
|
721
|
-
"Batch version of propose_translation: submit up to 500 "
|
|
722
|
-
"translation values in one call. Auto-creates missing keys "
|
|
723
|
-
"(write-through). Returns a per-item results array + summary; "
|
|
724
|
-
"an item whose value AND status already match returns "
|
|
725
|
-
"status=unchanged (no-op). Intra-batch duplicates "
|
|
726
|
-
"(same namespace+key+language twice) error per item; status is "
|
|
727
|
-
"draft (or translated if requested), reviewed/approved require "
|
|
728
|
-
"a human reviewer. NO server-side translation — values come "
|
|
729
|
-
"from you. Use this instead of N propose_translation calls. "
|
|
730
|
-
"Each result carries `variable_warnings: {missing, extra}` "
|
|
731
|
-
"(null when interpolation variables match the source) flagging "
|
|
732
|
-
"placeholder drift, non-blocking."
|
|
733
|
-
),
|
|
734
|
-
inputSchema={
|
|
735
|
-
"type": "object",
|
|
736
|
-
"properties": {
|
|
737
|
-
"project_uuid": {
|
|
738
|
-
"type": "string",
|
|
739
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
740
|
-
},
|
|
741
|
-
"items": {
|
|
742
|
-
"type": "array",
|
|
743
|
-
"minItems": 1,
|
|
744
|
-
"maxItems": 500,
|
|
745
|
-
"items": {
|
|
746
|
-
"type": "object",
|
|
747
|
-
"properties": {
|
|
748
|
-
"namespace": {"type": "string"},
|
|
749
|
-
"key": {"type": "string"},
|
|
750
|
-
"language_code": {"type": "string", "description": "BCP-47 code (e.g. fr, fr-CA, es)."},
|
|
751
|
-
"value": {"type": "string"},
|
|
752
|
-
"status": {"type": "string", "enum": ["draft", "translated"], "default": "draft"},
|
|
753
|
-
},
|
|
754
|
-
"required": ["namespace", "key", "language_code", "value"],
|
|
755
|
-
"additionalProperties": False,
|
|
756
|
-
},
|
|
757
|
-
},
|
|
758
|
-
},
|
|
759
|
-
"required": ["items"],
|
|
760
|
-
"additionalProperties": False,
|
|
761
|
-
},
|
|
762
|
-
),
|
|
763
|
-
Tool(
|
|
764
|
-
name="publish_cdn",
|
|
765
|
-
description=(
|
|
766
|
-
"Trigger a CDN release for the project. Builds bundles "
|
|
767
|
-
"for every (language, namespace) combo (or restricted to "
|
|
768
|
-
"the ones you pass) and pushes them to the public CDN. "
|
|
769
|
-
"Idempotent: re-running with no content changes returns "
|
|
770
|
-
"the same bundles as `reused`. Subscribed SDKs receive a "
|
|
771
|
-
"Centrifugo `translations_published` event for every "
|
|
772
|
-
"newly created bundle and can refresh in-place."
|
|
773
|
-
),
|
|
774
|
-
inputSchema={
|
|
775
|
-
"type": "object",
|
|
776
|
-
"properties": {
|
|
777
|
-
"project_uuid": {
|
|
778
|
-
"type": "string",
|
|
779
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
780
|
-
},
|
|
781
|
-
"language_code": {
|
|
782
|
-
"type": "string",
|
|
783
|
-
"description": "Restrict to one BCP-47 language. Omit for ALL.",
|
|
784
|
-
},
|
|
785
|
-
"namespace": {
|
|
786
|
-
"type": "string",
|
|
787
|
-
"description": "Restrict to one namespace slug. Omit for ALL.",
|
|
788
|
-
},
|
|
789
|
-
"version_slug": {
|
|
790
|
-
"type": "string",
|
|
791
|
-
"description": "Project version slug. Defaults to the production version.",
|
|
792
|
-
},
|
|
793
|
-
},
|
|
794
|
-
"additionalProperties": False,
|
|
795
|
-
},
|
|
796
|
-
),
|
|
797
|
-
Tool(
|
|
798
|
-
name="validate_translations",
|
|
799
|
-
description=(
|
|
800
|
-
"Lint a JSON i18next blob against the project: checks for "
|
|
801
|
-
"missing keys, extra keys, and placeholder parity ({var}, "
|
|
802
|
-
"{{var}}, %{var}) vs the source language. Each "
|
|
803
|
-
"`placeholder_mismatches[]` item carries `expected` / `got` plus "
|
|
804
|
-
"`missing` (source variables absent from the translation) and "
|
|
805
|
-
"`extra` (variables in the translation unknown to the source)."
|
|
806
|
-
),
|
|
807
|
-
inputSchema={
|
|
808
|
-
"type": "object",
|
|
809
|
-
"properties": {
|
|
810
|
-
"project_uuid": {
|
|
811
|
-
"type": "string",
|
|
812
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
813
|
-
},
|
|
814
|
-
"language_code": {"type": "string"},
|
|
815
|
-
"namespace": {
|
|
816
|
-
"type": "string",
|
|
817
|
-
"description": "Restrict to one namespace (default: all).",
|
|
818
|
-
},
|
|
819
|
-
"payload": {
|
|
820
|
-
"type": "object",
|
|
821
|
-
"description": "The translation tree to validate.",
|
|
822
|
-
},
|
|
823
|
-
},
|
|
824
|
-
"required": ["language_code", "payload"],
|
|
825
|
-
"additionalProperties": False,
|
|
826
|
-
},
|
|
827
|
-
),
|
|
828
|
-
Tool(
|
|
829
|
-
name="project_context_get",
|
|
830
|
-
description=(
|
|
831
|
-
"Read the project's markdown context document. The document is "
|
|
832
|
-
"free-form prose attached to the project (terminology, brand "
|
|
833
|
-
"voice, constraints, domain — e.g. religious, technical, "
|
|
834
|
-
"medical, gaming, legal) and is intended as ambient context for "
|
|
835
|
-
"human translators AND for AI agents producing translations. "
|
|
836
|
-
"Call this BEFORE batch-translating: prepend the content to your "
|
|
837
|
-
"translation prompts so every output stays consistent with the "
|
|
838
|
-
"project's terminology and tone. Empty content means the project "
|
|
839
|
-
"owner hasn't authored a context yet — that's not an error. "
|
|
840
|
-
"Requires API-key scope: project:read."
|
|
841
|
-
),
|
|
842
|
-
inputSchema={
|
|
843
|
-
"type": "object",
|
|
844
|
-
"properties": {
|
|
845
|
-
"project_uuid": {
|
|
846
|
-
"type": "string",
|
|
847
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
848
|
-
}
|
|
849
|
-
},
|
|
850
|
-
"additionalProperties": False,
|
|
851
|
-
},
|
|
852
|
-
),
|
|
853
|
-
Tool(
|
|
854
|
-
name="project_context_set",
|
|
855
|
-
description=(
|
|
856
|
-
"Upsert the project's markdown context document. Replaces the "
|
|
857
|
-
"entire document — fetch first with project_context_get if you "
|
|
858
|
-
"want to append rather than overwrite. Use this to memorise "
|
|
859
|
-
"terminology choices, glossary entries, domain framing (e.g. "
|
|
860
|
-
"'this is a Catholic-liturgy app — use traditional vocabulary, "
|
|
861
|
-
"avoid neologisms'), tone guidance, and translation constraints "
|
|
862
|
-
"you discover while working the project. Hard cap: 100 KiB. "
|
|
863
|
-
"Requires API-key scope: project:settings:write (narrower than "
|
|
864
|
-
"project:write — settings changes propagate to every translator's "
|
|
865
|
-
"prompt context)."
|
|
866
|
-
),
|
|
867
|
-
inputSchema={
|
|
868
|
-
"type": "object",
|
|
869
|
-
"properties": {
|
|
870
|
-
"project_uuid": {
|
|
871
|
-
"type": "string",
|
|
872
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
873
|
-
},
|
|
874
|
-
"content": {
|
|
875
|
-
"type": "string",
|
|
876
|
-
"description": "New markdown content (UTF-8, ≤ 100 KiB).",
|
|
877
|
-
},
|
|
878
|
-
},
|
|
879
|
-
"required": ["content"],
|
|
880
|
-
"additionalProperties": False,
|
|
881
|
-
},
|
|
882
|
-
),
|
|
883
|
-
Tool(
|
|
884
|
-
name="glossary_list",
|
|
885
|
-
description=(
|
|
886
|
-
"List a project's glossary entries — the terminology rules the "
|
|
887
|
-
"backend applies when matching source strings. Filters: rule_type "
|
|
888
|
-
"(translation | do_not_translate | forbidden), q (case-insensitive "
|
|
889
|
-
"substring on the term), limit (1..500, default 200). Returns "
|
|
890
|
-
"{items: [GlossaryEntry], total}. Matching itself is backend-side; "
|
|
891
|
-
"this only lists the rules."
|
|
892
|
-
),
|
|
893
|
-
inputSchema={
|
|
894
|
-
"type": "object",
|
|
895
|
-
"properties": {
|
|
896
|
-
"project_uuid": {
|
|
897
|
-
"type": "string",
|
|
898
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
899
|
-
},
|
|
900
|
-
"rule_type": {
|
|
901
|
-
"type": "string",
|
|
902
|
-
"enum": ["translation", "do_not_translate", "forbidden"],
|
|
903
|
-
"description": "Filter to one rule type.",
|
|
904
|
-
},
|
|
905
|
-
"q": {
|
|
906
|
-
"type": "string",
|
|
907
|
-
"description": "Case-insensitive substring filter on the term.",
|
|
908
|
-
},
|
|
909
|
-
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 200},
|
|
910
|
-
},
|
|
911
|
-
"additionalProperties": False,
|
|
912
|
-
},
|
|
913
|
-
),
|
|
914
|
-
Tool(
|
|
915
|
-
name="glossary_create",
|
|
916
|
-
description=(
|
|
917
|
-
"Create a glossary entry. rule_type is one of translation | "
|
|
918
|
-
"do_not_translate | forbidden and defaults to 'translation'. "
|
|
919
|
-
"`translations` ({<locale>: value}) is honored only for "
|
|
920
|
-
"rule_type='translation' (cleared otherwise by the backend). "
|
|
921
|
-
"Returns the created GlossaryEntry. Errors 409 if an entry with "
|
|
922
|
-
"the same (rule_type, term) already exists — use glossary_update "
|
|
923
|
-
"to change an existing one."
|
|
924
|
-
),
|
|
925
|
-
inputSchema={
|
|
926
|
-
"type": "object",
|
|
927
|
-
"properties": {
|
|
928
|
-
"project_uuid": {
|
|
929
|
-
"type": "string",
|
|
930
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
931
|
-
},
|
|
932
|
-
"rule_type": {
|
|
933
|
-
"type": "string",
|
|
934
|
-
"enum": ["translation", "do_not_translate", "forbidden"],
|
|
935
|
-
"default": "translation",
|
|
936
|
-
"description": "Rule type. Defaults to 'translation'.",
|
|
937
|
-
},
|
|
938
|
-
"term": {
|
|
939
|
-
"type": "string",
|
|
940
|
-
"description": "The source term/phrase the rule applies to.",
|
|
941
|
-
},
|
|
942
|
-
"translations": {
|
|
943
|
-
"type": "object",
|
|
944
|
-
"description": "Per-locale target translations, e.g. {\"fr\": \"tableau de bord\"}. Only used for rule_type='translation'.",
|
|
945
|
-
},
|
|
946
|
-
"case_sensitive": {
|
|
947
|
-
"type": "boolean",
|
|
948
|
-
"default": False,
|
|
949
|
-
"description": "Match the term case-sensitively.",
|
|
950
|
-
},
|
|
951
|
-
"note": {"type": "string", "description": "Optional human note."},
|
|
952
|
-
},
|
|
953
|
-
"required": ["term"],
|
|
954
|
-
"additionalProperties": False,
|
|
955
|
-
},
|
|
956
|
-
),
|
|
957
|
-
Tool(
|
|
958
|
-
name="glossary_update",
|
|
959
|
-
description=(
|
|
960
|
-
"Update a glossary entry by its uuid (entry_id). Send only the "
|
|
961
|
-
"fields to change: term, translations, case_sensitive, note. "
|
|
962
|
-
"rule_type is immutable — delete and recreate to change it. "
|
|
963
|
-
"Returns the updated GlossaryEntry; 404 if entry_id is unknown, "
|
|
964
|
-
"409 on a term clash with another entry of the same rule_type."
|
|
965
|
-
),
|
|
966
|
-
inputSchema={
|
|
967
|
-
"type": "object",
|
|
968
|
-
"properties": {
|
|
969
|
-
"project_uuid": {
|
|
970
|
-
"type": "string",
|
|
971
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
972
|
-
},
|
|
973
|
-
"entry_id": {
|
|
974
|
-
"type": "string",
|
|
975
|
-
"description": "The GlossaryEntry uuid to update.",
|
|
976
|
-
},
|
|
977
|
-
"term": {"type": "string"},
|
|
978
|
-
"translations": {
|
|
979
|
-
"type": "object",
|
|
980
|
-
"description": "Per-locale target translations (rule_type='translation' only).",
|
|
981
|
-
},
|
|
982
|
-
"case_sensitive": {"type": "boolean"},
|
|
983
|
-
"note": {"type": "string"},
|
|
984
|
-
},
|
|
985
|
-
"required": ["entry_id"],
|
|
986
|
-
"additionalProperties": False,
|
|
987
|
-
},
|
|
988
|
-
),
|
|
989
|
-
Tool(
|
|
990
|
-
name="glossary_delete",
|
|
991
|
-
description=(
|
|
992
|
-
"Delete a glossary entry by its uuid (entry_id). Returns "
|
|
993
|
-
"{deleted: true, entry_id}. A missing entry_id returns 404."
|
|
994
|
-
),
|
|
995
|
-
inputSchema={
|
|
996
|
-
"type": "object",
|
|
997
|
-
"properties": {
|
|
998
|
-
"project_uuid": {
|
|
999
|
-
"type": "string",
|
|
1000
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1001
|
-
},
|
|
1002
|
-
"entry_id": {
|
|
1003
|
-
"type": "string",
|
|
1004
|
-
"description": "The GlossaryEntry uuid to delete.",
|
|
1005
|
-
},
|
|
1006
|
-
},
|
|
1007
|
-
"required": ["entry_id"],
|
|
1008
|
-
"additionalProperties": False,
|
|
1009
|
-
},
|
|
1010
|
-
),
|
|
1011
|
-
Tool(
|
|
1012
|
-
name="health_report",
|
|
1013
|
-
description=(
|
|
1014
|
-
"Read-only SOURCE-HEALTH report for the project (#752): issues in the "
|
|
1015
|
-
"source strings with by_issue / by_severity rollups + per-key items. "
|
|
1016
|
-
"Defaults to the production version; pass version_id for a specific "
|
|
1017
|
-
"version. Surfaced verbatim (read-only)."
|
|
1018
|
-
),
|
|
1019
|
-
inputSchema={
|
|
1020
|
-
"type": "object",
|
|
1021
|
-
"properties": {
|
|
1022
|
-
"project_uuid": {
|
|
1023
|
-
"type": "string",
|
|
1024
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1025
|
-
},
|
|
1026
|
-
"version_id": {
|
|
1027
|
-
"type": "string",
|
|
1028
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1029
|
-
},
|
|
1030
|
-
},
|
|
1031
|
-
"additionalProperties": False,
|
|
1032
|
-
},
|
|
1033
|
-
),
|
|
1034
|
-
Tool(
|
|
1035
|
-
name="coverage_report",
|
|
1036
|
-
description=(
|
|
1037
|
-
"Read-only translation-COVERAGE report for the project (#752): global "
|
|
1038
|
-
"completeness, total key count, and per-language coverage. Defaults to "
|
|
1039
|
-
"the production version; pass version_id for a specific version. "
|
|
1040
|
-
"Surfaced verbatim (read-only)."
|
|
1041
|
-
),
|
|
1042
|
-
inputSchema={
|
|
1043
|
-
"type": "object",
|
|
1044
|
-
"properties": {
|
|
1045
|
-
"project_uuid": {
|
|
1046
|
-
"type": "string",
|
|
1047
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1048
|
-
},
|
|
1049
|
-
"version_id": {
|
|
1050
|
-
"type": "string",
|
|
1051
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1052
|
-
},
|
|
1053
|
-
},
|
|
1054
|
-
"additionalProperties": False,
|
|
1055
|
-
},
|
|
1056
|
-
),
|
|
1057
|
-
Tool(
|
|
1058
|
-
name="delete_keys_bulk",
|
|
1059
|
-
description=(
|
|
1060
|
-
"Soft-delete (move to TRASH) up to 500 keys by their key_uuid. "
|
|
1061
|
-
"Restorable via restore_keys_bulk. Idempotent: already-trashed keys "
|
|
1062
|
-
"are reported unchanged, unknown uuids not_found. Trashed keys "
|
|
1063
|
-
"disappear from list_keys, list_untranslated_keys, the CDN, and the "
|
|
1064
|
-
"reports. Get key_uuid from list_keys. NO hard-delete/purge over MCP "
|
|
1065
|
-
"(purge is human-only via the dashboard)."
|
|
1066
|
-
),
|
|
1067
|
-
inputSchema={
|
|
1068
|
-
"type": "object",
|
|
1069
|
-
"properties": {
|
|
1070
|
-
"project_uuid": {
|
|
1071
|
-
"type": "string",
|
|
1072
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1073
|
-
},
|
|
1074
|
-
"key_uuids": {
|
|
1075
|
-
"type": "array",
|
|
1076
|
-
"items": {"type": "string"},
|
|
1077
|
-
"minItems": 1,
|
|
1078
|
-
"maxItems": 500,
|
|
1079
|
-
"description": "Key uuids to trash (get them from list_keys).",
|
|
1080
|
-
},
|
|
1081
|
-
},
|
|
1082
|
-
"required": ["key_uuids"],
|
|
1083
|
-
"additionalProperties": False,
|
|
1084
|
-
},
|
|
1085
|
-
),
|
|
1086
|
-
Tool(
|
|
1087
|
-
name="restore_keys_bulk",
|
|
1088
|
-
description=(
|
|
1089
|
-
"Restore up to 500 trashed keys by their key_uuid (from list_trash). "
|
|
1090
|
-
"Idempotent: already-active keys are reported unchanged, unknown uuids "
|
|
1091
|
-
"not_found; a key whose name was re-created while it was trashed is "
|
|
1092
|
-
"reported as a conflict. Get key_uuid from list_trash."
|
|
1093
|
-
),
|
|
1094
|
-
inputSchema={
|
|
1095
|
-
"type": "object",
|
|
1096
|
-
"properties": {
|
|
1097
|
-
"project_uuid": {
|
|
1098
|
-
"type": "string",
|
|
1099
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1100
|
-
},
|
|
1101
|
-
"key_uuids": {
|
|
1102
|
-
"type": "array",
|
|
1103
|
-
"items": {"type": "string"},
|
|
1104
|
-
"minItems": 1,
|
|
1105
|
-
"maxItems": 500,
|
|
1106
|
-
"description": "Key uuids to restore (get them from list_trash).",
|
|
1107
|
-
},
|
|
1108
|
-
},
|
|
1109
|
-
"required": ["key_uuids"],
|
|
1110
|
-
"additionalProperties": False,
|
|
1111
|
-
},
|
|
1112
|
-
),
|
|
1113
|
-
Tool(
|
|
1114
|
-
name="list_trash",
|
|
1115
|
-
description=(
|
|
1116
|
-
"List trashed (soft-deleted) keys: items of {key_uuid, namespace, "
|
|
1117
|
-
"name, deleted_at}, cursor-paginated. Feed key_uuid to "
|
|
1118
|
-
"restore_keys_bulk to bring them back."
|
|
1119
|
-
),
|
|
1120
|
-
inputSchema={
|
|
1121
|
-
"type": "object",
|
|
1122
|
-
"properties": {
|
|
1123
|
-
"project_uuid": {
|
|
1124
|
-
"type": "string",
|
|
1125
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1126
|
-
},
|
|
1127
|
-
"cursor": {"type": "string"},
|
|
1128
|
-
"limit": {"type": "integer", "minimum": 1, "maximum": 200, "default": 50},
|
|
1129
|
-
},
|
|
1130
|
-
"additionalProperties": False,
|
|
1131
|
-
},
|
|
1132
|
-
),
|
|
1133
|
-
Tool(
|
|
1134
|
-
name="a11y_estimate",
|
|
1135
|
-
description=(
|
|
1136
|
-
"Preview the CREDIT cost of an a11y generate/translate run BEFORE "
|
|
1137
|
-
"spending anything (task 990). Returns {units, credit_cost_per_unit, "
|
|
1138
|
-
"credits_required, balance, sufficient}. ALWAYS call this before "
|
|
1139
|
-
"generate_a11y_variant / translate_a11y_variants so you know the cost "
|
|
1140
|
-
"and whether the balance covers it. mode=generate counts missing "
|
|
1141
|
-
"(key, surface) source variants; mode=translate counts "
|
|
1142
|
-
"(key, surface, target-language) cells lacking a translation."
|
|
1143
|
-
),
|
|
1144
|
-
inputSchema={
|
|
1145
|
-
"type": "object",
|
|
1146
|
-
"properties": {
|
|
1147
|
-
"project_uuid": {
|
|
1148
|
-
"type": "string",
|
|
1149
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1150
|
-
},
|
|
1151
|
-
"mode": {
|
|
1152
|
-
"type": "string",
|
|
1153
|
-
"enum": ["generate", "translate"],
|
|
1154
|
-
"description": "generate = source-language a11y variants; translate = propagate existing a11y overrides into target locales.",
|
|
1155
|
-
},
|
|
1156
|
-
"tier": {
|
|
1157
|
-
"type": "string",
|
|
1158
|
-
"enum": ["standard", "premium"],
|
|
1159
|
-
"default": "standard",
|
|
1160
|
-
"description": "Quality tier (credit_cost per unit: standard=1, premium=4).",
|
|
1161
|
-
},
|
|
1162
|
-
"surfaces": {
|
|
1163
|
-
"type": "array",
|
|
1164
|
-
"items": {"type": "string", "enum": list(A11Y_SURFACES)},
|
|
1165
|
-
"description": "a11y surfaces to scope to. Omit/empty = all four (aria_label, alt_text, screen_reader, plain_language).",
|
|
1166
|
-
},
|
|
1167
|
-
"language_codes": {
|
|
1168
|
-
"type": "array",
|
|
1169
|
-
"items": {"type": "string"},
|
|
1170
|
-
"description": "Target locales (translate mode only), e.g. ['fr','es'].",
|
|
1171
|
-
},
|
|
1172
|
-
"key_uuids": {
|
|
1173
|
-
"type": "array",
|
|
1174
|
-
"items": {"type": "string"},
|
|
1175
|
-
"description": "Restrict to these keys (get key_uuid from list_keys). Omit to scope the whole project (or a namespace).",
|
|
1176
|
-
},
|
|
1177
|
-
"namespace_uuid": {
|
|
1178
|
-
"type": "string",
|
|
1179
|
-
"description": "Restrict to one namespace by its UUID.",
|
|
1180
|
-
},
|
|
1181
|
-
},
|
|
1182
|
-
"required": ["mode"],
|
|
1183
|
-
"additionalProperties": False,
|
|
1184
|
-
},
|
|
1185
|
-
),
|
|
1186
|
-
Tool(
|
|
1187
|
-
name="generate_a11y_variant",
|
|
1188
|
-
description=(
|
|
1189
|
-
"AI-generate SOURCE-language a11y variants for keys that lack them "
|
|
1190
|
-
"(task 990). aria_label / screen_reader / plain_language are derived "
|
|
1191
|
-
"from the key's visible text; alt_text from the key's context/"
|
|
1192
|
-
"description. Only fills (key, surface) cells with NO existing override "
|
|
1193
|
-
"and an available source. BILLS CREDITS (standard=1, premium=4 per "
|
|
1194
|
-
"generated item, debit upfront + refund undelivered) — call a11y_estimate "
|
|
1195
|
-
"first. Results are draft (bot-authored) overrides that flow to the CDN "
|
|
1196
|
-
"a11y overlays. Returns {generated, requested, credits_charged, "
|
|
1197
|
-
"credits_refunded}."
|
|
1198
|
-
),
|
|
1199
|
-
inputSchema={
|
|
1200
|
-
"type": "object",
|
|
1201
|
-
"properties": {
|
|
1202
|
-
"project_uuid": {
|
|
1203
|
-
"type": "string",
|
|
1204
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1205
|
-
},
|
|
1206
|
-
"tier": {
|
|
1207
|
-
"type": "string",
|
|
1208
|
-
"enum": ["standard", "premium"],
|
|
1209
|
-
"default": "standard",
|
|
1210
|
-
"description": "Quality tier (credit_cost per unit: standard=1, premium=4).",
|
|
1211
|
-
},
|
|
1212
|
-
"surfaces": {
|
|
1213
|
-
"type": "array",
|
|
1214
|
-
"items": {"type": "string", "enum": list(A11Y_SURFACES)},
|
|
1215
|
-
"description": "a11y surfaces to generate. Omit/empty = all four.",
|
|
1216
|
-
},
|
|
1217
|
-
"key_uuids": {
|
|
1218
|
-
"type": "array",
|
|
1219
|
-
"items": {"type": "string"},
|
|
1220
|
-
"description": "Restrict to these keys (get key_uuid from list_keys). Omit to scope the whole project (or a namespace).",
|
|
1221
|
-
},
|
|
1222
|
-
"namespace_uuid": {
|
|
1223
|
-
"type": "string",
|
|
1224
|
-
"description": "Restrict to one namespace by its UUID.",
|
|
1225
|
-
},
|
|
1226
|
-
"idempotency_key": {
|
|
1227
|
-
"type": "string",
|
|
1228
|
-
"description": "Stable token making this billable call safe to retry: reuse the SAME value to avoid a double-charge on retry. A random one is generated if omitted (so a blind retry would re-bill).",
|
|
1229
|
-
},
|
|
1230
|
-
},
|
|
1231
|
-
"additionalProperties": False,
|
|
1232
|
-
},
|
|
1233
|
-
),
|
|
1234
|
-
Tool(
|
|
1235
|
-
name="translate_a11y_variants",
|
|
1236
|
-
description=(
|
|
1237
|
-
"Translate EXISTING source-language a11y overrides into target locales "
|
|
1238
|
-
"(task 990). Only translates (key, surface) that HAVE a source override, "
|
|
1239
|
-
"into the requested languages that lack one. BILLS CREDITS "
|
|
1240
|
-
"(standard=1, premium=4 per item, debit upfront + refund undelivered) — "
|
|
1241
|
-
"call a11y_estimate (mode=translate) first. Results are draft "
|
|
1242
|
-
"(bot-authored) overrides that flow to the CDN a11y overlays. Returns "
|
|
1243
|
-
"{translated, requested, credits_charged, credits_refunded}."
|
|
1244
|
-
),
|
|
1245
|
-
inputSchema={
|
|
1246
|
-
"type": "object",
|
|
1247
|
-
"properties": {
|
|
1248
|
-
"project_uuid": {
|
|
1249
|
-
"type": "string",
|
|
1250
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1251
|
-
},
|
|
1252
|
-
"tier": {
|
|
1253
|
-
"type": "string",
|
|
1254
|
-
"enum": ["standard", "premium"],
|
|
1255
|
-
"default": "standard",
|
|
1256
|
-
"description": "Quality tier (credit_cost per unit: standard=1, premium=4).",
|
|
1257
|
-
},
|
|
1258
|
-
"language_codes": {
|
|
1259
|
-
"type": "array",
|
|
1260
|
-
"items": {"type": "string"},
|
|
1261
|
-
"minItems": 1,
|
|
1262
|
-
"description": "Target locales to translate into, e.g. ['fr','es'].",
|
|
1263
|
-
},
|
|
1264
|
-
"surfaces": {
|
|
1265
|
-
"type": "array",
|
|
1266
|
-
"items": {"type": "string", "enum": list(A11Y_SURFACES)},
|
|
1267
|
-
"description": "a11y surfaces to translate. Omit/empty = all four.",
|
|
1268
|
-
},
|
|
1269
|
-
"key_uuids": {
|
|
1270
|
-
"type": "array",
|
|
1271
|
-
"items": {"type": "string"},
|
|
1272
|
-
"description": "Restrict to these keys (get key_uuid from list_keys). Omit to scope the whole project (or a namespace).",
|
|
1273
|
-
},
|
|
1274
|
-
"namespace_uuid": {
|
|
1275
|
-
"type": "string",
|
|
1276
|
-
"description": "Restrict to one namespace by its UUID.",
|
|
1277
|
-
},
|
|
1278
|
-
"idempotency_key": {
|
|
1279
|
-
"type": "string",
|
|
1280
|
-
"description": "Stable token making this billable call safe to retry: reuse the SAME value to avoid a double-charge on retry. A random one is generated if omitted (so a blind retry would re-bill).",
|
|
1281
|
-
},
|
|
1282
|
-
},
|
|
1283
|
-
"required": ["language_codes"],
|
|
1284
|
-
"additionalProperties": False,
|
|
1285
|
-
},
|
|
1286
|
-
),
|
|
1287
|
-
Tool(
|
|
1288
|
-
name="a11y_report",
|
|
1289
|
-
description=(
|
|
1290
|
-
"Read-only WCAG ACCESSIBILITY quality report for the project "
|
|
1291
|
-
"(task 991): per key/surface/locale a11y gaps with by_gap / "
|
|
1292
|
-
"by_severity / by_surface rollups + per-item details. Gap types: "
|
|
1293
|
-
"a11y_untranslated (a source a11y override exists but this target "
|
|
1294
|
-
"locale lacks it), alt_missing (image key with no source alt_text), "
|
|
1295
|
-
"reading_level_high (hard source text + no plain_language), "
|
|
1296
|
-
"a11y_variant_absent (a require_surface is missing in the source). "
|
|
1297
|
-
"Gaps are TYPE-AWARE: only treatments relevant to each key's type are "
|
|
1298
|
-
"reported (e.g. alt_missing only on image keys, aria gaps only on "
|
|
1299
|
-
"buttons/links/inputs), so a text key is never flagged for aria/alt. "
|
|
1300
|
-
"Defaults to the production version. Surfaced verbatim — use "
|
|
1301
|
-
"list_a11y_gaps for just the actionable item list."
|
|
1302
|
-
),
|
|
1303
|
-
inputSchema={
|
|
1304
|
-
"type": "object",
|
|
1305
|
-
"properties": {
|
|
1306
|
-
"project_uuid": {
|
|
1307
|
-
"type": "string",
|
|
1308
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1309
|
-
},
|
|
1310
|
-
"version_id": {
|
|
1311
|
-
"type": "string",
|
|
1312
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1313
|
-
},
|
|
1314
|
-
"require_surface": {
|
|
1315
|
-
"type": "array",
|
|
1316
|
-
"items": {"type": "string", "enum": list(A11Y_SURFACES)},
|
|
1317
|
-
"description": "a11y surfaces to additionally flag as ABSENT in the source (produces a11y_variant_absent gaps), e.g. ['aria_label','alt_text'].",
|
|
1318
|
-
},
|
|
1319
|
-
"reading_level_max_words": {
|
|
1320
|
-
"type": "integer",
|
|
1321
|
-
"minimum": 5,
|
|
1322
|
-
"maximum": 60,
|
|
1323
|
-
"description": "Readability heuristic threshold (sentence length) for reading_level_high gaps. Default 15 server-side.",
|
|
1324
|
-
},
|
|
1325
|
-
},
|
|
1326
|
-
"additionalProperties": False,
|
|
1327
|
-
},
|
|
1328
|
-
),
|
|
1329
|
-
Tool(
|
|
1330
|
-
name="list_a11y_gaps",
|
|
1331
|
-
description=(
|
|
1332
|
-
"List the actionable a11y gaps (the items[] of a11y_report, task "
|
|
1333
|
-
"991), optionally filtered client-side. Each gap = {key_uuid, "
|
|
1334
|
-
"key_name, namespace_slug, surface, locale, gap, severity, detail}. "
|
|
1335
|
-
"To find keys/surfaces with NO a11y variant in the source, pass "
|
|
1336
|
-
"require_surface=<surface> and filter gap='a11y_variant_absent'. "
|
|
1337
|
-
"Feed key_uuid + surface (+ locale) into set_a11y_variant to fix one, "
|
|
1338
|
-
"or generate_a11y_variant / translate_a11y_variants to fill them in "
|
|
1339
|
-
"bulk."
|
|
1340
|
-
),
|
|
1341
|
-
inputSchema={
|
|
1342
|
-
"type": "object",
|
|
1343
|
-
"properties": {
|
|
1344
|
-
"project_uuid": {
|
|
1345
|
-
"type": "string",
|
|
1346
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1347
|
-
},
|
|
1348
|
-
"gap": {
|
|
1349
|
-
"type": "string",
|
|
1350
|
-
"enum": [
|
|
1351
|
-
"a11y_untranslated",
|
|
1352
|
-
"alt_missing",
|
|
1353
|
-
"reading_level_high",
|
|
1354
|
-
"a11y_variant_absent",
|
|
1355
|
-
],
|
|
1356
|
-
"description": "Client-side filter: keep only gaps of this type.",
|
|
1357
|
-
},
|
|
1358
|
-
"surface": {
|
|
1359
|
-
"type": "string",
|
|
1360
|
-
"enum": list(A11Y_SURFACES),
|
|
1361
|
-
"description": "Client-side filter: keep only gaps on this a11y surface.",
|
|
1362
|
-
},
|
|
1363
|
-
"locale": {
|
|
1364
|
-
"type": "string",
|
|
1365
|
-
"description": "Client-side filter: keep only gaps for this target locale code (e.g. 'fr'). Source-level gaps have locale=null.",
|
|
1366
|
-
},
|
|
1367
|
-
"require_surface": {
|
|
1368
|
-
"type": "array",
|
|
1369
|
-
"items": {"type": "string", "enum": list(A11Y_SURFACES)},
|
|
1370
|
-
"description": "a11y surfaces to flag as ABSENT in the source (produces a11y_variant_absent gaps). Pair with gap='a11y_variant_absent' to list keys missing a surface.",
|
|
1371
|
-
},
|
|
1372
|
-
"version_id": {
|
|
1373
|
-
"type": "string",
|
|
1374
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1375
|
-
},
|
|
1376
|
-
"reading_level_max_words": {
|
|
1377
|
-
"type": "integer",
|
|
1378
|
-
"minimum": 5,
|
|
1379
|
-
"maximum": 60,
|
|
1380
|
-
"description": "Readability heuristic threshold for reading_level_high gaps. Default 15 server-side.",
|
|
1381
|
-
},
|
|
1382
|
-
},
|
|
1383
|
-
"additionalProperties": False,
|
|
1384
|
-
},
|
|
1385
|
-
),
|
|
1386
|
-
Tool(
|
|
1387
|
-
name="list_untranslated_surfaces",
|
|
1388
|
-
description=(
|
|
1389
|
-
"The TRANSLATION MAP: every a11y surface cell that has an APPROVED "
|
|
1390
|
-
"SOURCE value but is missing the target-language translation, WITH that "
|
|
1391
|
-
"source value to translate from. Returns {cells: [{key_uuid, key_name, "
|
|
1392
|
-
"namespace_slug, key_type, surface, language_code, source_value}]}. Use "
|
|
1393
|
-
"this to fill a11y surfaces (aria_label / alt_text / screen_reader / "
|
|
1394
|
-
"plain_language) by FAITHFULLY translating each source_value into the "
|
|
1395
|
-
"missing language — do NOT invent new content — then write each with "
|
|
1396
|
-
"set_a11y_variant(key_uuid, language_code, surface, <translation>). "
|
|
1397
|
-
"Optionally filter by surface and/or language_code. (Local-first: you "
|
|
1398
|
-
"translate; 0 AI credits. The billed server path is translate_a11y_variants.)"
|
|
1399
|
-
),
|
|
1400
|
-
inputSchema={
|
|
1401
|
-
"type": "object",
|
|
1402
|
-
"properties": {
|
|
1403
|
-
"project_uuid": {
|
|
1404
|
-
"type": "string",
|
|
1405
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1406
|
-
},
|
|
1407
|
-
"surface": {
|
|
1408
|
-
"type": "string",
|
|
1409
|
-
"enum": list(A11Y_SURFACES),
|
|
1410
|
-
"description": "Optional: only this a11y surface.",
|
|
1411
|
-
},
|
|
1412
|
-
"language_code": {
|
|
1413
|
-
"type": "string",
|
|
1414
|
-
"description": "Optional: only this target language (BCP-47).",
|
|
1415
|
-
},
|
|
1416
|
-
"version_id": {
|
|
1417
|
-
"type": "string",
|
|
1418
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1419
|
-
},
|
|
1420
|
-
},
|
|
1421
|
-
"additionalProperties": False,
|
|
1422
|
-
},
|
|
1423
|
-
),
|
|
1424
|
-
Tool(
|
|
1425
|
-
name="set_a11y_variant",
|
|
1426
|
-
description=(
|
|
1427
|
-
"Upsert ONE a11y variant override for (key, language, surface) — the "
|
|
1428
|
-
"manual fix for a gap (a11y V1). surface ∈ aria_label | alt_text | "
|
|
1429
|
-
"screen_reader | plain_language. The value is a TEXT string (the "
|
|
1430
|
-
"semantic a11y text, e.g. the accessible name or the simplified "
|
|
1431
|
-
"wording), translatable later via translate_a11y_variants. Address "
|
|
1432
|
-
"the key by key_uuid and the language by its locale CODE — exactly "
|
|
1433
|
-
"the (key_uuid, surface, locale) a gap carries in a11y_report, so you "
|
|
1434
|
-
"can fix it without resolving any UUID. Stored as a draft (bot) "
|
|
1435
|
-
"override. Returns {key_uuid, surface, language_code, value, status}."
|
|
1436
|
-
),
|
|
1437
|
-
inputSchema={
|
|
1438
|
-
"type": "object",
|
|
1439
|
-
"properties": {
|
|
1440
|
-
"project_uuid": {
|
|
1441
|
-
"type": "string",
|
|
1442
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1443
|
-
},
|
|
1444
|
-
"key_uuid": {
|
|
1445
|
-
"type": "string",
|
|
1446
|
-
"description": "Key UUID (from list_keys or a11y_report / list_a11y_gaps items).",
|
|
1447
|
-
},
|
|
1448
|
-
"language_code": {
|
|
1449
|
-
"type": "string",
|
|
1450
|
-
"description": "Target locale CODE, e.g. 'fr' (the `locale` field of an a11y_report item). The backend resolves it to the language; 404 if unknown.",
|
|
1451
|
-
},
|
|
1452
|
-
"surface": {
|
|
1453
|
-
"type": "string",
|
|
1454
|
-
"enum": list(A11Y_SURFACES),
|
|
1455
|
-
"description": "Which a11y surface to set.",
|
|
1456
|
-
},
|
|
1457
|
-
"value": {
|
|
1458
|
-
"type": "string",
|
|
1459
|
-
"description": "The a11y text value for this (key, language, surface).",
|
|
1460
|
-
},
|
|
1461
|
-
},
|
|
1462
|
-
"required": ["key_uuid", "language_code", "surface", "value"],
|
|
1463
|
-
"additionalProperties": False,
|
|
1464
|
-
},
|
|
1465
|
-
),
|
|
1466
|
-
Tool(
|
|
1467
|
-
name="delete_a11y_variant",
|
|
1468
|
-
description=(
|
|
1469
|
-
"Delete ONE a11y variant override for (key, language, surface) "
|
|
1470
|
-
"(a11y V1). The cell re-inherits the base value per the locale/surface "
|
|
1471
|
-
"fallback chain; a 404 means there was no override to remove. Address "
|
|
1472
|
-
"by key_uuid + language_code + surface (same as set_a11y_variant)."
|
|
1473
|
-
),
|
|
1474
|
-
inputSchema={
|
|
1475
|
-
"type": "object",
|
|
1476
|
-
"properties": {
|
|
1477
|
-
"project_uuid": {
|
|
1478
|
-
"type": "string",
|
|
1479
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1480
|
-
},
|
|
1481
|
-
"key_uuid": {
|
|
1482
|
-
"type": "string",
|
|
1483
|
-
"description": "Key UUID (from list_keys or a11y_report / list_a11y_gaps items).",
|
|
1484
|
-
},
|
|
1485
|
-
"language_code": {
|
|
1486
|
-
"type": "string",
|
|
1487
|
-
"description": "Target locale CODE, e.g. 'fr' (the `locale` field of an a11y_report item).",
|
|
1488
|
-
},
|
|
1489
|
-
"surface": {
|
|
1490
|
-
"type": "string",
|
|
1491
|
-
"enum": list(A11Y_SURFACES),
|
|
1492
|
-
"description": "Which a11y surface to clear.",
|
|
1493
|
-
},
|
|
1494
|
-
},
|
|
1495
|
-
"required": ["key_uuid", "language_code", "surface"],
|
|
1496
|
-
"additionalProperties": False,
|
|
1497
|
-
},
|
|
1498
|
-
),
|
|
1499
|
-
Tool(
|
|
1500
|
-
name="list_surfaces",
|
|
1501
|
-
description=(
|
|
1502
|
-
"List the project's configurable SURFACES (task 928): the device "
|
|
1503
|
-
"and a11y surfaces variants can be authored for. Returns "
|
|
1504
|
-
"{surfaces:[{slug, label, kind, builtin, active}]} where kind is "
|
|
1505
|
-
"'device' or 'a11y'. An un-customized project lazily defaults to the "
|
|
1506
|
-
"builtins — device: desktop/mobile/tablet; a11y: aria_label/alt_text/"
|
|
1507
|
-
"screen_reader/plain_language — all active. Only ACTIVE surfaces accept "
|
|
1508
|
-
"variant writes and get published to the CDN."
|
|
1509
|
-
),
|
|
1510
|
-
inputSchema={
|
|
1511
|
-
"type": "object",
|
|
1512
|
-
"properties": {
|
|
1513
|
-
"project_uuid": {
|
|
1514
|
-
"type": "string",
|
|
1515
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1516
|
-
},
|
|
1517
|
-
},
|
|
1518
|
-
"additionalProperties": False,
|
|
1519
|
-
},
|
|
1520
|
-
),
|
|
1521
|
-
Tool(
|
|
1522
|
-
name="create_surface",
|
|
1523
|
-
description=(
|
|
1524
|
-
"Create a CUSTOM DEVICE surface for the project (task 928), e.g. "
|
|
1525
|
-
"'watch', 'tv', 'kiosk'. Device surfaces are extensible; a11y surfaces "
|
|
1526
|
-
"are a FIXED semantic set — creating an a11y slug (e.g. aria_label) is "
|
|
1527
|
-
"rejected with 422. Returns the updated surface catalog. Builtins need "
|
|
1528
|
-
"no creation (they exist lazily)."
|
|
1529
|
-
),
|
|
1530
|
-
inputSchema={
|
|
1531
|
-
"type": "object",
|
|
1532
|
-
"properties": {
|
|
1533
|
-
"project_uuid": {
|
|
1534
|
-
"type": "string",
|
|
1535
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1536
|
-
},
|
|
1537
|
-
"slug": {
|
|
1538
|
-
"type": "string",
|
|
1539
|
-
"pattern": "^[a-z0-9_-]{1,40}$",
|
|
1540
|
-
"description": "Surface slug: lowercase letters, digits, '_' or '-', 1-40 chars (e.g. 'watch').",
|
|
1541
|
-
},
|
|
1542
|
-
"label": {
|
|
1543
|
-
"type": "string",
|
|
1544
|
-
"description": "Optional human-readable label (defaults from the slug).",
|
|
1545
|
-
},
|
|
1546
|
-
},
|
|
1547
|
-
"required": ["slug"],
|
|
1548
|
-
"additionalProperties": False,
|
|
1549
|
-
},
|
|
1550
|
-
),
|
|
1551
|
-
Tool(
|
|
1552
|
-
name="update_surface",
|
|
1553
|
-
description=(
|
|
1554
|
-
"Rename and/or toggle a surface (task 928): set its `label` and/or "
|
|
1555
|
-
"`active`. Deactivating is SOFT — existing variants are RETAINED but "
|
|
1556
|
-
"hidden and dropped from the CDN until reactivated (which republishes "
|
|
1557
|
-
"them); the response `affected_variants` reports how many are impacted. "
|
|
1558
|
-
"Builtins can be toggled/renamed but not deleted. Returns "
|
|
1559
|
-
"{surfaces, affected_variants}."
|
|
1560
|
-
),
|
|
1561
|
-
inputSchema={
|
|
1562
|
-
"type": "object",
|
|
1563
|
-
"properties": {
|
|
1564
|
-
"project_uuid": {
|
|
1565
|
-
"type": "string",
|
|
1566
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1567
|
-
},
|
|
1568
|
-
"slug": {
|
|
1569
|
-
"type": "string",
|
|
1570
|
-
"description": "Slug of the surface to update.",
|
|
1571
|
-
},
|
|
1572
|
-
"label": {
|
|
1573
|
-
"type": "string",
|
|
1574
|
-
"description": "New human-readable label (rename).",
|
|
1575
|
-
},
|
|
1576
|
-
"active": {
|
|
1577
|
-
"type": "boolean",
|
|
1578
|
-
"description": "Activate (true) or soft-deactivate (false) the surface.",
|
|
1579
|
-
},
|
|
1580
|
-
},
|
|
1581
|
-
"required": ["slug"],
|
|
1582
|
-
"additionalProperties": False,
|
|
1583
|
-
},
|
|
1584
|
-
),
|
|
1585
|
-
Tool(
|
|
1586
|
-
name="delete_surface",
|
|
1587
|
-
description=(
|
|
1588
|
-
"Delete a CUSTOM surface (task 928). Builtins cannot be deleted — "
|
|
1589
|
-
"deactivate them with update_surface(active=false) instead (a 422/409 "
|
|
1590
|
-
"is returned if you try to delete a builtin). Deleting a custom surface "
|
|
1591
|
-
"removes it from the catalog."
|
|
1592
|
-
),
|
|
1593
|
-
inputSchema={
|
|
1594
|
-
"type": "object",
|
|
1595
|
-
"properties": {
|
|
1596
|
-
"project_uuid": {
|
|
1597
|
-
"type": "string",
|
|
1598
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1599
|
-
},
|
|
1600
|
-
"slug": {
|
|
1601
|
-
"type": "string",
|
|
1602
|
-
"description": "Slug of the custom surface to delete.",
|
|
1603
|
-
},
|
|
1604
|
-
},
|
|
1605
|
-
"required": ["slug"],
|
|
1606
|
-
"additionalProperties": False,
|
|
1607
|
-
},
|
|
1608
|
-
),
|
|
1609
|
-
Tool(
|
|
1610
|
-
name="recommend_surfaces",
|
|
1611
|
-
description=(
|
|
1612
|
-
"Read-only per-key A11Y SURFACE RECOMMENDATIONS (#2): for each key, "
|
|
1613
|
-
"the a11y surfaces implied by its semantic TYPE, derived from the "
|
|
1614
|
-
"backend's authoritative key.type -> treatment mapping "
|
|
1615
|
-
"(A11Y_TREATMENTS_BY_TYPE) — the single source of truth; do NOT "
|
|
1616
|
-
"reimplement the mapping in the client. Returns {project_uuid, "
|
|
1617
|
-
"version_uuid, active_a11y_surfaces[], keys_scanned, keys_with_gaps, "
|
|
1618
|
-
"gaps_by_surface{surface:int}, items:[{key_uuid, key_name, "
|
|
1619
|
-
"namespace_slug, type, base_role (visible_text|alt_text|"
|
|
1620
|
-
"accessible_name), recommended_surfaces:[{surface, active, "
|
|
1621
|
-
"present_in_source}], has_gap}]}. A recommended surface that is "
|
|
1622
|
-
"active && !present_in_source is a config-satisfied GAP to AUTHOR — "
|
|
1623
|
-
"hand the actual text off to sonenta-a11y (set_a11y_variant), this "
|
|
1624
|
-
"tool only tells you WHERE. DEVICE surfaces are not included here. "
|
|
1625
|
-
"Defaults to the production version."
|
|
1626
|
-
),
|
|
1627
|
-
inputSchema={
|
|
1628
|
-
"type": "object",
|
|
1629
|
-
"properties": {
|
|
1630
|
-
"project_uuid": {
|
|
1631
|
-
"type": "string",
|
|
1632
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1633
|
-
},
|
|
1634
|
-
"version_id": {
|
|
1635
|
-
"type": "string",
|
|
1636
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1637
|
-
},
|
|
1638
|
-
"namespace_id": {
|
|
1639
|
-
"type": "string",
|
|
1640
|
-
"description": "Optional namespace UUID to scope the scan.",
|
|
1641
|
-
},
|
|
1642
|
-
"key_id": {
|
|
1643
|
-
"type": "string",
|
|
1644
|
-
"description": "Optional single key UUID to scope the scan.",
|
|
1645
|
-
},
|
|
1646
|
-
"only_gaps": {
|
|
1647
|
-
"type": "boolean",
|
|
1648
|
-
"description": "When true, return only keys that have a gap (has_gap=true).",
|
|
1649
|
-
},
|
|
1650
|
-
},
|
|
1651
|
-
"additionalProperties": False,
|
|
1652
|
-
},
|
|
1653
|
-
),
|
|
1654
|
-
Tool(
|
|
1655
|
-
name="surface_coverage",
|
|
1656
|
-
description=(
|
|
1657
|
-
"Read-only COVERAGE BY SURFACE (#3): per active surface, how complete "
|
|
1658
|
-
"it is. Returns {project_uuid, version_uuid, languages[], keys_total, "
|
|
1659
|
-
"surfaces:[{surface, label, kind, meaning, eligible_keys, "
|
|
1660
|
-
"eligible_cells, present_cells, pct, by_language:[{code, eligible, "
|
|
1661
|
-
"present, pct}]}]}. The `meaning` field tells you how to read `pct`: "
|
|
1662
|
-
"'a11y_completeness' for a11y surfaces (eligible = type-relevant keys; "
|
|
1663
|
-
"pct = real fill of NEEDED a11y gaps), 'override_density' for device "
|
|
1664
|
-
"surfaces (un-overridden cells fall back to the base value — that is "
|
|
1665
|
-
"NOT broken, just un-customized). Defaults to the production version."
|
|
1666
|
-
),
|
|
1667
|
-
inputSchema={
|
|
1668
|
-
"type": "object",
|
|
1669
|
-
"properties": {
|
|
1670
|
-
"project_uuid": {
|
|
1671
|
-
"type": "string",
|
|
1672
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1673
|
-
},
|
|
1674
|
-
"version_id": {
|
|
1675
|
-
"type": "string",
|
|
1676
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1677
|
-
},
|
|
1678
|
-
},
|
|
1679
|
-
"additionalProperties": False,
|
|
1680
|
-
},
|
|
1681
|
-
),
|
|
1682
|
-
Tool(
|
|
1683
|
-
name="get_variants",
|
|
1684
|
-
description=(
|
|
1685
|
-
"Read the full VARIANT MATRIX for one key (task 928): every surface "
|
|
1686
|
-
"OFFERED for the key x every project language, with each cell's "
|
|
1687
|
-
"{override, value, resolvedValue, status, drift, ...}. The response "
|
|
1688
|
-
"lists `surfaces` and `surfaceKinds` ({surface: 'device'|'a11y'}) — "
|
|
1689
|
-
"these are the key's offered surfaces only: the project's active "
|
|
1690
|
-
"device surfaces plus the a11y treatments relevant to the key's TYPE "
|
|
1691
|
-
"(e.g. a text key shows no aria/alt). This is the source of truth for "
|
|
1692
|
-
"which surfaces set_variant will accept. Address by key_uuid (from "
|
|
1693
|
-
"list_keys). Surfaced verbatim."
|
|
1694
|
-
),
|
|
1695
|
-
inputSchema={
|
|
1696
|
-
"type": "object",
|
|
1697
|
-
"properties": {
|
|
1698
|
-
"project_uuid": {
|
|
1699
|
-
"type": "string",
|
|
1700
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1701
|
-
},
|
|
1702
|
-
"key_uuid": {
|
|
1703
|
-
"type": "string",
|
|
1704
|
-
"description": "Key UUID (from list_keys).",
|
|
1705
|
-
},
|
|
1706
|
-
},
|
|
1707
|
-
"required": ["key_uuid"],
|
|
1708
|
-
"additionalProperties": False,
|
|
1709
|
-
},
|
|
1710
|
-
),
|
|
1711
|
-
Tool(
|
|
1712
|
-
name="set_variant",
|
|
1713
|
-
description=(
|
|
1714
|
-
"Upsert ONE variant override for (key, language, surface) for ANY "
|
|
1715
|
-
"surface active on the project (task 928) — device (desktop / mobile / "
|
|
1716
|
-
"tablet / custom like watch / tv) OR a11y (aria_label / alt_text / "
|
|
1717
|
-
"screen_reader / plain_language). Address by key_uuid + locale CODE + "
|
|
1718
|
-
"surface slug (the fields get_variants / list_keys already give you). "
|
|
1719
|
-
"Stored as a draft. 422 unless the surface is OFFERED for this key — "
|
|
1720
|
-
"an active device surface (see list_surfaces) OR an a11y treatment "
|
|
1721
|
-
"relevant to the key's TYPE (e.g. no aria_label on a text key; see "
|
|
1722
|
-
"get_variants for the key's offered surfaces); 409 if the cell is "
|
|
1723
|
-
"already reviewed/approved (never overwritten). Returns {key_uuid, "
|
|
1724
|
-
"surface, language_code, value, status}. (For a11y specifically you "
|
|
1725
|
-
"can also use set_a11y_variant.)"
|
|
1726
|
-
),
|
|
1727
|
-
inputSchema={
|
|
1728
|
-
"type": "object",
|
|
1729
|
-
"properties": {
|
|
1730
|
-
"project_uuid": {
|
|
1731
|
-
"type": "string",
|
|
1732
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1733
|
-
},
|
|
1734
|
-
"key_uuid": {
|
|
1735
|
-
"type": "string",
|
|
1736
|
-
"description": "Key UUID (from list_keys / get_variants).",
|
|
1737
|
-
},
|
|
1738
|
-
"language_code": {
|
|
1739
|
-
"type": "string",
|
|
1740
|
-
"description": "Target locale CODE, e.g. 'fr'. Backend resolves it; 404 if unknown.",
|
|
1741
|
-
},
|
|
1742
|
-
"surface": {
|
|
1743
|
-
"type": "string",
|
|
1744
|
-
"description": "Surface slug — any surface ACTIVE for the project (device or a11y). See list_surfaces.",
|
|
1745
|
-
},
|
|
1746
|
-
"value": {
|
|
1747
|
-
"type": "string",
|
|
1748
|
-
"description": "The variant text value for this (key, language, surface).",
|
|
1749
|
-
},
|
|
1750
|
-
},
|
|
1751
|
-
"required": ["key_uuid", "language_code", "surface", "value"],
|
|
1752
|
-
"additionalProperties": False,
|
|
1753
|
-
},
|
|
1754
|
-
),
|
|
1755
|
-
Tool(
|
|
1756
|
-
name="delete_variant",
|
|
1757
|
-
description=(
|
|
1758
|
-
"Delete ONE variant override for (key, language, surface), for ANY "
|
|
1759
|
-
"surface (device or a11y) — task 928. The cell re-inherits the base "
|
|
1760
|
-
"value; a 404 means there was no override. Address by key_uuid + "
|
|
1761
|
-
"language_code + surface (same as set_variant)."
|
|
1762
|
-
),
|
|
1763
|
-
inputSchema={
|
|
1764
|
-
"type": "object",
|
|
1765
|
-
"properties": {
|
|
1766
|
-
"project_uuid": {
|
|
1767
|
-
"type": "string",
|
|
1768
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1769
|
-
},
|
|
1770
|
-
"key_uuid": {
|
|
1771
|
-
"type": "string",
|
|
1772
|
-
"description": "Key UUID (from list_keys / get_variants).",
|
|
1773
|
-
},
|
|
1774
|
-
"language_code": {
|
|
1775
|
-
"type": "string",
|
|
1776
|
-
"description": "Target locale CODE, e.g. 'fr'.",
|
|
1777
|
-
},
|
|
1778
|
-
"surface": {
|
|
1779
|
-
"type": "string",
|
|
1780
|
-
"description": "Surface slug (device or a11y) to clear.",
|
|
1781
|
-
},
|
|
1782
|
-
},
|
|
1783
|
-
"required": ["key_uuid", "language_code", "surface"],
|
|
1784
|
-
"additionalProperties": False,
|
|
1785
|
-
},
|
|
1786
|
-
),
|
|
1787
|
-
Tool(
|
|
1788
|
-
name="list_cognitive_candidates",
|
|
1789
|
-
description=(
|
|
1790
|
-
"List keys that are CANDIDATES for plain-language (cognitive) scoring: "
|
|
1791
|
-
"text-relevant keys (a type that offers plain_language — text / heading "
|
|
1792
|
-
"/ label / caption) whose source value clears a small word floor. This "
|
|
1793
|
-
"is a scope filter, NOT a difficulty judgement — score them to find the "
|
|
1794
|
-
"hard ones. Pass only_unanalyzed=true to skip keys already scored. Each "
|
|
1795
|
-
"item carries key_uuid + source_value so you can score it locally with "
|
|
1796
|
-
"set_cognitive_score."
|
|
1797
|
-
),
|
|
1798
|
-
inputSchema={
|
|
1799
|
-
"type": "object",
|
|
1800
|
-
"properties": {
|
|
1801
|
-
"project_uuid": {
|
|
1802
|
-
"type": "string",
|
|
1803
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1804
|
-
},
|
|
1805
|
-
"only_unanalyzed": {
|
|
1806
|
-
"type": "boolean",
|
|
1807
|
-
"description": "Only return keys with no cognitive_score yet (not analyzed).",
|
|
1808
|
-
},
|
|
1809
|
-
"namespace_uuid": {
|
|
1810
|
-
"type": "string",
|
|
1811
|
-
"description": "Restrict to one namespace by its UUID.",
|
|
1812
|
-
},
|
|
1813
|
-
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100},
|
|
1814
|
-
"offset": {"type": "integer", "minimum": 0, "default": 0},
|
|
1815
|
-
},
|
|
1816
|
-
"additionalProperties": False,
|
|
1817
|
-
},
|
|
1818
|
-
),
|
|
1819
|
-
Tool(
|
|
1820
|
-
name="set_cognitive_score",
|
|
1821
|
-
description=(
|
|
1822
|
-
"Record a key's cognitive-difficulty SCORE (0-100, higher = harder to "
|
|
1823
|
-
"read) and an optional plain-language SUGGESTION — the local-first, "
|
|
1824
|
-
"**0-credit** path. Use this after scoring a candidate YOURSELF: you "
|
|
1825
|
-
"(the agent) judge the difficulty and write a clearer rewrite, then "
|
|
1826
|
-
"persist both here (marked by_bot). A key only enters the review queue "
|
|
1827
|
-
"(reading_level_high) once its score is >= the project threshold "
|
|
1828
|
-
"(default 60). The suggestion is a draft for a human to apply/approve."
|
|
1829
|
-
),
|
|
1830
|
-
inputSchema={
|
|
1831
|
-
"type": "object",
|
|
1832
|
-
"properties": {
|
|
1833
|
-
"project_uuid": {
|
|
1834
|
-
"type": "string",
|
|
1835
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1836
|
-
},
|
|
1837
|
-
"key_uuid": {
|
|
1838
|
-
"type": "string",
|
|
1839
|
-
"description": "Key UUID (from list_cognitive_candidates / list_keys).",
|
|
1840
|
-
},
|
|
1841
|
-
"score": {
|
|
1842
|
-
"type": "number",
|
|
1843
|
-
"minimum": 0,
|
|
1844
|
-
"maximum": 100,
|
|
1845
|
-
"description": "Cognitive difficulty score, 0 (easy) to 100 (hard).",
|
|
1846
|
-
},
|
|
1847
|
-
"suggestion": {
|
|
1848
|
-
"type": "string",
|
|
1849
|
-
"description": "Optional plain-language rewrite of the source value (draft).",
|
|
1850
|
-
},
|
|
1851
|
-
},
|
|
1852
|
-
"required": ["key_uuid", "score"],
|
|
1853
|
-
"additionalProperties": False,
|
|
1854
|
-
},
|
|
1855
|
-
),
|
|
1856
|
-
Tool(
|
|
1857
|
-
name="analyze_cognitive",
|
|
1858
|
-
description=(
|
|
1859
|
-
"Server-side AI cognitive analysis — the BILLED, opt-in FALLBACK to "
|
|
1860
|
-
"local scoring. Rates the cognitive difficulty of candidate keys and "
|
|
1861
|
-
"suggests plain-language rewrites server-side. Prefer scoring locally "
|
|
1862
|
-
"with set_cognitive_score (0 credits); use this only for very large "
|
|
1863
|
-
"volumes or on explicit request. Bills credits (standard=1, premium=4 "
|
|
1864
|
-
"per key; 402 if insufficient, 503 if AI unavailable). Returns "
|
|
1865
|
-
"{requested, analyzed, credits_charged, credits_refunded}."
|
|
1866
|
-
),
|
|
1867
|
-
inputSchema={
|
|
1868
|
-
"type": "object",
|
|
1869
|
-
"properties": {
|
|
1870
|
-
"project_uuid": {
|
|
1871
|
-
"type": "string",
|
|
1872
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1873
|
-
},
|
|
1874
|
-
"tier": {
|
|
1875
|
-
"type": "string",
|
|
1876
|
-
"enum": ["standard", "premium"],
|
|
1877
|
-
"default": "standard",
|
|
1878
|
-
"description": "Quality tier (credit_cost per key: standard=1, premium=4).",
|
|
1879
|
-
},
|
|
1880
|
-
"key_uuids": {
|
|
1881
|
-
"type": "array",
|
|
1882
|
-
"items": {"type": "string"},
|
|
1883
|
-
"description": "Restrict to these keys (from list_cognitive_candidates). Omit to scope the whole project (or a namespace).",
|
|
1884
|
-
},
|
|
1885
|
-
"namespace_uuid": {
|
|
1886
|
-
"type": "string",
|
|
1887
|
-
"description": "Restrict to one namespace by its UUID.",
|
|
1888
|
-
},
|
|
1889
|
-
"idempotency_key": {
|
|
1890
|
-
"type": "string",
|
|
1891
|
-
"description": "Stable token making this billable call safe to retry: reuse the SAME value to avoid a double-charge. A random one is generated if omitted.",
|
|
1892
|
-
},
|
|
1893
|
-
},
|
|
1894
|
-
"additionalProperties": False,
|
|
1895
|
-
},
|
|
1896
|
-
),
|
|
1897
|
-
# --- a11y conformance reports + remediation plan (sprint 111c4658, B5 MCP twins) ---
|
|
1898
|
-
Tool(
|
|
1899
|
-
name="wcag_report",
|
|
1900
|
-
description=(
|
|
1901
|
-
"Read-only WCAG 2.2 CONFORMANCE report for the content layer Sonenta "
|
|
1902
|
-
"governs (strings, alt text, accessible names, a11y variants, reading "
|
|
1903
|
-
"level), evaluated PER LOCALE with a level-AA score. Reports the "
|
|
1904
|
-
"success criteria decidable from content (1.1.1, 1.3.1, 2.4.4, 2.4.9, "
|
|
1905
|
-
"3.1.1, 3.1.2, 3.1.5, 4.1.2); SC requiring the rendered DOM (contrast, "
|
|
1906
|
-
"keyboard, focus) appear ONLY under scope.out_of_scope_sc and are NEVER "
|
|
1907
|
-
"counted as pass. AAA criteria (2.4.9, 3.1.5) are shown but EXCLUDED "
|
|
1908
|
-
"from the AA score. conformance.score_pct is the headline AA number "
|
|
1909
|
-
"(with per-locale breakdown). Read scope.content_layer_sc dynamically — "
|
|
1910
|
-
"do not hardcode the SC list. Defaults to the production version. "
|
|
1911
|
-
"0-credit, read-only."
|
|
1912
|
-
),
|
|
1913
|
-
inputSchema={
|
|
1914
|
-
"type": "object",
|
|
1915
|
-
"properties": {
|
|
1916
|
-
"project_uuid": {
|
|
1917
|
-
"type": "string",
|
|
1918
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1919
|
-
},
|
|
1920
|
-
"version_id": {
|
|
1921
|
-
"type": "string",
|
|
1922
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1923
|
-
},
|
|
1924
|
-
},
|
|
1925
|
-
"additionalProperties": False,
|
|
1926
|
-
},
|
|
1927
|
-
),
|
|
1928
|
-
Tool(
|
|
1929
|
-
name="eaa_statement",
|
|
1930
|
-
description=(
|
|
1931
|
-
"Read-only EAA / EN 301 549 accessibility conformance STATEMENT for the "
|
|
1932
|
-
"content layer, per project x locale set (JSON). Maps each covered WCAG "
|
|
1933
|
-
"2.2 SC to its EN 301 549 clause (e.g. 1.1.1 -> 9.1.1.1) and reports "
|
|
1934
|
-
"{org, product, version, level:'AA', score_aa_percent, generated_at, "
|
|
1935
|
-
"standard, criteria_summary[], scope_statement:{attested[], "
|
|
1936
|
-
"out_of_scope[]}, contact}. Honest scope: attests alt-text / labels / "
|
|
1937
|
-
"language / plain-language per locale; the rendered-DOM audit is out of "
|
|
1938
|
-
"scope. Defaults to the production version. 0-credit, read-only."
|
|
1939
|
-
),
|
|
1940
|
-
inputSchema={
|
|
1941
|
-
"type": "object",
|
|
1942
|
-
"properties": {
|
|
1943
|
-
"project_uuid": {
|
|
1944
|
-
"type": "string",
|
|
1945
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1946
|
-
},
|
|
1947
|
-
"version_id": {
|
|
1948
|
-
"type": "string",
|
|
1949
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1950
|
-
},
|
|
1951
|
-
},
|
|
1952
|
-
"additionalProperties": False,
|
|
1953
|
-
},
|
|
1954
|
-
),
|
|
1955
|
-
Tool(
|
|
1956
|
-
name="list_a11y_review_queue",
|
|
1957
|
-
description=(
|
|
1958
|
-
"Read the a11y REVIEW QUEUE — the per-(key, locale, surface) review "
|
|
1959
|
-
"items with their state. Each item: {key_uuid, key_name, "
|
|
1960
|
-
"namespace_slug, surface, surface_kind, locale, state (missing | draft "
|
|
1961
|
-
"| translated | reviewed | approved | rejected), value, is_bot, gap, "
|
|
1962
|
-
"base_value, screenshots[], drift, ignored, ignore_reason, "
|
|
1963
|
-
"reject_reason}; the envelope carries by_state / by_surface rollups. "
|
|
1964
|
-
"**Pass state='rejected' to drive the RECTIFICATION loop**: a reviewer "
|
|
1965
|
-
"rejected a suggestion and left a `reject_reason` — the retained "
|
|
1966
|
-
"`value` is the rejected text and `reject_reason` is the human's "
|
|
1967
|
-
"instruction. The agent regenerates a corrected value addressing the "
|
|
1968
|
-
"reason and re-proposes via set_a11y_variant (rejected is unprotected, "
|
|
1969
|
-
"so it returns to draft). Defaults to the production version. READ-ONLY."
|
|
1970
|
-
),
|
|
1971
|
-
inputSchema={
|
|
1972
|
-
"type": "object",
|
|
1973
|
-
"properties": {
|
|
1974
|
-
"project_uuid": {
|
|
1975
|
-
"type": "string",
|
|
1976
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
1977
|
-
},
|
|
1978
|
-
"version_id": {
|
|
1979
|
-
"type": "string",
|
|
1980
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
1981
|
-
},
|
|
1982
|
-
"state": {
|
|
1983
|
-
"type": "string",
|
|
1984
|
-
"enum": [
|
|
1985
|
-
"missing",
|
|
1986
|
-
"draft",
|
|
1987
|
-
"translated",
|
|
1988
|
-
"reviewed",
|
|
1989
|
-
"approved",
|
|
1990
|
-
"rejected",
|
|
1991
|
-
],
|
|
1992
|
-
"description": "Filter to one review state. Use 'rejected' for the rectification loop (items carry reject_reason).",
|
|
1993
|
-
},
|
|
1994
|
-
"locale": {
|
|
1995
|
-
"type": "string",
|
|
1996
|
-
"description": "Filter to one locale code (e.g. 'fr'). Use 'source' for source-locale rows.",
|
|
1997
|
-
},
|
|
1998
|
-
"surface": {
|
|
1999
|
-
"type": "string",
|
|
2000
|
-
"enum": list(A11Y_SURFACES),
|
|
2001
|
-
"description": "Filter to one a11y surface.",
|
|
2002
|
-
},
|
|
2003
|
-
"namespace_uuid": {
|
|
2004
|
-
"type": "string",
|
|
2005
|
-
"description": "Restrict to one namespace by its UUID.",
|
|
2006
|
-
},
|
|
2007
|
-
"key_uuid": {
|
|
2008
|
-
"type": "string",
|
|
2009
|
-
"description": "Restrict to one key by its UUID.",
|
|
2010
|
-
},
|
|
2011
|
-
"include_ignored": {
|
|
2012
|
-
"type": "boolean",
|
|
2013
|
-
"description": "Include items suppressed by the remediation plan (ignored cells). Default false.",
|
|
2014
|
-
},
|
|
2015
|
-
"group_by_key": {
|
|
2016
|
-
"type": "boolean",
|
|
2017
|
-
"description": "Aggregate items by key (one entry per key with its surfaces/locales nested) instead of a flat list.",
|
|
2018
|
-
},
|
|
2019
|
-
"limit": {"type": "integer", "minimum": 1, "maximum": 500},
|
|
2020
|
-
"offset": {"type": "integer", "minimum": 0},
|
|
2021
|
-
},
|
|
2022
|
-
"additionalProperties": False,
|
|
2023
|
-
},
|
|
2024
|
-
),
|
|
2025
|
-
Tool(
|
|
2026
|
-
name="a11y_remediation_plan_get",
|
|
2027
|
-
description=(
|
|
2028
|
-
"Read the a11y REMEDIATION PLAN for the project (or null if none "
|
|
2029
|
-
"exists). The plan is authored/approved in the dashboard; this tool "
|
|
2030
|
-
"OBSERVES it. Shape: {version_uuid, status (draft|approved), "
|
|
2031
|
-
"approved_at, plan:{items:[{key_uuid, locale, surface, decision "
|
|
2032
|
-
"(apply|ignore), reason?, value?}]}}. The backend mutates nothing on "
|
|
2033
|
-
"PUT/approve — once status=approved, call a11y_remediation_plan_apply "
|
|
2034
|
-
"to bulk-execute it. Defaults to the production version. Read-only."
|
|
2035
|
-
),
|
|
2036
|
-
inputSchema={
|
|
2037
|
-
"type": "object",
|
|
2038
|
-
"properties": {
|
|
2039
|
-
"project_uuid": {
|
|
2040
|
-
"type": "string",
|
|
2041
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2042
|
-
},
|
|
2043
|
-
"version_id": {
|
|
2044
|
-
"type": "string",
|
|
2045
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
2046
|
-
},
|
|
2047
|
-
},
|
|
2048
|
-
"additionalProperties": False,
|
|
2049
|
-
},
|
|
2050
|
-
),
|
|
2051
|
-
Tool(
|
|
2052
|
-
name="a11y_remediation_plan_apply",
|
|
2053
|
-
description=(
|
|
2054
|
-
"EXECUTE the APPROVED a11y remediation plan — the agent's bulk-execute "
|
|
2055
|
-
"entrypoint. Only acts on a plan whose status=approved (poll "
|
|
2056
|
-
"a11y_remediation_plan_get first); a draft/absent plan is rejected. For "
|
|
2057
|
-
"each 'apply' item it writes the a11y variant (the item's value, else "
|
|
2058
|
-
"the current draft override) via the normal variant overlay — "
|
|
2059
|
-
"non-destructive and reversible (trashable/restorable). 'ignore' items "
|
|
2060
|
-
"persistently suppress that (key, locale, surface) cell. Returns the "
|
|
2061
|
-
"execution summary. Defaults to the production version."
|
|
2062
|
-
),
|
|
2063
|
-
inputSchema={
|
|
2064
|
-
"type": "object",
|
|
2065
|
-
"properties": {
|
|
2066
|
-
"project_uuid": {
|
|
2067
|
-
"type": "string",
|
|
2068
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2069
|
-
},
|
|
2070
|
-
"version_id": {
|
|
2071
|
-
"type": "string",
|
|
2072
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
2073
|
-
},
|
|
2074
|
-
},
|
|
2075
|
-
"additionalProperties": False,
|
|
2076
|
-
},
|
|
2077
|
-
),
|
|
2078
|
-
Tool(
|
|
2079
|
-
name="score_cognitive_local",
|
|
2080
|
-
description=(
|
|
2081
|
-
"Compute and persist cognitive-difficulty scores from a VALIDATED, "
|
|
2082
|
-
"deterministic READABILITY metric — the local-first, **0-credit**, "
|
|
2083
|
-
"no-AI path (SC 3.1.5). English uses Flesch-Kincaid; every other "
|
|
2084
|
-
"language uses LIX, both normalised to difficulty 0-100 (higher = "
|
|
2085
|
-
"harder) with a per-language threshold. Writes cognitive_score for the "
|
|
2086
|
-
"scoped keys; a key enters the reading_level_high queue once its score "
|
|
2087
|
-
">= the project threshold. Prefer this over analyze_cognitive (billed "
|
|
2088
|
-
"AI) for difficulty scoring. Scope with key_uuids and/or namespace_uuid "
|
|
2089
|
-
"(omit both = whole project). Returns the scoring summary."
|
|
2090
|
-
),
|
|
2091
|
-
inputSchema={
|
|
2092
|
-
"type": "object",
|
|
2093
|
-
"properties": {
|
|
2094
|
-
"project_uuid": {
|
|
2095
|
-
"type": "string",
|
|
2096
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2097
|
-
},
|
|
2098
|
-
"key_uuids": {
|
|
2099
|
-
"type": "array",
|
|
2100
|
-
"items": {"type": "string"},
|
|
2101
|
-
"description": "Restrict scoring to these keys. Omit to scope the whole project (or a namespace).",
|
|
2102
|
-
},
|
|
2103
|
-
"namespace_uuid": {
|
|
2104
|
-
"type": "string",
|
|
2105
|
-
"description": "Restrict scoring to one namespace by its UUID.",
|
|
2106
|
-
},
|
|
2107
|
-
"overwrite": {
|
|
2108
|
-
"type": "boolean",
|
|
2109
|
-
"description": "Re-score keys that already have a cognitive_score (default false = only score unscored keys).",
|
|
2110
|
-
},
|
|
2111
|
-
},
|
|
2112
|
-
"additionalProperties": False,
|
|
2113
|
-
},
|
|
2114
|
-
),
|
|
2115
|
-
# --- Placeholder mismatches (PM2 #173): translations whose interpolation
|
|
2116
|
-
# variables drift from the source (i18next {{}}, ICU {}, Ruby %{}). ---
|
|
2117
|
-
Tool(
|
|
2118
|
-
name="list_placeholder_mismatches",
|
|
2119
|
-
description=(
|
|
2120
|
-
"List TRANSLATIONS whose interpolation VARIABLES drift from their "
|
|
2121
|
-
"source value (Placeholder mismatch, PM2) — a broken `{{name}}` / "
|
|
2122
|
-
"`{count}` is a runtime bug (missing data, or a crash). Detection is "
|
|
2123
|
-
"by variable NAME and brace-agnostic (i18next `{{}}`, ICU `{}`, Ruby "
|
|
2124
|
-
"`%{}` all compared by name), evaluated PER LANGUAGE. Each item = "
|
|
2125
|
-
"{key_uuid, key ('namespace::name'), namespace_slug, language (BCP-47), "
|
|
2126
|
-
"source_value, source_vars[], target_value, target_vars[], missing[] "
|
|
2127
|
-
"(vars in the source absent from this translation), extra[] (vars in "
|
|
2128
|
-
"this translation unknown to the source)}. This is the worklist the "
|
|
2129
|
-
"sonenta-source-health agent repairs: rewrite the target value so its "
|
|
2130
|
-
"variables match the source, write it back (propose_translation), then "
|
|
2131
|
-
"RE-CALL this tool — `total == 0` confirms the fix (no server auto-fix). "
|
|
2132
|
-
"READ-ONLY. 0 credits."
|
|
2133
|
-
),
|
|
2134
|
-
inputSchema={
|
|
2135
|
-
"type": "object",
|
|
2136
|
-
"properties": {
|
|
2137
|
-
"project_uuid": {
|
|
2138
|
-
"type": "string",
|
|
2139
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2140
|
-
},
|
|
2141
|
-
"version_id": {
|
|
2142
|
-
"type": "string",
|
|
2143
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
2144
|
-
},
|
|
2145
|
-
"key_id": {
|
|
2146
|
-
"type": "string",
|
|
2147
|
-
"description": "Restrict to a single key by its UUID.",
|
|
2148
|
-
},
|
|
2149
|
-
"language_code": {
|
|
2150
|
-
"type": "string",
|
|
2151
|
-
"description": "Restrict to a single target language by its BCP-47 code (e.g. 'fr').",
|
|
2152
|
-
},
|
|
2153
|
-
},
|
|
2154
|
-
"additionalProperties": False,
|
|
2155
|
-
},
|
|
2156
|
-
),
|
|
2157
|
-
# --- Source Health: duplicate source strings (#1116; backend #1114, PR #147) ---
|
|
2158
|
-
Tool(
|
|
2159
|
-
name="list_source_duplicates",
|
|
2160
|
-
description=(
|
|
2161
|
-
"List groups of keys that SHARE an identical source-language "
|
|
2162
|
-
"value (Source Health duplicates, #1116). Each group = "
|
|
2163
|
-
"{source_value, key_count, key_names[], key_uuids[], status, note}. "
|
|
2164
|
-
"Duplicates inflate translation cost and drift (the same string "
|
|
2165
|
-
"translated N ways), so this is the worklist the "
|
|
2166
|
-
"sonenta-source-health agent repairs. Status is tracked PROJECT-WIDE "
|
|
2167
|
-
"per source_value: to_fix (needs attention; the default), allowed "
|
|
2168
|
-
"(intentional duplicate — listed+flagged but excluded from "
|
|
2169
|
-
"total_issues), resolved (already fixed — re-scan confirms). Filter "
|
|
2170
|
-
"by status to focus. When status=to_fix, a group may also carry a "
|
|
2171
|
-
"human-prepared `merge_plan` ({clusters: [{canonical_key_uuid, "
|
|
2172
|
-
"redundant_key_uuids[]}], survivor_outcome: allowed|differentiate}) "
|
|
2173
|
-
"authored in the dashboard — the consolidation an agent applies "
|
|
2174
|
-
"(merge each cluster onto its canonical key, then allow or "
|
|
2175
|
-
"differentiate whatever still shares the text). READ-ONLY."
|
|
2176
|
-
),
|
|
2177
|
-
inputSchema={
|
|
2178
|
-
"type": "object",
|
|
2179
|
-
"properties": {
|
|
2180
|
-
"project_uuid": {
|
|
2181
|
-
"type": "string",
|
|
2182
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2183
|
-
},
|
|
2184
|
-
"status": {
|
|
2185
|
-
"type": "string",
|
|
2186
|
-
"enum": ["to_fix", "allowed", "resolved"],
|
|
2187
|
-
"description": "Filter to duplicate groups in this triage status. Omit for all.",
|
|
2188
|
-
},
|
|
2189
|
-
"version_id": {
|
|
2190
|
-
"type": "string",
|
|
2191
|
-
"description": "Optional version UUID; defaults to the production version.",
|
|
2192
|
-
},
|
|
2193
|
-
},
|
|
2194
|
-
"additionalProperties": False,
|
|
2195
|
-
},
|
|
2196
|
-
),
|
|
2197
|
-
Tool(
|
|
2198
|
-
name="set_duplicate_status",
|
|
2199
|
-
description=(
|
|
2200
|
-
"Set the triage STATUS of one duplicate group, addressed by its "
|
|
2201
|
-
"shared source_value (Source Health duplicates, #1116). status ∈ "
|
|
2202
|
-
"to_fix (this duplication should be repaired — the default; "
|
|
2203
|
-
"clears any override), allowed (an intentional, sanctioned "
|
|
2204
|
-
"duplicate — kept but EXCLUDED from total_issues, stop flagging "
|
|
2205
|
-
"it), resolved (the duplication has been fixed — removed from the "
|
|
2206
|
-
"list, a re-scan confirms). An invalid status returns 422. Status "
|
|
2207
|
-
"persists PROJECT-WIDE per source_value. Use this after the "
|
|
2208
|
-
"sonenta-source-health agent has de-duplicated the keys, to mark "
|
|
2209
|
-
"the group resolved, or to whitelist a legitimate duplicate as "
|
|
2210
|
-
"allowed. An optional note records WHY (audit trail)."
|
|
2211
|
-
),
|
|
2212
|
-
inputSchema={
|
|
2213
|
-
"type": "object",
|
|
2214
|
-
"properties": {
|
|
2215
|
-
"project_uuid": {
|
|
2216
|
-
"type": "string",
|
|
2217
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2218
|
-
},
|
|
2219
|
-
"source_value": {
|
|
2220
|
-
"type": "string",
|
|
2221
|
-
"description": "The shared source-language value identifying the duplicate group (from list_source_duplicates).",
|
|
2222
|
-
},
|
|
2223
|
-
"status": {
|
|
2224
|
-
"type": "string",
|
|
2225
|
-
"enum": ["to_fix", "allowed", "resolved"],
|
|
2226
|
-
"description": "New triage status for the group.",
|
|
2227
|
-
},
|
|
2228
|
-
"note": {
|
|
2229
|
-
"type": "string",
|
|
2230
|
-
"description": "Optional human/agent note recording why the status was set (audit trail).",
|
|
2231
|
-
},
|
|
2232
|
-
"files_modified": _FILES_MODIFIED_SCHEMA,
|
|
2233
|
-
},
|
|
2234
|
-
"required": ["source_value", "status"],
|
|
2235
|
-
"additionalProperties": False,
|
|
2236
|
-
},
|
|
2237
|
-
),
|
|
2238
|
-
# --- Source Health: agent-authored merge plan + per-cluster applied
|
|
2239
|
-
# marking (task 1363; backend PR #183 + cluster-applied route) ---
|
|
2240
|
-
Tool(
|
|
2241
|
-
name="set_source_duplicate_merge_plan",
|
|
2242
|
-
description=(
|
|
2243
|
-
"AUTHOR (or clear) the merge plan for one duplicate group, addressed "
|
|
2244
|
-
"by its shared source_value (Source Health duplicates). This is how "
|
|
2245
|
-
"the sonenta-source-health agent records the CONSOLIDATION it proposes "
|
|
2246
|
-
"and the dev accepted: which keys collapse onto which canonical, and "
|
|
2247
|
-
"what to do with whatever still shares the text. `merge_plan` = "
|
|
2248
|
-
"{clusters:[{canonical_key_uuid, redundant_key_uuids:[...]}], "
|
|
2249
|
-
"survivor_outcome:'allowed'|'differentiate'} — or null/omit to CLEAR "
|
|
2250
|
-
"the plan. Saving a plan sets the group's status to to_fix. After "
|
|
2251
|
-
"authoring, the agent repoints t() usages, trashes the redundant keys "
|
|
2252
|
-
"(delete_keys_bulk), marks each cluster applied (mark_cluster_applied), "
|
|
2253
|
-
"and finally set_duplicate_status(resolved). Author only a plan the "
|
|
2254
|
-
"dev accepted — never decide a merge silently."
|
|
2255
|
-
),
|
|
2256
|
-
inputSchema={
|
|
2257
|
-
"type": "object",
|
|
2258
|
-
"properties": {
|
|
2259
|
-
"project_uuid": {
|
|
2260
|
-
"type": "string",
|
|
2261
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2262
|
-
},
|
|
2263
|
-
"source_value": {
|
|
2264
|
-
"type": "string",
|
|
2265
|
-
"description": "The shared source-language value identifying the duplicate group (from list_source_duplicates).",
|
|
2266
|
-
},
|
|
2267
|
-
"merge_plan": {
|
|
2268
|
-
"type": ["object", "null"],
|
|
2269
|
-
"description": "The consolidation plan, or null to clear it.",
|
|
2270
|
-
"properties": {
|
|
2271
|
-
"clusters": {
|
|
2272
|
-
"type": "array",
|
|
2273
|
-
"items": {
|
|
2274
|
-
"type": "object",
|
|
2275
|
-
"properties": {
|
|
2276
|
-
"canonical_key_uuid": {
|
|
2277
|
-
"type": "string",
|
|
2278
|
-
"description": "An EXISTING key kept as the canonical survivor. Mutually exclusive with new_key.",
|
|
2279
|
-
},
|
|
2280
|
-
"new_key": {
|
|
2281
|
-
"type": "object",
|
|
2282
|
-
"description": "Create a BRAND-NEW canonical key instead of keeping an existing one (NK1). Mutually exclusive with canonical_key_uuid. For a new_key cluster, redundant_key_uuids = ALL the group's keys (none survives). After authoring, call apply_new_key_cluster — the backend creates the key + copies translations + trashes the old keys.",
|
|
2283
|
-
"properties": {
|
|
2284
|
-
"name": {"type": "string", "description": "New key name (must not collide with an active key)."},
|
|
2285
|
-
"namespace": {"type": "string", "description": "Namespace slug the new key lives in (must exist)."},
|
|
2286
|
-
},
|
|
2287
|
-
"required": ["name", "namespace"],
|
|
2288
|
-
},
|
|
2289
|
-
"redundant_key_uuids": {
|
|
2290
|
-
"type": "array",
|
|
2291
|
-
"items": {"type": "string"},
|
|
2292
|
-
"description": "Keys folded into the canonical. For a new_key cluster, this is ALL the group's keys.",
|
|
2293
|
-
},
|
|
2294
|
-
},
|
|
2295
|
-
"required": ["redundant_key_uuids"],
|
|
2296
|
-
},
|
|
2297
|
-
},
|
|
2298
|
-
"survivor_outcome": {
|
|
2299
|
-
"type": "string",
|
|
2300
|
-
"enum": ["allowed", "differentiate"],
|
|
2301
|
-
"description": "What to do with survivors that still share the text: allowed (sanctioned duplicate) or differentiate (make distinct).",
|
|
2302
|
-
},
|
|
2303
|
-
},
|
|
2304
|
-
},
|
|
2305
|
-
"files_modified": _FILES_MODIFIED_SCHEMA,
|
|
2306
|
-
},
|
|
2307
|
-
"required": ["source_value"],
|
|
2308
|
-
"additionalProperties": False,
|
|
2309
|
-
},
|
|
2310
|
-
),
|
|
2311
|
-
Tool(
|
|
2312
|
-
name="mark_cluster_applied",
|
|
2313
|
-
description=(
|
|
2314
|
-
"Mark ONE cluster of a duplicate group's merge plan as APPLIED — "
|
|
2315
|
-
"addressed by the group's source_value + the cluster's "
|
|
2316
|
-
"canonical_key_uuid. Call it after you have repointed the redundant "
|
|
2317
|
-
"keys' t() usages onto the canonical and trashed them "
|
|
2318
|
-
"(delete_keys_bulk), so the dashboard tracks per-cluster progress. "
|
|
2319
|
-
"Pass applied=false to UN-mark it. Once every cluster of the group is "
|
|
2320
|
-
"applied (and the survivor_outcome handled), set_duplicate_status("
|
|
2321
|
-
"resolved) closes the group."
|
|
2322
|
-
),
|
|
2323
|
-
inputSchema={
|
|
2324
|
-
"type": "object",
|
|
2325
|
-
"properties": {
|
|
2326
|
-
"project_uuid": {
|
|
2327
|
-
"type": "string",
|
|
2328
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2329
|
-
},
|
|
2330
|
-
"source_value": {
|
|
2331
|
-
"type": "string",
|
|
2332
|
-
"description": "The shared source-language value identifying the duplicate group.",
|
|
2333
|
-
},
|
|
2334
|
-
"canonical_key_uuid": {
|
|
2335
|
-
"type": "string",
|
|
2336
|
-
"description": "The surviving canonical key of the cluster to mark applied.",
|
|
2337
|
-
},
|
|
2338
|
-
"applied": {
|
|
2339
|
-
"type": "boolean",
|
|
2340
|
-
"description": "True to mark applied (default), false to un-mark.",
|
|
2341
|
-
},
|
|
2342
|
-
"files_modified": _FILES_MODIFIED_SCHEMA,
|
|
2343
|
-
},
|
|
2344
|
-
"required": ["source_value", "canonical_key_uuid"],
|
|
2345
|
-
"additionalProperties": False,
|
|
2346
|
-
},
|
|
2347
|
-
),
|
|
2348
|
-
Tool(
|
|
2349
|
-
name="apply_new_key_cluster",
|
|
2350
|
-
description=(
|
|
2351
|
-
"Consolidate a duplicate group onto a BRAND-NEW canonical key (NK1) — "
|
|
2352
|
-
"the BACKEND does the atomic data transaction so no translation is "
|
|
2353
|
-
"lost: it creates the new key (type/description/context inherited from "
|
|
2354
|
-
"the group's most-complete key), COPIES every language value from that "
|
|
2355
|
-
"most-complete key, and TRASHES all the old keys. Address it by "
|
|
2356
|
-
"source_value + new_key {name, namespace} (the namespace must exist and "
|
|
2357
|
-
"the name must be free). Returns {new_key_uuid, name, namespace, "
|
|
2358
|
-
"copied_from_key_uuid, copied_languages, trashed_key_uuids}. YOUR job "
|
|
2359
|
-
"after this is the CODE only: repoint every t() usage of the trashed "
|
|
2360
|
-
"keys onto new_key_uuid, and record those edits in `files_modified` "
|
|
2361
|
-
"(accepted right here, union-merged). You do NOT create_key or "
|
|
2362
|
-
"delete_keys_bulk yourself — the backend owns that. Author the "
|
|
2363
|
-
"new_key cluster in the merge plan first (on the dev's acceptance)."
|
|
2364
|
-
),
|
|
2365
|
-
inputSchema={
|
|
2366
|
-
"type": "object",
|
|
2367
|
-
"properties": {
|
|
2368
|
-
"project_uuid": {
|
|
2369
|
-
"type": "string",
|
|
2370
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2371
|
-
},
|
|
2372
|
-
"source_value": {
|
|
2373
|
-
"type": "string",
|
|
2374
|
-
"description": "The shared source-language value identifying the duplicate group.",
|
|
2375
|
-
},
|
|
2376
|
-
"new_key": {
|
|
2377
|
-
"type": "object",
|
|
2378
|
-
"description": "The new canonical key to create and consolidate the group onto.",
|
|
2379
|
-
"properties": {
|
|
2380
|
-
"name": {"type": "string", "description": "New key name (must not collide with an active key)."},
|
|
2381
|
-
"namespace": {"type": "string", "description": "Namespace slug the new key lives in (must exist)."},
|
|
2382
|
-
},
|
|
2383
|
-
"required": ["name", "namespace"],
|
|
2384
|
-
},
|
|
2385
|
-
"files_modified": _FILES_MODIFIED_SCHEMA,
|
|
2386
|
-
},
|
|
2387
|
-
"required": ["source_value", "new_key"],
|
|
2388
|
-
"additionalProperties": False,
|
|
2389
|
-
},
|
|
2390
|
-
),
|
|
2391
|
-
Tool(
|
|
2392
|
-
name="set_duplicate_status_bulk",
|
|
2393
|
-
description=(
|
|
2394
|
-
"Set ONE triage status across MANY duplicate groups at once (coarse "
|
|
2395
|
-
"triage). Body {source_values[], status, note?}. Intentionally NO "
|
|
2396
|
-
"files_modified (a single status over many groups). Use it to bulk-mark "
|
|
2397
|
-
"a batch as allowed (sanctioned) or to_fix; use the per-group "
|
|
2398
|
-
"set_duplicate_status for anything that records code changes."
|
|
2399
|
-
),
|
|
2400
|
-
inputSchema={
|
|
2401
|
-
"type": "object",
|
|
2402
|
-
"properties": {
|
|
2403
|
-
"project_uuid": {
|
|
2404
|
-
"type": "string",
|
|
2405
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2406
|
-
},
|
|
2407
|
-
"source_values": {
|
|
2408
|
-
"type": "array",
|
|
2409
|
-
"items": {"type": "string"},
|
|
2410
|
-
"description": "The shared source values identifying the duplicate groups.",
|
|
2411
|
-
},
|
|
2412
|
-
"status": {
|
|
2413
|
-
"type": "string",
|
|
2414
|
-
"enum": ["neutral", "allowed", "to_fix", "resolved"],
|
|
2415
|
-
"description": "New triage status applied to every listed group (neutral clears the override).",
|
|
2416
|
-
},
|
|
2417
|
-
"note": {"type": "string", "description": "Optional human/agent note (same on all)."},
|
|
2418
|
-
},
|
|
2419
|
-
"required": ["source_values", "status"],
|
|
2420
|
-
"additionalProperties": False,
|
|
2421
|
-
},
|
|
2422
|
-
),
|
|
2423
|
-
Tool(
|
|
2424
|
-
name="propose_duplicate_merge_plan",
|
|
2425
|
-
description=(
|
|
2426
|
-
"OPT-IN, METERED second opinion: a server-side LLM reads a duplicate "
|
|
2427
|
-
"group (keys + translations + glossary + context) and PROPOSES a plan — "
|
|
2428
|
-
"recommendation merge | split | keep_separate + a well-formed merge_plan "
|
|
2429
|
-
"(null when keep_separate) + a one-line reason. PROPOSE-ONLY: it does NOT "
|
|
2430
|
-
"change status; apply via set_source_duplicate_merge_plan + "
|
|
2431
|
-
"apply_new_key_cluster / mark_cluster_applied / set_duplicate_status. "
|
|
2432
|
-
"You are already an LLM and decide locally at 0 credit — use this only "
|
|
2433
|
-
"as a tie-breaker or when the dev wants the AI's take. Costs 1 credit per "
|
|
2434
|
-
"generation UNLESS a valid cached proposal exists (cached=true → free); "
|
|
2435
|
-
"regenerate=true forces a fresh one. 402 when credits are insufficient. "
|
|
2436
|
-
"The persisted proposal also surfaces on list_source_duplicates "
|
|
2437
|
-
"(ai_proposed_plan / ai_diagnosis, free to read)."
|
|
2438
|
-
),
|
|
2439
|
-
inputSchema={
|
|
2440
|
-
"type": "object",
|
|
2441
|
-
"properties": {
|
|
2442
|
-
"project_uuid": {
|
|
2443
|
-
"type": "string",
|
|
2444
|
-
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2445
|
-
},
|
|
2446
|
-
"source_value": {
|
|
2447
|
-
"type": "string",
|
|
2448
|
-
"description": "The shared source value identifying the duplicate group.",
|
|
2449
|
-
},
|
|
2450
|
-
"namespace_slug": {
|
|
2451
|
-
"type": "string",
|
|
2452
|
-
"description": "Required only if the value spans namespaces (groups are intra-namespace).",
|
|
2453
|
-
},
|
|
2454
|
-
"regenerate": {
|
|
2455
|
-
"type": "boolean",
|
|
2456
|
-
"description": "Force a fresh diagnosis (1 credit) even if a valid cached one exists. Default false.",
|
|
2457
|
-
},
|
|
2458
|
-
"version_id": {"type": "string", "description": "Optional version UUID; defaults to the live version."},
|
|
2459
|
-
},
|
|
2460
|
-
"required": ["source_value"],
|
|
2461
|
-
"additionalProperties": False,
|
|
2462
|
-
},
|
|
2463
|
-
),
|
|
2464
|
-
]
|
|
2465
|
-
|
|
2466
|
-
@server.call_tool()
|
|
2467
|
-
async def _call(name: str, arguments: dict | None) -> list[TextContent]:
|
|
2468
|
-
try:
|
|
2469
|
-
args = arguments or {}
|
|
2470
|
-
if name == "list_projects":
|
|
2471
|
-
data = await client.get(
|
|
2472
|
-
"/v1/mcp/projects", params={"limit": args.get("limit", 50)}
|
|
2473
|
-
)
|
|
2474
|
-
data = _filter_projects_by_allowlist(
|
|
2475
|
-
data, client.config.allowed_project_uuids
|
|
2476
|
-
)
|
|
2477
|
-
return _text(data)
|
|
2478
|
-
if name == "get_project_info":
|
|
2479
|
-
project_uuid = _project_uuid(args, client)
|
|
2480
|
-
data = await client.get(f"/v1/mcp/projects/{project_uuid}")
|
|
2481
|
-
return _text(data)
|
|
2482
|
-
if name == "distribution_info":
|
|
2483
|
-
project_uuid = _project_uuid(args, client)
|
|
2484
|
-
data = await client.get(
|
|
2485
|
-
f"/v1/mcp/projects/{project_uuid}/distribution"
|
|
2486
|
-
)
|
|
2487
|
-
return _text(data)
|
|
2488
|
-
if name == "sonenta_guide":
|
|
2489
|
-
data = await client.get("/v1/mcp/guide")
|
|
2490
|
-
return _text(data)
|
|
2491
|
-
if name == "list_keys":
|
|
2492
|
-
project_uuid = _project_uuid(args, client)
|
|
2493
|
-
params = {
|
|
2494
|
-
k: v
|
|
2495
|
-
for k, v in {
|
|
2496
|
-
"namespace": args.get("namespace"),
|
|
2497
|
-
"language_code": args.get("language_code"),
|
|
2498
|
-
"status_filter": args.get("status_filter"),
|
|
2499
|
-
"include_glossary_hints": (
|
|
2500
|
-
"true" if args.get("include_glossary_hints") else None
|
|
2501
|
-
),
|
|
2502
|
-
"cursor": args.get("cursor"),
|
|
2503
|
-
"limit": args.get("limit", 50),
|
|
2504
|
-
}.items()
|
|
2505
|
-
if v is not None
|
|
2506
|
-
}
|
|
2507
|
-
data = await client.get(
|
|
2508
|
-
f"/v1/mcp/projects/{project_uuid}/keys", params=params
|
|
2509
|
-
)
|
|
2510
|
-
return _text(data)
|
|
2511
|
-
if name == "list_untranslated_keys":
|
|
2512
|
-
project_uuid = _project_uuid(args, client)
|
|
2513
|
-
if "language_code" not in args:
|
|
2514
|
-
return _err("language_code is required")
|
|
2515
|
-
params = {
|
|
2516
|
-
k: v
|
|
2517
|
-
for k, v in {
|
|
2518
|
-
"language_code": args["language_code"],
|
|
2519
|
-
"namespace": args.get("namespace"),
|
|
2520
|
-
"include_glossary_hints": (
|
|
2521
|
-
"true" if args.get("include_glossary_hints") else None
|
|
2522
|
-
),
|
|
2523
|
-
"cursor": args.get("cursor"),
|
|
2524
|
-
"limit": args.get("limit", 50),
|
|
2525
|
-
}.items()
|
|
2526
|
-
if v is not None
|
|
2527
|
-
}
|
|
2528
|
-
data = await client.get(
|
|
2529
|
-
f"/v1/mcp/projects/{project_uuid}/untranslated-keys", params=params
|
|
2530
|
-
)
|
|
2531
|
-
return _text(data)
|
|
2532
|
-
if name == "list_missing_keys":
|
|
2533
|
-
project_uuid = _project_uuid(args, client)
|
|
2534
|
-
params = {
|
|
2535
|
-
k: v
|
|
2536
|
-
for k, v in {
|
|
2537
|
-
"namespace": args.get("namespace"),
|
|
2538
|
-
"language_code": args.get("language_code"),
|
|
2539
|
-
"status": args.get("status"),
|
|
2540
|
-
"cursor": args.get("cursor"),
|
|
2541
|
-
"limit": args.get("limit", 50),
|
|
2542
|
-
}.items()
|
|
2543
|
-
if v is not None
|
|
2544
|
-
}
|
|
2545
|
-
data = await client.get(
|
|
2546
|
-
f"/v1/mcp/projects/{project_uuid}/missing-keys", params=params
|
|
2547
|
-
)
|
|
2548
|
-
return _text(data)
|
|
2549
|
-
if name == "missing_keys_stats":
|
|
2550
|
-
project_uuid = _project_uuid(args, client)
|
|
2551
|
-
params = {
|
|
2552
|
-
k: v
|
|
2553
|
-
for k, v in {
|
|
2554
|
-
"namespace": args.get("namespace"),
|
|
2555
|
-
"language_code": args.get("language_code"),
|
|
2556
|
-
"status": args.get("status"),
|
|
2557
|
-
}.items()
|
|
2558
|
-
if v is not None
|
|
2559
|
-
}
|
|
2560
|
-
data = await client.get(
|
|
2561
|
-
f"/v1/mcp/projects/{project_uuid}/missing-keys/stats",
|
|
2562
|
-
params=params,
|
|
2563
|
-
)
|
|
2564
|
-
return _text(data)
|
|
2565
|
-
if name == "acknowledge_missing_keys":
|
|
2566
|
-
project_uuid = _project_uuid(args, client)
|
|
2567
|
-
uuids = args.get("uuids") or []
|
|
2568
|
-
if not uuids:
|
|
2569
|
-
return _err("uuids must be a non-empty list")
|
|
2570
|
-
data = await client.post(
|
|
2571
|
-
f"/v1/mcp/projects/{project_uuid}/missing-keys/acknowledge",
|
|
2572
|
-
json={"uuids": uuids},
|
|
2573
|
-
)
|
|
2574
|
-
return _text(data)
|
|
2575
|
-
if name == "create_namespace":
|
|
2576
|
-
project_uuid = _project_uuid(args, client)
|
|
2577
|
-
if "slug" not in args or "name" not in args:
|
|
2578
|
-
return _err("slug and name are required")
|
|
2579
|
-
body: dict[str, Any] = {"slug": args["slug"], "name": args["name"]}
|
|
2580
|
-
if "description" in args:
|
|
2581
|
-
body["description"] = args["description"]
|
|
2582
|
-
data = await client.post(
|
|
2583
|
-
f"/v1/mcp/projects/{project_uuid}/namespaces", json=body
|
|
2584
|
-
)
|
|
2585
|
-
return _text(data)
|
|
2586
|
-
if name == "create_key":
|
|
2587
|
-
project_uuid = _project_uuid(args, client)
|
|
2588
|
-
if "namespace" not in args or "name" not in args:
|
|
2589
|
-
return _err("namespace and name are required")
|
|
2590
|
-
body: dict[str, Any] = {
|
|
2591
|
-
"namespace": args["namespace"],
|
|
2592
|
-
"name": args["name"],
|
|
2593
|
-
}
|
|
2594
|
-
for opt in ("source_value", "description", "plurals", "max_length", "type"):
|
|
2595
|
-
if opt in args:
|
|
2596
|
-
body[opt] = args[opt]
|
|
2597
|
-
data = await client.post(
|
|
2598
|
-
f"/v1/mcp/projects/{project_uuid}/keys", json=body
|
|
2599
|
-
)
|
|
2600
|
-
return _text(data)
|
|
2601
|
-
if name == "propose_translation":
|
|
2602
|
-
project_uuid = _project_uuid(args, client)
|
|
2603
|
-
body = {
|
|
2604
|
-
"namespace": args["namespace"],
|
|
2605
|
-
"key": args["key"],
|
|
2606
|
-
"language_code": args["language_code"],
|
|
2607
|
-
"value": args["value"],
|
|
2608
|
-
}
|
|
2609
|
-
if "status" in args:
|
|
2610
|
-
body["status"] = args["status"]
|
|
2611
|
-
data = await client.put(
|
|
2612
|
-
f"/v1/mcp/projects/{project_uuid}/translations", json=body
|
|
2613
|
-
)
|
|
2614
|
-
return _text(data)
|
|
2615
|
-
if name == "create_keys_bulk":
|
|
2616
|
-
project_uuid = _project_uuid(args, client)
|
|
2617
|
-
items = args.get("items")
|
|
2618
|
-
if not isinstance(items, list) or not items:
|
|
2619
|
-
return _err("items must be a non-empty list")
|
|
2620
|
-
data = await client.post(
|
|
2621
|
-
f"/v1/mcp/projects/{project_uuid}/keys/bulk", json={"items": items}
|
|
2622
|
-
)
|
|
2623
|
-
return _text(data)
|
|
2624
|
-
if name == "update_key":
|
|
2625
|
-
project_uuid = _project_uuid(args, client)
|
|
2626
|
-
if not any(k in args for k in ("source_value", "type", "description")):
|
|
2627
|
-
return _err(
|
|
2628
|
-
"nothing to update: pass at least one of source_value, type, description"
|
|
2629
|
-
)
|
|
2630
|
-
has_uuid = bool(args.get("key_uuid"))
|
|
2631
|
-
has_name = bool(args.get("namespace")) and bool(args.get("name"))
|
|
2632
|
-
if has_uuid == has_name:
|
|
2633
|
-
return _err(
|
|
2634
|
-
"address the key by exactly one of key_uuid OR (namespace + name)"
|
|
2635
|
-
)
|
|
2636
|
-
body: dict[str, Any] = {}
|
|
2637
|
-
for opt in ("source_value", "key_uuid", "namespace", "name", "description", "type"):
|
|
2638
|
-
if opt in args:
|
|
2639
|
-
body[opt] = args[opt]
|
|
2640
|
-
data = await client.patch(
|
|
2641
|
-
f"/v1/mcp/projects/{project_uuid}/keys", json=body
|
|
2642
|
-
)
|
|
2643
|
-
return _text(data)
|
|
2644
|
-
if name == "update_keys_bulk":
|
|
2645
|
-
project_uuid = _project_uuid(args, client)
|
|
2646
|
-
items = args.get("items")
|
|
2647
|
-
if not isinstance(items, list) or not items:
|
|
2648
|
-
return _err("items must be a non-empty list")
|
|
2649
|
-
data = await client.patch(
|
|
2650
|
-
f"/v1/mcp/projects/{project_uuid}/keys/bulk", json={"items": items}
|
|
2651
|
-
)
|
|
2652
|
-
return _text(data)
|
|
2653
|
-
if name == "propose_translations_bulk":
|
|
2654
|
-
project_uuid = _project_uuid(args, client)
|
|
2655
|
-
items = args.get("items")
|
|
2656
|
-
if not isinstance(items, list) or not items:
|
|
2657
|
-
return _err("items must be a non-empty list")
|
|
2658
|
-
data = await client.post(
|
|
2659
|
-
f"/v1/mcp/projects/{project_uuid}/translations/bulk",
|
|
2660
|
-
json={"items": items},
|
|
2661
|
-
)
|
|
2662
|
-
return _text(data)
|
|
2663
|
-
if name == "publish_cdn":
|
|
2664
|
-
project_uuid = _project_uuid(args, client)
|
|
2665
|
-
body: dict[str, Any] = {}
|
|
2666
|
-
for opt in ("language_code", "namespace", "version_slug"):
|
|
2667
|
-
if args.get(opt):
|
|
2668
|
-
body[opt] = args[opt]
|
|
2669
|
-
data = await client.post(
|
|
2670
|
-
f"/v1/mcp/projects/{project_uuid}/cdn/releases", json=body
|
|
2671
|
-
)
|
|
2672
|
-
return _text(data)
|
|
2673
|
-
if name == "validate_translations":
|
|
2674
|
-
project_uuid = _project_uuid(args, client)
|
|
2675
|
-
body = {
|
|
2676
|
-
"language_code": args["language_code"],
|
|
2677
|
-
"payload": args.get("payload", {}),
|
|
2678
|
-
}
|
|
2679
|
-
if "namespace" in args:
|
|
2680
|
-
body["namespace"] = args["namespace"]
|
|
2681
|
-
data = await client.post(
|
|
2682
|
-
f"/v1/mcp/projects/{project_uuid}/validate", json=body
|
|
2683
|
-
)
|
|
2684
|
-
return _text(data)
|
|
2685
|
-
if name == "project_context_get":
|
|
2686
|
-
project_uuid = _project_uuid(args, client)
|
|
2687
|
-
data = await client.get(
|
|
2688
|
-
f"/v1/mcp/projects/{project_uuid}/context"
|
|
2689
|
-
)
|
|
2690
|
-
return _text(data)
|
|
2691
|
-
if name == "project_context_set":
|
|
2692
|
-
project_uuid = _project_uuid(args, client)
|
|
2693
|
-
if "content" not in args:
|
|
2694
|
-
return _err("content is required")
|
|
2695
|
-
data = await client.put(
|
|
2696
|
-
f"/v1/mcp/projects/{project_uuid}/context",
|
|
2697
|
-
json={"content": args["content"]},
|
|
2698
|
-
)
|
|
2699
|
-
return _text(data)
|
|
2700
|
-
if name == "glossary_list":
|
|
2701
|
-
project_uuid = _project_uuid(args, client)
|
|
2702
|
-
params = {
|
|
2703
|
-
k: v
|
|
2704
|
-
for k, v in {
|
|
2705
|
-
"rule_type": args.get("rule_type"),
|
|
2706
|
-
"q": args.get("q"),
|
|
2707
|
-
"limit": args.get("limit", 200),
|
|
2708
|
-
}.items()
|
|
2709
|
-
if v is not None
|
|
2710
|
-
}
|
|
2711
|
-
data = await client.get(
|
|
2712
|
-
f"/v1/mcp/projects/{project_uuid}/glossary", params=params
|
|
2713
|
-
)
|
|
2714
|
-
return _text(data)
|
|
2715
|
-
if name == "glossary_create":
|
|
2716
|
-
project_uuid = _project_uuid(args, client)
|
|
2717
|
-
if "term" not in args:
|
|
2718
|
-
return _err("term is required")
|
|
2719
|
-
body: dict[str, Any] = {"term": args["term"]}
|
|
2720
|
-
for opt in ("rule_type", "translations", "case_sensitive", "note"):
|
|
2721
|
-
if opt in args:
|
|
2722
|
-
body[opt] = args[opt]
|
|
2723
|
-
data = await client.post(
|
|
2724
|
-
f"/v1/mcp/projects/{project_uuid}/glossary", json=body
|
|
2725
|
-
)
|
|
2726
|
-
return _text(data)
|
|
2727
|
-
if name == "glossary_update":
|
|
2728
|
-
project_uuid = _project_uuid(args, client)
|
|
2729
|
-
if "entry_id" not in args:
|
|
2730
|
-
return _err("entry_id is required")
|
|
2731
|
-
body: dict[str, Any] = {}
|
|
2732
|
-
for opt in ("term", "translations", "case_sensitive", "note"):
|
|
2733
|
-
if opt in args:
|
|
2734
|
-
body[opt] = args[opt]
|
|
2735
|
-
if not body:
|
|
2736
|
-
return _err(
|
|
2737
|
-
"nothing to update: pass at least one of term, translations, "
|
|
2738
|
-
"case_sensitive, note"
|
|
2739
|
-
)
|
|
2740
|
-
data = await client.patch(
|
|
2741
|
-
f"/v1/mcp/projects/{project_uuid}/glossary/{args['entry_id']}",
|
|
2742
|
-
json=body,
|
|
2743
|
-
)
|
|
2744
|
-
return _text(data)
|
|
2745
|
-
if name == "glossary_delete":
|
|
2746
|
-
project_uuid = _project_uuid(args, client)
|
|
2747
|
-
if "entry_id" not in args:
|
|
2748
|
-
return _err("entry_id is required")
|
|
2749
|
-
await client.delete(
|
|
2750
|
-
f"/v1/mcp/projects/{project_uuid}/glossary/{args['entry_id']}"
|
|
2751
|
-
)
|
|
2752
|
-
return _text({"deleted": True, "entry_id": args["entry_id"]})
|
|
2753
|
-
if name == "health_report":
|
|
2754
|
-
project_uuid = _project_uuid(args, client)
|
|
2755
|
-
params = {
|
|
2756
|
-
k: v
|
|
2757
|
-
for k, v in {"version_id": args.get("version_id")}.items()
|
|
2758
|
-
if v is not None
|
|
2759
|
-
}
|
|
2760
|
-
data = await client.get(
|
|
2761
|
-
f"/v1/mcp/projects/{project_uuid}/source-health", params=params
|
|
2762
|
-
)
|
|
2763
|
-
return _text(data)
|
|
2764
|
-
if name == "coverage_report":
|
|
2765
|
-
project_uuid = _project_uuid(args, client)
|
|
2766
|
-
params = {
|
|
2767
|
-
k: v
|
|
2768
|
-
for k, v in {"version_id": args.get("version_id")}.items()
|
|
2769
|
-
if v is not None
|
|
2770
|
-
}
|
|
2771
|
-
data = await client.get(
|
|
2772
|
-
f"/v1/mcp/projects/{project_uuid}/coverage", params=params
|
|
2773
|
-
)
|
|
2774
|
-
return _text(data)
|
|
2775
|
-
if name == "delete_keys_bulk":
|
|
2776
|
-
project_uuid = _project_uuid(args, client)
|
|
2777
|
-
key_uuids = args.get("key_uuids")
|
|
2778
|
-
if not isinstance(key_uuids, list) or not key_uuids:
|
|
2779
|
-
return _err("key_uuids must be a non-empty list")
|
|
2780
|
-
data = await client.post(
|
|
2781
|
-
f"/v1/mcp/projects/{project_uuid}/keys/bulk/delete",
|
|
2782
|
-
json={"key_uuids": key_uuids},
|
|
2783
|
-
)
|
|
2784
|
-
return _text(data)
|
|
2785
|
-
if name == "restore_keys_bulk":
|
|
2786
|
-
project_uuid = _project_uuid(args, client)
|
|
2787
|
-
key_uuids = args.get("key_uuids")
|
|
2788
|
-
if not isinstance(key_uuids, list) or not key_uuids:
|
|
2789
|
-
return _err("key_uuids must be a non-empty list")
|
|
2790
|
-
data = await client.post(
|
|
2791
|
-
f"/v1/mcp/projects/{project_uuid}/keys/bulk/restore",
|
|
2792
|
-
json={"key_uuids": key_uuids},
|
|
2793
|
-
)
|
|
2794
|
-
return _text(data)
|
|
2795
|
-
if name == "list_trash":
|
|
2796
|
-
project_uuid = _project_uuid(args, client)
|
|
2797
|
-
params = {
|
|
2798
|
-
k: v
|
|
2799
|
-
for k, v in {
|
|
2800
|
-
"cursor": args.get("cursor"),
|
|
2801
|
-
"limit": args.get("limit", 50),
|
|
2802
|
-
}.items()
|
|
2803
|
-
if v is not None
|
|
2804
|
-
}
|
|
2805
|
-
data = await client.get(
|
|
2806
|
-
f"/v1/mcp/projects/{project_uuid}/keys/trashed", params=params
|
|
2807
|
-
)
|
|
2808
|
-
return _text(data)
|
|
2809
|
-
if name == "a11y_estimate":
|
|
2810
|
-
project_uuid = _project_uuid(args, client)
|
|
2811
|
-
mode = args.get("mode")
|
|
2812
|
-
if mode not in ("generate", "translate"):
|
|
2813
|
-
return _err("mode must be 'generate' or 'translate'")
|
|
2814
|
-
body: dict[str, Any] = {"mode": mode, "tier": args.get("tier", "standard")}
|
|
2815
|
-
for opt in ("surfaces", "language_codes", "key_uuids", "namespace_uuid"):
|
|
2816
|
-
if args.get(opt):
|
|
2817
|
-
body[opt] = args[opt]
|
|
2818
|
-
data = await client.post(
|
|
2819
|
-
f"/v1/mcp/projects/{project_uuid}/a11y/estimate", json=body
|
|
2820
|
-
)
|
|
2821
|
-
return _text(data)
|
|
2822
|
-
if name == "generate_a11y_variant":
|
|
2823
|
-
project_uuid = _project_uuid(args, client)
|
|
2824
|
-
body: dict[str, Any] = {
|
|
2825
|
-
"tier": args.get("tier", "standard"),
|
|
2826
|
-
"idempotency_key": args.get("idempotency_key") or str(uuid.uuid4()),
|
|
2827
|
-
}
|
|
2828
|
-
for opt in ("surfaces", "key_uuids", "namespace_uuid"):
|
|
2829
|
-
if args.get(opt):
|
|
2830
|
-
body[opt] = args[opt]
|
|
2831
|
-
data = await client.post(
|
|
2832
|
-
f"/v1/mcp/projects/{project_uuid}/a11y/generate", json=body
|
|
2833
|
-
)
|
|
2834
|
-
return _text(data)
|
|
2835
|
-
if name == "translate_a11y_variants":
|
|
2836
|
-
project_uuid = _project_uuid(args, client)
|
|
2837
|
-
language_codes = args.get("language_codes")
|
|
2838
|
-
if not isinstance(language_codes, list) or not language_codes:
|
|
2839
|
-
return _err("language_codes must be a non-empty list")
|
|
2840
|
-
body: dict[str, Any] = {
|
|
2841
|
-
"tier": args.get("tier", "standard"),
|
|
2842
|
-
"language_codes": language_codes,
|
|
2843
|
-
"idempotency_key": args.get("idempotency_key") or str(uuid.uuid4()),
|
|
2844
|
-
}
|
|
2845
|
-
for opt in ("surfaces", "key_uuids", "namespace_uuid"):
|
|
2846
|
-
if args.get(opt):
|
|
2847
|
-
body[opt] = args[opt]
|
|
2848
|
-
data = await client.post(
|
|
2849
|
-
f"/v1/mcp/projects/{project_uuid}/a11y/translate", json=body
|
|
2850
|
-
)
|
|
2851
|
-
return _text(data)
|
|
2852
|
-
if name in ("a11y_report", "list_a11y_gaps"):
|
|
2853
|
-
project_uuid = _project_uuid(args, client)
|
|
2854
|
-
params: dict[str, Any] = {}
|
|
2855
|
-
if args.get("version_id"):
|
|
2856
|
-
params["version_id"] = args["version_id"]
|
|
2857
|
-
if args.get("require_surface"):
|
|
2858
|
-
# repeatable query param: httpx expands a list into repeats
|
|
2859
|
-
params["require_surface"] = args["require_surface"]
|
|
2860
|
-
if args.get("reading_level_max_words") is not None:
|
|
2861
|
-
params["reading_level_max_words"] = args["reading_level_max_words"]
|
|
2862
|
-
report = await client.get(
|
|
2863
|
-
f"/v1/mcp/projects/{project_uuid}/a11y-report", params=params
|
|
2864
|
-
)
|
|
2865
|
-
if name == "a11y_report":
|
|
2866
|
-
return _text(report)
|
|
2867
|
-
# list_a11y_gaps: flatten + client-side filter the items[] list.
|
|
2868
|
-
items = report.get("items", []) if isinstance(report, dict) else []
|
|
2869
|
-
gap_f = args.get("gap")
|
|
2870
|
-
surface_f = args.get("surface")
|
|
2871
|
-
locale_f = args.get("locale")
|
|
2872
|
-
gaps = [
|
|
2873
|
-
it
|
|
2874
|
-
for it in items
|
|
2875
|
-
if (gap_f is None or it.get("gap") == gap_f)
|
|
2876
|
-
and (surface_f is None or it.get("surface") == surface_f)
|
|
2877
|
-
and (locale_f is None or it.get("locale") == locale_f)
|
|
2878
|
-
]
|
|
2879
|
-
applied = {
|
|
2880
|
-
k: v
|
|
2881
|
-
for k, v in {
|
|
2882
|
-
"gap": gap_f,
|
|
2883
|
-
"surface": surface_f,
|
|
2884
|
-
"locale": locale_f,
|
|
2885
|
-
"require_surface": args.get("require_surface"),
|
|
2886
|
-
}.items()
|
|
2887
|
-
if v
|
|
2888
|
-
}
|
|
2889
|
-
return _text(
|
|
2890
|
-
{
|
|
2891
|
-
"project_uuid": report.get("project_uuid", project_uuid)
|
|
2892
|
-
if isinstance(report, dict)
|
|
2893
|
-
else project_uuid,
|
|
2894
|
-
"version_uuid": report.get("version_uuid")
|
|
2895
|
-
if isinstance(report, dict)
|
|
2896
|
-
else None,
|
|
2897
|
-
"keys_scanned": report.get("keys_scanned")
|
|
2898
|
-
if isinstance(report, dict)
|
|
2899
|
-
else None,
|
|
2900
|
-
"filters": applied,
|
|
2901
|
-
"count": len(gaps),
|
|
2902
|
-
"gaps": gaps,
|
|
2903
|
-
}
|
|
2904
|
-
)
|
|
2905
|
-
if name == "list_untranslated_surfaces":
|
|
2906
|
-
project_uuid = _project_uuid(args, client)
|
|
2907
|
-
params: dict[str, Any] = {"include_untranslated": True}
|
|
2908
|
-
if args.get("version_id"):
|
|
2909
|
-
params["version_id"] = args["version_id"]
|
|
2910
|
-
report = await client.get(
|
|
2911
|
-
f"/v1/mcp/projects/{project_uuid}/a11y-report", params=params
|
|
2912
|
-
)
|
|
2913
|
-
items = report.get("items", []) if isinstance(report, dict) else []
|
|
2914
|
-
surf_f = args.get("surface")
|
|
2915
|
-
lang_f = args.get("language_code")
|
|
2916
|
-
cells = [
|
|
2917
|
-
{
|
|
2918
|
-
"key_uuid": it.get("key_uuid"),
|
|
2919
|
-
"key_name": it.get("key_name"),
|
|
2920
|
-
"namespace_slug": it.get("namespace_slug"),
|
|
2921
|
-
"key_type": it.get("key_type"),
|
|
2922
|
-
"surface": it.get("surface"),
|
|
2923
|
-
"language_code": it.get("locale"),
|
|
2924
|
-
"source_value": it.get("source_value"),
|
|
2925
|
-
}
|
|
2926
|
-
for it in items
|
|
2927
|
-
if it.get("gap") == "a11y_untranslated"
|
|
2928
|
-
and (surf_f is None or it.get("surface") == surf_f)
|
|
2929
|
-
and (lang_f is None or it.get("locale") == lang_f)
|
|
2930
|
-
]
|
|
2931
|
-
return _text({"project_uuid": project_uuid, "count": len(cells), "cells": cells})
|
|
2932
|
-
if name in ("set_a11y_variant", "delete_a11y_variant"):
|
|
2933
|
-
project_uuid = _project_uuid(args, client)
|
|
2934
|
-
key_uuid = args.get("key_uuid")
|
|
2935
|
-
language_code = args.get("language_code")
|
|
2936
|
-
surface = args.get("surface")
|
|
2937
|
-
if not key_uuid or not language_code or not surface:
|
|
2938
|
-
return _err("key_uuid, language_code and surface are required")
|
|
2939
|
-
if surface not in A11Y_SURFACES:
|
|
2940
|
-
return _err(f"surface must be one of {', '.join(A11Y_SURFACES)}")
|
|
2941
|
-
# mcp:* code-addressed GENERIC variant route (covers a11y + device;
|
|
2942
|
-
# task 928). Backend resolves the locale code -> uuid and validates the
|
|
2943
|
-
# surface is active server-side. (The legacy /a11y-variants/ path still
|
|
2944
|
-
# works for back-compat, but /variants/ is canonical.)
|
|
2945
|
-
path = (
|
|
2946
|
-
f"/v1/mcp/projects/{project_uuid}/keys/{key_uuid}"
|
|
2947
|
-
f"/variants/{language_code}/{surface}"
|
|
2948
|
-
)
|
|
2949
|
-
if name == "delete_a11y_variant":
|
|
2950
|
-
await client.delete(path)
|
|
2951
|
-
return _text(
|
|
2952
|
-
{
|
|
2953
|
-
"deleted": True,
|
|
2954
|
-
"key_uuid": key_uuid,
|
|
2955
|
-
"language_code": language_code,
|
|
2956
|
-
"surface": surface,
|
|
2957
|
-
}
|
|
2958
|
-
)
|
|
2959
|
-
if "value" not in args:
|
|
2960
|
-
return _err("value is required")
|
|
2961
|
-
data = await client.put(path, json={"value": args["value"]})
|
|
2962
|
-
return _text(data)
|
|
2963
|
-
if name == "list_surfaces":
|
|
2964
|
-
project_uuid = _project_uuid(args, client)
|
|
2965
|
-
data = await client.get(f"/v1/mcp/projects/{project_uuid}/surfaces")
|
|
2966
|
-
return _text(data)
|
|
2967
|
-
if name == "create_surface":
|
|
2968
|
-
project_uuid = _project_uuid(args, client)
|
|
2969
|
-
if not args.get("slug"):
|
|
2970
|
-
return _err("slug is required")
|
|
2971
|
-
body: dict[str, Any] = {"slug": args["slug"]}
|
|
2972
|
-
if "label" in args:
|
|
2973
|
-
body["label"] = args["label"]
|
|
2974
|
-
data = await client.post(
|
|
2975
|
-
f"/v1/mcp/projects/{project_uuid}/surfaces", json=body
|
|
2976
|
-
)
|
|
2977
|
-
return _text(data)
|
|
2978
|
-
if name == "update_surface":
|
|
2979
|
-
project_uuid = _project_uuid(args, client)
|
|
2980
|
-
slug = args.get("slug")
|
|
2981
|
-
if not slug:
|
|
2982
|
-
return _err("slug is required")
|
|
2983
|
-
body: dict[str, Any] = {}
|
|
2984
|
-
for opt in ("label", "active"):
|
|
2985
|
-
if opt in args:
|
|
2986
|
-
body[opt] = args[opt]
|
|
2987
|
-
if not body:
|
|
2988
|
-
return _err("nothing to update: pass label and/or active")
|
|
2989
|
-
data = await client.put(
|
|
2990
|
-
f"/v1/mcp/projects/{project_uuid}/surfaces/{slug}", json=body
|
|
2991
|
-
)
|
|
2992
|
-
return _text(data)
|
|
2993
|
-
if name == "delete_surface":
|
|
2994
|
-
project_uuid = _project_uuid(args, client)
|
|
2995
|
-
slug = args.get("slug")
|
|
2996
|
-
if not slug:
|
|
2997
|
-
return _err("slug is required")
|
|
2998
|
-
await client.delete(f"/v1/mcp/projects/{project_uuid}/surfaces/{slug}")
|
|
2999
|
-
return _text({"deleted": True, "slug": slug})
|
|
3000
|
-
if name == "recommend_surfaces":
|
|
3001
|
-
project_uuid = _project_uuid(args, client)
|
|
3002
|
-
params: dict[str, Any] = {}
|
|
3003
|
-
for opt in ("version_id", "namespace_id", "key_id"):
|
|
3004
|
-
if args.get(opt):
|
|
3005
|
-
params[opt] = args[opt]
|
|
3006
|
-
if args.get("only_gaps"):
|
|
3007
|
-
params["only_gaps"] = "true"
|
|
3008
|
-
data = await client.get(
|
|
3009
|
-
f"/v1/mcp/projects/{project_uuid}/recommend-surfaces",
|
|
3010
|
-
params=params,
|
|
3011
|
-
)
|
|
3012
|
-
return _text(data)
|
|
3013
|
-
if name == "surface_coverage":
|
|
3014
|
-
project_uuid = _project_uuid(args, client)
|
|
3015
|
-
params: dict[str, Any] = {}
|
|
3016
|
-
if args.get("version_id"):
|
|
3017
|
-
params["version_id"] = args["version_id"]
|
|
3018
|
-
data = await client.get(
|
|
3019
|
-
f"/v1/mcp/projects/{project_uuid}/surface-coverage",
|
|
3020
|
-
params=params,
|
|
3021
|
-
)
|
|
3022
|
-
return _text(data)
|
|
3023
|
-
if name == "get_variants":
|
|
3024
|
-
project_uuid = _project_uuid(args, client)
|
|
3025
|
-
key_uuid = args.get("key_uuid")
|
|
3026
|
-
if not key_uuid:
|
|
3027
|
-
return _err("key_uuid is required")
|
|
3028
|
-
data = await client.get(
|
|
3029
|
-
f"/v1/mcp/projects/{project_uuid}/keys/{key_uuid}/variants"
|
|
3030
|
-
)
|
|
3031
|
-
return _text(data)
|
|
3032
|
-
if name in ("set_variant", "delete_variant"):
|
|
3033
|
-
project_uuid = _project_uuid(args, client)
|
|
3034
|
-
key_uuid = args.get("key_uuid")
|
|
3035
|
-
language_code = args.get("language_code")
|
|
3036
|
-
surface = args.get("surface")
|
|
3037
|
-
if not key_uuid or not language_code or not surface:
|
|
3038
|
-
return _err("key_uuid, language_code and surface are required")
|
|
3039
|
-
# generic variant route: backend validates the surface is active (422)
|
|
3040
|
-
# and guards reviewed/approved cells (409).
|
|
3041
|
-
path = (
|
|
3042
|
-
f"/v1/mcp/projects/{project_uuid}/keys/{key_uuid}"
|
|
3043
|
-
f"/variants/{language_code}/{surface}"
|
|
3044
|
-
)
|
|
3045
|
-
if name == "delete_variant":
|
|
3046
|
-
await client.delete(path)
|
|
3047
|
-
return _text(
|
|
3048
|
-
{
|
|
3049
|
-
"deleted": True,
|
|
3050
|
-
"key_uuid": key_uuid,
|
|
3051
|
-
"language_code": language_code,
|
|
3052
|
-
"surface": surface,
|
|
3053
|
-
}
|
|
3054
|
-
)
|
|
3055
|
-
if "value" not in args:
|
|
3056
|
-
return _err("value is required")
|
|
3057
|
-
data = await client.put(path, json={"value": args["value"]})
|
|
3058
|
-
return _text(data)
|
|
3059
|
-
if name == "list_cognitive_candidates":
|
|
3060
|
-
project_uuid = _project_uuid(args, client)
|
|
3061
|
-
params = {
|
|
3062
|
-
k: v
|
|
3063
|
-
for k, v in {
|
|
3064
|
-
"only_unanalyzed": ("true" if args.get("only_unanalyzed") else None),
|
|
3065
|
-
"namespace_uuid": args.get("namespace_uuid"),
|
|
3066
|
-
"limit": args.get("limit", 100),
|
|
3067
|
-
"offset": args.get("offset", 0),
|
|
3068
|
-
}.items()
|
|
3069
|
-
if v is not None
|
|
3070
|
-
}
|
|
3071
|
-
data = await client.get(
|
|
3072
|
-
f"/v1/mcp/projects/{project_uuid}/cognitive/candidates", params=params
|
|
3073
|
-
)
|
|
3074
|
-
return _text(data)
|
|
3075
|
-
if name == "set_cognitive_score":
|
|
3076
|
-
project_uuid = _project_uuid(args, client)
|
|
3077
|
-
key_uuid = args.get("key_uuid")
|
|
3078
|
-
if not key_uuid:
|
|
3079
|
-
return _err("key_uuid is required")
|
|
3080
|
-
if "score" not in args:
|
|
3081
|
-
return _err("score is required")
|
|
3082
|
-
body: dict[str, Any] = {"score": args["score"]}
|
|
3083
|
-
if "suggestion" in args:
|
|
3084
|
-
body["suggestion"] = args["suggestion"]
|
|
3085
|
-
data = await client.put(
|
|
3086
|
-
f"/v1/mcp/projects/{project_uuid}/keys/{key_uuid}/cognitive-score",
|
|
3087
|
-
json=body,
|
|
3088
|
-
)
|
|
3089
|
-
return _text(data)
|
|
3090
|
-
if name == "analyze_cognitive":
|
|
3091
|
-
project_uuid = _project_uuid(args, client)
|
|
3092
|
-
body: dict[str, Any] = {
|
|
3093
|
-
"tier": args.get("tier", "standard"),
|
|
3094
|
-
"idempotency_key": args.get("idempotency_key") or str(uuid.uuid4()),
|
|
3095
|
-
}
|
|
3096
|
-
for opt in ("key_uuids", "namespace_uuid"):
|
|
3097
|
-
if args.get(opt):
|
|
3098
|
-
body[opt] = args[opt]
|
|
3099
|
-
data = await client.post(
|
|
3100
|
-
f"/v1/mcp/projects/{project_uuid}/cognitive/analyze", json=body
|
|
3101
|
-
)
|
|
3102
|
-
return _text(data)
|
|
3103
|
-
# --- a11y conformance reports + remediation plan (sprint 111c4658, B5) ---
|
|
3104
|
-
if name in ("wcag_report", "eaa_statement"):
|
|
3105
|
-
project_uuid = _project_uuid(args, client)
|
|
3106
|
-
params: dict[str, Any] = {}
|
|
3107
|
-
if args.get("version_id"):
|
|
3108
|
-
params["version_id"] = args["version_id"]
|
|
3109
|
-
segment = "wcag-report" if name == "wcag_report" else "eaa-statement"
|
|
3110
|
-
data = await client.get(
|
|
3111
|
-
f"/v1/mcp/projects/{project_uuid}/{segment}", params=params
|
|
3112
|
-
)
|
|
3113
|
-
return _text(data)
|
|
3114
|
-
if name == "list_a11y_review_queue":
|
|
3115
|
-
project_uuid = _project_uuid(args, client)
|
|
3116
|
-
params: dict[str, Any] = {}
|
|
3117
|
-
for opt in (
|
|
3118
|
-
"version_id",
|
|
3119
|
-
"state",
|
|
3120
|
-
"locale",
|
|
3121
|
-
"surface",
|
|
3122
|
-
"namespace_uuid",
|
|
3123
|
-
"key_uuid",
|
|
3124
|
-
"limit",
|
|
3125
|
-
"offset",
|
|
3126
|
-
):
|
|
3127
|
-
if args.get(opt) is not None:
|
|
3128
|
-
params[opt] = args[opt]
|
|
3129
|
-
if args.get("include_ignored"):
|
|
3130
|
-
params["include_ignored"] = "true"
|
|
3131
|
-
if args.get("group_by_key"):
|
|
3132
|
-
params["group_by_key"] = "true"
|
|
3133
|
-
data = await client.get(
|
|
3134
|
-
f"/v1/mcp/projects/{project_uuid}/a11y/review/queue", params=params
|
|
3135
|
-
)
|
|
3136
|
-
return _text(data)
|
|
3137
|
-
if name == "a11y_remediation_plan_get":
|
|
3138
|
-
project_uuid = _project_uuid(args, client)
|
|
3139
|
-
params: dict[str, Any] = {}
|
|
3140
|
-
if args.get("version_id"):
|
|
3141
|
-
params["version_id"] = args["version_id"]
|
|
3142
|
-
data = await client.get(
|
|
3143
|
-
f"/v1/mcp/projects/{project_uuid}/a11y/remediation-plan", params=params
|
|
3144
|
-
)
|
|
3145
|
-
return _text(data)
|
|
3146
|
-
if name == "a11y_remediation_plan_apply":
|
|
3147
|
-
project_uuid = _project_uuid(args, client)
|
|
3148
|
-
body: dict[str, Any] = {}
|
|
3149
|
-
if args.get("version_id"):
|
|
3150
|
-
body["version_id"] = args["version_id"]
|
|
3151
|
-
data = await client.post(
|
|
3152
|
-
f"/v1/mcp/projects/{project_uuid}/a11y/remediation-plan/apply",
|
|
3153
|
-
json=body,
|
|
3154
|
-
)
|
|
3155
|
-
return _text(data)
|
|
3156
|
-
if name == "score_cognitive_local":
|
|
3157
|
-
project_uuid = _project_uuid(args, client)
|
|
3158
|
-
body: dict[str, Any] = {}
|
|
3159
|
-
for opt in ("key_uuids", "namespace_uuid"):
|
|
3160
|
-
if args.get(opt):
|
|
3161
|
-
body[opt] = args[opt]
|
|
3162
|
-
if args.get("overwrite"):
|
|
3163
|
-
body["overwrite"] = True
|
|
3164
|
-
data = await client.post(
|
|
3165
|
-
f"/v1/mcp/projects/{project_uuid}/cognitive/score-local", json=body
|
|
3166
|
-
)
|
|
3167
|
-
return _text(data)
|
|
3168
|
-
# --- Placeholder mismatches (PM2 #173) ---
|
|
3169
|
-
if name == "list_placeholder_mismatches":
|
|
3170
|
-
project_uuid = _project_uuid(args, client)
|
|
3171
|
-
params: dict[str, Any] = {}
|
|
3172
|
-
for opt in ("version_id", "key_id", "language_code"):
|
|
3173
|
-
if args.get(opt):
|
|
3174
|
-
params[opt] = args[opt]
|
|
3175
|
-
data = await client.get(
|
|
3176
|
-
f"/v1/mcp/projects/{project_uuid}/placeholder-mismatches",
|
|
3177
|
-
params=params,
|
|
3178
|
-
)
|
|
3179
|
-
return _text(data)
|
|
3180
|
-
# --- Source Health: duplicates (#1116; backend #1114, PR #147) ---
|
|
3181
|
-
if name == "list_source_duplicates":
|
|
3182
|
-
project_uuid = _project_uuid(args, client)
|
|
3183
|
-
params = {
|
|
3184
|
-
k: v
|
|
3185
|
-
for k, v in {
|
|
3186
|
-
"status": args.get("status"),
|
|
3187
|
-
"version_id": args.get("version_id"),
|
|
3188
|
-
}.items()
|
|
3189
|
-
if v is not None
|
|
3190
|
-
}
|
|
3191
|
-
data = await client.get(
|
|
3192
|
-
f"/v1/mcp/projects/{project_uuid}/source-duplicates",
|
|
3193
|
-
params=params,
|
|
3194
|
-
)
|
|
3195
|
-
return _text(data)
|
|
3196
|
-
if name == "set_duplicate_status":
|
|
3197
|
-
project_uuid = _project_uuid(args, client)
|
|
3198
|
-
if not args.get("source_value"):
|
|
3199
|
-
return _err("source_value is required")
|
|
3200
|
-
if not args.get("status"):
|
|
3201
|
-
return _err("status is required")
|
|
3202
|
-
body: dict[str, Any] = {
|
|
3203
|
-
"source_value": args["source_value"],
|
|
3204
|
-
"status": args["status"],
|
|
3205
|
-
}
|
|
3206
|
-
if args.get("note"):
|
|
3207
|
-
body["note"] = args["note"]
|
|
3208
|
-
if args.get("files_modified"):
|
|
3209
|
-
body["files_modified"] = args["files_modified"]
|
|
3210
|
-
data = await client.put(
|
|
3211
|
-
f"/v1/mcp/projects/{project_uuid}/source-duplicates/status",
|
|
3212
|
-
json=body,
|
|
3213
|
-
)
|
|
3214
|
-
return _text(data)
|
|
3215
|
-
if name == "set_source_duplicate_merge_plan":
|
|
3216
|
-
project_uuid = _project_uuid(args, client)
|
|
3217
|
-
if not args.get("source_value"):
|
|
3218
|
-
return _err("source_value is required")
|
|
3219
|
-
body = {"source_value": args["source_value"]}
|
|
3220
|
-
# merge_plan may be an object OR explicit null (to clear); send it
|
|
3221
|
-
# whenever the key is present at all.
|
|
3222
|
-
if "merge_plan" in args:
|
|
3223
|
-
body["merge_plan"] = args["merge_plan"]
|
|
3224
|
-
if args.get("files_modified"):
|
|
3225
|
-
body["files_modified"] = args["files_modified"]
|
|
3226
|
-
data = await client.put(
|
|
3227
|
-
f"/v1/mcp/projects/{project_uuid}/source-duplicates/merge-plan",
|
|
3228
|
-
json=body,
|
|
3229
|
-
)
|
|
3230
|
-
return _text(data)
|
|
3231
|
-
if name == "mark_cluster_applied":
|
|
3232
|
-
project_uuid = _project_uuid(args, client)
|
|
3233
|
-
if not args.get("source_value") or not args.get("canonical_key_uuid"):
|
|
3234
|
-
return _err("source_value and canonical_key_uuid are required")
|
|
3235
|
-
body = {
|
|
3236
|
-
"source_value": args["source_value"],
|
|
3237
|
-
"canonical_key_uuid": args["canonical_key_uuid"],
|
|
3238
|
-
}
|
|
3239
|
-
if "applied" in args:
|
|
3240
|
-
body["applied"] = bool(args["applied"])
|
|
3241
|
-
if args.get("files_modified"):
|
|
3242
|
-
body["files_modified"] = args["files_modified"]
|
|
3243
|
-
data = await client.put(
|
|
3244
|
-
f"/v1/mcp/projects/{project_uuid}/source-duplicates/cluster-applied",
|
|
3245
|
-
json=body,
|
|
3246
|
-
)
|
|
3247
|
-
return _text(data)
|
|
3248
|
-
if name == "apply_new_key_cluster":
|
|
3249
|
-
project_uuid = _project_uuid(args, client)
|
|
3250
|
-
new_key = args.get("new_key")
|
|
3251
|
-
if not args.get("source_value") or not isinstance(new_key, dict):
|
|
3252
|
-
return _err("source_value and new_key {name, namespace} are required")
|
|
3253
|
-
if not new_key.get("name") or not new_key.get("namespace"):
|
|
3254
|
-
return _err("new_key requires name and namespace")
|
|
3255
|
-
body = {"source_value": args["source_value"], "new_key": new_key}
|
|
3256
|
-
if args.get("files_modified"):
|
|
3257
|
-
body["files_modified"] = args["files_modified"]
|
|
3258
|
-
data = await client.post(
|
|
3259
|
-
f"/v1/mcp/projects/{project_uuid}/source-duplicates/apply-cluster",
|
|
3260
|
-
json=body,
|
|
3261
|
-
)
|
|
3262
|
-
return _text(data)
|
|
3263
|
-
if name == "set_duplicate_status_bulk":
|
|
3264
|
-
project_uuid = _project_uuid(args, client)
|
|
3265
|
-
source_values = args.get("source_values")
|
|
3266
|
-
if not isinstance(source_values, list) or not source_values:
|
|
3267
|
-
return _err("source_values must be a non-empty list")
|
|
3268
|
-
if not args.get("status"):
|
|
3269
|
-
return _err("status is required")
|
|
3270
|
-
body = {"source_values": source_values, "status": args["status"]}
|
|
3271
|
-
if args.get("note") is not None:
|
|
3272
|
-
body["note"] = args["note"]
|
|
3273
|
-
data = await client.put(
|
|
3274
|
-
f"/v1/mcp/projects/{project_uuid}/source-duplicates/status/bulk",
|
|
3275
|
-
json=body,
|
|
3276
|
-
)
|
|
3277
|
-
return _text(data)
|
|
3278
|
-
if name == "propose_duplicate_merge_plan":
|
|
3279
|
-
project_uuid = _project_uuid(args, client)
|
|
3280
|
-
if not args.get("source_value"):
|
|
3281
|
-
return _err("source_value is required")
|
|
3282
|
-
body = {"source_value": args["source_value"]}
|
|
3283
|
-
if args.get("namespace_slug"):
|
|
3284
|
-
body["namespace_slug"] = args["namespace_slug"]
|
|
3285
|
-
if args.get("regenerate"):
|
|
3286
|
-
body["regenerate"] = True
|
|
3287
|
-
url = f"/v1/mcp/projects/{project_uuid}/source-duplicates/propose-plan"
|
|
3288
|
-
if args.get("version_id"):
|
|
3289
|
-
url += f"?version_id={args['version_id']}"
|
|
3290
|
-
data = await client.post(url, json=body)
|
|
3291
|
-
return _text(data)
|
|
3292
|
-
return _err(f"unknown tool: {name}")
|
|
3293
|
-
except VerbumiaApiError as exc:
|
|
3294
|
-
return _err(f"API {exc.status}: {exc.body}")
|
|
3295
|
-
except KeyError as exc:
|
|
3296
|
-
return _err(f"missing required argument: {exc}")
|
|
3297
|
-
except Exception as exc: # pragma: no cover
|
|
3298
|
-
return _err(f"{type(exc).__name__}: {exc}")
|
|
3299
|
-
|
|
3300
|
-
return _list_tools, _call
|