@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.
- package/README.md +21 -11
- package/package.json +1 -1
- package/src/cli.js +289 -24
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# XMemo CLI
|
|
2
2
|
|
|
3
|
+
[](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 `
|
|
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
|
|
289
|
-
|
|
290
|
-
|
|
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 `
|
|
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
|
|
326
|
-
config manually if you need to rotate the endpoint.
|
|
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
|
|
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
|
|
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
package/src/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const PACKAGE_NAME = '@xmemo/client';
|
|
|
10
10
|
const FALLBACK_PACKAGE_NAME = '@yonro/xmemo-client';
|
|
11
11
|
const COMMAND_NAME = 'xmemo';
|
|
12
12
|
const LEGACY_COMMAND_NAME = 'memory-os';
|
|
13
|
-
const CLI_VERSION = '0.4.
|
|
13
|
+
const CLI_VERSION = '0.4.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 = '
|
|
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
|
-
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
|
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.${
|
|
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
|
-
|
|
2178
|
-
|
|
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
|
-
|
|
2200
|
-
|
|
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
|
-
|
|
2244
|
-
|
|
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
|
-
|
|
2267
|
-
|
|
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
|
-
|
|
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');
|