@syntholoc/mcp 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/build-server.d.ts +24 -2
- package/dist/build-server.js +7 -3
- package/dist/{chunk-RJ5GG3TT.js → chunk-DZKWHV2O.js} +334 -74
- 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) {
|
|
@@ -20,10 +49,15 @@ function registerPrompts(server) {
|
|
|
20
49
|
"1. Read `.syntholoc/config.json` for the source file(s) and project.",
|
|
21
50
|
projectId ? ` (Use project ${projectId} if config is missing.)` : "",
|
|
22
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.',
|
|
23
55
|
"3. Call `get_config` for `maxBulkSize`. For EACH target locale check `locale_progress` \u2192 `untranslated`.",
|
|
24
56
|
" - If untranslated >= maxBulkSize: `translate_locale` (scope UNTRANSLATED) and `wait_for_job`.",
|
|
25
57
|
" - If below the threshold: tell me the counts and ASK whether to translate now or accumulate.",
|
|
26
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.",
|
|
27
61
|
"5. Summarize keys added, locales translated, and the cost from the job usage."
|
|
28
62
|
].filter(Boolean).join("\n")
|
|
29
63
|
)
|
|
@@ -55,10 +89,21 @@ function registerPrompts(server) {
|
|
|
55
89
|
() => userMessage(
|
|
56
90
|
[
|
|
57
91
|
"Set up Syntholoc for this repository:",
|
|
58
|
-
"1. Find the i18n source files
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
92
|
+
"1. Find the i18n source files and infer the source locale. Note the LAYOUT:",
|
|
93
|
+
" - Single file per locale (e.g. messages/en.json) \u2192 one mapping, no namespace.",
|
|
94
|
+
" - A directory per locale with many namespace files (e.g. messages/en/home.json, common.json) \u2192",
|
|
95
|
+
" each file is a NAMESPACE. Syntholoc stores a flat key space, so import each file under a",
|
|
96
|
+
" `keyPrefix` = its namespace to avoid collisions between files.",
|
|
97
|
+
"2. `create_project`, then `seed_starter_locales` (or `add_locale`) for the target languages. Use valid",
|
|
98
|
+
' BCP-47 codes (e.g. "zh-CN", not "cn"); `add_locale` accepts `code` or `locale`.',
|
|
99
|
+
"3. Import the source strings:",
|
|
100
|
+
" - Single file \u2192 `import_source_strings` once.",
|
|
101
|
+
" - Namespaced \u2192 `import_source_strings` once PER FILE with `keyPrefix` set to the namespace",
|
|
102
|
+
" (filename without extension). Keys become e.g. `home.title`, `common.title`.",
|
|
103
|
+
"4. Write `.syntholoc/config.json` (schema https://syntholoc.com/schemas/config.json): the `projectId` and",
|
|
104
|
+
" a Crowdin-style `files` array of `{ source, translation }` patterns with a `{locale}` placeholder,",
|
|
105
|
+
' e.g. { "source": "messages/en/home.json", "translation": "messages/{locale}/home.json", "namespace": "home" }.',
|
|
106
|
+
' Do NOT invent fields like `i18nDir`, `format: "nested-dirs"`, or `localeMapping` \u2014 they are not read.',
|
|
62
107
|
"5. Offer to run the localize_changed flow next."
|
|
63
108
|
].join("\n")
|
|
64
109
|
)
|
|
@@ -67,7 +112,20 @@ function registerPrompts(server) {
|
|
|
67
112
|
|
|
68
113
|
// src/resources.ts
|
|
69
114
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
70
|
-
import {
|
|
115
|
+
import {
|
|
116
|
+
CONFIG_RESOURCE,
|
|
117
|
+
LIST_GLOSSARY,
|
|
118
|
+
LIST_JOBS,
|
|
119
|
+
LIST_KEYS,
|
|
120
|
+
LIST_LOCALES,
|
|
121
|
+
LIST_QA_ISSUES,
|
|
122
|
+
LIST_STYLE_GUIDES,
|
|
123
|
+
MY_ORGANIZATIONS,
|
|
124
|
+
PROJECT_ANALYTICS,
|
|
125
|
+
TM_STATS,
|
|
126
|
+
PROJECT_RESOURCE,
|
|
127
|
+
PROJECTS_RESOURCE
|
|
128
|
+
} from "@syntholoc/sdk";
|
|
71
129
|
function json(uri, data) {
|
|
72
130
|
return { contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) }] };
|
|
73
131
|
}
|
|
@@ -82,6 +140,16 @@ function registerResources(server, gql) {
|
|
|
82
140
|
},
|
|
83
141
|
async (uri) => json(uri, (await gql.request(CONFIG_RESOURCE)).appInfo)
|
|
84
142
|
);
|
|
143
|
+
server.registerResource(
|
|
144
|
+
"organizations",
|
|
145
|
+
"syntholoc://organizations",
|
|
146
|
+
{
|
|
147
|
+
title: "Organizations",
|
|
148
|
+
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.",
|
|
149
|
+
mimeType: "application/json"
|
|
150
|
+
},
|
|
151
|
+
async (uri) => json(uri, (await gql.request(MY_ORGANIZATIONS)).myProjects.map((g) => ({ ...g.organization, viewerRole: g.viewerRole })))
|
|
152
|
+
);
|
|
85
153
|
server.registerResource(
|
|
86
154
|
"projects",
|
|
87
155
|
"syntholoc://projects",
|
|
@@ -102,11 +170,99 @@ function registerResources(server, gql) {
|
|
|
102
170
|
},
|
|
103
171
|
async (uri, { projectId }) => json(uri, await gql.request(PROJECT_RESOURCE, { id: String(projectId) }))
|
|
104
172
|
);
|
|
173
|
+
server.registerResource(
|
|
174
|
+
"project-keys",
|
|
175
|
+
new ResourceTemplate("syntholoc://project/{projectId}/keys", { list: void 0 }),
|
|
176
|
+
{
|
|
177
|
+
title: "Project source keys",
|
|
178
|
+
description: "Source keys in a project (first 100).",
|
|
179
|
+
mimeType: "application/json"
|
|
180
|
+
},
|
|
181
|
+
async (uri, { projectId }) => json(uri, (await gql.request(LIST_KEYS, { projectId: String(projectId), skip: 0, take: 100 })).keys)
|
|
182
|
+
);
|
|
183
|
+
server.registerResource(
|
|
184
|
+
"project-locales",
|
|
185
|
+
new ResourceTemplate("syntholoc://project/{projectId}/locales", { list: void 0 }),
|
|
186
|
+
{
|
|
187
|
+
title: "Project locales",
|
|
188
|
+
description: "Target locales configured for a project.",
|
|
189
|
+
mimeType: "application/json"
|
|
190
|
+
},
|
|
191
|
+
async (uri, { projectId }) => json(uri, (await gql.request(LIST_LOCALES, { projectId: String(projectId) })).locales)
|
|
192
|
+
);
|
|
193
|
+
server.registerResource(
|
|
194
|
+
"project-progress",
|
|
195
|
+
new ResourceTemplate("syntholoc://project/{projectId}/progress", { list: void 0 }),
|
|
196
|
+
{
|
|
197
|
+
title: "Project progress",
|
|
198
|
+
description: "Per-locale translation progress and open QA issues for a project.",
|
|
199
|
+
mimeType: "application/json"
|
|
200
|
+
},
|
|
201
|
+
async (uri, { projectId }) => json(uri, (await gql.request(PROJECT_ANALYTICS, { projectId: String(projectId) })).projectAnalytics)
|
|
202
|
+
);
|
|
203
|
+
server.registerResource(
|
|
204
|
+
"project-jobs",
|
|
205
|
+
new ResourceTemplate("syntholoc://project/{projectId}/jobs", { list: void 0 }),
|
|
206
|
+
{
|
|
207
|
+
title: "Project jobs",
|
|
208
|
+
description: "Recent background jobs (translate / QA / git sync) for a project.",
|
|
209
|
+
mimeType: "application/json"
|
|
210
|
+
},
|
|
211
|
+
async (uri, { projectId }) => json(uri, (await gql.request(LIST_JOBS, { projectId: String(projectId), skip: 0, take: 25 })).jobs)
|
|
212
|
+
);
|
|
213
|
+
server.registerResource(
|
|
214
|
+
"project-qa-issues",
|
|
215
|
+
new ResourceTemplate("syntholoc://project/{projectId}/locale/{localeId}/qa-issues", { list: void 0 }),
|
|
216
|
+
{
|
|
217
|
+
title: "Locale QA issues",
|
|
218
|
+
description: "Open QA issues for a locale, with issue-type facets.",
|
|
219
|
+
mimeType: "application/json"
|
|
220
|
+
},
|
|
221
|
+
async (uri, { projectId, localeId }) => json(
|
|
222
|
+
uri,
|
|
223
|
+
await gql.request(LIST_QA_ISSUES, {
|
|
224
|
+
projectId: String(projectId),
|
|
225
|
+
localeId: String(localeId),
|
|
226
|
+
skip: 0,
|
|
227
|
+
take: 25
|
|
228
|
+
})
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
server.registerResource(
|
|
232
|
+
"glossary",
|
|
233
|
+
"syntholoc://glossary",
|
|
234
|
+
{
|
|
235
|
+
title: "Glossary",
|
|
236
|
+
description: "Organization-wide glossary terms (first 100).",
|
|
237
|
+
mimeType: "application/json"
|
|
238
|
+
},
|
|
239
|
+
async (uri) => json(uri, (await gql.request(LIST_GLOSSARY, { skip: 0, take: 100 })).glossaryTerms)
|
|
240
|
+
);
|
|
241
|
+
server.registerResource(
|
|
242
|
+
"style-guides",
|
|
243
|
+
"syntholoc://style-guides",
|
|
244
|
+
{
|
|
245
|
+
title: "Style guides",
|
|
246
|
+
description: "Organization-wide style guides.",
|
|
247
|
+
mimeType: "application/json"
|
|
248
|
+
},
|
|
249
|
+
async (uri) => json(uri, (await gql.request(LIST_STYLE_GUIDES, {})).styleGuides)
|
|
250
|
+
);
|
|
251
|
+
server.registerResource(
|
|
252
|
+
"tm",
|
|
253
|
+
"syntholoc://tm",
|
|
254
|
+
{
|
|
255
|
+
title: "Translation memory",
|
|
256
|
+
description: "Organization-wide Translation Memory coverage: entry counts per target locale.",
|
|
257
|
+
mimeType: "application/json"
|
|
258
|
+
},
|
|
259
|
+
async (uri) => json(uri, (await gql.request(TM_STATS, {})).tmStats)
|
|
260
|
+
);
|
|
105
261
|
}
|
|
106
262
|
|
|
107
263
|
// src/tools/analytics.ts
|
|
108
264
|
import { z as z2 } from "zod";
|
|
109
|
-
import { COST_ANALYTICS, LOCALE_PROGRESS, PROJECT_ANALYTICS, USAGE } from "@syntholoc/sdk";
|
|
265
|
+
import { COST_ANALYTICS, LOCALE_PROGRESS, PROJECT_ANALYTICS as PROJECT_ANALYTICS2, USAGE } from "@syntholoc/sdk";
|
|
110
266
|
|
|
111
267
|
// src/util.ts
|
|
112
268
|
function jsonResult(data) {
|
|
@@ -123,7 +279,7 @@ function registerAnalyticsTools(server, gql) {
|
|
|
123
279
|
inputSchema: { projectId: z2.string() },
|
|
124
280
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
125
281
|
},
|
|
126
|
-
async ({ projectId }) => jsonResult((await gql.request(
|
|
282
|
+
async ({ projectId }) => jsonResult((await gql.request(PROJECT_ANALYTICS2, { projectId })).projectAnalytics)
|
|
127
283
|
);
|
|
128
284
|
server.registerTool(
|
|
129
285
|
"cost_analytics",
|
|
@@ -238,7 +394,7 @@ import { z as z4 } from "zod";
|
|
|
238
394
|
import {
|
|
239
395
|
CREATE_GLOSSARY_TERM,
|
|
240
396
|
DELETE_GLOSSARY_TERM,
|
|
241
|
-
LIST_GLOSSARY,
|
|
397
|
+
LIST_GLOSSARY as LIST_GLOSSARY2,
|
|
242
398
|
SUGGEST_GLOSSARY_TERMS,
|
|
243
399
|
UPDATE_GLOSSARY_TERM
|
|
244
400
|
} from "@syntholoc/sdk";
|
|
@@ -255,7 +411,7 @@ function registerGlossaryTools(server, gql) {
|
|
|
255
411
|
},
|
|
256
412
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
257
413
|
},
|
|
258
|
-
async (vars) => jsonResult((await gql.request(
|
|
414
|
+
async (vars) => jsonResult((await gql.request(LIST_GLOSSARY2, vars)).glossaryTerms)
|
|
259
415
|
);
|
|
260
416
|
server.registerTool(
|
|
261
417
|
"upsert_glossary_term",
|
|
@@ -310,7 +466,7 @@ function registerGlossaryTools(server, gql) {
|
|
|
310
466
|
|
|
311
467
|
// src/tools/jobs.ts
|
|
312
468
|
import { z as z5 } from "zod";
|
|
313
|
-
import { GET_JOB, LIST_JOBS, waitForJob } from "@syntholoc/sdk";
|
|
469
|
+
import { GET_JOB, LIST_JOBS as LIST_JOBS2, waitForJob } from "@syntholoc/sdk";
|
|
314
470
|
function registerJobTools(server, gql) {
|
|
315
471
|
server.registerTool(
|
|
316
472
|
"get_job",
|
|
@@ -334,7 +490,7 @@ function registerJobTools(server, gql) {
|
|
|
334
490
|
},
|
|
335
491
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
336
492
|
},
|
|
337
|
-
async (vars) => jsonResult((await gql.request(
|
|
493
|
+
async (vars) => jsonResult((await gql.request(LIST_JOBS2, vars)).jobs)
|
|
338
494
|
);
|
|
339
495
|
server.registerTool(
|
|
340
496
|
"wait_for_job",
|
|
@@ -361,7 +517,7 @@ import { z as z6 } from "zod";
|
|
|
361
517
|
import {
|
|
362
518
|
DELETE_KEY,
|
|
363
519
|
IMPORT_SOURCE_STRINGS,
|
|
364
|
-
LIST_KEYS,
|
|
520
|
+
LIST_KEYS as LIST_KEYS2,
|
|
365
521
|
UPDATE_KEY,
|
|
366
522
|
UPSERT_KEYS
|
|
367
523
|
} from "@syntholoc/sdk";
|
|
@@ -382,7 +538,7 @@ function registerKeyTools(server, gql) {
|
|
|
382
538
|
},
|
|
383
539
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
384
540
|
},
|
|
385
|
-
async (vars) => jsonResult((await gql.request(
|
|
541
|
+
async (vars) => jsonResult((await gql.request(LIST_KEYS2, vars)).keys)
|
|
386
542
|
);
|
|
387
543
|
server.registerTool(
|
|
388
544
|
"upsert_keys",
|
|
@@ -435,24 +591,29 @@ function registerKeyTools(server, gql) {
|
|
|
435
591
|
"import_source_strings",
|
|
436
592
|
{
|
|
437
593
|
title: "Import source strings",
|
|
438
|
-
description:
|
|
594
|
+
description: 'Imports source strings from ONE file (paste the raw content) and returns the diff summary. 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.',
|
|
439
595
|
inputSchema: {
|
|
440
596
|
projectId: z6.string(),
|
|
441
597
|
content: z6.string(),
|
|
442
598
|
format: z6.enum(FORMATS),
|
|
599
|
+
keyPrefix: z6.string().optional().describe('Namespace prefix, e.g. "home" (per-file, multi-file projects).'),
|
|
600
|
+
sourceFile: z6.string().optional().describe('Real source file path, e.g. "messages/en/home.json" \u2014 stored for the file tree + reliable export.'),
|
|
443
601
|
dryRun: z6.boolean().default(false)
|
|
444
602
|
},
|
|
445
603
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
446
604
|
},
|
|
447
|
-
async ({ projectId, content, format, dryRun }) => jsonResult(
|
|
448
|
-
(await gql.request(IMPORT_SOURCE_STRINGS, {
|
|
605
|
+
async ({ projectId, content, format, keyPrefix, sourceFile, dryRun }) => jsonResult(
|
|
606
|
+
(await gql.request(IMPORT_SOURCE_STRINGS, {
|
|
607
|
+
projectId,
|
|
608
|
+
input: { content, format, keyPrefix, sourceFile, dryRun }
|
|
609
|
+
})).importSourceStrings
|
|
449
610
|
)
|
|
450
611
|
);
|
|
451
612
|
}
|
|
452
613
|
|
|
453
614
|
// src/tools/locales.ts
|
|
454
615
|
import { z as z7 } from "zod";
|
|
455
|
-
import { ADD_LOCALE, LIST_LOCALES, REMOVE_LOCALE, SEED_STARTER_LOCALES } from "@syntholoc/sdk";
|
|
616
|
+
import { ADD_LOCALE, LIST_LOCALES as LIST_LOCALES2, REMOVE_LOCALE, SEED_STARTER_LOCALES } from "@syntholoc/sdk";
|
|
456
617
|
function registerLocaleTools(server, gql) {
|
|
457
618
|
server.registerTool(
|
|
458
619
|
"list_locales",
|
|
@@ -462,16 +623,19 @@ function registerLocaleTools(server, gql) {
|
|
|
462
623
|
inputSchema: { projectId: z7.string() },
|
|
463
624
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
464
625
|
},
|
|
465
|
-
async ({ projectId }) => jsonResult((await gql.request(
|
|
626
|
+
async ({ projectId }) => jsonResult((await gql.request(LIST_LOCALES2, { projectId })).locales)
|
|
466
627
|
);
|
|
467
628
|
server.registerTool(
|
|
468
629
|
"add_locale",
|
|
469
630
|
{
|
|
470
631
|
title: "Add locale",
|
|
471
|
-
description: "Adds
|
|
632
|
+
description: "Adds ONE target locale to a project. To add several at once, prefer seed_starter_locales (one call for the whole list).",
|
|
472
633
|
inputSchema: {
|
|
473
634
|
projectId: z7.string(),
|
|
474
|
-
code
|
|
635
|
+
// `code` and `locale` are aliases — accept either so the natural
|
|
636
|
+
// `locale: "de-DE"` form works as well as `code`.
|
|
637
|
+
code: z7.string().optional().describe('BCP-47 code, e.g. "de-DE" or "ru". Alias: `locale`.'),
|
|
638
|
+
locale: z7.string().optional().describe("Alias for `code`."),
|
|
475
639
|
name: z7.string().optional(),
|
|
476
640
|
formality: z7.string().optional(),
|
|
477
641
|
gender: z7.string().optional(),
|
|
@@ -479,14 +643,26 @@ function registerLocaleTools(server, gql) {
|
|
|
479
643
|
},
|
|
480
644
|
annotations: { openWorldHint: true }
|
|
481
645
|
},
|
|
482
|
-
async ({ projectId,
|
|
646
|
+
async ({ projectId, code, locale, name, formality, gender, variant }) => {
|
|
647
|
+
const resolvedCode = code ?? locale;
|
|
648
|
+
if (!resolvedCode) {
|
|
649
|
+
return jsonResult({
|
|
650
|
+
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.'
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
const input = { code: resolvedCode, name, formality, gender, variant };
|
|
654
|
+
return jsonResult((await gql.request(ADD_LOCALE, { projectId, input })).addLocale);
|
|
655
|
+
}
|
|
483
656
|
);
|
|
484
657
|
server.registerTool(
|
|
485
658
|
"seed_starter_locales",
|
|
486
659
|
{
|
|
487
660
|
title: "Seed starter locales",
|
|
488
|
-
description:
|
|
489
|
-
inputSchema: {
|
|
661
|
+
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.',
|
|
662
|
+
inputSchema: {
|
|
663
|
+
projectId: z7.string(),
|
|
664
|
+
codes: z7.array(z7.string()).optional().describe('BCP-47 codes to add, e.g. ["ru","de-DE","fr"].')
|
|
665
|
+
},
|
|
490
666
|
annotations: { openWorldHint: true }
|
|
491
667
|
},
|
|
492
668
|
async ({ projectId, codes }) => jsonResult((await gql.request(SEED_STARTER_LOCALES, { projectId, codes })).seedStarterLocales)
|
|
@@ -505,8 +681,29 @@ function registerLocaleTools(server, gql) {
|
|
|
505
681
|
|
|
506
682
|
// src/tools/projects.ts
|
|
507
683
|
import { z as z8 } from "zod";
|
|
508
|
-
import {
|
|
684
|
+
import {
|
|
685
|
+
CREATE_PROJECT,
|
|
686
|
+
GET_PROJECT,
|
|
687
|
+
LIST_PROJECTS,
|
|
688
|
+
MY_ORGANIZATIONS as MY_ORGANIZATIONS2,
|
|
689
|
+
UPDATE_PROJECT
|
|
690
|
+
} from "@syntholoc/sdk";
|
|
509
691
|
function registerProjectTools(server, gql) {
|
|
692
|
+
server.registerTool(
|
|
693
|
+
"list_organizations",
|
|
694
|
+
{
|
|
695
|
+
title: "List organizations",
|
|
696
|
+
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.",
|
|
697
|
+
inputSchema: {},
|
|
698
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
699
|
+
},
|
|
700
|
+
async () => jsonResult(
|
|
701
|
+
(await gql.request(MY_ORGANIZATIONS2)).myProjects.map((g) => ({
|
|
702
|
+
...g.organization,
|
|
703
|
+
viewerRole: g.viewerRole
|
|
704
|
+
}))
|
|
705
|
+
)
|
|
706
|
+
);
|
|
510
707
|
server.registerTool(
|
|
511
708
|
"list_projects",
|
|
512
709
|
{
|
|
@@ -604,7 +801,7 @@ function registerProviderTools(server, gql) {
|
|
|
604
801
|
|
|
605
802
|
// src/tools/qa.ts
|
|
606
803
|
import { z as z10 } from "zod";
|
|
607
|
-
import { LIST_QA_ISSUES, RUN_LOCALE_QA, RUN_QA } from "@syntholoc/sdk";
|
|
804
|
+
import { LIST_QA_ISSUES as LIST_QA_ISSUES2, RUN_LOCALE_QA, RUN_QA } from "@syntholoc/sdk";
|
|
608
805
|
function registerQaTools(server, gql) {
|
|
609
806
|
server.registerTool(
|
|
610
807
|
"run_locale_qa",
|
|
@@ -640,13 +837,13 @@ function registerQaTools(server, gql) {
|
|
|
640
837
|
},
|
|
641
838
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
642
839
|
},
|
|
643
|
-
async (vars) => jsonResult(await gql.request(
|
|
840
|
+
async (vars) => jsonResult(await gql.request(LIST_QA_ISSUES2, vars))
|
|
644
841
|
);
|
|
645
842
|
}
|
|
646
843
|
|
|
647
844
|
// src/tools/styleguides.ts
|
|
648
845
|
import { z as z11 } from "zod";
|
|
649
|
-
import { CREATE_STYLE_GUIDE, GENERATE_STYLE_GUIDE, LIST_STYLE_GUIDES } from "@syntholoc/sdk";
|
|
846
|
+
import { CREATE_STYLE_GUIDE, GENERATE_STYLE_GUIDE, LIST_STYLE_GUIDES as LIST_STYLE_GUIDES2 } from "@syntholoc/sdk";
|
|
650
847
|
function registerStyleGuideTools(server, gql) {
|
|
651
848
|
server.registerTool(
|
|
652
849
|
"list_style_guides",
|
|
@@ -656,7 +853,7 @@ function registerStyleGuideTools(server, gql) {
|
|
|
656
853
|
inputSchema: { projectId: z11.string().optional() },
|
|
657
854
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
658
855
|
},
|
|
659
|
-
async ({ projectId }) => jsonResult((await gql.request(
|
|
856
|
+
async ({ projectId }) => jsonResult((await gql.request(LIST_STYLE_GUIDES2, { projectId })).styleGuides)
|
|
660
857
|
);
|
|
661
858
|
server.registerTool(
|
|
662
859
|
"generate_style_guide",
|
|
@@ -687,8 +884,54 @@ function registerStyleGuideTools(server, gql) {
|
|
|
687
884
|
);
|
|
688
885
|
}
|
|
689
886
|
|
|
690
|
-
// src/tools/
|
|
887
|
+
// src/tools/tm.ts
|
|
691
888
|
import { z as z12 } from "zod";
|
|
889
|
+
import { TM_ENTRIES, TM_SEARCH, TM_STATS as TM_STATS2 } from "@syntholoc/sdk";
|
|
890
|
+
function registerTmTools(server, gql) {
|
|
891
|
+
server.registerTool(
|
|
892
|
+
"tm_search",
|
|
893
|
+
{
|
|
894
|
+
title: "Search translation memory",
|
|
895
|
+
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.",
|
|
896
|
+
inputSchema: {
|
|
897
|
+
sourceText: z12.string(),
|
|
898
|
+
targetLocale: z12.string(),
|
|
899
|
+
projectId: z12.string().optional(),
|
|
900
|
+
limit: z12.number().int().min(1).max(20).default(5)
|
|
901
|
+
},
|
|
902
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
903
|
+
},
|
|
904
|
+
async ({ sourceText, targetLocale, projectId, limit }) => jsonResult((await gql.request(TM_SEARCH, { sourceText, targetLocale, projectId, limit })).tmSearch)
|
|
905
|
+
);
|
|
906
|
+
server.registerTool(
|
|
907
|
+
"tm_stats",
|
|
908
|
+
{
|
|
909
|
+
title: "Translation memory stats",
|
|
910
|
+
description: "Entry counts per target locale (org-wide, or for a project with `projectId`).",
|
|
911
|
+
inputSchema: { projectId: z12.string().optional() },
|
|
912
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
913
|
+
},
|
|
914
|
+
async ({ projectId }) => jsonResult((await gql.request(TM_STATS2, { projectId })).tmStats)
|
|
915
|
+
);
|
|
916
|
+
server.registerTool(
|
|
917
|
+
"list_tm",
|
|
918
|
+
{
|
|
919
|
+
title: "Browse translation memory",
|
|
920
|
+
description: "Lists stored TM entries (paginated, newest first), optionally filtered by project and locale.",
|
|
921
|
+
inputSchema: {
|
|
922
|
+
projectId: z12.string().optional(),
|
|
923
|
+
targetLocale: z12.string().optional(),
|
|
924
|
+
skip: z12.number().int().min(0).default(0),
|
|
925
|
+
take: z12.number().int().min(1).max(100).default(25)
|
|
926
|
+
},
|
|
927
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
928
|
+
},
|
|
929
|
+
async (vars) => jsonResult((await gql.request(TM_ENTRIES, vars)).tmEntries)
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/tools/translations.ts
|
|
934
|
+
import { z as z13 } from "zod";
|
|
692
935
|
import {
|
|
693
936
|
APPROVE_TRANSLATION,
|
|
694
937
|
EXPORT_LOCALE,
|
|
@@ -707,9 +950,9 @@ function registerTranslationTools(server, gql) {
|
|
|
707
950
|
title: "Translate locale",
|
|
708
951
|
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
952
|
inputSchema: {
|
|
710
|
-
projectId:
|
|
711
|
-
localeId:
|
|
712
|
-
scope:
|
|
953
|
+
projectId: z13.string(),
|
|
954
|
+
localeId: z13.string(),
|
|
955
|
+
scope: z13.enum(["ALL", "UNAPPROVED", "UNTRANSLATED"]).default("UNTRANSLATED")
|
|
713
956
|
},
|
|
714
957
|
annotations: { openWorldHint: true }
|
|
715
958
|
},
|
|
@@ -720,7 +963,7 @@ function registerTranslationTools(server, gql) {
|
|
|
720
963
|
{
|
|
721
964
|
title: "Translate key",
|
|
722
965
|
description: "Queues AI translation of a single key into a locale. Returns tracking `jobIds`.",
|
|
723
|
-
inputSchema: { projectId:
|
|
966
|
+
inputSchema: { projectId: z13.string(), keyId: z13.string(), localeId: z13.string() },
|
|
724
967
|
annotations: { openWorldHint: true }
|
|
725
968
|
},
|
|
726
969
|
async (vars) => jsonResult((await gql.request(TRANSLATE_KEY, vars)).translateKey)
|
|
@@ -731,11 +974,11 @@ function registerTranslationTools(server, gql) {
|
|
|
731
974
|
title: "Set translation",
|
|
732
975
|
description: "Manually sets the translation text for a key in a locale.",
|
|
733
976
|
inputSchema: {
|
|
734
|
-
projectId:
|
|
735
|
-
keyId:
|
|
736
|
-
localeId:
|
|
737
|
-
text:
|
|
738
|
-
origin:
|
|
977
|
+
projectId: z13.string(),
|
|
978
|
+
keyId: z13.string(),
|
|
979
|
+
localeId: z13.string(),
|
|
980
|
+
text: z13.string(),
|
|
981
|
+
origin: z13.string().optional()
|
|
739
982
|
},
|
|
740
983
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
741
984
|
},
|
|
@@ -749,10 +992,10 @@ function registerTranslationTools(server, gql) {
|
|
|
749
992
|
title: "List translations",
|
|
750
993
|
description: "Lists translations for a locale with status, origin, model and QA score.",
|
|
751
994
|
inputSchema: {
|
|
752
|
-
projectId:
|
|
753
|
-
localeId:
|
|
754
|
-
skip:
|
|
755
|
-
take:
|
|
995
|
+
projectId: z13.string(),
|
|
996
|
+
localeId: z13.string(),
|
|
997
|
+
skip: z13.number().int().min(0).default(0),
|
|
998
|
+
take: z13.number().int().min(1).max(200).default(25)
|
|
756
999
|
},
|
|
757
1000
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
758
1001
|
},
|
|
@@ -763,7 +1006,7 @@ function registerTranslationTools(server, gql) {
|
|
|
763
1006
|
{
|
|
764
1007
|
title: "Approve translation",
|
|
765
1008
|
description: "Marks a translation APPROVED.",
|
|
766
|
-
inputSchema: { projectId:
|
|
1009
|
+
inputSchema: { projectId: z13.string(), translationId: z13.string() },
|
|
767
1010
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
768
1011
|
},
|
|
769
1012
|
async ({ projectId, translationId }) => jsonResult((await gql.request(APPROVE_TRANSLATION, { projectId, id: translationId })).approveTranslation)
|
|
@@ -773,7 +1016,7 @@ function registerTranslationTools(server, gql) {
|
|
|
773
1016
|
{
|
|
774
1017
|
title: "Reject translation",
|
|
775
1018
|
description: "Marks a translation REJECTED (queued for re-translation).",
|
|
776
|
-
inputSchema: { projectId:
|
|
1019
|
+
inputSchema: { projectId: z13.string(), translationId: z13.string() },
|
|
777
1020
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
778
1021
|
},
|
|
779
1022
|
async ({ projectId, translationId }) => jsonResult((await gql.request(REJECT_TRANSLATION, { projectId, id: translationId })).rejectTranslation)
|
|
@@ -782,12 +1025,14 @@ function registerTranslationTools(server, gql) {
|
|
|
782
1025
|
"export_locale",
|
|
783
1026
|
{
|
|
784
1027
|
title: "Export locale",
|
|
785
|
-
description:
|
|
1028
|
+
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.',
|
|
786
1029
|
inputSchema: {
|
|
787
|
-
projectId:
|
|
788
|
-
localeId:
|
|
789
|
-
format:
|
|
790
|
-
onlyApproved:
|
|
1030
|
+
projectId: z13.string(),
|
|
1031
|
+
localeId: z13.string(),
|
|
1032
|
+
format: z13.enum(FORMATS2).default("JSON"),
|
|
1033
|
+
onlyApproved: z13.boolean().optional(),
|
|
1034
|
+
sourceFile: z13.string().optional().describe('Real source file path to export, e.g. "messages/en/home.json" (reliable, preferred).'),
|
|
1035
|
+
keyPrefix: z13.string().optional().describe('Legacy namespace prefix, e.g. "home".')
|
|
791
1036
|
},
|
|
792
1037
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
793
1038
|
},
|
|
@@ -797,53 +1042,68 @@ function registerTranslationTools(server, gql) {
|
|
|
797
1042
|
"import_translations",
|
|
798
1043
|
{
|
|
799
1044
|
title: "Import translations",
|
|
800
|
-
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.",
|
|
1045
|
+
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.",
|
|
801
1046
|
inputSchema: {
|
|
802
|
-
projectId:
|
|
803
|
-
localeId:
|
|
804
|
-
content:
|
|
805
|
-
format:
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1047
|
+
projectId: z13.string(),
|
|
1048
|
+
localeId: z13.string(),
|
|
1049
|
+
content: z13.string(),
|
|
1050
|
+
format: z13.enum(FORMATS2),
|
|
1051
|
+
keyPrefix: z13.string().optional().describe('Namespace prefix, e.g. "home" (per-file, multi-file projects).'),
|
|
1052
|
+
autoApprove: z13.boolean().default(false),
|
|
1053
|
+
conflict: z13.enum(["OVERWRITE", "SKIP_EXISTING", "KEEP_APPROVED"]).default("OVERWRITE"),
|
|
1054
|
+
dryRun: z13.boolean().default(false)
|
|
809
1055
|
},
|
|
810
1056
|
annotations: { idempotentHint: true, openWorldHint: true }
|
|
811
1057
|
},
|
|
812
|
-
async ({ projectId, localeId, content, format, autoApprove, conflict, dryRun }) => jsonResult(
|
|
1058
|
+
async ({ projectId, localeId, content, format, keyPrefix, autoApprove, conflict, dryRun }) => jsonResult(
|
|
813
1059
|
(await gql.request(IMPORT_TRANSLATIONS, {
|
|
814
1060
|
projectId,
|
|
815
1061
|
localeId,
|
|
816
|
-
input: { content, format, autoApprove, conflict, dryRun }
|
|
1062
|
+
input: { content, format, keyPrefix, autoApprove, conflict, dryRun }
|
|
817
1063
|
})).importTranslations
|
|
818
1064
|
)
|
|
819
1065
|
);
|
|
820
1066
|
}
|
|
821
1067
|
|
|
822
1068
|
// 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
|
-
|
|
1069
|
+
var TOOL_SETS = {
|
|
1070
|
+
config: registerConfigTools,
|
|
1071
|
+
projects: registerProjectTools,
|
|
1072
|
+
locales: registerLocaleTools,
|
|
1073
|
+
keys: registerKeyTools,
|
|
1074
|
+
translations: registerTranslationTools,
|
|
1075
|
+
qa: registerQaTools,
|
|
1076
|
+
glossary: registerGlossaryTools,
|
|
1077
|
+
styleguides: registerStyleGuideTools,
|
|
1078
|
+
tm: registerTmTools,
|
|
1079
|
+
git: registerGitTools,
|
|
1080
|
+
providers: registerProviderTools,
|
|
1081
|
+
jobs: registerJobTools,
|
|
1082
|
+
analytics: registerAnalyticsTools
|
|
1083
|
+
};
|
|
1084
|
+
var TOOL_SET_NAMES = Object.keys(TOOL_SETS);
|
|
1085
|
+
function selectSets(requested) {
|
|
1086
|
+
if (!requested || requested.length === 0 || requested.includes("all")) return TOOL_SET_NAMES;
|
|
1087
|
+
return TOOL_SET_NAMES.filter((name) => requested.includes(name));
|
|
1088
|
+
}
|
|
1089
|
+
function registerAllTools(server, gql, opts = {}) {
|
|
1090
|
+
const target = applyToolFilter(server, opts);
|
|
1091
|
+
for (const name of selectSets(opts.toolSets)) {
|
|
1092
|
+
TOOL_SETS[name]?.(target, gql);
|
|
1093
|
+
}
|
|
836
1094
|
}
|
|
837
1095
|
|
|
838
1096
|
// src/build-server.ts
|
|
839
|
-
function buildMcpServer(gql) {
|
|
1097
|
+
function buildMcpServer(gql, opts = {}) {
|
|
840
1098
|
const server = new McpServer({ name: "syntholoc", version: "0.1.0" });
|
|
841
|
-
registerAllTools(server, gql);
|
|
1099
|
+
registerAllTools(server, gql, opts);
|
|
842
1100
|
registerResources(server, gql);
|
|
843
1101
|
registerPrompts(server);
|
|
844
1102
|
return server;
|
|
845
1103
|
}
|
|
846
1104
|
|
|
847
1105
|
export {
|
|
1106
|
+
parseList,
|
|
1107
|
+
TOOL_SET_NAMES,
|
|
848
1108
|
buildMcpServer
|
|
849
1109
|
};
|
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-DZKWHV2O.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.
|
|
3
|
+
"version": "0.2.0",
|
|
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": {
|