@xmemo/client 0.4.137 → 0.4.139

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 (3) hide show
  1. package/README.md +21 -11
  2. package/package.json +1 -1
  3. package/src/cli.js +289 -24
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # XMemo CLI
2
2
 
3
+ [![smithery badge](https://smithery.ai/badge/xmemo/xmemo)](https://smithery.ai/servers/xmemo/xmemo)
4
+
3
5
  `@xmemo/client` is the privacy-first command line entry point for XMemo client
4
6
  setup. It is intentionally small: the npm package contains only the CLI and
5
7
  setup helper code needed on a user's machine.
@@ -235,7 +237,7 @@ xmemo setup copilot
235
237
  xmemo mcp proxy
236
238
  ```
237
239
 
238
- `xmemo setup copilot` writes `memory-os` to Copilot CLI's user MCP config and
240
+ `xmemo setup copilot` writes `XMemo` to Copilot CLI's user MCP config and
239
241
  does not include token or identity headers. Use `xmemo setup copilot --dry-run`
240
242
  to preview without writing. `xmemo mcp proxy` reads the token saved by
241
243
  `xmemo login` or `xmemo token add --from-stdin`, adds the XMemo bearer token and
@@ -285,9 +287,9 @@ Write it to the default Codex config path:
285
287
  xmemo mcp add codex --url "$XMEMO_URL" --write
286
288
  ```
287
289
 
288
- The generated config references `XMEMO_KEY` and does not include the token
289
- value. Codex custom identity headers are not written until the CLI format is
290
- verified to support them.
290
+ The generated config references `XMEMO_KEY`, includes the non-secret
291
+ `X-Memory-OS-Agent-ID` / `X-Memory-OS-Agent-Instance-ID` attribution headers,
292
+ and does not include the token value.
291
293
 
292
294
  Codex MCP-depth checks:
293
295
 
@@ -302,12 +304,17 @@ xmemo smoke --client codex
302
304
  `xmemo mcp profile codex` prints the recommended memory behavior profile:
303
305
  recall/search at the start of non-trivial tasks, write back high-signal
304
306
  decisions and fixes, and never store secrets. `xmemo smoke --client codex`
305
- checks the local Codex TOML config for the `memory_os` MCP server,
307
+ checks the local Codex TOML config for the `XMemo` MCP server,
306
308
  `bearer_token_env_var = "XMEMO_KEY"`, token presence in the environment, and
307
309
  absence of embedded token values.
308
310
 
309
311
  ### Cursor
310
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
+
311
318
  Recommended Cursor setup:
312
319
 
313
320
  ```bash
@@ -322,12 +329,15 @@ lower-level equivalent remains:
322
329
  xmemo mcp add cursor --url "$XMEMO_URL" --write
323
330
  ```
324
331
 
325
- The CLI refuses to overwrite an existing `memory_os` MCP server entry. Edit the
326
- config manually if you need to rotate the endpoint. Cursor configs include
332
+ The CLI refuses to overwrite an existing `XMemo`, `memory_os`, or `memory-os`
333
+ MCP server entry. Edit the config manually if you need to rotate the endpoint.
334
+ Cursor configs include
327
335
  `X-Memory-OS-Agent-ID` and `X-Memory-OS-Agent-Instance-ID`; the instance ID is
328
336
  non-secret and stored under the user's XMemo CLI config directory. By default,
329
337
  the setup prompt also installs a Cursor behavior profile at
330
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.
331
341
 
332
342
  ### Gemini CLI
333
343
 
@@ -352,8 +362,8 @@ run `/mcp` (or the first XMemo tool call) to complete the OAuth login. By
352
362
  default, the setup prompt also installs a Gemini behavior profile at
353
363
  `~/.gemini/GEMINI.md`; answer `n` or pass `--no-profile` to skip it.
354
364
 
355
- The CLI refuses to overwrite an existing `memory_os` MCP server entry. Edit the
356
- config manually if you need to rotate the endpoint.
365
+ The CLI refuses to overwrite an existing `XMemo`, `memory_os`, or `memory-os`
366
+ MCP server entry. Edit the config manually if you need to rotate the endpoint.
357
367
 
358
368
  ### Antigravity
359
369
 
@@ -381,8 +391,8 @@ Use `xmemo setup antigravity` for normal installs because it performs discovery
381
391
  and chooses the recommended Antigravity path automatically. Use
382
392
  `xmemo mcp add antigravity --write` when you want the generic MCP writer
383
393
  directly, for example with `--url` or `--config` in advanced/multi-client setup.
384
- The CLI refuses to overwrite an existing `memory_os` MCP server entry. Edit the
385
- config manually if you need to rotate the endpoint.
394
+ The CLI refuses to overwrite an existing `XMemo`, `memory_os`, or `memory-os`
395
+ MCP server entry. Edit the config manually if you need to rotate the endpoint.
386
396
 
387
397
  ## Release model
388
398
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmemo/client",
3
- "version": "0.4.137",
3
+ "version": "0.4.139",
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.137';
13
+ const CLI_VERSION = '0.4.139';
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';
@@ -18,7 +18,8 @@ const AGENT_ID_ENV_VAR = 'XMEMO_AGENT_ID';
18
18
  const AGENT_INSTANCE_ENV_VAR = 'XMEMO_AGENT_INSTANCE_ID';
19
19
  const AGENT_ID_HEADER = 'X-Memory-OS-Agent-ID';
20
20
  const AGENT_INSTANCE_HEADER = 'X-Memory-OS-Agent-Instance-ID';
21
- const MCP_SERVER_NAME = 'memory_os';
21
+ const MCP_SERVER_NAME = 'XMemo';
22
+ const LEGACY_MCP_SERVER_NAMES = ['memory_os', 'memory-os'];
22
23
  const CODEX_PROFILE_TARGET = 'AGENTS.md';
23
24
  const CODEX_PROFILE_MARKER_START = '<!-- memory-os:codex-profile:start -->';
24
25
  const CODEX_PROFILE_MARKER_END = '<!-- memory-os:codex-profile:end -->';
@@ -63,6 +64,27 @@ const MCP_CLIENTS = new Map([
63
64
  buildSnippet: antigravityJsonSnippet,
64
65
  writeConfig: mergeAntigravityMcpConfig,
65
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'
66
88
  }]
67
89
  ]);
68
90
 
@@ -73,7 +95,10 @@ const SETUP_CLIENT_ALIASES = new Map([
73
95
  ['copilot-cli', 'copilot-cli'],
74
96
  ['gemini', 'gemini-cli'],
75
97
  ['gemini-cli', 'gemini-cli'],
76
- ['antigravity', 'antigravity']
98
+ ['antigravity', 'antigravity'],
99
+ ['antigravity-ide', 'antigravity-ide'],
100
+ ['antigravity2', 'antigravity2'],
101
+ ['antigravity-cli', 'antigravity-cli']
77
102
  ]);
78
103
 
79
104
  class UsageError extends Error {
@@ -190,7 +215,7 @@ function writeHelp(io) {
190
215
  writeLine(io.stdout, ` ${COMMAND_NAME} token add --from-stdin`);
191
216
  writeLine(io.stdout, ` ${COMMAND_NAME} token set --from-stdin [--allow-plaintext]`);
192
217
  writeLine(io.stdout, ` ${COMMAND_NAME} mcp list`);
193
- writeLine(io.stdout, ` ${COMMAND_NAME} mcp config --client <codex|cursor|copilot-cli|antigravity|generic> [--base-url <url>] [--json]`);
218
+ writeLine(io.stdout, ` ${COMMAND_NAME} mcp config --client <codex|cursor|copilot-cli|gemini-cli|antigravity|generic> [--base-url <url>] [--json]`);
194
219
  writeLine(io.stdout, ` ${COMMAND_NAME} mcp proxy [--port ${DEFAULT_PROXY_PORT}]`);
195
220
  writeLine(io.stdout, ` ${COMMAND_NAME} mcp profile codex [--json]`);
196
221
  writeLine(io.stdout, ` ${COMMAND_NAME} profile install <codex|cursor|gemini|antigravity> [--target <path>] [--dry-run|--json]`);
@@ -779,6 +804,15 @@ async function mcpCommand(args, io) {
779
804
  } else {
780
805
  writeLine(io.stdout, JSON.stringify(template.snippet, null, 2));
781
806
  }
807
+ if (template.optionalEnv?.includes(AGENT_INSTANCE_ENV_VAR)) {
808
+ writeLine(io.stdout, '');
809
+ writeLine(io.stdout, `${AGENT_INSTANCE_ENV_VAR} must be stable per local client install.`);
810
+ if (template.agentInstanceGeneration?.automaticCommand) {
811
+ writeLine(io.stdout, `Use ${template.agentInstanceGeneration.automaticCommand} to generate and persist it, or set it to a unique value such as xmemo-${clientId}-<uuid>.`);
812
+ } else {
813
+ writeLine(io.stdout, `Set it to a unique value such as xmemo-${clientId}-<uuid> and persist it outside git.`);
814
+ }
815
+ }
782
816
  writeLine(io.stdout, 'Review the template before applying it. Token values are not included.');
783
817
  return 0;
784
818
  }
@@ -829,6 +863,7 @@ async function mcpCommand(args, io) {
829
863
  agentId: identity.agentId,
830
864
  agentInstanceId: identity.agentInstanceId,
831
865
  agentInstanceIdPath: identity.path,
866
+ agentInstanceGeneration: agentInstanceGenerationPolicy(target),
832
867
  writesTokenValue: false
833
868
  }, null, 2));
834
869
  return 0;
@@ -857,6 +892,7 @@ async function mcpCommand(args, io) {
857
892
  } else {
858
893
  writeLine(io.stdout, `Set ${TOKEN_ENV_VAR} in your user environment or secret manager. The token value is not included here.`);
859
894
  }
895
+ writeLine(io.stdout, `${AGENT_INSTANCE_ENV_VAR} must be stable per local ${client.label} install; run ${COMMAND_NAME} mcp add ${target} --write to generate it automatically.`);
860
896
  return 0;
861
897
  }
862
898
 
@@ -1389,10 +1425,15 @@ function mcpConfigTemplate(clientId, mcpUrl) {
1389
1425
  agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
1390
1426
  agentInstanceHeader: AGENT_INSTANCE_HEADER
1391
1427
  },
1428
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId),
1392
1429
  writesTokenValue: false
1393
1430
  };
1394
1431
  }
1395
1432
 
1433
+ if (clientId === 'cursor') {
1434
+ return bearerJsonMcpTemplate(clientId, mcpUrl, cursorJsonConfig(mcpUrl));
1435
+ }
1436
+
1396
1437
  if (clientId === 'gemini-cli') {
1397
1438
  return oauthJsonMcpTemplate(clientId, mcpUrl, geminiJsonConfig(mcpUrl));
1398
1439
  }
@@ -1401,14 +1442,25 @@ function mcpConfigTemplate(clientId, mcpUrl) {
1401
1442
  return oauthJsonMcpTemplate(clientId, mcpUrl, antigravityJsonConfig(mcpUrl));
1402
1443
  }
1403
1444
 
1404
- const serverName = clientId === 'cursor' || clientId === 'gemini-cli' || clientId === 'antigravity' ? 'memory_os' : 'memory-os';
1445
+ if (clientId === 'antigravity-ide') {
1446
+ return oauthJsonMcpTemplate(clientId, mcpUrl, antigravityIdeJsonConfig(mcpUrl));
1447
+ }
1448
+
1449
+ if (clientId === 'antigravity2') {
1450
+ return oauthJsonMcpTemplate(clientId, mcpUrl, antigravity2JsonConfig(mcpUrl));
1451
+ }
1452
+
1453
+ if (clientId === 'antigravity-cli') {
1454
+ return oauthJsonMcpTemplate(clientId, mcpUrl, antigravityCliJsonConfig(mcpUrl));
1455
+ }
1456
+
1405
1457
  return {
1406
1458
  client: clientId,
1407
- serverName,
1459
+ serverName: MCP_SERVER_NAME,
1408
1460
  snippetFormat: 'json',
1409
1461
  snippet: {
1410
1462
  mcpServers: {
1411
- [serverName]: {
1463
+ [MCP_SERVER_NAME]: {
1412
1464
  type: 'http',
1413
1465
  url: mcpUrl,
1414
1466
  headers: {
@@ -1427,6 +1479,28 @@ function mcpConfigTemplate(clientId, mcpUrl) {
1427
1479
  agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
1428
1480
  agentInstanceHeader: AGENT_INSTANCE_HEADER
1429
1481
  },
1482
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId),
1483
+ writesTokenValue: false
1484
+ };
1485
+ }
1486
+
1487
+ function bearerJsonMcpTemplate(clientId, mcpUrl, snippet) {
1488
+ return {
1489
+ client: clientId,
1490
+ serverName: MCP_SERVER_NAME,
1491
+ snippetFormat: 'json',
1492
+ snippet,
1493
+ requiresEnv: [TOKEN_ENV_VAR],
1494
+ optionalEnv: [AGENT_INSTANCE_ENV_VAR],
1495
+ authentication: 'env-bearer',
1496
+ agentIdentity: {
1497
+ agentId: clientId,
1498
+ agentIdHeader: AGENT_ID_HEADER,
1499
+ agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
1500
+ agentInstanceHeader: AGENT_INSTANCE_HEADER
1501
+ },
1502
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId),
1503
+ mcpUrl,
1430
1504
  writesTokenValue: false
1431
1505
  };
1432
1506
  }
@@ -1446,20 +1520,20 @@ function oauthJsonMcpTemplate(clientId, mcpUrl, snippet) {
1446
1520
  agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
1447
1521
  agentInstanceHeader: AGENT_INSTANCE_HEADER
1448
1522
  },
1523
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId),
1449
1524
  mcpUrl,
1450
1525
  writesTokenValue: false
1451
1526
  };
1452
1527
  }
1453
1528
 
1454
1529
  function mcpLocalProxyTemplate(clientId, proxyUrl) {
1455
- const serverName = clientId === 'cursor' || clientId === 'gemini-cli' || clientId === 'antigravity' ? 'memory_os' : 'memory-os';
1456
1530
  return {
1457
1531
  client: clientId,
1458
- serverName,
1532
+ serverName: MCP_SERVER_NAME,
1459
1533
  snippetFormat: 'json',
1460
1534
  snippet: {
1461
1535
  mcpServers: {
1462
- [serverName]: {
1536
+ [MCP_SERVER_NAME]: {
1463
1537
  type: 'http',
1464
1538
  url: proxyUrl
1465
1539
  }
@@ -1473,10 +1547,27 @@ function mcpLocalProxyTemplate(clientId, proxyUrl) {
1473
1547
  agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
1474
1548
  agentInstanceHeader: AGENT_INSTANCE_HEADER
1475
1549
  },
1550
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId),
1476
1551
  writesTokenValue: false
1477
1552
  };
1478
1553
  }
1479
1554
 
1555
+ function agentInstanceGenerationPolicy(clientId) {
1556
+ const automaticCommand = MCP_CLIENTS.has(clientId)
1557
+ ? `${COMMAND_NAME} mcp add ${clientId} --write`
1558
+ : clientId === 'copilot-cli'
1559
+ ? `${COMMAND_NAME} setup copilot --write`
1560
+ : null;
1561
+ return {
1562
+ requiredForHeaders: true,
1563
+ stablePerInstall: true,
1564
+ automaticCommand,
1565
+ generatedPattern: `xmemo-${clientId}-<uuid>`,
1566
+ storagePath: `~/.config/xmemo/agent-instances/${clientId}.json`,
1567
+ manualEnvVar: AGENT_INSTANCE_ENV_VAR
1568
+ };
1569
+ }
1570
+
1480
1571
  function sameMajorMinor(left, right) {
1481
1572
  const leftParts = left.split('.');
1482
1573
  const rightParts = right.split('.');
@@ -2079,7 +2170,8 @@ function writeProfileResult(action, result, io) {
2079
2170
 
2080
2171
  async function codexSmokeReport(configPath, env) {
2081
2172
  const configText = await readTextIfExists(configPath);
2082
- const block = tomlServerBlock(configText, MCP_SERVER_NAME);
2173
+ const serverBlock = findTomlServerBlock(configText);
2174
+ const block = serverBlock.block;
2083
2175
  const mcpUrl = block ? tomlStringValue(block, 'url') : null;
2084
2176
  const bearerTokenEnvVar = block ? tomlStringValue(block, 'bearer_token_env_var') : null;
2085
2177
  const tokenValue = env[TOKEN_ENV_VAR] ?? '';
@@ -2096,7 +2188,7 @@ async function codexSmokeReport(configPath, env) {
2096
2188
  name: 'memory_os_server_present',
2097
2189
  ok: Boolean(block),
2098
2190
  required: true,
2099
- detail: block ? `[mcp_servers.${MCP_SERVER_NAME}]` : `missing [mcp_servers.${MCP_SERVER_NAME}]`
2191
+ detail: block ? `[mcp_servers.${serverBlock.name}]` : `missing [mcp_servers.${MCP_SERVER_NAME}]`
2100
2192
  },
2101
2193
  {
2102
2194
  name: 'mcp_url_present',
@@ -2134,7 +2226,7 @@ async function codexSmokeReport(configPath, env) {
2134
2226
  ok: checks.every((check) => !check.required || check.ok),
2135
2227
  client: 'codex',
2136
2228
  configPath,
2137
- serverName: MCP_SERVER_NAME,
2229
+ serverName: serverBlock.name ?? MCP_SERVER_NAME,
2138
2230
  mcpUrl,
2139
2231
  tokenEnvVar: TOKEN_ENV_VAR,
2140
2232
  agentInstanceIdPath: identityPath,
@@ -2142,6 +2234,26 @@ async function codexSmokeReport(configPath, env) {
2142
2234
  };
2143
2235
  }
2144
2236
 
2237
+ function knownMcpServerNames() {
2238
+ return [MCP_SERVER_NAME, ...LEGACY_MCP_SERVER_NAMES];
2239
+ }
2240
+
2241
+ function existingJsonMcpServerName(mcpServers) {
2242
+ return knownMcpServerNames().find((name) => mcpServers[name]);
2243
+ }
2244
+
2245
+ function existingTomlMcpServerName(content) {
2246
+ return knownMcpServerNames().find((name) => content.includes(`[mcp_servers.${name}]`));
2247
+ }
2248
+
2249
+ function findTomlServerBlock(content) {
2250
+ const name = existingTomlMcpServerName(content);
2251
+ return {
2252
+ name: name ?? null,
2253
+ block: name ? tomlServerBlock(content, name) : ''
2254
+ };
2255
+ }
2256
+
2145
2257
  function tomlServerBlock(content, serverName) {
2146
2258
  const header = `[mcp_servers.${serverName}]`;
2147
2259
  const lines = content.split(/\r?\n/);
@@ -2174,8 +2286,9 @@ function cursorJsonSnippet(mcpUrl, identity = envReferenceIdentity('cursor')) {
2174
2286
  async function appendTomlServerConfig(configPath, mcpUrl) {
2175
2287
  const snippet = codexTomlSnippet(mcpUrl);
2176
2288
  const existing = await readTextIfExists(configPath);
2177
- if (existing.includes(`[mcp_servers.${MCP_SERVER_NAME}]`)) {
2178
- throw new UsageError(`MCP config already contains [mcp_servers.${MCP_SERVER_NAME}]. Edit ${configPath} manually to avoid duplicate server definitions.`);
2289
+ const existingName = existingTomlMcpServerName(existing);
2290
+ if (existingName) {
2291
+ throw new UsageError(`MCP config already contains [mcp_servers.${existingName}]. Edit ${configPath} manually to avoid duplicate server definitions.`);
2179
2292
  }
2180
2293
 
2181
2294
  await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
@@ -2196,8 +2309,9 @@ async function mergeJsonMcpConfig(configPath, mcpUrl, identity) {
2196
2309
  parsed.mcpServers = {};
2197
2310
  }
2198
2311
 
2199
- if (parsed.mcpServers[MCP_SERVER_NAME]) {
2200
- throw new UsageError(`MCP config already contains mcpServers.${MCP_SERVER_NAME}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2312
+ const existingName = existingJsonMcpServerName(parsed.mcpServers);
2313
+ if (existingName) {
2314
+ throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2201
2315
  }
2202
2316
 
2203
2317
  parsed.mcpServers[MCP_SERVER_NAME] = cursorJsonServerConfig(mcpUrl, identity);
@@ -2240,8 +2354,9 @@ async function mergeAntigravityMcpConfig(configPath, mcpUrl, identity) {
2240
2354
  parsed.mcpServers = {};
2241
2355
  }
2242
2356
 
2243
- if (parsed.mcpServers[MCP_SERVER_NAME]) {
2244
- throw new UsageError(`MCP config already contains mcpServers.${MCP_SERVER_NAME}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2357
+ const existingName = existingJsonMcpServerName(parsed.mcpServers);
2358
+ if (existingName) {
2359
+ throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2245
2360
  }
2246
2361
 
2247
2362
  parsed.mcpServers[MCP_SERVER_NAME] = antigravityJsonServerConfig(mcpUrl, identity);
@@ -2250,6 +2365,135 @@ async function mergeAntigravityMcpConfig(configPath, mcpUrl, identity) {
2250
2365
  await bestEffortChmod(configPath, 0o600);
2251
2366
  }
2252
2367
 
2368
+ function antigravityIdeJsonServerConfig(mcpUrl) {
2369
+ return {
2370
+ type: 'http',
2371
+ url: mcpUrl
2372
+ };
2373
+ }
2374
+
2375
+ function antigravityIdeJsonConfig(mcpUrl) {
2376
+ return {
2377
+ mcpServers: {
2378
+ [MCP_SERVER_NAME]: antigravityIdeJsonServerConfig(mcpUrl)
2379
+ }
2380
+ };
2381
+ }
2382
+
2383
+ function antigravityIdeJsonSnippet(mcpUrl, identity = envReferenceIdentity('antigravity-ide')) {
2384
+ return `${JSON.stringify(antigravityIdeJsonConfig(mcpUrl), null, 2)}\n`;
2385
+ }
2386
+
2387
+ async function mergeAntigravityIdeMcpConfig(configPath, mcpUrl, identity) {
2388
+ const existing = await readTextIfExists(configPath);
2389
+ const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
2390
+
2391
+ if (!isPlainObject(parsed)) {
2392
+ throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
2393
+ }
2394
+
2395
+ if (!isPlainObject(parsed.mcpServers)) {
2396
+ parsed.mcpServers = {};
2397
+ }
2398
+
2399
+ const existingName = existingJsonMcpServerName(parsed.mcpServers);
2400
+ if (existingName) {
2401
+ throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2402
+ }
2403
+
2404
+ parsed.mcpServers[MCP_SERVER_NAME] = antigravityIdeJsonServerConfig(mcpUrl);
2405
+ await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
2406
+ await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
2407
+ await bestEffortChmod(configPath, 0o600);
2408
+ }
2409
+
2410
+ function antigravity2JsonServerConfig(mcpUrl) {
2411
+ return {
2412
+ type: 'http',
2413
+ url: mcpUrl
2414
+ };
2415
+ }
2416
+
2417
+ function antigravity2JsonConfig(mcpUrl) {
2418
+ return {
2419
+ mcpServers: {
2420
+ [MCP_SERVER_NAME]: antigravity2JsonServerConfig(mcpUrl)
2421
+ }
2422
+ };
2423
+ }
2424
+
2425
+ function antigravity2JsonSnippet(mcpUrl, identity = envReferenceIdentity('antigravity2')) {
2426
+ return `${JSON.stringify(antigravity2JsonConfig(mcpUrl), null, 2)}\n`;
2427
+ }
2428
+
2429
+ async function mergeAntigravity2McpConfig(configPath, mcpUrl, identity) {
2430
+ const existing = await readTextIfExists(configPath);
2431
+ const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
2432
+
2433
+ if (!isPlainObject(parsed)) {
2434
+ throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
2435
+ }
2436
+
2437
+ if (!isPlainObject(parsed.mcpServers)) {
2438
+ parsed.mcpServers = {};
2439
+ }
2440
+
2441
+ const existingName = existingJsonMcpServerName(parsed.mcpServers);
2442
+ if (existingName) {
2443
+ throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2444
+ }
2445
+
2446
+ parsed.mcpServers[MCP_SERVER_NAME] = antigravity2JsonServerConfig(mcpUrl);
2447
+ await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
2448
+ await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
2449
+ await bestEffortChmod(configPath, 0o600);
2450
+ }
2451
+
2452
+ function antigravityCliJsonServerConfig(mcpUrl, identity = envReferenceIdentity('antigravity-cli')) {
2453
+ return {
2454
+ httpUrl: mcpUrl,
2455
+ headers: {
2456
+ [AGENT_ID_HEADER]: identity.agentId,
2457
+ [AGENT_INSTANCE_HEADER]: identity.agentInstanceId
2458
+ }
2459
+ };
2460
+ }
2461
+
2462
+ function antigravityCliJsonConfig(mcpUrl, identity = envReferenceIdentity('antigravity-cli')) {
2463
+ return {
2464
+ mcpServers: {
2465
+ [MCP_SERVER_NAME]: antigravityCliJsonServerConfig(mcpUrl, identity)
2466
+ }
2467
+ };
2468
+ }
2469
+
2470
+ function antigravityCliJsonSnippet(mcpUrl, identity = envReferenceIdentity('antigravity-cli')) {
2471
+ return `${JSON.stringify(antigravityCliJsonConfig(mcpUrl, identity), null, 2)}\n`;
2472
+ }
2473
+
2474
+ async function mergeAntigravityCliMcpConfig(configPath, mcpUrl, identity) {
2475
+ const existing = await readTextIfExists(configPath);
2476
+ const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
2477
+
2478
+ if (!isPlainObject(parsed)) {
2479
+ throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
2480
+ }
2481
+
2482
+ if (!isPlainObject(parsed.mcpServers)) {
2483
+ parsed.mcpServers = {};
2484
+ }
2485
+
2486
+ const existingName = existingJsonMcpServerName(parsed.mcpServers);
2487
+ if (existingName) {
2488
+ throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2489
+ }
2490
+
2491
+ parsed.mcpServers[MCP_SERVER_NAME] = antigravityCliJsonServerConfig(mcpUrl, identity);
2492
+ await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
2493
+ await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
2494
+ await bestEffortChmod(configPath, 0o600);
2495
+ }
2496
+
2253
2497
 
2254
2498
  async function mergeGeminiMcpConfig(configPath, mcpUrl, identity) {
2255
2499
  const existing = await readTextIfExists(configPath);
@@ -2263,8 +2507,9 @@ async function mergeGeminiMcpConfig(configPath, mcpUrl, identity) {
2263
2507
  parsed.mcpServers = {};
2264
2508
  }
2265
2509
 
2266
- if (parsed.mcpServers[MCP_SERVER_NAME]) {
2267
- throw new UsageError(`MCP config already contains mcpServers.${MCP_SERVER_NAME}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2510
+ const existingName = existingJsonMcpServerName(parsed.mcpServers);
2511
+ if (existingName) {
2512
+ throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2268
2513
  }
2269
2514
 
2270
2515
  parsed.mcpServers[MCP_SERVER_NAME] = geminiJsonServerConfig(mcpUrl, identity);
@@ -2285,7 +2530,12 @@ async function mergeCopilotMcpConfig(configPath, proxyUrl) {
2285
2530
  parsed.mcpServers = {};
2286
2531
  }
2287
2532
 
2288
- parsed.mcpServers['memory-os'] = copilotLocalProxyServerConfig(proxyUrl);
2533
+ const existingName = existingJsonMcpServerName(parsed.mcpServers);
2534
+ if (existingName) {
2535
+ throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
2536
+ }
2537
+
2538
+ parsed.mcpServers[MCP_SERVER_NAME] = copilotLocalProxyServerConfig(proxyUrl);
2289
2539
  await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
2290
2540
  await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
2291
2541
  await bestEffortChmod(configPath, 0o600);
@@ -2400,11 +2650,11 @@ function supportedMcpClientIds() {
2400
2650
  }
2401
2651
 
2402
2652
  function supportedSetupClientIds() {
2403
- return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity'];
2653
+ return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli'];
2404
2654
  }
2405
2655
 
2406
2656
  function usesClientOAuth(clientId) {
2407
- return clientId === 'gemini-cli' || clientId === 'antigravity';
2657
+ return clientId === 'gemini-cli' || clientId === 'antigravity' || clientId === 'antigravity-ide' || clientId === 'antigravity2' || clientId === 'antigravity-cli';
2408
2658
  }
2409
2659
 
2410
2660
  function credentialsPath(env) {
@@ -2452,6 +2702,21 @@ function defaultAntigravityConfigPath(env) {
2452
2702
  return path.join(home, '.gemini', 'antigravity', 'mcp_config.json');
2453
2703
  }
2454
2704
 
2705
+ function defaultAntigravityIdeConfigPath(env) {
2706
+ const home = env.USERPROFILE || env.HOME || os.homedir();
2707
+ return path.join(home, '.antigravity-ide', 'mcp.json');
2708
+ }
2709
+
2710
+ function defaultAntigravity2ConfigPath(env) {
2711
+ const home = env.USERPROFILE || env.HOME || os.homedir();
2712
+ return path.join(home, '.antigravity2', 'mcp.json');
2713
+ }
2714
+
2715
+ function defaultAntigravityCliConfigPath(env) {
2716
+ const home = env.USERPROFILE || env.HOME || os.homedir();
2717
+ return path.join(home, '.antigravity', 'settings.json');
2718
+ }
2719
+
2455
2720
  function defaultCopilotConfigPath(env) {
2456
2721
  const home = env.USERPROFILE || env.HOME || os.homedir();
2457
2722
  return path.join(env.COPILOT_HOME ?? path.join(home, '.copilot'), 'mcp-config.json');