@xmemo/client 0.4.139 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +892 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmemo/client",
3
- "version": "0.4.139",
3
+ "version": "0.4.141",
4
4
  "description": "Privacy-first CLI and MCP setup helper for XMemo.",
5
5
  "type": "module",
6
6
  "bin": {
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.139';
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';
@@ -85,6 +85,97 @@ const MCP_CLIENTS = new Map([
85
85
  buildSnippet: antigravityCliJsonSnippet,
86
86
  writeConfig: mergeAntigravityCliMcpConfig,
87
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'
88
179
  }]
89
180
  ]);
90
181
 
@@ -98,7 +189,26 @@ const SETUP_CLIENT_ALIASES = new Map([
98
189
  ['antigravity', 'antigravity'],
99
190
  ['antigravity-ide', 'antigravity-ide'],
100
191
  ['antigravity2', 'antigravity2'],
101
- ['antigravity-cli', 'antigravity-cli']
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']
102
212
  ]);
103
213
 
104
214
  class UsageError extends Error {
@@ -417,13 +527,27 @@ async function setupCommand(args, io) {
417
527
  const baseUrl = normalizeBaseUrl(baseUrlOption(optionArgs, io.env));
418
528
  const outputJson = hasFlag(optionArgs, '--json');
419
529
  const shortClientSetup = Boolean(positionalClientId);
420
- const clientId = normalizeSetupClientId(positionalClientId ?? optionValue(optionArgs, '--client'));
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
+
421
545
  const dryRun = hasFlag(optionArgs, '--dry-run') || hasFlag(optionArgs, '--preview');
422
- 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'))));
423
547
  const timeoutMs = parsePositiveInteger(optionValue(optionArgs, '--timeout-ms') ?? '5000', '--timeout-ms');
424
548
 
425
- if (writeConfig && !clientId) {
426
- 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.`);
427
551
  }
428
552
 
429
553
  const discoveryUrl = endpointUrl(baseUrl, '/.well-known/memory-os.json');
@@ -436,7 +560,46 @@ async function setupCommand(args, io) {
436
560
  const status = await fetchJson(statusUrl, timeoutMs, io);
437
561
  const setupPlan = buildSetupPlan({ baseUrl, discoveryUrl, statusUrl, discovery, status });
438
562
 
439
- if (clientId) {
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) {
440
603
  if (clientId === 'copilot-cli') {
441
604
  const proxyPort = parsePositiveInteger(optionValue(optionArgs, '--port') ?? String(DEFAULT_PROXY_PORT), '--port');
442
605
  setupPlan.selectedClient = copilotSetupPlan(setupPlan.mcpUrl, proxyPort, io.env);
@@ -1454,6 +1617,22 @@ function mcpConfigTemplate(clientId, mcpUrl) {
1454
1617
  return oauthJsonMcpTemplate(clientId, mcpUrl, antigravityCliJsonConfig(mcpUrl));
1455
1618
  }
1456
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
+
1457
1636
  return {
1458
1637
  client: clientId,
1459
1638
  serverName: MCP_SERVER_NAME,
@@ -1642,6 +1821,30 @@ function writeSetupSummary(plan, io) {
1642
1821
  writeLine(io.stdout, `Admin-only actions: ${plan.boundaries.adminRequired.join(', ')}`);
1643
1822
  }
1644
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
+
1645
1848
  if (plan.selectedClient) {
1646
1849
  writeLine(io.stdout, '');
1647
1850
  writeLine(io.stdout, `Selected client: ${plan.selectedClient.label}`);
@@ -2650,7 +2853,7 @@ function supportedMcpClientIds() {
2650
2853
  }
2651
2854
 
2652
2855
  function supportedSetupClientIds() {
2653
- return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli'];
2856
+ return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude'];
2654
2857
  }
2655
2858
 
2656
2859
  function usesClientOAuth(clientId) {
@@ -2704,7 +2907,7 @@ function defaultAntigravityConfigPath(env) {
2704
2907
 
2705
2908
  function defaultAntigravityIdeConfigPath(env) {
2706
2909
  const home = env.USERPROFILE || env.HOME || os.homedir();
2707
- return path.join(home, '.antigravity-ide', 'mcp.json');
2910
+ return path.join(home, '.gemini', 'config', 'mcp_config.json');
2708
2911
  }
2709
2912
 
2710
2913
  function defaultAntigravity2ConfigPath(env) {
@@ -2846,6 +3049,51 @@ async function sleep(ms) {
2846
3049
  }
2847
3050
 
2848
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
+
2849
3097
  function npmExecutable() {
2850
3098
  return os.platform() === 'win32' ? 'npm.cmd' : 'npm';
2851
3099
  }
@@ -2933,6 +3181,641 @@ function escapeRegExp(value) {
2933
3181
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2934
3182
  }
2935
3183
 
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
+ };
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
+
2936
3816
  function writeLine(stream, line) {
2937
3817
  stream.write(`${line}\n`);
2938
3818
  }
3819
+
3820
+
3821
+