@sonenta/mcp 0.18.0 → 0.19.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.18.0",
3
+ "version": "0.19.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 = "verbumia-mcp"
3
- version = "0.18.0"
3
+ version = "0.19.0"
4
4
  description = "Model Context Protocol server for Verbumia — 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
  """Verbumia MCP server (MIT)."""
2
2
 
3
- __version__ = "0.18.0"
3
+ __version__ = "0.19.0"
@@ -9,9 +9,9 @@ Tools (V1 MCP surface; glossary_* added 2026-05-24; reports + trash added 2026-0
9
9
  - missing_keys_stats — count-only summary of missing events (drives pagination)
10
10
  - acknowledge_missing_keys— batch-resolve missing events (cleanup the dashboard)
11
11
  - create_namespace — idempotent namespace creation (slug validated, slug_invalid on bad input)
12
- - create_key — idempotent key creation, optionally seeds source value
12
+ - create_key — idempotent key creation; optionally seeds source value + sets semantic `type`
13
13
  - create_keys_bulk — batch create_key (up to 500/call, per-item results)
14
- - update_key — edit a key's SOURCE value in place (PATCH); demotes reviewed/approved targets to needs-review
14
+ - update_key — edit a key's SOURCE value / `type` / description in place (PATCH); demotes reviewed/approved targets to needs-review
15
15
  - update_keys_bulk — batch update_key (up to 500/call, per-item results)
16
16
  - propose_translation — submit a translation value (auto-creates the key if missing)
17
17
  - propose_translations_bulk— batch propose_translation (up to 500/call, per-item results)
@@ -84,6 +84,30 @@ from .client import VerbumiaApiError, VerbumiaClient
84
84
  # visible text. `surfaceKinds` in the variants matrix tags each as a11y.
85
85
  A11Y_SURFACES = ("aria_label", "alt_text", "screen_reader", "plain_language")
86
86
 
87
+ # Key semantic types (a11y key.type pivot). Default "text". The type decides
88
+ # which a11y treatments a key offers (e.g. an `image` key's base value IS its
89
+ # alt text; a `button` offers aria_label) — the engine gates variant/a11y tools
90
+ # by type server-side, so listing these here is just for create/update_key.
91
+ KEY_TYPES = (
92
+ "text",
93
+ "heading",
94
+ "label",
95
+ "caption",
96
+ "badge",
97
+ "tooltip",
98
+ "error_message",
99
+ "toast",
100
+ "button",
101
+ "link",
102
+ "menu_item",
103
+ "input_placeholder",
104
+ "input_label",
105
+ "image",
106
+ "icon",
107
+ "meta_title",
108
+ "meta_description",
109
+ )
110
+
87
111
 
88
112
  def _project_uuid(arguments: dict | None, client: VerbumiaClient) -> str:
89
113
  """Resolve project_uuid from per-call args or the configured env scope.
@@ -371,7 +395,8 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
371
395
  "Returns created=false if the key already exists. When "
372
396
  "source_value is supplied AND the project has a source "
373
397
  "language, the value is also written as a source-language "
374
- "translation in the same call."
398
+ "translation in the same call. Pass `type` (default 'text') to "
399
+ "set the key's semantic type, which decides its a11y treatments."
375
400
  ),
376
401
  inputSchema={
377
402
  "type": "object",
@@ -395,6 +420,12 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
395
420
  "description": {"type": "string"},
396
421
  "plurals": {"type": "boolean", "default": False},
397
422
  "max_length": {"type": "integer", "minimum": 1},
423
+ "type": {
424
+ "type": "string",
425
+ "enum": list(KEY_TYPES),
426
+ "default": "text",
427
+ "description": "Semantic key type (default 'text'). Decides which a11y treatments apply — e.g. 'image' (base value is the alt text), 'icon' (base is the accessible name), 'button'/'link' (offer aria_label), 'heading'/'label' (plain_language/screen_reader).",
428
+ },
398
429
  },
399
430
  "required": ["namespace", "name"],
400
431
  "additionalProperties": False,
@@ -465,6 +496,7 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
465
496
  "description": {"type": "string"},
466
497
  "plurals": {"type": "boolean", "default": False},
467
498
  "max_length": {"type": "integer", "minimum": 1},
499
+ "type": {"type": "string", "enum": list(KEY_TYPES), "default": "text", "description": "Semantic key type (default 'text'); see create_key."},
468
500
  },
469
501
  "required": ["namespace", "name"],
470
502
  "additionalProperties": False,
@@ -478,7 +510,8 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
478
510
  Tool(
479
511
  name="update_key",
480
512
  description=(
481
- "Edit a key's SOURCE-language value in place. Preserves the "
513
+ "Edit a key in place — its SOURCE-language value, its `type`, "
514
+ "and/or its description (pass at least one). Preserves the "
482
515
  "key and its history (version bump + history revision + audit). "
483
516
  "Address the key by EITHER key_uuid (preferred; from list_keys) "
484
517
  "OR namespace + name — exactly one of the two. When the source "
@@ -516,8 +549,13 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
516
549
  "type": "string",
517
550
  "description": "Optional: also update the key's description.",
518
551
  },
552
+ "type": {
553
+ "type": "string",
554
+ "enum": list(KEY_TYPES),
555
+ "description": "Optional: reclassify the key's semantic type (see create_key). Changing the type re-gates which a11y treatments the key offers.",
556
+ },
519
557
  },
520
- "required": ["source_value"],
558
+ "required": [],
521
559
  "additionalProperties": False,
522
560
  },
523
561
  ),
@@ -552,8 +590,9 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
552
590
  "name": {"type": "string", "description": "Dot-notation key name (use with namespace)."},
553
591
  "source_value": {"type": "string", "description": "New source-language value to write."},
554
592
  "description": {"type": "string", "description": "Optional: also update the key's description."},
593
+ "type": {"type": "string", "enum": list(KEY_TYPES), "description": "Optional: reclassify the key's semantic type (see create_key)."},
555
594
  },
556
- "required": ["source_value"],
595
+ "required": [],
557
596
  "additionalProperties": False,
558
597
  },
559
598
  },
@@ -1135,6 +1174,9 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
1135
1174
  "locale lacks it), alt_missing (image key with no source alt_text), "
1136
1175
  "reading_level_high (hard source text + no plain_language), "
1137
1176
  "a11y_variant_absent (a require_surface is missing in the source). "
1177
+ "Gaps are TYPE-AWARE: only treatments relevant to each key's type are "
1178
+ "reported (e.g. alt_missing only on image keys, aria gaps only on "
1179
+ "buttons/links/inputs), so a text key is never flagged for aria/alt. "
1138
1180
  "Defaults to the production version. Surfaced verbatim — use "
1139
1181
  "list_a11y_gaps for just the actionable item list."
1140
1182
  ),
@@ -1409,12 +1451,15 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
1409
1451
  Tool(
1410
1452
  name="get_variants",
1411
1453
  description=(
1412
- "Read the full VARIANT MATRIX for one key (task 928): every ACTIVE "
1413
- "surface (device + a11y) x every project language, with each cell's "
1454
+ "Read the full VARIANT MATRIX for one key (task 928): every surface "
1455
+ "OFFERED for the key x every project language, with each cell's "
1414
1456
  "{override, value, resolvedValue, status, drift, ...}. The response "
1415
- "also lists `surfaces` (active) and `surfaceKinds` ({surface: "
1416
- "'device'|'a11y'}). Address the key by key_uuid (from list_keys). "
1417
- "Surfaced verbatim."
1457
+ "lists `surfaces` and `surfaceKinds` ({surface: 'device'|'a11y'}) — "
1458
+ "these are the key's offered surfaces only: the project's active "
1459
+ "device surfaces plus the a11y treatments relevant to the key's TYPE "
1460
+ "(e.g. a text key shows no aria/alt). This is the source of truth for "
1461
+ "which surfaces set_variant will accept. Address by key_uuid (from "
1462
+ "list_keys). Surfaced verbatim."
1418
1463
  ),
1419
1464
  inputSchema={
1420
1465
  "type": "object",
@@ -1440,11 +1485,13 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
1440
1485
  "tablet / custom like watch / tv) OR a11y (aria_label / alt_text / "
1441
1486
  "screen_reader / plain_language). Address by key_uuid + locale CODE + "
1442
1487
  "surface slug (the fields get_variants / list_keys already give you). "
1443
- "Stored as a draft. 422 if the surface is not active for the project "
1444
- "(see list_surfaces); 409 if the cell is already reviewed/approved "
1445
- "(never overwritten). Returns {key_uuid, surface, language_code, "
1446
- "value, status}. (For a11y specifically you can also use "
1447
- "set_a11y_variant.)"
1488
+ "Stored as a draft. 422 unless the surface is OFFERED for this key "
1489
+ "an active device surface (see list_surfaces) OR an a11y treatment "
1490
+ "relevant to the key's TYPE (e.g. no aria_label on a text key; see "
1491
+ "get_variants for the key's offered surfaces); 409 if the cell is "
1492
+ "already reviewed/approved (never overwritten). Returns {key_uuid, "
1493
+ "surface, language_code, value, status}. (For a11y specifically you "
1494
+ "can also use set_a11y_variant.)"
1448
1495
  ),
1449
1496
  inputSchema={
1450
1497
  "type": "object",
@@ -1627,7 +1674,7 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
1627
1674
  "namespace": args["namespace"],
1628
1675
  "name": args["name"],
1629
1676
  }
1630
- for opt in ("source_value", "description", "plurals", "max_length"):
1677
+ for opt in ("source_value", "description", "plurals", "max_length", "type"):
1631
1678
  if opt in args:
1632
1679
  body[opt] = args[opt]
1633
1680
  data = await client.post(
@@ -1659,16 +1706,18 @@ def register(server: Server, client: VerbumiaClient) -> tuple[Any, Any]:
1659
1706
  return _text(data)
1660
1707
  if name == "update_key":
1661
1708
  project_uuid = _project_uuid(args, client)
1662
- if "source_value" not in args:
1663
- return _err("source_value is required")
1709
+ if not any(k in args for k in ("source_value", "type", "description")):
1710
+ return _err(
1711
+ "nothing to update: pass at least one of source_value, type, description"
1712
+ )
1664
1713
  has_uuid = bool(args.get("key_uuid"))
1665
1714
  has_name = bool(args.get("namespace")) and bool(args.get("name"))
1666
1715
  if has_uuid == has_name:
1667
1716
  return _err(
1668
1717
  "address the key by exactly one of key_uuid OR (namespace + name)"
1669
1718
  )
1670
- body: dict[str, Any] = {"source_value": args["source_value"]}
1671
- for opt in ("key_uuid", "namespace", "name", "description"):
1719
+ body: dict[str, Any] = {}
1720
+ for opt in ("source_value", "key_uuid", "namespace", "name", "description", "type"):
1672
1721
  if opt in args:
1673
1722
  body[opt] = args[opt]
1674
1723
  data = await client.patch(