@syntholoc/mcp 0.1.1 → 0.1.2
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 +28 -0
- package/dist/build-server.d.ts +24 -2
- package/dist/build-server.js +7 -3
- package/dist/{chunk-RJ5GG3TT.js → chunk-QOVGANRN.js} +279 -58
- package/dist/index.js +11 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,6 +22,34 @@ never passed as a tool argument.
|
|
|
22
22
|
|---|---|---|
|
|
23
23
|
| `SYNTHOLOC_API_KEY` | – | Machine API key (`x-api-key`). Required for most tools. |
|
|
24
24
|
| `SYNTHOLOC_API_URL` | `https://api.syntholoc.com/graphql` | GraphQL endpoint. Override to target a self-hosted or local instance. |
|
|
25
|
+
| `SYNTHOLOC_TOOL_SETS` | *(all)* | Comma-separated tool sets to load (`projects,keys,translations,qa,glossary,styleguides,tm,git,providers,jobs,analytics,locales,config`). Fewer tools = a more focused agent. |
|
|
26
|
+
| `SYNTHOLOC_READ_ONLY` | `false` | Set `true` to expose only read-only tools (list/get) — safe exploration and onboarding. |
|
|
27
|
+
| `SYNTHOLOC_TOOL_FILTER` | *(none)* | Comma-separated wildcard patterns matched against tool names (e.g. `list_*,get_*`). |
|
|
28
|
+
|
|
29
|
+
> The remote (HTTP) transport accepts the same controls as request headers:
|
|
30
|
+
> `syntholoc-tool-sets`, `syntholoc-read-only`, `syntholoc-tool-filter`. The hosted
|
|
31
|
+
> endpoint is versioned at `/mcp/v1` (the legacy `/mcp` path still works).
|
|
32
|
+
|
|
33
|
+
### Choosing an organization (hosted / OAuth)
|
|
34
|
+
|
|
35
|
+
An OAuth token is per-user and may belong to several organizations. By default the
|
|
36
|
+
earliest one is used. To pin a specific org, send the `syntholoc-org-id` header
|
|
37
|
+
(verified against your membership). List the ids with the `list_organizations` tool
|
|
38
|
+
or the `syntholoc://organizations` resource, then add it to your client config, e.g.:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"syntholoc": {
|
|
44
|
+
"type": "http",
|
|
45
|
+
"url": "https://api.syntholoc.com/mcp/v1",
|
|
46
|
+
"headers": { "syntholoc-org-id": "<org-id>" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
API-key (stdio) clients don't need this — the key is already scoped to one org.
|
|
25
53
|
|
|
26
54
|
## What you can do
|
|
27
55
|
|
package/dist/build-server.d.ts
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { GraphQLClient } from '@syntholoc/sdk';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Server-side tool-filtering options, mirroring Crowdin's `crowdin-tool-sets` /
|
|
6
|
+
* `crowdin-read-only` / `crowdin-tool-filter` headers. Applied before the tool
|
|
7
|
+
* list is returned so the agent only sees a focused surface.
|
|
8
|
+
*/
|
|
9
|
+
interface ToolFilterOptions {
|
|
10
|
+
/** Tool-set names to load (see TOOL_SET_NAMES). Empty / undefined / ['all'] loads everything. */
|
|
11
|
+
toolSets?: string[];
|
|
12
|
+
/** When true, only read-only tools (annotations.readOnlyHint) are exposed. */
|
|
13
|
+
readOnly?: boolean;
|
|
14
|
+
/** Wildcard patterns matched against tool names (e.g. "list_*", "project*"). */
|
|
15
|
+
toolFilter?: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Parses a comma-separated header/env value into a trimmed, non-empty string list. */
|
|
18
|
+
declare function parseList(value: string | undefined | null): string[] | undefined;
|
|
19
|
+
|
|
20
|
+
/** The valid tool-set names clients may request. */
|
|
21
|
+
declare const TOOL_SET_NAMES: string[];
|
|
22
|
+
|
|
4
23
|
/**
|
|
5
24
|
* Builds a fully-configured Syntholoc MCP server (tools + resources + prompts)
|
|
6
25
|
* bound to the given GraphQL client. Shared by the stdio entrypoint and the
|
|
7
26
|
* NestJS remote `/mcp` (Streamable HTTP) endpoint.
|
|
27
|
+
*
|
|
28
|
+
* `opts` optionally narrows the exposed tool surface (tool sets, read-only mode,
|
|
29
|
+
* name-pattern filter) so the agent sees only what a given client needs.
|
|
8
30
|
*/
|
|
9
|
-
declare function buildMcpServer(gql: GraphQLClient): McpServer;
|
|
31
|
+
declare function buildMcpServer(gql: GraphQLClient, opts?: ToolFilterOptions): McpServer;
|
|
10
32
|
|
|
11
|
-
export { buildMcpServer };
|
|
33
|
+
export { TOOL_SET_NAMES, type ToolFilterOptions, buildMcpServer, parseList };
|
package/dist/build-server.js
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
// src/build-server.ts
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
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
|
+
|
|
4
33
|
// src/prompts.ts
|
|
5
34
|
import { z } from "zod";
|
|
6
35
|
function userMessage(text) {
|
|
@@ -67,7 +96,20 @@ function registerPrompts(server) {
|
|
|
67
96
|
|
|
68
97
|
// src/resources.ts
|
|
69
98
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
70
|
-
import {
|
|
99
|
+
import {
|
|
100
|
+
CONFIG_RESOURCE,
|
|
101
|
+
LIST_GLOSSARY,
|
|
102
|
+
LIST_JOBS,
|
|
103
|
+
LIST_KEYS,
|
|
104
|
+
LIST_LOCALES,
|
|
105
|
+
LIST_QA_ISSUES,
|
|
106
|
+
LIST_STYLE_GUIDES,
|
|
107
|
+
MY_ORGANIZATIONS,
|
|
108
|
+
PROJECT_ANALYTICS,
|
|
109
|
+
TM_STATS,
|
|
110
|
+
PROJECT_RESOURCE,
|
|
111
|
+
PROJECTS_RESOURCE
|
|
112
|
+
} from "@syntholoc/sdk";
|
|
71
113
|
function json(uri, data) {
|
|
72
114
|
return { contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) }] };
|
|
73
115
|
}
|
|
@@ -82,6 +124,16 @@ function registerResources(server, gql) {
|
|
|
82
124
|
},
|
|
83
125
|
async (uri) => json(uri, (await gql.request(CONFIG_RESOURCE)).appInfo)
|
|
84
126
|
);
|
|
127
|
+
server.registerResource(
|
|
128
|
+
"organizations",
|
|
129
|
+
"syntholoc://organizations",
|
|
130
|
+
{
|
|
131
|
+
title: "Organizations",
|
|
132
|
+
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.",
|
|
133
|
+
mimeType: "application/json"
|
|
134
|
+
},
|
|
135
|
+
async (uri) => json(uri, (await gql.request(MY_ORGANIZATIONS)).myProjects.map((g) => ({ ...g.organization, viewerRole: g.viewerRole })))
|
|
136
|
+
);
|
|
85
137
|
server.registerResource(
|
|
86
138
|
"projects",
|
|
87
139
|
"syntholoc://projects",
|
|
@@ -102,11 +154,99 @@ function registerResources(server, gql) {
|
|
|
102
154
|
},
|
|
103
155
|
async (uri, { projectId }) => json(uri, await gql.request(PROJECT_RESOURCE, { id: String(projectId) }))
|
|
104
156
|
);
|
|
157
|
+
server.registerResource(
|
|
158
|
+
"project-keys",
|
|
159
|
+
new ResourceTemplate("syntholoc://project/{projectId}/keys", { list: void 0 }),
|
|
160
|
+
{
|
|
161
|
+
title: "Project source keys",
|
|
162
|
+
description: "Source keys in a project (first 100).",
|
|
163
|
+
mimeType: "application/json"
|
|
164
|
+
},
|
|
165
|
+
async (uri, { projectId }) => json(uri, (await gql.request(LIST_KEYS, { projectId: String(projectId), skip: 0, take: 100 })).keys)
|
|
166
|
+
);
|
|
167
|
+
server.registerResource(
|
|
168
|
+
"project-locales",
|
|
169
|
+
new ResourceTemplate("syntholoc://project/{projectId}/locales", { list: void 0 }),
|
|
170
|
+
{
|
|
171
|
+
title: "Project locales",
|
|
172
|
+
description: "Target locales configured for a project.",
|
|
173
|
+
mimeType: "application/json"
|
|
174
|
+
},
|
|
175
|
+
async (uri, { projectId }) => json(uri, (await gql.request(LIST_LOCALES, { projectId: String(projectId) })).locales)
|
|
176
|
+
);
|
|
177
|
+
server.registerResource(
|
|
178
|
+
"project-progress",
|
|
179
|
+
new ResourceTemplate("syntholoc://project/{projectId}/progress", { list: void 0 }),
|
|
180
|
+
{
|
|
181
|
+
title: "Project progress",
|
|
182
|
+
description: "Per-locale translation progress and open QA issues for a project.",
|
|
183
|
+
mimeType: "application/json"
|
|
184
|
+
},
|
|
185
|
+
async (uri, { projectId }) => json(uri, (await gql.request(PROJECT_ANALYTICS, { projectId: String(projectId) })).projectAnalytics)
|
|
186
|
+
);
|
|
187
|
+
server.registerResource(
|
|
188
|
+
"project-jobs",
|
|
189
|
+
new ResourceTemplate("syntholoc://project/{projectId}/jobs", { list: void 0 }),
|
|
190
|
+
{
|
|
191
|
+
title: "Project jobs",
|
|
192
|
+
description: "Recent background jobs (translate / QA / git sync) for a project.",
|
|
193
|
+
mimeType: "application/json"
|
|
194
|
+
},
|
|
195
|
+
async (uri, { projectId }) => json(uri, (await gql.request(LIST_JOBS, { projectId: String(projectId), skip: 0, take: 25 })).jobs)
|
|
196
|
+
);
|
|
197
|
+
server.registerResource(
|
|
198
|
+
"project-qa-issues",
|
|
199
|
+
new ResourceTemplate("syntholoc://project/{projectId}/locale/{localeId}/qa-issues", { list: void 0 }),
|
|
200
|
+
{
|
|
201
|
+
title: "Locale QA issues",
|
|
202
|
+
description: "Open QA issues for a locale, with issue-type facets.",
|
|
203
|
+
mimeType: "application/json"
|
|
204
|
+
},
|
|
205
|
+
async (uri, { projectId, localeId }) => json(
|
|
206
|
+
uri,
|
|
207
|
+
await gql.request(LIST_QA_ISSUES, {
|
|
208
|
+
projectId: String(projectId),
|
|
209
|
+
localeId: String(localeId),
|
|
210
|
+
skip: 0,
|
|
211
|
+
take: 25
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
server.registerResource(
|
|
216
|
+
"glossary",
|
|
217
|
+
"syntholoc://glossary",
|
|
218
|
+
{
|
|
219
|
+
title: "Glossary",
|
|
220
|
+
description: "Organization-wide glossary terms (first 100).",
|
|
221
|
+
mimeType: "application/json"
|
|
222
|
+
},
|
|
223
|
+
async (uri) => json(uri, (await gql.request(LIST_GLOSSARY, { skip: 0, take: 100 })).glossaryTerms)
|
|
224
|
+
);
|
|
225
|
+
server.registerResource(
|
|
226
|
+
"style-guides",
|
|
227
|
+
"syntholoc://style-guides",
|
|
228
|
+
{
|
|
229
|
+
title: "Style guides",
|
|
230
|
+
description: "Organization-wide style guides.",
|
|
231
|
+
mimeType: "application/json"
|
|
232
|
+
},
|
|
233
|
+
async (uri) => json(uri, (await gql.request(LIST_STYLE_GUIDES, {})).styleGuides)
|
|
234
|
+
);
|
|
235
|
+
server.registerResource(
|
|
236
|
+
"tm",
|
|
237
|
+
"syntholoc://tm",
|
|
238
|
+
{
|
|
239
|
+
title: "Translation memory",
|
|
240
|
+
description: "Organization-wide Translation Memory coverage: entry counts per target locale.",
|
|
241
|
+
mimeType: "application/json"
|
|
242
|
+
},
|
|
243
|
+
async (uri) => json(uri, (await gql.request(TM_STATS, {})).tmStats)
|
|
244
|
+
);
|
|
105
245
|
}
|
|
106
246
|
|
|
107
247
|
// src/tools/analytics.ts
|
|
108
248
|
import { z as z2 } from "zod";
|
|
109
|
-
import { COST_ANALYTICS, LOCALE_PROGRESS, PROJECT_ANALYTICS, USAGE } from "@syntholoc/sdk";
|
|
249
|
+
import { COST_ANALYTICS, LOCALE_PROGRESS, PROJECT_ANALYTICS as PROJECT_ANALYTICS2, USAGE } from "@syntholoc/sdk";
|
|
110
250
|
|
|
111
251
|
// src/util.ts
|
|
112
252
|
function jsonResult(data) {
|
|
@@ -123,7 +263,7 @@ function registerAnalyticsTools(server, gql) {
|
|
|
123
263
|
inputSchema: { projectId: z2.string() },
|
|
124
264
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
125
265
|
},
|
|
126
|
-
async ({ projectId }) => jsonResult((await gql.request(
|
|
266
|
+
async ({ projectId }) => jsonResult((await gql.request(PROJECT_ANALYTICS2, { projectId })).projectAnalytics)
|
|
127
267
|
);
|
|
128
268
|
server.registerTool(
|
|
129
269
|
"cost_analytics",
|
|
@@ -238,7 +378,7 @@ import { z as z4 } from "zod";
|
|
|
238
378
|
import {
|
|
239
379
|
CREATE_GLOSSARY_TERM,
|
|
240
380
|
DELETE_GLOSSARY_TERM,
|
|
241
|
-
LIST_GLOSSARY,
|
|
381
|
+
LIST_GLOSSARY as LIST_GLOSSARY2,
|
|
242
382
|
SUGGEST_GLOSSARY_TERMS,
|
|
243
383
|
UPDATE_GLOSSARY_TERM
|
|
244
384
|
} from "@syntholoc/sdk";
|
|
@@ -255,7 +395,7 @@ function registerGlossaryTools(server, gql) {
|
|
|
255
395
|
},
|
|
256
396
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
257
397
|
},
|
|
258
|
-
async (vars) => jsonResult((await gql.request(
|
|
398
|
+
async (vars) => jsonResult((await gql.request(LIST_GLOSSARY2, vars)).glossaryTerms)
|
|
259
399
|
);
|
|
260
400
|
server.registerTool(
|
|
261
401
|
"upsert_glossary_term",
|
|
@@ -310,7 +450,7 @@ function registerGlossaryTools(server, gql) {
|
|
|
310
450
|
|
|
311
451
|
// src/tools/jobs.ts
|
|
312
452
|
import { z as z5 } from "zod";
|
|
313
|
-
import { GET_JOB, LIST_JOBS, waitForJob } from "@syntholoc/sdk";
|
|
453
|
+
import { GET_JOB, LIST_JOBS as LIST_JOBS2, waitForJob } from "@syntholoc/sdk";
|
|
314
454
|
function registerJobTools(server, gql) {
|
|
315
455
|
server.registerTool(
|
|
316
456
|
"get_job",
|
|
@@ -334,7 +474,7 @@ function registerJobTools(server, gql) {
|
|
|
334
474
|
},
|
|
335
475
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
336
476
|
},
|
|
337
|
-
async (vars) => jsonResult((await gql.request(
|
|
477
|
+
async (vars) => jsonResult((await gql.request(LIST_JOBS2, vars)).jobs)
|
|
338
478
|
);
|
|
339
479
|
server.registerTool(
|
|
340
480
|
"wait_for_job",
|
|
@@ -361,7 +501,7 @@ import { z as z6 } from "zod";
|
|
|
361
501
|
import {
|
|
362
502
|
DELETE_KEY,
|
|
363
503
|
IMPORT_SOURCE_STRINGS,
|
|
364
|
-
LIST_KEYS,
|
|
504
|
+
LIST_KEYS as LIST_KEYS2,
|
|
365
505
|
UPDATE_KEY,
|
|
366
506
|
UPSERT_KEYS
|
|
367
507
|
} from "@syntholoc/sdk";
|
|
@@ -382,7 +522,7 @@ function registerKeyTools(server, gql) {
|
|
|
382
522
|
},
|
|
383
523
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
384
524
|
},
|
|
385
|
-
async (vars) => jsonResult((await gql.request(
|
|
525
|
+
async (vars) => jsonResult((await gql.request(LIST_KEYS2, vars)).keys)
|
|
386
526
|
);
|
|
387
527
|
server.registerTool(
|
|
388
528
|
"upsert_keys",
|
|
@@ -452,7 +592,7 @@ function registerKeyTools(server, gql) {
|
|
|
452
592
|
|
|
453
593
|
// src/tools/locales.ts
|
|
454
594
|
import { z as z7 } from "zod";
|
|
455
|
-
import { ADD_LOCALE, LIST_LOCALES, REMOVE_LOCALE, SEED_STARTER_LOCALES } from "@syntholoc/sdk";
|
|
595
|
+
import { ADD_LOCALE, LIST_LOCALES as LIST_LOCALES2, REMOVE_LOCALE, SEED_STARTER_LOCALES } from "@syntholoc/sdk";
|
|
456
596
|
function registerLocaleTools(server, gql) {
|
|
457
597
|
server.registerTool(
|
|
458
598
|
"list_locales",
|
|
@@ -462,7 +602,7 @@ function registerLocaleTools(server, gql) {
|
|
|
462
602
|
inputSchema: { projectId: z7.string() },
|
|
463
603
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
464
604
|
},
|
|
465
|
-
async ({ projectId }) => jsonResult((await gql.request(
|
|
605
|
+
async ({ projectId }) => jsonResult((await gql.request(LIST_LOCALES2, { projectId })).locales)
|
|
466
606
|
);
|
|
467
607
|
server.registerTool(
|
|
468
608
|
"add_locale",
|
|
@@ -505,8 +645,29 @@ function registerLocaleTools(server, gql) {
|
|
|
505
645
|
|
|
506
646
|
// src/tools/projects.ts
|
|
507
647
|
import { z as z8 } from "zod";
|
|
508
|
-
import {
|
|
648
|
+
import {
|
|
649
|
+
CREATE_PROJECT,
|
|
650
|
+
GET_PROJECT,
|
|
651
|
+
LIST_PROJECTS,
|
|
652
|
+
MY_ORGANIZATIONS as MY_ORGANIZATIONS2,
|
|
653
|
+
UPDATE_PROJECT
|
|
654
|
+
} from "@syntholoc/sdk";
|
|
509
655
|
function registerProjectTools(server, gql) {
|
|
656
|
+
server.registerTool(
|
|
657
|
+
"list_organizations",
|
|
658
|
+
{
|
|
659
|
+
title: "List organizations",
|
|
660
|
+
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.",
|
|
661
|
+
inputSchema: {},
|
|
662
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
663
|
+
},
|
|
664
|
+
async () => jsonResult(
|
|
665
|
+
(await gql.request(MY_ORGANIZATIONS2)).myProjects.map((g) => ({
|
|
666
|
+
...g.organization,
|
|
667
|
+
viewerRole: g.viewerRole
|
|
668
|
+
}))
|
|
669
|
+
)
|
|
670
|
+
);
|
|
510
671
|
server.registerTool(
|
|
511
672
|
"list_projects",
|
|
512
673
|
{
|
|
@@ -604,7 +765,7 @@ function registerProviderTools(server, gql) {
|
|
|
604
765
|
|
|
605
766
|
// src/tools/qa.ts
|
|
606
767
|
import { z as z10 } from "zod";
|
|
607
|
-
import { LIST_QA_ISSUES, RUN_LOCALE_QA, RUN_QA } from "@syntholoc/sdk";
|
|
768
|
+
import { LIST_QA_ISSUES as LIST_QA_ISSUES2, RUN_LOCALE_QA, RUN_QA } from "@syntholoc/sdk";
|
|
608
769
|
function registerQaTools(server, gql) {
|
|
609
770
|
server.registerTool(
|
|
610
771
|
"run_locale_qa",
|
|
@@ -640,13 +801,13 @@ function registerQaTools(server, gql) {
|
|
|
640
801
|
},
|
|
641
802
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
642
803
|
},
|
|
643
|
-
async (vars) => jsonResult(await gql.request(
|
|
804
|
+
async (vars) => jsonResult(await gql.request(LIST_QA_ISSUES2, vars))
|
|
644
805
|
);
|
|
645
806
|
}
|
|
646
807
|
|
|
647
808
|
// src/tools/styleguides.ts
|
|
648
809
|
import { z as z11 } from "zod";
|
|
649
|
-
import { CREATE_STYLE_GUIDE, GENERATE_STYLE_GUIDE, LIST_STYLE_GUIDES } from "@syntholoc/sdk";
|
|
810
|
+
import { CREATE_STYLE_GUIDE, GENERATE_STYLE_GUIDE, LIST_STYLE_GUIDES as LIST_STYLE_GUIDES2 } from "@syntholoc/sdk";
|
|
650
811
|
function registerStyleGuideTools(server, gql) {
|
|
651
812
|
server.registerTool(
|
|
652
813
|
"list_style_guides",
|
|
@@ -656,7 +817,7 @@ function registerStyleGuideTools(server, gql) {
|
|
|
656
817
|
inputSchema: { projectId: z11.string().optional() },
|
|
657
818
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
658
819
|
},
|
|
659
|
-
async ({ projectId }) => jsonResult((await gql.request(
|
|
820
|
+
async ({ projectId }) => jsonResult((await gql.request(LIST_STYLE_GUIDES2, { projectId })).styleGuides)
|
|
660
821
|
);
|
|
661
822
|
server.registerTool(
|
|
662
823
|
"generate_style_guide",
|
|
@@ -687,8 +848,54 @@ function registerStyleGuideTools(server, gql) {
|
|
|
687
848
|
);
|
|
688
849
|
}
|
|
689
850
|
|
|
690
|
-
// src/tools/
|
|
851
|
+
// src/tools/tm.ts
|
|
691
852
|
import { z as z12 } from "zod";
|
|
853
|
+
import { TM_ENTRIES, TM_SEARCH, TM_STATS as TM_STATS2 } from "@syntholoc/sdk";
|
|
854
|
+
function registerTmTools(server, gql) {
|
|
855
|
+
server.registerTool(
|
|
856
|
+
"tm_search",
|
|
857
|
+
{
|
|
858
|
+
title: "Search translation memory",
|
|
859
|
+
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.",
|
|
860
|
+
inputSchema: {
|
|
861
|
+
sourceText: z12.string(),
|
|
862
|
+
targetLocale: z12.string(),
|
|
863
|
+
projectId: z12.string().optional(),
|
|
864
|
+
limit: z12.number().int().min(1).max(20).default(5)
|
|
865
|
+
},
|
|
866
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
867
|
+
},
|
|
868
|
+
async ({ sourceText, targetLocale, projectId, limit }) => jsonResult((await gql.request(TM_SEARCH, { sourceText, targetLocale, projectId, limit })).tmSearch)
|
|
869
|
+
);
|
|
870
|
+
server.registerTool(
|
|
871
|
+
"tm_stats",
|
|
872
|
+
{
|
|
873
|
+
title: "Translation memory stats",
|
|
874
|
+
description: "Entry counts per target locale (org-wide, or for a project with `projectId`).",
|
|
875
|
+
inputSchema: { projectId: z12.string().optional() },
|
|
876
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
877
|
+
},
|
|
878
|
+
async ({ projectId }) => jsonResult((await gql.request(TM_STATS2, { projectId })).tmStats)
|
|
879
|
+
);
|
|
880
|
+
server.registerTool(
|
|
881
|
+
"list_tm",
|
|
882
|
+
{
|
|
883
|
+
title: "Browse translation memory",
|
|
884
|
+
description: "Lists stored TM entries (paginated, newest first), optionally filtered by project and locale.",
|
|
885
|
+
inputSchema: {
|
|
886
|
+
projectId: z12.string().optional(),
|
|
887
|
+
targetLocale: z12.string().optional(),
|
|
888
|
+
skip: z12.number().int().min(0).default(0),
|
|
889
|
+
take: z12.number().int().min(1).max(100).default(25)
|
|
890
|
+
},
|
|
891
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
892
|
+
},
|
|
893
|
+
async (vars) => jsonResult((await gql.request(TM_ENTRIES, vars)).tmEntries)
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/tools/translations.ts
|
|
898
|
+
import { z as z13 } from "zod";
|
|
692
899
|
import {
|
|
693
900
|
APPROVE_TRANSLATION,
|
|
694
901
|
EXPORT_LOCALE,
|
|
@@ -707,9 +914,9 @@ function registerTranslationTools(server, gql) {
|
|
|
707
914
|
title: "Translate locale",
|
|
708
915
|
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.",
|
|
709
916
|
inputSchema: {
|
|
710
|
-
projectId:
|
|
711
|
-
localeId:
|
|
712
|
-
scope:
|
|
917
|
+
projectId: z13.string(),
|
|
918
|
+
localeId: z13.string(),
|
|
919
|
+
scope: z13.enum(["ALL", "UNAPPROVED", "UNTRANSLATED"]).default("UNTRANSLATED")
|
|
713
920
|
},
|
|
714
921
|
annotations: { openWorldHint: true }
|
|
715
922
|
},
|
|
@@ -720,7 +927,7 @@ function registerTranslationTools(server, gql) {
|
|
|
720
927
|
{
|
|
721
928
|
title: "Translate key",
|
|
722
929
|
description: "Queues AI translation of a single key into a locale. Returns tracking `jobIds`.",
|
|
723
|
-
inputSchema: { projectId:
|
|
930
|
+
inputSchema: { projectId: z13.string(), keyId: z13.string(), localeId: z13.string() },
|
|
724
931
|
annotations: { openWorldHint: true }
|
|
725
932
|
},
|
|
726
933
|
async (vars) => jsonResult((await gql.request(TRANSLATE_KEY, vars)).translateKey)
|
|
@@ -731,11 +938,11 @@ function registerTranslationTools(server, gql) {
|
|
|
731
938
|
title: "Set translation",
|
|
732
939
|
description: "Manually sets the translation text for a key in a locale.",
|
|
733
940
|
inputSchema: {
|
|
734
|
-
projectId:
|
|
735
|
-
keyId:
|
|
736
|
-
localeId:
|
|
737
|
-
text:
|
|
738
|
-
origin:
|
|
941
|
+
projectId: z13.string(),
|
|
942
|
+
keyId: z13.string(),
|
|
943
|
+
localeId: z13.string(),
|
|
944
|
+
text: z13.string(),
|
|
945
|
+
origin: z13.string().optional()
|
|
739
946
|
},
|
|
740
947
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
741
948
|
},
|
|
@@ -749,10 +956,10 @@ function registerTranslationTools(server, gql) {
|
|
|
749
956
|
title: "List translations",
|
|
750
957
|
description: "Lists translations for a locale with status, origin, model and QA score.",
|
|
751
958
|
inputSchema: {
|
|
752
|
-
projectId:
|
|
753
|
-
localeId:
|
|
754
|
-
skip:
|
|
755
|
-
take:
|
|
959
|
+
projectId: z13.string(),
|
|
960
|
+
localeId: z13.string(),
|
|
961
|
+
skip: z13.number().int().min(0).default(0),
|
|
962
|
+
take: z13.number().int().min(1).max(200).default(25)
|
|
756
963
|
},
|
|
757
964
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
758
965
|
},
|
|
@@ -763,7 +970,7 @@ function registerTranslationTools(server, gql) {
|
|
|
763
970
|
{
|
|
764
971
|
title: "Approve translation",
|
|
765
972
|
description: "Marks a translation APPROVED.",
|
|
766
|
-
inputSchema: { projectId:
|
|
973
|
+
inputSchema: { projectId: z13.string(), translationId: z13.string() },
|
|
767
974
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
768
975
|
},
|
|
769
976
|
async ({ projectId, translationId }) => jsonResult((await gql.request(APPROVE_TRANSLATION, { projectId, id: translationId })).approveTranslation)
|
|
@@ -773,7 +980,7 @@ function registerTranslationTools(server, gql) {
|
|
|
773
980
|
{
|
|
774
981
|
title: "Reject translation",
|
|
775
982
|
description: "Marks a translation REJECTED (queued for re-translation).",
|
|
776
|
-
inputSchema: { projectId:
|
|
983
|
+
inputSchema: { projectId: z13.string(), translationId: z13.string() },
|
|
777
984
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
778
985
|
},
|
|
779
986
|
async ({ projectId, translationId }) => jsonResult((await gql.request(REJECT_TRANSLATION, { projectId, id: translationId })).rejectTranslation)
|
|
@@ -784,10 +991,10 @@ function registerTranslationTools(server, gql) {
|
|
|
784
991
|
title: "Export locale",
|
|
785
992
|
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.",
|
|
786
993
|
inputSchema: {
|
|
787
|
-
projectId:
|
|
788
|
-
localeId:
|
|
789
|
-
format:
|
|
790
|
-
onlyApproved:
|
|
994
|
+
projectId: z13.string(),
|
|
995
|
+
localeId: z13.string(),
|
|
996
|
+
format: z13.enum(FORMATS2).default("JSON"),
|
|
997
|
+
onlyApproved: z13.boolean().optional()
|
|
791
998
|
},
|
|
792
999
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
793
1000
|
},
|
|
@@ -799,13 +1006,13 @@ function registerTranslationTools(server, gql) {
|
|
|
799
1006
|
title: "Import translations",
|
|
800
1007
|
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.",
|
|
801
1008
|
inputSchema: {
|
|
802
|
-
projectId:
|
|
803
|
-
localeId:
|
|
804
|
-
content:
|
|
805
|
-
format:
|
|
806
|
-
autoApprove:
|
|
807
|
-
conflict:
|
|
808
|
-
dryRun:
|
|
1009
|
+
projectId: z13.string(),
|
|
1010
|
+
localeId: z13.string(),
|
|
1011
|
+
content: z13.string(),
|
|
1012
|
+
format: z13.enum(FORMATS2),
|
|
1013
|
+
autoApprove: z13.boolean().default(false),
|
|
1014
|
+
conflict: z13.enum(["OVERWRITE", "SKIP_EXISTING", "KEEP_APPROVED"]).default("OVERWRITE"),
|
|
1015
|
+
dryRun: z13.boolean().default(false)
|
|
809
1016
|
},
|
|
810
1017
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
811
1018
|
},
|
|
@@ -820,30 +1027,44 @@ function registerTranslationTools(server, gql) {
|
|
|
820
1027
|
}
|
|
821
1028
|
|
|
822
1029
|
// src/tools/index.ts
|
|
823
|
-
|
|
824
|
-
registerConfigTools
|
|
825
|
-
registerProjectTools
|
|
826
|
-
registerLocaleTools
|
|
827
|
-
registerKeyTools
|
|
828
|
-
registerTranslationTools
|
|
829
|
-
registerQaTools
|
|
830
|
-
registerGlossaryTools
|
|
831
|
-
registerStyleGuideTools
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1030
|
+
var TOOL_SETS = {
|
|
1031
|
+
config: registerConfigTools,
|
|
1032
|
+
projects: registerProjectTools,
|
|
1033
|
+
locales: registerLocaleTools,
|
|
1034
|
+
keys: registerKeyTools,
|
|
1035
|
+
translations: registerTranslationTools,
|
|
1036
|
+
qa: registerQaTools,
|
|
1037
|
+
glossary: registerGlossaryTools,
|
|
1038
|
+
styleguides: registerStyleGuideTools,
|
|
1039
|
+
tm: registerTmTools,
|
|
1040
|
+
git: registerGitTools,
|
|
1041
|
+
providers: registerProviderTools,
|
|
1042
|
+
jobs: registerJobTools,
|
|
1043
|
+
analytics: registerAnalyticsTools
|
|
1044
|
+
};
|
|
1045
|
+
var TOOL_SET_NAMES = Object.keys(TOOL_SETS);
|
|
1046
|
+
function selectSets(requested) {
|
|
1047
|
+
if (!requested || requested.length === 0 || requested.includes("all")) return TOOL_SET_NAMES;
|
|
1048
|
+
return TOOL_SET_NAMES.filter((name) => requested.includes(name));
|
|
1049
|
+
}
|
|
1050
|
+
function registerAllTools(server, gql, opts = {}) {
|
|
1051
|
+
const target = applyToolFilter(server, opts);
|
|
1052
|
+
for (const name of selectSets(opts.toolSets)) {
|
|
1053
|
+
TOOL_SETS[name]?.(target, gql);
|
|
1054
|
+
}
|
|
836
1055
|
}
|
|
837
1056
|
|
|
838
1057
|
// src/build-server.ts
|
|
839
|
-
function buildMcpServer(gql) {
|
|
1058
|
+
function buildMcpServer(gql, opts = {}) {
|
|
840
1059
|
const server = new McpServer({ name: "syntholoc", version: "0.1.0" });
|
|
841
|
-
registerAllTools(server, gql);
|
|
1060
|
+
registerAllTools(server, gql, opts);
|
|
842
1061
|
registerResources(server, gql);
|
|
843
1062
|
registerPrompts(server);
|
|
844
1063
|
return server;
|
|
845
1064
|
}
|
|
846
1065
|
|
|
847
1066
|
export {
|
|
1067
|
+
parseList,
|
|
1068
|
+
TOOL_SET_NAMES,
|
|
848
1069
|
buildMcpServer
|
|
849
1070
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
buildMcpServer
|
|
4
|
-
|
|
3
|
+
buildMcpServer,
|
|
4
|
+
parseList
|
|
5
|
+
} from "./chunk-QOVGANRN.js";
|
|
5
6
|
|
|
6
7
|
// src/index.ts
|
|
7
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -14,12 +15,19 @@ function loadConfig(env = process.env) {
|
|
|
14
15
|
apiKey: env.SYNTHOLOC_API_KEY
|
|
15
16
|
};
|
|
16
17
|
}
|
|
18
|
+
function loadToolOptions(env = process.env) {
|
|
19
|
+
return {
|
|
20
|
+
toolSets: parseList(env.SYNTHOLOC_TOOL_SETS),
|
|
21
|
+
readOnly: env.SYNTHOLOC_READ_ONLY === "true",
|
|
22
|
+
toolFilter: parseList(env.SYNTHOLOC_TOOL_FILTER)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
17
25
|
|
|
18
26
|
// src/index.ts
|
|
19
27
|
async function main() {
|
|
20
28
|
const config = loadConfig();
|
|
21
29
|
const gql = new GraphQLClient(config);
|
|
22
|
-
const server = buildMcpServer(gql);
|
|
30
|
+
const server = buildMcpServer(gql, loadToolOptions());
|
|
23
31
|
await server.connect(new StdioServerTransport());
|
|
24
32
|
console.error(`[syntholoc-mcp] ready (api: ${config.apiUrl}${config.apiKey ? "" : ", no API key set"})`);
|
|
25
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syntholoc/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Syntholoc MCP server — manage AI localization and translation from Claude Code.",
|
|
5
5
|
"homepage": "https://syntholoc.com",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@modelcontextprotocol/sdk": "1.22.0",
|
|
49
|
-
"@syntholoc/sdk": "0.1.
|
|
49
|
+
"@syntholoc/sdk": "0.1.1",
|
|
50
50
|
"zod": "3.25.76"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|