@sonenta/mcp 0.31.0 → 0.33.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.31.0",
3
+ "version": "0.33.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.31.0"
3
+ version = "0.33.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.31.0"
3
+ __version__ = "0.33.0"
@@ -89,27 +89,33 @@ 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) + FM1 (task 1390): audit trail of code edits an agent made while
93
- # resolving a duplicate group — accepted on ALL FOUR resolution paths
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
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.
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).
97
98
  _FILES_MODIFIED_SCHEMA = {
98
99
  "type": "array",
99
100
  "description": (
100
- "Optional audit trail of the code edits you made (e.g. t() repoints) while "
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."
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."
106
109
  ),
107
110
  "items": {
108
111
  "type": "object",
109
112
  "properties": {
110
113
  "path": {"type": "string", "description": "Repo-relative file path that was edited."},
111
114
  "line": {"type": "integer", "description": "Optional 1-based line number of the edit."},
112
- "key_name": {"type": "string", "description": "Optional key (namespace::name or name) the edit concerns."},
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."},
113
119
  },
114
120
  "required": ["path"],
115
121
  "additionalProperties": False,
@@ -1377,6 +1383,44 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
1377
1383
  "additionalProperties": False,
1378
1384
  },
1379
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
+ ),
1380
1424
  Tool(
1381
1425
  name="set_a11y_variant",
1382
1426
  description=(
@@ -2785,6 +2829,33 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
2785
2829
  "gaps": gaps,
2786
2830
  }
2787
2831
  )
2832
+ if name == "list_untranslated_surfaces":
2833
+ project_uuid = _project_uuid(args, client)
2834
+ params: dict[str, Any] = {"include_untranslated": True}
2835
+ if args.get("version_id"):
2836
+ params["version_id"] = args["version_id"]
2837
+ report = await client.get(
2838
+ f"/v1/mcp/projects/{project_uuid}/a11y-report", params=params
2839
+ )
2840
+ items = report.get("items", []) if isinstance(report, dict) else []
2841
+ surf_f = args.get("surface")
2842
+ lang_f = args.get("language_code")
2843
+ cells = [
2844
+ {
2845
+ "key_uuid": it.get("key_uuid"),
2846
+ "key_name": it.get("key_name"),
2847
+ "namespace_slug": it.get("namespace_slug"),
2848
+ "key_type": it.get("key_type"),
2849
+ "surface": it.get("surface"),
2850
+ "language_code": it.get("locale"),
2851
+ "source_value": it.get("source_value"),
2852
+ }
2853
+ for it in items
2854
+ if it.get("gap") == "a11y_untranslated"
2855
+ and (surf_f is None or it.get("surface") == surf_f)
2856
+ and (lang_f is None or it.get("locale") == lang_f)
2857
+ ]
2858
+ return _text({"project_uuid": project_uuid, "count": len(cells), "cells": cells})
2788
2859
  if name in ("set_a11y_variant", "delete_a11y_variant"):
2789
2860
  project_uuid = _project_uuid(args, client)
2790
2861
  key_uuid = args.get("key_uuid")