metheus-governance-mcp-cli 0.2.24 → 0.2.26
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 +17 -6
- package/cli.mjs +320 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@ Compatibility note: legacy command alias `metheus-governance-mcp` is still suppo
|
|
|
7
7
|
- `metheus-governance-mcp-cli` (no args): bootstrap mode
|
|
8
8
|
- checks auth token
|
|
9
9
|
- if token is missing/expired, starts `auth login`
|
|
10
|
-
- checks Codex/Claude MCP registration
|
|
10
|
+
- checks Codex/Claude/Gemini/Antigravity/Cursor MCP registration
|
|
11
11
|
- registers only missing clients
|
|
12
|
-
- `setup`: register `metheus-governance-mcp` into Codex/Claude (if installed)
|
|
12
|
+
- `setup`: register `metheus-governance-mcp` into Codex/Claude/Gemini/Antigravity/Cursor (if installed)
|
|
13
13
|
- `doctor`: run end-to-end health checks (auth/registration/gateway/project/ctxpack/tools)
|
|
14
14
|
- `proxy`: stdio MCP bridge to Metheus HTTPS gateway
|
|
15
15
|
- `auth`: save/check/clear local Metheus token used by proxy
|
|
@@ -40,12 +40,15 @@ metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctx
|
|
|
40
40
|
`setup` defaults to `--workspace-dir auto` (dynamic workspace detection).
|
|
41
41
|
Use an explicit path only when you intentionally want a fixed workspace.
|
|
42
42
|
|
|
43
|
-
Recommended for Codex/Claude multi-workspace sessions:
|
|
43
|
+
Recommended for Codex/Claude/Gemini/Antigravity/Cursor multi-workspace sessions:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com --workspace-dir auto
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
Gemini CLI note:
|
|
50
|
+
- `gemini mcp` commands require Gemini auth to be configured first (`GEMINI_API_KEY` or `~/.gemini/settings.json` auth).
|
|
51
|
+
|
|
49
52
|
When a client does not send workspace metadata (for example some Codex sessions),
|
|
50
53
|
set a stable fallback root once:
|
|
51
54
|
|
|
@@ -53,7 +56,9 @@ set a stable fallback root once:
|
|
|
53
56
|
metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com --workspace-dir auto --workspace-fallback-dir C:\code_test
|
|
54
57
|
```
|
|
55
58
|
|
|
56
|
-
This
|
|
59
|
+
This sets fallback workspace context for clients that do not pass workspace metadata:
|
|
60
|
+
- Codex/Antigravity/Cursor: `METHEUS_WORKSPACE_DIR` env
|
|
61
|
+
- Gemini: pinned `--workspace-dir <fallback>` in registered MCP command
|
|
57
62
|
|
|
58
63
|
Guardrail note:
|
|
59
64
|
- By default, CLI blocks reading/writing ctxpack sync metadata when workspace root resolves to the home directory.
|
|
@@ -76,7 +81,7 @@ metheus-governance-mcp-cli doctor --project-id <project_uuid> --base-url https:/
|
|
|
76
81
|
|
|
77
82
|
Checks:
|
|
78
83
|
- auth token status (+ auto refresh attempt)
|
|
79
|
-
- codex/claude registration state
|
|
84
|
+
- codex/claude/gemini/antigravity/cursor registration state
|
|
80
85
|
- gateway `tools/list` reachability
|
|
81
86
|
- `project.summary` access
|
|
82
87
|
- ctxpack auto sync status
|
|
@@ -84,7 +89,13 @@ Checks:
|
|
|
84
89
|
|
|
85
90
|
## Use in MCP
|
|
86
91
|
|
|
87
|
-
`setup` auto-registers this server for `codex` and `
|
|
92
|
+
`setup` auto-registers this server for `codex`, `claude`, `gemini`, `antigravity`, and `cursor` when those CLIs are available.
|
|
93
|
+
|
|
94
|
+
Antigravity note:
|
|
95
|
+
- this CLI manages MCP registration via Antigravity local config file (`mcp.json`) because Antigravity does not provide full `mcp list/get/remove` subcommands.
|
|
96
|
+
|
|
97
|
+
Cursor note:
|
|
98
|
+
- this CLI manages MCP registration via Cursor global MCP config (`~/.cursor/mcp.json`).
|
|
88
99
|
|
|
89
100
|
Local bootstrap tools exposed by proxy:
|
|
90
101
|
|
package/cli.mjs
CHANGED
|
@@ -27,6 +27,7 @@ const SELF_UPDATE_ENV_KEY = "METHEUS_CLI_AUTO_UPDATE";
|
|
|
27
27
|
const SELF_UPDATE_GUARD_ENV_KEY = "METHEUS_CLI_SELF_UPDATE_ATTEMPTED";
|
|
28
28
|
const ALLOW_HOME_WORKSPACE_ENV_KEY = "METHEUS_ALLOW_HOME_WORKSPACE";
|
|
29
29
|
const AUTO_CTXPACK_SYNC_INTERVAL_MS = 60 * 1000;
|
|
30
|
+
const MCP_CLIENTS = ["codex", "claude", "gemini", "antigravity", "cursor"];
|
|
30
31
|
const autoCtxpackSyncTracker = new Map();
|
|
31
32
|
|
|
32
33
|
function printUsage() {
|
|
@@ -85,6 +86,113 @@ function resolveHomeFilePath(relativePath) {
|
|
|
85
86
|
return path.join(home, relativePath);
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
function antigravityMcpConfigFilePath() {
|
|
90
|
+
const home = String(process.env.USERPROFILE || process.env.HOME || "").trim();
|
|
91
|
+
if (process.platform === "win32") {
|
|
92
|
+
const appData = String(process.env.APPDATA || "").trim();
|
|
93
|
+
if (appData) {
|
|
94
|
+
return path.join(appData, "Antigravity", "User", "mcp.json");
|
|
95
|
+
}
|
|
96
|
+
if (home) {
|
|
97
|
+
return path.join(home, "AppData", "Roaming", "Antigravity", "User", "mcp.json");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (process.platform === "darwin" && home) {
|
|
101
|
+
return path.join(home, "Library", "Application Support", "Antigravity", "User", "mcp.json");
|
|
102
|
+
}
|
|
103
|
+
const xdgConfig = String(process.env.XDG_CONFIG_HOME || "").trim();
|
|
104
|
+
if (xdgConfig) {
|
|
105
|
+
return path.join(xdgConfig, "Antigravity", "User", "mcp.json");
|
|
106
|
+
}
|
|
107
|
+
if (home) {
|
|
108
|
+
return path.join(home, ".config", "Antigravity", "User", "mcp.json");
|
|
109
|
+
}
|
|
110
|
+
return path.resolve(process.cwd(), ".antigravity", "mcp.json");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function loadAntigravityMcpConfig() {
|
|
114
|
+
const filePath = antigravityMcpConfigFilePath();
|
|
115
|
+
try {
|
|
116
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
117
|
+
const parsed = tryJsonParse(raw);
|
|
118
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
119
|
+
return { filePath, config: parsed };
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// no-op
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
filePath,
|
|
126
|
+
config: {
|
|
127
|
+
servers: {},
|
|
128
|
+
inputs: [],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function saveAntigravityMcpConfig(filePath, config) {
|
|
134
|
+
try {
|
|
135
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
136
|
+
fs.writeFileSync(filePath, `${JSON.stringify(config, null, "\t")}\n`, "utf8");
|
|
137
|
+
return true;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getAntigravityServerEntry(serverName) {
|
|
144
|
+
const { config } = loadAntigravityMcpConfig();
|
|
145
|
+
const servers = safeObject(config?.servers);
|
|
146
|
+
const entry = safeObject(servers[serverName]);
|
|
147
|
+
if (!entry.command && !entry.url) return null;
|
|
148
|
+
return entry;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function cursorMcpConfigFilePath() {
|
|
152
|
+
const home = String(process.env.USERPROFILE || process.env.HOME || "").trim();
|
|
153
|
+
if (home) {
|
|
154
|
+
return path.join(home, ".cursor", "mcp.json");
|
|
155
|
+
}
|
|
156
|
+
return path.resolve(process.cwd(), ".cursor", "mcp.json");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function loadCursorMcpConfig() {
|
|
160
|
+
const filePath = cursorMcpConfigFilePath();
|
|
161
|
+
try {
|
|
162
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
163
|
+
const parsed = tryJsonParse(raw);
|
|
164
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
165
|
+
return { filePath, config: parsed };
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
// no-op
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
filePath,
|
|
172
|
+
config: {
|
|
173
|
+
mcpServers: {},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function saveCursorMcpConfig(filePath, config) {
|
|
179
|
+
try {
|
|
180
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
181
|
+
fs.writeFileSync(filePath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
182
|
+
return true;
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getCursorServerEntry(serverName) {
|
|
189
|
+
const { config } = loadCursorMcpConfig();
|
|
190
|
+
const servers = safeObject(config?.mcpServers);
|
|
191
|
+
const entry = safeObject(servers[serverName]);
|
|
192
|
+
if (!entry.command && !entry.url) return null;
|
|
193
|
+
return entry;
|
|
194
|
+
}
|
|
195
|
+
|
|
88
196
|
function updateStateFilePath() {
|
|
89
197
|
return resolveHomeFilePath(SELF_UPDATE_STATE_RELATIVE_PATH);
|
|
90
198
|
}
|
|
@@ -1392,7 +1500,7 @@ async function runAuthLoginManual(flags) {
|
|
|
1392
1500
|
if (exp) {
|
|
1393
1501
|
process.stdout.write(`Token expires: ${exp}\n`);
|
|
1394
1502
|
}
|
|
1395
|
-
process.stdout.write("Restart Codex/Claude session to apply new token to MCP proxy.\n");
|
|
1503
|
+
process.stdout.write("Restart Codex/Claude/Gemini/Antigravity/Cursor session to apply new token to MCP proxy.\n");
|
|
1396
1504
|
}
|
|
1397
1505
|
|
|
1398
1506
|
async function runAuthLoginCallback(flags, baseURL, oidc) {
|
|
@@ -1474,7 +1582,7 @@ async function runAuthLoginCallback(flags, baseURL, oidc) {
|
|
|
1474
1582
|
process.stdout.write("Refresh token saved.\n");
|
|
1475
1583
|
}
|
|
1476
1584
|
process.stdout.write("Auth login complete (callback flow).\n");
|
|
1477
|
-
process.stdout.write("Restart Codex/Claude session to apply new token to MCP proxy.\n");
|
|
1585
|
+
process.stdout.write("Restart Codex/Claude/Gemini/Antigravity/Cursor session to apply new token to MCP proxy.\n");
|
|
1478
1586
|
}
|
|
1479
1587
|
|
|
1480
1588
|
async function runAuthLoginDevice(flags, baseURL, oidc) {
|
|
@@ -1541,7 +1649,7 @@ async function runAuthLoginDevice(flags, baseURL, oidc) {
|
|
|
1541
1649
|
process.stdout.write("Refresh token saved.\n");
|
|
1542
1650
|
}
|
|
1543
1651
|
process.stdout.write("Auth login complete (device flow).\n");
|
|
1544
|
-
process.stdout.write("Restart Codex/Claude session to apply new token to MCP proxy.\n");
|
|
1652
|
+
process.stdout.write("Restart Codex/Claude/Gemini/Antigravity/Cursor session to apply new token to MCP proxy.\n");
|
|
1545
1653
|
}
|
|
1546
1654
|
|
|
1547
1655
|
async function runAuthLogin(flags) {
|
|
@@ -1825,7 +1933,7 @@ async function runDoctor(flags) {
|
|
|
1825
1933
|
);
|
|
1826
1934
|
}
|
|
1827
1935
|
|
|
1828
|
-
for (const cliBin of
|
|
1936
|
+
for (const cliBin of MCP_CLIENTS) {
|
|
1829
1937
|
if (!commandExists(cliBin)) {
|
|
1830
1938
|
addDoctorCheck(rows, "warn", `${cliBin} CLI`, "not installed; registration check skipped");
|
|
1831
1939
|
continue;
|
|
@@ -3288,13 +3396,32 @@ function ensureArray(value) {
|
|
|
3288
3396
|
return Array.isArray(value) ? value : [];
|
|
3289
3397
|
}
|
|
3290
3398
|
|
|
3399
|
+
function injectWorkspaceDirIntoToolSchemas(tools) {
|
|
3400
|
+
const workspaceDirProp = {
|
|
3401
|
+
type: "string",
|
|
3402
|
+
description: "Current working directory / project folder path. Include this so ctxpack files sync to the correct workspace.",
|
|
3403
|
+
};
|
|
3404
|
+
for (const tool of tools) {
|
|
3405
|
+
if (!tool || typeof tool !== "object") continue;
|
|
3406
|
+
if (!tool.inputSchema || typeof tool.inputSchema !== "object") continue;
|
|
3407
|
+
const schema = tool.inputSchema;
|
|
3408
|
+
if (!schema.properties || typeof schema.properties !== "object") {
|
|
3409
|
+
schema.properties = {};
|
|
3410
|
+
}
|
|
3411
|
+
if (!schema.properties.workspace_dir) {
|
|
3412
|
+
schema.properties.workspace_dir = workspaceDirProp;
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
return tools;
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3291
3418
|
function appendLocalToolToToolsList(responseObj) {
|
|
3292
3419
|
const result = safeObject(responseObj.result);
|
|
3293
3420
|
const tools = ensureArray(result.tools);
|
|
3294
3421
|
const localSpecs = buildLocalToolSpecs();
|
|
3295
3422
|
const localNames = new Set(localSpecs.map((spec) => spec.name));
|
|
3296
3423
|
const filtered = tools.filter((tool) => !localNames.has(String(tool?.name || "").trim()));
|
|
3297
|
-
result.tools = [...localSpecs, ...filtered];
|
|
3424
|
+
result.tools = injectWorkspaceDirIntoToolSchemas([...localSpecs, ...filtered]);
|
|
3298
3425
|
responseObj.result = result;
|
|
3299
3426
|
return responseObj;
|
|
3300
3427
|
}
|
|
@@ -3307,6 +3434,7 @@ function appendProjectHintToInitialize(responseObj, args) {
|
|
|
3307
3434
|
"- `project.describe` and `project.get` are aliases of `project.summary`.",
|
|
3308
3435
|
"- After project summary, use workitem/evidence/decision tools as follow-up.",
|
|
3309
3436
|
"- Before any ctxpack merge, call `ctxpack.merge.brief` first, share recommendation to owner, get explicit owner confirmation, then call `ctxpack.merge.execute`.",
|
|
3437
|
+
"- IMPORTANT: When calling ANY tool, always include your current working directory / project folder path as the `workspace_dir` argument. This enables ctxpack files to be synced to the correct workspace location.",
|
|
3310
3438
|
];
|
|
3311
3439
|
if (args.projectID) {
|
|
3312
3440
|
hintLines.splice(1, 0, `- Default project_id is ${args.projectID}.`);
|
|
@@ -3563,6 +3691,19 @@ function appendCtxpackEnsureSyncHints(responseObj, args, toolArgs, requestObj, w
|
|
|
3563
3691
|
return responseObj;
|
|
3564
3692
|
}
|
|
3565
3693
|
|
|
3694
|
+
function stripLocalOnlyToolArgs(requestObj) {
|
|
3695
|
+
if (!isJsonRpcMethod(requestObj, "tools/call")) return requestObj;
|
|
3696
|
+
const params = safeObject(requestObj.params);
|
|
3697
|
+
const rawArgs = safeObject(params.arguments ?? params.args);
|
|
3698
|
+
if (!rawArgs.workspace_dir && !rawArgs.workspaceDir) return requestObj;
|
|
3699
|
+
const nextArgs = { ...rawArgs };
|
|
3700
|
+
delete nextArgs.workspace_dir;
|
|
3701
|
+
delete nextArgs.workspaceDir;
|
|
3702
|
+
const nextParams = { ...params };
|
|
3703
|
+
nextParams.arguments = nextArgs;
|
|
3704
|
+
return { ...requestObj, params: nextParams };
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3566
3707
|
function injectCtxpackPushDefaults(requestObj, toolName, workspaceDir) {
|
|
3567
3708
|
if (!isJsonRpcMethod(requestObj, "tools/call")) return requestObj;
|
|
3568
3709
|
const normalizedTool = String(toolName || "").trim().toLowerCase();
|
|
@@ -4031,13 +4172,15 @@ async function runProxy(flags) {
|
|
|
4031
4172
|
|
|
4032
4173
|
try {
|
|
4033
4174
|
const requestWithDefaults = injectCtxpackPushDefaults(requestObj, toolName, requestWorkspaceDir);
|
|
4034
|
-
const outboundRequestObj =
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4175
|
+
const outboundRequestObj = stripLocalOnlyToolArgs(
|
|
4176
|
+
await injectCtxpackPreflightToken(
|
|
4177
|
+
requestWithDefaults,
|
|
4178
|
+
toolName,
|
|
4179
|
+
toolArgs,
|
|
4180
|
+
args,
|
|
4181
|
+
token,
|
|
4182
|
+
requestWorkspaceDir,
|
|
4183
|
+
),
|
|
4041
4184
|
);
|
|
4042
4185
|
const responseText = await postJSON(gatewayURL, args.timeoutSeconds, token, outboundRequestObj);
|
|
4043
4186
|
if (responseText) {
|
|
@@ -4105,7 +4248,7 @@ function runCLICommand(cliBin, args, options = {}) {
|
|
|
4105
4248
|
return direct;
|
|
4106
4249
|
}
|
|
4107
4250
|
// On Windows, npm global CLIs are often .cmd wrappers.
|
|
4108
|
-
// Fallback through cmd.exe so tools like "claude" are discoverable.
|
|
4251
|
+
// Fallback through cmd.exe so tools like "claude"/"gemini"/"antigravity"/"cursor" are discoverable.
|
|
4109
4252
|
return spawnSync("cmd.exe", ["/d", "/s", "/c", cliBin, ...args], execOptions);
|
|
4110
4253
|
}
|
|
4111
4254
|
|
|
@@ -4114,21 +4257,112 @@ function commandExists(bin) {
|
|
|
4114
4257
|
return check.status === 0;
|
|
4115
4258
|
}
|
|
4116
4259
|
|
|
4260
|
+
function escapeRegExp(value) {
|
|
4261
|
+
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
function hasServerNameInListJSON(value, serverName) {
|
|
4265
|
+
if (Array.isArray(value)) {
|
|
4266
|
+
return value.some((entry) => hasServerNameInListJSON(entry, serverName));
|
|
4267
|
+
}
|
|
4268
|
+
if (!value || typeof value !== "object") return false;
|
|
4269
|
+
if (String(value.name || "").trim() === serverName) return true;
|
|
4270
|
+
return Object.values(value).some((entry) => hasServerNameInListJSON(entry, serverName));
|
|
4271
|
+
}
|
|
4272
|
+
|
|
4117
4273
|
function tryRegister(cliBin, serverName, proxyArgs, options = {}) {
|
|
4118
4274
|
const selfPath = fileURLToPath(import.meta.url);
|
|
4119
|
-
const baseAddArgs =
|
|
4120
|
-
cliBin === "claude"
|
|
4121
|
-
? ["mcp", "add", "--scope", "user", serverName]
|
|
4122
|
-
: ["mcp", "add", serverName];
|
|
4123
4275
|
const workspaceEnv = String(options.workspaceDir || "").trim();
|
|
4124
|
-
|
|
4276
|
+
if (cliBin === "cursor") {
|
|
4277
|
+
const { filePath, config } = loadCursorMcpConfig();
|
|
4278
|
+
const nextConfig = safeObject(config);
|
|
4279
|
+
const nextServers = safeObject(nextConfig.mcpServers);
|
|
4280
|
+
const existing = safeObject(nextServers[serverName]);
|
|
4281
|
+
const args = [selfPath, "proxy", ...proxyArgs];
|
|
4282
|
+
const entry = {
|
|
4283
|
+
...existing,
|
|
4284
|
+
command: process.execPath,
|
|
4285
|
+
args,
|
|
4286
|
+
};
|
|
4287
|
+
const existingEnv = safeObject(existing.env);
|
|
4288
|
+
if (workspaceEnv) {
|
|
4289
|
+
entry.env = {
|
|
4290
|
+
...existingEnv,
|
|
4291
|
+
METHEUS_WORKSPACE_DIR: workspaceEnv,
|
|
4292
|
+
};
|
|
4293
|
+
} else if (existingEnv.METHEUS_WORKSPACE_DIR) {
|
|
4294
|
+
const nextEnv = { ...existingEnv };
|
|
4295
|
+
delete nextEnv.METHEUS_WORKSPACE_DIR;
|
|
4296
|
+
if (Object.keys(nextEnv).length > 0) {
|
|
4297
|
+
entry.env = nextEnv;
|
|
4298
|
+
} else {
|
|
4299
|
+
delete entry.env;
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
nextServers[serverName] = entry;
|
|
4303
|
+
nextConfig.mcpServers = nextServers;
|
|
4304
|
+
return saveCursorMcpConfig(filePath, nextConfig);
|
|
4305
|
+
}
|
|
4306
|
+
if (cliBin === "antigravity") {
|
|
4307
|
+
const { filePath, config } = loadAntigravityMcpConfig();
|
|
4308
|
+
const nextConfig = safeObject(config);
|
|
4309
|
+
const nextServers = safeObject(nextConfig.servers);
|
|
4310
|
+
const existing = safeObject(nextServers[serverName]);
|
|
4311
|
+
const args = [selfPath, "proxy", ...proxyArgs];
|
|
4312
|
+
const entry = {
|
|
4313
|
+
...existing,
|
|
4314
|
+
command: process.execPath,
|
|
4315
|
+
args,
|
|
4316
|
+
};
|
|
4317
|
+
const existingEnv = safeObject(existing.env);
|
|
4318
|
+
if (workspaceEnv) {
|
|
4319
|
+
entry.env = {
|
|
4320
|
+
...existingEnv,
|
|
4321
|
+
METHEUS_WORKSPACE_DIR: workspaceEnv,
|
|
4322
|
+
};
|
|
4323
|
+
} else if (existingEnv.METHEUS_WORKSPACE_DIR) {
|
|
4324
|
+
const nextEnv = { ...existingEnv };
|
|
4325
|
+
delete nextEnv.METHEUS_WORKSPACE_DIR;
|
|
4326
|
+
if (Object.keys(nextEnv).length > 0) {
|
|
4327
|
+
entry.env = nextEnv;
|
|
4328
|
+
} else {
|
|
4329
|
+
delete entry.env;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
nextServers[serverName] = entry;
|
|
4333
|
+
nextConfig.servers = nextServers;
|
|
4334
|
+
if (!Array.isArray(nextConfig.inputs)) {
|
|
4335
|
+
nextConfig.inputs = [];
|
|
4336
|
+
}
|
|
4337
|
+
return saveAntigravityMcpConfig(filePath, nextConfig);
|
|
4338
|
+
}
|
|
4339
|
+
if (cliBin === "gemini") {
|
|
4340
|
+
// Gemini CLI (0.31.x) currently supports:
|
|
4341
|
+
// gemini mcp add <name> <commandOrUrl> [args...]
|
|
4342
|
+
// It does not expose --env for MCP registration, so pin workspace-dir directly.
|
|
4343
|
+
const geminiProxyArgs = workspaceEnv
|
|
4344
|
+
? withWorkspaceDirArg(proxyArgs, workspaceEnv)
|
|
4345
|
+
: [...proxyArgs];
|
|
4346
|
+
const run = runCLICommand(
|
|
4347
|
+
cliBin,
|
|
4348
|
+
["mcp", "add", serverName, process.execPath, selfPath, "proxy", ...geminiProxyArgs],
|
|
4349
|
+
{ stdio: "inherit" },
|
|
4350
|
+
);
|
|
4351
|
+
return run.status === 0;
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
const baseAddArgs = (() => {
|
|
4355
|
+
if (cliBin === "claude") return ["mcp", "add", "--scope", "user", serverName];
|
|
4356
|
+
return ["mcp", "add", serverName];
|
|
4357
|
+
})();
|
|
4358
|
+
const envArgs =
|
|
4125
4359
|
cliBin === "codex" && workspaceEnv
|
|
4126
4360
|
? ["--env", `METHEUS_WORKSPACE_DIR=${workspaceEnv}`]
|
|
4127
4361
|
: [];
|
|
4128
|
-
if (
|
|
4362
|
+
if (envArgs.length > 0) {
|
|
4129
4363
|
const envAttempts = [];
|
|
4130
|
-
envAttempts.push([...baseAddArgs, ...
|
|
4131
|
-
envAttempts.push([...baseAddArgs, ...
|
|
4364
|
+
envAttempts.push([...baseAddArgs, ...envArgs, "--", process.execPath, selfPath, "proxy", ...proxyArgs]);
|
|
4365
|
+
envAttempts.push([...baseAddArgs, ...envArgs, process.execPath, selfPath, "proxy", ...proxyArgs]);
|
|
4132
4366
|
for (const args of envAttempts) {
|
|
4133
4367
|
const run = runCLICommand(cliBin, args, { stdio: "inherit" });
|
|
4134
4368
|
if (run.status === 0) return true;
|
|
@@ -4147,6 +4381,31 @@ function tryRegister(cliBin, serverName, proxyArgs, options = {}) {
|
|
|
4147
4381
|
}
|
|
4148
4382
|
|
|
4149
4383
|
function runRemove(cliBin, serverName) {
|
|
4384
|
+
if (cliBin === "cursor") {
|
|
4385
|
+
const { filePath, config } = loadCursorMcpConfig();
|
|
4386
|
+
const nextConfig = safeObject(config);
|
|
4387
|
+
const nextServers = safeObject(nextConfig.mcpServers);
|
|
4388
|
+
if (Object.prototype.hasOwnProperty.call(nextServers, serverName)) {
|
|
4389
|
+
delete nextServers[serverName];
|
|
4390
|
+
nextConfig.mcpServers = nextServers;
|
|
4391
|
+
saveCursorMcpConfig(filePath, nextConfig);
|
|
4392
|
+
}
|
|
4393
|
+
return;
|
|
4394
|
+
}
|
|
4395
|
+
if (cliBin === "antigravity") {
|
|
4396
|
+
const { filePath, config } = loadAntigravityMcpConfig();
|
|
4397
|
+
const nextConfig = safeObject(config);
|
|
4398
|
+
const nextServers = safeObject(nextConfig.servers);
|
|
4399
|
+
if (Object.prototype.hasOwnProperty.call(nextServers, serverName)) {
|
|
4400
|
+
delete nextServers[serverName];
|
|
4401
|
+
nextConfig.servers = nextServers;
|
|
4402
|
+
if (!Array.isArray(nextConfig.inputs)) {
|
|
4403
|
+
nextConfig.inputs = [];
|
|
4404
|
+
}
|
|
4405
|
+
saveAntigravityMcpConfig(filePath, nextConfig);
|
|
4406
|
+
}
|
|
4407
|
+
return;
|
|
4408
|
+
}
|
|
4150
4409
|
if (cliBin === "claude") {
|
|
4151
4410
|
runCLICommand(cliBin, ["mcp", "remove", serverName, "-s", "user"], {
|
|
4152
4411
|
stdio: "ignore",
|
|
@@ -4156,10 +4415,27 @@ function runRemove(cliBin, serverName) {
|
|
|
4156
4415
|
});
|
|
4157
4416
|
return;
|
|
4158
4417
|
}
|
|
4418
|
+
if (cliBin === "gemini") {
|
|
4419
|
+
runCLICommand(cliBin, ["mcp", "remove", serverName], { stdio: "ignore" });
|
|
4420
|
+
return;
|
|
4421
|
+
}
|
|
4159
4422
|
runCLICommand(cliBin, ["mcp", "remove", serverName], { stdio: "ignore" });
|
|
4160
4423
|
}
|
|
4161
4424
|
|
|
4162
4425
|
function isRegistered(cliBin, serverName) {
|
|
4426
|
+
if (cliBin === "cursor") {
|
|
4427
|
+
return Boolean(getCursorServerEntry(serverName));
|
|
4428
|
+
}
|
|
4429
|
+
if (cliBin === "antigravity") {
|
|
4430
|
+
return Boolean(getAntigravityServerEntry(serverName));
|
|
4431
|
+
}
|
|
4432
|
+
if (cliBin === "gemini") {
|
|
4433
|
+
const listRun = runCLICommand(cliBin, ["mcp", "list"], { stdio: "pipe" });
|
|
4434
|
+
if (listRun.status !== 0) return false;
|
|
4435
|
+
const text = `${String(listRun.stdout || "")}\n${String(listRun.stderr || "")}`;
|
|
4436
|
+
const pattern = new RegExp(`(^|[\\s"'])${escapeRegExp(serverName)}([\\s"':]|$)`, "m");
|
|
4437
|
+
return pattern.test(text);
|
|
4438
|
+
}
|
|
4163
4439
|
const args =
|
|
4164
4440
|
cliBin === "claude"
|
|
4165
4441
|
? ["mcp", "get", serverName]
|
|
@@ -4169,6 +4445,26 @@ function isRegistered(cliBin, serverName) {
|
|
|
4169
4445
|
}
|
|
4170
4446
|
|
|
4171
4447
|
function getRegisteredTransport(cliBin, serverName) {
|
|
4448
|
+
if (cliBin === "cursor") {
|
|
4449
|
+
const entry = getCursorServerEntry(serverName);
|
|
4450
|
+
if (!entry) return null;
|
|
4451
|
+
return {
|
|
4452
|
+
type: "stdio",
|
|
4453
|
+
command: String(entry.command || "").trim(),
|
|
4454
|
+
args: Array.isArray(entry.args) ? entry.args : [],
|
|
4455
|
+
env: safeObject(entry.env),
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
if (cliBin === "antigravity") {
|
|
4459
|
+
const entry = getAntigravityServerEntry(serverName);
|
|
4460
|
+
if (!entry) return null;
|
|
4461
|
+
return {
|
|
4462
|
+
type: "stdio",
|
|
4463
|
+
command: String(entry.command || "").trim(),
|
|
4464
|
+
args: Array.isArray(entry.args) ? entry.args : [],
|
|
4465
|
+
env: safeObject(entry.env),
|
|
4466
|
+
};
|
|
4467
|
+
}
|
|
4172
4468
|
if (cliBin !== "codex") return null;
|
|
4173
4469
|
const run = runCLICommand(cliBin, ["mcp", "get", serverName, "--json"], { stdio: "pipe" });
|
|
4174
4470
|
if (run.status !== 0) return null;
|
|
@@ -4254,7 +4550,7 @@ function resolveSetupContext(flags) {
|
|
|
4254
4550
|
function runSetupInternal(flags, options = {}) {
|
|
4255
4551
|
const ensureOnly = Boolean(options.ensureOnly);
|
|
4256
4552
|
const context = resolveSetupContext(flags);
|
|
4257
|
-
const clients = [
|
|
4553
|
+
const clients = [...MCP_CLIENTS];
|
|
4258
4554
|
const results = [];
|
|
4259
4555
|
|
|
4260
4556
|
for (const cliBin of clients) {
|
|
@@ -4270,7 +4566,7 @@ function runSetupInternal(flags, options = {}) {
|
|
|
4270
4566
|
? context.workspaceDir
|
|
4271
4567
|
: context.workspaceFallbackDir;
|
|
4272
4568
|
|
|
4273
|
-
if (cliBin === "codex" && !context.hasWorkspaceDirFlag && !context.hasWorkspaceFallbackDirFlag) {
|
|
4569
|
+
if ((cliBin === "codex" || cliBin === "cursor" || cliBin === "antigravity") && !context.hasWorkspaceDirFlag && !context.hasWorkspaceFallbackDirFlag) {
|
|
4274
4570
|
const transport = getRegisteredTransport(cliBin, context.serverName);
|
|
4275
4571
|
if (transport) {
|
|
4276
4572
|
const existingWorkspaceDir = extractWorkspaceDirArg(transport.args);
|
|
@@ -4313,7 +4609,7 @@ function runSetupInternal(flags, options = {}) {
|
|
|
4313
4609
|
process.stdout.write(`Ctxpack: ${context.ctxpackKey}\n`);
|
|
4314
4610
|
}
|
|
4315
4611
|
if (results.length === 0) {
|
|
4316
|
-
process.stdout.write("No codex/claude CLI found. Registration skipped.\n");
|
|
4612
|
+
process.stdout.write("No codex/claude/gemini/antigravity/cursor CLI found. Registration skipped.\n");
|
|
4317
4613
|
return;
|
|
4318
4614
|
}
|
|
4319
4615
|
for (const row of results) {
|