sonenta-mcp 0.21.2__tar.gz
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.
- sonenta_mcp-0.21.2/.gitignore +28 -0
- sonenta_mcp-0.21.2/LICENSE +21 -0
- sonenta_mcp-0.21.2/PKG-INFO +325 -0
- sonenta_mcp-0.21.2/README.md +304 -0
- sonenta_mcp-0.21.2/alias-pypi/README.md +17 -0
- sonenta_mcp-0.21.2/alias-pypi/pyproject.toml +37 -0
- sonenta_mcp-0.21.2/alias-pypi/verbumia_mcp_alias.py +30 -0
- sonenta_mcp-0.21.2/brew/sonenta-mcp.rb +47 -0
- sonenta_mcp-0.21.2/npm/.npmignore +12 -0
- sonenta_mcp-0.21.2/npm/.npmrc +5 -0
- sonenta_mcp-0.21.2/npm/LICENSE +21 -0
- sonenta_mcp-0.21.2/npm/README.md +89 -0
- sonenta_mcp-0.21.2/npm/bin/sonenta-mcp.js +142 -0
- sonenta_mcp-0.21.2/npm/bin/verbumia-mcp.js +6 -0
- sonenta_mcp-0.21.2/npm/package.json +41 -0
- sonenta_mcp-0.21.2/npm/python/README.md +114 -0
- sonenta_mcp-0.21.2/npm/python/dist/.npmignore +0 -0
- sonenta_mcp-0.21.2/npm/python/pyproject.toml +58 -0
- sonenta_mcp-0.21.2/npm/python/verbumia_mcp/__init__.py +3 -0
- sonenta_mcp-0.21.2/npm/python/verbumia_mcp/__main__.py +23 -0
- sonenta_mcp-0.21.2/npm/python/verbumia_mcp/client.py +85 -0
- sonenta_mcp-0.21.2/npm/python/verbumia_mcp/config.py +105 -0
- sonenta_mcp-0.21.2/npm/python/verbumia_mcp/server.py +88 -0
- sonenta_mcp-0.21.2/npm/python/verbumia_mcp/tools.py +2420 -0
- sonenta_mcp-0.21.2/pyproject.toml +61 -0
- sonenta_mcp-0.21.2/tests/__init__.py +0 -0
- sonenta_mcp-0.21.2/tests/test_a11y_mcp.py +377 -0
- sonenta_mcp-0.21.2/tests/test_client_health.py +24 -0
- sonenta_mcp-0.21.2/tests/test_cognitive_mcp.py +152 -0
- sonenta_mcp-0.21.2/tests/test_config.py +123 -0
- sonenta_mcp-0.21.2/tests/test_glossary_mcp.py +253 -0
- sonenta_mcp-0.21.2/tests/test_preflight.py +70 -0
- sonenta_mcp-0.21.2/tests/test_source_duplicates_mcp.py +135 -0
- sonenta_mcp-0.21.2/tests/test_surfaces_mcp.py +239 -0
- sonenta_mcp-0.21.2/tests/test_tools.py +100 -0
- sonenta_mcp-0.21.2/tests/test_trash_reports_mcp.py +149 -0
- sonenta_mcp-0.21.2/tests/test_update_key_mcp.py +239 -0
- sonenta_mcp-0.21.2/uv.lock +620 -0
- sonenta_mcp-0.21.2/verbumia_mcp/__init__.py +3 -0
- sonenta_mcp-0.21.2/verbumia_mcp/__main__.py +23 -0
- sonenta_mcp-0.21.2/verbumia_mcp/client.py +85 -0
- sonenta_mcp-0.21.2/verbumia_mcp/config.py +105 -0
- sonenta_mcp-0.21.2/verbumia_mcp/server.py +88 -0
- sonenta_mcp-0.21.2/verbumia_mcp/tools.py +2420 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Node
|
|
2
|
+
node_modules/
|
|
3
|
+
cli/dist/
|
|
4
|
+
*.tsbuildinfo
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
|
|
15
|
+
# Built artifacts (rebuilt at publish time)
|
|
16
|
+
mcp/dist/
|
|
17
|
+
mcp/npm/python/dist/*.whl
|
|
18
|
+
mcp/npm/python/dist/*.tar.gz
|
|
19
|
+
mcp/alias-pypi/dist/
|
|
20
|
+
|
|
21
|
+
# Env / secrets
|
|
22
|
+
.env
|
|
23
|
+
.env.*
|
|
24
|
+
!.env.example
|
|
25
|
+
|
|
26
|
+
# OS / editor
|
|
27
|
+
.DS_Store
|
|
28
|
+
*.log
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Verbumia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sonenta-mcp
|
|
3
|
+
Version: 0.21.2
|
|
4
|
+
Summary: Model Context Protocol server for Sonenta — list projects, missing keys, propose translations from Claude Desktop and other MCP clients.
|
|
5
|
+
Project-URL: Homepage, https://sonenta.com
|
|
6
|
+
Project-URL: Documentation, https://sonenta.com/docs/mcp/setup
|
|
7
|
+
Project-URL: Source, https://github.com/sonenta/tools
|
|
8
|
+
Project-URL: Issues, https://github.com/sonenta/tools/issues
|
|
9
|
+
Author: Sonenta
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: claude,i18n,mcp,sonenta,translations
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Internationalization
|
|
16
|
+
Requires-Python: <3.14,>=3.12
|
|
17
|
+
Requires-Dist: httpx>=0.28
|
|
18
|
+
Requires-Dist: mcp>=1.0
|
|
19
|
+
Requires-Dist: pydantic>=2.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Sonenta MCP server
|
|
23
|
+
|
|
24
|
+
Model Context Protocol server for [Sonenta](https://sonenta.com) — exposes
|
|
25
|
+
your translation project to Claude Desktop and other MCP clients.
|
|
26
|
+
|
|
27
|
+
## Status
|
|
28
|
+
|
|
29
|
+
Tools wired: `list_projects`, `get_project_info`, `list_keys`,
|
|
30
|
+
`list_untranslated_keys`, `list_missing_keys`, `missing_keys_stats`,
|
|
31
|
+
`acknowledge_missing_keys`, `create_namespace`, `create_key`,
|
|
32
|
+
`create_keys_bulk`, `update_key`, `update_keys_bulk`, `propose_translation`,
|
|
33
|
+
`propose_translations_bulk`, `publish_cdn`, `validate_translations`,
|
|
34
|
+
`project_context_get`, `project_context_set`, `glossary_list`, `glossary_create`,
|
|
35
|
+
`glossary_update`, `glossary_delete`, `health_report`, `coverage_report`,
|
|
36
|
+
`delete_keys_bulk`, `restore_keys_bulk`, `list_trash`, `a11y_estimate`,
|
|
37
|
+
`generate_a11y_variant`, `translate_a11y_variants`, `a11y_report`,
|
|
38
|
+
`list_a11y_gaps`, `set_a11y_variant`, `delete_a11y_variant`, `list_surfaces`,
|
|
39
|
+
`create_surface`, `update_surface`, `delete_surface`, `get_variants`,
|
|
40
|
+
`set_variant`, `delete_variant`, `list_cognitive_candidates`,
|
|
41
|
+
`set_cognitive_score`, `analyze_cognitive`.
|
|
42
|
+
|
|
43
|
+
### Project context document (V1.2)
|
|
44
|
+
|
|
45
|
+
`project_context_get` / `project_context_set` read and write a free-form
|
|
46
|
+
markdown blob attached to the project — terminology, brand voice, domain
|
|
47
|
+
notes (religious, legal, medical, gaming, etc.). The content is intended
|
|
48
|
+
as ambient context for human translators **and** for AI agents producing
|
|
49
|
+
translations: prepend it to your translation prompts so every output stays
|
|
50
|
+
consistent with the project's vocabulary and tone. Hard cap 100 KiB.
|
|
51
|
+
|
|
52
|
+
Scopes:
|
|
53
|
+
- `project_context_get` requires `project:read` (existing scope).
|
|
54
|
+
- `project_context_set` requires `project:settings:write` (new scope, narrower
|
|
55
|
+
than `project:write` since settings changes propagate to every translator's
|
|
56
|
+
prompt context).
|
|
57
|
+
|
|
58
|
+
Note: the blanket `mcp:*` scope is **not** sufficient for these tools — grant
|
|
59
|
+
the precise scope on the key.
|
|
60
|
+
|
|
61
|
+
### Glossary (V1.3)
|
|
62
|
+
|
|
63
|
+
CRUD over the project glossary — the terminology rules the backend applies when
|
|
64
|
+
translating. The single public `rule_type` vocabulary is `translation` |
|
|
65
|
+
`do_not_translate` | `forbidden` (the internal storage type is never exposed).
|
|
66
|
+
|
|
67
|
+
- `glossary_list` — filters `rule_type`, `q` (substring on term), `limit`
|
|
68
|
+
(1..500, default 200) → `{ items, total }`.
|
|
69
|
+
- `glossary_create` — `{ rule_type?, term, translations?, case_sensitive?, note? }`;
|
|
70
|
+
`rule_type` defaults to `translation`; `translations` is honored only for
|
|
71
|
+
`translation`. 409 on a duplicate `(rule_type, term)`.
|
|
72
|
+
- `glossary_update` — by `entry_id`; send only the fields to change (`term`,
|
|
73
|
+
`translations`, `case_sensitive`, `note`). `rule_type` is immutable.
|
|
74
|
+
- `glossary_delete` — by `entry_id`.
|
|
75
|
+
|
|
76
|
+
These use the dedicated MCP surface `/v1/mcp/projects/{id}/glossary` and need the
|
|
77
|
+
`mcp:*` scope (the dashboard glossary endpoints are project-role only).
|
|
78
|
+
|
|
79
|
+
**Per-string glossary hints.** `list_untranslated_keys` and `list_keys` accept
|
|
80
|
+
`include_glossary_hints: true` (default false). When set, each returned key
|
|
81
|
+
carries a `glossary_hints[]` array — `{ term, rule_type, target_translation,
|
|
82
|
+
matched_text }` — for the rules the backend matched against the key's source
|
|
83
|
+
string. All matching is backend-side; the MCP forwards the flag and surfaces the
|
|
84
|
+
hints verbatim.
|
|
85
|
+
|
|
86
|
+
### Quality reports + trash (V1.4)
|
|
87
|
+
|
|
88
|
+
Read-only inspection (surfaced verbatim):
|
|
89
|
+
- `health_report` — project source-health report (source-string issues, with
|
|
90
|
+
`by_issue` / `by_severity` rollups). Optional `version_id`.
|
|
91
|
+
- `coverage_report` — translation-coverage report (global completeness +
|
|
92
|
+
per-language). Optional `version_id`.
|
|
93
|
+
|
|
94
|
+
Trash — soft-delete, restorable; **no purge over MCP** (hard-delete is
|
|
95
|
+
human-only via the dashboard):
|
|
96
|
+
- `delete_keys_bulk` — move up to 500 keys to the trash by `key_uuid` (idempotent).
|
|
97
|
+
- `restore_keys_bulk` — restore trashed keys by `key_uuid`.
|
|
98
|
+
- `list_trash` — list trashed keys (`{key_uuid, namespace, name, deleted_at}`,
|
|
99
|
+
cursor-paginated).
|
|
100
|
+
|
|
101
|
+
The trash tools are **uuid-addressed**: get `key_uuid` from `list_keys` (active)
|
|
102
|
+
or `list_trash` (trashed), then pass it to delete/restore. A trashed key
|
|
103
|
+
disappears from `list_keys`, `list_untranslated_keys`, the CDN, and the reports
|
|
104
|
+
until it is restored.
|
|
105
|
+
|
|
106
|
+
### Edit a key's source value (V1.5)
|
|
107
|
+
|
|
108
|
+
`update_key` / `update_keys_bulk` edit a key's **source-language value in place**
|
|
109
|
+
(`PATCH`), preserving the key and its history (version bump + history revision +
|
|
110
|
+
audit). Address the key by **exactly one** of `key_uuid` (preferred — from
|
|
111
|
+
`list_keys`) **or** `namespace` + `name`; an optional `description` can be updated
|
|
112
|
+
in the same call.
|
|
113
|
+
|
|
114
|
+
- When the `source_value` **actually changes**, the key's `reviewed` / `approved`
|
|
115
|
+
target translations are demoted to needs-review (`translated`, the "à revoir"
|
|
116
|
+
state); `missing` / `draft` / `translated` targets are left untouched.
|
|
117
|
+
`stale_flagged` reports how many were demoted.
|
|
118
|
+
- Submitting the current value is a **no-op** (`result: "unchanged"`, not billed);
|
|
119
|
+
a real source change — or a `description`-only change — bills 1 unit per key.
|
|
120
|
+
- `update_keys_bulk` takes up to 500 items and returns a per-item `results` array
|
|
121
|
+
(`status`: `ok` | `unchanged` | `error`) plus a `summary` roll-up; valid items
|
|
122
|
+
commit even if siblings fail.
|
|
123
|
+
|
|
124
|
+
### Key semantic type (a11y key.type)
|
|
125
|
+
|
|
126
|
+
`create_key` / `update_key` (and their bulk variants) accept a `type`. **Always
|
|
127
|
+
set it at creation — don't leave everything as the `text` default** — because the
|
|
128
|
+
type DRIVES which a11y treatments (and cognitive / plain-language scoring) a key
|
|
129
|
+
gets. Pick by the string's UI role:
|
|
130
|
+
|
|
131
|
+
| type | use for | a11y treatments |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `text` | body / paragraph copy | plain_language, screen_reader |
|
|
134
|
+
| `heading` | a title / section heading | plain_language, screen_reader |
|
|
135
|
+
| `label` | a standalone short label | plain_language, screen_reader |
|
|
136
|
+
| `caption` | image caption / helper text | plain_language, screen_reader |
|
|
137
|
+
| `button` | a button's label | aria_label, screen_reader |
|
|
138
|
+
| `link` | a hyperlink / anchor label | aria_label, screen_reader |
|
|
139
|
+
| `menu_item` | a nav / menu entry | aria_label, screen_reader |
|
|
140
|
+
| `input_placeholder` | a form input placeholder | aria_label |
|
|
141
|
+
| `input_label` | a form field's label | aria_label |
|
|
142
|
+
| `tooltip` | tooltip text | screen_reader |
|
|
143
|
+
| `badge` | a small status / count badge | screen_reader |
|
|
144
|
+
| `error_message` | a validation / error message | screen_reader |
|
|
145
|
+
| `toast` | a transient toast / snackbar | screen_reader |
|
|
146
|
+
| `image` | an image — value **is** the alt text | base = alt |
|
|
147
|
+
| `icon` | an icon — value **is** the accessible name | base = aria name |
|
|
148
|
+
| `meta_title` | an SEO page `<title>` | — |
|
|
149
|
+
| `meta_description` | an SEO meta description | — |
|
|
150
|
+
|
|
151
|
+
`update_key` / `update_keys_bulk` can change the type alone (no `source_value`
|
|
152
|
+
needed) to **reclassify** a mis-typed key; `list_keys` returns each key's current
|
|
153
|
+
`type` so you can audit and fix typing.
|
|
154
|
+
|
|
155
|
+
The engine gates everything by type server-side, so the a11y and variant tools are
|
|
156
|
+
**type-aware** automatically: `a11y_report` only flags treatments relevant to each
|
|
157
|
+
key's type (no aria/alt on a text key), `get_variants` lists only a key's offered
|
|
158
|
+
surfaces, and `set_variant` returns `422` for a surface the key's type doesn't
|
|
159
|
+
offer.
|
|
160
|
+
|
|
161
|
+
### Accessibility (a11y) variants (a11y V1)
|
|
162
|
+
|
|
163
|
+
Semantic accessibility variants that extend the variant engine with four
|
|
164
|
+
surfaces — `aria_label`, `alt_text`, `screen_reader`, `plain_language` —
|
|
165
|
+
orthogonal to the visible text. All are thin HTTP wrappers surfaced verbatim.
|
|
166
|
+
|
|
167
|
+
Discover gaps (read-only WCAG report):
|
|
168
|
+
- `a11y_report` — per key/surface/locale a11y gaps with `by_gap` / `by_severity` /
|
|
169
|
+
`by_surface` rollups + per-item details. Gap types: `a11y_untranslated`,
|
|
170
|
+
`alt_missing`, `reading_level_high`, `a11y_variant_absent`. Optional
|
|
171
|
+
`version_id`, `require_surface[]` (flag surfaces absent in the source), and
|
|
172
|
+
`reading_level_max_words` (5..60, default 15).
|
|
173
|
+
- `list_a11y_gaps` — the actionable `items[]` of `a11y_report`, filtered
|
|
174
|
+
client-side by `gap` / `surface` / `locale`. To find keys with no a11y variant,
|
|
175
|
+
pass `require_surface=<surface>` and filter `gap=a11y_variant_absent`.
|
|
176
|
+
|
|
177
|
+
AI fill (billed in credits — call `a11y_estimate` first):
|
|
178
|
+
- `a11y_estimate` — preview `{units, credit_cost_per_unit, credits_required,
|
|
179
|
+
balance, sufficient}` for a `generate` or `translate` run (tier `standard`=1 /
|
|
180
|
+
`premium`=4 per unit).
|
|
181
|
+
- `generate_a11y_variant` — AI-generate **source-language** a11y variants for keys
|
|
182
|
+
that lack them (aria/screen_reader/plain_language from the visible text,
|
|
183
|
+
alt_text from the key context). Draft, bot-authored.
|
|
184
|
+
- `translate_a11y_variants` — translate existing source a11y overrides into the
|
|
185
|
+
requested `language_codes`.
|
|
186
|
+
|
|
187
|
+
Manual CRUD (one cell at a time):
|
|
188
|
+
- `set_a11y_variant` — upsert one override for `(key_uuid, language_code,
|
|
189
|
+
surface)` with a text `value`. Address by the locale **code** an `a11y_report`
|
|
190
|
+
gap already carries — no UUID resolution. Stored as a draft (bot) override.
|
|
191
|
+
- `delete_a11y_variant` — clear one override; the cell re-inherits the base value.
|
|
192
|
+
|
|
193
|
+
All a11y tools are reachable with the same `mcp:*` key as the rest of the MCP
|
|
194
|
+
surface — no extra scope. `estimate` / `generate` / `translate` hit
|
|
195
|
+
`/v1/mcp/projects/{id}/a11y/*`; the report hits
|
|
196
|
+
`/v1/mcp/projects/{id}/a11y-report`; set/delete use the generic code-addressed
|
|
197
|
+
variant route below.
|
|
198
|
+
|
|
199
|
+
### Configurable surfaces + generic variants (task 928)
|
|
200
|
+
|
|
201
|
+
Surfaces are **per-project**: device surfaces (`desktop`/`mobile`/`tablet` +
|
|
202
|
+
custom like `watch`/`tv`) are extensible; the four a11y surfaces are a fixed
|
|
203
|
+
semantic set (toggle-active only). An un-customized project behaves exactly as
|
|
204
|
+
before (all builtins active). Manage them with:
|
|
205
|
+
|
|
206
|
+
- `list_surfaces` — `{surfaces:[{slug, label, kind, builtin, active}]}` (`kind` ∈
|
|
207
|
+
`device`/`a11y`).
|
|
208
|
+
- `create_surface` — add a **custom device** surface (`{slug, label?}`); creating
|
|
209
|
+
an a11y slug is rejected.
|
|
210
|
+
- `update_surface` — rename and/or toggle `active`. Deactivating is **soft**
|
|
211
|
+
(variants retained but hidden; the response `affected_variants` reports the
|
|
212
|
+
count; reactivating republishes them).
|
|
213
|
+
- `delete_surface` — delete a custom surface (builtins can only be deactivated).
|
|
214
|
+
|
|
215
|
+
Authoring variants works for **any active surface** (device or a11y), addressed
|
|
216
|
+
by the locale **code** + surface slug a report/matrix already gives you:
|
|
217
|
+
|
|
218
|
+
- `get_variants` — the full matrix for one key: every active surface × language,
|
|
219
|
+
with each cell's `{override, value, resolvedValue, status, drift}` plus
|
|
220
|
+
`surfaces` + `surfaceKinds`. (`GET .../keys/{key_uuid}/variants`)
|
|
221
|
+
- `set_variant` — upsert one override `(key_uuid, language_code, surface, value)`;
|
|
222
|
+
`422` if the surface is not active, `409` if the cell is already
|
|
223
|
+
reviewed/approved (never overwritten). Stored as a draft.
|
|
224
|
+
- `delete_variant` — clear one override (cell re-inherits the base value).
|
|
225
|
+
|
|
226
|
+
Variant writes (`set_variant`/`delete_variant`, and the a11y-named
|
|
227
|
+
`set_a11y_variant`/`delete_a11y_variant`) all use the mcp:* code-addressed route
|
|
228
|
+
`/v1/mcp/projects/{id}/keys/{key_uuid}/variants/{language_code}/{surface}`.
|
|
229
|
+
|
|
230
|
+
### Plain-language by cognitive score
|
|
231
|
+
|
|
232
|
+
Replaces the a-priori reading-level heuristic with an actual cognitive-difficulty
|
|
233
|
+
score (0–100, higher = harder). A key only enters the "review" queue
|
|
234
|
+
(`reading_level_high`) once it's been scored at or above the project threshold —
|
|
235
|
+
so long-but-simple strings no longer clutter the queue. Same local-first model as
|
|
236
|
+
the rest of the a11y surface:
|
|
237
|
+
|
|
238
|
+
- `list_cognitive_candidates` — text-relevant keys (a type that offers
|
|
239
|
+
`plain_language`: text/heading/label/caption) whose source clears a word floor;
|
|
240
|
+
a scope filter, not a difficulty judgement. `only_unanalyzed`, `namespace_uuid`,
|
|
241
|
+
`limit`, `offset`.
|
|
242
|
+
- `set_cognitive_score` — the **0-credit** local path: record a `score` (0–100)
|
|
243
|
+
you judged yourself plus an optional plain-language `suggestion` (draft,
|
|
244
|
+
bot-authored).
|
|
245
|
+
- `analyze_cognitive` — the **billed** server-side AI fallback (estimate/opt-in;
|
|
246
|
+
`tier`, `key_uuids?`, `namespace_uuid?`, `idempotency_key`) for bulk runs.
|
|
247
|
+
|
|
248
|
+
## Install
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Recommended (zero-install, used by Claude Desktop's mcp.json):
|
|
252
|
+
npx -y @sonenta/mcp
|
|
253
|
+
|
|
254
|
+
# Or from PyPI:
|
|
255
|
+
pipx install sonenta-mcp
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
A Homebrew tap (`brew install sonenta/tap/sonenta-mcp`) is coming once we
|
|
259
|
+
publish to npm.
|
|
260
|
+
|
|
261
|
+
## Configure (Claude Desktop)
|
|
262
|
+
|
|
263
|
+
Open **Settings → Developer → Edit Config**, then:
|
|
264
|
+
|
|
265
|
+
Single-project setup:
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"mcpServers": {
|
|
270
|
+
"sonenta": {
|
|
271
|
+
"command": "npx",
|
|
272
|
+
"args": ["-y", "@sonenta/mcp"],
|
|
273
|
+
"env": {
|
|
274
|
+
"SONENTA_API_KEY": "vrb_live_<prefix>.<secret>",
|
|
275
|
+
"SONENTA_PROJECTS": "<project_uuid>",
|
|
276
|
+
"SONENTA_BASE_URL": "https://api.sonenta.dev"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Multi-project setup (v0.11+) — comma-separate the UUIDs and Claude will pass
|
|
284
|
+
`project_uuid` on each tool call:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
"env": {
|
|
288
|
+
"SONENTA_API_KEY": "vrb_live_<prefix>.<secret>",
|
|
289
|
+
"SONENTA_PROJECTS": "01993a..,01993b..,01993c.."
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Restart Claude Desktop. Tools show up in the prompt UI.
|
|
294
|
+
|
|
295
|
+
## Environment
|
|
296
|
+
|
|
297
|
+
| Variable | Required | Default | Description |
|
|
298
|
+
|----------------------|----------|--------------------------------|--------------------------------------------------------------------------------------------|
|
|
299
|
+
| `SONENTA_API_KEY` | yes | | API key from Org Settings → API Keys (`SONENTA_TOKEN` also accepted, back-compat) |
|
|
300
|
+
| `SONENTA_PROJECTS` | no | (LLM passes per call) | CSV of project UUIDs. When >1, the LLM MUST pass `project_uuid` on each tool call. |
|
|
301
|
+
| `SONENTA_PROJECT` | no | (legacy) | Singular fallback for v0.10.x users. Ignored when `SONENTA_PROJECTS` is set (warns). |
|
|
302
|
+
| `SONENTA_BASE_URL` | no | `https://api.sonenta.dev` | Override for self-host or staging (`SONENTA_API_BASE` also accepted, back-compat) |
|
|
303
|
+
|
|
304
|
+
The token format is `vrb_live_<prefix>.<secret>` and must carry the `mcp:*`
|
|
305
|
+
scope. For the `project_context_*` tools, also grant `project:read` and/or
|
|
306
|
+
`project:settings:write` — see the **Project context document** section
|
|
307
|
+
above. Treat the token like any other secret — keychain or `.env`, never
|
|
308
|
+
commit.
|
|
309
|
+
|
|
310
|
+
If your API key is pinned to a single project (its `project_uuid` is set
|
|
311
|
+
server-side), listing additional UUIDs in `SONENTA_PROJECTS` will surface
|
|
312
|
+
as a `403 API key is scoped to a different project` from the backend on
|
|
313
|
+
any call targeting one of the others.
|
|
314
|
+
|
|
315
|
+
## Local development
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
cd mcp
|
|
319
|
+
uv sync
|
|
320
|
+
uv run sonenta-mcp # stdio transport — pipe MCP frames over stdin/stdout
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## License
|
|
324
|
+
|
|
325
|
+
MIT.
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Sonenta MCP server
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server for [Sonenta](https://sonenta.com) — exposes
|
|
4
|
+
your translation project to Claude Desktop and other MCP clients.
|
|
5
|
+
|
|
6
|
+
## Status
|
|
7
|
+
|
|
8
|
+
Tools wired: `list_projects`, `get_project_info`, `list_keys`,
|
|
9
|
+
`list_untranslated_keys`, `list_missing_keys`, `missing_keys_stats`,
|
|
10
|
+
`acknowledge_missing_keys`, `create_namespace`, `create_key`,
|
|
11
|
+
`create_keys_bulk`, `update_key`, `update_keys_bulk`, `propose_translation`,
|
|
12
|
+
`propose_translations_bulk`, `publish_cdn`, `validate_translations`,
|
|
13
|
+
`project_context_get`, `project_context_set`, `glossary_list`, `glossary_create`,
|
|
14
|
+
`glossary_update`, `glossary_delete`, `health_report`, `coverage_report`,
|
|
15
|
+
`delete_keys_bulk`, `restore_keys_bulk`, `list_trash`, `a11y_estimate`,
|
|
16
|
+
`generate_a11y_variant`, `translate_a11y_variants`, `a11y_report`,
|
|
17
|
+
`list_a11y_gaps`, `set_a11y_variant`, `delete_a11y_variant`, `list_surfaces`,
|
|
18
|
+
`create_surface`, `update_surface`, `delete_surface`, `get_variants`,
|
|
19
|
+
`set_variant`, `delete_variant`, `list_cognitive_candidates`,
|
|
20
|
+
`set_cognitive_score`, `analyze_cognitive`.
|
|
21
|
+
|
|
22
|
+
### Project context document (V1.2)
|
|
23
|
+
|
|
24
|
+
`project_context_get` / `project_context_set` read and write a free-form
|
|
25
|
+
markdown blob attached to the project — terminology, brand voice, domain
|
|
26
|
+
notes (religious, legal, medical, gaming, etc.). The content is intended
|
|
27
|
+
as ambient context for human translators **and** for AI agents producing
|
|
28
|
+
translations: prepend it to your translation prompts so every output stays
|
|
29
|
+
consistent with the project's vocabulary and tone. Hard cap 100 KiB.
|
|
30
|
+
|
|
31
|
+
Scopes:
|
|
32
|
+
- `project_context_get` requires `project:read` (existing scope).
|
|
33
|
+
- `project_context_set` requires `project:settings:write` (new scope, narrower
|
|
34
|
+
than `project:write` since settings changes propagate to every translator's
|
|
35
|
+
prompt context).
|
|
36
|
+
|
|
37
|
+
Note: the blanket `mcp:*` scope is **not** sufficient for these tools — grant
|
|
38
|
+
the precise scope on the key.
|
|
39
|
+
|
|
40
|
+
### Glossary (V1.3)
|
|
41
|
+
|
|
42
|
+
CRUD over the project glossary — the terminology rules the backend applies when
|
|
43
|
+
translating. The single public `rule_type` vocabulary is `translation` |
|
|
44
|
+
`do_not_translate` | `forbidden` (the internal storage type is never exposed).
|
|
45
|
+
|
|
46
|
+
- `glossary_list` — filters `rule_type`, `q` (substring on term), `limit`
|
|
47
|
+
(1..500, default 200) → `{ items, total }`.
|
|
48
|
+
- `glossary_create` — `{ rule_type?, term, translations?, case_sensitive?, note? }`;
|
|
49
|
+
`rule_type` defaults to `translation`; `translations` is honored only for
|
|
50
|
+
`translation`. 409 on a duplicate `(rule_type, term)`.
|
|
51
|
+
- `glossary_update` — by `entry_id`; send only the fields to change (`term`,
|
|
52
|
+
`translations`, `case_sensitive`, `note`). `rule_type` is immutable.
|
|
53
|
+
- `glossary_delete` — by `entry_id`.
|
|
54
|
+
|
|
55
|
+
These use the dedicated MCP surface `/v1/mcp/projects/{id}/glossary` and need the
|
|
56
|
+
`mcp:*` scope (the dashboard glossary endpoints are project-role only).
|
|
57
|
+
|
|
58
|
+
**Per-string glossary hints.** `list_untranslated_keys` and `list_keys` accept
|
|
59
|
+
`include_glossary_hints: true` (default false). When set, each returned key
|
|
60
|
+
carries a `glossary_hints[]` array — `{ term, rule_type, target_translation,
|
|
61
|
+
matched_text }` — for the rules the backend matched against the key's source
|
|
62
|
+
string. All matching is backend-side; the MCP forwards the flag and surfaces the
|
|
63
|
+
hints verbatim.
|
|
64
|
+
|
|
65
|
+
### Quality reports + trash (V1.4)
|
|
66
|
+
|
|
67
|
+
Read-only inspection (surfaced verbatim):
|
|
68
|
+
- `health_report` — project source-health report (source-string issues, with
|
|
69
|
+
`by_issue` / `by_severity` rollups). Optional `version_id`.
|
|
70
|
+
- `coverage_report` — translation-coverage report (global completeness +
|
|
71
|
+
per-language). Optional `version_id`.
|
|
72
|
+
|
|
73
|
+
Trash — soft-delete, restorable; **no purge over MCP** (hard-delete is
|
|
74
|
+
human-only via the dashboard):
|
|
75
|
+
- `delete_keys_bulk` — move up to 500 keys to the trash by `key_uuid` (idempotent).
|
|
76
|
+
- `restore_keys_bulk` — restore trashed keys by `key_uuid`.
|
|
77
|
+
- `list_trash` — list trashed keys (`{key_uuid, namespace, name, deleted_at}`,
|
|
78
|
+
cursor-paginated).
|
|
79
|
+
|
|
80
|
+
The trash tools are **uuid-addressed**: get `key_uuid` from `list_keys` (active)
|
|
81
|
+
or `list_trash` (trashed), then pass it to delete/restore. A trashed key
|
|
82
|
+
disappears from `list_keys`, `list_untranslated_keys`, the CDN, and the reports
|
|
83
|
+
until it is restored.
|
|
84
|
+
|
|
85
|
+
### Edit a key's source value (V1.5)
|
|
86
|
+
|
|
87
|
+
`update_key` / `update_keys_bulk` edit a key's **source-language value in place**
|
|
88
|
+
(`PATCH`), preserving the key and its history (version bump + history revision +
|
|
89
|
+
audit). Address the key by **exactly one** of `key_uuid` (preferred — from
|
|
90
|
+
`list_keys`) **or** `namespace` + `name`; an optional `description` can be updated
|
|
91
|
+
in the same call.
|
|
92
|
+
|
|
93
|
+
- When the `source_value` **actually changes**, the key's `reviewed` / `approved`
|
|
94
|
+
target translations are demoted to needs-review (`translated`, the "à revoir"
|
|
95
|
+
state); `missing` / `draft` / `translated` targets are left untouched.
|
|
96
|
+
`stale_flagged` reports how many were demoted.
|
|
97
|
+
- Submitting the current value is a **no-op** (`result: "unchanged"`, not billed);
|
|
98
|
+
a real source change — or a `description`-only change — bills 1 unit per key.
|
|
99
|
+
- `update_keys_bulk` takes up to 500 items and returns a per-item `results` array
|
|
100
|
+
(`status`: `ok` | `unchanged` | `error`) plus a `summary` roll-up; valid items
|
|
101
|
+
commit even if siblings fail.
|
|
102
|
+
|
|
103
|
+
### Key semantic type (a11y key.type)
|
|
104
|
+
|
|
105
|
+
`create_key` / `update_key` (and their bulk variants) accept a `type`. **Always
|
|
106
|
+
set it at creation — don't leave everything as the `text` default** — because the
|
|
107
|
+
type DRIVES which a11y treatments (and cognitive / plain-language scoring) a key
|
|
108
|
+
gets. Pick by the string's UI role:
|
|
109
|
+
|
|
110
|
+
| type | use for | a11y treatments |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `text` | body / paragraph copy | plain_language, screen_reader |
|
|
113
|
+
| `heading` | a title / section heading | plain_language, screen_reader |
|
|
114
|
+
| `label` | a standalone short label | plain_language, screen_reader |
|
|
115
|
+
| `caption` | image caption / helper text | plain_language, screen_reader |
|
|
116
|
+
| `button` | a button's label | aria_label, screen_reader |
|
|
117
|
+
| `link` | a hyperlink / anchor label | aria_label, screen_reader |
|
|
118
|
+
| `menu_item` | a nav / menu entry | aria_label, screen_reader |
|
|
119
|
+
| `input_placeholder` | a form input placeholder | aria_label |
|
|
120
|
+
| `input_label` | a form field's label | aria_label |
|
|
121
|
+
| `tooltip` | tooltip text | screen_reader |
|
|
122
|
+
| `badge` | a small status / count badge | screen_reader |
|
|
123
|
+
| `error_message` | a validation / error message | screen_reader |
|
|
124
|
+
| `toast` | a transient toast / snackbar | screen_reader |
|
|
125
|
+
| `image` | an image — value **is** the alt text | base = alt |
|
|
126
|
+
| `icon` | an icon — value **is** the accessible name | base = aria name |
|
|
127
|
+
| `meta_title` | an SEO page `<title>` | — |
|
|
128
|
+
| `meta_description` | an SEO meta description | — |
|
|
129
|
+
|
|
130
|
+
`update_key` / `update_keys_bulk` can change the type alone (no `source_value`
|
|
131
|
+
needed) to **reclassify** a mis-typed key; `list_keys` returns each key's current
|
|
132
|
+
`type` so you can audit and fix typing.
|
|
133
|
+
|
|
134
|
+
The engine gates everything by type server-side, so the a11y and variant tools are
|
|
135
|
+
**type-aware** automatically: `a11y_report` only flags treatments relevant to each
|
|
136
|
+
key's type (no aria/alt on a text key), `get_variants` lists only a key's offered
|
|
137
|
+
surfaces, and `set_variant` returns `422` for a surface the key's type doesn't
|
|
138
|
+
offer.
|
|
139
|
+
|
|
140
|
+
### Accessibility (a11y) variants (a11y V1)
|
|
141
|
+
|
|
142
|
+
Semantic accessibility variants that extend the variant engine with four
|
|
143
|
+
surfaces — `aria_label`, `alt_text`, `screen_reader`, `plain_language` —
|
|
144
|
+
orthogonal to the visible text. All are thin HTTP wrappers surfaced verbatim.
|
|
145
|
+
|
|
146
|
+
Discover gaps (read-only WCAG report):
|
|
147
|
+
- `a11y_report` — per key/surface/locale a11y gaps with `by_gap` / `by_severity` /
|
|
148
|
+
`by_surface` rollups + per-item details. Gap types: `a11y_untranslated`,
|
|
149
|
+
`alt_missing`, `reading_level_high`, `a11y_variant_absent`. Optional
|
|
150
|
+
`version_id`, `require_surface[]` (flag surfaces absent in the source), and
|
|
151
|
+
`reading_level_max_words` (5..60, default 15).
|
|
152
|
+
- `list_a11y_gaps` — the actionable `items[]` of `a11y_report`, filtered
|
|
153
|
+
client-side by `gap` / `surface` / `locale`. To find keys with no a11y variant,
|
|
154
|
+
pass `require_surface=<surface>` and filter `gap=a11y_variant_absent`.
|
|
155
|
+
|
|
156
|
+
AI fill (billed in credits — call `a11y_estimate` first):
|
|
157
|
+
- `a11y_estimate` — preview `{units, credit_cost_per_unit, credits_required,
|
|
158
|
+
balance, sufficient}` for a `generate` or `translate` run (tier `standard`=1 /
|
|
159
|
+
`premium`=4 per unit).
|
|
160
|
+
- `generate_a11y_variant` — AI-generate **source-language** a11y variants for keys
|
|
161
|
+
that lack them (aria/screen_reader/plain_language from the visible text,
|
|
162
|
+
alt_text from the key context). Draft, bot-authored.
|
|
163
|
+
- `translate_a11y_variants` — translate existing source a11y overrides into the
|
|
164
|
+
requested `language_codes`.
|
|
165
|
+
|
|
166
|
+
Manual CRUD (one cell at a time):
|
|
167
|
+
- `set_a11y_variant` — upsert one override for `(key_uuid, language_code,
|
|
168
|
+
surface)` with a text `value`. Address by the locale **code** an `a11y_report`
|
|
169
|
+
gap already carries — no UUID resolution. Stored as a draft (bot) override.
|
|
170
|
+
- `delete_a11y_variant` — clear one override; the cell re-inherits the base value.
|
|
171
|
+
|
|
172
|
+
All a11y tools are reachable with the same `mcp:*` key as the rest of the MCP
|
|
173
|
+
surface — no extra scope. `estimate` / `generate` / `translate` hit
|
|
174
|
+
`/v1/mcp/projects/{id}/a11y/*`; the report hits
|
|
175
|
+
`/v1/mcp/projects/{id}/a11y-report`; set/delete use the generic code-addressed
|
|
176
|
+
variant route below.
|
|
177
|
+
|
|
178
|
+
### Configurable surfaces + generic variants (task 928)
|
|
179
|
+
|
|
180
|
+
Surfaces are **per-project**: device surfaces (`desktop`/`mobile`/`tablet` +
|
|
181
|
+
custom like `watch`/`tv`) are extensible; the four a11y surfaces are a fixed
|
|
182
|
+
semantic set (toggle-active only). An un-customized project behaves exactly as
|
|
183
|
+
before (all builtins active). Manage them with:
|
|
184
|
+
|
|
185
|
+
- `list_surfaces` — `{surfaces:[{slug, label, kind, builtin, active}]}` (`kind` ∈
|
|
186
|
+
`device`/`a11y`).
|
|
187
|
+
- `create_surface` — add a **custom device** surface (`{slug, label?}`); creating
|
|
188
|
+
an a11y slug is rejected.
|
|
189
|
+
- `update_surface` — rename and/or toggle `active`. Deactivating is **soft**
|
|
190
|
+
(variants retained but hidden; the response `affected_variants` reports the
|
|
191
|
+
count; reactivating republishes them).
|
|
192
|
+
- `delete_surface` — delete a custom surface (builtins can only be deactivated).
|
|
193
|
+
|
|
194
|
+
Authoring variants works for **any active surface** (device or a11y), addressed
|
|
195
|
+
by the locale **code** + surface slug a report/matrix already gives you:
|
|
196
|
+
|
|
197
|
+
- `get_variants` — the full matrix for one key: every active surface × language,
|
|
198
|
+
with each cell's `{override, value, resolvedValue, status, drift}` plus
|
|
199
|
+
`surfaces` + `surfaceKinds`. (`GET .../keys/{key_uuid}/variants`)
|
|
200
|
+
- `set_variant` — upsert one override `(key_uuid, language_code, surface, value)`;
|
|
201
|
+
`422` if the surface is not active, `409` if the cell is already
|
|
202
|
+
reviewed/approved (never overwritten). Stored as a draft.
|
|
203
|
+
- `delete_variant` — clear one override (cell re-inherits the base value).
|
|
204
|
+
|
|
205
|
+
Variant writes (`set_variant`/`delete_variant`, and the a11y-named
|
|
206
|
+
`set_a11y_variant`/`delete_a11y_variant`) all use the mcp:* code-addressed route
|
|
207
|
+
`/v1/mcp/projects/{id}/keys/{key_uuid}/variants/{language_code}/{surface}`.
|
|
208
|
+
|
|
209
|
+
### Plain-language by cognitive score
|
|
210
|
+
|
|
211
|
+
Replaces the a-priori reading-level heuristic with an actual cognitive-difficulty
|
|
212
|
+
score (0–100, higher = harder). A key only enters the "review" queue
|
|
213
|
+
(`reading_level_high`) once it's been scored at or above the project threshold —
|
|
214
|
+
so long-but-simple strings no longer clutter the queue. Same local-first model as
|
|
215
|
+
the rest of the a11y surface:
|
|
216
|
+
|
|
217
|
+
- `list_cognitive_candidates` — text-relevant keys (a type that offers
|
|
218
|
+
`plain_language`: text/heading/label/caption) whose source clears a word floor;
|
|
219
|
+
a scope filter, not a difficulty judgement. `only_unanalyzed`, `namespace_uuid`,
|
|
220
|
+
`limit`, `offset`.
|
|
221
|
+
- `set_cognitive_score` — the **0-credit** local path: record a `score` (0–100)
|
|
222
|
+
you judged yourself plus an optional plain-language `suggestion` (draft,
|
|
223
|
+
bot-authored).
|
|
224
|
+
- `analyze_cognitive` — the **billed** server-side AI fallback (estimate/opt-in;
|
|
225
|
+
`tier`, `key_uuids?`, `namespace_uuid?`, `idempotency_key`) for bulk runs.
|
|
226
|
+
|
|
227
|
+
## Install
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Recommended (zero-install, used by Claude Desktop's mcp.json):
|
|
231
|
+
npx -y @sonenta/mcp
|
|
232
|
+
|
|
233
|
+
# Or from PyPI:
|
|
234
|
+
pipx install sonenta-mcp
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
A Homebrew tap (`brew install sonenta/tap/sonenta-mcp`) is coming once we
|
|
238
|
+
publish to npm.
|
|
239
|
+
|
|
240
|
+
## Configure (Claude Desktop)
|
|
241
|
+
|
|
242
|
+
Open **Settings → Developer → Edit Config**, then:
|
|
243
|
+
|
|
244
|
+
Single-project setup:
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"mcpServers": {
|
|
249
|
+
"sonenta": {
|
|
250
|
+
"command": "npx",
|
|
251
|
+
"args": ["-y", "@sonenta/mcp"],
|
|
252
|
+
"env": {
|
|
253
|
+
"SONENTA_API_KEY": "vrb_live_<prefix>.<secret>",
|
|
254
|
+
"SONENTA_PROJECTS": "<project_uuid>",
|
|
255
|
+
"SONENTA_BASE_URL": "https://api.sonenta.dev"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Multi-project setup (v0.11+) — comma-separate the UUIDs and Claude will pass
|
|
263
|
+
`project_uuid` on each tool call:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
"env": {
|
|
267
|
+
"SONENTA_API_KEY": "vrb_live_<prefix>.<secret>",
|
|
268
|
+
"SONENTA_PROJECTS": "01993a..,01993b..,01993c.."
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Restart Claude Desktop. Tools show up in the prompt UI.
|
|
273
|
+
|
|
274
|
+
## Environment
|
|
275
|
+
|
|
276
|
+
| Variable | Required | Default | Description |
|
|
277
|
+
|----------------------|----------|--------------------------------|--------------------------------------------------------------------------------------------|
|
|
278
|
+
| `SONENTA_API_KEY` | yes | | API key from Org Settings → API Keys (`SONENTA_TOKEN` also accepted, back-compat) |
|
|
279
|
+
| `SONENTA_PROJECTS` | no | (LLM passes per call) | CSV of project UUIDs. When >1, the LLM MUST pass `project_uuid` on each tool call. |
|
|
280
|
+
| `SONENTA_PROJECT` | no | (legacy) | Singular fallback for v0.10.x users. Ignored when `SONENTA_PROJECTS` is set (warns). |
|
|
281
|
+
| `SONENTA_BASE_URL` | no | `https://api.sonenta.dev` | Override for self-host or staging (`SONENTA_API_BASE` also accepted, back-compat) |
|
|
282
|
+
|
|
283
|
+
The token format is `vrb_live_<prefix>.<secret>` and must carry the `mcp:*`
|
|
284
|
+
scope. For the `project_context_*` tools, also grant `project:read` and/or
|
|
285
|
+
`project:settings:write` — see the **Project context document** section
|
|
286
|
+
above. Treat the token like any other secret — keychain or `.env`, never
|
|
287
|
+
commit.
|
|
288
|
+
|
|
289
|
+
If your API key is pinned to a single project (its `project_uuid` is set
|
|
290
|
+
server-side), listing additional UUIDs in `SONENTA_PROJECTS` will surface
|
|
291
|
+
as a `403 API key is scoped to a different project` from the backend on
|
|
292
|
+
any call targeting one of the others.
|
|
293
|
+
|
|
294
|
+
## Local development
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
cd mcp
|
|
298
|
+
uv sync
|
|
299
|
+
uv run sonenta-mcp # stdio transport — pipe MCP frames over stdin/stdout
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT.
|