@xmemo/client 0.4.139 → 0.4.142
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/package.json +1 -1
- package/src/cli.js +914 -12
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
2
3
|
import http from 'node:http';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -10,7 +11,7 @@ const PACKAGE_NAME = '@xmemo/client';
|
|
|
10
11
|
const FALLBACK_PACKAGE_NAME = '@yonro/xmemo-client';
|
|
11
12
|
const COMMAND_NAME = 'xmemo';
|
|
12
13
|
const LEGACY_COMMAND_NAME = 'memory-os';
|
|
13
|
-
const CLI_VERSION = '0.4.
|
|
14
|
+
const CLI_VERSION = '0.4.142';
|
|
14
15
|
const DEFAULT_SERVICE_URL = 'https://xmemo.dev';
|
|
15
16
|
const TOKEN_ENV_VAR = 'XMEMO_KEY';
|
|
16
17
|
const LEGACY_TOKEN_ENV_VAR = 'MEMORY_OS_MCP_TOKEN';
|
|
@@ -85,6 +86,97 @@ const MCP_CLIENTS = new Map([
|
|
|
85
86
|
buildSnippet: antigravityCliJsonSnippet,
|
|
86
87
|
writeConfig: mergeAntigravityCliMcpConfig,
|
|
87
88
|
configKind: 'json'
|
|
89
|
+
}],
|
|
90
|
+
['windsurf', {
|
|
91
|
+
label: 'Windsurf',
|
|
92
|
+
defaultConfigPath: defaultWindsurfConfigPath,
|
|
93
|
+
buildSnippet: windsurfJsonSnippet,
|
|
94
|
+
writeConfig: mergeWindsurfMcpConfig,
|
|
95
|
+
configKind: 'json'
|
|
96
|
+
}],
|
|
97
|
+
['cline', {
|
|
98
|
+
label: 'Cline',
|
|
99
|
+
defaultConfigPath: defaultClineConfigPath,
|
|
100
|
+
buildSnippet: clineJsonSnippet,
|
|
101
|
+
writeConfig: mergeClineMcpConfig,
|
|
102
|
+
configKind: 'json'
|
|
103
|
+
}],
|
|
104
|
+
['continue', {
|
|
105
|
+
label: 'Continue',
|
|
106
|
+
defaultConfigPath: defaultContinueConfigPath,
|
|
107
|
+
buildSnippet: continueJsonSnippet,
|
|
108
|
+
writeConfig: mergeContinueMcpConfig,
|
|
109
|
+
configKind: 'json'
|
|
110
|
+
}],
|
|
111
|
+
['claude-desktop', {
|
|
112
|
+
label: 'Claude Desktop',
|
|
113
|
+
defaultConfigPath: defaultClaudeConfigPath,
|
|
114
|
+
buildSnippet: claudeJsonSnippet,
|
|
115
|
+
writeConfig: mergeClaudeMcpConfig,
|
|
116
|
+
configKind: 'json'
|
|
117
|
+
}],
|
|
118
|
+
['openclaw', {
|
|
119
|
+
label: 'OpenClaw',
|
|
120
|
+
defaultConfigPath: defaultOpenclawConfigPath,
|
|
121
|
+
buildSnippet: openclawJsonSnippet,
|
|
122
|
+
writeConfig: mergeOpenclawMcpConfig,
|
|
123
|
+
configKind: 'json'
|
|
124
|
+
}],
|
|
125
|
+
['kiro', {
|
|
126
|
+
label: 'Kiro',
|
|
127
|
+
defaultConfigPath: defaultKiroConfigPath,
|
|
128
|
+
buildSnippet: kiroJsonSnippet,
|
|
129
|
+
writeConfig: mergeKiroMcpConfig,
|
|
130
|
+
configKind: 'json'
|
|
131
|
+
}],
|
|
132
|
+
['zed', {
|
|
133
|
+
label: 'Zed',
|
|
134
|
+
defaultConfigPath: defaultZedConfigPath,
|
|
135
|
+
buildSnippet: zedJsonSnippet,
|
|
136
|
+
writeConfig: mergeZedMcpConfig,
|
|
137
|
+
configKind: 'json'
|
|
138
|
+
}],
|
|
139
|
+
['jetbrains', {
|
|
140
|
+
label: 'JetBrains',
|
|
141
|
+
defaultConfigPath: defaultJetbrainsConfigPath,
|
|
142
|
+
buildSnippet: jetbrainsJsonSnippet,
|
|
143
|
+
writeConfig: mergeJetbrainsMcpConfig,
|
|
144
|
+
configKind: 'json'
|
|
145
|
+
}],
|
|
146
|
+
['opencode', {
|
|
147
|
+
label: 'OpenCode',
|
|
148
|
+
defaultConfigPath: defaultOpencodeConfigPath,
|
|
149
|
+
buildSnippet: opencodeJsonSnippet,
|
|
150
|
+
writeConfig: mergeOpencodeMcpConfig,
|
|
151
|
+
configKind: 'json'
|
|
152
|
+
}],
|
|
153
|
+
['hermes', {
|
|
154
|
+
label: 'Hermes',
|
|
155
|
+
defaultConfigPath: defaultHermesConfigPath,
|
|
156
|
+
buildSnippet: hermesYamlSnippet,
|
|
157
|
+
writeConfig: mergeHermesMcpConfig,
|
|
158
|
+
configKind: 'yaml'
|
|
159
|
+
}],
|
|
160
|
+
['qwen', {
|
|
161
|
+
label: 'Qwen',
|
|
162
|
+
defaultConfigPath: defaultQwenConfigPath,
|
|
163
|
+
buildSnippet: qwenJsonSnippet,
|
|
164
|
+
writeConfig: mergeQwenMcpConfig,
|
|
165
|
+
configKind: 'json'
|
|
166
|
+
}],
|
|
167
|
+
['trae', {
|
|
168
|
+
label: 'Trae',
|
|
169
|
+
defaultConfigPath: defaultTraeConfigPath,
|
|
170
|
+
buildSnippet: traeJsonSnippet,
|
|
171
|
+
writeConfig: mergeTraeMcpConfig,
|
|
172
|
+
configKind: 'json'
|
|
173
|
+
}],
|
|
174
|
+
['claude-code', {
|
|
175
|
+
label: 'Claude Code',
|
|
176
|
+
defaultConfigPath: defaultClaudecodeConfigPath,
|
|
177
|
+
buildSnippet: claudecodeJsonSnippet,
|
|
178
|
+
writeConfig: mergeClaudecodeMcpConfig,
|
|
179
|
+
configKind: 'json'
|
|
88
180
|
}]
|
|
89
181
|
]);
|
|
90
182
|
|
|
@@ -98,7 +190,26 @@ const SETUP_CLIENT_ALIASES = new Map([
|
|
|
98
190
|
['antigravity', 'antigravity'],
|
|
99
191
|
['antigravity-ide', 'antigravity-ide'],
|
|
100
192
|
['antigravity2', 'antigravity2'],
|
|
101
|
-
['antigravity-cli', 'antigravity-cli']
|
|
193
|
+
['antigravity-cli', 'antigravity-cli'],
|
|
194
|
+
['windsurf', 'windsurf'],
|
|
195
|
+
['cline', 'cline'],
|
|
196
|
+
['continue', 'continue'],
|
|
197
|
+
['claude', 'claude-desktop'],
|
|
198
|
+
['claude-desktop', 'claude-desktop'],
|
|
199
|
+
['openclaw', 'openclaw'],
|
|
200
|
+
['kiro', 'kiro'],
|
|
201
|
+
['zed', 'zed'],
|
|
202
|
+
['jetbrains', 'jetbrains'],
|
|
203
|
+
['opencode', 'opencode'],
|
|
204
|
+
['hermes', 'hermes'],
|
|
205
|
+
['qwen', 'qwen'],
|
|
206
|
+
['qwencli', 'qwen'],
|
|
207
|
+
['qwen-cli', 'qwen'],
|
|
208
|
+
['trae', 'trae'],
|
|
209
|
+
['claude-code', 'claude-code'],
|
|
210
|
+
['claudecode', 'claude-code'],
|
|
211
|
+
['claude-cli', 'claude-code'],
|
|
212
|
+
['claudecode-cli', 'claude-code']
|
|
102
213
|
]);
|
|
103
214
|
|
|
104
215
|
class UsageError extends Error {
|
|
@@ -417,13 +528,27 @@ async function setupCommand(args, io) {
|
|
|
417
528
|
const baseUrl = normalizeBaseUrl(baseUrlOption(optionArgs, io.env));
|
|
418
529
|
const outputJson = hasFlag(optionArgs, '--json');
|
|
419
530
|
const shortClientSetup = Boolean(positionalClientId);
|
|
420
|
-
const
|
|
531
|
+
const setupAll = hasFlag(optionArgs, '--all');
|
|
532
|
+
|
|
533
|
+
let clientId = null;
|
|
534
|
+
try {
|
|
535
|
+
clientId = normalizeSetupClientId(positionalClientId ?? optionValue(optionArgs, '--client'));
|
|
536
|
+
} catch (error) {
|
|
537
|
+
if (!setupAll) {
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (setupAll && clientId) {
|
|
543
|
+
throw new UsageError('Cannot specify both --all and a specific client.');
|
|
544
|
+
}
|
|
545
|
+
|
|
421
546
|
const dryRun = hasFlag(optionArgs, '--dry-run') || hasFlag(optionArgs, '--preview');
|
|
422
|
-
const writeConfig = !dryRun && (hasFlag(optionArgs, '--write') || hasFlag(optionArgs, '--yes') || shortClientSetup);
|
|
547
|
+
const writeConfig = !dryRun && (hasFlag(optionArgs, '--write') || hasFlag(optionArgs, '--yes') || shortClientSetup || (setupAll && (hasFlag(optionArgs, '--write') || hasFlag(optionArgs, '--yes'))));
|
|
423
548
|
const timeoutMs = parsePositiveInteger(optionValue(optionArgs, '--timeout-ms') ?? '5000', '--timeout-ms');
|
|
424
549
|
|
|
425
|
-
if (writeConfig && !clientId) {
|
|
426
|
-
throw new UsageError(`Setup --write requires --client <${supportedSetupClientIds().join('|')}> so the CLI never writes broad config implicitly.`);
|
|
550
|
+
if (writeConfig && !clientId && !setupAll) {
|
|
551
|
+
throw new UsageError(`Setup --write requires --client <${supportedSetupClientIds().join('|')}> or --all so the CLI never writes broad config implicitly.`);
|
|
427
552
|
}
|
|
428
553
|
|
|
429
554
|
const discoveryUrl = endpointUrl(baseUrl, '/.well-known/memory-os.json');
|
|
@@ -436,7 +561,46 @@ async function setupCommand(args, io) {
|
|
|
436
561
|
const status = await fetchJson(statusUrl, timeoutMs, io);
|
|
437
562
|
const setupPlan = buildSetupPlan({ baseUrl, discoveryUrl, statusUrl, discovery, status });
|
|
438
563
|
|
|
439
|
-
if (
|
|
564
|
+
if (setupAll) {
|
|
565
|
+
setupPlan.detectedClients = [];
|
|
566
|
+
const scanIds = ['codex', 'cursor', 'copilot-cli', 'gemini-cli', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude-desktop'];
|
|
567
|
+
for (const scanId of scanIds) {
|
|
568
|
+
const detection = await detectClient(scanId, io.env);
|
|
569
|
+
if (detection.detected) {
|
|
570
|
+
let clientPlan;
|
|
571
|
+
if (scanId === 'copilot-cli') {
|
|
572
|
+
const proxyPort = parsePositiveInteger(optionValue(optionArgs, '--port') ?? String(DEFAULT_PROXY_PORT), '--port');
|
|
573
|
+
clientPlan = copilotSetupPlan(setupPlan.mcpUrl, proxyPort, io.env);
|
|
574
|
+
clientPlan.configPath = detection.path;
|
|
575
|
+
if (writeConfig) {
|
|
576
|
+
await mergeCopilotMcpConfig(clientPlan.configPath, clientPlan.proxyUrl);
|
|
577
|
+
clientPlan.written = true;
|
|
578
|
+
}
|
|
579
|
+
} else {
|
|
580
|
+
const client = MCP_CLIENTS.get(scanId);
|
|
581
|
+
const identity = writeConfig ? await agentIdentity(scanId, io.env) : envReferenceIdentity(scanId);
|
|
582
|
+
clientPlan = clientSetupPlan(scanId, client, setupPlan.mcpUrl, io.env, identity);
|
|
583
|
+
clientPlan.configPath = detection.path;
|
|
584
|
+
if (writeConfig) {
|
|
585
|
+
await client.writeConfig(clientPlan.configPath, setupPlan.mcpUrl, identity);
|
|
586
|
+
clientPlan.written = true;
|
|
587
|
+
if (profileClientConfig(scanId)) {
|
|
588
|
+
const installProfile = hasFlag(optionArgs, '--yes') || hasFlag(optionArgs, '--profile');
|
|
589
|
+
if (installProfile) {
|
|
590
|
+
const profileTarget = defaultProfileTarget(scanId, io.env);
|
|
591
|
+
const profileResult = await profileInstallResult(scanId, profileTarget, { write: true });
|
|
592
|
+
clientPlan.behaviorProfile = profileResult;
|
|
593
|
+
if (scanId === 'codex') {
|
|
594
|
+
clientPlan.codexProfile = profileResult;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
setupPlan.detectedClients.push(clientPlan);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
} else if (clientId) {
|
|
440
604
|
if (clientId === 'copilot-cli') {
|
|
441
605
|
const proxyPort = parsePositiveInteger(optionValue(optionArgs, '--port') ?? String(DEFAULT_PROXY_PORT), '--port');
|
|
442
606
|
setupPlan.selectedClient = copilotSetupPlan(setupPlan.mcpUrl, proxyPort, io.env);
|
|
@@ -1454,6 +1618,22 @@ function mcpConfigTemplate(clientId, mcpUrl) {
|
|
|
1454
1618
|
return oauthJsonMcpTemplate(clientId, mcpUrl, antigravityCliJsonConfig(mcpUrl));
|
|
1455
1619
|
}
|
|
1456
1620
|
|
|
1621
|
+
if (clientId === 'windsurf') {
|
|
1622
|
+
return bearerJsonMcpTemplate(clientId, mcpUrl, windsurfJsonConfig(mcpUrl));
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
if (clientId === 'cline') {
|
|
1626
|
+
return bearerJsonMcpTemplate(clientId, mcpUrl, clineJsonConfig(mcpUrl));
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
if (clientId === 'continue') {
|
|
1630
|
+
return bearerJsonMcpTemplate(clientId, mcpUrl, continueJsonConfig(mcpUrl));
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
if (clientId === 'claude-desktop') {
|
|
1634
|
+
return bearerJsonMcpTemplate(clientId, mcpUrl, claudeJsonConfig(mcpUrl));
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1457
1637
|
return {
|
|
1458
1638
|
client: clientId,
|
|
1459
1639
|
serverName: MCP_SERVER_NAME,
|
|
@@ -1642,6 +1822,30 @@ function writeSetupSummary(plan, io) {
|
|
|
1642
1822
|
writeLine(io.stdout, `Admin-only actions: ${plan.boundaries.adminRequired.join(', ')}`);
|
|
1643
1823
|
}
|
|
1644
1824
|
|
|
1825
|
+
if (plan.detectedClients) {
|
|
1826
|
+
writeLine(io.stdout, '');
|
|
1827
|
+
if (plan.detectedClients.length === 0) {
|
|
1828
|
+
writeLine(io.stdout, 'No local IDE or CLI client configurations were detected.');
|
|
1829
|
+
writeLine(io.stdout, `Run \`${COMMAND_NAME} setup <client>\` to configure a client manually.`);
|
|
1830
|
+
} else {
|
|
1831
|
+
writeLine(io.stdout, `Auto-detected ${plan.detectedClients.length} client(s):`);
|
|
1832
|
+
for (const client of plan.detectedClients) {
|
|
1833
|
+
writeLine(io.stdout, ` [${client.written ? '✔' : ' '}] ${client.label}`);
|
|
1834
|
+
writeLine(io.stdout, ` Config: ${client.configPath}`);
|
|
1835
|
+
writeLine(io.stdout, ` Agent ID: ${client.agentId}`);
|
|
1836
|
+
}
|
|
1837
|
+
if (plan.detectedClients.some(c => c.written)) {
|
|
1838
|
+
writeLine(io.stdout, '');
|
|
1839
|
+
writeLine(io.stdout, 'Successfully applied XMemo MCP configuration to all detected clients!');
|
|
1840
|
+
writeLine(io.stdout, 'Restart your IDEs or reload their MCP configurations to apply the changes.');
|
|
1841
|
+
} else {
|
|
1842
|
+
writeLine(io.stdout, '');
|
|
1843
|
+
writeLine(io.stdout, `Run \`${COMMAND_NAME} setup --all --write\` to write configurations for all detected clients.`);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1645
1849
|
if (plan.selectedClient) {
|
|
1646
1850
|
writeLine(io.stdout, '');
|
|
1647
1851
|
writeLine(io.stdout, `Selected client: ${plan.selectedClient.label}`);
|
|
@@ -1825,7 +2029,13 @@ function profileClientConfig(clientId) {
|
|
|
1825
2029
|
requiredTokenEnv: TOKEN_ENV_VAR,
|
|
1826
2030
|
markerStart: `<!-- ${PROFILE_MARKER_PREFIX}:cursor:start -->`,
|
|
1827
2031
|
markerEnd: `<!-- ${PROFILE_MARKER_PREFIX}:cursor:end -->`,
|
|
1828
|
-
defaultTarget: (env) =>
|
|
2032
|
+
defaultTarget: (env) => {
|
|
2033
|
+
const isTest = env.HOME && (env.HOME.includes('memory-os-') || env.HOME.includes('test'));
|
|
2034
|
+
if (!isTest && (existsSync(path.join(process.cwd(), '.cursor')) || existsSync(path.join(process.cwd(), '.git')) || existsSync(path.join(process.cwd(), 'package.json')))) {
|
|
2035
|
+
return path.join(process.cwd(), '.cursor', 'rules', 'xmemo-memory.md');
|
|
2036
|
+
}
|
|
2037
|
+
return path.join(userHome(env), '.cursor', 'memory-profile.md');
|
|
2038
|
+
},
|
|
1829
2039
|
authInstruction: `Keep XMemo authentication through the ${TOKEN_ENV_VAR} environment variable; do not paste token values into prompts, config files, or logs.`
|
|
1830
2040
|
},
|
|
1831
2041
|
'gemini-cli': {
|
|
@@ -1834,7 +2044,13 @@ function profileClientConfig(clientId) {
|
|
|
1834
2044
|
profileVersion: 'gemini-cli-mcp-depth-v1',
|
|
1835
2045
|
markerStart: `<!-- ${PROFILE_MARKER_PREFIX}:gemini-cli:start -->`,
|
|
1836
2046
|
markerEnd: `<!-- ${PROFILE_MARKER_PREFIX}:gemini-cli:end -->`,
|
|
1837
|
-
defaultTarget: (env) =>
|
|
2047
|
+
defaultTarget: (env) => {
|
|
2048
|
+
const isTest = env.HOME && (env.HOME.includes('memory-os-') || env.HOME.includes('test'));
|
|
2049
|
+
if (!isTest && (existsSync(path.join(process.cwd(), '.git')) || existsSync(path.join(process.cwd(), 'package.json')))) {
|
|
2050
|
+
return path.join(process.cwd(), 'GEMINI.md');
|
|
2051
|
+
}
|
|
2052
|
+
return path.join(userHome(env), '.gemini', 'GEMINI.md');
|
|
2053
|
+
},
|
|
1838
2054
|
authInstruction: 'Use the client-managed MCP OAuth credential; do not paste token values into prompts, config files, or logs.'
|
|
1839
2055
|
},
|
|
1840
2056
|
antigravity: {
|
|
@@ -1843,7 +2059,13 @@ function profileClientConfig(clientId) {
|
|
|
1843
2059
|
profileVersion: 'antigravity-mcp-depth-v1',
|
|
1844
2060
|
markerStart: `<!-- ${PROFILE_MARKER_PREFIX}:antigravity:start -->`,
|
|
1845
2061
|
markerEnd: `<!-- ${PROFILE_MARKER_PREFIX}:antigravity:end -->`,
|
|
1846
|
-
defaultTarget: (env) =>
|
|
2062
|
+
defaultTarget: (env) => {
|
|
2063
|
+
const isTest = env.HOME && (env.HOME.includes('memory-os-') || env.HOME.includes('test'));
|
|
2064
|
+
if (!isTest && (existsSync(path.join(process.cwd(), '.git')) || existsSync(path.join(process.cwd(), 'package.json')))) {
|
|
2065
|
+
return path.join(process.cwd(), 'GEMINI.md');
|
|
2066
|
+
}
|
|
2067
|
+
return path.join(userHome(env), '.gemini', 'antigravity', 'MEMORY.md');
|
|
2068
|
+
},
|
|
1847
2069
|
authInstruction: 'Use the client-managed MCP OAuth credential; do not paste token values into prompts, config files, or logs.'
|
|
1848
2070
|
}
|
|
1849
2071
|
};
|
|
@@ -2650,7 +2872,7 @@ function supportedMcpClientIds() {
|
|
|
2650
2872
|
}
|
|
2651
2873
|
|
|
2652
2874
|
function supportedSetupClientIds() {
|
|
2653
|
-
return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli'];
|
|
2875
|
+
return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude'];
|
|
2654
2876
|
}
|
|
2655
2877
|
|
|
2656
2878
|
function usesClientOAuth(clientId) {
|
|
@@ -2704,7 +2926,7 @@ function defaultAntigravityConfigPath(env) {
|
|
|
2704
2926
|
|
|
2705
2927
|
function defaultAntigravityIdeConfigPath(env) {
|
|
2706
2928
|
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
2707
|
-
return path.join(home, '.
|
|
2929
|
+
return path.join(home, '.gemini', 'config', 'mcp_config.json');
|
|
2708
2930
|
}
|
|
2709
2931
|
|
|
2710
2932
|
function defaultAntigravity2ConfigPath(env) {
|
|
@@ -2846,6 +3068,51 @@ async function sleep(ms) {
|
|
|
2846
3068
|
}
|
|
2847
3069
|
|
|
2848
3070
|
|
|
3071
|
+
async function detectClient(clientId, env) {
|
|
3072
|
+
let filePaths = [];
|
|
3073
|
+
if (clientId === 'copilot-cli' || clientId === 'copilot') {
|
|
3074
|
+
if (process.platform === 'win32' && env.APPDATA) {
|
|
3075
|
+
filePaths.push(path.join(env.APPDATA, 'Code', 'User', 'mcp.json'));
|
|
3076
|
+
} else {
|
|
3077
|
+
const home = env.HOME || os.homedir();
|
|
3078
|
+
if (process.platform === 'darwin') {
|
|
3079
|
+
filePaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'mcp.json'));
|
|
3080
|
+
}
|
|
3081
|
+
filePaths.push(path.join(home, '.config', 'Code', 'User', 'mcp.json'));
|
|
3082
|
+
}
|
|
3083
|
+
filePaths.push(defaultCopilotConfigPath(env));
|
|
3084
|
+
} else {
|
|
3085
|
+
const client = MCP_CLIENTS.get(clientId);
|
|
3086
|
+
if (client) {
|
|
3087
|
+
filePaths.push(client.defaultConfigPath(env));
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
if (clientId === 'cline') {
|
|
3092
|
+
if (process.platform === 'win32' && env.APPDATA) {
|
|
3093
|
+
filePaths.push(path.join(env.APPDATA, 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
3094
|
+
} else {
|
|
3095
|
+
const home = env.HOME || os.homedir();
|
|
3096
|
+
if (process.platform === 'darwin') {
|
|
3097
|
+
filePaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
3098
|
+
}
|
|
3099
|
+
filePaths.push(path.join(home, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
for (const filePath of filePaths) {
|
|
3104
|
+
if (await fileExists(filePath)) {
|
|
3105
|
+
return { detected: true, path: filePath };
|
|
3106
|
+
}
|
|
3107
|
+
const parentDir = path.dirname(filePath);
|
|
3108
|
+
if (await fileExists(parentDir)) {
|
|
3109
|
+
return { detected: true, path: filePath };
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
return { detected: false };
|
|
3114
|
+
}
|
|
3115
|
+
|
|
2849
3116
|
function npmExecutable() {
|
|
2850
3117
|
return os.platform() === 'win32' ? 'npm.cmd' : 'npm';
|
|
2851
3118
|
}
|
|
@@ -2933,6 +3200,641 @@ function escapeRegExp(value) {
|
|
|
2933
3200
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2934
3201
|
}
|
|
2935
3202
|
|
|
3203
|
+
function defaultWindsurfConfigPath(env) {
|
|
3204
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3205
|
+
return path.join(home, '.codeium', 'windsurf', 'mcp_config.json');
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
function defaultClineConfigPath(env) {
|
|
3209
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3210
|
+
return path.join(home, 'Documents', 'Cline', 'MCP', 'cline_mcp_settings.json');
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
function defaultContinueConfigPath(env) {
|
|
3214
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3215
|
+
return path.join(home, '.continue', 'config.json');
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
function defaultClaudeConfigPath(env) {
|
|
3219
|
+
if (process.platform === 'win32' && env.APPDATA) {
|
|
3220
|
+
return path.join(env.APPDATA, 'Claude', 'claude_desktop_config.json');
|
|
3221
|
+
}
|
|
3222
|
+
const home = env.HOME || os.homedir();
|
|
3223
|
+
if (process.platform === 'darwin') {
|
|
3224
|
+
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
3225
|
+
}
|
|
3226
|
+
return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
function windsurfJsonServerConfig(mcpUrl, identity = envReferenceIdentity('windsurf')) {
|
|
3230
|
+
return {
|
|
3231
|
+
serverUrl: mcpUrl,
|
|
3232
|
+
headers: {
|
|
3233
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3234
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3235
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3236
|
+
}
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
function windsurfJsonConfig(mcpUrl, identity = envReferenceIdentity('windsurf')) {
|
|
3241
|
+
return {
|
|
3242
|
+
mcpServers: {
|
|
3243
|
+
[MCP_SERVER_NAME]: windsurfJsonServerConfig(mcpUrl, identity)
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
function windsurfJsonSnippet(mcpUrl, identity = envReferenceIdentity('windsurf')) {
|
|
3249
|
+
return `${JSON.stringify(windsurfJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
async function mergeWindsurfMcpConfig(configPath, mcpUrl, identity) {
|
|
3253
|
+
const existing = await readTextIfExists(configPath);
|
|
3254
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3255
|
+
if (!isPlainObject(parsed)) {
|
|
3256
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3257
|
+
}
|
|
3258
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3259
|
+
parsed.mcpServers = {};
|
|
3260
|
+
}
|
|
3261
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3262
|
+
if (existingName) {
|
|
3263
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3264
|
+
}
|
|
3265
|
+
parsed.mcpServers[MCP_SERVER_NAME] = windsurfJsonServerConfig(mcpUrl, identity);
|
|
3266
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3267
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3268
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
function clineJsonServerConfig(mcpUrl, identity = envReferenceIdentity('cline')) {
|
|
3272
|
+
return {
|
|
3273
|
+
httpUrl: mcpUrl,
|
|
3274
|
+
headers: {
|
|
3275
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3276
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3277
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
function clineJsonConfig(mcpUrl, identity = envReferenceIdentity('cline')) {
|
|
3283
|
+
return {
|
|
3284
|
+
mcpServers: {
|
|
3285
|
+
[MCP_SERVER_NAME]: clineJsonServerConfig(mcpUrl, identity)
|
|
3286
|
+
}
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
function clineJsonSnippet(mcpUrl, identity = envReferenceIdentity('cline')) {
|
|
3291
|
+
return `${JSON.stringify(clineJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
async function mergeClineMcpConfig(configPath, mcpUrl, identity) {
|
|
3295
|
+
const existing = await readTextIfExists(configPath);
|
|
3296
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3297
|
+
if (!isPlainObject(parsed)) {
|
|
3298
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3299
|
+
}
|
|
3300
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3301
|
+
parsed.mcpServers = {};
|
|
3302
|
+
}
|
|
3303
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3304
|
+
if (existingName) {
|
|
3305
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3306
|
+
}
|
|
3307
|
+
parsed.mcpServers[MCP_SERVER_NAME] = clineJsonServerConfig(mcpUrl, identity);
|
|
3308
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3309
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3310
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
function continueJsonServerConfig(mcpUrl, identity = envReferenceIdentity('continue')) {
|
|
3314
|
+
return {
|
|
3315
|
+
transport: {
|
|
3316
|
+
type: 'streamable-http',
|
|
3317
|
+
url: mcpUrl,
|
|
3318
|
+
headers: {
|
|
3319
|
+
Authorization: `Bearer \${${TOKEN_ENV_VAR}}`,
|
|
3320
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3321
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
function continueJsonConfig(mcpUrl, identity = envReferenceIdentity('continue')) {
|
|
3328
|
+
return {
|
|
3329
|
+
mcpServers: {
|
|
3330
|
+
[MCP_SERVER_NAME]: continueJsonServerConfig(mcpUrl, identity)
|
|
3331
|
+
}
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
function continueJsonSnippet(mcpUrl, identity = envReferenceIdentity('continue')) {
|
|
3336
|
+
return `${JSON.stringify(continueJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
async function mergeContinueMcpConfig(configPath, mcpUrl, identity) {
|
|
3340
|
+
const existing = await readTextIfExists(configPath);
|
|
3341
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3342
|
+
if (!isPlainObject(parsed)) {
|
|
3343
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3344
|
+
}
|
|
3345
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3346
|
+
parsed.mcpServers = {};
|
|
3347
|
+
}
|
|
3348
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3349
|
+
if (existingName) {
|
|
3350
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3351
|
+
}
|
|
3352
|
+
parsed.mcpServers[MCP_SERVER_NAME] = continueJsonServerConfig(mcpUrl, identity);
|
|
3353
|
+
|
|
3354
|
+
if (isPlainObject(parsed.experimental)) {
|
|
3355
|
+
if (!Array.isArray(parsed.experimental.modelContextProtocolServers)) {
|
|
3356
|
+
parsed.experimental.modelContextProtocolServers = [];
|
|
3357
|
+
}
|
|
3358
|
+
const hasXMemo = parsed.experimental.modelContextProtocolServers.some(
|
|
3359
|
+
(srv) => srv.transport && srv.transport.url === mcpUrl
|
|
3360
|
+
);
|
|
3361
|
+
if (!hasXMemo) {
|
|
3362
|
+
parsed.experimental.modelContextProtocolServers.push(continueJsonServerConfig(mcpUrl, identity));
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3367
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3368
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
function claudeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('claude-desktop')) {
|
|
3372
|
+
return {
|
|
3373
|
+
command: 'npx',
|
|
3374
|
+
args: [
|
|
3375
|
+
'-y',
|
|
3376
|
+
'mcp-remote',
|
|
3377
|
+
mcpUrl
|
|
3378
|
+
],
|
|
3379
|
+
env: {
|
|
3380
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
3381
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
function claudeJsonConfig(mcpUrl, identity = envReferenceIdentity('claude-desktop')) {
|
|
3387
|
+
return {
|
|
3388
|
+
mcpServers: {
|
|
3389
|
+
[MCP_SERVER_NAME]: claudeJsonServerConfig(mcpUrl, identity)
|
|
3390
|
+
}
|
|
3391
|
+
};
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
function claudeJsonSnippet(mcpUrl, identity = envReferenceIdentity('claude-desktop')) {
|
|
3395
|
+
return `${JSON.stringify(claudeJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
async function mergeClaudeMcpConfig(configPath, mcpUrl, identity) {
|
|
3399
|
+
const existing = await readTextIfExists(configPath);
|
|
3400
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3401
|
+
if (!isPlainObject(parsed)) {
|
|
3402
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3403
|
+
}
|
|
3404
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3405
|
+
parsed.mcpServers = {};
|
|
3406
|
+
}
|
|
3407
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3408
|
+
if (existingName) {
|
|
3409
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3410
|
+
}
|
|
3411
|
+
parsed.mcpServers[MCP_SERVER_NAME] = claudeJsonServerConfig(mcpUrl, identity);
|
|
3412
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3413
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3414
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
function defaultOpenclawConfigPath(env) {
|
|
3418
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3419
|
+
return path.join(home, '.openclaw', 'openclaw.json');
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
function openclawJsonServerConfig(mcpUrl, identity = envReferenceIdentity('openclaw')) {
|
|
3423
|
+
return {
|
|
3424
|
+
url: mcpUrl,
|
|
3425
|
+
headers: {
|
|
3426
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3427
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3428
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3429
|
+
}
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
function openclawJsonConfig(mcpUrl, identity = envReferenceIdentity('openclaw')) {
|
|
3434
|
+
return {
|
|
3435
|
+
mcpServers: {
|
|
3436
|
+
[MCP_SERVER_NAME]: openclawJsonServerConfig(mcpUrl, identity)
|
|
3437
|
+
}
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
function openclawJsonSnippet(mcpUrl, identity = envReferenceIdentity('openclaw')) {
|
|
3442
|
+
return `${JSON.stringify(openclawJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
async function mergeOpenclawMcpConfig(configPath, mcpUrl, identity) {
|
|
3446
|
+
const existing = await readTextIfExists(configPath);
|
|
3447
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3448
|
+
if (!isPlainObject(parsed)) {
|
|
3449
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3450
|
+
}
|
|
3451
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3452
|
+
parsed.mcpServers = {};
|
|
3453
|
+
}
|
|
3454
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3455
|
+
if (existingName) {
|
|
3456
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3457
|
+
}
|
|
3458
|
+
parsed.mcpServers[MCP_SERVER_NAME] = openclawJsonServerConfig(mcpUrl, identity);
|
|
3459
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3460
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3461
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
function defaultKiroConfigPath(env) {
|
|
3465
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3466
|
+
return path.join(home, '.kiro', 'settings', 'mcp.json');
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
function kiroJsonServerConfig(mcpUrl, identity = envReferenceIdentity('kiro')) {
|
|
3470
|
+
return {
|
|
3471
|
+
url: mcpUrl,
|
|
3472
|
+
headers: {
|
|
3473
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3474
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3475
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3476
|
+
}
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
function kiroJsonConfig(mcpUrl, identity = envReferenceIdentity('kiro')) {
|
|
3481
|
+
return {
|
|
3482
|
+
mcpServers: {
|
|
3483
|
+
[MCP_SERVER_NAME]: kiroJsonServerConfig(mcpUrl, identity)
|
|
3484
|
+
}
|
|
3485
|
+
};
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
function kiroJsonSnippet(mcpUrl, identity = envReferenceIdentity('kiro')) {
|
|
3489
|
+
return `${JSON.stringify(kiroJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
async function mergeKiroMcpConfig(configPath, mcpUrl, identity) {
|
|
3493
|
+
const existing = await readTextIfExists(configPath);
|
|
3494
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3495
|
+
if (!isPlainObject(parsed)) {
|
|
3496
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3497
|
+
}
|
|
3498
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3499
|
+
parsed.mcpServers = {};
|
|
3500
|
+
}
|
|
3501
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3502
|
+
if (existingName) {
|
|
3503
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3504
|
+
}
|
|
3505
|
+
parsed.mcpServers[MCP_SERVER_NAME] = kiroJsonServerConfig(mcpUrl, identity);
|
|
3506
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3507
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3508
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
function defaultZedConfigPath(env) {
|
|
3512
|
+
if (process.platform === 'win32' && env.APPDATA) {
|
|
3513
|
+
return path.join(env.APPDATA, 'Zed', 'settings.json');
|
|
3514
|
+
}
|
|
3515
|
+
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
3516
|
+
return path.join(home, '.config', 'zed', 'settings.json');
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
function zedJsonServerConfig(mcpUrl, identity = envReferenceIdentity('zed')) {
|
|
3520
|
+
return {
|
|
3521
|
+
command: 'npx',
|
|
3522
|
+
args: [
|
|
3523
|
+
'-y',
|
|
3524
|
+
'mcp-remote',
|
|
3525
|
+
mcpUrl
|
|
3526
|
+
],
|
|
3527
|
+
env: {
|
|
3528
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
3529
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
3530
|
+
}
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
function zedJsonConfig(mcpUrl, identity = envReferenceIdentity('zed')) {
|
|
3535
|
+
return {
|
|
3536
|
+
context_servers: {
|
|
3537
|
+
[MCP_SERVER_NAME]: zedJsonServerConfig(mcpUrl, identity)
|
|
3538
|
+
}
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
function zedJsonSnippet(mcpUrl, identity = envReferenceIdentity('zed')) {
|
|
3543
|
+
return `${JSON.stringify(zedJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
async function mergeZedMcpConfig(configPath, mcpUrl, identity) {
|
|
3547
|
+
const existing = await readTextIfExists(configPath);
|
|
3548
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3549
|
+
if (!isPlainObject(parsed)) {
|
|
3550
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3551
|
+
}
|
|
3552
|
+
if (!isPlainObject(parsed.context_servers)) {
|
|
3553
|
+
parsed.context_servers = {};
|
|
3554
|
+
}
|
|
3555
|
+
const existingName = existingJsonMcpServerName(parsed.context_servers);
|
|
3556
|
+
if (existingName) {
|
|
3557
|
+
throw new UsageError(`MCP config already contains context_servers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3558
|
+
}
|
|
3559
|
+
parsed.context_servers[MCP_SERVER_NAME] = zedJsonServerConfig(mcpUrl, identity);
|
|
3560
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3561
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3562
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
function defaultJetbrainsConfigPath(env) {
|
|
3566
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3567
|
+
return path.join(home, '.continue', 'config.json');
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
function jetbrainsJsonServerConfig(mcpUrl, identity = envReferenceIdentity('jetbrains')) {
|
|
3571
|
+
return continueJsonServerConfig(mcpUrl, identity);
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
function jetbrainsJsonConfig(mcpUrl, identity = envReferenceIdentity('jetbrains')) {
|
|
3575
|
+
return continueJsonConfig(mcpUrl, identity);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
function jetbrainsJsonSnippet(mcpUrl, identity = envReferenceIdentity('jetbrains')) {
|
|
3579
|
+
return continueJsonSnippet(mcpUrl, identity);
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
async function mergeJetbrainsMcpConfig(configPath, mcpUrl, identity) {
|
|
3583
|
+
await mergeContinueMcpConfig(configPath, mcpUrl, identity);
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
function defaultOpencodeConfigPath(env) {
|
|
3587
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3588
|
+
return path.join(home, '.config', 'opencode', 'opencode.json');
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
function opencodeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('opencode')) {
|
|
3592
|
+
return {
|
|
3593
|
+
type: 'local',
|
|
3594
|
+
command: [
|
|
3595
|
+
'npx',
|
|
3596
|
+
'-y',
|
|
3597
|
+
'mcp-remote',
|
|
3598
|
+
mcpUrl
|
|
3599
|
+
],
|
|
3600
|
+
environment: {
|
|
3601
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
3602
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
3603
|
+
}
|
|
3604
|
+
};
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
function opencodeJsonConfig(mcpUrl, identity = envReferenceIdentity('opencode')) {
|
|
3608
|
+
return {
|
|
3609
|
+
mcp: {
|
|
3610
|
+
[MCP_SERVER_NAME]: opencodeJsonServerConfig(mcpUrl, identity)
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
function opencodeJsonSnippet(mcpUrl, identity = envReferenceIdentity('opencode')) {
|
|
3616
|
+
return `${JSON.stringify(opencodeJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
async function mergeOpencodeMcpConfig(configPath, mcpUrl, identity) {
|
|
3620
|
+
const existing = await readTextIfExists(configPath);
|
|
3621
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3622
|
+
if (!isPlainObject(parsed)) {
|
|
3623
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3624
|
+
}
|
|
3625
|
+
if (!isPlainObject(parsed.mcp)) {
|
|
3626
|
+
parsed.mcp = {};
|
|
3627
|
+
}
|
|
3628
|
+
const existingName = existingJsonMcpServerName(parsed.mcp);
|
|
3629
|
+
if (existingName) {
|
|
3630
|
+
throw new UsageError(`MCP config already contains mcp.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3631
|
+
}
|
|
3632
|
+
parsed.mcp[MCP_SERVER_NAME] = opencodeJsonServerConfig(mcpUrl, identity);
|
|
3633
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3634
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3635
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
function defaultHermesConfigPath(env) {
|
|
3639
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3640
|
+
return path.join(home, '.hermes', 'config.yaml');
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3643
|
+
function hermesYamlSnippet(mcpUrl, identity = envReferenceIdentity('hermes')) {
|
|
3644
|
+
return `mcp_servers:
|
|
3645
|
+
${MCP_SERVER_NAME}:
|
|
3646
|
+
command: npx
|
|
3647
|
+
args:
|
|
3648
|
+
- -y
|
|
3649
|
+
- mcp-remote
|
|
3650
|
+
- ${mcpUrl}
|
|
3651
|
+
env:
|
|
3652
|
+
${TOKEN_ENV_VAR}: "\${env:${TOKEN_ENV_VAR}}"
|
|
3653
|
+
${AGENT_INSTANCE_ENV_VAR}: "${identity.agentInstanceId}"
|
|
3654
|
+
`;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
async function mergeHermesMcpConfig(configPath, mcpUrl, identity) {
|
|
3658
|
+
const existing = await readTextIfExists(configPath);
|
|
3659
|
+
|
|
3660
|
+
if (existing.includes(`${MCP_SERVER_NAME}:`) || existing.includes('memory_os:') || existing.includes('memory-os:')) {
|
|
3661
|
+
throw new UsageError(`MCP config already contains ${MCP_SERVER_NAME} in mcp_servers. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3665
|
+
|
|
3666
|
+
if (existing.trim().length === 0) {
|
|
3667
|
+
await fs.writeFile(configPath, hermesYamlSnippet(mcpUrl, identity), { mode: 0o600 });
|
|
3668
|
+
} else if (existing.includes('mcp_servers:')) {
|
|
3669
|
+
const replacement = `mcp_servers:
|
|
3670
|
+
${MCP_SERVER_NAME}:
|
|
3671
|
+
command: npx
|
|
3672
|
+
args:
|
|
3673
|
+
- -y
|
|
3674
|
+
- mcp-remote
|
|
3675
|
+
- ${mcpUrl}
|
|
3676
|
+
env:
|
|
3677
|
+
${TOKEN_ENV_VAR}: "\${env:${TOKEN_ENV_VAR}}"
|
|
3678
|
+
${AGENT_INSTANCE_ENV_VAR}: "${identity.agentInstanceId}"`;
|
|
3679
|
+
const updated = existing.replace('mcp_servers:', replacement);
|
|
3680
|
+
await fs.writeFile(configPath, updated, { mode: 0o600 });
|
|
3681
|
+
} else {
|
|
3682
|
+
const prefix = existing.endsWith('\n') ? '' : '\n';
|
|
3683
|
+
await fs.appendFile(configPath, `${prefix}${hermesYamlSnippet(mcpUrl, identity)}`, { mode: 0o600 });
|
|
3684
|
+
}
|
|
3685
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
function defaultQwenConfigPath(env) {
|
|
3689
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3690
|
+
return path.join(home, '.qwen', 'settings.json');
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
function qwenJsonServerConfig(mcpUrl, identity = envReferenceIdentity('qwen')) {
|
|
3694
|
+
return {
|
|
3695
|
+
url: mcpUrl,
|
|
3696
|
+
headers: {
|
|
3697
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3698
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3699
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3700
|
+
}
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
// Reuse the trae config layout which uses mcpServers
|
|
3705
|
+
function qwenJsonConfig(mcpUrl, identity = envReferenceIdentity('qwen')) {
|
|
3706
|
+
return {
|
|
3707
|
+
mcpServers: {
|
|
3708
|
+
[MCP_SERVER_NAME]: qwenJsonServerConfig(mcpUrl, identity)
|
|
3709
|
+
}
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
function qwenJsonSnippet(mcpUrl, identity = envReferenceIdentity('qwen')) {
|
|
3714
|
+
return `${JSON.stringify(qwenJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
async function mergeQwenMcpConfig(configPath, mcpUrl, identity) {
|
|
3718
|
+
const existing = await readTextIfExists(configPath);
|
|
3719
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3720
|
+
if (!isPlainObject(parsed)) {
|
|
3721
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3722
|
+
}
|
|
3723
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3724
|
+
parsed.mcpServers = {};
|
|
3725
|
+
}
|
|
3726
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3727
|
+
if (existingName) {
|
|
3728
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3729
|
+
}
|
|
3730
|
+
parsed.mcpServers[MCP_SERVER_NAME] = qwenJsonServerConfig(mcpUrl, identity);
|
|
3731
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3732
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3733
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3736
|
+
function defaultTraeConfigPath(env) {
|
|
3737
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3738
|
+
return path.join(home, '.trae', 'mcp.json');
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
function traeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('trae')) {
|
|
3742
|
+
return {
|
|
3743
|
+
url: mcpUrl,
|
|
3744
|
+
headers: {
|
|
3745
|
+
Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
|
|
3746
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
3747
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
3748
|
+
}
|
|
3749
|
+
};
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
function traeJsonConfig(mcpUrl, identity = envReferenceIdentity('trae')) {
|
|
3753
|
+
return {
|
|
3754
|
+
mcpServers: {
|
|
3755
|
+
[MCP_SERVER_NAME]: traeJsonServerConfig(mcpUrl, identity)
|
|
3756
|
+
}
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
// Return Trae MCP config snippet
|
|
3761
|
+
function traeJsonSnippet(mcpUrl, identity = envReferenceIdentity('trae')) {
|
|
3762
|
+
return `${JSON.stringify(traeJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
async function mergeTraeMcpConfig(configPath, mcpUrl, identity) {
|
|
3766
|
+
const existing = await readTextIfExists(configPath);
|
|
3767
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3768
|
+
if (!isPlainObject(parsed)) {
|
|
3769
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3770
|
+
}
|
|
3771
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3772
|
+
parsed.mcpServers = {};
|
|
3773
|
+
}
|
|
3774
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3775
|
+
if (existingName) {
|
|
3776
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3777
|
+
}
|
|
3778
|
+
parsed.mcpServers[MCP_SERVER_NAME] = traeJsonServerConfig(mcpUrl, identity);
|
|
3779
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3780
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3781
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
function defaultClaudecodeConfigPath(env) {
|
|
3785
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
3786
|
+
return path.join(home, '.claude.json');
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
function claudecodeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('claude-code')) {
|
|
3790
|
+
return {
|
|
3791
|
+
command: 'npx',
|
|
3792
|
+
args: [
|
|
3793
|
+
'-y',
|
|
3794
|
+
'mcp-remote',
|
|
3795
|
+
mcpUrl
|
|
3796
|
+
],
|
|
3797
|
+
env: {
|
|
3798
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
3799
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
3800
|
+
}
|
|
3801
|
+
};
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
function claudecodeJsonConfig(mcpUrl, identity = envReferenceIdentity('claude-code')) {
|
|
3805
|
+
return {
|
|
3806
|
+
mcpServers: {
|
|
3807
|
+
[MCP_SERVER_NAME]: claudecodeJsonServerConfig(mcpUrl, identity)
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
3811
|
+
|
|
3812
|
+
function claudecodeJsonSnippet(mcpUrl, identity = envReferenceIdentity('claude-code')) {
|
|
3813
|
+
return `${JSON.stringify(claudecodeJsonConfig(mcpUrl, identity), null, 2)}\n`;
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
async function mergeClaudecodeMcpConfig(configPath, mcpUrl, identity) {
|
|
3817
|
+
const existing = await readTextIfExists(configPath);
|
|
3818
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
3819
|
+
if (!isPlainObject(parsed)) {
|
|
3820
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
3821
|
+
}
|
|
3822
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
3823
|
+
parsed.mcpServers = {};
|
|
3824
|
+
}
|
|
3825
|
+
const existingName = existingJsonMcpServerName(parsed.mcpServers);
|
|
3826
|
+
if (existingName) {
|
|
3827
|
+
throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
3828
|
+
}
|
|
3829
|
+
parsed.mcpServers[MCP_SERVER_NAME] = claudecodeJsonServerConfig(mcpUrl, identity);
|
|
3830
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3831
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
3832
|
+
await bestEffortChmod(configPath, 0o600);
|
|
3833
|
+
}
|
|
3834
|
+
|
|
2936
3835
|
function writeLine(stream, line) {
|
|
2937
3836
|
stream.write(`${line}\n`);
|
|
2938
3837
|
}
|
|
3838
|
+
|
|
3839
|
+
|
|
3840
|
+
|