@sonenta/mcp 0.29.0 → 0.31.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.29.0",
3
+ "version": "0.31.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",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sonenta-mcp"
3
- version = "0.29.0"
3
+ version = "0.31.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"
@@ -1,3 +1,3 @@
1
1
  """Sonenta MCP server (MIT)."""
2
2
 
3
- __version__ = "0.29.0"
3
+ __version__ = "0.31.0"
@@ -89,16 +89,20 @@ 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.
92
+ # RS1 (task 1367) + FM1 (task 1390): audit trail of code edits an agent made while
93
+ # 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[]. FM1 changed
96
+ # the semantics to UNION-MERGE (accumulate + dedup by tuple), not overwrite.
96
97
  _FILES_MODIFIED_SCHEMA = {
97
98
  "type": "array",
98
99
  "description": (
99
100
  "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."
101
+ "resolving this group: each {path (required), line?, key_name?}. Accepted on "
102
+ "every resolution path and MERGED (union + dedup by the full {path,line,"
103
+ "key_name} tuple) into the record — so record your edits at whatever step you "
104
+ "make them and they accumulate; omit to preserve the existing list. Always "
105
+ "pass the STRUCTURED field, never just a note."
102
106
  ),
103
107
  "items": {
104
108
  "type": "object",
@@ -2225,13 +2229,26 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
2225
2229
  "items": {
2226
2230
  "type": "object",
2227
2231
  "properties": {
2228
- "canonical_key_uuid": {"type": "string"},
2232
+ "canonical_key_uuid": {
2233
+ "type": "string",
2234
+ "description": "An EXISTING key kept as the canonical survivor. Mutually exclusive with new_key.",
2235
+ },
2236
+ "new_key": {
2237
+ "type": "object",
2238
+ "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.",
2239
+ "properties": {
2240
+ "name": {"type": "string", "description": "New key name (must not collide with an active key)."},
2241
+ "namespace": {"type": "string", "description": "Namespace slug the new key lives in (must exist)."},
2242
+ },
2243
+ "required": ["name", "namespace"],
2244
+ },
2229
2245
  "redundant_key_uuids": {
2230
2246
  "type": "array",
2231
2247
  "items": {"type": "string"},
2248
+ "description": "Keys folded into the canonical. For a new_key cluster, this is ALL the group's keys.",
2232
2249
  },
2233
2250
  },
2234
- "required": ["canonical_key_uuid", "redundant_key_uuids"],
2251
+ "required": ["redundant_key_uuids"],
2235
2252
  },
2236
2253
  },
2237
2254
  "survivor_outcome": {
@@ -2278,11 +2295,55 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
2278
2295
  "type": "boolean",
2279
2296
  "description": "True to mark applied (default), false to un-mark.",
2280
2297
  },
2298
+ "files_modified": _FILES_MODIFIED_SCHEMA,
2281
2299
  },
2282
2300
  "required": ["source_value", "canonical_key_uuid"],
2283
2301
  "additionalProperties": False,
2284
2302
  },
2285
2303
  ),
2304
+ Tool(
2305
+ name="apply_new_key_cluster",
2306
+ description=(
2307
+ "Consolidate a duplicate group onto a BRAND-NEW canonical key (NK1) — "
2308
+ "the BACKEND does the atomic data transaction so no translation is "
2309
+ "lost: it creates the new key (type/description/context inherited from "
2310
+ "the group's most-complete key), COPIES every language value from that "
2311
+ "most-complete key, and TRASHES all the old keys. Address it by "
2312
+ "source_value + new_key {name, namespace} (the namespace must exist and "
2313
+ "the name must be free). Returns {new_key_uuid, name, namespace, "
2314
+ "copied_from_key_uuid, copied_languages, trashed_key_uuids}. YOUR job "
2315
+ "after this is the CODE only: repoint every t() usage of the trashed "
2316
+ "keys onto new_key_uuid, and record those edits in `files_modified` "
2317
+ "(accepted right here, union-merged). You do NOT create_key or "
2318
+ "delete_keys_bulk yourself — the backend owns that. Author the "
2319
+ "new_key cluster in the merge plan first (on the dev's acceptance)."
2320
+ ),
2321
+ inputSchema={
2322
+ "type": "object",
2323
+ "properties": {
2324
+ "project_uuid": {
2325
+ "type": "string",
2326
+ "description": "Project UUID. Required when multiple projects are configured (SONENTA_PROJECTS); defaults to the single configured project otherwise.",
2327
+ },
2328
+ "source_value": {
2329
+ "type": "string",
2330
+ "description": "The shared source-language value identifying the duplicate group.",
2331
+ },
2332
+ "new_key": {
2333
+ "type": "object",
2334
+ "description": "The new canonical key to create and consolidate the group onto.",
2335
+ "properties": {
2336
+ "name": {"type": "string", "description": "New key name (must not collide with an active key)."},
2337
+ "namespace": {"type": "string", "description": "Namespace slug the new key lives in (must exist)."},
2338
+ },
2339
+ "required": ["name", "namespace"],
2340
+ },
2341
+ "files_modified": _FILES_MODIFIED_SCHEMA,
2342
+ },
2343
+ "required": ["source_value", "new_key"],
2344
+ "additionalProperties": False,
2345
+ },
2346
+ ),
2286
2347
  ]
2287
2348
 
2288
2349
  @server.call_tool()
@@ -3033,11 +3094,28 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
3033
3094
  }
3034
3095
  if "applied" in args:
3035
3096
  body["applied"] = bool(args["applied"])
3097
+ if args.get("files_modified"):
3098
+ body["files_modified"] = args["files_modified"]
3036
3099
  data = await client.put(
3037
3100
  f"/v1/mcp/projects/{project_uuid}/source-duplicates/cluster-applied",
3038
3101
  json=body,
3039
3102
  )
3040
3103
  return _text(data)
3104
+ if name == "apply_new_key_cluster":
3105
+ project_uuid = _project_uuid(args, client)
3106
+ new_key = args.get("new_key")
3107
+ if not args.get("source_value") or not isinstance(new_key, dict):
3108
+ return _err("source_value and new_key {name, namespace} are required")
3109
+ if not new_key.get("name") or not new_key.get("namespace"):
3110
+ return _err("new_key requires name and namespace")
3111
+ body = {"source_value": args["source_value"], "new_key": new_key}
3112
+ if args.get("files_modified"):
3113
+ body["files_modified"] = args["files_modified"]
3114
+ data = await client.post(
3115
+ f"/v1/mcp/projects/{project_uuid}/source-duplicates/apply-cluster",
3116
+ json=body,
3117
+ )
3118
+ return _text(data)
3041
3119
  return _err(f"unknown tool: {name}")
3042
3120
  except VerbumiaApiError as exc:
3043
3121
  return _err(f"API {exc.status}: {exc.body}")