@syntholoc/mcp 0.3.0 → 0.4.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/README.md +21 -0
- package/dist/build-server.js +1 -1
- package/dist/{chunk-GNXMB53B.js → chunk-P7LZNIW2.js} +138 -82
- package/dist/index.js +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -113,6 +113,27 @@ Translation runs asynchronously and returns a job id; a `wait_for_job` tool
|
|
|
113
113
|
blocks until completion, so you can chain "translate → wait → export" in a
|
|
114
114
|
single request.
|
|
115
115
|
|
|
116
|
+
## Contributing: adding a tool
|
|
117
|
+
|
|
118
|
+
Tools are authored by hand — the curated descriptions, input aliases, and
|
|
119
|
+
validation are what make an LLM use them well, so they are deliberately not
|
|
120
|
+
generated one-per-operation. Two mechanisms keep the manual layer honest:
|
|
121
|
+
|
|
122
|
+
- **Generated input shapes.** `bun run codegen` emits zod schemas for every
|
|
123
|
+
GraphQL input type into `src/generated/input-schemas.ts` from the API's
|
|
124
|
+
committed SDL. Compose a schema's `.shape` instead of re-typing a mutation's
|
|
125
|
+
fields, then curate on top — see `configure_llm_provider` in
|
|
126
|
+
`src/tools/providers.ts`, which spreads `ConfigureProviderInputSchema().shape`
|
|
127
|
+
and tightens the API's free-form `type: String` down to a concrete enum.
|
|
128
|
+
- **Drift guard.** `src/operation-coverage.spec.ts` fails the build when an
|
|
129
|
+
operation exported by `@syntholoc/sdk` is not surfaced by any tool or resource
|
|
130
|
+
(or explicitly exempted). Add an operation to the SDK and you must either wire
|
|
131
|
+
up a tool/resource or record why it is MCP-exempt.
|
|
132
|
+
|
|
133
|
+
Every tool set has contract tests (`src/tools/*.spec.ts`) that pin both the
|
|
134
|
+
GraphQL operation + variables a tool emits and the JSON it returns, via an
|
|
135
|
+
in-memory MCP client against a mock GraphQL client (`src/spec-helpers.ts`).
|
|
136
|
+
|
|
116
137
|
## License
|
|
117
138
|
|
|
118
139
|
MIT
|
package/dist/build-server.js
CHANGED
|
@@ -105,30 +105,41 @@ function registerPrompts(server) {
|
|
|
105
105
|
" default to the likeliest) and record the file\u2192locale mapping in the config `translation` pattern.",
|
|
106
106
|
" (Target locales must exist before a Git pull so their translation files import.)",
|
|
107
107
|
"3. RECOMMENDED \u2014 connect the repo and sync, instead of pushing strings one by one:",
|
|
108
|
-
" a. `list_repositories`. If one is already connected, skip to (
|
|
109
|
-
" b.
|
|
108
|
+
" a. `list_repositories`. If one is already connected, skip to (e).",
|
|
109
|
+
" b. Write `.syntholoc/config.json` FIRST and commit it to the source branch \u2014 the server reads it",
|
|
110
|
+
" from the repo as the SOURCE OF TRUTH for mappings on every sync/pull/write-back (Crowdin-style),",
|
|
111
|
+
" so it must land in the branch before the first sync. Schema (https://syntholoc.com/schemas/config.json):",
|
|
112
|
+
" the `projectId`, `sourceLocale`, and a Crowdin-style `files` array of `{ source, translation }`",
|
|
113
|
+
' patterns, e.g. { "source": "messages/en/*.json", "translation": "messages/{locale}/%original_file_name%" }.',
|
|
114
|
+
" - Placeholders: `{locale}` (or %locale% / %two_letters_code% / %original_file_name% / %original_path%).",
|
|
115
|
+
` - If a locale's ON-DISK code differs from its BCP-47 code (e.g. "zh-CN" stored as "cn", "pt-BR" as "pt"),`,
|
|
116
|
+
' add `languagesMapping`, e.g. { "two_letters_code": { "zh-CN": "cn", "pt-BR": "pt" } }.',
|
|
117
|
+
' - Do NOT invent fields like `i18nDir`, `format: "nested-dirs"`, or `localeMapping` \u2014 they are not read.',
|
|
118
|
+
" - CRITICAL: `.syntholoc/config.json` MUST be committed and tracked \u2014 it is the mapping source of",
|
|
119
|
+
" truth the server reads from the branch. NEVER add `.syntholoc` (or the config file) to `.gitignore`.",
|
|
120
|
+
" If `.gitignore` already ignores it (a common reflex, next to `.claude`), remove that line. After",
|
|
121
|
+
" committing, VERIFY it is tracked (e.g. `git ls-files .syntholoc/config.json` returns the path, or",
|
|
122
|
+
" `git check-ignore` reports nothing) \u2014 an ignored config means every sync/pull/write-back resolves",
|
|
123
|
+
" zero files and silently opens no PR.",
|
|
124
|
+
" c. Connect it with `connect_repository`. Ask the user which method they prefer:",
|
|
110
125
|
" - GitHub App (no token, recommended): `get_github_app_install_url` \u2192 give the URL to the user",
|
|
111
126
|
" to open and authorize (you cannot click it for them; you may offer to open it via the shell).",
|
|
112
127
|
" Once they confirm, `list_github_installations` \u2192 `list_github_installation_repos` to pick the",
|
|
113
128
|
" repo, then `connect_repository` with that `installationId`.",
|
|
114
129
|
" - Token (any host): ask the user to paste a personal access token (repo read + PR scope), then",
|
|
115
130
|
" `connect_repository` with `token`. Treat the token as a secret \u2014 do not echo it back.",
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
"
|
|
131
|
+
" d. `files`/`paths` on `connect_repository` are an OPTIONAL fallback used only when the repo has no",
|
|
132
|
+
" committed `.syntholoc/config.json`. If you committed the config in (b) you may pass the same `files`",
|
|
133
|
+
" for redundancy, but the in-repo config wins. Same Crowdin placeholders as the config file.",
|
|
134
|
+
" e. `sync_repository` \u2014 imports ALL source strings from the repo at once and opens the translation PR.",
|
|
120
135
|
" Then `pull_translations` to import any translation files already in the repo (choose `conflict`",
|
|
121
136
|
" KEEP_APPROVED to preserve in-app edits).",
|
|
122
|
-
"
|
|
137
|
+
" f. `wait_for_job` on the returned job id, then report keys imported and the PR url.",
|
|
123
138
|
"4. FALLBACK \u2014 file-based import when the user does NOT want Git integration:",
|
|
124
139
|
" - Single file \u2192 `import_source_strings` once.",
|
|
125
140
|
" - Namespaced \u2192 `import_source_strings` once PER FILE with `keyPrefix` set to the namespace",
|
|
126
141
|
" (filename without extension). Keys become e.g. `home.title`, `common.title`.",
|
|
127
|
-
"5.
|
|
128
|
-
" a Crowdin-style `files` array of `{ source, translation }` patterns with a `{locale}` placeholder,",
|
|
129
|
-
' e.g. { "source": "messages/en/home.json", "translation": "messages/{locale}/home.json", "namespace": "home" }.',
|
|
130
|
-
' Do NOT invent fields like `i18nDir`, `format: "nested-dirs"`, or `localeMapping` \u2014 they are not read.',
|
|
131
|
-
"6. Offer to run the localize_changed flow next."
|
|
142
|
+
"5. Offer to run the localize_changed flow next."
|
|
132
143
|
].join("\n")
|
|
133
144
|
)
|
|
134
145
|
);
|
|
@@ -376,6 +387,9 @@ var fileMapping = z3.object({
|
|
|
376
387
|
source: z3.string().describe('Source file path or glob, e.g. "messages/en.json" or "locales/en/*.json".'),
|
|
377
388
|
translation: z3.string().describe(
|
|
378
389
|
'Translation pattern with Crowdin placeholders, e.g. "messages/%two_letters_code%/%original_file_name%" or "locales/%locale%/%original_file_name%".'
|
|
390
|
+
),
|
|
391
|
+
languagesMapping: z3.record(z3.string(), z3.record(z3.string(), z3.string())).optional().describe(
|
|
392
|
+
'Crowdin `languages_mapping`: per-placeholder, per-locale path overrides. Keyed by placeholder name without % (e.g. "two_letters_code", "locale"), then by locale code, to the on-disk replacement. E.g. { "two_letters_code": { "zh-CN": "cn" } } writes Chinese under "cn" instead of "zh".'
|
|
379
393
|
)
|
|
380
394
|
});
|
|
381
395
|
function registerGitTools(server, gql) {
|
|
@@ -426,14 +440,18 @@ function registerGitTools(server, gql) {
|
|
|
426
440
|
"connect_repository",
|
|
427
441
|
{
|
|
428
442
|
title: "Connect repository",
|
|
429
|
-
description: "Connects a git repository to the project so syncs can pull source strings and push translation PRs. Provide EITHER `installationId` (GitHub App mode \u2014 after the browser authorize flow: `get_github_app_install_url` \u2192 `list_github_installations` \u2192 `list_github_installation_repos`) OR `token` (PAT mode \u2014 a personal access token with repo read + pull-request scope; works for GitHub, GitLab and Bitbucket, no browser needed).
|
|
443
|
+
description: "Connects a git repository to the project so syncs can pull source strings and push translation PRs. Provide EITHER `installationId` (GitHub App mode \u2014 after the browser authorize flow: `get_github_app_install_url` \u2192 `list_github_installations` \u2192 `list_github_installation_repos`) OR `token` (PAT mode \u2014 a personal access token with repo read + pull-request scope; works for GitHub, GitLab and Bitbucket, no browser needed). Mappings come from the repo's `.syntholoc/config.json` when present (the source of truth); `files`/`paths` here are an optional fallback used only when that file is absent. After connecting, call `sync_repository` to import all source strings at once.",
|
|
430
444
|
inputSchema: {
|
|
431
445
|
projectId: z3.string(),
|
|
432
446
|
provider: z3.enum(["GITHUB", "GITLAB", "BITBUCKET"]).default("GITHUB"),
|
|
433
447
|
fullName: z3.string().describe('owner/repo, e.g. "acme/webapp".'),
|
|
434
448
|
branch: z3.string().optional().describe('Source branch (default "main").'),
|
|
435
|
-
files: z3.array(fileMapping).optional().describe(
|
|
436
|
-
|
|
449
|
+
files: z3.array(fileMapping).optional().describe(
|
|
450
|
+
"Crowdin-style source\u2192translation mappings. Fallback only \u2014 an in-repo `.syntholoc/config.json` wins."
|
|
451
|
+
),
|
|
452
|
+
paths: z3.array(z3.string()).optional().describe(
|
|
453
|
+
"Simple mode: source file paths/globs to track (use instead of `files`). Fallback for in-repo config."
|
|
454
|
+
),
|
|
437
455
|
installationId: z3.number().int().optional().describe("GitHub App installation id. Mutually exclusive with `token`."),
|
|
438
456
|
token: z3.string().optional().describe("Access token (PAT mode), stored encrypted. Mutually exclusive with `installationId`."),
|
|
439
457
|
translationBranch: z3.string().optional().describe("Branch translations are committed to (default l10n_<branch>)."),
|
|
@@ -448,7 +466,7 @@ function registerGitTools(server, gql) {
|
|
|
448
466
|
"update_repository",
|
|
449
467
|
{
|
|
450
468
|
title: "Update repository",
|
|
451
|
-
description: "Updates
|
|
469
|
+
description: "Updates an already-connected repository in place \u2014 no disconnect/reconnect, stored credentials and webhooks are preserved. Use it for integration settings (source `branch`, translation/PR branches, `pushSources`, auto-sync), and for `files`/`paths` only as the DB fallback. IMPORTANT: to change file MAPPINGS, prefer editing the repo's `.syntholoc/config.json` and opening a PR \u2014 that file is the source of truth and, when present, overrides `files`/`paths` here (they are ignored). So do NOT use this tool to change mappings of a config-managed repo; edit the file, open a PR, and after it merges call `sync_repository`. Only the fields you pass are changed.",
|
|
452
470
|
inputSchema: {
|
|
453
471
|
projectId: z3.string(),
|
|
454
472
|
repositoryId: z3.string(),
|
|
@@ -499,7 +517,7 @@ function registerGitTools(server, gql) {
|
|
|
499
517
|
"pull_translations",
|
|
500
518
|
{
|
|
501
519
|
title: "Pull translations",
|
|
502
|
-
description: "
|
|
520
|
+
description: "Queues a pull of translation files from the connected repo, importing them per locale. Returns the tracking job ids \u2014 the import itself runs in the background. `autoApprove` lands them as APPROVED; `conflict` is OVERWRITE | SKIP_EXISTING | KEEP_APPROVED.",
|
|
503
521
|
inputSchema: {
|
|
504
522
|
projectId: z3.string(),
|
|
505
523
|
repositoryId: z3.string(),
|
|
@@ -892,6 +910,21 @@ function resolveLocaleCodes(raw) {
|
|
|
892
910
|
return out;
|
|
893
911
|
}
|
|
894
912
|
|
|
913
|
+
// ../../packages/shared/src/constants.ts
|
|
914
|
+
var ORG_PLANS = ["FREE", "PRO", "ENTERPRISE"];
|
|
915
|
+
function isPaidPlan(plan) {
|
|
916
|
+
switch (plan) {
|
|
917
|
+
case "PRO":
|
|
918
|
+
case "ENTERPRISE":
|
|
919
|
+
return true;
|
|
920
|
+
case "FREE":
|
|
921
|
+
return false;
|
|
922
|
+
default:
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
var PAID_PLANS = ORG_PLANS.filter(isPaidPlan);
|
|
927
|
+
|
|
895
928
|
// src/tools/locales.ts
|
|
896
929
|
function registerLocaleTools(server, gql) {
|
|
897
930
|
server.registerTool(
|
|
@@ -1073,8 +1106,27 @@ function registerProjectTools(server, gql) {
|
|
|
1073
1106
|
}
|
|
1074
1107
|
|
|
1075
1108
|
// src/tools/providers.ts
|
|
1076
|
-
import { z as
|
|
1109
|
+
import { z as z10 } from "zod";
|
|
1077
1110
|
import { CONFIGURE_LLM_PROVIDER, LIST_LLM_PROVIDERS } from "@syntholoc/sdk";
|
|
1111
|
+
|
|
1112
|
+
// src/generated/input-schemas.ts
|
|
1113
|
+
import { z as z9 } from "zod";
|
|
1114
|
+
var isDefinedNonNullAny = (v) => v !== void 0 && v !== null;
|
|
1115
|
+
var definedNonNullAnySchema = z9.any().refine((v) => isDefinedNonNullAny(v));
|
|
1116
|
+
var CoverageCategorySchema = z9.enum(["DYNAMIC", "NOT_CAPTURED", "NOT_IN_CODE", "SCREENSHOT", "SERVER", "TEST", "UNKNOWN"]);
|
|
1117
|
+
var QaCriticScopeSchema = z9.enum(["ALL", "UNREVIEWED"]);
|
|
1118
|
+
var TranslateScopeSchema = z9.enum(["ALL", "DRAFT", "UNAPPROVED", "UNTRANSLATED"]);
|
|
1119
|
+
function ConfigureProviderInputSchema() {
|
|
1120
|
+
return z9.object({
|
|
1121
|
+
apiKey: z9.string(),
|
|
1122
|
+
baseUrl: z9.string().nullish(),
|
|
1123
|
+
defaultModel: z9.string().nullish(),
|
|
1124
|
+
label: z9.string(),
|
|
1125
|
+
type: z9.string()
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// src/tools/providers.ts
|
|
1078
1130
|
function registerProviderTools(server, gql) {
|
|
1079
1131
|
server.registerTool(
|
|
1080
1132
|
"list_llm_providers",
|
|
@@ -1091,12 +1143,12 @@ function registerProviderTools(server, gql) {
|
|
|
1091
1143
|
{
|
|
1092
1144
|
title: "Configure LLM provider",
|
|
1093
1145
|
description: "Adds a BYOK LLM provider (OPENROUTER | OPENAI | ANTHROPIC). The key is stored encrypted at rest.",
|
|
1146
|
+
// Base field shapes (apiKey/label/baseUrl/defaultModel) are generated from
|
|
1147
|
+
// the GraphQL ConfigureProviderInput; we curate `type` from the API's
|
|
1148
|
+
// free-form String down to the concrete provider set.
|
|
1094
1149
|
inputSchema: {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
apiKey: z9.string(),
|
|
1098
|
-
baseUrl: z9.string().optional(),
|
|
1099
|
-
defaultModel: z9.string().optional()
|
|
1150
|
+
...ConfigureProviderInputSchema().shape,
|
|
1151
|
+
type: z10.enum(["OPENROUTER", "OPENAI", "ANTHROPIC"])
|
|
1100
1152
|
},
|
|
1101
1153
|
annotations: { openWorldHint: true }
|
|
1102
1154
|
},
|
|
@@ -1105,15 +1157,19 @@ function registerProviderTools(server, gql) {
|
|
|
1105
1157
|
}
|
|
1106
1158
|
|
|
1107
1159
|
// src/tools/qa.ts
|
|
1108
|
-
import { z as
|
|
1160
|
+
import { z as z11 } from "zod";
|
|
1109
1161
|
import { LIST_QA_ISSUES as LIST_QA_ISSUES2, RUN_LOCALE_QA, RUN_QA } from "@syntholoc/sdk";
|
|
1110
1162
|
function registerQaTools(server, gql) {
|
|
1111
1163
|
server.registerTool(
|
|
1112
1164
|
"run_locale_qa",
|
|
1113
1165
|
{
|
|
1114
1166
|
title: "Run locale QA",
|
|
1115
|
-
description: "Queues an LLM QA-critic pass over a locale's non-approved translations. Returns tracking `jobIds`.",
|
|
1116
|
-
inputSchema: {
|
|
1167
|
+
description: "Queues an LLM QA-critic pass over a locale's non-approved translations. `scope` UNREVIEWED (default) critiques only translations not yet reviewed at their current text; ALL re-critiques every one. Returns tracking `jobIds`.",
|
|
1168
|
+
inputSchema: {
|
|
1169
|
+
projectId: z11.string(),
|
|
1170
|
+
localeId: z11.string(),
|
|
1171
|
+
scope: z11.enum(["UNREVIEWED", "ALL"]).optional()
|
|
1172
|
+
},
|
|
1117
1173
|
annotations: { openWorldHint: true }
|
|
1118
1174
|
},
|
|
1119
1175
|
async (vars) => jsonResult((await gql.request(RUN_LOCALE_QA, vars)).runLocaleQa)
|
|
@@ -1123,7 +1179,7 @@ function registerQaTools(server, gql) {
|
|
|
1123
1179
|
{
|
|
1124
1180
|
title: "Run QA on a translation",
|
|
1125
1181
|
description: "Runs QA on one translation and returns its score and issues (synchronous).",
|
|
1126
|
-
inputSchema: { projectId:
|
|
1182
|
+
inputSchema: { projectId: z11.string(), translationId: z11.string() },
|
|
1127
1183
|
annotations: { openWorldHint: true }
|
|
1128
1184
|
},
|
|
1129
1185
|
async (vars) => jsonResult((await gql.request(RUN_QA, vars)).runQa)
|
|
@@ -1134,11 +1190,11 @@ function registerQaTools(server, gql) {
|
|
|
1134
1190
|
title: "List QA issues",
|
|
1135
1191
|
description: "Open QA issues for a locale, plus the issue-type facets (filter tabs).",
|
|
1136
1192
|
inputSchema: {
|
|
1137
|
-
projectId:
|
|
1138
|
-
localeId:
|
|
1139
|
-
type:
|
|
1140
|
-
skip:
|
|
1141
|
-
take:
|
|
1193
|
+
projectId: z11.string(),
|
|
1194
|
+
localeId: z11.string(),
|
|
1195
|
+
type: z11.string().optional(),
|
|
1196
|
+
skip: z11.number().int().min(0).default(0),
|
|
1197
|
+
take: z11.number().int().min(1).max(100).default(25)
|
|
1142
1198
|
},
|
|
1143
1199
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1144
1200
|
},
|
|
@@ -1147,7 +1203,7 @@ function registerQaTools(server, gql) {
|
|
|
1147
1203
|
}
|
|
1148
1204
|
|
|
1149
1205
|
// src/tools/styleguides.ts
|
|
1150
|
-
import { z as
|
|
1206
|
+
import { z as z12 } from "zod";
|
|
1151
1207
|
import { CREATE_STYLE_GUIDE, GENERATE_STYLE_GUIDE, LIST_STYLE_GUIDES as LIST_STYLE_GUIDES2 } from "@syntholoc/sdk";
|
|
1152
1208
|
function registerStyleGuideTools(server, gql) {
|
|
1153
1209
|
server.registerTool(
|
|
@@ -1155,7 +1211,7 @@ function registerStyleGuideTools(server, gql) {
|
|
|
1155
1211
|
{
|
|
1156
1212
|
title: "List style guides",
|
|
1157
1213
|
description: "Lists style guides (project-scoped if projectId given, else org-wide).",
|
|
1158
|
-
inputSchema: { projectId:
|
|
1214
|
+
inputSchema: { projectId: z12.string().optional() },
|
|
1159
1215
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1160
1216
|
},
|
|
1161
1217
|
async ({ projectId }) => jsonResult((await gql.request(LIST_STYLE_GUIDES2, { projectId })).styleGuides)
|
|
@@ -1165,7 +1221,7 @@ function registerStyleGuideTools(server, gql) {
|
|
|
1165
1221
|
{
|
|
1166
1222
|
title: "Generate style guide (draft)",
|
|
1167
1223
|
description: "Drafts a tone/rules/prompt-fragment style guide from the project (optionally per locale). Returns a draft \u2014 persist it with create_style_guide.",
|
|
1168
|
-
inputSchema: { projectId:
|
|
1224
|
+
inputSchema: { projectId: z12.string(), localeCode: z12.string().optional() },
|
|
1169
1225
|
annotations: { openWorldHint: true }
|
|
1170
1226
|
},
|
|
1171
1227
|
async ({ projectId, localeCode }) => jsonResult((await gql.request(GENERATE_STYLE_GUIDE, { input: { projectId, localeCode } })).generateStyleGuide)
|
|
@@ -1176,12 +1232,12 @@ function registerStyleGuideTools(server, gql) {
|
|
|
1176
1232
|
title: "Create style guide",
|
|
1177
1233
|
description: "Persists a style guide (tone, rules JSON, prompt fragment), project- or locale-scoped.",
|
|
1178
1234
|
inputSchema: {
|
|
1179
|
-
name:
|
|
1180
|
-
projectId:
|
|
1181
|
-
localeCode:
|
|
1182
|
-
tone:
|
|
1183
|
-
promptFragment:
|
|
1184
|
-
rules:
|
|
1235
|
+
name: z12.string(),
|
|
1236
|
+
projectId: z12.string().optional(),
|
|
1237
|
+
localeCode: z12.string().optional(),
|
|
1238
|
+
tone: z12.string().optional(),
|
|
1239
|
+
promptFragment: z12.string().optional(),
|
|
1240
|
+
rules: z12.array(z12.string()).optional()
|
|
1185
1241
|
},
|
|
1186
1242
|
annotations: { openWorldHint: true }
|
|
1187
1243
|
},
|
|
@@ -1190,7 +1246,7 @@ function registerStyleGuideTools(server, gql) {
|
|
|
1190
1246
|
}
|
|
1191
1247
|
|
|
1192
1248
|
// src/tools/tm.ts
|
|
1193
|
-
import { z as
|
|
1249
|
+
import { z as z13 } from "zod";
|
|
1194
1250
|
import { TM_ENTRIES, TM_SEARCH, TM_STATS as TM_STATS2 } from "@syntholoc/sdk";
|
|
1195
1251
|
function registerTmTools(server, gql) {
|
|
1196
1252
|
server.registerTool(
|
|
@@ -1199,10 +1255,10 @@ function registerTmTools(server, gql) {
|
|
|
1199
1255
|
title: "Search translation memory",
|
|
1200
1256
|
description: "Concordance search \u2014 the nearest prior translations for a source string in a target locale, with cosine distance (0 = identical). Scope to a project with `projectId` (includes its group siblings); omit for the whole organization. Requires an embedding-capable provider.",
|
|
1201
1257
|
inputSchema: {
|
|
1202
|
-
sourceText:
|
|
1203
|
-
targetLocale:
|
|
1204
|
-
projectId:
|
|
1205
|
-
limit:
|
|
1258
|
+
sourceText: z13.string(),
|
|
1259
|
+
targetLocale: z13.string(),
|
|
1260
|
+
projectId: z13.string().optional(),
|
|
1261
|
+
limit: z13.number().int().min(1).max(20).default(5)
|
|
1206
1262
|
},
|
|
1207
1263
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1208
1264
|
},
|
|
@@ -1213,7 +1269,7 @@ function registerTmTools(server, gql) {
|
|
|
1213
1269
|
{
|
|
1214
1270
|
title: "Translation memory stats",
|
|
1215
1271
|
description: "Entry counts per target locale (org-wide, or for a project with `projectId`).",
|
|
1216
|
-
inputSchema: { projectId:
|
|
1272
|
+
inputSchema: { projectId: z13.string().optional() },
|
|
1217
1273
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1218
1274
|
},
|
|
1219
1275
|
async ({ projectId }) => jsonResult((await gql.request(TM_STATS2, { projectId })).tmStats)
|
|
@@ -1224,10 +1280,10 @@ function registerTmTools(server, gql) {
|
|
|
1224
1280
|
title: "Browse translation memory",
|
|
1225
1281
|
description: "Lists stored TM entries (paginated, newest first), optionally filtered by project and locale.",
|
|
1226
1282
|
inputSchema: {
|
|
1227
|
-
projectId:
|
|
1228
|
-
targetLocale:
|
|
1229
|
-
skip:
|
|
1230
|
-
take:
|
|
1283
|
+
projectId: z13.string().optional(),
|
|
1284
|
+
targetLocale: z13.string().optional(),
|
|
1285
|
+
skip: z13.number().int().min(0).default(0),
|
|
1286
|
+
take: z13.number().int().min(1).max(100).default(25)
|
|
1231
1287
|
},
|
|
1232
1288
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1233
1289
|
},
|
|
@@ -1236,7 +1292,7 @@ function registerTmTools(server, gql) {
|
|
|
1236
1292
|
}
|
|
1237
1293
|
|
|
1238
1294
|
// src/tools/translations.ts
|
|
1239
|
-
import { z as
|
|
1295
|
+
import { z as z14 } from "zod";
|
|
1240
1296
|
import {
|
|
1241
1297
|
APPROVE_TRANSLATION,
|
|
1242
1298
|
EXPORT_LOCALE,
|
|
@@ -1255,9 +1311,9 @@ function registerTranslationTools(server, gql) {
|
|
|
1255
1311
|
title: "Translate locale",
|
|
1256
1312
|
description: "Queues AI translation of a locale (scope ALL | UNAPPROVED | UNTRANSLATED) and returns the tracking `jobIds`. Executes immediately and unconditionally \u2014 the batch threshold is a client-side policy for PROACTIVE syncs, not for this tool.",
|
|
1257
1313
|
inputSchema: {
|
|
1258
|
-
projectId:
|
|
1259
|
-
localeId:
|
|
1260
|
-
scope:
|
|
1314
|
+
projectId: z14.string(),
|
|
1315
|
+
localeId: z14.string(),
|
|
1316
|
+
scope: z14.enum(["ALL", "UNAPPROVED", "UNTRANSLATED"]).default("UNTRANSLATED")
|
|
1261
1317
|
},
|
|
1262
1318
|
annotations: { openWorldHint: true }
|
|
1263
1319
|
},
|
|
@@ -1268,7 +1324,7 @@ function registerTranslationTools(server, gql) {
|
|
|
1268
1324
|
{
|
|
1269
1325
|
title: "Translate key",
|
|
1270
1326
|
description: "Queues AI translation of a single key into a locale. Returns tracking `jobIds`.",
|
|
1271
|
-
inputSchema: { projectId:
|
|
1327
|
+
inputSchema: { projectId: z14.string(), keyId: z14.string(), localeId: z14.string() },
|
|
1272
1328
|
annotations: { openWorldHint: true }
|
|
1273
1329
|
},
|
|
1274
1330
|
async (vars) => jsonResult((await gql.request(TRANSLATE_KEY, vars)).translateKey)
|
|
@@ -1279,11 +1335,11 @@ function registerTranslationTools(server, gql) {
|
|
|
1279
1335
|
title: "Set translation",
|
|
1280
1336
|
description: "Manually sets the translation text for a key in a locale.",
|
|
1281
1337
|
inputSchema: {
|
|
1282
|
-
projectId:
|
|
1283
|
-
keyId:
|
|
1284
|
-
localeId:
|
|
1285
|
-
text:
|
|
1286
|
-
origin:
|
|
1338
|
+
projectId: z14.string(),
|
|
1339
|
+
keyId: z14.string(),
|
|
1340
|
+
localeId: z14.string(),
|
|
1341
|
+
text: z14.string(),
|
|
1342
|
+
origin: z14.string().optional()
|
|
1287
1343
|
},
|
|
1288
1344
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
1289
1345
|
},
|
|
@@ -1297,10 +1353,10 @@ function registerTranslationTools(server, gql) {
|
|
|
1297
1353
|
title: "List translations",
|
|
1298
1354
|
description: "Lists translations for a locale with status, origin, model and QA score.",
|
|
1299
1355
|
inputSchema: {
|
|
1300
|
-
projectId:
|
|
1301
|
-
localeId:
|
|
1302
|
-
skip:
|
|
1303
|
-
take:
|
|
1356
|
+
projectId: z14.string(),
|
|
1357
|
+
localeId: z14.string(),
|
|
1358
|
+
skip: z14.number().int().min(0).default(0),
|
|
1359
|
+
take: z14.number().int().min(1).max(200).default(25)
|
|
1304
1360
|
},
|
|
1305
1361
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1306
1362
|
},
|
|
@@ -1311,7 +1367,7 @@ function registerTranslationTools(server, gql) {
|
|
|
1311
1367
|
{
|
|
1312
1368
|
title: "Approve translation",
|
|
1313
1369
|
description: "Marks a translation APPROVED.",
|
|
1314
|
-
inputSchema: { projectId:
|
|
1370
|
+
inputSchema: { projectId: z14.string(), translationId: z14.string() },
|
|
1315
1371
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
1316
1372
|
},
|
|
1317
1373
|
async ({ projectId, translationId }) => jsonResult((await gql.request(APPROVE_TRANSLATION, { projectId, id: translationId })).approveTranslation)
|
|
@@ -1321,7 +1377,7 @@ function registerTranslationTools(server, gql) {
|
|
|
1321
1377
|
{
|
|
1322
1378
|
title: "Reject translation",
|
|
1323
1379
|
description: "Marks a translation REJECTED (queued for re-translation).",
|
|
1324
|
-
inputSchema: { projectId:
|
|
1380
|
+
inputSchema: { projectId: z14.string(), translationId: z14.string() },
|
|
1325
1381
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
1326
1382
|
},
|
|
1327
1383
|
async ({ projectId, translationId }) => jsonResult((await gql.request(REJECT_TRANSLATION, { projectId, id: translationId })).rejectTranslation)
|
|
@@ -1332,12 +1388,12 @@ function registerTranslationTools(server, gql) {
|
|
|
1332
1388
|
title: "Export locale",
|
|
1333
1389
|
description: 'Exports a locale to a file string in the chosen format \u2014 the basis for writing translations back into local files. Set `onlyApproved` to exclude drafts. For a multi-file project prefer `sourceFile` (the real path, e.g. "messages/en/home.json") to export exactly that file with file-local key names \u2014 reliable. `keyPrefix` is the legacy name-prefix heuristic.',
|
|
1334
1390
|
inputSchema: {
|
|
1335
|
-
projectId:
|
|
1336
|
-
localeId:
|
|
1337
|
-
format:
|
|
1338
|
-
onlyApproved:
|
|
1339
|
-
sourceFile:
|
|
1340
|
-
keyPrefix:
|
|
1391
|
+
projectId: z14.string(),
|
|
1392
|
+
localeId: z14.string(),
|
|
1393
|
+
format: z14.enum(FORMATS2).default("JSON"),
|
|
1394
|
+
onlyApproved: z14.boolean().optional(),
|
|
1395
|
+
sourceFile: z14.string().optional().describe('Real source file path to export, e.g. "messages/en/home.json" (reliable, preferred).'),
|
|
1396
|
+
keyPrefix: z14.string().optional().describe('Legacy namespace prefix, e.g. "home".')
|
|
1341
1397
|
},
|
|
1342
1398
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1343
1399
|
},
|
|
@@ -1349,14 +1405,14 @@ function registerTranslationTools(server, gql) {
|
|
|
1349
1405
|
title: "Import translations",
|
|
1350
1406
|
description: "Imports ready-made translations for a locale (paste file content). `autoApprove` lands them as APPROVED; `conflict` is OVERWRITE | SKIP_EXISTING | KEEP_APPROVED; `dryRun` previews. For a multi-file / namespaced project pass `keyPrefix` (the namespace) to match prefixed source keys.",
|
|
1351
1407
|
inputSchema: {
|
|
1352
|
-
projectId:
|
|
1353
|
-
localeId:
|
|
1354
|
-
content:
|
|
1355
|
-
format:
|
|
1356
|
-
keyPrefix:
|
|
1357
|
-
autoApprove:
|
|
1358
|
-
conflict:
|
|
1359
|
-
dryRun:
|
|
1408
|
+
projectId: z14.string(),
|
|
1409
|
+
localeId: z14.string(),
|
|
1410
|
+
content: z14.string(),
|
|
1411
|
+
format: z14.enum(FORMATS2),
|
|
1412
|
+
keyPrefix: z14.string().optional().describe('Namespace prefix, e.g. "home" (per-file, multi-file projects).'),
|
|
1413
|
+
autoApprove: z14.boolean().default(false),
|
|
1414
|
+
conflict: z14.enum(["OVERWRITE", "SKIP_EXISTING", "KEEP_APPROVED"]).default("OVERWRITE"),
|
|
1415
|
+
dryRun: z14.boolean().default(false)
|
|
1360
1416
|
},
|
|
1361
1417
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
1362
1418
|
},
|
|
@@ -1399,7 +1455,7 @@ function registerAllTools(server, gql, opts = {}) {
|
|
|
1399
1455
|
}
|
|
1400
1456
|
|
|
1401
1457
|
// package.json
|
|
1402
|
-
var version = "0.
|
|
1458
|
+
var version = "0.4.0";
|
|
1403
1459
|
|
|
1404
1460
|
// src/build-server.ts
|
|
1405
1461
|
function buildMcpServer(gql, opts = {}) {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syntholoc/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Syntholoc MCP server — manage AI localization and translation from Codex, Claude Code, and other MCP clients.",
|
|
5
5
|
"homepage": "https://syntholoc.com",
|
|
6
6
|
"keywords": [
|
|
@@ -47,14 +47,17 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/sdk": "1.22.0",
|
|
50
|
-
"@syntholoc/sdk": "0.
|
|
50
|
+
"@syntholoc/sdk": "0.3.1",
|
|
51
51
|
"zod": "3.25.76"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
+
"@graphql-codegen/cli": "7.1.2",
|
|
55
|
+
"@graphql-codegen/typescript": "5.0.2",
|
|
54
56
|
"@syntholoc/shared": "0.1.0",
|
|
55
57
|
"@types/bun": "1.3.14",
|
|
56
58
|
"@types/node": "22.19.21",
|
|
57
59
|
"graphql": "16.14.2",
|
|
60
|
+
"graphql-codegen-typescript-validation-schema": "0.19.0",
|
|
58
61
|
"tsup": "8.5.1",
|
|
59
62
|
"typescript": "6.0.3"
|
|
60
63
|
}
|