metheus-governance-mcp-cli 0.2.34 → 0.2.36
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/cli.mjs +147 -79
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -4045,13 +4045,95 @@ async function runProxy(flags) {
|
|
|
4045
4045
|
let sessionWorkspaceDir = "";
|
|
4046
4046
|
let sessionWorkspaceTrusted = false;
|
|
4047
4047
|
|
|
4048
|
+
// Proxy-initiated requests (e.g., roots/list) pending client responses.
|
|
4049
|
+
const pendingProxyRequests = new Map(); // id → callback(responseObj)
|
|
4050
|
+
let proxyRequestIdCounter = 0;
|
|
4051
|
+
let stdoutEnvelopeMode = "line"; // "line" | "framed"
|
|
4052
|
+
|
|
4053
|
+
function writeProxyMessage(messageText) {
|
|
4054
|
+
const normalized = String(messageText || "").trim();
|
|
4055
|
+
if (!normalized) return;
|
|
4056
|
+
if (stdoutEnvelopeMode === "framed") {
|
|
4057
|
+
const contentLength = Buffer.byteLength(normalized, "utf8");
|
|
4058
|
+
process.stdout.write(`Content-Length: ${contentLength}\r\n\r\n${normalized}`);
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
process.stdout.write(`${normalized}\n`);
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
function writeProxyJson(payload) {
|
|
4065
|
+
writeProxyMessage(JSON.stringify(payload));
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4068
|
+
function clientSupportsRootsList(initParamsRaw) {
|
|
4069
|
+
const initParams = safeObject(initParamsRaw);
|
|
4070
|
+
const capabilities = safeObject(initParams.capabilities);
|
|
4071
|
+
return Object.prototype.hasOwnProperty.call(capabilities, "roots");
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
/**
|
|
4075
|
+
* Probe the MCP client for workspace roots via the standard roots/list request.
|
|
4076
|
+
* This allows the proxy to discover the actual workspace folder even when the client
|
|
4077
|
+
* (e.g., Antigravity) sets process.cwd() to its install directory instead of the workspace.
|
|
4078
|
+
*/
|
|
4079
|
+
function sendRootsListProbe() {
|
|
4080
|
+
const id = `_proxy_roots_${++proxyRequestIdCounter}`;
|
|
4081
|
+
pendingProxyRequests.set(id, (response) => {
|
|
4082
|
+
const roots = Array.isArray(response?.result?.roots) ? response.result.roots : [];
|
|
4083
|
+
let foundWorkspace = "";
|
|
4084
|
+
for (const root of roots) {
|
|
4085
|
+
const candidate = sanitizeWorkspaceCandidate(String(root?.uri || root?.path || root?.name || ""));
|
|
4086
|
+
if (candidate) {
|
|
4087
|
+
foundWorkspace = candidate;
|
|
4088
|
+
break;
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
try {
|
|
4092
|
+
const _diagDir = path.join(String(process.env.USERPROFILE || process.env.HOME || "."), ".metheus");
|
|
4093
|
+
fs.appendFileSync(
|
|
4094
|
+
path.join(_diagDir, "mcp-proxy-diag.log"),
|
|
4095
|
+
foundWorkspace
|
|
4096
|
+
? `[${new Date().toISOString()}] roots/list: workspace=${foundWorkspace} from ${roots.length} root(s)\n`
|
|
4097
|
+
: `[${new Date().toISOString()}] roots/list: no valid workspace from ${roots.length} root(s), raw=${JSON.stringify(roots).substring(0, 300)}\n`,
|
|
4098
|
+
);
|
|
4099
|
+
} catch { /* best-effort */ }
|
|
4100
|
+
if (foundWorkspace) {
|
|
4101
|
+
sessionWorkspaceDir = foundWorkspace;
|
|
4102
|
+
sessionWorkspaceTrusted = true;
|
|
4103
|
+
}
|
|
4104
|
+
});
|
|
4105
|
+
// Timeout: if client does not respond within 10 seconds, discard the pending callback.
|
|
4106
|
+
setTimeout(() => {
|
|
4107
|
+
if (pendingProxyRequests.has(id)) {
|
|
4108
|
+
pendingProxyRequests.delete(id);
|
|
4109
|
+
try {
|
|
4110
|
+
const _diagDir = path.join(String(process.env.USERPROFILE || process.env.HOME || "."), ".metheus");
|
|
4111
|
+
fs.appendFileSync(
|
|
4112
|
+
path.join(_diagDir, "mcp-proxy-diag.log"),
|
|
4113
|
+
`[${new Date().toISOString()}] roots/list: timeout (client did not respond)\n`,
|
|
4114
|
+
);
|
|
4115
|
+
} catch { /* best-effort */ }
|
|
4116
|
+
}
|
|
4117
|
+
}, 10000);
|
|
4118
|
+
writeProxyJson({ jsonrpc: "2.0", id, method: "roots/list" });
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4048
4121
|
const handleIncomingMessage = async (lineRaw) => {
|
|
4049
4122
|
const line = String(lineRaw || "").trim();
|
|
4050
4123
|
if (!line) return;
|
|
4051
4124
|
|
|
4052
4125
|
const requestObj = tryJsonParse(line);
|
|
4053
4126
|
if (requestObj == null) {
|
|
4054
|
-
|
|
4127
|
+
writeProxyJson(jsonRpcError(null, -32700, "parse error"));
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
// Intercept responses to proxy-initiated requests (e.g., roots/list).
|
|
4132
|
+
// These have an id but no method field — they are client responses, not new requests.
|
|
4133
|
+
if (!requestObj.method && requestObj.id != null && pendingProxyRequests.has(String(requestObj.id))) {
|
|
4134
|
+
const callback = pendingProxyRequests.get(String(requestObj.id));
|
|
4135
|
+
pendingProxyRequests.delete(String(requestObj.id));
|
|
4136
|
+
try { callback(requestObj); } catch { /* best-effort */ }
|
|
4055
4137
|
return;
|
|
4056
4138
|
}
|
|
4057
4139
|
|
|
@@ -4081,16 +4163,14 @@ async function runProxy(flags) {
|
|
|
4081
4163
|
? `; expired token ignored: ${resolved.expired.source}${resolved.expired.expires ? ` (${resolved.expired.expires})` : ""}`
|
|
4082
4164
|
: "";
|
|
4083
4165
|
const refreshHint = lastRefreshError ? `; refresh failed: ${lastRefreshError}` : "";
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
),
|
|
4093
|
-
)}\n`,
|
|
4166
|
+
writeProxyJson(
|
|
4167
|
+
jsonRpcError(
|
|
4168
|
+
requestObj,
|
|
4169
|
+
-32001,
|
|
4170
|
+
`missing auth token (set METHEUS_TOKEN or run 'metheus-governance-mcp auth login --base-url ${normalizeSiteBaseURL(
|
|
4171
|
+
args.baseURL,
|
|
4172
|
+
)}'; login: ${helpURL}${expiredHint}${refreshHint})`,
|
|
4173
|
+
),
|
|
4094
4174
|
);
|
|
4095
4175
|
return;
|
|
4096
4176
|
}
|
|
@@ -4179,15 +4259,11 @@ async function runProxy(flags) {
|
|
|
4179
4259
|
}),
|
|
4180
4260
|
).trim();
|
|
4181
4261
|
if (!projectID) {
|
|
4182
|
-
|
|
4183
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"))}\n`,
|
|
4184
|
-
);
|
|
4262
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"));
|
|
4185
4263
|
return;
|
|
4186
4264
|
}
|
|
4187
4265
|
if (!isUUID(projectID)) {
|
|
4188
|
-
|
|
4189
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"))}\n`,
|
|
4190
|
-
);
|
|
4266
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"));
|
|
4191
4267
|
return;
|
|
4192
4268
|
}
|
|
4193
4269
|
try {
|
|
@@ -4214,23 +4290,19 @@ async function runProxy(flags) {
|
|
|
4214
4290
|
workspaceSignalTrusted,
|
|
4215
4291
|
});
|
|
4216
4292
|
const text = buildProjectSummaryText(summary);
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
}),
|
|
4228
|
-
)}\n`,
|
|
4293
|
+
writeProxyJson(
|
|
4294
|
+
jsonRpcResult(requestObj, {
|
|
4295
|
+
content: [
|
|
4296
|
+
{
|
|
4297
|
+
type: "text",
|
|
4298
|
+
text,
|
|
4299
|
+
},
|
|
4300
|
+
],
|
|
4301
|
+
structuredContent: summary,
|
|
4302
|
+
}),
|
|
4229
4303
|
);
|
|
4230
4304
|
} catch (err) {
|
|
4231
|
-
|
|
4232
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
|
|
4233
|
-
);
|
|
4305
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
|
|
4234
4306
|
}
|
|
4235
4307
|
return;
|
|
4236
4308
|
}
|
|
@@ -4243,15 +4315,11 @@ async function runProxy(flags) {
|
|
|
4243
4315
|
}),
|
|
4244
4316
|
).trim();
|
|
4245
4317
|
if (!projectID) {
|
|
4246
|
-
|
|
4247
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"))}\n`,
|
|
4248
|
-
);
|
|
4318
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"));
|
|
4249
4319
|
return;
|
|
4250
4320
|
}
|
|
4251
4321
|
if (!isUUID(projectID)) {
|
|
4252
|
-
|
|
4253
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"))}\n`,
|
|
4254
|
-
);
|
|
4322
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"));
|
|
4255
4323
|
return;
|
|
4256
4324
|
}
|
|
4257
4325
|
|
|
@@ -4260,14 +4328,12 @@ async function runProxy(flags) {
|
|
|
4260
4328
|
const statusRaw = String(toolArgs.status || "").trim().toLowerCase();
|
|
4261
4329
|
const statusFilter = normalizeMergeStatusFilter(statusRaw);
|
|
4262
4330
|
if (statusRaw && !statusFilter) {
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
),
|
|
4270
|
-
)}\n`,
|
|
4331
|
+
writeProxyJson(
|
|
4332
|
+
jsonRpcError(
|
|
4333
|
+
requestObj,
|
|
4334
|
+
-32001,
|
|
4335
|
+
"status must be one of: open, review, approved, rejected, merged, closed, all",
|
|
4336
|
+
),
|
|
4271
4337
|
);
|
|
4272
4338
|
return;
|
|
4273
4339
|
}
|
|
@@ -4282,13 +4348,11 @@ async function runProxy(flags) {
|
|
|
4282
4348
|
workspaceDir: requestWorkspaceDir,
|
|
4283
4349
|
});
|
|
4284
4350
|
const text = buildCtxpackMergeBriefText(brief);
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
}),
|
|
4291
|
-
)}\n`,
|
|
4351
|
+
writeProxyJson(
|
|
4352
|
+
jsonRpcResult(requestObj, {
|
|
4353
|
+
content: [{ type: "text", text }],
|
|
4354
|
+
structuredContent: brief,
|
|
4355
|
+
}),
|
|
4292
4356
|
);
|
|
4293
4357
|
return;
|
|
4294
4358
|
}
|
|
@@ -4300,16 +4364,12 @@ async function runProxy(flags) {
|
|
|
4300
4364
|
toolArgs.owner_confirmation || toolArgs.ownerConfirmation || "",
|
|
4301
4365
|
).trim();
|
|
4302
4366
|
if (!mergeRequestID || !isUUID(mergeRequestID)) {
|
|
4303
|
-
|
|
4304
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "merge_request_id must be a valid UUID"))}\n`,
|
|
4305
|
-
);
|
|
4367
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "merge_request_id must be a valid UUID"));
|
|
4306
4368
|
return;
|
|
4307
4369
|
}
|
|
4308
4370
|
if (!action) {
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
jsonRpcError(requestObj, -32001, "action must be one of: review, approve, reject, close, merge"),
|
|
4312
|
-
)}\n`,
|
|
4371
|
+
writeProxyJson(
|
|
4372
|
+
jsonRpcError(requestObj, -32001, "action must be one of: review, approve, reject, close, merge"),
|
|
4313
4373
|
);
|
|
4314
4374
|
return;
|
|
4315
4375
|
}
|
|
@@ -4324,20 +4384,16 @@ async function runProxy(flags) {
|
|
|
4324
4384
|
workspaceDir: requestWorkspaceDir,
|
|
4325
4385
|
});
|
|
4326
4386
|
const text = buildCtxpackMergeExecuteText(result);
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
}),
|
|
4333
|
-
)}\n`,
|
|
4387
|
+
writeProxyJson(
|
|
4388
|
+
jsonRpcResult(requestObj, {
|
|
4389
|
+
content: [{ type: "text", text }],
|
|
4390
|
+
structuredContent: result,
|
|
4391
|
+
}),
|
|
4334
4392
|
);
|
|
4335
4393
|
return;
|
|
4336
4394
|
}
|
|
4337
4395
|
} catch (err) {
|
|
4338
|
-
|
|
4339
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
|
|
4340
|
-
);
|
|
4396
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
|
|
4341
4397
|
return;
|
|
4342
4398
|
}
|
|
4343
4399
|
}
|
|
@@ -4369,13 +4425,28 @@ async function runProxy(flags) {
|
|
|
4369
4425
|
requestWorkspaceDir,
|
|
4370
4426
|
workspaceSignalTrusted,
|
|
4371
4427
|
);
|
|
4372
|
-
|
|
4428
|
+
writeProxyJson(patched);
|
|
4373
4429
|
return;
|
|
4374
4430
|
}
|
|
4375
4431
|
if (isJsonRpcMethod(requestObj, "tools/list")) {
|
|
4376
4432
|
patched = appendLocalToolToToolsList(patched);
|
|
4377
4433
|
} else if (isJsonRpcMethod(requestObj, "initialize")) {
|
|
4378
4434
|
patched = appendProjectHintToInitialize(patched, args);
|
|
4435
|
+
// Log initialize params for workspace debugging.
|
|
4436
|
+
try {
|
|
4437
|
+
const _diagDir = path.join(String(process.env.USERPROFILE || process.env.HOME || "."), ".metheus");
|
|
4438
|
+
const initParams = safeObject(requestObj?.params);
|
|
4439
|
+
fs.appendFileSync(
|
|
4440
|
+
path.join(_diagDir, "mcp-proxy-diag.log"),
|
|
4441
|
+
`[${new Date().toISOString()}] initialize: clientInfo=${JSON.stringify(initParams.clientInfo || {})} capabilities=${JSON.stringify(initParams.capabilities || {})} rootUri=${initParams.rootUri || "(none)"} workspaceFolders=${JSON.stringify(initParams.workspaceFolders || []).substring(0, 300)}\n`,
|
|
4442
|
+
);
|
|
4443
|
+
} catch { /* best-effort */ }
|
|
4444
|
+
// Probe roots/list only when client advertised roots capability.
|
|
4445
|
+
// Some MCP clients reject unsolicited server requests and then hide tools.
|
|
4446
|
+
if (clientSupportsRootsList(requestObj?.params)) {
|
|
4447
|
+
// Use setImmediate so the initialize response is flushed first.
|
|
4448
|
+
setImmediate(() => sendRootsListProbe());
|
|
4449
|
+
}
|
|
4379
4450
|
} else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "workitem.list") {
|
|
4380
4451
|
patched = await appendWorkitemListHints(patched, args, toolArgs, token, workspaceSignalTrusted);
|
|
4381
4452
|
} else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "ctxpack.ensure") {
|
|
@@ -4390,21 +4461,17 @@ async function runProxy(flags) {
|
|
|
4390
4461
|
if (autoSyncSummary) {
|
|
4391
4462
|
patched = appendAutoCtxpackSyncHint(patched, autoSyncSummary);
|
|
4392
4463
|
}
|
|
4393
|
-
|
|
4464
|
+
writeProxyJson(patched);
|
|
4394
4465
|
return;
|
|
4395
4466
|
}
|
|
4396
|
-
|
|
4467
|
+
writeProxyMessage(responseText);
|
|
4397
4468
|
return;
|
|
4398
4469
|
}
|
|
4399
4470
|
if (Object.prototype.hasOwnProperty.call(requestObj, "id")) {
|
|
4400
|
-
|
|
4401
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "empty gateway response"))}\n`,
|
|
4402
|
-
);
|
|
4471
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "empty gateway response"));
|
|
4403
4472
|
}
|
|
4404
4473
|
} catch (err) {
|
|
4405
|
-
|
|
4406
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
|
|
4407
|
-
);
|
|
4474
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
|
|
4408
4475
|
}
|
|
4409
4476
|
};
|
|
4410
4477
|
|
|
@@ -4503,6 +4570,7 @@ async function runProxy(flags) {
|
|
|
4503
4570
|
if (pendingInputBuffer.length === 0) break;
|
|
4504
4571
|
|
|
4505
4572
|
if (startsWithContentLengthHeader(pendingInputBuffer)) {
|
|
4573
|
+
stdoutEnvelopeMode = "framed";
|
|
4506
4574
|
const framed = extractFramedMessage(pendingInputBuffer);
|
|
4507
4575
|
if (!framed) break;
|
|
4508
4576
|
pendingInputBuffer = framed.remaining;
|