@xmemo/client 0.4.138 → 0.4.141
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 +7 -0
- package/package.json +1 -1
- package/src/cli.js +1074 -11
package/README.md
CHANGED
|
@@ -310,6 +310,11 @@ absence of embedded token values.
|
|
|
310
310
|
|
|
311
311
|
### Cursor
|
|
312
312
|
|
|
313
|
+
Cursor marketplace plugin assets live in `.cursor-plugin/marketplace.json` and
|
|
314
|
+
`plugins/xmemo/`. The marketplace plugin is OAuth-first and its `mcp.json` stores
|
|
315
|
+
only `https://xmemo.dev/mcp`; it must not contain `Authorization`, `Bearer`, or
|
|
316
|
+
`XMEMO_KEY`.
|
|
317
|
+
|
|
313
318
|
Recommended Cursor setup:
|
|
314
319
|
|
|
315
320
|
```bash
|
|
@@ -331,6 +336,8 @@ Cursor configs include
|
|
|
331
336
|
non-secret and stored under the user's XMemo CLI config directory. By default,
|
|
332
337
|
the setup prompt also installs a Cursor behavior profile at
|
|
333
338
|
`~/.cursor/memory-profile.md`; answer `n` or pass `--no-profile` to skip it.
|
|
339
|
+
Use this direct-key setup only for local/manual installs where Cursor OAuth is
|
|
340
|
+
unavailable; public plugin submission should use the OAuth-first plugin config.
|
|
334
341
|
|
|
335
342
|
### Gemini CLI
|
|
336
343
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const PACKAGE_NAME = '@xmemo/client';
|
|
|
10
10
|
const FALLBACK_PACKAGE_NAME = '@yonro/xmemo-client';
|
|
11
11
|
const COMMAND_NAME = 'xmemo';
|
|
12
12
|
const LEGACY_COMMAND_NAME = 'memory-os';
|
|
13
|
-
const CLI_VERSION = '0.4.
|
|
13
|
+
const CLI_VERSION = '0.4.141';
|
|
14
14
|
const DEFAULT_SERVICE_URL = 'https://xmemo.dev';
|
|
15
15
|
const TOKEN_ENV_VAR = 'XMEMO_KEY';
|
|
16
16
|
const LEGACY_TOKEN_ENV_VAR = 'MEMORY_OS_MCP_TOKEN';
|
|
@@ -64,6 +64,118 @@ const MCP_CLIENTS = new Map([
|
|
|
64
64
|
buildSnippet: antigravityJsonSnippet,
|
|
65
65
|
writeConfig: mergeAntigravityMcpConfig,
|
|
66
66
|
configKind: 'json'
|
|
67
|
+
}],
|
|
68
|
+
['antigravity-ide', {
|
|
69
|
+
label: 'Antigravity IDE',
|
|
70
|
+
defaultConfigPath: defaultAntigravityIdeConfigPath,
|
|
71
|
+
buildSnippet: antigravityIdeJsonSnippet,
|
|
72
|
+
writeConfig: mergeAntigravityIdeMcpConfig,
|
|
73
|
+
configKind: 'json'
|
|
74
|
+
}],
|
|
75
|
+
['antigravity2', {
|
|
76
|
+
label: 'Antigravity 2.0',
|
|
77
|
+
defaultConfigPath: defaultAntigravity2ConfigPath,
|
|
78
|
+
buildSnippet: antigravity2JsonSnippet,
|
|
79
|
+
writeConfig: mergeAntigravity2McpConfig,
|
|
80
|
+
configKind: 'json'
|
|
81
|
+
}],
|
|
82
|
+
['antigravity-cli', {
|
|
83
|
+
label: 'Antigravity CLI',
|
|
84
|
+
defaultConfigPath: defaultAntigravityCliConfigPath,
|
|
85
|
+
buildSnippet: antigravityCliJsonSnippet,
|
|
86
|
+
writeConfig: mergeAntigravityCliMcpConfig,
|
|
87
|
+
configKind: 'json'
|
|
88
|
+
}],
|
|
89
|
+
['windsurf', {
|
|
90
|
+
label: 'Windsurf',
|
|
91
|
+
defaultConfigPath: defaultWindsurfConfigPath,
|
|
92
|
+
buildSnippet: windsurfJsonSnippet,
|
|
93
|
+
writeConfig: mergeWindsurfMcpConfig,
|
|
94
|
+
configKind: 'json'
|
|
95
|
+
}],
|
|
96
|
+
['cline', {
|
|
97
|
+
label: 'Cline',
|
|
98
|
+
defaultConfigPath: defaultClineConfigPath,
|
|
99
|
+
buildSnippet: clineJsonSnippet,
|
|
100
|
+
writeConfig: mergeClineMcpConfig,
|
|
101
|
+
configKind: 'json'
|
|
102
|
+
}],
|
|
103
|
+
['continue', {
|
|
104
|
+
label: 'Continue',
|
|
105
|
+
defaultConfigPath: defaultContinueConfigPath,
|
|
106
|
+
buildSnippet: continueJsonSnippet,
|
|
107
|
+
writeConfig: mergeContinueMcpConfig,
|
|
108
|
+
configKind: 'json'
|
|
109
|
+
}],
|
|
110
|
+
['claude-desktop', {
|
|
111
|
+
label: 'Claude Desktop',
|
|
112
|
+
defaultConfigPath: defaultClaudeConfigPath,
|
|
113
|
+
buildSnippet: claudeJsonSnippet,
|
|
114
|
+
writeConfig: mergeClaudeMcpConfig,
|
|
115
|
+
configKind: 'json'
|
|
116
|
+
}],
|
|
117
|
+
['openclaw', {
|
|
118
|
+
label: 'OpenClaw',
|
|
119
|
+
defaultConfigPath: defaultOpenclawConfigPath,
|
|
120
|
+
buildSnippet: openclawJsonSnippet,
|
|
121
|
+
writeConfig: mergeOpenclawMcpConfig,
|
|
122
|
+
configKind: 'json'
|
|
123
|
+
}],
|
|
124
|
+
['kiro', {
|
|
125
|
+
label: 'Kiro',
|
|
126
|
+
defaultConfigPath: defaultKiroConfigPath,
|
|
127
|
+
buildSnippet: kiroJsonSnippet,
|
|
128
|
+
writeConfig: mergeKiroMcpConfig,
|
|
129
|
+
configKind: 'json'
|
|
130
|
+
}],
|
|
131
|
+
['zed', {
|
|
132
|
+
label: 'Zed',
|
|
133
|
+
defaultConfigPath: defaultZedConfigPath,
|
|
134
|
+
buildSnippet: zedJsonSnippet,
|
|
135
|
+
writeConfig: mergeZedMcpConfig,
|
|
136
|
+
configKind: 'json'
|
|
137
|
+
}],
|
|
138
|
+
['jetbrains', {
|
|
139
|
+
label: 'JetBrains',
|
|
140
|
+
defaultConfigPath: defaultJetbrainsConfigPath,
|
|
141
|
+
buildSnippet: jetbrainsJsonSnippet,
|
|
142
|
+
writeConfig: mergeJetbrainsMcpConfig,
|
|
143
|
+
configKind: 'json'
|
|
144
|
+
}],
|
|
145
|
+
['opencode', {
|
|
146
|
+
label: 'OpenCode',
|
|
147
|
+
defaultConfigPath: defaultOpencodeConfigPath,
|
|
148
|
+
buildSnippet: opencodeJsonSnippet,
|
|
149
|
+
writeConfig: mergeOpencodeMcpConfig,
|
|
150
|
+
configKind: 'json'
|
|
151
|
+
}],
|
|
152
|
+
['hermes', {
|
|
153
|
+
label: 'Hermes',
|
|
154
|
+
defaultConfigPath: defaultHermesConfigPath,
|
|
155
|
+
buildSnippet: hermesYamlSnippet,
|
|
156
|
+
writeConfig: mergeHermesMcpConfig,
|
|
157
|
+
configKind: 'yaml'
|
|
158
|
+
}],
|
|
159
|
+
['qwen', {
|
|
160
|
+
label: 'Qwen',
|
|
161
|
+
defaultConfigPath: defaultQwenConfigPath,
|
|
162
|
+
buildSnippet: qwenJsonSnippet,
|
|
163
|
+
writeConfig: mergeQwenMcpConfig,
|
|
164
|
+
configKind: 'json'
|
|
165
|
+
}],
|
|
166
|
+
['trae', {
|
|
167
|
+
label: 'Trae',
|
|
168
|
+
defaultConfigPath: defaultTraeConfigPath,
|
|
169
|
+
buildSnippet: traeJsonSnippet,
|
|
170
|
+
writeConfig: mergeTraeMcpConfig,
|
|
171
|
+
configKind: 'json'
|
|
172
|
+
}],
|
|
173
|
+
['claude-code', {
|
|
174
|
+
label: 'Claude Code',
|
|
175
|
+
defaultConfigPath: defaultClaudecodeConfigPath,
|
|
176
|
+
buildSnippet: claudecodeJsonSnippet,
|
|
177
|
+
writeConfig: mergeClaudecodeMcpConfig,
|
|
178
|
+
configKind: 'json'
|
|
67
179
|
}]
|
|
68
180
|
]);
|
|
69
181
|
|
|
@@ -74,7 +186,29 @@ const SETUP_CLIENT_ALIASES = new Map([
|
|
|
74
186
|
['copilot-cli', 'copilot-cli'],
|
|
75
187
|
['gemini', 'gemini-cli'],
|
|
76
188
|
['gemini-cli', 'gemini-cli'],
|
|
77
|
-
['antigravity', 'antigravity']
|
|
189
|
+
['antigravity', 'antigravity'],
|
|
190
|
+
['antigravity-ide', 'antigravity-ide'],
|
|
191
|
+
['antigravity2', 'antigravity2'],
|
|
192
|
+
['antigravity-cli', 'antigravity-cli'],
|
|
193
|
+
['windsurf', 'windsurf'],
|
|
194
|
+
['cline', 'cline'],
|
|
195
|
+
['continue', 'continue'],
|
|
196
|
+
['claude', 'claude-desktop'],
|
|
197
|
+
['claude-desktop', 'claude-desktop'],
|
|
198
|
+
['openclaw', 'openclaw'],
|
|
199
|
+
['kiro', 'kiro'],
|
|
200
|
+
['zed', 'zed'],
|
|
201
|
+
['jetbrains', 'jetbrains'],
|
|
202
|
+
['opencode', 'opencode'],
|
|
203
|
+
['hermes', 'hermes'],
|
|
204
|
+
['qwen', 'qwen'],
|
|
205
|
+
['qwencli', 'qwen'],
|
|
206
|
+
['qwen-cli', 'qwen'],
|
|
207
|
+
['trae', 'trae'],
|
|
208
|
+
['claude-code', 'claude-code'],
|
|
209
|
+
['claudecode', 'claude-code'],
|
|
210
|
+
['claude-cli', 'claude-code'],
|
|
211
|
+
['claudecode-cli', 'claude-code']
|
|
78
212
|
]);
|
|
79
213
|
|
|
80
214
|
class UsageError extends Error {
|
|
@@ -393,13 +527,27 @@ async function setupCommand(args, io) {
|
|
|
393
527
|
const baseUrl = normalizeBaseUrl(baseUrlOption(optionArgs, io.env));
|
|
394
528
|
const outputJson = hasFlag(optionArgs, '--json');
|
|
395
529
|
const shortClientSetup = Boolean(positionalClientId);
|
|
396
|
-
const
|
|
530
|
+
const setupAll = hasFlag(optionArgs, '--all');
|
|
531
|
+
|
|
532
|
+
let clientId = null;
|
|
533
|
+
try {
|
|
534
|
+
clientId = normalizeSetupClientId(positionalClientId ?? optionValue(optionArgs, '--client'));
|
|
535
|
+
} catch (error) {
|
|
536
|
+
if (!setupAll) {
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (setupAll && clientId) {
|
|
542
|
+
throw new UsageError('Cannot specify both --all and a specific client.');
|
|
543
|
+
}
|
|
544
|
+
|
|
397
545
|
const dryRun = hasFlag(optionArgs, '--dry-run') || hasFlag(optionArgs, '--preview');
|
|
398
|
-
const writeConfig = !dryRun && (hasFlag(optionArgs, '--write') || hasFlag(optionArgs, '--yes') || shortClientSetup);
|
|
546
|
+
const writeConfig = !dryRun && (hasFlag(optionArgs, '--write') || hasFlag(optionArgs, '--yes') || shortClientSetup || (setupAll && (hasFlag(optionArgs, '--write') || hasFlag(optionArgs, '--yes'))));
|
|
399
547
|
const timeoutMs = parsePositiveInteger(optionValue(optionArgs, '--timeout-ms') ?? '5000', '--timeout-ms');
|
|
400
548
|
|
|
401
|
-
if (writeConfig && !clientId) {
|
|
402
|
-
throw new UsageError(`Setup --write requires --client <${supportedSetupClientIds().join('|')}> so the CLI never writes broad config implicitly.`);
|
|
549
|
+
if (writeConfig && !clientId && !setupAll) {
|
|
550
|
+
throw new UsageError(`Setup --write requires --client <${supportedSetupClientIds().join('|')}> or --all so the CLI never writes broad config implicitly.`);
|
|
403
551
|
}
|
|
404
552
|
|
|
405
553
|
const discoveryUrl = endpointUrl(baseUrl, '/.well-known/memory-os.json');
|
|
@@ -412,7 +560,46 @@ async function setupCommand(args, io) {
|
|
|
412
560
|
const status = await fetchJson(statusUrl, timeoutMs, io);
|
|
413
561
|
const setupPlan = buildSetupPlan({ baseUrl, discoveryUrl, statusUrl, discovery, status });
|
|
414
562
|
|
|
415
|
-
if (
|
|
563
|
+
if (setupAll) {
|
|
564
|
+
setupPlan.detectedClients = [];
|
|
565
|
+
const scanIds = ['codex', 'cursor', 'copilot-cli', 'gemini-cli', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude-desktop'];
|
|
566
|
+
for (const scanId of scanIds) {
|
|
567
|
+
const detection = await detectClient(scanId, io.env);
|
|
568
|
+
if (detection.detected) {
|
|
569
|
+
let clientPlan;
|
|
570
|
+
if (scanId === 'copilot-cli') {
|
|
571
|
+
const proxyPort = parsePositiveInteger(optionValue(optionArgs, '--port') ?? String(DEFAULT_PROXY_PORT), '--port');
|
|
572
|
+
clientPlan = copilotSetupPlan(setupPlan.mcpUrl, proxyPort, io.env);
|
|
573
|
+
clientPlan.configPath = detection.path;
|
|
574
|
+
if (writeConfig) {
|
|
575
|
+
await mergeCopilotMcpConfig(clientPlan.configPath, clientPlan.proxyUrl);
|
|
576
|
+
clientPlan.written = true;
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
const client = MCP_CLIENTS.get(scanId);
|
|
580
|
+
const identity = writeConfig ? await agentIdentity(scanId, io.env) : envReferenceIdentity(scanId);
|
|
581
|
+
clientPlan = clientSetupPlan(scanId, client, setupPlan.mcpUrl, io.env, identity);
|
|
582
|
+
clientPlan.configPath = detection.path;
|
|
583
|
+
if (writeConfig) {
|
|
584
|
+
await client.writeConfig(clientPlan.configPath, setupPlan.mcpUrl, identity);
|
|
585
|
+
clientPlan.written = true;
|
|
586
|
+
if (profileClientConfig(scanId)) {
|
|
587
|
+
const installProfile = hasFlag(optionArgs, '--yes') || hasFlag(optionArgs, '--profile');
|
|
588
|
+
if (installProfile) {
|
|
589
|
+
const profileTarget = defaultProfileTarget(scanId, io.env);
|
|
590
|
+
const profileResult = await profileInstallResult(scanId, profileTarget, { write: true });
|
|
591
|
+
clientPlan.behaviorProfile = profileResult;
|
|
592
|
+
if (scanId === 'codex') {
|
|
593
|
+
clientPlan.codexProfile = profileResult;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
setupPlan.detectedClients.push(clientPlan);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
} else if (clientId) {
|
|
416
603
|
if (clientId === 'copilot-cli') {
|
|
417
604
|
const proxyPort = parsePositiveInteger(optionValue(optionArgs, '--port') ?? String(DEFAULT_PROXY_PORT), '--port');
|
|
418
605
|
setupPlan.selectedClient = copilotSetupPlan(setupPlan.mcpUrl, proxyPort, io.env);
|
|
@@ -1418,6 +1605,34 @@ function mcpConfigTemplate(clientId, mcpUrl) {
|
|
|
1418
1605
|
return oauthJsonMcpTemplate(clientId, mcpUrl, antigravityJsonConfig(mcpUrl));
|
|
1419
1606
|
}
|
|
1420
1607
|
|
|
1608
|
+
if (clientId === 'antigravity-ide') {
|
|
1609
|
+
return oauthJsonMcpTemplate(clientId, mcpUrl, antigravityIdeJsonConfig(mcpUrl));
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
if (clientId === 'antigravity2') {
|
|
1613
|
+
return oauthJsonMcpTemplate(clientId, mcpUrl, antigravity2JsonConfig(mcpUrl));
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (clientId === 'antigravity-cli') {
|
|
1617
|
+
return oauthJsonMcpTemplate(clientId, mcpUrl, antigravityCliJsonConfig(mcpUrl));
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
if (clientId === 'windsurf') {
|
|
1621
|
+
return bearerJsonMcpTemplate(clientId, mcpUrl, windsurfJsonConfig(mcpUrl));
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
if (clientId === 'cline') {
|
|
1625
|
+
return bearerJsonMcpTemplate(clientId, mcpUrl, clineJsonConfig(mcpUrl));
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
if (clientId === 'continue') {
|
|
1629
|
+
return bearerJsonMcpTemplate(clientId, mcpUrl, continueJsonConfig(mcpUrl));
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
if (clientId === 'claude-desktop') {
|
|
1633
|
+
return bearerJsonMcpTemplate(clientId, mcpUrl, claudeJsonConfig(mcpUrl));
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1421
1636
|
return {
|
|
1422
1637
|
client: clientId,
|
|
1423
1638
|
serverName: MCP_SERVER_NAME,
|
|
@@ -1606,6 +1821,30 @@ function writeSetupSummary(plan, io) {
|
|
|
1606
1821
|
writeLine(io.stdout, `Admin-only actions: ${plan.boundaries.adminRequired.join(', ')}`);
|
|
1607
1822
|
}
|
|
1608
1823
|
|
|
1824
|
+
if (plan.detectedClients) {
|
|
1825
|
+
writeLine(io.stdout, '');
|
|
1826
|
+
if (plan.detectedClients.length === 0) {
|
|
1827
|
+
writeLine(io.stdout, 'No local IDE or CLI client configurations were detected.');
|
|
1828
|
+
writeLine(io.stdout, `Run \`${COMMAND_NAME} setup <client>\` to configure a client manually.`);
|
|
1829
|
+
} else {
|
|
1830
|
+
writeLine(io.stdout, `Auto-detected ${plan.detectedClients.length} client(s):`);
|
|
1831
|
+
for (const client of plan.detectedClients) {
|
|
1832
|
+
writeLine(io.stdout, ` [${client.written ? '✔' : ' '}] ${client.label}`);
|
|
1833
|
+
writeLine(io.stdout, ` Config: ${client.configPath}`);
|
|
1834
|
+
writeLine(io.stdout, ` Agent ID: ${client.agentId}`);
|
|
1835
|
+
}
|
|
1836
|
+
if (plan.detectedClients.some(c => c.written)) {
|
|
1837
|
+
writeLine(io.stdout, '');
|
|
1838
|
+
writeLine(io.stdout, 'Successfully applied XMemo MCP configuration to all detected clients!');
|
|
1839
|
+
writeLine(io.stdout, 'Restart your IDEs or reload their MCP configurations to apply the changes.');
|
|
1840
|
+
} else {
|
|
1841
|
+
writeLine(io.stdout, '');
|
|
1842
|
+
writeLine(io.stdout, `Run \`${COMMAND_NAME} setup --all --write\` to write configurations for all detected clients.`);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1609
1848
|
if (plan.selectedClient) {
|
|
1610
1849
|
writeLine(io.stdout, '');
|
|
1611
1850
|
writeLine(io.stdout, `Selected client: ${plan.selectedClient.label}`);
|
|
@@ -2329,6 +2568,135 @@ async function mergeAntigravityMcpConfig(configPath, mcpUrl, identity) {
|
|
|
2329
2568
|
await bestEffortChmod(configPath, 0o600);
|
|
2330
2569
|
}
|
|
2331
2570
|
|
|
2571
|
+
function antigravityIdeJsonServerConfig(mcpUrl) {
|
|
2572
|
+
return {
|
|
2573
|
+
type: 'http',
|
|
2574
|
+
url: mcpUrl
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
function antigravityIdeJsonConfig(mcpUrl) {
|
|
2579
|
+
return {
|
|
2580
|
+
mcpServers: {
|
|
2581
|
+
[MCP_SERVER_NAME]: antigravityIdeJsonServerConfig(mcpUrl)
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
function antigravityIdeJsonSnippet(mcpUrl, identity = envReferenceIdentity('antigravity-ide')) {
|
|
2587
|
+
return `${JSON.stringify(antigravityIdeJsonConfig(mcpUrl), null, 2)}\n`;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
async function mergeAntigravityIdeMcpConfig(configPath, mcpUrl, identity) {
|
|
2591
|
+
const existing = await readTextIfExists(configPath);
|
|
2592
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
2593
|
+
|
|
2594
|
+
if (!isPlainObject(parsed)) {
|
|
2595
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
2599
|
+
parsed.mcpServers = {};
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
2603
|
+
if (existingName) {
|
|
2604
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
parsed.mcpServers[MCP_SERVER_NAME] = antigravityIdeJsonServerConfig(mcpUrl);
|
|
2608
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
2609
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
2610
|
+
await bestEffortChmod(configPath, 0o600);
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
function antigravity2JsonServerConfig(mcpUrl) {
|
|
2614
|
+
return {
|
|
2615
|
+
type: 'http',
|
|
2616
|
+
url: mcpUrl
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
function antigravity2JsonConfig(mcpUrl) {
|
|
2621
|
+
return {
|
|
2622
|
+
mcpServers: {
|
|
2623
|
+
[MCP_SERVER_NAME]: antigravity2JsonServerConfig(mcpUrl)
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
function antigravity2JsonSnippet(mcpUrl, identity = envReferenceIdentity('antigravity2')) {
|
|
2629
|
+
return `${JSON.stringify(antigravity2JsonConfig(mcpUrl), null, 2)}\n`;
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
async function mergeAntigravity2McpConfig(configPath, mcpUrl, identity) {
|
|
2633
|
+
const existing = await readTextIfExists(configPath);
|
|
2634
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
2635
|
+
|
|
2636
|
+
if (!isPlainObject(parsed)) {
|
|
2637
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
2641
|
+
parsed.mcpServers = {};
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
2645
|
+
if (existingName) {
|
|
2646
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
parsed.mcpServers[MCP_SERVER_NAME] = antigravity2JsonServerConfig(mcpUrl);
|
|
2650
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
2651
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
2652
|
+
await bestEffortChmod(configPath, 0o600);
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
function antigravityCliJsonServerConfig(mcpUrl, identity = envReferenceIdentity('antigravity-cli')) {
|
|
2656
|
+
return {
|
|
2657
|
+
httpUrl: mcpUrl,
|
|
2658
|
+
headers: {
|
|
2659
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
2660
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
2661
|
+
}
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
function antigravityCliJsonConfig(mcpUrl, identity = envReferenceIdentity('antigravity-cli')) {
|
|
2666
|
+
return {
|
|
2667
|
+
mcpServers: {
|
|
2668
|
+
[MCP_SERVER_NAME]: antigravityCliJsonServerConfig(mcpUrl, identity)
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
function antigravityCliJsonSnippet(mcpUrl, identity = envReferenceIdentity('antigravity-cli')) {
|
|
2674
|
+
return `${JSON.stringify(antigravityCliJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
async function mergeAntigravityCliMcpConfig(configPath, mcpUrl, identity) {
|
|
2678
|
+
const existing = await readTextIfExists(configPath);
|
|
2679
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
2680
|
+
|
|
2681
|
+
if (!isPlainObject(parsed)) {
|
|
2682
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
2686
|
+
parsed.mcpServers = {};
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
2690
|
+
if (existingName) {
|
|
2691
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
parsed.mcpServers[MCP_SERVER_NAME] = antigravityCliJsonServerConfig(mcpUrl, identity);
|
|
2695
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
2696
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
2697
|
+
await bestEffortChmod(configPath, 0o600);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2332
2700
|
|
|
2333
2701
|
async function mergeGeminiMcpConfig(configPath, mcpUrl, identity) {
|
|
2334
2702
|
const existing = await readTextIfExists(configPath);
|
|
@@ -2485,11 +2853,11 @@ function supportedMcpClientIds() {
|
|
|
2485
2853
|
}
|
|
2486
2854
|
|
|
2487
2855
|
function supportedSetupClientIds() {
|
|
2488
|
-
return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity'];
|
|
2856
|
+
return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude'];
|
|
2489
2857
|
}
|
|
2490
2858
|
|
|
2491
2859
|
function usesClientOAuth(clientId) {
|
|
2492
|
-
return clientId === 'gemini-cli' || clientId === 'antigravity';
|
|
2860
|
+
return clientId === 'gemini-cli' || clientId === 'antigravity' || clientId === 'antigravity-ide' || clientId === 'antigravity2' || clientId === 'antigravity-cli';
|
|
2493
2861
|
}
|
|
2494
2862
|
|
|
2495
2863
|
function credentialsPath(env) {
|
|
@@ -2537,6 +2905,21 @@ function defaultAntigravityConfigPath(env) {
|
|
|
2537
2905
|
return path.join(home, '.gemini', 'antigravity', 'mcp_config.json');
|
|
2538
2906
|
}
|
|
2539
2907
|
|
|
2908
|
+
function defaultAntigravityIdeConfigPath(env) {
|
|
2909
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
2910
|
+
return path.join(home, '.gemini', 'config', 'mcp_config.json');
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
function defaultAntigravity2ConfigPath(env) {
|
|
2914
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
2915
|
+
return path.join(home, '.antigravity2', 'mcp.json');
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
function defaultAntigravityCliConfigPath(env) {
|
|
2919
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
2920
|
+
return path.join(home, '.antigravity', 'settings.json');
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2540
2923
|
function defaultCopilotConfigPath(env) {
|
|
2541
2924
|
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
2542
2925
|
return path.join(env.COPILOT_HOME ?? path.join(home, '.copilot'), 'mcp-config.json');
|
|
@@ -2666,6 +3049,51 @@ async function sleep(ms) {
|
|
|
2666
3049
|
}
|
|
2667
3050
|
|
|
2668
3051
|
|
|
3052
|
+
async function detectClient(clientId, env) {
|
|
3053
|
+
let filePaths = [];
|
|
3054
|
+
if (clientId === 'copilot-cli' || clientId === 'copilot') {
|
|
3055
|
+
if (process.platform === 'win32' && env.APPDATA) {
|
|
3056
|
+
filePaths.push(path.join(env.APPDATA, 'Code', 'User', 'mcp.json'));
|
|
3057
|
+
} else {
|
|
3058
|
+
const home = env.HOME || os.homedir();
|
|
3059
|
+
if (process.platform === 'darwin') {
|
|
3060
|
+
filePaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'mcp.json'));
|
|
3061
|
+
}
|
|
3062
|
+
filePaths.push(path.join(home, '.config', 'Code', 'User', 'mcp.json'));
|
|
3063
|
+
}
|
|
3064
|
+
filePaths.push(defaultCopilotConfigPath(env));
|
|
3065
|
+
} else {
|
|
3066
|
+
const client = MCP_CLIENTS.get(clientId);
|
|
3067
|
+
if (client) {
|
|
3068
|
+
filePaths.push(client.defaultConfigPath(env));
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
if (clientId === 'cline') {
|
|
3073
|
+
if (process.platform === 'win32' && env.APPDATA) {
|
|
3074
|
+
filePaths.push(path.join(env.APPDATA, 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
3075
|
+
} else {
|
|
3076
|
+
const home = env.HOME || os.homedir();
|
|
3077
|
+
if (process.platform === 'darwin') {
|
|
3078
|
+
filePaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
3079
|
+
}
|
|
3080
|
+
filePaths.push(path.join(home, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
for (const filePath of filePaths) {
|
|
3085
|
+
if (await fileExists(filePath)) {
|
|
3086
|
+
return { detected: true, path: filePath };
|
|
3087
|
+
}
|
|
3088
|
+
const parentDir = path.dirname(filePath);
|
|
3089
|
+
if (await fileExists(parentDir)) {
|
|
3090
|
+
return { detected: true, path: filePath };
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
return { detected: false };
|
|
3095
|
+
}
|
|
3096
|
+
|
|
2669
3097
|
function npmExecutable() {
|
|
2670
3098
|
return os.platform() === 'win32' ? 'npm.cmd' : 'npm';
|
|
2671
3099
|
}
|
|
@@ -2753,6 +3181,641 @@ function escapeRegExp(value) {
|
|
|
2753
3181
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2754
3182
|
}
|
|
2755
3183
|
|
|
2756
|
-
function
|
|
2757
|
-
|
|
3184
|
+
function defaultWindsurfConfigPath(env) {
|
|
3185
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3186
|
+
return path.join(home, '.codeium', 'windsurf', 'mcp_config.json');
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
function defaultClineConfigPath(env) {
|
|
3190
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3191
|
+
return path.join(home, 'Documents', 'Cline', 'MCP', 'cline_mcp_settings.json');
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
function defaultContinueConfigPath(env) {
|
|
3195
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3196
|
+
return path.join(home, '.continue', 'config.json');
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
function defaultClaudeConfigPath(env) {
|
|
3200
|
+
if (process.platform === 'win32' && env.APPDATA) {
|
|
3201
|
+
return path.join(env.APPDATA, 'Claude', 'claude_desktop_config.json');
|
|
3202
|
+
}
|
|
3203
|
+
const home = env.HOME || os.homedir();
|
|
3204
|
+
if (process.platform === 'darwin') {
|
|
3205
|
+
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
3206
|
+
}
|
|
3207
|
+
return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
function windsurfJsonServerConfig(mcpUrl, identity = envReferenceIdentity('windsurf')) {
|
|
3211
|
+
return {
|
|
3212
|
+
serverUrl: mcpUrl,
|
|
3213
|
+
headers: {
|
|
3214
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3215
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3216
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3217
|
+
}
|
|
3218
|
+
};
|
|
2758
3219
|
}
|
|
3220
|
+
|
|
3221
|
+
function windsurfJsonConfig(mcpUrl, identity = envReferenceIdentity('windsurf')) {
|
|
3222
|
+
return {
|
|
3223
|
+
mcpServers: {
|
|
3224
|
+
[MCP_SERVER_NAME]: windsurfJsonServerConfig(mcpUrl, identity)
|
|
3225
|
+
}
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
function windsurfJsonSnippet(mcpUrl, identity = envReferenceIdentity('windsurf')) {
|
|
3230
|
+
return `${JSON.stringify(windsurfJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
async function mergeWindsurfMcpConfig(configPath, mcpUrl, identity) {
|
|
3234
|
+
const existing = await readTextIfExists(configPath);
|
|
3235
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3236
|
+
if (!isPlainObject(parsed)) {
|
|
3237
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3238
|
+
}
|
|
3239
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3240
|
+
parsed.mcpServers = {};
|
|
3241
|
+
}
|
|
3242
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3243
|
+
if (existingName) {
|
|
3244
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3245
|
+
}
|
|
3246
|
+
parsed.mcpServers[MCP_SERVER_NAME] = windsurfJsonServerConfig(mcpUrl, identity);
|
|
3247
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3248
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3249
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
function clineJsonServerConfig(mcpUrl, identity = envReferenceIdentity('cline')) {
|
|
3253
|
+
return {
|
|
3254
|
+
httpUrl: mcpUrl,
|
|
3255
|
+
headers: {
|
|
3256
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3257
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3258
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3259
|
+
}
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
function clineJsonConfig(mcpUrl, identity = envReferenceIdentity('cline')) {
|
|
3264
|
+
return {
|
|
3265
|
+
mcpServers: {
|
|
3266
|
+
[MCP_SERVER_NAME]: clineJsonServerConfig(mcpUrl, identity)
|
|
3267
|
+
}
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
function clineJsonSnippet(mcpUrl, identity = envReferenceIdentity('cline')) {
|
|
3272
|
+
return `${JSON.stringify(clineJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
async function mergeClineMcpConfig(configPath, mcpUrl, identity) {
|
|
3276
|
+
const existing = await readTextIfExists(configPath);
|
|
3277
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3278
|
+
if (!isPlainObject(parsed)) {
|
|
3279
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3280
|
+
}
|
|
3281
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3282
|
+
parsed.mcpServers = {};
|
|
3283
|
+
}
|
|
3284
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3285
|
+
if (existingName) {
|
|
3286
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3287
|
+
}
|
|
3288
|
+
parsed.mcpServers[MCP_SERVER_NAME] = clineJsonServerConfig(mcpUrl, identity);
|
|
3289
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3290
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3291
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
function continueJsonServerConfig(mcpUrl, identity = envReferenceIdentity('continue')) {
|
|
3295
|
+
return {
|
|
3296
|
+
transport: {
|
|
3297
|
+
type: 'streamable-http',
|
|
3298
|
+
url: mcpUrl,
|
|
3299
|
+
headers: {
|
|
3300
|
+
Authorization: `Bearer \${${TOKEN_ENV_VAR}}`,
|
|
3301
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3302
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
function continueJsonConfig(mcpUrl, identity = envReferenceIdentity('continue')) {
|
|
3309
|
+
return {
|
|
3310
|
+
mcpServers: {
|
|
3311
|
+
[MCP_SERVER_NAME]: continueJsonServerConfig(mcpUrl, identity)
|
|
3312
|
+
}
|
|
3313
|
+
};
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
function continueJsonSnippet(mcpUrl, identity = envReferenceIdentity('continue')) {
|
|
3317
|
+
return `${JSON.stringify(continueJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
async function mergeContinueMcpConfig(configPath, mcpUrl, identity) {
|
|
3321
|
+
const existing = await readTextIfExists(configPath);
|
|
3322
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3323
|
+
if (!isPlainObject(parsed)) {
|
|
3324
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3325
|
+
}
|
|
3326
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3327
|
+
parsed.mcpServers = {};
|
|
3328
|
+
}
|
|
3329
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3330
|
+
if (existingName) {
|
|
3331
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3332
|
+
}
|
|
3333
|
+
parsed.mcpServers[MCP_SERVER_NAME] = continueJsonServerConfig(mcpUrl, identity);
|
|
3334
|
+
|
|
3335
|
+
if (isPlainObject(parsed.experimental)) {
|
|
3336
|
+
if (!Array.isArray(parsed.experimental.modelContextProtocolServers)) {
|
|
3337
|
+
parsed.experimental.modelContextProtocolServers = [];
|
|
3338
|
+
}
|
|
3339
|
+
const hasXMemo = parsed.experimental.modelContextProtocolServers.some(
|
|
3340
|
+
(srv) => srv.transport && srv.transport.url === mcpUrl
|
|
3341
|
+
);
|
|
3342
|
+
if (!hasXMemo) {
|
|
3343
|
+
parsed.experimental.modelContextProtocolServers.push(continueJsonServerConfig(mcpUrl, identity));
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3348
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3349
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
function claudeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('claude-desktop')) {
|
|
3353
|
+
return {
|
|
3354
|
+
command: 'npx',
|
|
3355
|
+
args: [
|
|
3356
|
+
'-y',
|
|
3357
|
+
'mcp-remote',
|
|
3358
|
+
mcpUrl
|
|
3359
|
+
],
|
|
3360
|
+
env: {
|
|
3361
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
3362
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
3363
|
+
}
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
function claudeJsonConfig(mcpUrl, identity = envReferenceIdentity('claude-desktop')) {
|
|
3368
|
+
return {
|
|
3369
|
+
mcpServers: {
|
|
3370
|
+
[MCP_SERVER_NAME]: claudeJsonServerConfig(mcpUrl, identity)
|
|
3371
|
+
}
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
function claudeJsonSnippet(mcpUrl, identity = envReferenceIdentity('claude-desktop')) {
|
|
3376
|
+
return `${JSON.stringify(claudeJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
async function mergeClaudeMcpConfig(configPath, mcpUrl, identity) {
|
|
3380
|
+
const existing = await readTextIfExists(configPath);
|
|
3381
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3382
|
+
if (!isPlainObject(parsed)) {
|
|
3383
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3384
|
+
}
|
|
3385
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3386
|
+
parsed.mcpServers = {};
|
|
3387
|
+
}
|
|
3388
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3389
|
+
if (existingName) {
|
|
3390
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3391
|
+
}
|
|
3392
|
+
parsed.mcpServers[MCP_SERVER_NAME] = claudeJsonServerConfig(mcpUrl, identity);
|
|
3393
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3394
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3395
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
function defaultOpenclawConfigPath(env) {
|
|
3399
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3400
|
+
return path.join(home, '.openclaw', 'openclaw.json');
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
function openclawJsonServerConfig(mcpUrl, identity = envReferenceIdentity('openclaw')) {
|
|
3404
|
+
return {
|
|
3405
|
+
url: mcpUrl,
|
|
3406
|
+
headers: {
|
|
3407
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3408
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3409
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3410
|
+
}
|
|
3411
|
+
};
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
function openclawJsonConfig(mcpUrl, identity = envReferenceIdentity('openclaw')) {
|
|
3415
|
+
return {
|
|
3416
|
+
mcpServers: {
|
|
3417
|
+
[MCP_SERVER_NAME]: openclawJsonServerConfig(mcpUrl, identity)
|
|
3418
|
+
}
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
function openclawJsonSnippet(mcpUrl, identity = envReferenceIdentity('openclaw')) {
|
|
3423
|
+
return `${JSON.stringify(openclawJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
async function mergeOpenclawMcpConfig(configPath, mcpUrl, identity) {
|
|
3427
|
+
const existing = await readTextIfExists(configPath);
|
|
3428
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3429
|
+
if (!isPlainObject(parsed)) {
|
|
3430
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3431
|
+
}
|
|
3432
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3433
|
+
parsed.mcpServers = {};
|
|
3434
|
+
}
|
|
3435
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3436
|
+
if (existingName) {
|
|
3437
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3438
|
+
}
|
|
3439
|
+
parsed.mcpServers[MCP_SERVER_NAME] = openclawJsonServerConfig(mcpUrl, identity);
|
|
3440
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3441
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3442
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
function defaultKiroConfigPath(env) {
|
|
3446
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3447
|
+
return path.join(home, '.kiro', 'settings', 'mcp.json');
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
function kiroJsonServerConfig(mcpUrl, identity = envReferenceIdentity('kiro')) {
|
|
3451
|
+
return {
|
|
3452
|
+
url: mcpUrl,
|
|
3453
|
+
headers: {
|
|
3454
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3455
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3456
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3457
|
+
}
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
function kiroJsonConfig(mcpUrl, identity = envReferenceIdentity('kiro')) {
|
|
3462
|
+
return {
|
|
3463
|
+
mcpServers: {
|
|
3464
|
+
[MCP_SERVER_NAME]: kiroJsonServerConfig(mcpUrl, identity)
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
function kiroJsonSnippet(mcpUrl, identity = envReferenceIdentity('kiro')) {
|
|
3470
|
+
return `${JSON.stringify(kiroJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
async function mergeKiroMcpConfig(configPath, mcpUrl, identity) {
|
|
3474
|
+
const existing = await readTextIfExists(configPath);
|
|
3475
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3476
|
+
if (!isPlainObject(parsed)) {
|
|
3477
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3478
|
+
}
|
|
3479
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3480
|
+
parsed.mcpServers = {};
|
|
3481
|
+
}
|
|
3482
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3483
|
+
if (existingName) {
|
|
3484
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3485
|
+
}
|
|
3486
|
+
parsed.mcpServers[MCP_SERVER_NAME] = kiroJsonServerConfig(mcpUrl, identity);
|
|
3487
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3488
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3489
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
function defaultZedConfigPath(env) {
|
|
3493
|
+
if (process.platform === 'win32' && env.APPDATA) {
|
|
3494
|
+
return path.join(env.APPDATA, 'Zed', 'settings.json');
|
|
3495
|
+
}
|
|
3496
|
+
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
3497
|
+
return path.join(home, '.config', 'zed', 'settings.json');
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
function zedJsonServerConfig(mcpUrl, identity = envReferenceIdentity('zed')) {
|
|
3501
|
+
return {
|
|
3502
|
+
command: 'npx',
|
|
3503
|
+
args: [
|
|
3504
|
+
'-y',
|
|
3505
|
+
'mcp-remote',
|
|
3506
|
+
mcpUrl
|
|
3507
|
+
],
|
|
3508
|
+
env: {
|
|
3509
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
3510
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
3511
|
+
}
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
function zedJsonConfig(mcpUrl, identity = envReferenceIdentity('zed')) {
|
|
3516
|
+
return {
|
|
3517
|
+
context_servers: {
|
|
3518
|
+
[MCP_SERVER_NAME]: zedJsonServerConfig(mcpUrl, identity)
|
|
3519
|
+
}
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
function zedJsonSnippet(mcpUrl, identity = envReferenceIdentity('zed')) {
|
|
3524
|
+
return `${JSON.stringify(zedJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
async function mergeZedMcpConfig(configPath, mcpUrl, identity) {
|
|
3528
|
+
const existing = await readTextIfExists(configPath);
|
|
3529
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3530
|
+
if (!isPlainObject(parsed)) {
|
|
3531
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3532
|
+
}
|
|
3533
|
+
if (!isPlainObject(parsed.context_servers)) {
|
|
3534
|
+
parsed.context_servers = {};
|
|
3535
|
+
}
|
|
3536
|
+
const existingName = existingJsonMcpServerName(parsed.context_servers);
|
|
3537
|
+
if (existingName) {
|
|
3538
|
+
throw new UsageError(`MCP config already contains context_servers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3539
|
+
}
|
|
3540
|
+
parsed.context_servers[MCP_SERVER_NAME] = zedJsonServerConfig(mcpUrl, identity);
|
|
3541
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3542
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3543
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
function defaultJetbrainsConfigPath(env) {
|
|
3547
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3548
|
+
return path.join(home, '.continue', 'config.json');
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
function jetbrainsJsonServerConfig(mcpUrl, identity = envReferenceIdentity('jetbrains')) {
|
|
3552
|
+
return continueJsonServerConfig(mcpUrl, identity);
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
function jetbrainsJsonConfig(mcpUrl, identity = envReferenceIdentity('jetbrains')) {
|
|
3556
|
+
return continueJsonConfig(mcpUrl, identity);
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
function jetbrainsJsonSnippet(mcpUrl, identity = envReferenceIdentity('jetbrains')) {
|
|
3560
|
+
return continueJsonSnippet(mcpUrl, identity);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
async function mergeJetbrainsMcpConfig(configPath, mcpUrl, identity) {
|
|
3564
|
+
await mergeContinueMcpConfig(configPath, mcpUrl, identity);
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
function defaultOpencodeConfigPath(env) {
|
|
3568
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3569
|
+
return path.join(home, '.config', 'opencode', 'opencode.json');
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
function opencodeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('opencode')) {
|
|
3573
|
+
return {
|
|
3574
|
+
type: 'local',
|
|
3575
|
+
command: [
|
|
3576
|
+
'npx',
|
|
3577
|
+
'-y',
|
|
3578
|
+
'mcp-remote',
|
|
3579
|
+
mcpUrl
|
|
3580
|
+
],
|
|
3581
|
+
environment: {
|
|
3582
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
3583
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
3584
|
+
}
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
function opencodeJsonConfig(mcpUrl, identity = envReferenceIdentity('opencode')) {
|
|
3589
|
+
return {
|
|
3590
|
+
mcp: {
|
|
3591
|
+
[MCP_SERVER_NAME]: opencodeJsonServerConfig(mcpUrl, identity)
|
|
3592
|
+
}
|
|
3593
|
+
};
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
function opencodeJsonSnippet(mcpUrl, identity = envReferenceIdentity('opencode')) {
|
|
3597
|
+
return `${JSON.stringify(opencodeJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
async function mergeOpencodeMcpConfig(configPath, mcpUrl, identity) {
|
|
3601
|
+
const existing = await readTextIfExists(configPath);
|
|
3602
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3603
|
+
if (!isPlainObject(parsed)) {
|
|
3604
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3605
|
+
}
|
|
3606
|
+
if (!isPlainObject(parsed.mcp)) {
|
|
3607
|
+
parsed.mcp = {};
|
|
3608
|
+
}
|
|
3609
|
+
const existingName = existingJsonMcpServerName(parsed.mcp);
|
|
3610
|
+
if (existingName) {
|
|
3611
|
+
throw new UsageError(`MCP config already contains mcp.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3612
|
+
}
|
|
3613
|
+
parsed.mcp[MCP_SERVER_NAME] = opencodeJsonServerConfig(mcpUrl, identity);
|
|
3614
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3615
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3616
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
function defaultHermesConfigPath(env) {
|
|
3620
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3621
|
+
return path.join(home, '.hermes', 'config.yaml');
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
function hermesYamlSnippet(mcpUrl, identity = envReferenceIdentity('hermes')) {
|
|
3625
|
+
return `mcp_servers:
|
|
3626
|
+
${MCP_SERVER_NAME}:
|
|
3627
|
+
command: npx
|
|
3628
|
+
args:
|
|
3629
|
+
- -y
|
|
3630
|
+
- mcp-remote
|
|
3631
|
+
- ${mcpUrl}
|
|
3632
|
+
env:
|
|
3633
|
+
${TOKEN_ENV_VAR}: "\${env:${TOKEN_ENV_VAR}}"
|
|
3634
|
+
${AGENT_INSTANCE_ENV_VAR}: "${identity.agentInstanceId}"
|
|
3635
|
+
`;
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
async function mergeHermesMcpConfig(configPath, mcpUrl, identity) {
|
|
3639
|
+
const existing = await readTextIfExists(configPath);
|
|
3640
|
+
|
|
3641
|
+
if (existing.includes(`${MCP_SERVER_NAME}:`) || existing.includes('memory_os:') || existing.includes('memory-os:')) {
|
|
3642
|
+
throw new UsageError(`MCP config already contains ${MCP_SERVER_NAME} in mcp_servers. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3646
|
+
|
|
3647
|
+
if (existing.trim().length === 0) {
|
|
3648
|
+
await fs.writeFile(configPath, hermesYamlSnippet(mcpUrl, identity), { mode: 0o600 });
|
|
3649
|
+
} else if (existing.includes('mcp_servers:')) {
|
|
3650
|
+
const replacement = `mcp_servers:
|
|
3651
|
+
${MCP_SERVER_NAME}:
|
|
3652
|
+
command: npx
|
|
3653
|
+
args:
|
|
3654
|
+
- -y
|
|
3655
|
+
- mcp-remote
|
|
3656
|
+
- ${mcpUrl}
|
|
3657
|
+
env:
|
|
3658
|
+
${TOKEN_ENV_VAR}: "\${env:${TOKEN_ENV_VAR}}"
|
|
3659
|
+
${AGENT_INSTANCE_ENV_VAR}: "${identity.agentInstanceId}"`;
|
|
3660
|
+
const updated = existing.replace('mcp_servers:', replacement);
|
|
3661
|
+
await fs.writeFile(configPath, updated, { mode: 0o600 });
|
|
3662
|
+
} else {
|
|
3663
|
+
const prefix = existing.endsWith('\n') ? '' : '\n';
|
|
3664
|
+
await fs.appendFile(configPath, `${prefix}${hermesYamlSnippet(mcpUrl, identity)}`, { mode: 0o600 });
|
|
3665
|
+
}
|
|
3666
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
function defaultQwenConfigPath(env) {
|
|
3670
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3671
|
+
return path.join(home, '.qwen', 'settings.json');
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
function qwenJsonServerConfig(mcpUrl, identity = envReferenceIdentity('qwen')) {
|
|
3675
|
+
return {
|
|
3676
|
+
url: mcpUrl,
|
|
3677
|
+
headers: {
|
|
3678
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3679
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3680
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3681
|
+
}
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
// Reuse the trae config layout which uses mcpServers
|
|
3686
|
+
function qwenJsonConfig(mcpUrl, identity = envReferenceIdentity('qwen')) {
|
|
3687
|
+
return {
|
|
3688
|
+
mcpServers: {
|
|
3689
|
+
[MCP_SERVER_NAME]: qwenJsonServerConfig(mcpUrl, identity)
|
|
3690
|
+
}
|
|
3691
|
+
};
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
function qwenJsonSnippet(mcpUrl, identity = envReferenceIdentity('qwen')) {
|
|
3695
|
+
return `${JSON.stringify(qwenJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
async function mergeQwenMcpConfig(configPath, mcpUrl, identity) {
|
|
3699
|
+
const existing = await readTextIfExists(configPath);
|
|
3700
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3701
|
+
if (!isPlainObject(parsed)) {
|
|
3702
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3703
|
+
}
|
|
3704
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3705
|
+
parsed.mcpServers = {};
|
|
3706
|
+
}
|
|
3707
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3708
|
+
if (existingName) {
|
|
3709
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3710
|
+
}
|
|
3711
|
+
parsed.mcpServers[MCP_SERVER_NAME] = qwenJsonServerConfig(mcpUrl, identity);
|
|
3712
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3713
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3714
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
function defaultTraeConfigPath(env) {
|
|
3718
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3719
|
+
return path.join(home, '.trae', 'mcp.json');
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
function traeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('trae')) {
|
|
3723
|
+
return {
|
|
3724
|
+
url: mcpUrl,
|
|
3725
|
+
headers: {
|
|
3726
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3727
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3728
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3729
|
+
}
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
function traeJsonConfig(mcpUrl, identity = envReferenceIdentity('trae')) {
|
|
3734
|
+
return {
|
|
3735
|
+
mcpServers: {
|
|
3736
|
+
[MCP_SERVER_NAME]: traeJsonServerConfig(mcpUrl, identity)
|
|
3737
|
+
}
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
// Return Trae MCP config snippet
|
|
3742
|
+
function traeJsonSnippet(mcpUrl, identity = envReferenceIdentity('trae')) {
|
|
3743
|
+
return `${JSON.stringify(traeJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
async function mergeTraeMcpConfig(configPath, mcpUrl, identity) {
|
|
3747
|
+
const existing = await readTextIfExists(configPath);
|
|
3748
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3749
|
+
if (!isPlainObject(parsed)) {
|
|
3750
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3751
|
+
}
|
|
3752
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3753
|
+
parsed.mcpServers = {};
|
|
3754
|
+
}
|
|
3755
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3756
|
+
if (existingName) {
|
|
3757
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3758
|
+
}
|
|
3759
|
+
parsed.mcpServers[MCP_SERVER_NAME] = traeJsonServerConfig(mcpUrl, identity);
|
|
3760
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3761
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3762
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
function defaultClaudecodeConfigPath(env) {
|
|
3766
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3767
|
+
return path.join(home, '.claude.json');
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
function claudecodeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('claude-code')) {
|
|
3771
|
+
return {
|
|
3772
|
+
command: 'npx',
|
|
3773
|
+
args: [
|
|
3774
|
+
'-y',
|
|
3775
|
+
'mcp-remote',
|
|
3776
|
+
mcpUrl
|
|
3777
|
+
],
|
|
3778
|
+
env: {
|
|
3779
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
3780
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
3781
|
+
}
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3785
|
+
function claudecodeJsonConfig(mcpUrl, identity = envReferenceIdentity('claude-code')) {
|
|
3786
|
+
return {
|
|
3787
|
+
mcpServers: {
|
|
3788
|
+
[MCP_SERVER_NAME]: claudecodeJsonServerConfig(mcpUrl, identity)
|
|
3789
|
+
}
|
|
3790
|
+
};
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
function claudecodeJsonSnippet(mcpUrl, identity = envReferenceIdentity('claude-code')) {
|
|
3794
|
+
return `${JSON.stringify(claudecodeJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3795
|
+
}
|
|
3796
|
+
|
|
3797
|
+
async function mergeClaudecodeMcpConfig(configPath, mcpUrl, identity) {
|
|
3798
|
+
const existing = await readTextIfExists(configPath);
|
|
3799
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3800
|
+
if (!isPlainObject(parsed)) {
|
|
3801
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3802
|
+
}
|
|
3803
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3804
|
+
parsed.mcpServers = {};
|
|
3805
|
+
}
|
|
3806
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3807
|
+
if (existingName) {
|
|
3808
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3809
|
+
}
|
|
3810
|
+
parsed.mcpServers[MCP_SERVER_NAME] = claudecodeJsonServerConfig(mcpUrl, identity);
|
|
3811
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3812
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3813
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
function writeLine(stream, line) {
|
|
3817
|
+
stream.write(`${line}\n`);
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
|
|
3821
|
+
|