@syntholoc/mcp 0.4.0 → 0.6.1

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.
@@ -1,1473 +0,0 @@
1
- // src/build-server.ts
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
-
4
- // src/filter.ts
5
- function wildcardToRegExp(pattern) {
6
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
7
- return new RegExp(`^${escaped}$`);
8
- }
9
- function matchesAnyPattern(name, patterns) {
10
- return patterns.some((re) => re.test(name));
11
- }
12
- function applyToolFilter(server, opts) {
13
- const patterns = (opts.toolFilter ?? []).filter(Boolean).map(wildcardToRegExp);
14
- if (!opts.readOnly && patterns.length === 0) return server;
15
- const original = server.registerTool.bind(server);
16
- const filtered = (name, config, handler) => {
17
- const readOnly = config.annotations?.readOnlyHint;
18
- if (opts.readOnly && !readOnly) return void 0;
19
- if (patterns.length > 0 && !matchesAnyPattern(name, patterns)) {
20
- return void 0;
21
- }
22
- return original(name, config, handler);
23
- };
24
- server.registerTool = filtered;
25
- return server;
26
- }
27
- function parseList(value) {
28
- if (!value) return void 0;
29
- const items = value.split(",").map((s) => s.trim()).filter(Boolean);
30
- return items.length > 0 ? items : void 0;
31
- }
32
-
33
- // src/prompts.ts
34
- import { z } from "zod";
35
- function userMessage(text) {
36
- return { messages: [{ role: "user", content: { type: "text", text } }] };
37
- }
38
- function registerPrompts(server) {
39
- server.registerPrompt(
40
- "localize_changed",
41
- {
42
- title: "Localize changed keys",
43
- description: "Sync changed source strings from the local diff, translate them, and write back.",
44
- argsSchema: { projectId: z.string().optional() }
45
- },
46
- ({ projectId }) => userMessage(
47
- [
48
- "Localize my recent source-string changes via Syntholoc:",
49
- "1. Read `.syntholoc/config.json` for the source file(s) and project.",
50
- projectId ? ` (Use project ${projectId} if config is missing.)` : "",
51
- "2. From `git diff` on the source file(s), collect added/changed keys and `upsert_keys` (dryRun first to preview).",
52
- " - MULTI-FILE / namespaced project (a file per namespace, e.g. messages/en/home.json): import each",
53
- " changed file with `import_source_strings` passing `keyPrefix` = the namespace (the filename",
54
- ' without extension, e.g. "home"). This keeps overlapping keys from different files distinct.',
55
- "3. Call `get_config` for `maxBulkSize`. For EACH target locale check `locale_progress` \u2192 `untranslated`.",
56
- " - If untranslated >= maxBulkSize: `translate_locale` (scope UNTRANSLATED) and `wait_for_job`.",
57
- " - If below the threshold: tell me the counts and ASK whether to translate now or accumulate.",
58
- "4. `export_locale` for each translated locale and write the result back into the mapped local files.",
59
- " - Namespaced project: call `export_locale` once per namespace with the same `keyPrefix` (the prefix",
60
- " is stripped on export) and write each result to its file, e.g. messages/{locale}/home.json.",
61
- "5. Summarize keys added, locales translated, and the cost from the job usage."
62
- ].filter(Boolean).join("\n")
63
- )
64
- );
65
- server.registerPrompt(
66
- "review_locale",
67
- {
68
- title: "Review a locale",
69
- description: "Surface low-confidence and QA-flagged translations for a locale and help fix them.",
70
- argsSchema: { projectId: z.string(), locale: z.string() }
71
- },
72
- ({ projectId, locale }) => userMessage(
73
- [
74
- `Review the "${locale}" locale in project ${projectId}:`,
75
- "1. Resolve the locale id via `list_locales`.",
76
- "2. `list_qa_issues` and `list_translations` (focus on low qaScore / non-approved).",
77
- "3. For each problem, propose a fix; apply with `set_translation`, then `approve_translation`.",
78
- "4. Summarize what changed and what still needs human attention."
79
- ].join("\n")
80
- )
81
- );
82
- server.registerPrompt(
83
- "bootstrap_project",
84
- {
85
- title: "Bootstrap a project from this repo",
86
- description: "Detect i18n files, create a Syntholoc project, seed locales, and write .syntholoc/config.json.",
87
- argsSchema: {}
88
- },
89
- () => userMessage(
90
- [
91
- "Set up Syntholoc for this repository. PREFER the Git-sync path \u2014 it pulls every",
92
- "source string (and any existing translations) in one server-side sync and keeps",
93
- "the repo in sync going forward. Only fall back to manual imports if the user",
94
- "declines Git or the code lives outside a supported host.",
95
- "",
96
- "1. Find the i18n source files and infer the source locale. Note the LAYOUT:",
97
- " - Single file per locale (e.g. messages/en.json) \u2192 one mapping, no namespace.",
98
- " - A directory per locale with many namespace files (e.g. messages/en/home.json, common.json) \u2192",
99
- " each file is a NAMESPACE. Syntholoc stores a flat key space, so each file is imported under a",
100
- " `keyPrefix` = its namespace to avoid collisions between files.",
101
- "2. `create_project`, then `seed_starter_locales` (or `add_locale`) for the target languages. Use valid",
102
- ' BCP-47 codes (e.g. "zh-CN", not "cn"); `add_locale` accepts `code` or `locale`. Region-ambiguous',
103
- ' languages MUST be qualified \u2014 "pt" \u2192 "pt-BR" or "pt-PT", "zh" \u2192 "zh-CN" or "zh-TW". The tools reject',
104
- ' ambiguous codes and return the candidates; if a source filename is just "pt", ask which region (or',
105
- " default to the likeliest) and record the file\u2192locale mapping in the config `translation` pattern.",
106
- " (Target locales must exist before a Git pull so their translation files import.)",
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 (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:",
125
- " - GitHub App (no token, recommended): `get_github_app_install_url` \u2192 give the URL to the user",
126
- " to open and authorize (you cannot click it for them; you may offer to open it via the shell).",
127
- " Once they confirm, `list_github_installations` \u2192 `list_github_installation_repos` to pick the",
128
- " repo, then `connect_repository` with that `installationId`.",
129
- " - Token (any host): ask the user to paste a personal access token (repo read + PR scope), then",
130
- " `connect_repository` with `token`. Treat the token as a secret \u2014 do not echo it back.",
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.",
135
- " Then `pull_translations` to import any translation files already in the repo (choose `conflict`",
136
- " KEEP_APPROVED to preserve in-app edits).",
137
- " f. `wait_for_job` on the returned job id, then report keys imported and the PR url.",
138
- "4. FALLBACK \u2014 file-based import when the user does NOT want Git integration:",
139
- " - Single file \u2192 `import_source_strings` once.",
140
- " - Namespaced \u2192 `import_source_strings` once PER FILE with `keyPrefix` set to the namespace",
141
- " (filename without extension). Keys become e.g. `home.title`, `common.title`.",
142
- "5. Offer to run the localize_changed flow next."
143
- ].join("\n")
144
- )
145
- );
146
- }
147
-
148
- // src/resources.ts
149
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
150
- import {
151
- CONFIG_RESOURCE,
152
- LIST_GLOSSARY,
153
- LIST_JOBS,
154
- LIST_KEYS,
155
- LIST_LOCALES,
156
- LIST_QA_ISSUES,
157
- LIST_STYLE_GUIDES,
158
- MY_ORGANIZATIONS,
159
- PROJECT_ANALYTICS,
160
- TM_STATS,
161
- PROJECT_RESOURCE,
162
- PROJECTS_RESOURCE
163
- } from "@syntholoc/sdk";
164
- function json(uri, data) {
165
- return { contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) }] };
166
- }
167
- function registerResources(server, gql) {
168
- server.registerResource(
169
- "config",
170
- "syntholoc://config",
171
- {
172
- title: "Syntholoc config",
173
- description: "Server metadata incl. maxBulkSize (auto-translate batch threshold).",
174
- mimeType: "application/json"
175
- },
176
- async (uri) => json(uri, (await gql.request(CONFIG_RESOURCE)).appInfo)
177
- );
178
- server.registerResource(
179
- "organizations",
180
- "syntholoc://organizations",
181
- {
182
- title: "Organizations",
183
- description: "Organizations you belong to (id, name, slug, your role). Pin one for a request with the `syntholoc-org-id` header; otherwise the earliest org is used.",
184
- mimeType: "application/json"
185
- },
186
- async (uri) => json(
187
- uri,
188
- (await gql.request(MY_ORGANIZATIONS)).myProjects.map((g) => ({ ...g.organization, viewerRole: g.viewerRole }))
189
- )
190
- );
191
- server.registerResource(
192
- "projects",
193
- "syntholoc://projects",
194
- {
195
- title: "Projects",
196
- description: "All localization projects in the active organization.",
197
- mimeType: "application/json"
198
- },
199
- async (uri) => json(uri, (await gql.request(PROJECTS_RESOURCE)).projects)
200
- );
201
- server.registerResource(
202
- "project",
203
- new ResourceTemplate("syntholoc://project/{projectId}", { list: void 0 }),
204
- {
205
- title: "Project overview",
206
- description: "A project with its locales and per-locale progress.",
207
- mimeType: "application/json"
208
- },
209
- async (uri, { projectId }) => json(uri, await gql.request(PROJECT_RESOURCE, { id: String(projectId) }))
210
- );
211
- server.registerResource(
212
- "project-keys",
213
- new ResourceTemplate("syntholoc://project/{projectId}/keys", { list: void 0 }),
214
- {
215
- title: "Project source keys",
216
- description: "Source keys in a project (first 100).",
217
- mimeType: "application/json"
218
- },
219
- async (uri, { projectId }) => json(uri, (await gql.request(LIST_KEYS, { projectId: String(projectId), skip: 0, take: 100 })).keys)
220
- );
221
- server.registerResource(
222
- "project-locales",
223
- new ResourceTemplate("syntholoc://project/{projectId}/locales", { list: void 0 }),
224
- {
225
- title: "Project locales",
226
- description: "Target locales configured for a project.",
227
- mimeType: "application/json"
228
- },
229
- async (uri, { projectId }) => json(uri, (await gql.request(LIST_LOCALES, { projectId: String(projectId) })).locales)
230
- );
231
- server.registerResource(
232
- "project-progress",
233
- new ResourceTemplate("syntholoc://project/{projectId}/progress", { list: void 0 }),
234
- {
235
- title: "Project progress",
236
- description: "Per-locale translation progress and open QA issues for a project.",
237
- mimeType: "application/json"
238
- },
239
- async (uri, { projectId }) => json(uri, (await gql.request(PROJECT_ANALYTICS, { projectId: String(projectId) })).projectAnalytics)
240
- );
241
- server.registerResource(
242
- "project-jobs",
243
- new ResourceTemplate("syntholoc://project/{projectId}/jobs", { list: void 0 }),
244
- {
245
- title: "Project jobs",
246
- description: "Recent background jobs (translate / QA / git sync) for a project.",
247
- mimeType: "application/json"
248
- },
249
- async (uri, { projectId }) => json(uri, (await gql.request(LIST_JOBS, { projectId: String(projectId), skip: 0, take: 25 })).jobs)
250
- );
251
- server.registerResource(
252
- "project-qa-issues",
253
- new ResourceTemplate("syntholoc://project/{projectId}/locale/{localeId}/qa-issues", { list: void 0 }),
254
- {
255
- title: "Locale QA issues",
256
- description: "Open QA issues for a locale, with issue-type facets.",
257
- mimeType: "application/json"
258
- },
259
- async (uri, { projectId, localeId }) => json(
260
- uri,
261
- await gql.request(LIST_QA_ISSUES, {
262
- projectId: String(projectId),
263
- localeId: String(localeId),
264
- skip: 0,
265
- take: 25
266
- })
267
- )
268
- );
269
- server.registerResource(
270
- "glossary",
271
- "syntholoc://glossary",
272
- {
273
- title: "Glossary",
274
- description: "Organization-wide glossary terms (first 100).",
275
- mimeType: "application/json"
276
- },
277
- async (uri) => json(uri, (await gql.request(LIST_GLOSSARY, { skip: 0, take: 100 })).glossaryTerms)
278
- );
279
- server.registerResource(
280
- "style-guides",
281
- "syntholoc://style-guides",
282
- {
283
- title: "Style guides",
284
- description: "Organization-wide style guides.",
285
- mimeType: "application/json"
286
- },
287
- async (uri) => json(uri, (await gql.request(LIST_STYLE_GUIDES, {})).styleGuides)
288
- );
289
- server.registerResource(
290
- "tm",
291
- "syntholoc://tm",
292
- {
293
- title: "Translation memory",
294
- description: "Organization-wide Translation Memory coverage: entry counts per target locale.",
295
- mimeType: "application/json"
296
- },
297
- async (uri) => json(uri, (await gql.request(TM_STATS, {})).tmStats)
298
- );
299
- }
300
-
301
- // src/tools/analytics.ts
302
- import { z as z2 } from "zod";
303
- import { COST_ANALYTICS, LOCALE_PROGRESS, PROJECT_ANALYTICS as PROJECT_ANALYTICS2, USAGE } from "@syntholoc/sdk";
304
-
305
- // src/util.ts
306
- function jsonResult(data) {
307
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
308
- }
309
-
310
- // src/tools/analytics.ts
311
- function registerAnalyticsTools(server, gql) {
312
- server.registerTool(
313
- "project_analytics",
314
- {
315
- title: "Project analytics",
316
- description: "Real-time quality + progress metrics for a project (per-locale progress, open issues).",
317
- inputSchema: { projectId: z2.string() },
318
- annotations: { readOnlyHint: true, openWorldHint: true }
319
- },
320
- async ({ projectId }) => jsonResult((await gql.request(PROJECT_ANALYTICS2, { projectId })).projectAnalytics)
321
- );
322
- server.registerTool(
323
- "cost_analytics",
324
- {
325
- title: "Cost analytics",
326
- description: "LLM token/cost time-series and per-model breakdown over a window (default 30 days).",
327
- inputSchema: {
328
- projectId: z2.string().optional(),
329
- days: z2.number().int().min(1).max(365).optional()
330
- },
331
- annotations: { readOnlyHint: true, openWorldHint: true }
332
- },
333
- async (vars) => jsonResult((await gql.request(COST_ANALYTICS, vars)).costAnalytics)
334
- );
335
- server.registerTool(
336
- "usage",
337
- {
338
- title: "Org usage & plan",
339
- description: "Organization plan, hosted words and seat usage.",
340
- inputSchema: {},
341
- annotations: { readOnlyHint: true, openWorldHint: true }
342
- },
343
- async () => jsonResult((await gql.request(USAGE)).usage)
344
- );
345
- server.registerTool(
346
- "locale_progress",
347
- {
348
- title: "Locale progress",
349
- description: "Progress counts for one locale: total / untranslated / autoTranslated / humanApproved. The `untranslated` count drives the batch threshold (see get_config).",
350
- inputSchema: { projectId: z2.string(), localeId: z2.string() },
351
- annotations: { readOnlyHint: true, openWorldHint: true }
352
- },
353
- async (vars) => jsonResult((await gql.request(LOCALE_PROGRESS, vars)).localeProgress)
354
- );
355
- }
356
-
357
- // src/tools/config.ts
358
- import { APP_INFO } from "@syntholoc/sdk";
359
- function registerConfigTools(server, gql) {
360
- server.registerTool(
361
- "get_config",
362
- {
363
- title: "Get Syntholoc config",
364
- description: "Server metadata incl. `maxBulkSize` \u2014 the max keys per LLM batch. Use it as the auto-translate threshold: translate proactively only when pending untranslated keys reach this number; otherwise ask the user (an explicit translate request always runs).",
365
- inputSchema: {},
366
- annotations: { readOnlyHint: true, openWorldHint: true }
367
- },
368
- async () => jsonResult((await gql.request(APP_INFO)).appInfo)
369
- );
370
- }
371
-
372
- // src/tools/git.ts
373
- import { z as z3 } from "zod";
374
- import {
375
- CONNECT_REPOSITORY,
376
- DISCONNECT_REPOSITORY,
377
- GITHUB_APP,
378
- GITHUB_INSTALLATIONS,
379
- GITHUB_INSTALLATION_REPOS,
380
- LIST_REPOSITORIES,
381
- PULL_TRANSLATIONS,
382
- PUSH_TRANSLATIONS,
383
- SYNC_REPOSITORY,
384
- UPDATE_REPOSITORY
385
- } from "@syntholoc/sdk";
386
- var fileMapping = z3.object({
387
- source: z3.string().describe('Source file path or glob, e.g. "messages/en.json" or "locales/en/*.json".'),
388
- translation: z3.string().describe(
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".'
393
- )
394
- });
395
- function registerGitTools(server, gql) {
396
- server.registerTool(
397
- "list_repositories",
398
- {
399
- title: "List repositories",
400
- description: "Lists git repositories connected to a project.",
401
- inputSchema: { projectId: z3.string() },
402
- annotations: { readOnlyHint: true, openWorldHint: true }
403
- },
404
- async ({ projectId }) => jsonResult((await gql.request(LIST_REPOSITORIES, { projectId })).repositories)
405
- );
406
- server.registerTool(
407
- "get_github_app_install_url",
408
- {
409
- title: "GitHub App install URL",
410
- description: "Returns whether the Syntholoc GitHub App is available and a signed install URL for this project. GitHub App connect flow, step 1: give this URL to the user to open in a browser and authorize Syntholoc on the repositories they want (this authorize click cannot be automated). After they finish, call `list_github_installations` to see the new installation. If `enabled` is false, the server has no GitHub App configured \u2014 fall back to the token (PAT) path via `connect_repository`.",
411
- inputSchema: { projectId: z3.string() },
412
- annotations: { readOnlyHint: true, openWorldHint: true }
413
- },
414
- async ({ projectId }) => jsonResult((await gql.request(GITHUB_APP, { projectId })).githubApp)
415
- );
416
- server.registerTool(
417
- "list_github_installations",
418
- {
419
- title: "List GitHub installations",
420
- description: "Lists GitHub App installations available to the active organization. GitHub App connect flow, step 2: run this after the user authorized the App (see `get_github_app_install_url`) to get the `installationId` to pass to `list_github_installation_repos` and `connect_repository`.",
421
- inputSchema: { projectId: z3.string() },
422
- annotations: { readOnlyHint: true, openWorldHint: true }
423
- },
424
- async ({ projectId }) => jsonResult((await gql.request(GITHUB_INSTALLATIONS, { projectId })).githubInstallations)
425
- );
426
- server.registerTool(
427
- "list_github_installation_repos",
428
- {
429
- title: "List installation repositories",
430
- description: "Lists the repositories Syntholoc can access through a GitHub App installation. GitHub App connect flow, step 3: pick the target repo (its `fullName` and `defaultBranch`) before calling `connect_repository` with the same `installationId`.",
431
- inputSchema: {
432
- projectId: z3.string(),
433
- installationId: z3.number().int().describe("From `list_github_installations`.")
434
- },
435
- annotations: { readOnlyHint: true, openWorldHint: true }
436
- },
437
- async ({ projectId, installationId }) => jsonResult((await gql.request(GITHUB_INSTALLATION_REPOS, { projectId, installationId })).githubInstallationRepos)
438
- );
439
- server.registerTool(
440
- "connect_repository",
441
- {
442
- title: "Connect repository",
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.",
444
- inputSchema: {
445
- projectId: z3.string(),
446
- provider: z3.enum(["GITHUB", "GITLAB", "BITBUCKET"]).default("GITHUB"),
447
- fullName: z3.string().describe('owner/repo, e.g. "acme/webapp".'),
448
- branch: z3.string().optional().describe('Source branch (default "main").'),
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
- ),
455
- installationId: z3.number().int().optional().describe("GitHub App installation id. Mutually exclusive with `token`."),
456
- token: z3.string().optional().describe("Access token (PAT mode), stored encrypted. Mutually exclusive with `installationId`."),
457
- translationBranch: z3.string().optional().describe("Branch translations are committed to (default l10n_<branch>)."),
458
- prTargetBranch: z3.string().optional().describe("Branch translation PRs target (default = branch)."),
459
- autoSyncEnabled: z3.boolean().optional().describe("Enable scheduled auto-sync.")
460
- },
461
- annotations: { openWorldHint: true }
462
- },
463
- async ({ projectId, ...input }) => jsonResult((await gql.request(CONNECT_REPOSITORY, { projectId, input })).connectRepository)
464
- );
465
- server.registerTool(
466
- "update_repository",
467
- {
468
- title: "Update repository",
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.",
470
- inputSchema: {
471
- projectId: z3.string(),
472
- repositoryId: z3.string(),
473
- branch: z3.string().optional().describe("Source branch."),
474
- files: z3.array(fileMapping).optional().describe("Crowdin-style source\u2192translation mappings (replaces existing)."),
475
- paths: z3.array(z3.string()).optional().describe("Simple mode: source file paths/globs to track (replaces existing; use instead of `files`)."),
476
- translationBranch: z3.string().optional().describe("Branch translations are committed to."),
477
- prTargetBranch: z3.string().optional().describe("Branch translation PRs target."),
478
- pushSources: z3.boolean().optional().describe("Rewrite the source file(s) in the repo from in-app keys."),
479
- autoSyncEnabled: z3.boolean().optional().describe("Enable scheduled auto-sync."),
480
- syncIntervalHours: z3.number().int().optional().describe("Auto-sync interval in hours.")
481
- },
482
- annotations: { openWorldHint: true }
483
- },
484
- async ({ projectId, repositoryId, ...input }) => jsonResult((await gql.request(UPDATE_REPOSITORY, { projectId, id: repositoryId, input })).updateRepository)
485
- );
486
- server.registerTool(
487
- "disconnect_repository",
488
- {
489
- title: "Disconnect repository",
490
- description: "Removes the link to a connected repository and its stored credentials. The repository itself is not affected. Destructive \u2014 confirm with the user first.",
491
- inputSchema: { projectId: z3.string(), repositoryId: z3.string() },
492
- annotations: { destructiveHint: true, openWorldHint: true }
493
- },
494
- async ({ projectId, repositoryId }) => jsonResult((await gql.request(DISCONNECT_REPOSITORY, { projectId, id: repositoryId })).disconnectRepository)
495
- );
496
- server.registerTool(
497
- "sync_repository",
498
- {
499
- title: "Sync repository",
500
- description: "Queues an immediate sync of a connected repository (pull \u2192 translate \u2192 PR).",
501
- inputSchema: { projectId: z3.string(), repositoryId: z3.string() },
502
- annotations: { openWorldHint: true }
503
- },
504
- async ({ projectId, repositoryId }) => jsonResult((await gql.request(SYNC_REPOSITORY, { projectId, id: repositoryId })).syncRepository)
505
- );
506
- server.registerTool(
507
- "push_translations",
508
- {
509
- title: "Push translations (PR)",
510
- description: "Queues write-back of a locale: export approved translations and open a PR.",
511
- inputSchema: { projectId: z3.string(), repositoryId: z3.string(), localeId: z3.string() },
512
- annotations: { openWorldHint: true }
513
- },
514
- async (vars) => jsonResult((await gql.request(PUSH_TRANSLATIONS, vars)).pushTranslations)
515
- );
516
- server.registerTool(
517
- "pull_translations",
518
- {
519
- title: "Pull translations",
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.",
521
- inputSchema: {
522
- projectId: z3.string(),
523
- repositoryId: z3.string(),
524
- autoApprove: z3.boolean().default(false),
525
- conflict: z3.enum(["OVERWRITE", "SKIP_EXISTING", "KEEP_APPROVED"]).default("OVERWRITE")
526
- },
527
- annotations: { openWorldHint: true }
528
- },
529
- async ({ projectId, repositoryId, autoApprove, conflict }) => jsonResult(
530
- (await gql.request(PULL_TRANSLATIONS, { projectId, repositoryId, input: { autoApprove, conflict } })).pullTranslations
531
- )
532
- );
533
- }
534
-
535
- // src/tools/glossary.ts
536
- import { z as z4 } from "zod";
537
- import {
538
- CREATE_GLOSSARY_TERM,
539
- DELETE_GLOSSARY_TERM,
540
- LIST_GLOSSARY as LIST_GLOSSARY2,
541
- SUGGEST_GLOSSARY_TERMS,
542
- UPDATE_GLOSSARY_TERM
543
- } from "@syntholoc/sdk";
544
- function registerGlossaryTools(server, gql) {
545
- server.registerTool(
546
- "list_glossary",
547
- {
548
- title: "List glossary terms",
549
- description: "Lists glossary terms (project-scoped if projectId given, else org-wide).",
550
- inputSchema: {
551
- projectId: z4.string().optional(),
552
- skip: z4.number().int().min(0).default(0),
553
- take: z4.number().int().min(1).max(100).default(25)
554
- },
555
- annotations: { readOnlyHint: true, openWorldHint: true }
556
- },
557
- async (vars) => jsonResult((await gql.request(LIST_GLOSSARY2, vars)).glossaryTerms)
558
- );
559
- server.registerTool(
560
- "upsert_glossary_term",
561
- {
562
- title: "Create or update glossary term",
563
- description: 'Creates a glossary term, or updates it when `termId` is given. `translations` is a per-locale map, e.g. {"de-DE":"\u2026"}. `forbidden` lists disallowed renderings.',
564
- inputSchema: {
565
- termId: z4.string().optional().describe("Provide to update an existing term"),
566
- projectId: z4.string().optional().describe("Project scope; omit for org-wide (create only)"),
567
- term: z4.string().optional(),
568
- description: z4.string().optional(),
569
- partOfSpeech: z4.string().optional(),
570
- doNotTranslate: z4.boolean().optional(),
571
- forbidden: z4.array(z4.string()).optional(),
572
- translations: z4.record(z4.string(), z4.string()).optional()
573
- },
574
- annotations: { idempotentHint: true, openWorldHint: true }
575
- },
576
- async ({ termId, projectId, ...rest }) => {
577
- if (termId) {
578
- const data2 = await gql.request(UPDATE_GLOSSARY_TERM, { id: termId, input: rest });
579
- return jsonResult(data2.updateGlossaryTerm);
580
- }
581
- if (!rest.term) throw new Error("`term` is required when creating a glossary term");
582
- const data = await gql.request(CREATE_GLOSSARY_TERM, {
583
- input: { term: rest.term, ...rest, projectId }
584
- });
585
- return jsonResult(data.createGlossaryTerm);
586
- }
587
- );
588
- server.registerTool(
589
- "delete_glossary_term",
590
- {
591
- title: "Delete glossary term",
592
- description: "Deletes a glossary term. Destructive \u2014 confirm first.",
593
- inputSchema: { termId: z4.string() },
594
- annotations: { destructiveHint: true, openWorldHint: true }
595
- },
596
- async ({ termId }) => jsonResult((await gql.request(DELETE_GLOSSARY_TERM, { id: termId })).deleteGlossaryTerm)
597
- );
598
- server.registerTool(
599
- "suggest_glossary_terms",
600
- {
601
- title: "Suggest glossary terms",
602
- description: "AI-suggested glossary terms extracted from the project source strings.",
603
- inputSchema: { projectId: z4.string() },
604
- annotations: { readOnlyHint: true, openWorldHint: true }
605
- },
606
- async ({ projectId }) => jsonResult((await gql.request(SUGGEST_GLOSSARY_TERMS, { projectId })).suggestGlossaryTerms)
607
- );
608
- }
609
-
610
- // src/tools/jobs.ts
611
- import { z as z5 } from "zod";
612
- import { GET_JOB, LIST_JOBS as LIST_JOBS2, waitForJob } from "@syntholoc/sdk";
613
- function registerJobTools(server, gql) {
614
- server.registerTool(
615
- "get_job",
616
- {
617
- title: "Get job",
618
- description: "Status, progress, token usage and cost of a background job by id.",
619
- inputSchema: { jobId: z5.string(), projectId: z5.string() },
620
- annotations: { readOnlyHint: true, openWorldHint: true }
621
- },
622
- async ({ jobId, projectId }) => jsonResult((await gql.request(GET_JOB, { id: jobId, projectId })).job)
623
- );
624
- server.registerTool(
625
- "list_jobs",
626
- {
627
- title: "List jobs",
628
- description: "Recent background jobs for a project (translate / QA / git sync).",
629
- inputSchema: {
630
- projectId: z5.string(),
631
- skip: z5.number().int().min(0).default(0),
632
- take: z5.number().int().min(1).max(100).default(25)
633
- },
634
- annotations: { readOnlyHint: true, openWorldHint: true }
635
- },
636
- async (vars) => jsonResult((await gql.request(LIST_JOBS2, vars)).jobs)
637
- );
638
- server.registerTool(
639
- "wait_for_job",
640
- {
641
- title: "Wait for job",
642
- description: "Polls a job until it reaches a terminal state (COMPLETED/FAILED/CANCELLED) or the timeout elapses, then returns its final detail. Use after translate/QA to follow a run.",
643
- inputSchema: {
644
- jobId: z5.string(),
645
- projectId: z5.string(),
646
- timeoutSec: z5.number().int().min(1).max(900).default(180),
647
- pollIntervalSec: z5.number().int().min(1).max(30).default(2)
648
- },
649
- annotations: { readOnlyHint: true, openWorldHint: true }
650
- },
651
- async ({ jobId, projectId, timeoutSec, pollIntervalSec }) => {
652
- const { job, timedOut } = await waitForJob(gql, projectId, jobId, { timeoutSec, pollIntervalSec });
653
- return jsonResult({ ...job, timedOut });
654
- }
655
- );
656
- }
657
-
658
- // src/tools/keys.ts
659
- import { z as z6 } from "zod";
660
- import {
661
- DELETE_KEY,
662
- IMPORT_SOURCE_STRINGS,
663
- LIST_KEYS as LIST_KEYS2,
664
- UPDATE_KEY,
665
- UPSERT_KEYS
666
- } from "@syntholoc/sdk";
667
- var FORMATS = ["JSON", "YAML", "CSV", "PO", "XLIFF", "ANDROID_XML", "IOS_STRINGS", "NJK"];
668
- function registerKeyTools(server, gql) {
669
- server.registerTool(
670
- "list_keys",
671
- {
672
- title: "List keys",
673
- description: "Lists source keys. With `localeId` + `status` (untranslated | flagged | approved) the result `totalCount` is the pending count for that status \u2014 use it for the batch threshold.",
674
- inputSchema: {
675
- projectId: z6.string(),
676
- localeId: z6.string().optional(),
677
- status: z6.enum(["untranslated", "flagged", "approved"]).optional(),
678
- search: z6.string().optional(),
679
- skip: z6.number().int().min(0).default(0),
680
- take: z6.number().int().min(1).max(200).default(25)
681
- },
682
- annotations: { readOnlyHint: true, openWorldHint: true }
683
- },
684
- async (vars) => jsonResult((await gql.request(LIST_KEYS2, vars)).keys)
685
- );
686
- server.registerTool(
687
- "upsert_keys",
688
- {
689
- title: "Upsert keys (bulk)",
690
- description: "Bulk-upserts source keys by name (delta sync from local i18n files). Keys not listed are left untouched. Returns added/updated/unchanged counts; set `dryRun` to preview.",
691
- inputSchema: {
692
- projectId: z6.string(),
693
- keys: z6.array(
694
- z6.object({
695
- key: z6.string(),
696
- sourceText: z6.string(),
697
- context: z6.string().optional(),
698
- maxLength: z6.number().int().positive().optional()
699
- })
700
- ).min(1),
701
- dryRun: z6.boolean().default(false)
702
- },
703
- annotations: { idempotentHint: true, openWorldHint: true }
704
- },
705
- async ({ projectId, keys, dryRun }) => jsonResult((await gql.request(UPSERT_KEYS, { projectId, input: keys, dryRun })).upsertKeys)
706
- );
707
- server.registerTool(
708
- "update_key",
709
- {
710
- title: "Update key",
711
- description: "Updates a single key (source text, context, max length).",
712
- inputSchema: {
713
- projectId: z6.string(),
714
- keyId: z6.string(),
715
- sourceText: z6.string().optional(),
716
- context: z6.string().optional(),
717
- maxLength: z6.number().int().positive().optional()
718
- },
719
- annotations: { idempotentHint: true, openWorldHint: true }
720
- },
721
- async ({ projectId, keyId, ...input }) => jsonResult((await gql.request(UPDATE_KEY, { projectId, id: keyId, input })).updateKey)
722
- );
723
- server.registerTool(
724
- "delete_key",
725
- {
726
- title: "Delete key",
727
- description: "Deletes a key and its translations. Destructive \u2014 confirm first.",
728
- inputSchema: { projectId: z6.string(), keyId: z6.string() },
729
- annotations: { destructiveHint: true, openWorldHint: true }
730
- },
731
- async ({ projectId, keyId }) => jsonResult((await gql.request(DELETE_KEY, { projectId, id: keyId })).deleteKey)
732
- );
733
- server.registerTool(
734
- "import_source_strings",
735
- {
736
- title: "Import source strings",
737
- description: 'Imports source strings from ONE file (paste the raw content) and returns the diff summary. For initial setup of a repo-hosted project, PREFER connecting the repo and running `sync_repository` \u2014 it imports every source file server-side in one pass; use this tool only for file-based projects with no Git integration, or for one-off single-file imports. For a multi-file / namespaced project (a file per namespace, e.g. messages/en/home.json), pass `keyPrefix` (the namespace, e.g. "home") so keys become `home.title` and files with overlapping top-level keys do not collide \u2014 call once per file. Use `dryRun` to preview.',
738
- inputSchema: {
739
- projectId: z6.string(),
740
- content: z6.string(),
741
- format: z6.enum(FORMATS),
742
- keyPrefix: z6.string().optional().describe('Namespace prefix, e.g. "home" (per-file, multi-file projects).'),
743
- sourceFile: z6.string().optional().describe(
744
- 'Real source file path, e.g. "messages/en/home.json" \u2014 stored for the file tree + reliable export.'
745
- ),
746
- dryRun: z6.boolean().default(false)
747
- },
748
- annotations: { idempotentHint: true, openWorldHint: true }
749
- },
750
- async ({ projectId, content, format, keyPrefix, sourceFile, dryRun }) => jsonResult(
751
- (await gql.request(IMPORT_SOURCE_STRINGS, {
752
- projectId,
753
- input: { content, format, keyPrefix, sourceFile, dryRun }
754
- })).importSourceStrings
755
- )
756
- );
757
- }
758
-
759
- // src/tools/locales.ts
760
- import { z as z7 } from "zod";
761
- import { ADD_LOCALE, LIST_LOCALES as LIST_LOCALES2, REMOVE_LOCALE, SEED_STARTER_LOCALES } from "@syntholoc/sdk";
762
-
763
- // ../../packages/shared/src/locales.ts
764
- var STARTER_LOCALES = [
765
- { code: "ar", name: "Arabic", nativeName: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", rtl: true },
766
- { code: "az", name: "Azerbaijani", nativeName: "Az\u0259rbaycan" },
767
- { code: "be", name: "Belarusian", nativeName: "\u0411\u0435\u043B\u0430\u0440\u0443\u0441\u043A\u0430\u044F" },
768
- { code: "bn", name: "Bengali", nativeName: "\u09AC\u09BE\u0982\u09B2\u09BE" },
769
- { code: "cs", name: "Czech", nativeName: "\u010Ce\u0161tina" },
770
- { code: "de", name: "German", nativeName: "Deutsch" },
771
- { code: "el", name: "Greek", nativeName: "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC" },
772
- { code: "en", name: "English", nativeName: "English" },
773
- { code: "es", name: "Spanish", nativeName: "Espa\xF1ol" },
774
- { code: "es-419", name: "Spanish (Latin America)", nativeName: "Espa\xF1ol (Latinoam\xE9rica)" },
775
- { code: "fa", name: "Persian", nativeName: "\u0641\u0627\u0631\u0633\u06CC", rtl: true },
776
- { code: "fr", name: "French", nativeName: "Fran\xE7ais" },
777
- { code: "hi", name: "Hindi", nativeName: "\u0939\u093F\u0928\u094D\u0926\u0940" },
778
- { code: "hy", name: "Armenian", nativeName: "\u0540\u0561\u0575\u0565\u0580\u0565\u0576" },
779
- { code: "id", name: "Indonesian", nativeName: "Bahasa Indonesia" },
780
- { code: "it", name: "Italian", nativeName: "Italiano" },
781
- { code: "ja", name: "Japanese", nativeName: "\u65E5\u672C\u8A9E" },
782
- { code: "ka", name: "Georgian", nativeName: "\u10E5\u10D0\u10E0\u10D7\u10E3\u10DA\u10D8" },
783
- { code: "kk", name: "Kazakh", nativeName: "\u049A\u0430\u0437\u0430\u049B \u0442\u0456\u043B\u0456" },
784
- { code: "ko", name: "Korean", nativeName: "\uD55C\uAD6D\uC5B4" },
785
- { code: "lv", name: "Latvian", nativeName: "Latvie\u0161u" },
786
- { code: "ms", name: "Malay", nativeName: "Bahasa Melayu" },
787
- { code: "nl", name: "Dutch", nativeName: "Nederlands" },
788
- { code: "pl", name: "Polish", nativeName: "Polski" },
789
- { code: "pt-BR", name: "Portuguese (Brazil)", nativeName: "Portugu\xEAs (Brasil)" },
790
- { code: "pt-PT", name: "Portuguese (Portugal)", nativeName: "Portugu\xEAs (Portugal)" },
791
- { code: "ro", name: "Romanian", nativeName: "Rom\xE2n\u0103" },
792
- { code: "ru", name: "Russian", nativeName: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" },
793
- { code: "sk", name: "Slovak", nativeName: "Sloven\u010Dina" },
794
- { code: "sl", name: "Slovenian", nativeName: "Sloven\u0161\u010Dina" },
795
- { code: "sw", name: "Swahili", nativeName: "Kiswahili" },
796
- { code: "th", name: "Thai", nativeName: "\u0E44\u0E17\u0E22" },
797
- { code: "tl", name: "Filipino", nativeName: "Filipino" },
798
- { code: "tr", name: "Turkish", nativeName: "T\xFCrk\xE7e" },
799
- { code: "uk", name: "Ukrainian", nativeName: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430" },
800
- { code: "ur", name: "Urdu", nativeName: "\u0627\u0631\u062F\u0648", rtl: true },
801
- { code: "uz", name: "Uzbek", nativeName: "O\u02BBzbek" },
802
- { code: "vi", name: "Vietnamese", nativeName: "Ti\u1EBFng Vi\u1EC7t" },
803
- { code: "zh-CN", name: "Chinese (Simplified)", nativeName: "\u7B80\u4F53\u4E2D\u6587" },
804
- { code: "zh-TW", name: "Chinese (Traditional)", nativeName: "\u7E41\u9AD4\u4E2D\u6587" }
805
- ];
806
- var STARTER_LOCALE_CODES = STARTER_LOCALES.map((l) => l.code);
807
- var STARTER_BY_CODE = new Map(STARTER_LOCALES.map((l) => [l.code, l]));
808
- function findStarterLocale(code) {
809
- return STARTER_BY_CODE.get(code);
810
- }
811
-
812
- // ../../packages/shared/src/locale-resolve.ts
813
- var ALIASES = {
814
- cn: "zh-CN",
815
- "zh-hans": "zh-CN",
816
- "zh-hant": "zh-TW",
817
- jp: "ja",
818
- kr: "ko",
819
- ua: "uk",
820
- cz: "cs",
821
- gr: "el",
822
- iw: "he",
823
- // legacy Hebrew
824
- in: "id"
825
- // legacy Indonesian
826
- };
827
- var VARIANTS_BY_LANG = (() => {
828
- const map = /* @__PURE__ */ new Map();
829
- for (const { code } of STARTER_LOCALES) {
830
- const lang = code.split("-")[0].toLowerCase();
831
- (map.get(lang) ?? map.set(lang, []).get(lang)).push(code);
832
- }
833
- return map;
834
- })();
835
- function canonicalize(input) {
836
- try {
837
- return Intl.getCanonicalLocales(input)[0] ?? null;
838
- } catch {
839
- return null;
840
- }
841
- }
842
- function isRealLanguage(code) {
843
- try {
844
- const name = new Intl.DisplayNames(["en"], { type: "language" }).of(code);
845
- return !!name && name.toLowerCase() !== code.toLowerCase();
846
- } catch {
847
- return false;
848
- }
849
- }
850
- function resolveLocaleCode(raw) {
851
- const input = raw.trim();
852
- if (!input) {
853
- return { status: "unknown", input: raw, message: "Empty locale code." };
854
- }
855
- const alias = ALIASES[input.toLowerCase()];
856
- if (alias) {
857
- return { status: "normalized", code: alias, from: input, reason: `mapped "${input}" to catalog code "${alias}"` };
858
- }
859
- const canonical = canonicalize(input);
860
- if (!canonical) {
861
- return { status: "unknown", input, message: `"${input}" is not a valid BCP-47 locale code.` };
862
- }
863
- if (findStarterLocale(canonical)) {
864
- return canonical === input ? { status: "ok", code: canonical } : { status: "normalized", code: canonical, from: input, reason: `casing normalized to "${canonical}"` };
865
- }
866
- const lang = canonical.split("-")[0].toLowerCase();
867
- const hasRegionOrScript = canonical.includes("-");
868
- const variants = VARIANTS_BY_LANG.get(lang) ?? [];
869
- if (!hasRegionOrScript) {
870
- if (variants.length > 1) {
871
- return {
872
- status: "ambiguous",
873
- input,
874
- candidates: variants,
875
- message: `"${input}" is ambiguous \u2014 the catalog has ${variants.join(", ")}. Pick one.`
876
- };
877
- }
878
- if (variants.length === 1) {
879
- return {
880
- status: "normalized",
881
- code: variants[0],
882
- from: input,
883
- reason: `expanded "${input}" to "${variants[0]}"`
884
- };
885
- }
886
- return isRealLanguage(canonical) ? { status: "ok", code: canonical } : { status: "unknown", input, message: `"${input}" is not a recognized language.` };
887
- }
888
- if (!isRealLanguage(canonical)) {
889
- return { status: "unknown", input, message: `"${input}" is not a valid locale.` };
890
- }
891
- return canonical === input ? { status: "ok", code: canonical } : { status: "normalized", code: canonical, from: input, reason: `canonicalized to "${canonical}"` };
892
- }
893
- function resolveLocaleCodes(raw) {
894
- const out = { codes: [], normalized: [], rejected: [] };
895
- const seen = /* @__PURE__ */ new Set();
896
- for (const code of raw) {
897
- const res = resolveLocaleCode(code);
898
- if (res.status === "ambiguous") {
899
- out.rejected.push({ input: res.input, message: res.message, candidates: res.candidates });
900
- } else if (res.status === "unknown") {
901
- out.rejected.push({ input: res.input, message: res.message });
902
- } else {
903
- if (res.status === "normalized") out.normalized.push({ from: res.from, code: res.code, reason: res.reason });
904
- if (!seen.has(res.code)) {
905
- seen.add(res.code);
906
- out.codes.push(res.code);
907
- }
908
- }
909
- }
910
- return out;
911
- }
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
-
928
- // src/tools/locales.ts
929
- function registerLocaleTools(server, gql) {
930
- server.registerTool(
931
- "list_locales",
932
- {
933
- title: "List locales",
934
- description: "Lists the target locales configured for a project.",
935
- inputSchema: { projectId: z7.string() },
936
- annotations: { readOnlyHint: true, openWorldHint: true }
937
- },
938
- async ({ projectId }) => jsonResult((await gql.request(LIST_LOCALES2, { projectId })).locales)
939
- );
940
- server.registerTool(
941
- "add_locale",
942
- {
943
- title: "Add locale",
944
- description: 'Adds ONE target locale to a project. To add several at once, prefer seed_starter_locales (one call for the whole list). Codes are validated: aliases/casing are normalized (e.g. "cn" \u2192 "zh-CN"), and ambiguous macro-languages (e.g. "pt", "zh") are rejected with the concrete variants to choose from (pt-BR / pt-PT, zh-CN / zh-TW).',
945
- inputSchema: {
946
- projectId: z7.string(),
947
- // `code` and `locale` are aliases — accept either so the natural
948
- // `locale: "de-DE"` form works as well as `code`.
949
- code: z7.string().optional().describe('BCP-47 code, e.g. "de-DE" or "ru". Alias: `locale`.'),
950
- locale: z7.string().optional().describe("Alias for `code`."),
951
- name: z7.string().optional(),
952
- formality: z7.string().optional(),
953
- gender: z7.string().optional(),
954
- variant: z7.string().optional()
955
- },
956
- annotations: { openWorldHint: true }
957
- },
958
- async ({ projectId, code, locale, name, formality, gender, variant }) => {
959
- const resolvedCode = code ?? locale;
960
- if (!resolvedCode) {
961
- return jsonResult({
962
- error: 'Missing locale code. Pass `code` (BCP-47, e.g. "de-DE" or "ru"). To add several locales at once, use seed_starter_locales with a `codes` array.'
963
- });
964
- }
965
- const res = resolveLocaleCode(resolvedCode);
966
- if (res.status === "ambiguous") {
967
- return jsonResult({
968
- error: res.message,
969
- hint: `Retry add_locale with one of: ${res.candidates.join(", ")}.`,
970
- candidates: res.candidates
971
- });
972
- }
973
- if (res.status === "unknown") {
974
- return jsonResult({ error: res.message, hint: 'Use a valid BCP-47 code, e.g. "de-DE" or "zh-CN".' });
975
- }
976
- const input = { code: res.code, name, formality, gender, variant };
977
- const added = (await gql.request(ADD_LOCALE, { projectId, input })).addLocale;
978
- return res.status === "normalized" ? jsonResult({ ...added, note: `Interpreted "${res.from}" as "${res.code}" (${res.reason}).` }) : jsonResult(added);
979
- }
980
- );
981
- server.registerTool(
982
- "seed_starter_locales",
983
- {
984
- title: "Seed starter locales",
985
- description: 'Adds MULTIPLE target locales in one call \u2014 pass `codes` (BCP-47, e.g. ["ru","de-DE","fr"]), or omit for a common starter set. Prefer this over repeated add_locale calls. Codes are validated as a batch: if any are ambiguous (e.g. "pt", "zh") or invalid, NONE are added and the response lists what to fix; aliases like "cn" are normalized to "zh-CN".',
986
- inputSchema: {
987
- projectId: z7.string(),
988
- codes: z7.array(z7.string()).optional().describe('BCP-47 codes to add, e.g. ["ru","de-DE","fr"].')
989
- },
990
- annotations: { openWorldHint: true }
991
- },
992
- async ({ projectId, codes }) => {
993
- if (codes && codes.length > 0) {
994
- const resolved = resolveLocaleCodes(codes);
995
- if (resolved.rejected.length > 0) {
996
- return jsonResult({
997
- error: "Some locale codes are ambiguous or invalid; nothing was added.",
998
- hint: "Fix the codes below and retry. For ambiguous ones, pick a listed candidate.",
999
- rejected: resolved.rejected
1000
- });
1001
- }
1002
- const seeded = (await gql.request(SEED_STARTER_LOCALES, { projectId, codes: resolved.codes })).seedStarterLocales;
1003
- return resolved.normalized.length > 0 ? jsonResult({ locales: seeded, normalized: resolved.normalized }) : jsonResult(seeded);
1004
- }
1005
- return jsonResult((await gql.request(SEED_STARTER_LOCALES, { projectId, codes })).seedStarterLocales);
1006
- }
1007
- );
1008
- server.registerTool(
1009
- "remove_locale",
1010
- {
1011
- title: "Remove locale",
1012
- description: "Removes a target locale and its translations. Destructive \u2014 confirm first.",
1013
- inputSchema: { projectId: z7.string(), localeId: z7.string() },
1014
- annotations: { destructiveHint: true, openWorldHint: true }
1015
- },
1016
- async ({ projectId, localeId }) => jsonResult((await gql.request(REMOVE_LOCALE, { projectId, id: localeId })).removeLocale)
1017
- );
1018
- }
1019
-
1020
- // src/tools/projects.ts
1021
- import { z as z8 } from "zod";
1022
- import {
1023
- CREATE_PROJECT,
1024
- GET_PROJECT,
1025
- LIST_PROJECTS,
1026
- MY_ORGANIZATIONS as MY_ORGANIZATIONS2,
1027
- UPDATE_PROJECT
1028
- } from "@syntholoc/sdk";
1029
- function registerProjectTools(server, gql) {
1030
- server.registerTool(
1031
- "list_organizations",
1032
- {
1033
- title: "List organizations",
1034
- description: "Organizations you belong to (id, name, slug, your role). To act in a specific org, set the `syntholoc-org-id` request header (hosted transport); otherwise the earliest org is used.",
1035
- inputSchema: {},
1036
- annotations: { readOnlyHint: true, openWorldHint: true }
1037
- },
1038
- async () => jsonResult(
1039
- (await gql.request(MY_ORGANIZATIONS2)).myProjects.map((g) => ({
1040
- ...g.organization,
1041
- viewerRole: g.viewerRole
1042
- }))
1043
- )
1044
- );
1045
- server.registerTool(
1046
- "list_projects",
1047
- {
1048
- title: "List projects",
1049
- description: "Lists localization projects in the active organization (paginated).",
1050
- inputSchema: {
1051
- skip: z8.number().int().min(0).default(0),
1052
- take: z8.number().int().min(1).max(100).default(25)
1053
- },
1054
- annotations: { readOnlyHint: true, openWorldHint: true }
1055
- },
1056
- async ({ skip, take }) => jsonResult((await gql.request(LIST_PROJECTS, { skip, take })).projects)
1057
- );
1058
- server.registerTool(
1059
- "get_project",
1060
- {
1061
- title: "Get project",
1062
- description: "Project settings plus analytics, including per-locale progress with the `untranslated` count used to decide the batch threshold (see get_config).",
1063
- inputSchema: { projectId: z8.string() },
1064
- annotations: { readOnlyHint: true, openWorldHint: true }
1065
- },
1066
- async ({ projectId }) => jsonResult(await gql.request(GET_PROJECT, { id: projectId }))
1067
- );
1068
- server.registerTool(
1069
- "create_project",
1070
- {
1071
- title: "Create project",
1072
- description: 'Creates a localization project. `sourceLocale` defaults to "en".',
1073
- inputSchema: {
1074
- name: z8.string(),
1075
- sourceLocale: z8.string().default("en"),
1076
- description: z8.string().optional(),
1077
- context: z8.string().optional(),
1078
- requireReview: z8.boolean().optional(),
1079
- qaCriticEnabled: z8.boolean().optional()
1080
- },
1081
- annotations: { openWorldHint: true }
1082
- },
1083
- async (input) => jsonResult((await gql.request(CREATE_PROJECT, { input })).createProject)
1084
- );
1085
- server.registerTool(
1086
- "update_project",
1087
- {
1088
- title: "Update project",
1089
- description: "Updates project settings (name, source locale, review/QA flags, context).",
1090
- inputSchema: {
1091
- projectId: z8.string(),
1092
- name: z8.string().optional(),
1093
- sourceLocale: z8.string().optional(),
1094
- description: z8.string().optional(),
1095
- context: z8.string().optional(),
1096
- requireReview: z8.boolean().optional(),
1097
- qaCriticEnabled: z8.boolean().optional(),
1098
- exportApprovedOnly: z8.boolean().optional(),
1099
- sortKeysAlphabetically: z8.boolean().optional(),
1100
- status: z8.string().optional()
1101
- },
1102
- annotations: { idempotentHint: true, openWorldHint: true }
1103
- },
1104
- async ({ projectId, ...input }) => jsonResult((await gql.request(UPDATE_PROJECT, { id: projectId, input })).updateProject)
1105
- );
1106
- }
1107
-
1108
- // src/tools/providers.ts
1109
- import { z as z10 } from "zod";
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
1130
- function registerProviderTools(server, gql) {
1131
- server.registerTool(
1132
- "list_llm_providers",
1133
- {
1134
- title: "List LLM providers",
1135
- description: "Configured LLM providers for the org (never exposes the API key).",
1136
- inputSchema: {},
1137
- annotations: { readOnlyHint: true, openWorldHint: true }
1138
- },
1139
- async () => jsonResult((await gql.request(LIST_LLM_PROVIDERS)).llmProviders)
1140
- );
1141
- server.registerTool(
1142
- "configure_llm_provider",
1143
- {
1144
- title: "Configure LLM provider",
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.
1149
- inputSchema: {
1150
- ...ConfigureProviderInputSchema().shape,
1151
- type: z10.enum(["OPENROUTER", "OPENAI", "ANTHROPIC"])
1152
- },
1153
- annotations: { openWorldHint: true }
1154
- },
1155
- async (input) => jsonResult((await gql.request(CONFIGURE_LLM_PROVIDER, { input })).configureLlmProvider)
1156
- );
1157
- }
1158
-
1159
- // src/tools/qa.ts
1160
- import { z as z11 } from "zod";
1161
- import { LIST_QA_ISSUES as LIST_QA_ISSUES2, RUN_LOCALE_QA, RUN_QA } from "@syntholoc/sdk";
1162
- function registerQaTools(server, gql) {
1163
- server.registerTool(
1164
- "run_locale_qa",
1165
- {
1166
- title: "Run locale QA",
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
- },
1173
- annotations: { openWorldHint: true }
1174
- },
1175
- async (vars) => jsonResult((await gql.request(RUN_LOCALE_QA, vars)).runLocaleQa)
1176
- );
1177
- server.registerTool(
1178
- "run_qa",
1179
- {
1180
- title: "Run QA on a translation",
1181
- description: "Runs QA on one translation and returns its score and issues (synchronous).",
1182
- inputSchema: { projectId: z11.string(), translationId: z11.string() },
1183
- annotations: { openWorldHint: true }
1184
- },
1185
- async (vars) => jsonResult((await gql.request(RUN_QA, vars)).runQa)
1186
- );
1187
- server.registerTool(
1188
- "list_qa_issues",
1189
- {
1190
- title: "List QA issues",
1191
- description: "Open QA issues for a locale, plus the issue-type facets (filter tabs).",
1192
- inputSchema: {
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)
1198
- },
1199
- annotations: { readOnlyHint: true, openWorldHint: true }
1200
- },
1201
- async (vars) => jsonResult(await gql.request(LIST_QA_ISSUES2, vars))
1202
- );
1203
- }
1204
-
1205
- // src/tools/styleguides.ts
1206
- import { z as z12 } from "zod";
1207
- import { CREATE_STYLE_GUIDE, GENERATE_STYLE_GUIDE, LIST_STYLE_GUIDES as LIST_STYLE_GUIDES2 } from "@syntholoc/sdk";
1208
- function registerStyleGuideTools(server, gql) {
1209
- server.registerTool(
1210
- "list_style_guides",
1211
- {
1212
- title: "List style guides",
1213
- description: "Lists style guides (project-scoped if projectId given, else org-wide).",
1214
- inputSchema: { projectId: z12.string().optional() },
1215
- annotations: { readOnlyHint: true, openWorldHint: true }
1216
- },
1217
- async ({ projectId }) => jsonResult((await gql.request(LIST_STYLE_GUIDES2, { projectId })).styleGuides)
1218
- );
1219
- server.registerTool(
1220
- "generate_style_guide",
1221
- {
1222
- title: "Generate style guide (draft)",
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.",
1224
- inputSchema: { projectId: z12.string(), localeCode: z12.string().optional() },
1225
- annotations: { openWorldHint: true }
1226
- },
1227
- async ({ projectId, localeCode }) => jsonResult((await gql.request(GENERATE_STYLE_GUIDE, { input: { projectId, localeCode } })).generateStyleGuide)
1228
- );
1229
- server.registerTool(
1230
- "create_style_guide",
1231
- {
1232
- title: "Create style guide",
1233
- description: "Persists a style guide (tone, rules JSON, prompt fragment), project- or locale-scoped.",
1234
- inputSchema: {
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()
1241
- },
1242
- annotations: { openWorldHint: true }
1243
- },
1244
- async (input) => jsonResult((await gql.request(CREATE_STYLE_GUIDE, { input })).createStyleGuide)
1245
- );
1246
- }
1247
-
1248
- // src/tools/tm.ts
1249
- import { z as z13 } from "zod";
1250
- import { TM_ENTRIES, TM_SEARCH, TM_STATS as TM_STATS2 } from "@syntholoc/sdk";
1251
- function registerTmTools(server, gql) {
1252
- server.registerTool(
1253
- "tm_search",
1254
- {
1255
- title: "Search translation memory",
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.",
1257
- inputSchema: {
1258
- sourceText: z13.string(),
1259
- targetLocale: z13.string(),
1260
- projectId: z13.string().optional(),
1261
- limit: z13.number().int().min(1).max(20).default(5)
1262
- },
1263
- annotations: { readOnlyHint: true, openWorldHint: true }
1264
- },
1265
- async ({ sourceText, targetLocale, projectId, limit }) => jsonResult((await gql.request(TM_SEARCH, { sourceText, targetLocale, projectId, limit })).tmSearch)
1266
- );
1267
- server.registerTool(
1268
- "tm_stats",
1269
- {
1270
- title: "Translation memory stats",
1271
- description: "Entry counts per target locale (org-wide, or for a project with `projectId`).",
1272
- inputSchema: { projectId: z13.string().optional() },
1273
- annotations: { readOnlyHint: true, openWorldHint: true }
1274
- },
1275
- async ({ projectId }) => jsonResult((await gql.request(TM_STATS2, { projectId })).tmStats)
1276
- );
1277
- server.registerTool(
1278
- "list_tm",
1279
- {
1280
- title: "Browse translation memory",
1281
- description: "Lists stored TM entries (paginated, newest first), optionally filtered by project and locale.",
1282
- inputSchema: {
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)
1287
- },
1288
- annotations: { readOnlyHint: true, openWorldHint: true }
1289
- },
1290
- async (vars) => jsonResult((await gql.request(TM_ENTRIES, vars)).tmEntries)
1291
- );
1292
- }
1293
-
1294
- // src/tools/translations.ts
1295
- import { z as z14 } from "zod";
1296
- import {
1297
- APPROVE_TRANSLATION,
1298
- EXPORT_LOCALE,
1299
- IMPORT_TRANSLATIONS,
1300
- LIST_TRANSLATIONS,
1301
- REJECT_TRANSLATION,
1302
- SET_TRANSLATION,
1303
- TRANSLATE_KEY,
1304
- TRANSLATE_LOCALE
1305
- } from "@syntholoc/sdk";
1306
- var FORMATS2 = ["JSON", "YAML", "CSV", "PO", "XLIFF", "ANDROID_XML", "IOS_STRINGS", "NJK"];
1307
- function registerTranslationTools(server, gql) {
1308
- server.registerTool(
1309
- "translate_locale",
1310
- {
1311
- title: "Translate locale",
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.",
1313
- inputSchema: {
1314
- projectId: z14.string(),
1315
- localeId: z14.string(),
1316
- scope: z14.enum(["ALL", "UNAPPROVED", "UNTRANSLATED"]).default("UNTRANSLATED")
1317
- },
1318
- annotations: { openWorldHint: true }
1319
- },
1320
- async (vars) => jsonResult((await gql.request(TRANSLATE_LOCALE, vars)).translateLocale)
1321
- );
1322
- server.registerTool(
1323
- "translate_key",
1324
- {
1325
- title: "Translate key",
1326
- description: "Queues AI translation of a single key into a locale. Returns tracking `jobIds`.",
1327
- inputSchema: { projectId: z14.string(), keyId: z14.string(), localeId: z14.string() },
1328
- annotations: { openWorldHint: true }
1329
- },
1330
- async (vars) => jsonResult((await gql.request(TRANSLATE_KEY, vars)).translateKey)
1331
- );
1332
- server.registerTool(
1333
- "set_translation",
1334
- {
1335
- title: "Set translation",
1336
- description: "Manually sets the translation text for a key in a locale.",
1337
- inputSchema: {
1338
- projectId: z14.string(),
1339
- keyId: z14.string(),
1340
- localeId: z14.string(),
1341
- text: z14.string(),
1342
- origin: z14.string().optional()
1343
- },
1344
- annotations: { idempotentHint: true, openWorldHint: true }
1345
- },
1346
- async ({ projectId, keyId, localeId, text, origin }) => jsonResult(
1347
- (await gql.request(SET_TRANSLATION, { projectId, input: { keyId, localeId, text, origin } })).setTranslation
1348
- )
1349
- );
1350
- server.registerTool(
1351
- "list_translations",
1352
- {
1353
- title: "List translations",
1354
- description: "Lists translations for a locale with status, origin, model and QA score.",
1355
- inputSchema: {
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)
1360
- },
1361
- annotations: { readOnlyHint: true, openWorldHint: true }
1362
- },
1363
- async (vars) => jsonResult((await gql.request(LIST_TRANSLATIONS, vars)).translations)
1364
- );
1365
- server.registerTool(
1366
- "approve_translation",
1367
- {
1368
- title: "Approve translation",
1369
- description: "Marks a translation APPROVED.",
1370
- inputSchema: { projectId: z14.string(), translationId: z14.string() },
1371
- annotations: { idempotentHint: true, openWorldHint: true }
1372
- },
1373
- async ({ projectId, translationId }) => jsonResult((await gql.request(APPROVE_TRANSLATION, { projectId, id: translationId })).approveTranslation)
1374
- );
1375
- server.registerTool(
1376
- "reject_translation",
1377
- {
1378
- title: "Reject translation",
1379
- description: "Marks a translation REJECTED (queued for re-translation).",
1380
- inputSchema: { projectId: z14.string(), translationId: z14.string() },
1381
- annotations: { idempotentHint: true, openWorldHint: true }
1382
- },
1383
- async ({ projectId, translationId }) => jsonResult((await gql.request(REJECT_TRANSLATION, { projectId, id: translationId })).rejectTranslation)
1384
- );
1385
- server.registerTool(
1386
- "export_locale",
1387
- {
1388
- title: "Export locale",
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.',
1390
- inputSchema: {
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".')
1397
- },
1398
- annotations: { readOnlyHint: true, openWorldHint: true }
1399
- },
1400
- async (vars) => jsonResult({ content: (await gql.request(EXPORT_LOCALE, vars)).exportLocale })
1401
- );
1402
- server.registerTool(
1403
- "import_translations",
1404
- {
1405
- title: "Import translations",
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.",
1407
- inputSchema: {
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)
1416
- },
1417
- annotations: { idempotentHint: true, openWorldHint: true }
1418
- },
1419
- async ({ projectId, localeId, content, format, keyPrefix, autoApprove, conflict, dryRun }) => jsonResult(
1420
- (await gql.request(IMPORT_TRANSLATIONS, {
1421
- projectId,
1422
- localeId,
1423
- input: { content, format, keyPrefix, autoApprove, conflict, dryRun }
1424
- })).importTranslations
1425
- )
1426
- );
1427
- }
1428
-
1429
- // src/tools/index.ts
1430
- var TOOL_SETS = {
1431
- config: registerConfigTools,
1432
- projects: registerProjectTools,
1433
- locales: registerLocaleTools,
1434
- keys: registerKeyTools,
1435
- translations: registerTranslationTools,
1436
- qa: registerQaTools,
1437
- glossary: registerGlossaryTools,
1438
- styleguides: registerStyleGuideTools,
1439
- tm: registerTmTools,
1440
- git: registerGitTools,
1441
- providers: registerProviderTools,
1442
- jobs: registerJobTools,
1443
- analytics: registerAnalyticsTools
1444
- };
1445
- var TOOL_SET_NAMES = Object.keys(TOOL_SETS);
1446
- function selectSets(requested) {
1447
- if (!requested || requested.length === 0 || requested.includes("all")) return TOOL_SET_NAMES;
1448
- return TOOL_SET_NAMES.filter((name) => requested.includes(name));
1449
- }
1450
- function registerAllTools(server, gql, opts = {}) {
1451
- const target = applyToolFilter(server, opts);
1452
- for (const name of selectSets(opts.toolSets)) {
1453
- TOOL_SETS[name]?.(target, gql);
1454
- }
1455
- }
1456
-
1457
- // package.json
1458
- var version = "0.4.0";
1459
-
1460
- // src/build-server.ts
1461
- function buildMcpServer(gql, opts = {}) {
1462
- const server = new McpServer({ name: "syntholoc", version });
1463
- registerAllTools(server, gql, opts);
1464
- registerResources(server, gql);
1465
- registerPrompts(server);
1466
- return server;
1467
- }
1468
-
1469
- export {
1470
- parseList,
1471
- TOOL_SET_NAMES,
1472
- buildMcpServer
1473
- };