@sonenta/mcp 0.28.0 → 0.30.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sonenta/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "MCP server for Sonenta translation management \u2014 wires Claude Desktop and other MCP clients into your project's keys, missing-key feed, and translation drafts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://sonenta.com",
|
|
Binary file
|
package/python/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sonenta-mcp"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.30.0"
|
|
4
4
|
description = "Model Context Protocol server for Sonenta — list projects, missing keys, propose translations from Claude Desktop and other MCP clients."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12,<3.14"
|
|
@@ -89,6 +89,29 @@ from .client import VerbumiaApiError, VerbumiaClient
|
|
|
89
89
|
# visible text. `surfaceKinds` in the variants matrix tags each as a11y.
|
|
90
90
|
A11Y_SURFACES = ("aria_label", "alt_text", "screen_reader", "plain_language")
|
|
91
91
|
|
|
92
|
+
# RS1 (task 1367): audit trail of code edits an agent made while resolving a
|
|
93
|
+
# duplicate group — passed on set_source_duplicate_merge_plan / set_duplicate_status,
|
|
94
|
+
# persisted + surfaced in resolved_duplicates[]. Supplying it overwrites; omitting
|
|
95
|
+
# preserves the prior value.
|
|
96
|
+
_FILES_MODIFIED_SCHEMA = {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"description": (
|
|
99
|
+
"Optional audit trail of the code edits you made (e.g. t() repoints) while "
|
|
100
|
+
"resolving this group: each {path (required), line?, key_name?}. Supplying it "
|
|
101
|
+
"overwrites the stored record; omit to preserve the prior value."
|
|
102
|
+
),
|
|
103
|
+
"items": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"properties": {
|
|
106
|
+
"path": {"type": "string", "description": "Repo-relative file path that was edited."},
|
|
107
|
+
"line": {"type": "integer", "description": "Optional 1-based line number of the edit."},
|
|
108
|
+
"key_name": {"type": "string", "description": "Optional key (namespace::name or name) the edit concerns."},
|
|
109
|
+
},
|
|
110
|
+
"required": ["path"],
|
|
111
|
+
"additionalProperties": False,
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
92
115
|
# Key semantic types (a11y key.type pivot). Default "text". The type decides
|
|
93
116
|
# which a11y treatments a key offers (e.g. an `image` key's base value IS its
|
|
94
117
|
# alt text; a `button` offers aria_label) — the engine gates variant/a11y tools
|
|
@@ -2158,6 +2181,7 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
|
|
|
2158
2181
|
"type": "string",
|
|
2159
2182
|
"description": "Optional human/agent note recording why the status was set (audit trail).",
|
|
2160
2183
|
},
|
|
2184
|
+
"files_modified": _FILES_MODIFIED_SCHEMA,
|
|
2161
2185
|
},
|
|
2162
2186
|
"required": ["source_value", "status"],
|
|
2163
2187
|
"additionalProperties": False,
|
|
@@ -2201,13 +2225,26 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
|
|
|
2201
2225
|
"items": {
|
|
2202
2226
|
"type": "object",
|
|
2203
2227
|
"properties": {
|
|
2204
|
-
"canonical_key_uuid": {
|
|
2228
|
+
"canonical_key_uuid": {
|
|
2229
|
+
"type": "string",
|
|
2230
|
+
"description": "An EXISTING key kept as the canonical survivor. Mutually exclusive with new_key.",
|
|
2231
|
+
},
|
|
2232
|
+
"new_key": {
|
|
2233
|
+
"type": "object",
|
|
2234
|
+
"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.",
|
|
2235
|
+
"properties": {
|
|
2236
|
+
"name": {"type": "string", "description": "New key name (must not collide with an active key)."},
|
|
2237
|
+
"namespace": {"type": "string", "description": "Namespace slug the new key lives in (must exist)."},
|
|
2238
|
+
},
|
|
2239
|
+
"required": ["name", "namespace"],
|
|
2240
|
+
},
|
|
2205
2241
|
"redundant_key_uuids": {
|
|
2206
2242
|
"type": "array",
|
|
2207
2243
|
"items": {"type": "string"},
|
|
2244
|
+
"description": "Keys folded into the canonical. For a new_key cluster, this is ALL the group's keys.",
|
|
2208
2245
|
},
|
|
2209
2246
|
},
|
|
2210
|
-
"required": ["
|
|
2247
|
+
"required": ["redundant_key_uuids"],
|
|
2211
2248
|
},
|
|
2212
2249
|
},
|
|
2213
2250
|
"survivor_outcome": {
|
|
@@ -2217,6 +2254,7 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
|
|
|
2217
2254
|
},
|
|
2218
2255
|
},
|
|
2219
2256
|
},
|
|
2257
|
+
"files_modified": _FILES_MODIFIED_SCHEMA,
|
|
2220
2258
|
},
|
|
2221
2259
|
"required": ["source_value"],
|
|
2222
2260
|
"additionalProperties": False,
|
|
@@ -2258,6 +2296,48 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
|
|
|
2258
2296
|
"additionalProperties": False,
|
|
2259
2297
|
},
|
|
2260
2298
|
),
|
|
2299
|
+
Tool(
|
|
2300
|
+
name="apply_new_key_cluster",
|
|
2301
|
+
description=(
|
|
2302
|
+
"Consolidate a duplicate group onto a BRAND-NEW canonical key (NK1) — "
|
|
2303
|
+
"the BACKEND does the atomic data transaction so no translation is "
|
|
2304
|
+
"lost: it creates the new key (type/description/context inherited from "
|
|
2305
|
+
"the group's most-complete key), COPIES every language value from that "
|
|
2306
|
+
"most-complete key, and TRASHES all the old keys. Address it by "
|
|
2307
|
+
"source_value + new_key {name, namespace} (the namespace must exist and "
|
|
2308
|
+
"the name must be free). Returns {new_key_uuid, name, namespace, "
|
|
2309
|
+
"copied_from_key_uuid, copied_languages, trashed_key_uuids}. YOUR job "
|
|
2310
|
+
"after this is the CODE only: repoint every t() usage of the trashed "
|
|
2311
|
+
"keys onto new_key_uuid, then record it via set_source_duplicate_merge_"
|
|
2312
|
+
"plan / set_duplicate_status files_modified. You do NOT create_key or "
|
|
2313
|
+
"delete_keys_bulk yourself — the backend owns that. Author the "
|
|
2314
|
+
"new_key cluster in the merge plan first (on the dev's acceptance)."
|
|
2315
|
+
),
|
|
2316
|
+
inputSchema={
|
|
2317
|
+
"type": "object",
|
|
2318
|
+
"properties": {
|
|
2319
|
+
"project_uuid": {
|
|
2320
|
+
"type": "string",
|
|
2321
|
+
"description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
|
|
2322
|
+
},
|
|
2323
|
+
"source_value": {
|
|
2324
|
+
"type": "string",
|
|
2325
|
+
"description": "The shared source-language value identifying the duplicate group.",
|
|
2326
|
+
},
|
|
2327
|
+
"new_key": {
|
|
2328
|
+
"type": "object",
|
|
2329
|
+
"description": "The new canonical key to create and consolidate the group onto.",
|
|
2330
|
+
"properties": {
|
|
2331
|
+
"name": {"type": "string", "description": "New key name (must not collide with an active key)."},
|
|
2332
|
+
"namespace": {"type": "string", "description": "Namespace slug the new key lives in (must exist)."},
|
|
2333
|
+
},
|
|
2334
|
+
"required": ["name", "namespace"],
|
|
2335
|
+
},
|
|
2336
|
+
},
|
|
2337
|
+
"required": ["source_value", "new_key"],
|
|
2338
|
+
"additionalProperties": False,
|
|
2339
|
+
},
|
|
2340
|
+
),
|
|
2261
2341
|
]
|
|
2262
2342
|
|
|
2263
2343
|
@server.call_tool()
|
|
@@ -2975,6 +3055,8 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
|
|
|
2975
3055
|
}
|
|
2976
3056
|
if args.get("note"):
|
|
2977
3057
|
body["note"] = args["note"]
|
|
3058
|
+
if args.get("files_modified"):
|
|
3059
|
+
body["files_modified"] = args["files_modified"]
|
|
2978
3060
|
data = await client.put(
|
|
2979
3061
|
f"/v1/mcp/projects/{project_uuid}/source-duplicates/status",
|
|
2980
3062
|
json=body,
|
|
@@ -2989,6 +3071,8 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
|
|
|
2989
3071
|
# whenever the key is present at all.
|
|
2990
3072
|
if "merge_plan" in args:
|
|
2991
3073
|
body["merge_plan"] = args["merge_plan"]
|
|
3074
|
+
if args.get("files_modified"):
|
|
3075
|
+
body["files_modified"] = args["files_modified"]
|
|
2992
3076
|
data = await client.put(
|
|
2993
3077
|
f"/v1/mcp/projects/{project_uuid}/source-duplicates/merge-plan",
|
|
2994
3078
|
json=body,
|
|
@@ -3009,6 +3093,18 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
|
|
|
3009
3093
|
json=body,
|
|
3010
3094
|
)
|
|
3011
3095
|
return _text(data)
|
|
3096
|
+
if name == "apply_new_key_cluster":
|
|
3097
|
+
project_uuid = _project_uuid(args, client)
|
|
3098
|
+
new_key = args.get("new_key")
|
|
3099
|
+
if not args.get("source_value") or not isinstance(new_key, dict):
|
|
3100
|
+
return _err("source_value and new_key {name, namespace} are required")
|
|
3101
|
+
if not new_key.get("name") or not new_key.get("namespace"):
|
|
3102
|
+
return _err("new_key requires name and namespace")
|
|
3103
|
+
data = await client.post(
|
|
3104
|
+
f"/v1/mcp/projects/{project_uuid}/source-duplicates/apply-cluster",
|
|
3105
|
+
json={"source_value": args["source_value"], "new_key": new_key},
|
|
3106
|
+
)
|
|
3107
|
+
return _text(data)
|
|
3012
3108
|
return _err(f"unknown tool: {name}")
|
|
3013
3109
|
except VerbumiaApiError as exc:
|
|
3014
3110
|
return _err(f"API {exc.status}: {exc.body}")
|
|
Binary file
|