@kwonye/mcpx 0.1.91 → 0.1.93
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/dist/adapters/claude-desktop.d.ts +2 -1
- package/dist/adapters/claude-desktop.d.ts.map +1 -1
- package/dist/adapters/claude.d.ts +2 -1
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/codex.d.ts +2 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/cursor.d.ts +2 -1
- package/dist/adapters/cursor.d.ts.map +1 -1
- package/dist/adapters/kiro.d.ts +2 -1
- package/dist/adapters/kiro.d.ts.map +1 -1
- package/dist/adapters/qwen.d.ts +2 -1
- package/dist/adapters/qwen.d.ts.map +1 -1
- package/dist/adapters/vscode.d.ts +2 -1
- package/dist/adapters/vscode.d.ts.map +1 -1
- package/dist/{cli-qfvawh8e.js → cli-8vxva0cb.js} +1 -1
- package/dist/cli-kwyp9ywh.js +1948 -0
- package/dist/{cli-grafmdtc.js → cli-m2645syd.js} +58 -1653
- package/dist/{cli-btx8jwra.js → cli-np0wwh6m.js} +58 -4
- package/dist/cli-qvkpwy9w.js +904 -0
- package/dist/{cli-b83jnbz7.js → cli-y7ba5qvr.js} +57 -1652
- package/dist/{cli-zbbeadz8.js → cli-zjepj3sj.js} +7 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +143 -15
- package/dist/{config-nkgc4c4n.js → config-2s5nsm2g.js} +2 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/index.d.ts +9 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +58 -12
- package/dist/core/paths.d.ts +4 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/plugin-cache.d.ts +21 -0
- package/dist/core/plugin-cache.d.ts.map +1 -0
- package/dist/core/plugin-data.d.ts +29 -0
- package/dist/core/plugin-data.d.ts.map +1 -0
- package/dist/core/plugin-lifecycle.d.ts +8 -0
- package/dist/core/plugin-lifecycle.d.ts.map +1 -0
- package/dist/core/plugin-manager.d.ts +52 -0
- package/dist/core/plugin-manager.d.ts.map +1 -0
- package/dist/core/plugin-parse.d.ts +6 -0
- package/dist/core/plugin-parse.d.ts.map +1 -0
- package/dist/core/plugin-projections.d.ts +5 -0
- package/dist/core/plugin-projections.d.ts.map +1 -0
- package/dist/core/plugin-source.d.ts +5 -0
- package/dist/core/plugin-source.d.ts.map +1 -0
- package/dist/core/sync.d.ts.map +1 -1
- package/dist/plugin-manager-1sc4b6am.js +31 -0
- package/dist/{registry-vykv33et.js → registry-mztfy0cz.js} +2 -2
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +3 -2
|
@@ -1,73 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getGatewayUrl,
|
|
3
|
+
syncAllClients
|
|
4
|
+
} from "./cli-kwyp9ywh.js";
|
|
1
5
|
import {
|
|
2
6
|
loadConfig,
|
|
3
7
|
loadMergedConfig,
|
|
4
8
|
saveConfig
|
|
5
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-np0wwh6m.js";
|
|
6
10
|
import {
|
|
7
11
|
SecretsManager,
|
|
8
12
|
ensureGatewayToken
|
|
9
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-8vxva0cb.js";
|
|
10
14
|
import {
|
|
11
15
|
ensureDir,
|
|
12
16
|
ensureParentDir,
|
|
13
17
|
getLogPath,
|
|
14
|
-
getManagedIndexPath,
|
|
15
18
|
getPidPath,
|
|
16
|
-
getSkillsDir,
|
|
17
19
|
getStagedVersionPath,
|
|
18
20
|
getUpdateLockPath,
|
|
19
|
-
getUpdatesDir
|
|
20
|
-
|
|
21
|
-
readJsonFile,
|
|
22
|
-
sha256,
|
|
23
|
-
writeJsonAtomic
|
|
24
|
-
} from "./cli-zbbeadz8.js";
|
|
21
|
+
getUpdatesDir
|
|
22
|
+
} from "./cli-zjepj3sj.js";
|
|
25
23
|
import {
|
|
26
24
|
isServerEnabled
|
|
27
25
|
} from "./cli-s4gspqdf.js";
|
|
28
26
|
|
|
29
|
-
// src/core/skills.ts
|
|
30
|
-
import fs from "node:fs";
|
|
31
|
-
import path from "node:path";
|
|
32
|
-
function listSkills() {
|
|
33
|
-
const skillsDir = getSkillsDir();
|
|
34
|
-
if (!fs.existsSync(skillsDir)) {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
const files = fs.readdirSync(skillsDir);
|
|
38
|
-
const skills = [];
|
|
39
|
-
for (const file of files) {
|
|
40
|
-
if (file.endsWith(".md")) {
|
|
41
|
-
const id = path.basename(file, ".md");
|
|
42
|
-
const content = fs.readFileSync(path.join(skillsDir, file), "utf-8");
|
|
43
|
-
skills.push({ id, content });
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return skills;
|
|
47
|
-
}
|
|
48
|
-
function getSkill(id) {
|
|
49
|
-
const skillsDir = getSkillsDir();
|
|
50
|
-
const filePath = path.join(skillsDir, `${id}.md`);
|
|
51
|
-
if (!fs.existsSync(filePath)) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
55
|
-
return { id, content };
|
|
56
|
-
}
|
|
57
|
-
function saveSkill(id, content) {
|
|
58
|
-
const skillsDir = getSkillsDir();
|
|
59
|
-
ensureDir(skillsDir);
|
|
60
|
-
const filePath = path.join(skillsDir, `${id}.md`);
|
|
61
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
62
|
-
}
|
|
63
|
-
function deleteSkill(id) {
|
|
64
|
-
const skillsDir = getSkillsDir();
|
|
65
|
-
const filePath = path.join(skillsDir, `${id}.md`);
|
|
66
|
-
if (fs.existsSync(filePath)) {
|
|
67
|
-
fs.unlinkSync(filePath);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
27
|
// src/core/auth-probe.ts
|
|
72
28
|
var JSON_RPC_VERSION = "2.0";
|
|
73
29
|
var DEFAULT_AUTH_PROBE_TIMEOUT_MS = 8000;
|
|
@@ -289,1557 +245,6 @@ function mapRegistryToSpec(_name, option, resolvedValues) {
|
|
|
289
245
|
};
|
|
290
246
|
}
|
|
291
247
|
|
|
292
|
-
// src/adapters/claude.ts
|
|
293
|
-
import fs2 from "node:fs";
|
|
294
|
-
import path2 from "node:path";
|
|
295
|
-
import { z } from "zod";
|
|
296
|
-
|
|
297
|
-
// src/adapters/utils/index.ts
|
|
298
|
-
function getManagedEntryNames(managedIndex, clientId) {
|
|
299
|
-
return Object.keys(managedIndex.managed[clientId]?.entries ?? {});
|
|
300
|
-
}
|
|
301
|
-
function isManagedEntry(managedIndex, clientId, entryName) {
|
|
302
|
-
return Boolean(managedIndex.managed[clientId]?.entries?.[entryName]);
|
|
303
|
-
}
|
|
304
|
-
function pruneStaleManagedEntries(managedIndex, clientId, servers, keepEntryNames) {
|
|
305
|
-
const keep = new Set(keepEntryNames);
|
|
306
|
-
const managedNames = getManagedEntryNames(managedIndex, clientId);
|
|
307
|
-
for (const name of managedNames) {
|
|
308
|
-
if (!keep.has(name)) {
|
|
309
|
-
delete servers[name];
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
for (const name of Object.keys(servers)) {
|
|
313
|
-
if (!keep.has(name) && name.endsWith(" (mcpx)")) {
|
|
314
|
-
delete servers[name];
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
function ensureManagedEntryWritable(managedIndex, clientId, entryName, existingValue) {
|
|
319
|
-
if (existingValue === undefined || existingValue === null) {
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
if (isManagedEntry(managedIndex, clientId, entryName)) {
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
return `Cannot sync managed entry "${entryName}" because an unmanaged entry already exists.`;
|
|
326
|
-
}
|
|
327
|
-
function setManagedEntries(managedIndex, clientId, configPath, entries) {
|
|
328
|
-
if (!managedIndex.managed[clientId]) {
|
|
329
|
-
managedIndex.managed[clientId] = {
|
|
330
|
-
configPath,
|
|
331
|
-
entries: {}
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
managedIndex.managed[clientId].configPath = configPath;
|
|
335
|
-
managedIndex.managed[clientId].entries = Object.fromEntries(Object.entries(entries).map(([entryName, serializedEntry]) => [entryName, {
|
|
336
|
-
fingerprint: sha256(serializedEntry),
|
|
337
|
-
lastSyncedAt: new Date().toISOString()
|
|
338
|
-
}]));
|
|
339
|
-
}
|
|
340
|
-
function okResult(clientId, configPath, message) {
|
|
341
|
-
return {
|
|
342
|
-
clientId,
|
|
343
|
-
status: "SYNCED",
|
|
344
|
-
configPath,
|
|
345
|
-
message
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
function errorResult(clientId, configPath, message) {
|
|
349
|
-
return {
|
|
350
|
-
clientId,
|
|
351
|
-
status: "ERROR",
|
|
352
|
-
configPath,
|
|
353
|
-
message
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
function managedGatewayEntryName(serverName) {
|
|
357
|
-
return `${serverName} (mcpx)`;
|
|
358
|
-
}
|
|
359
|
-
function isManagedGatewayProjection(entryName) {
|
|
360
|
-
return entryName.endsWith(" (mcpx)");
|
|
361
|
-
}
|
|
362
|
-
function sortRecord(record) {
|
|
363
|
-
if (!record || Object.keys(record).length === 0) {
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
367
|
-
}
|
|
368
|
-
function normalizeServerSpec(spec) {
|
|
369
|
-
if (spec.transport === "http") {
|
|
370
|
-
return {
|
|
371
|
-
transport: "http",
|
|
372
|
-
url: spec.url,
|
|
373
|
-
headers: sortRecord(spec.headers)
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
return {
|
|
377
|
-
transport: "stdio",
|
|
378
|
-
command: spec.command,
|
|
379
|
-
args: spec.args && spec.args.length > 0 ? [...spec.args] : undefined,
|
|
380
|
-
env: sortRecord(spec.env),
|
|
381
|
-
cwd: spec.cwd
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
function serializeServerSpec(spec) {
|
|
385
|
-
return JSON.stringify(normalizeServerSpec(spec));
|
|
386
|
-
}
|
|
387
|
-
function serverSpecsEqual(left, right) {
|
|
388
|
-
return serializeServerSpec(left) === serializeServerSpec(right);
|
|
389
|
-
}
|
|
390
|
-
function emptyImportScan(clientId, configPath) {
|
|
391
|
-
return {
|
|
392
|
-
clientId,
|
|
393
|
-
configPath,
|
|
394
|
-
candidates: [],
|
|
395
|
-
skipped: []
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
function buildImportSkip(clientId, sourceEntryName, reason, configPath, serverName = sourceEntryName) {
|
|
399
|
-
return {
|
|
400
|
-
clientId,
|
|
401
|
-
configPath,
|
|
402
|
-
sourceEntryName,
|
|
403
|
-
serverName,
|
|
404
|
-
reason
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
function removeSourceEntries(entries, names) {
|
|
408
|
-
for (const name of names ?? []) {
|
|
409
|
-
delete entries[name];
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
function syncDisabledMcpServersArray(raw, managedNames, managedEntries) {
|
|
413
|
-
const existingDisabled = (raw.disabledMcpServers ?? []).filter((name) => !managedNames.includes(name));
|
|
414
|
-
const disabledManaged = managedEntries.filter((entry) => !entry.enabled).map((entry) => entry.name);
|
|
415
|
-
raw.disabledMcpServers = [...existingDisabled, ...disabledManaged];
|
|
416
|
-
}
|
|
417
|
-
function syncMcpExcludedArray(raw, managedNames, managedEntries) {
|
|
418
|
-
const mcp = raw.mcp ?? {};
|
|
419
|
-
const existingExcluded = (mcp.excluded ?? []).filter((name) => !managedNames.includes(name));
|
|
420
|
-
const disabledManaged = managedEntries.filter((entry) => !entry.enabled).map((entry) => entry.name);
|
|
421
|
-
mcp.excluded = [...existingExcluded, ...disabledManaged];
|
|
422
|
-
raw.mcp = mcp;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/adapters/claude.ts
|
|
426
|
-
function normalizeProjectKey(p) {
|
|
427
|
-
try {
|
|
428
|
-
return fs2.realpathSync(path2.resolve(p));
|
|
429
|
-
} catch {
|
|
430
|
-
return path2.resolve(p);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
function syncProjectDisabledArray(project, managedNames, disabledForThisProject) {
|
|
434
|
-
const existingNonManaged = (project.disabledMcpServers ?? []).filter((name) => !managedNames.includes(name));
|
|
435
|
-
const combined = [...existingNonManaged, ...disabledForThisProject];
|
|
436
|
-
if (combined.length > 0) {
|
|
437
|
-
project.disabledMcpServers = combined;
|
|
438
|
-
} else {
|
|
439
|
-
delete project.disabledMcpServers;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
var stringMapSchema = z.record(z.string(), z.string());
|
|
443
|
-
var claudeEntrySchema = z.object({
|
|
444
|
-
type: z.string().optional(),
|
|
445
|
-
url: z.string().min(1).optional(),
|
|
446
|
-
headers: stringMapSchema.optional(),
|
|
447
|
-
command: z.string().min(1).optional(),
|
|
448
|
-
args: z.array(z.string()).optional(),
|
|
449
|
-
env: stringMapSchema.optional(),
|
|
450
|
-
cwd: z.string().min(1).optional(),
|
|
451
|
-
disabled: z.boolean().optional()
|
|
452
|
-
}).passthrough();
|
|
453
|
-
|
|
454
|
-
class ClaudeAdapter {
|
|
455
|
-
id = "claude";
|
|
456
|
-
detectConfigPath() {
|
|
457
|
-
return path2.join(homeDir(), ".claude.json");
|
|
458
|
-
}
|
|
459
|
-
supportsHttp() {
|
|
460
|
-
return true;
|
|
461
|
-
}
|
|
462
|
-
scanForImports(_config, managedIndex) {
|
|
463
|
-
const configPath = this.detectConfigPath();
|
|
464
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
465
|
-
if (!configPath) {
|
|
466
|
-
return result;
|
|
467
|
-
}
|
|
468
|
-
const raw = readJsonFile(configPath, {});
|
|
469
|
-
const topLevelServers = raw.mcpServers ?? {};
|
|
470
|
-
const disabledMcpServers = new Set(raw.disabledMcpServers ?? []);
|
|
471
|
-
const projects = raw.projects ?? {};
|
|
472
|
-
for (const project of Object.values(projects)) {
|
|
473
|
-
const projectDisabled = project?.disabledMcpServers ?? [];
|
|
474
|
-
for (const name of projectDisabled) {
|
|
475
|
-
disabledMcpServers.add(name);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
for (const [name, rawEntry] of Object.entries(topLevelServers)) {
|
|
479
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
const parsed = claudeEntrySchema.safeParse(rawEntry);
|
|
483
|
-
if (!parsed.success) {
|
|
484
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
const entry = parsed.data;
|
|
488
|
-
const isDisabled = entry.disabled === true || disabledMcpServers.has(name);
|
|
489
|
-
if ((entry.type === undefined || entry.type === "http" || entry.type === "streamable-http") && entry.url && !entry.command) {
|
|
490
|
-
result.candidates.push({
|
|
491
|
-
clientId: this.id,
|
|
492
|
-
configPath,
|
|
493
|
-
sourceEntryName: name,
|
|
494
|
-
serverName: name,
|
|
495
|
-
spec: {
|
|
496
|
-
transport: "http",
|
|
497
|
-
url: entry.url,
|
|
498
|
-
headers: entry.headers,
|
|
499
|
-
enabled: !isDisabled
|
|
500
|
-
}
|
|
501
|
-
});
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
if ((entry.type === undefined || entry.type === "stdio") && entry.command && !entry.url) {
|
|
505
|
-
result.candidates.push({
|
|
506
|
-
clientId: this.id,
|
|
507
|
-
configPath,
|
|
508
|
-
sourceEntryName: name,
|
|
509
|
-
serverName: name,
|
|
510
|
-
spec: {
|
|
511
|
-
transport: "stdio",
|
|
512
|
-
command: entry.command,
|
|
513
|
-
args: entry.args,
|
|
514
|
-
env: entry.env,
|
|
515
|
-
cwd: entry.cwd,
|
|
516
|
-
enabled: !isDisabled
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
522
|
-
}
|
|
523
|
-
return result;
|
|
524
|
-
}
|
|
525
|
-
syncGateway(_config, options) {
|
|
526
|
-
const configPath = this.detectConfigPath();
|
|
527
|
-
if (!configPath) {
|
|
528
|
-
return errorResult(this.id, undefined, "Unable to resolve Claude config path.");
|
|
529
|
-
}
|
|
530
|
-
try {
|
|
531
|
-
const raw = readJsonFile(configPath, {});
|
|
532
|
-
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
533
|
-
const enabledEntries = options.managedEntries.filter((entry) => entry.enabled);
|
|
534
|
-
const serverEntries = Object.fromEntries(enabledEntries.map((entry) => [entry.name, {
|
|
535
|
-
type: "http",
|
|
536
|
-
url: entry.url,
|
|
537
|
-
headers: entry.headers
|
|
538
|
-
}]));
|
|
539
|
-
const enabledManagedNames = enabledEntries.map((entry) => entry.name);
|
|
540
|
-
const topLevelServers = raw.mcpServers ?? {};
|
|
541
|
-
removeSourceEntries(topLevelServers, options.sourceEntriesToRemove);
|
|
542
|
-
for (const name of enabledManagedNames) {
|
|
543
|
-
const topLevelConflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, topLevelServers[name]);
|
|
544
|
-
if (topLevelConflict) {
|
|
545
|
-
return errorResult(this.id, configPath, topLevelConflict);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, topLevelServers, enabledManagedNames);
|
|
549
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
550
|
-
topLevelServers[name] = entry;
|
|
551
|
-
}
|
|
552
|
-
raw.mcpServers = topLevelServers;
|
|
553
|
-
syncDisabledMcpServersArray(raw, managedNames, options.managedEntries);
|
|
554
|
-
const scopeByNormalizedPath = new Map;
|
|
555
|
-
for (const scope of options.projectScopes ?? []) {
|
|
556
|
-
scopeByNormalizedPath.set(normalizeProjectKey(scope.path), scope.disabledServerNames);
|
|
557
|
-
}
|
|
558
|
-
const projects = raw.projects ?? {};
|
|
559
|
-
for (const projectPath of Object.keys(projects)) {
|
|
560
|
-
const project = projects[projectPath] ?? {};
|
|
561
|
-
const disabledForThis = scopeByNormalizedPath.get(normalizeProjectKey(projectPath)) ?? [];
|
|
562
|
-
syncProjectDisabledArray(project, managedNames, disabledForThis);
|
|
563
|
-
projects[projectPath] = project;
|
|
564
|
-
}
|
|
565
|
-
for (const scope of options.projectScopes ?? []) {
|
|
566
|
-
if (scope.disabledServerNames.length === 0)
|
|
567
|
-
continue;
|
|
568
|
-
const normalizedKey = normalizeProjectKey(scope.path);
|
|
569
|
-
const alreadyExists = Object.keys(projects).some((p) => normalizeProjectKey(p) === normalizedKey);
|
|
570
|
-
if (!alreadyExists) {
|
|
571
|
-
projects[scope.path] = { disabledMcpServers: [...scope.disabledServerNames] };
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
raw.projects = projects;
|
|
575
|
-
writeJsonAtomic(configPath, raw);
|
|
576
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
577
|
-
return okResult(this.id, configPath);
|
|
578
|
-
} catch (error) {
|
|
579
|
-
return errorResult(this.id, configPath, error.message);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// src/adapters/claude-desktop.ts
|
|
585
|
-
import { execFileSync } from "node:child_process";
|
|
586
|
-
import path3 from "node:path";
|
|
587
|
-
import { z as z2 } from "zod";
|
|
588
|
-
var stringMapSchema2 = z2.record(z2.string(), z2.string());
|
|
589
|
-
var claudeDesktopEntrySchema = z2.object({
|
|
590
|
-
type: z2.string().optional(),
|
|
591
|
-
url: z2.string().min(1).optional(),
|
|
592
|
-
headers: stringMapSchema2.optional(),
|
|
593
|
-
command: z2.string().min(1).optional(),
|
|
594
|
-
args: z2.array(z2.string()).optional(),
|
|
595
|
-
env: stringMapSchema2.optional(),
|
|
596
|
-
cwd: z2.string().min(1).optional(),
|
|
597
|
-
disabled: z2.boolean().optional()
|
|
598
|
-
}).passthrough();
|
|
599
|
-
function buildProxyEntry(entry) {
|
|
600
|
-
const upstreamName = entry.name.replace(/ \(mcpx\)$/, "");
|
|
601
|
-
try {
|
|
602
|
-
const mcpxPath = execFileSync("which", ["mcpx"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
603
|
-
if (mcpxPath)
|
|
604
|
-
return { command: mcpxPath, args: ["proxy", upstreamName] };
|
|
605
|
-
} catch {}
|
|
606
|
-
return { command: "npx", args: ["-y", "@kwonye/mcpx@latest", "proxy", upstreamName] };
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
class ClaudeDesktopAdapter {
|
|
610
|
-
id = "claude-desktop";
|
|
611
|
-
detectConfigPath() {
|
|
612
|
-
if (process.platform === "linux") {
|
|
613
|
-
return path3.join(homeDir(), ".config", "Claude", "claude_desktop_config.json");
|
|
614
|
-
}
|
|
615
|
-
if (process.platform === "win32") {
|
|
616
|
-
return path3.join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
|
|
617
|
-
}
|
|
618
|
-
return path3.join(homeDir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
619
|
-
}
|
|
620
|
-
supportsHttp() {
|
|
621
|
-
return true;
|
|
622
|
-
}
|
|
623
|
-
scanForImports(_config, managedIndex) {
|
|
624
|
-
const configPath = this.detectConfigPath();
|
|
625
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
626
|
-
if (!configPath) {
|
|
627
|
-
return result;
|
|
628
|
-
}
|
|
629
|
-
const raw = readJsonFile(configPath, {});
|
|
630
|
-
const topLevelServers = raw.mcpServers ?? {};
|
|
631
|
-
for (const [name, rawEntry] of Object.entries(topLevelServers)) {
|
|
632
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
633
|
-
continue;
|
|
634
|
-
}
|
|
635
|
-
const parsed = claudeDesktopEntrySchema.safeParse(rawEntry);
|
|
636
|
-
if (!parsed.success) {
|
|
637
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
638
|
-
continue;
|
|
639
|
-
}
|
|
640
|
-
const entry = parsed.data;
|
|
641
|
-
if ((entry.type === undefined || entry.type === "http" || entry.type === "streamable-http") && entry.url && !entry.command) {
|
|
642
|
-
result.candidates.push({
|
|
643
|
-
clientId: this.id,
|
|
644
|
-
configPath,
|
|
645
|
-
sourceEntryName: name,
|
|
646
|
-
serverName: name,
|
|
647
|
-
spec: {
|
|
648
|
-
transport: "http",
|
|
649
|
-
url: entry.url,
|
|
650
|
-
headers: entry.headers,
|
|
651
|
-
enabled: entry.disabled !== true
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
if ((entry.type === undefined || entry.type === "stdio") && entry.command && !entry.url) {
|
|
657
|
-
result.candidates.push({
|
|
658
|
-
clientId: this.id,
|
|
659
|
-
configPath,
|
|
660
|
-
sourceEntryName: name,
|
|
661
|
-
serverName: name,
|
|
662
|
-
spec: {
|
|
663
|
-
transport: "stdio",
|
|
664
|
-
command: entry.command,
|
|
665
|
-
args: entry.args,
|
|
666
|
-
env: entry.env,
|
|
667
|
-
cwd: entry.cwd,
|
|
668
|
-
enabled: entry.disabled !== true
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
674
|
-
}
|
|
675
|
-
return result;
|
|
676
|
-
}
|
|
677
|
-
syncGateway(_config, options) {
|
|
678
|
-
const configPath = this.detectConfigPath();
|
|
679
|
-
if (!configPath) {
|
|
680
|
-
return errorResult(this.id, undefined, "Unable to resolve Claude Desktop config path.");
|
|
681
|
-
}
|
|
682
|
-
try {
|
|
683
|
-
const raw = readJsonFile(configPath, {});
|
|
684
|
-
const enabledEntries = options.managedEntries.filter((entry) => entry.enabled);
|
|
685
|
-
const serverEntries = Object.fromEntries(enabledEntries.map((entry) => {
|
|
686
|
-
const { command, args } = buildProxyEntry(entry);
|
|
687
|
-
return [entry.name, { command, args }];
|
|
688
|
-
}));
|
|
689
|
-
const enabledManagedNames = enabledEntries.map((entry) => entry.name);
|
|
690
|
-
const topLevelServers = raw.mcpServers ?? {};
|
|
691
|
-
removeSourceEntries(topLevelServers, options.sourceEntriesToRemove);
|
|
692
|
-
for (const name of enabledManagedNames) {
|
|
693
|
-
const topLevelConflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, topLevelServers[name]);
|
|
694
|
-
if (topLevelConflict) {
|
|
695
|
-
return errorResult(this.id, configPath, topLevelConflict);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, topLevelServers, enabledManagedNames);
|
|
699
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
700
|
-
topLevelServers[name] = entry;
|
|
701
|
-
}
|
|
702
|
-
raw.mcpServers = topLevelServers;
|
|
703
|
-
writeJsonAtomic(configPath, raw);
|
|
704
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(enabledEntries.map((entry) => {
|
|
705
|
-
const { command, args } = buildProxyEntry(entry);
|
|
706
|
-
return [entry.name, JSON.stringify({ command, args })];
|
|
707
|
-
})));
|
|
708
|
-
return okResult(this.id, configPath);
|
|
709
|
-
} catch (error) {
|
|
710
|
-
return errorResult(this.id, configPath, error.message);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// src/adapters/codex.ts
|
|
716
|
-
import path4 from "node:path";
|
|
717
|
-
import fs3 from "node:fs";
|
|
718
|
-
import { parse, stringify } from "@iarna/toml";
|
|
719
|
-
import { z as z3 } from "zod";
|
|
720
|
-
var stringMapSchema3 = z3.preprocess((value) => {
|
|
721
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
722
|
-
return value;
|
|
723
|
-
}
|
|
724
|
-
return Object.fromEntries(Object.entries(value));
|
|
725
|
-
}, z3.record(z3.string(), z3.string()));
|
|
726
|
-
var codexEntrySchema = z3.object({
|
|
727
|
-
enabled: z3.boolean().optional(),
|
|
728
|
-
url: z3.string().min(1).optional(),
|
|
729
|
-
headers: stringMapSchema3.optional(),
|
|
730
|
-
http_headers: stringMapSchema3.optional(),
|
|
731
|
-
command: z3.string().min(1).optional(),
|
|
732
|
-
args: z3.array(z3.string()).optional(),
|
|
733
|
-
env: stringMapSchema3.optional(),
|
|
734
|
-
cwd: z3.string().min(1).optional()
|
|
735
|
-
}).passthrough();
|
|
736
|
-
|
|
737
|
-
class CodexAdapter {
|
|
738
|
-
id = "codex";
|
|
739
|
-
detectConfigPath() {
|
|
740
|
-
return path4.join(homeDir(), ".codex", "config.toml");
|
|
741
|
-
}
|
|
742
|
-
supportsHttp() {
|
|
743
|
-
return true;
|
|
744
|
-
}
|
|
745
|
-
scanForImports(_config, managedIndex) {
|
|
746
|
-
const configPath = this.detectConfigPath();
|
|
747
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
748
|
-
if (!configPath || !fs3.existsSync(configPath)) {
|
|
749
|
-
return result;
|
|
750
|
-
}
|
|
751
|
-
const existing = fs3.readFileSync(configPath, "utf8");
|
|
752
|
-
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
753
|
-
const mcpServers = doc.mcp_servers ?? {};
|
|
754
|
-
for (const [name, rawEntry] of Object.entries(mcpServers)) {
|
|
755
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
const parsed = codexEntrySchema.safeParse(rawEntry);
|
|
759
|
-
if (!parsed.success) {
|
|
760
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
const entry = parsed.data;
|
|
764
|
-
if (entry.url && !entry.command) {
|
|
765
|
-
result.candidates.push({
|
|
766
|
-
clientId: this.id,
|
|
767
|
-
configPath,
|
|
768
|
-
sourceEntryName: name,
|
|
769
|
-
serverName: name,
|
|
770
|
-
spec: {
|
|
771
|
-
transport: "http",
|
|
772
|
-
url: entry.url,
|
|
773
|
-
headers: entry.http_headers ?? entry.headers,
|
|
774
|
-
enabled: entry.enabled !== false
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
continue;
|
|
778
|
-
}
|
|
779
|
-
if (entry.command && !entry.url) {
|
|
780
|
-
result.candidates.push({
|
|
781
|
-
clientId: this.id,
|
|
782
|
-
configPath,
|
|
783
|
-
sourceEntryName: name,
|
|
784
|
-
serverName: name,
|
|
785
|
-
spec: {
|
|
786
|
-
transport: "stdio",
|
|
787
|
-
command: entry.command,
|
|
788
|
-
args: entry.args,
|
|
789
|
-
env: entry.env,
|
|
790
|
-
cwd: entry.cwd,
|
|
791
|
-
enabled: entry.enabled !== false
|
|
792
|
-
}
|
|
793
|
-
});
|
|
794
|
-
continue;
|
|
795
|
-
}
|
|
796
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
797
|
-
}
|
|
798
|
-
return result;
|
|
799
|
-
}
|
|
800
|
-
syncGateway(_config, options) {
|
|
801
|
-
const configPath = this.detectConfigPath();
|
|
802
|
-
if (!configPath) {
|
|
803
|
-
return errorResult(this.id, undefined, "Unable to resolve Codex config.toml path.");
|
|
804
|
-
}
|
|
805
|
-
try {
|
|
806
|
-
const existing = fs3.existsSync(configPath) ? fs3.readFileSync(configPath, "utf8") : "";
|
|
807
|
-
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
808
|
-
const mcpServers = doc.mcp_servers ?? {};
|
|
809
|
-
removeSourceEntries(mcpServers, options.sourceEntriesToRemove);
|
|
810
|
-
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
811
|
-
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
812
|
-
enabled: entry.enabled,
|
|
813
|
-
url: entry.url,
|
|
814
|
-
http_headers: entry.headers
|
|
815
|
-
}]));
|
|
816
|
-
for (const name of managedNames) {
|
|
817
|
-
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, mcpServers[name]);
|
|
818
|
-
if (conflict) {
|
|
819
|
-
return errorResult(this.id, configPath, conflict);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, mcpServers, managedNames);
|
|
823
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
824
|
-
mcpServers[name] = entry;
|
|
825
|
-
}
|
|
826
|
-
doc.mcp_servers = mcpServers;
|
|
827
|
-
fs3.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
828
|
-
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
829
|
-
fs3.writeFileSync(tmpPath, stringify(doc), { mode: 384 });
|
|
830
|
-
fs3.renameSync(tmpPath, configPath);
|
|
831
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
832
|
-
return okResult(this.id, configPath);
|
|
833
|
-
} catch (error) {
|
|
834
|
-
return errorResult(this.id, configPath, error.message);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
// src/adapters/cursor.ts
|
|
840
|
-
import path5 from "node:path";
|
|
841
|
-
import { z as z4 } from "zod";
|
|
842
|
-
var stringMapSchema4 = z4.record(z4.string(), z4.string());
|
|
843
|
-
var cursorEntrySchema = z4.object({
|
|
844
|
-
type: z4.string().optional(),
|
|
845
|
-
url: z4.string().min(1).optional(),
|
|
846
|
-
headers: stringMapSchema4.optional(),
|
|
847
|
-
command: z4.string().min(1).optional(),
|
|
848
|
-
args: z4.array(z4.string()).optional(),
|
|
849
|
-
env: stringMapSchema4.optional(),
|
|
850
|
-
cwd: z4.string().min(1).optional(),
|
|
851
|
-
disabled: z4.boolean().optional()
|
|
852
|
-
}).passthrough();
|
|
853
|
-
|
|
854
|
-
class CursorAdapter {
|
|
855
|
-
id = "cursor";
|
|
856
|
-
detectConfigPath() {
|
|
857
|
-
return path5.join(homeDir(), ".cursor", "mcp.json");
|
|
858
|
-
}
|
|
859
|
-
supportsHttp() {
|
|
860
|
-
return true;
|
|
861
|
-
}
|
|
862
|
-
scanForImports(_config, managedIndex) {
|
|
863
|
-
const configPath = this.detectConfigPath();
|
|
864
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
865
|
-
if (!configPath) {
|
|
866
|
-
return result;
|
|
867
|
-
}
|
|
868
|
-
const raw = readJsonFile(configPath, {});
|
|
869
|
-
const servers = {
|
|
870
|
-
...raw.mcpServers ?? {}
|
|
871
|
-
};
|
|
872
|
-
for (const [name, rawEntry] of Object.entries(servers)) {
|
|
873
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
874
|
-
continue;
|
|
875
|
-
}
|
|
876
|
-
const parsed = cursorEntrySchema.safeParse(rawEntry);
|
|
877
|
-
if (!parsed.success) {
|
|
878
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
881
|
-
const entry = parsed.data;
|
|
882
|
-
if ((entry.type === undefined || entry.type === "http" || entry.type === "streamable-http") && entry.url && !entry.command) {
|
|
883
|
-
result.candidates.push({
|
|
884
|
-
clientId: this.id,
|
|
885
|
-
configPath,
|
|
886
|
-
sourceEntryName: name,
|
|
887
|
-
serverName: name,
|
|
888
|
-
spec: {
|
|
889
|
-
transport: "http",
|
|
890
|
-
url: entry.url,
|
|
891
|
-
headers: entry.headers,
|
|
892
|
-
enabled: entry.disabled !== true
|
|
893
|
-
}
|
|
894
|
-
});
|
|
895
|
-
continue;
|
|
896
|
-
}
|
|
897
|
-
if ((entry.type === undefined || entry.type === "stdio") && entry.command && !entry.url) {
|
|
898
|
-
result.candidates.push({
|
|
899
|
-
clientId: this.id,
|
|
900
|
-
configPath,
|
|
901
|
-
sourceEntryName: name,
|
|
902
|
-
serverName: name,
|
|
903
|
-
spec: {
|
|
904
|
-
transport: "stdio",
|
|
905
|
-
command: entry.command,
|
|
906
|
-
args: entry.args,
|
|
907
|
-
env: entry.env,
|
|
908
|
-
cwd: entry.cwd,
|
|
909
|
-
enabled: entry.disabled !== true
|
|
910
|
-
}
|
|
911
|
-
});
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
915
|
-
}
|
|
916
|
-
return result;
|
|
917
|
-
}
|
|
918
|
-
syncGateway(_config, options) {
|
|
919
|
-
const configPath = this.detectConfigPath();
|
|
920
|
-
if (!configPath) {
|
|
921
|
-
return errorResult(this.id, undefined, "Unable to resolve Cursor MCP config path.");
|
|
922
|
-
}
|
|
923
|
-
try {
|
|
924
|
-
const raw = readJsonFile(configPath, {});
|
|
925
|
-
const servers = {
|
|
926
|
-
...raw.mcpServers ?? {}
|
|
927
|
-
};
|
|
928
|
-
removeSourceEntries(servers, options.sourceEntriesToRemove);
|
|
929
|
-
const enabledEntries = options.managedEntries.filter((entry) => entry.enabled);
|
|
930
|
-
const serverEntries = Object.fromEntries(enabledEntries.map((entry) => [entry.name, {
|
|
931
|
-
type: "http",
|
|
932
|
-
url: entry.url,
|
|
933
|
-
headers: entry.headers
|
|
934
|
-
}]));
|
|
935
|
-
const enabledManagedNames = enabledEntries.map((entry) => entry.name);
|
|
936
|
-
for (const name of enabledManagedNames) {
|
|
937
|
-
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, servers[name]);
|
|
938
|
-
if (conflict) {
|
|
939
|
-
return errorResult(this.id, configPath, conflict);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, servers, enabledManagedNames);
|
|
943
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
944
|
-
servers[name] = entry;
|
|
945
|
-
}
|
|
946
|
-
const next = {
|
|
947
|
-
...raw,
|
|
948
|
-
mcpServers: servers
|
|
949
|
-
};
|
|
950
|
-
writeJsonAtomic(configPath, next);
|
|
951
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(options.managedEntries.map((entry) => [
|
|
952
|
-
entry.name,
|
|
953
|
-
JSON.stringify({ type: "http", url: entry.url, headers: entry.headers, enabled: entry.enabled })
|
|
954
|
-
])));
|
|
955
|
-
return okResult(this.id, configPath);
|
|
956
|
-
} catch (error) {
|
|
957
|
-
return errorResult(this.id, configPath, error.message);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// src/adapters/cline.ts
|
|
963
|
-
import path6 from "node:path";
|
|
964
|
-
import fs4 from "node:fs";
|
|
965
|
-
import { z as z5 } from "zod";
|
|
966
|
-
var HTTP_TRANSPORT_TYPES = new Set(["http", "sse", "streamableHttp"]);
|
|
967
|
-
var stringMapSchema5 = z5.record(z5.string(), z5.string());
|
|
968
|
-
var clineEntrySchema = z5.object({
|
|
969
|
-
type: z5.string().optional(),
|
|
970
|
-
transportType: z5.string().optional(),
|
|
971
|
-
url: z5.string().min(1).optional(),
|
|
972
|
-
headers: stringMapSchema5.optional(),
|
|
973
|
-
command: z5.string().min(1).optional(),
|
|
974
|
-
args: z5.array(z5.string()).optional(),
|
|
975
|
-
env: stringMapSchema5.optional(),
|
|
976
|
-
cwd: z5.string().min(1).optional(),
|
|
977
|
-
disabled: z5.boolean().optional()
|
|
978
|
-
}).passthrough();
|
|
979
|
-
function getCandidatePaths() {
|
|
980
|
-
if (process.platform === "linux") {
|
|
981
|
-
return [
|
|
982
|
-
path6.join(homeDir(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
983
|
-
path6.join(homeDir(), ".config", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
984
|
-
path6.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
|
|
985
|
-
];
|
|
986
|
-
}
|
|
987
|
-
if (process.platform === "win32") {
|
|
988
|
-
return [
|
|
989
|
-
path6.join(process.env.APPDATA ?? "", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
990
|
-
path6.join(process.env.APPDATA ?? "", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
991
|
-
path6.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
|
|
992
|
-
];
|
|
993
|
-
}
|
|
994
|
-
return [
|
|
995
|
-
path6.join(homeDir(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
996
|
-
path6.join(homeDir(), "Library", "Application Support", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
997
|
-
path6.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
|
|
998
|
-
];
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
class ClineAdapter {
|
|
1002
|
-
id = "cline";
|
|
1003
|
-
detectConfigPath() {
|
|
1004
|
-
const candidates = getCandidatePaths();
|
|
1005
|
-
for (const candidate of candidates) {
|
|
1006
|
-
if (fs4.existsSync(candidate)) {
|
|
1007
|
-
return candidate;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
return candidates[0] ?? null;
|
|
1011
|
-
}
|
|
1012
|
-
supportsHttp() {
|
|
1013
|
-
return true;
|
|
1014
|
-
}
|
|
1015
|
-
scanForImports(_config, managedIndex) {
|
|
1016
|
-
const configPath = this.detectConfigPath();
|
|
1017
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
1018
|
-
if (!configPath || !fs4.existsSync(configPath)) {
|
|
1019
|
-
return result;
|
|
1020
|
-
}
|
|
1021
|
-
const raw = readJsonFile(configPath, {});
|
|
1022
|
-
const servers = {
|
|
1023
|
-
...raw.mcpServers ?? {}
|
|
1024
|
-
};
|
|
1025
|
-
for (const [name, rawEntry] of Object.entries(servers)) {
|
|
1026
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
1027
|
-
continue;
|
|
1028
|
-
}
|
|
1029
|
-
const parsed = clineEntrySchema.safeParse(rawEntry);
|
|
1030
|
-
if (!parsed.success) {
|
|
1031
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
1032
|
-
continue;
|
|
1033
|
-
}
|
|
1034
|
-
const entry = parsed.data;
|
|
1035
|
-
const transport = entry.type ?? entry.transportType;
|
|
1036
|
-
if ((transport === undefined || HTTP_TRANSPORT_TYPES.has(transport)) && entry.url && !entry.command) {
|
|
1037
|
-
result.candidates.push({
|
|
1038
|
-
clientId: this.id,
|
|
1039
|
-
configPath,
|
|
1040
|
-
sourceEntryName: name,
|
|
1041
|
-
serverName: name,
|
|
1042
|
-
spec: {
|
|
1043
|
-
transport: "http",
|
|
1044
|
-
url: entry.url,
|
|
1045
|
-
headers: entry.headers,
|
|
1046
|
-
enabled: entry.disabled !== true
|
|
1047
|
-
}
|
|
1048
|
-
});
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
if ((transport === undefined || transport === "stdio") && entry.command && !entry.url) {
|
|
1052
|
-
result.candidates.push({
|
|
1053
|
-
clientId: this.id,
|
|
1054
|
-
configPath,
|
|
1055
|
-
sourceEntryName: name,
|
|
1056
|
-
serverName: name,
|
|
1057
|
-
spec: {
|
|
1058
|
-
transport: "stdio",
|
|
1059
|
-
command: entry.command,
|
|
1060
|
-
args: entry.args,
|
|
1061
|
-
env: entry.env,
|
|
1062
|
-
cwd: entry.cwd,
|
|
1063
|
-
enabled: entry.disabled !== true
|
|
1064
|
-
}
|
|
1065
|
-
});
|
|
1066
|
-
continue;
|
|
1067
|
-
}
|
|
1068
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
1069
|
-
}
|
|
1070
|
-
return result;
|
|
1071
|
-
}
|
|
1072
|
-
syncGateway(_config, options) {
|
|
1073
|
-
const configPath = this.detectConfigPath();
|
|
1074
|
-
if (!configPath) {
|
|
1075
|
-
return errorResult(this.id, undefined, "Unable to resolve Cline MCP config path.");
|
|
1076
|
-
}
|
|
1077
|
-
if (!fs4.existsSync(configPath)) {
|
|
1078
|
-
return okResult(this.id, configPath, "Cline config not found; skipping sync.");
|
|
1079
|
-
}
|
|
1080
|
-
try {
|
|
1081
|
-
const raw = readJsonFile(configPath, {});
|
|
1082
|
-
const servers = {
|
|
1083
|
-
...raw.mcpServers ?? {}
|
|
1084
|
-
};
|
|
1085
|
-
removeSourceEntries(servers, options.sourceEntriesToRemove);
|
|
1086
|
-
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
1087
|
-
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
1088
|
-
type: "streamableHttp",
|
|
1089
|
-
url: entry.url,
|
|
1090
|
-
headers: entry.headers,
|
|
1091
|
-
disabled: !entry.enabled
|
|
1092
|
-
}]));
|
|
1093
|
-
for (const name of managedNames) {
|
|
1094
|
-
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, servers[name]);
|
|
1095
|
-
if (conflict) {
|
|
1096
|
-
return errorResult(this.id, configPath, conflict);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, servers, managedNames);
|
|
1100
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
1101
|
-
servers[name] = entry;
|
|
1102
|
-
}
|
|
1103
|
-
const next = {
|
|
1104
|
-
...raw,
|
|
1105
|
-
mcpServers: servers
|
|
1106
|
-
};
|
|
1107
|
-
writeJsonAtomic(configPath, next);
|
|
1108
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
1109
|
-
return okResult(this.id, configPath);
|
|
1110
|
-
} catch (error) {
|
|
1111
|
-
return errorResult(this.id, configPath, error.message);
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// src/adapters/kiro.ts
|
|
1117
|
-
import fs5 from "node:fs";
|
|
1118
|
-
import path7 from "node:path";
|
|
1119
|
-
import { z as z6 } from "zod";
|
|
1120
|
-
var stringMapSchema6 = z6.record(z6.string(), z6.string());
|
|
1121
|
-
var kiroEntrySchema = z6.object({
|
|
1122
|
-
type: z6.string().optional(),
|
|
1123
|
-
url: z6.string().min(1).optional(),
|
|
1124
|
-
headers: stringMapSchema6.optional(),
|
|
1125
|
-
command: z6.string().min(1).optional(),
|
|
1126
|
-
args: z6.array(z6.string()).optional(),
|
|
1127
|
-
env: stringMapSchema6.optional(),
|
|
1128
|
-
cwd: z6.string().min(1).optional(),
|
|
1129
|
-
disabled: z6.boolean().optional()
|
|
1130
|
-
}).passthrough();
|
|
1131
|
-
|
|
1132
|
-
class KiroAdapter {
|
|
1133
|
-
id = "kiro";
|
|
1134
|
-
detectConfigPath() {
|
|
1135
|
-
return path7.join(homeDir(), ".kiro", "settings", "mcp.json");
|
|
1136
|
-
}
|
|
1137
|
-
supportsHttp() {
|
|
1138
|
-
return true;
|
|
1139
|
-
}
|
|
1140
|
-
scanForImports(_config, managedIndex) {
|
|
1141
|
-
const configPath = this.detectConfigPath();
|
|
1142
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
1143
|
-
if (!configPath) {
|
|
1144
|
-
return result;
|
|
1145
|
-
}
|
|
1146
|
-
const raw = readJsonFile(configPath, {});
|
|
1147
|
-
const servers = {
|
|
1148
|
-
...raw.mcpServers ?? {}
|
|
1149
|
-
};
|
|
1150
|
-
for (const [name, rawEntry] of Object.entries(servers)) {
|
|
1151
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
1152
|
-
continue;
|
|
1153
|
-
}
|
|
1154
|
-
const parsed = kiroEntrySchema.safeParse(rawEntry);
|
|
1155
|
-
if (!parsed.success) {
|
|
1156
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
1157
|
-
continue;
|
|
1158
|
-
}
|
|
1159
|
-
const entry = parsed.data;
|
|
1160
|
-
if ((entry.type === undefined || entry.type === "http" || entry.type === "streamable-http" || entry.type === "sse") && entry.url && !entry.command) {
|
|
1161
|
-
result.candidates.push({
|
|
1162
|
-
clientId: this.id,
|
|
1163
|
-
configPath,
|
|
1164
|
-
sourceEntryName: name,
|
|
1165
|
-
serverName: name,
|
|
1166
|
-
spec: {
|
|
1167
|
-
transport: "http",
|
|
1168
|
-
url: entry.url,
|
|
1169
|
-
headers: entry.headers,
|
|
1170
|
-
enabled: entry.disabled !== true
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1173
|
-
continue;
|
|
1174
|
-
}
|
|
1175
|
-
if ((entry.type === undefined || entry.type === "stdio") && entry.command && !entry.url) {
|
|
1176
|
-
result.candidates.push({
|
|
1177
|
-
clientId: this.id,
|
|
1178
|
-
configPath,
|
|
1179
|
-
sourceEntryName: name,
|
|
1180
|
-
serverName: name,
|
|
1181
|
-
spec: {
|
|
1182
|
-
transport: "stdio",
|
|
1183
|
-
command: entry.command,
|
|
1184
|
-
args: entry.args,
|
|
1185
|
-
env: entry.env,
|
|
1186
|
-
cwd: entry.cwd,
|
|
1187
|
-
enabled: entry.disabled !== true
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
continue;
|
|
1191
|
-
}
|
|
1192
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
1193
|
-
}
|
|
1194
|
-
return result;
|
|
1195
|
-
}
|
|
1196
|
-
syncGateway(_config, options) {
|
|
1197
|
-
const configPath = this.detectConfigPath();
|
|
1198
|
-
if (!configPath) {
|
|
1199
|
-
return errorResult(this.id, undefined, "Unable to resolve Kiro MCP config path.");
|
|
1200
|
-
}
|
|
1201
|
-
try {
|
|
1202
|
-
const raw = readJsonFile(configPath, {});
|
|
1203
|
-
const servers = {
|
|
1204
|
-
...raw.mcpServers ?? {}
|
|
1205
|
-
};
|
|
1206
|
-
removeSourceEntries(servers, options.sourceEntriesToRemove);
|
|
1207
|
-
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
1208
|
-
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
1209
|
-
url: entry.url,
|
|
1210
|
-
headers: entry.headers,
|
|
1211
|
-
disabled: !entry.enabled
|
|
1212
|
-
}]));
|
|
1213
|
-
for (const name of managedNames) {
|
|
1214
|
-
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, servers[name]);
|
|
1215
|
-
if (conflict) {
|
|
1216
|
-
return errorResult(this.id, configPath, conflict);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, servers, managedNames);
|
|
1220
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
1221
|
-
servers[name] = entry;
|
|
1222
|
-
}
|
|
1223
|
-
const next = {
|
|
1224
|
-
...raw,
|
|
1225
|
-
mcpServers: servers
|
|
1226
|
-
};
|
|
1227
|
-
writeJsonAtomic(configPath, next);
|
|
1228
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
1229
|
-
return okResult(this.id, configPath);
|
|
1230
|
-
} catch (error) {
|
|
1231
|
-
return errorResult(this.id, configPath, error.message);
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
syncSkills(skills) {
|
|
1235
|
-
const skillsDir = path7.join(homeDir(), ".kiro", "skills");
|
|
1236
|
-
if (!fs5.existsSync(skillsDir)) {
|
|
1237
|
-
fs5.mkdirSync(skillsDir, { recursive: true });
|
|
1238
|
-
}
|
|
1239
|
-
const existingFiles = fs5.readdirSync(skillsDir);
|
|
1240
|
-
const managedIds = new Set(skills.map((s) => s.id));
|
|
1241
|
-
for (const file of existingFiles) {
|
|
1242
|
-
if (file.endsWith(".md")) {
|
|
1243
|
-
const id = path7.basename(file, ".md");
|
|
1244
|
-
if (!managedIds.has(id)) {
|
|
1245
|
-
fs5.unlinkSync(path7.join(skillsDir, file));
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
for (const skill of skills) {
|
|
1250
|
-
const targetPath = path7.join(skillsDir, `${skill.id}.md`);
|
|
1251
|
-
fs5.writeFileSync(targetPath, skill.content, "utf-8");
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
// src/adapters/opencode.ts
|
|
1257
|
-
import path8 from "node:path";
|
|
1258
|
-
import { z as z7 } from "zod";
|
|
1259
|
-
var stringMapSchema7 = z7.record(z7.string(), z7.string());
|
|
1260
|
-
var openCodeEntrySchema = z7.object({
|
|
1261
|
-
type: z7.string().optional(),
|
|
1262
|
-
url: z7.string().min(1).optional(),
|
|
1263
|
-
headers: stringMapSchema7.optional(),
|
|
1264
|
-
command: z7.union([z7.string().min(1), z7.array(z7.string())]).optional(),
|
|
1265
|
-
args: z7.array(z7.string()).optional(),
|
|
1266
|
-
env: stringMapSchema7.optional(),
|
|
1267
|
-
environment: stringMapSchema7.optional(),
|
|
1268
|
-
cwd: z7.string().min(1).optional(),
|
|
1269
|
-
enabled: z7.boolean().optional()
|
|
1270
|
-
}).passthrough();
|
|
1271
|
-
|
|
1272
|
-
class OpenCodeAdapter {
|
|
1273
|
-
id = "opencode";
|
|
1274
|
-
detectConfigPath() {
|
|
1275
|
-
return path8.join(homeDir(), ".config", "opencode", "opencode.json");
|
|
1276
|
-
}
|
|
1277
|
-
supportsHttp() {
|
|
1278
|
-
return true;
|
|
1279
|
-
}
|
|
1280
|
-
scanForImports(_config, managedIndex) {
|
|
1281
|
-
const configPath = this.detectConfigPath();
|
|
1282
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
1283
|
-
if (!configPath) {
|
|
1284
|
-
return result;
|
|
1285
|
-
}
|
|
1286
|
-
const raw = readJsonFile(configPath, {});
|
|
1287
|
-
const mcp = {
|
|
1288
|
-
...raw.mcp ?? {}
|
|
1289
|
-
};
|
|
1290
|
-
for (const [name, rawEntry] of Object.entries(mcp)) {
|
|
1291
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
1292
|
-
continue;
|
|
1293
|
-
}
|
|
1294
|
-
const parsed = openCodeEntrySchema.safeParse(rawEntry);
|
|
1295
|
-
if (!parsed.success) {
|
|
1296
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
1297
|
-
continue;
|
|
1298
|
-
}
|
|
1299
|
-
const entry = parsed.data;
|
|
1300
|
-
if ((entry.type === undefined || entry.type === "remote" || entry.type === "http" || entry.type === "streamable-http") && entry.url && !entry.command) {
|
|
1301
|
-
result.candidates.push({
|
|
1302
|
-
clientId: this.id,
|
|
1303
|
-
configPath,
|
|
1304
|
-
sourceEntryName: name,
|
|
1305
|
-
serverName: name,
|
|
1306
|
-
spec: {
|
|
1307
|
-
transport: "http",
|
|
1308
|
-
url: entry.url,
|
|
1309
|
-
headers: entry.headers,
|
|
1310
|
-
enabled: entry.enabled !== false
|
|
1311
|
-
}
|
|
1312
|
-
});
|
|
1313
|
-
continue;
|
|
1314
|
-
}
|
|
1315
|
-
if ((entry.type === undefined || entry.type === "local" || entry.type === "stdio") && entry.command && !entry.url) {
|
|
1316
|
-
const commandParts = Array.isArray(entry.command) ? entry.command : [entry.command, ...entry.args ?? []];
|
|
1317
|
-
if (commandParts.length === 0) {
|
|
1318
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
1319
|
-
continue;
|
|
1320
|
-
}
|
|
1321
|
-
result.candidates.push({
|
|
1322
|
-
clientId: this.id,
|
|
1323
|
-
configPath,
|
|
1324
|
-
sourceEntryName: name,
|
|
1325
|
-
serverName: name,
|
|
1326
|
-
spec: {
|
|
1327
|
-
transport: "stdio",
|
|
1328
|
-
command: commandParts[0],
|
|
1329
|
-
args: commandParts.slice(1),
|
|
1330
|
-
env: entry.environment ?? entry.env,
|
|
1331
|
-
cwd: entry.cwd,
|
|
1332
|
-
enabled: entry.enabled !== false
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1335
|
-
continue;
|
|
1336
|
-
}
|
|
1337
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
1338
|
-
}
|
|
1339
|
-
return result;
|
|
1340
|
-
}
|
|
1341
|
-
syncGateway(_config, options) {
|
|
1342
|
-
const configPath = this.detectConfigPath();
|
|
1343
|
-
if (!configPath) {
|
|
1344
|
-
return errorResult(this.id, undefined, "Unable to resolve OpenCode config path.");
|
|
1345
|
-
}
|
|
1346
|
-
try {
|
|
1347
|
-
const raw = readJsonFile(configPath, {});
|
|
1348
|
-
const mcp = {
|
|
1349
|
-
...raw.mcp ?? {}
|
|
1350
|
-
};
|
|
1351
|
-
removeSourceEntries(mcp, options.sourceEntriesToRemove);
|
|
1352
|
-
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
1353
|
-
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
1354
|
-
type: "remote",
|
|
1355
|
-
url: entry.url,
|
|
1356
|
-
headers: entry.headers,
|
|
1357
|
-
enabled: entry.enabled
|
|
1358
|
-
}]));
|
|
1359
|
-
for (const name of managedNames) {
|
|
1360
|
-
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, mcp[name]);
|
|
1361
|
-
if (conflict) {
|
|
1362
|
-
return errorResult(this.id, configPath, conflict);
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, mcp, managedNames);
|
|
1366
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
1367
|
-
mcp[name] = entry;
|
|
1368
|
-
}
|
|
1369
|
-
const next = {
|
|
1370
|
-
...raw,
|
|
1371
|
-
mcp
|
|
1372
|
-
};
|
|
1373
|
-
writeJsonAtomic(configPath, next);
|
|
1374
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
1375
|
-
return okResult(this.id, configPath);
|
|
1376
|
-
} catch (error) {
|
|
1377
|
-
return errorResult(this.id, configPath, error.message);
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
// src/adapters/vscode.ts
|
|
1383
|
-
import path9 from "node:path";
|
|
1384
|
-
import { z as z8 } from "zod";
|
|
1385
|
-
var stringMapSchema8 = z8.record(z8.string(), z8.string());
|
|
1386
|
-
var vscodeEntrySchema = z8.object({
|
|
1387
|
-
type: z8.string().optional(),
|
|
1388
|
-
url: z8.string().min(1).optional(),
|
|
1389
|
-
headers: stringMapSchema8.optional(),
|
|
1390
|
-
command: z8.string().min(1).optional(),
|
|
1391
|
-
args: z8.array(z8.string()).optional(),
|
|
1392
|
-
env: stringMapSchema8.optional(),
|
|
1393
|
-
cwd: z8.string().min(1).optional(),
|
|
1394
|
-
disabled: z8.boolean().optional()
|
|
1395
|
-
}).passthrough();
|
|
1396
|
-
|
|
1397
|
-
class VsCodeAdapter {
|
|
1398
|
-
id = "vscode";
|
|
1399
|
-
detectConfigPath() {
|
|
1400
|
-
if (process.platform === "linux") {
|
|
1401
|
-
return path9.join(homeDir(), ".config", "Code", "User", "mcp.json");
|
|
1402
|
-
}
|
|
1403
|
-
if (process.platform === "win32") {
|
|
1404
|
-
return path9.join(process.env.APPDATA ?? "", "Code", "User", "mcp.json");
|
|
1405
|
-
}
|
|
1406
|
-
return path9.join(homeDir(), "Library", "Application Support", "Code", "User", "mcp.json");
|
|
1407
|
-
}
|
|
1408
|
-
supportsHttp() {
|
|
1409
|
-
return true;
|
|
1410
|
-
}
|
|
1411
|
-
scanForImports(_config, managedIndex) {
|
|
1412
|
-
const configPath = this.detectConfigPath();
|
|
1413
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
1414
|
-
if (!configPath) {
|
|
1415
|
-
return result;
|
|
1416
|
-
}
|
|
1417
|
-
const raw = readJsonFile(configPath, {});
|
|
1418
|
-
const servers = {
|
|
1419
|
-
...raw.servers ?? {}
|
|
1420
|
-
};
|
|
1421
|
-
for (const [name, rawEntry] of Object.entries(servers)) {
|
|
1422
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
1423
|
-
continue;
|
|
1424
|
-
}
|
|
1425
|
-
const parsed = vscodeEntrySchema.safeParse(rawEntry);
|
|
1426
|
-
if (!parsed.success) {
|
|
1427
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
1428
|
-
continue;
|
|
1429
|
-
}
|
|
1430
|
-
const entry = parsed.data;
|
|
1431
|
-
if ((entry.type === undefined || entry.type === "http" || entry.type === "streamable-http") && entry.url && !entry.command) {
|
|
1432
|
-
result.candidates.push({
|
|
1433
|
-
clientId: this.id,
|
|
1434
|
-
configPath,
|
|
1435
|
-
sourceEntryName: name,
|
|
1436
|
-
serverName: name,
|
|
1437
|
-
spec: {
|
|
1438
|
-
transport: "http",
|
|
1439
|
-
url: entry.url,
|
|
1440
|
-
headers: entry.headers,
|
|
1441
|
-
enabled: entry.disabled !== true
|
|
1442
|
-
}
|
|
1443
|
-
});
|
|
1444
|
-
continue;
|
|
1445
|
-
}
|
|
1446
|
-
if ((entry.type === undefined || entry.type === "stdio") && entry.command && !entry.url) {
|
|
1447
|
-
result.candidates.push({
|
|
1448
|
-
clientId: this.id,
|
|
1449
|
-
configPath,
|
|
1450
|
-
sourceEntryName: name,
|
|
1451
|
-
serverName: name,
|
|
1452
|
-
spec: {
|
|
1453
|
-
transport: "stdio",
|
|
1454
|
-
command: entry.command,
|
|
1455
|
-
args: entry.args,
|
|
1456
|
-
env: entry.env,
|
|
1457
|
-
cwd: entry.cwd,
|
|
1458
|
-
enabled: entry.disabled !== true
|
|
1459
|
-
}
|
|
1460
|
-
});
|
|
1461
|
-
continue;
|
|
1462
|
-
}
|
|
1463
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
1464
|
-
}
|
|
1465
|
-
return result;
|
|
1466
|
-
}
|
|
1467
|
-
syncGateway(_config, options) {
|
|
1468
|
-
const configPath = this.detectConfigPath();
|
|
1469
|
-
if (!configPath) {
|
|
1470
|
-
return errorResult(this.id, undefined, "Unable to resolve VS Code MCP config path.");
|
|
1471
|
-
}
|
|
1472
|
-
try {
|
|
1473
|
-
const raw = readJsonFile(configPath, {});
|
|
1474
|
-
const servers = {
|
|
1475
|
-
...raw.servers ?? {}
|
|
1476
|
-
};
|
|
1477
|
-
removeSourceEntries(servers, options.sourceEntriesToRemove);
|
|
1478
|
-
const enabledEntries = options.managedEntries.filter((entry) => entry.enabled);
|
|
1479
|
-
const serverEntries = Object.fromEntries(enabledEntries.map((entry) => [entry.name, {
|
|
1480
|
-
type: "http",
|
|
1481
|
-
url: entry.url,
|
|
1482
|
-
headers: entry.headers
|
|
1483
|
-
}]));
|
|
1484
|
-
const enabledManagedNames = enabledEntries.map((entry) => entry.name);
|
|
1485
|
-
for (const name of enabledManagedNames) {
|
|
1486
|
-
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, servers[name]);
|
|
1487
|
-
if (conflict) {
|
|
1488
|
-
return errorResult(this.id, configPath, conflict);
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, servers, enabledManagedNames);
|
|
1492
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
1493
|
-
servers[name] = entry;
|
|
1494
|
-
}
|
|
1495
|
-
const next = {
|
|
1496
|
-
...raw,
|
|
1497
|
-
servers
|
|
1498
|
-
};
|
|
1499
|
-
writeJsonAtomic(configPath, next);
|
|
1500
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(options.managedEntries.map((entry) => [
|
|
1501
|
-
entry.name,
|
|
1502
|
-
JSON.stringify({ type: "http", url: entry.url, headers: entry.headers, enabled: entry.enabled })
|
|
1503
|
-
])));
|
|
1504
|
-
return okResult(this.id, configPath);
|
|
1505
|
-
} catch (error) {
|
|
1506
|
-
return errorResult(this.id, configPath, error.message);
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// src/adapters/qwen.ts
|
|
1512
|
-
import fs6 from "node:fs";
|
|
1513
|
-
import path10 from "node:path";
|
|
1514
|
-
import { z as z9 } from "zod";
|
|
1515
|
-
var stringMapSchema9 = z9.record(z9.string(), z9.string());
|
|
1516
|
-
var qwenEntrySchema = z9.object({
|
|
1517
|
-
httpUrl: z9.string().min(1).optional(),
|
|
1518
|
-
url: z9.string().min(1).optional(),
|
|
1519
|
-
headers: stringMapSchema9.optional(),
|
|
1520
|
-
command: z9.string().min(1).optional(),
|
|
1521
|
-
args: z9.array(z9.string()).optional(),
|
|
1522
|
-
env: stringMapSchema9.optional(),
|
|
1523
|
-
cwd: z9.string().min(1).optional(),
|
|
1524
|
-
disabled: z9.boolean().optional()
|
|
1525
|
-
}).passthrough();
|
|
1526
|
-
|
|
1527
|
-
class QwenAdapter {
|
|
1528
|
-
id = "qwen";
|
|
1529
|
-
detectConfigPath() {
|
|
1530
|
-
return path10.join(homeDir(), ".qwen", "settings.json");
|
|
1531
|
-
}
|
|
1532
|
-
supportsHttp() {
|
|
1533
|
-
return true;
|
|
1534
|
-
}
|
|
1535
|
-
scanForImports(_config, managedIndex) {
|
|
1536
|
-
const configPath = this.detectConfigPath();
|
|
1537
|
-
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
1538
|
-
if (!configPath) {
|
|
1539
|
-
return result;
|
|
1540
|
-
}
|
|
1541
|
-
const raw = readJsonFile(configPath, {});
|
|
1542
|
-
const excludedSet = new Set(raw.mcp?.excluded ?? []);
|
|
1543
|
-
const topLevelServers = raw.mcpServers ?? {};
|
|
1544
|
-
for (const [name, rawEntry] of Object.entries(topLevelServers)) {
|
|
1545
|
-
if (isManagedGatewayProjection(name) || managedIndex.managed[this.id]?.entries?.[name]) {
|
|
1546
|
-
continue;
|
|
1547
|
-
}
|
|
1548
|
-
const parsed = qwenEntrySchema.safeParse(rawEntry);
|
|
1549
|
-
if (!parsed.success) {
|
|
1550
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry could not be mapped to an mcpx server.", configPath));
|
|
1551
|
-
continue;
|
|
1552
|
-
}
|
|
1553
|
-
const entry = parsed.data;
|
|
1554
|
-
if (entry.httpUrl && !entry.command) {
|
|
1555
|
-
result.candidates.push({
|
|
1556
|
-
clientId: this.id,
|
|
1557
|
-
configPath,
|
|
1558
|
-
sourceEntryName: name,
|
|
1559
|
-
serverName: name,
|
|
1560
|
-
spec: {
|
|
1561
|
-
transport: "http",
|
|
1562
|
-
url: entry.httpUrl,
|
|
1563
|
-
headers: entry.headers,
|
|
1564
|
-
enabled: !(entry.disabled === true || excludedSet.has(name))
|
|
1565
|
-
}
|
|
1566
|
-
});
|
|
1567
|
-
continue;
|
|
1568
|
-
}
|
|
1569
|
-
if (entry.url && !entry.httpUrl && !entry.command) {
|
|
1570
|
-
result.candidates.push({
|
|
1571
|
-
clientId: this.id,
|
|
1572
|
-
configPath,
|
|
1573
|
-
sourceEntryName: name,
|
|
1574
|
-
serverName: name,
|
|
1575
|
-
spec: {
|
|
1576
|
-
transport: "http",
|
|
1577
|
-
url: entry.url,
|
|
1578
|
-
headers: entry.headers,
|
|
1579
|
-
enabled: !(entry.disabled === true || excludedSet.has(name))
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
continue;
|
|
1583
|
-
}
|
|
1584
|
-
if (entry.command && !entry.httpUrl) {
|
|
1585
|
-
result.candidates.push({
|
|
1586
|
-
clientId: this.id,
|
|
1587
|
-
configPath,
|
|
1588
|
-
sourceEntryName: name,
|
|
1589
|
-
serverName: name,
|
|
1590
|
-
spec: {
|
|
1591
|
-
transport: "stdio",
|
|
1592
|
-
command: entry.command,
|
|
1593
|
-
args: entry.args,
|
|
1594
|
-
env: entry.env,
|
|
1595
|
-
cwd: entry.cwd,
|
|
1596
|
-
enabled: !(entry.disabled === true || excludedSet.has(name))
|
|
1597
|
-
}
|
|
1598
|
-
});
|
|
1599
|
-
continue;
|
|
1600
|
-
}
|
|
1601
|
-
result.skipped.push(buildImportSkip(this.id, name, "Entry must define exactly one transport that mcpx supports.", configPath));
|
|
1602
|
-
}
|
|
1603
|
-
return result;
|
|
1604
|
-
}
|
|
1605
|
-
syncGateway(_config, options) {
|
|
1606
|
-
const configPath = this.detectConfigPath();
|
|
1607
|
-
if (!configPath) {
|
|
1608
|
-
return errorResult(this.id, undefined, "Unable to resolve Qwen config path.");
|
|
1609
|
-
}
|
|
1610
|
-
try {
|
|
1611
|
-
const raw = readJsonFile(configPath, {});
|
|
1612
|
-
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
1613
|
-
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
1614
|
-
httpUrl: entry.url,
|
|
1615
|
-
headers: entry.headers
|
|
1616
|
-
}]));
|
|
1617
|
-
const topLevelServers = raw.mcpServers ?? {};
|
|
1618
|
-
removeSourceEntries(topLevelServers, options.sourceEntriesToRemove);
|
|
1619
|
-
for (const name of managedNames) {
|
|
1620
|
-
const topLevelConflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, topLevelServers[name]);
|
|
1621
|
-
if (topLevelConflict) {
|
|
1622
|
-
return errorResult(this.id, configPath, topLevelConflict);
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
pruneStaleManagedEntries(options.managedIndex, this.id, topLevelServers, managedNames);
|
|
1626
|
-
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
1627
|
-
topLevelServers[name] = entry;
|
|
1628
|
-
}
|
|
1629
|
-
raw.mcpServers = topLevelServers;
|
|
1630
|
-
syncMcpExcludedArray(raw, managedNames, options.managedEntries);
|
|
1631
|
-
writeJsonAtomic(configPath, raw);
|
|
1632
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(options.managedEntries.map((entry) => [
|
|
1633
|
-
entry.name,
|
|
1634
|
-
JSON.stringify({ httpUrl: entry.url, headers: entry.headers, enabled: entry.enabled })
|
|
1635
|
-
])));
|
|
1636
|
-
return okResult(this.id, configPath);
|
|
1637
|
-
} catch (error) {
|
|
1638
|
-
return errorResult(this.id, configPath, error.message);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
syncSkills(skills) {
|
|
1642
|
-
const skillsDir = path10.join(homeDir(), ".qwen", "skills");
|
|
1643
|
-
if (!fs6.existsSync(skillsDir)) {
|
|
1644
|
-
fs6.mkdirSync(skillsDir, { recursive: true });
|
|
1645
|
-
}
|
|
1646
|
-
const existingFiles = fs6.readdirSync(skillsDir);
|
|
1647
|
-
const managedIds = new Set(skills.map((s) => s.id));
|
|
1648
|
-
for (const file of existingFiles) {
|
|
1649
|
-
if (file.endsWith(".md")) {
|
|
1650
|
-
const id = path10.basename(file, ".md");
|
|
1651
|
-
if (!managedIds.has(id)) {
|
|
1652
|
-
fs6.unlinkSync(path10.join(skillsDir, file));
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
for (const skill of skills) {
|
|
1657
|
-
const targetPath = path10.join(skillsDir, `${skill.id}.md`);
|
|
1658
|
-
fs6.writeFileSync(targetPath, skill.content, "utf-8");
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
// src/adapters/index.ts
|
|
1664
|
-
function getAdapters() {
|
|
1665
|
-
return [
|
|
1666
|
-
new ClaudeAdapter,
|
|
1667
|
-
new ClaudeDesktopAdapter,
|
|
1668
|
-
new CodexAdapter,
|
|
1669
|
-
new CursorAdapter,
|
|
1670
|
-
new ClineAdapter,
|
|
1671
|
-
new OpenCodeAdapter,
|
|
1672
|
-
new KiroAdapter,
|
|
1673
|
-
new VsCodeAdapter,
|
|
1674
|
-
new QwenAdapter
|
|
1675
|
-
];
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
// src/core/managed-index.ts
|
|
1679
|
-
function defaultManagedIndex() {
|
|
1680
|
-
return {
|
|
1681
|
-
schemaVersion: 1,
|
|
1682
|
-
managed: {}
|
|
1683
|
-
};
|
|
1684
|
-
}
|
|
1685
|
-
function loadManagedIndex(filePath = getManagedIndexPath()) {
|
|
1686
|
-
const raw = readJsonFile(filePath, defaultManagedIndex());
|
|
1687
|
-
if (!raw || typeof raw !== "object") {
|
|
1688
|
-
return defaultManagedIndex();
|
|
1689
|
-
}
|
|
1690
|
-
if (raw.schemaVersion !== 1) {
|
|
1691
|
-
return defaultManagedIndex();
|
|
1692
|
-
}
|
|
1693
|
-
return raw;
|
|
1694
|
-
}
|
|
1695
|
-
function saveManagedIndex(index, filePath = getManagedIndexPath()) {
|
|
1696
|
-
writeJsonAtomic(filePath, index);
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
// src/core/sync.ts
|
|
1700
|
-
function getGatewayUrl(config) {
|
|
1701
|
-
return `http://127.0.0.1:${config.gateway.port}/mcp`;
|
|
1702
|
-
}
|
|
1703
|
-
function buildProjectScopes(config) {
|
|
1704
|
-
const globallyEnabled = new Set(Object.entries(config.servers).filter(([, spec]) => isServerEnabled(spec)).map(([name]) => name));
|
|
1705
|
-
return Object.values(config.projects ?? {}).map((project) => {
|
|
1706
|
-
const disabledServerNames = (project.disabledServers ?? []).filter((name) => globallyEnabled.has(name)).map((name) => managedGatewayEntryName(name));
|
|
1707
|
-
return { path: project.path, disabledServerNames };
|
|
1708
|
-
});
|
|
1709
|
-
}
|
|
1710
|
-
function buildManagedEntries(config, gatewayUrl, localToken) {
|
|
1711
|
-
const entries = Object.entries(config.servers);
|
|
1712
|
-
return entries.map(([name, spec]) => ({
|
|
1713
|
-
name: `${name} (mcpx)`,
|
|
1714
|
-
url: `${gatewayUrl}?upstream=${encodeURIComponent(name)}`,
|
|
1715
|
-
headers: {
|
|
1716
|
-
Authorization: `Bearer ${localToken}`
|
|
1717
|
-
},
|
|
1718
|
-
enabled: isServerEnabled(spec)
|
|
1719
|
-
}));
|
|
1720
|
-
}
|
|
1721
|
-
function isClientInternalServerCommand(candidate) {
|
|
1722
|
-
return candidate.spec.transport === "stdio" && /\.app\/Contents\//.test(candidate.spec.command ?? "");
|
|
1723
|
-
}
|
|
1724
|
-
function syncAllClients(config, secrets, targetClients) {
|
|
1725
|
-
const adapters = getAdapters();
|
|
1726
|
-
const managedIndexPath = getManagedIndexPath();
|
|
1727
|
-
const managedIndex = loadManagedIndex(managedIndexPath);
|
|
1728
|
-
const filteredAdapters = targetClients && targetClients.length > 0 ? adapters.filter((adapter) => targetClients.includes(adapter.id)) : adapters;
|
|
1729
|
-
const imports = {
|
|
1730
|
-
imported: [],
|
|
1731
|
-
duplicates: [],
|
|
1732
|
-
skipped: [],
|
|
1733
|
-
conflicts: [],
|
|
1734
|
-
errors: []
|
|
1735
|
-
};
|
|
1736
|
-
const sourceEntriesToRemove = new Map;
|
|
1737
|
-
for (const adapter of filteredAdapters) {
|
|
1738
|
-
try {
|
|
1739
|
-
const scan = adapter.scanForImports(config, managedIndex);
|
|
1740
|
-
imports.skipped.push(...scan.skipped);
|
|
1741
|
-
for (const candidate of scan.candidates) {
|
|
1742
|
-
if (isClientInternalServerCommand(candidate)) {
|
|
1743
|
-
imports.skipped.push({
|
|
1744
|
-
clientId: candidate.clientId,
|
|
1745
|
-
configPath: candidate.configPath,
|
|
1746
|
-
sourceEntryName: candidate.sourceEntryName,
|
|
1747
|
-
serverName: candidate.serverName,
|
|
1748
|
-
reason: "client-internal server"
|
|
1749
|
-
});
|
|
1750
|
-
continue;
|
|
1751
|
-
}
|
|
1752
|
-
const existing = config.servers[candidate.serverName];
|
|
1753
|
-
if (!existing) {
|
|
1754
|
-
config.servers[candidate.serverName] = candidate.spec;
|
|
1755
|
-
imports.imported.push({
|
|
1756
|
-
clientId: candidate.clientId,
|
|
1757
|
-
configPath: candidate.configPath,
|
|
1758
|
-
sourceEntryName: candidate.sourceEntryName,
|
|
1759
|
-
serverName: candidate.serverName
|
|
1760
|
-
});
|
|
1761
|
-
if (!sourceEntriesToRemove.has(candidate.clientId)) {
|
|
1762
|
-
sourceEntriesToRemove.set(candidate.clientId, new Set);
|
|
1763
|
-
}
|
|
1764
|
-
sourceEntriesToRemove.get(candidate.clientId)?.add(candidate.sourceEntryName);
|
|
1765
|
-
continue;
|
|
1766
|
-
}
|
|
1767
|
-
if (serverSpecsEqual(existing, candidate.spec)) {
|
|
1768
|
-
imports.duplicates.push({
|
|
1769
|
-
clientId: candidate.clientId,
|
|
1770
|
-
configPath: candidate.configPath,
|
|
1771
|
-
sourceEntryName: candidate.sourceEntryName,
|
|
1772
|
-
serverName: candidate.serverName
|
|
1773
|
-
});
|
|
1774
|
-
if (!sourceEntriesToRemove.has(candidate.clientId)) {
|
|
1775
|
-
sourceEntriesToRemove.set(candidate.clientId, new Set);
|
|
1776
|
-
}
|
|
1777
|
-
sourceEntriesToRemove.get(candidate.clientId)?.add(candidate.sourceEntryName);
|
|
1778
|
-
continue;
|
|
1779
|
-
}
|
|
1780
|
-
imports.conflicts.push({
|
|
1781
|
-
clientId: candidate.clientId,
|
|
1782
|
-
configPath: candidate.configPath,
|
|
1783
|
-
sourceEntryName: candidate.sourceEntryName,
|
|
1784
|
-
serverName: candidate.serverName,
|
|
1785
|
-
message: `Server "${candidate.serverName}" already exists in mcpx with a different configuration.`
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
} catch (error) {
|
|
1789
|
-
imports.errors.push({
|
|
1790
|
-
clientId: adapter.id,
|
|
1791
|
-
configPath: adapter.detectConfigPath() ?? undefined,
|
|
1792
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1793
|
-
});
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
const localToken = ensureGatewayToken(config, secrets);
|
|
1797
|
-
const gatewayUrl = getGatewayUrl(config);
|
|
1798
|
-
const managedEntries = buildManagedEntries(config, gatewayUrl, localToken);
|
|
1799
|
-
const skills = listSkills();
|
|
1800
|
-
const results = [];
|
|
1801
|
-
for (const adapter of filteredAdapters) {
|
|
1802
|
-
if (adapter.syncSkills) {
|
|
1803
|
-
try {
|
|
1804
|
-
adapter.syncSkills(skills);
|
|
1805
|
-
} catch (error) {}
|
|
1806
|
-
}
|
|
1807
|
-
if (!adapter.supportsHttp()) {
|
|
1808
|
-
results.push({
|
|
1809
|
-
clientId: adapter.id,
|
|
1810
|
-
status: "UNSUPPORTED_HTTP",
|
|
1811
|
-
message: "Client adapter does not support MCP over HTTP."
|
|
1812
|
-
});
|
|
1813
|
-
continue;
|
|
1814
|
-
}
|
|
1815
|
-
const result = adapter.syncGateway(config, {
|
|
1816
|
-
managedEntries,
|
|
1817
|
-
managedIndex,
|
|
1818
|
-
managedIndexPath,
|
|
1819
|
-
sourceEntriesToRemove: Array.from(sourceEntriesToRemove.get(adapter.id) ?? []),
|
|
1820
|
-
projectScopes: buildProjectScopes(config)
|
|
1821
|
-
});
|
|
1822
|
-
results.push(result);
|
|
1823
|
-
}
|
|
1824
|
-
for (const result of results) {
|
|
1825
|
-
config.clients[result.clientId] = {
|
|
1826
|
-
status: result.status,
|
|
1827
|
-
configPath: result.configPath,
|
|
1828
|
-
message: result.message,
|
|
1829
|
-
lastSyncAt: new Date().toISOString()
|
|
1830
|
-
};
|
|
1831
|
-
}
|
|
1832
|
-
saveManagedIndex(managedIndex, managedIndexPath);
|
|
1833
|
-
saveConfig(config);
|
|
1834
|
-
const hasErrors = results.some((result) => result.status === "ERROR") || imports.conflicts.length > 0 || imports.errors.length > 0;
|
|
1835
|
-
return {
|
|
1836
|
-
gatewayUrl,
|
|
1837
|
-
imports,
|
|
1838
|
-
results,
|
|
1839
|
-
hasErrors
|
|
1840
|
-
};
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
248
|
// src/core/server-auth.ts
|
|
1844
249
|
function normalizeNamePart(value) {
|
|
1845
250
|
return value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "") || "value";
|
|
@@ -1951,20 +356,20 @@ function maybePrefixBearer(target, value, raw = false) {
|
|
|
1951
356
|
|
|
1952
357
|
// src/core/spawn-env.ts
|
|
1953
358
|
import os from "node:os";
|
|
1954
|
-
import
|
|
1955
|
-
import
|
|
359
|
+
import path from "node:path";
|
|
360
|
+
import fs from "node:fs";
|
|
1956
361
|
function existingNvmCurrentBin() {
|
|
1957
|
-
const nvmDir = process.env.NVM_DIR ??
|
|
1958
|
-
const current =
|
|
1959
|
-
return
|
|
362
|
+
const nvmDir = process.env.NVM_DIR ?? path.join(os.homedir(), ".nvm");
|
|
363
|
+
const current = path.join(nvmDir, "current", "bin");
|
|
364
|
+
return fs.existsSync(current) ? current : null;
|
|
1960
365
|
}
|
|
1961
366
|
function buildEnrichedPath(current = process.env.PATH ?? "") {
|
|
1962
367
|
const candidates = [
|
|
1963
368
|
...current.split(":"),
|
|
1964
369
|
"/opt/homebrew/bin",
|
|
1965
370
|
"/usr/local/bin",
|
|
1966
|
-
|
|
1967
|
-
|
|
371
|
+
path.join(os.homedir(), ".bun", "bin"),
|
|
372
|
+
path.join(os.homedir(), ".local", "bin"),
|
|
1968
373
|
existingNvmCurrentBin()
|
|
1969
374
|
].filter((entry) => Boolean(entry));
|
|
1970
375
|
const seen = new Set;
|
|
@@ -2273,12 +678,12 @@ async function getOAuthAccessToken(serverName, spec, secrets, options = {}) {
|
|
|
2273
678
|
}
|
|
2274
679
|
|
|
2275
680
|
// src/version.ts
|
|
2276
|
-
var APP_VERSION = "0.1.
|
|
681
|
+
var APP_VERSION = "0.1.93";
|
|
2277
682
|
|
|
2278
683
|
// src/core/update.ts
|
|
2279
|
-
import
|
|
2280
|
-
import
|
|
2281
|
-
import { execFileSync
|
|
684
|
+
import fs2 from "node:fs";
|
|
685
|
+
import path2 from "node:path";
|
|
686
|
+
import { execFileSync } from "node:child_process";
|
|
2282
687
|
function compareVersions(a, b) {
|
|
2283
688
|
const partsA = a.split(".").map(Number);
|
|
2284
689
|
const partsB = b.split(".").map(Number);
|
|
@@ -2294,7 +699,7 @@ function compareVersions(a, b) {
|
|
|
2294
699
|
}
|
|
2295
700
|
async function checkForUpdates() {
|
|
2296
701
|
try {
|
|
2297
|
-
const output =
|
|
702
|
+
const output = execFileSync("npm", ["view", "@kwonye/mcpx", "version"], {
|
|
2298
703
|
encoding: "utf8",
|
|
2299
704
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2300
705
|
}).trim();
|
|
@@ -2316,17 +721,17 @@ async function checkForUpdates() {
|
|
|
2316
721
|
}
|
|
2317
722
|
function getStagedUpdate() {
|
|
2318
723
|
const stagedPath = getStagedVersionPath();
|
|
2319
|
-
if (!
|
|
724
|
+
if (!fs2.existsSync(stagedPath)) {
|
|
2320
725
|
return null;
|
|
2321
726
|
}
|
|
2322
727
|
try {
|
|
2323
|
-
const raw =
|
|
728
|
+
const raw = fs2.readFileSync(stagedPath, "utf8");
|
|
2324
729
|
const info = JSON.parse(raw);
|
|
2325
730
|
if (!info.version || !info.cliPath) {
|
|
2326
731
|
return null;
|
|
2327
732
|
}
|
|
2328
|
-
const absoluteCliPath =
|
|
2329
|
-
if (!
|
|
733
|
+
const absoluteCliPath = path2.isAbsolute(info.cliPath) ? info.cliPath : path2.join(getUpdatesDir(), info.cliPath);
|
|
734
|
+
if (!fs2.existsSync(absoluteCliPath)) {
|
|
2330
735
|
return null;
|
|
2331
736
|
}
|
|
2332
737
|
return {
|
|
@@ -2342,18 +747,18 @@ function stageUpdate(version, cliPath) {
|
|
|
2342
747
|
ensureDir(updatesDir);
|
|
2343
748
|
const stagedInfo = {
|
|
2344
749
|
version,
|
|
2345
|
-
cliPath:
|
|
750
|
+
cliPath: path2.isAbsolute(cliPath) ? path2.relative(updatesDir, cliPath) : cliPath,
|
|
2346
751
|
stagedAt: new Date().toISOString()
|
|
2347
752
|
};
|
|
2348
|
-
|
|
753
|
+
fs2.writeFileSync(getStagedVersionPath(), JSON.stringify(stagedInfo, null, 2), "utf8");
|
|
2349
754
|
}
|
|
2350
755
|
function shouldUseStagedCli(stagedVersion, currentVersion) {
|
|
2351
756
|
return compareVersions(stagedVersion, currentVersion) > 0;
|
|
2352
757
|
}
|
|
2353
758
|
function clearStagedUpdate() {
|
|
2354
759
|
const stagedPath = getStagedVersionPath();
|
|
2355
|
-
if (
|
|
2356
|
-
|
|
760
|
+
if (fs2.existsSync(stagedPath)) {
|
|
761
|
+
fs2.unlinkSync(stagedPath);
|
|
2357
762
|
}
|
|
2358
763
|
}
|
|
2359
764
|
function getStagedCliPath() {
|
|
@@ -2362,31 +767,31 @@ function getStagedCliPath() {
|
|
|
2362
767
|
}
|
|
2363
768
|
function removeOldVersions(keepCount = 2) {
|
|
2364
769
|
const updatesDir = getUpdatesDir();
|
|
2365
|
-
if (!
|
|
770
|
+
if (!fs2.existsSync(updatesDir)) {
|
|
2366
771
|
return;
|
|
2367
772
|
}
|
|
2368
|
-
const entries =
|
|
773
|
+
const entries = fs2.readdirSync(updatesDir, { withFileTypes: true });
|
|
2369
774
|
const versionDirs = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith("v")).map((entry) => entry.name).sort((a, b) => compareVersions(b.slice(1), a.slice(1)));
|
|
2370
775
|
if (versionDirs.length <= keepCount) {
|
|
2371
776
|
return;
|
|
2372
777
|
}
|
|
2373
778
|
for (const oldVersion of versionDirs.slice(keepCount)) {
|
|
2374
|
-
const oldPath =
|
|
779
|
+
const oldPath = path2.join(updatesDir, oldVersion);
|
|
2375
780
|
try {
|
|
2376
|
-
|
|
781
|
+
fs2.rmSync(oldPath, { recursive: true, force: true });
|
|
2377
782
|
} catch {}
|
|
2378
783
|
}
|
|
2379
784
|
}
|
|
2380
785
|
|
|
2381
786
|
// src/core/update-manager.ts
|
|
2382
|
-
import
|
|
2383
|
-
import
|
|
787
|
+
import fs3 from "node:fs";
|
|
788
|
+
import path3 from "node:path";
|
|
2384
789
|
import { execSync, spawn } from "node:child_process";
|
|
2385
790
|
function acquireLock() {
|
|
2386
791
|
const lockPath = getUpdateLockPath();
|
|
2387
792
|
try {
|
|
2388
|
-
ensureDir(
|
|
2389
|
-
|
|
793
|
+
ensureDir(path3.dirname(lockPath));
|
|
794
|
+
fs3.writeFileSync(lockPath, `${process.pid}
|
|
2390
795
|
`, { flag: "wx" });
|
|
2391
796
|
return true;
|
|
2392
797
|
} catch {
|
|
@@ -2396,25 +801,25 @@ function acquireLock() {
|
|
|
2396
801
|
function releaseLock() {
|
|
2397
802
|
const lockPath = getUpdateLockPath();
|
|
2398
803
|
try {
|
|
2399
|
-
|
|
804
|
+
fs3.unlinkSync(lockPath);
|
|
2400
805
|
} catch {}
|
|
2401
806
|
}
|
|
2402
807
|
function isUpdateInProgress() {
|
|
2403
808
|
const lockPath = getUpdateLockPath();
|
|
2404
|
-
if (!
|
|
809
|
+
if (!fs3.existsSync(lockPath)) {
|
|
2405
810
|
return false;
|
|
2406
811
|
}
|
|
2407
812
|
try {
|
|
2408
|
-
const raw =
|
|
813
|
+
const raw = fs3.readFileSync(lockPath, "utf8").trim();
|
|
2409
814
|
const pid = Number(raw);
|
|
2410
815
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
2411
|
-
|
|
816
|
+
fs3.unlinkSync(lockPath);
|
|
2412
817
|
return false;
|
|
2413
818
|
}
|
|
2414
819
|
process.kill(pid, 0);
|
|
2415
820
|
return true;
|
|
2416
821
|
} catch {
|
|
2417
|
-
|
|
822
|
+
fs3.unlinkSync(lockPath);
|
|
2418
823
|
return false;
|
|
2419
824
|
}
|
|
2420
825
|
}
|
|
@@ -2425,8 +830,8 @@ async function downloadAndStageUpdate() {
|
|
|
2425
830
|
}
|
|
2426
831
|
const targetVersion = updateStatus.latestVersion;
|
|
2427
832
|
const updatesDir = getUpdatesDir();
|
|
2428
|
-
const versionDir =
|
|
2429
|
-
if (
|
|
833
|
+
const versionDir = path3.join(updatesDir, `v${targetVersion}`);
|
|
834
|
+
if (fs3.existsSync(versionDir)) {
|
|
2430
835
|
const existingStaged = getStagedUpdate();
|
|
2431
836
|
if (existingStaged?.version === targetVersion) {
|
|
2432
837
|
return { success: false, error: `Version ${targetVersion} already staged` };
|
|
@@ -2438,8 +843,8 @@ async function downloadAndStageUpdate() {
|
|
|
2438
843
|
cwd: versionDir,
|
|
2439
844
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2440
845
|
});
|
|
2441
|
-
const tarballPath =
|
|
2442
|
-
if (!
|
|
846
|
+
const tarballPath = path3.join(versionDir, `kwonye-mcpx-${targetVersion}.tgz`);
|
|
847
|
+
if (!fs3.existsSync(tarballPath)) {
|
|
2443
848
|
throw new Error("Downloaded tarball not found");
|
|
2444
849
|
}
|
|
2445
850
|
execSync(`tar -xzf ${tarballPath} --strip-components=1`, {
|
|
@@ -2450,8 +855,8 @@ async function downloadAndStageUpdate() {
|
|
|
2450
855
|
cwd: versionDir,
|
|
2451
856
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2452
857
|
});
|
|
2453
|
-
const cliPath =
|
|
2454
|
-
if (!
|
|
858
|
+
const cliPath = path3.join(versionDir, "dist", "cli.js");
|
|
859
|
+
if (!fs3.existsSync(cliPath)) {
|
|
2455
860
|
throw new Error("CLI entry point not found in downloaded package");
|
|
2456
861
|
}
|
|
2457
862
|
stageUpdate(targetVersion, cliPath);
|
|
@@ -2459,7 +864,7 @@ async function downloadAndStageUpdate() {
|
|
|
2459
864
|
return { success: true, version: targetVersion };
|
|
2460
865
|
} catch (error) {
|
|
2461
866
|
try {
|
|
2462
|
-
|
|
867
|
+
fs3.rmSync(versionDir, { recursive: true, force: true });
|
|
2463
868
|
} catch {}
|
|
2464
869
|
return {
|
|
2465
870
|
success: false,
|
|
@@ -2537,7 +942,7 @@ function performRollback() {
|
|
|
2537
942
|
}
|
|
2538
943
|
|
|
2539
944
|
// src/core/daemon.ts
|
|
2540
|
-
import
|
|
945
|
+
import fs4 from "node:fs";
|
|
2541
946
|
import net from "node:net";
|
|
2542
947
|
import { spawn as spawn2 } from "node:child_process";
|
|
2543
948
|
|
|
@@ -3489,10 +1894,10 @@ function createGatewayServer(options) {
|
|
|
3489
1894
|
|
|
3490
1895
|
// src/core/daemon.ts
|
|
3491
1896
|
function readPidFromFile(pidPath = getPidPath()) {
|
|
3492
|
-
if (!
|
|
1897
|
+
if (!fs4.existsSync(pidPath)) {
|
|
3493
1898
|
return null;
|
|
3494
1899
|
}
|
|
3495
|
-
const raw =
|
|
1900
|
+
const raw = fs4.readFileSync(pidPath, "utf8").trim();
|
|
3496
1901
|
if (!raw) {
|
|
3497
1902
|
return null;
|
|
3498
1903
|
}
|
|
@@ -3594,7 +1999,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3594
1999
|
};
|
|
3595
2000
|
}
|
|
3596
2001
|
try {
|
|
3597
|
-
|
|
2002
|
+
fs4.unlinkSync(getPidPath());
|
|
3598
2003
|
} catch {}
|
|
3599
2004
|
}
|
|
3600
2005
|
const port = await resolveGatewayPort(config);
|
|
@@ -3603,7 +2008,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3603
2008
|
const logPath = getLogPath();
|
|
3604
2009
|
ensureParentDir(pidPath);
|
|
3605
2010
|
ensureParentDir(logPath);
|
|
3606
|
-
const logFd =
|
|
2011
|
+
const logFd = fs4.openSync(logPath, "a", 384);
|
|
3607
2012
|
const child = spawn2(process.execPath, [cliPath, "daemon", "run", "--port", String(port)], {
|
|
3608
2013
|
detached: true,
|
|
3609
2014
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -3613,7 +2018,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3613
2018
|
}
|
|
3614
2019
|
});
|
|
3615
2020
|
child.unref();
|
|
3616
|
-
|
|
2021
|
+
fs4.writeFileSync(pidPath, `${child.pid}
|
|
3617
2022
|
`, { mode: 384 });
|
|
3618
2023
|
startBackgroundUpdateCheck();
|
|
3619
2024
|
await waitForGatewayReady(port, token);
|
|
@@ -3637,7 +2042,7 @@ function stopDaemon() {
|
|
|
3637
2042
|
process.kill(pid, "SIGTERM");
|
|
3638
2043
|
} catch {}
|
|
3639
2044
|
try {
|
|
3640
|
-
|
|
2045
|
+
fs4.unlinkSync(pidPath);
|
|
3641
2046
|
} catch {}
|
|
3642
2047
|
return {
|
|
3643
2048
|
stopped: true,
|
|
@@ -3650,10 +2055,10 @@ async function restartDaemon(config, cliPath, secrets) {
|
|
|
3650
2055
|
}
|
|
3651
2056
|
function readDaemonLogs(maxLines = 200) {
|
|
3652
2057
|
const logPath = getLogPath();
|
|
3653
|
-
if (!
|
|
2058
|
+
if (!fs4.existsSync(logPath)) {
|
|
3654
2059
|
return "";
|
|
3655
2060
|
}
|
|
3656
|
-
const lines =
|
|
2061
|
+
const lines = fs4.readFileSync(logPath, "utf8").split(`
|
|
3657
2062
|
`);
|
|
3658
2063
|
return lines.slice(Math.max(lines.length - maxLines, 0)).join(`
|
|
3659
2064
|
`);
|
|
@@ -3791,4 +2196,4 @@ async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(
|
|
|
3791
2196
|
};
|
|
3792
2197
|
}
|
|
3793
2198
|
|
|
3794
|
-
export {
|
|
2199
|
+
export { probeHttpAuthRequirement, fetchRegistryServerDetail, selectBestPackage, extractRequiredInputs, mapRegistryToSpec, toSecretRef, secretRefName, defaultAuthSecretName, resolveAuthTarget, applyAuthReference, removeAuthReference, listAuthBindings, maybePrefixBearer, buildEnrichedPath, isOAuthReference, oauthReferenceServerName, runOAuthLogin, getOAuthAccessToken, APP_VERSION, compareVersions, checkForUpdates, getStagedUpdate, shouldUseStagedCli, clearStagedUpdate, getStagedCliPath, startBackgroundUpdateCheck, runBackgroundUpdate, performUpdate, performRollback, getDaemonStatus, startDaemon, stopDaemon, restartDaemon, readDaemonLogs, runDaemonForeground, STATUS_CLIENTS, buildStatusReport };
|