metheus-governance-mcp-cli 0.2.35 → 0.2.37
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 +174 -85
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -3491,6 +3491,83 @@ function ensureArray(value) {
|
|
|
3491
3491
|
return Array.isArray(value) ? value : [];
|
|
3492
3492
|
}
|
|
3493
3493
|
|
|
3494
|
+
function normalizeSafeToolAliasName(rawName) {
|
|
3495
|
+
const base = String(rawName || "")
|
|
3496
|
+
.trim()
|
|
3497
|
+
.replace(/[^a-zA-Z0-9_]/g, "_")
|
|
3498
|
+
.replace(/_+/g, "_")
|
|
3499
|
+
.replace(/^_+|_+$/g, "");
|
|
3500
|
+
if (!base) return "";
|
|
3501
|
+
if (/^[0-9]/.test(base)) return `tool_${base}`;
|
|
3502
|
+
return base;
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
function buildToolAliasMaps(tools) {
|
|
3506
|
+
const canonicalNames = new Set(
|
|
3507
|
+
ensureArray(tools)
|
|
3508
|
+
.map((tool) => String(tool?.name || "").trim())
|
|
3509
|
+
.filter(Boolean),
|
|
3510
|
+
);
|
|
3511
|
+
const aliasToCanonical = new Map();
|
|
3512
|
+
const canonicalToAlias = new Map();
|
|
3513
|
+
const reserved = new Set(canonicalNames);
|
|
3514
|
+
|
|
3515
|
+
for (const canonicalName of canonicalNames) {
|
|
3516
|
+
const aliasBase = normalizeSafeToolAliasName(canonicalName);
|
|
3517
|
+
if (!aliasBase || aliasBase === canonicalName) continue;
|
|
3518
|
+
let alias = aliasBase;
|
|
3519
|
+
let counter = 2;
|
|
3520
|
+
while (reserved.has(alias) || aliasToCanonical.has(alias)) {
|
|
3521
|
+
alias = `${aliasBase}_${counter}`;
|
|
3522
|
+
counter += 1;
|
|
3523
|
+
}
|
|
3524
|
+
aliasToCanonical.set(alias, canonicalName);
|
|
3525
|
+
canonicalToAlias.set(canonicalName, alias);
|
|
3526
|
+
reserved.add(alias);
|
|
3527
|
+
}
|
|
3528
|
+
return { aliasToCanonical, canonicalToAlias };
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
function applyToolAliasesToToolsListResponse(responseObj, canonicalToAlias) {
|
|
3532
|
+
const result = safeObject(responseObj.result);
|
|
3533
|
+
const tools = ensureArray(result.tools);
|
|
3534
|
+
result.tools = tools.map((tool) => {
|
|
3535
|
+
const safeTool = safeObject(tool);
|
|
3536
|
+
const currentName = String(safeTool.name || "").trim();
|
|
3537
|
+
const aliasName = canonicalToAlias.get(currentName);
|
|
3538
|
+
if (!aliasName) return safeTool;
|
|
3539
|
+
return { ...safeTool, name: aliasName };
|
|
3540
|
+
});
|
|
3541
|
+
responseObj.result = result;
|
|
3542
|
+
return responseObj;
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
function rewriteAliasedToolCallToCanonical(requestObj, aliasToCanonical) {
|
|
3546
|
+
if (!isJsonRpcMethod(requestObj, "tools/call")) return requestObj;
|
|
3547
|
+
const params = safeObject(requestObj.params);
|
|
3548
|
+
const currentName = String(params.name ?? params.tool_name ?? params.toolName ?? "").trim();
|
|
3549
|
+
if (!currentName) return requestObj;
|
|
3550
|
+
const canonicalName = String(aliasToCanonical.get(currentName) || "").trim();
|
|
3551
|
+
if (!canonicalName) return requestObj;
|
|
3552
|
+
|
|
3553
|
+
const nextParams = { ...params, name: canonicalName };
|
|
3554
|
+
if (Object.prototype.hasOwnProperty.call(nextParams, "tool_name")) {
|
|
3555
|
+
nextParams.tool_name = canonicalName;
|
|
3556
|
+
}
|
|
3557
|
+
if (Object.prototype.hasOwnProperty.call(nextParams, "toolName")) {
|
|
3558
|
+
nextParams.toolName = canonicalName;
|
|
3559
|
+
}
|
|
3560
|
+
return { ...requestObj, params: nextParams };
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
function shouldUseSafeToolAliasesForClient(initParamsRaw) {
|
|
3564
|
+
const initParams = safeObject(initParamsRaw);
|
|
3565
|
+
const clientInfo = safeObject(initParams.clientInfo);
|
|
3566
|
+
const name = String(clientInfo.name || "").trim().toLowerCase();
|
|
3567
|
+
if (!name) return false;
|
|
3568
|
+
return name.includes("cursor") || name.includes("antigravity");
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3494
3571
|
function injectWorkspaceDirIntoToolSchemas(tools) {
|
|
3495
3572
|
const workspaceDirProp = {
|
|
3496
3573
|
type: "string",
|
|
@@ -4044,10 +4121,35 @@ async function runProxy(flags) {
|
|
|
4044
4121
|
let lastRefreshError = "";
|
|
4045
4122
|
let sessionWorkspaceDir = "";
|
|
4046
4123
|
let sessionWorkspaceTrusted = false;
|
|
4124
|
+
let sessionUseSafeToolAliases = false;
|
|
4125
|
+
let sessionToolAliasToCanonical = new Map();
|
|
4126
|
+
let sessionToolCanonicalToAlias = new Map();
|
|
4047
4127
|
|
|
4048
4128
|
// Proxy-initiated requests (e.g., roots/list) pending client responses.
|
|
4049
4129
|
const pendingProxyRequests = new Map(); // id → callback(responseObj)
|
|
4050
4130
|
let proxyRequestIdCounter = 0;
|
|
4131
|
+
let stdoutEnvelopeMode = "line"; // "line" | "framed"
|
|
4132
|
+
|
|
4133
|
+
function writeProxyMessage(messageText) {
|
|
4134
|
+
const normalized = String(messageText || "").trim();
|
|
4135
|
+
if (!normalized) return;
|
|
4136
|
+
if (stdoutEnvelopeMode === "framed") {
|
|
4137
|
+
const contentLength = Buffer.byteLength(normalized, "utf8");
|
|
4138
|
+
process.stdout.write(`Content-Length: ${contentLength}\r\n\r\n${normalized}`);
|
|
4139
|
+
return;
|
|
4140
|
+
}
|
|
4141
|
+
process.stdout.write(`${normalized}\n`);
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
function writeProxyJson(payload) {
|
|
4145
|
+
writeProxyMessage(JSON.stringify(payload));
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
function clientSupportsRootsList(initParamsRaw) {
|
|
4149
|
+
const initParams = safeObject(initParamsRaw);
|
|
4150
|
+
const capabilities = safeObject(initParams.capabilities);
|
|
4151
|
+
return Object.prototype.hasOwnProperty.call(capabilities, "roots");
|
|
4152
|
+
}
|
|
4051
4153
|
|
|
4052
4154
|
/**
|
|
4053
4155
|
* Probe the MCP client for workspace roots via the standard roots/list request.
|
|
@@ -4093,16 +4195,16 @@ async function runProxy(flags) {
|
|
|
4093
4195
|
} catch { /* best-effort */ }
|
|
4094
4196
|
}
|
|
4095
4197
|
}, 10000);
|
|
4096
|
-
|
|
4198
|
+
writeProxyJson({ jsonrpc: "2.0", id, method: "roots/list" });
|
|
4097
4199
|
}
|
|
4098
4200
|
|
|
4099
4201
|
const handleIncomingMessage = async (lineRaw) => {
|
|
4100
4202
|
const line = String(lineRaw || "").trim();
|
|
4101
4203
|
if (!line) return;
|
|
4102
4204
|
|
|
4103
|
-
|
|
4205
|
+
let requestObj = tryJsonParse(line);
|
|
4104
4206
|
if (requestObj == null) {
|
|
4105
|
-
|
|
4207
|
+
writeProxyJson(jsonRpcError(null, -32700, "parse error"));
|
|
4106
4208
|
return;
|
|
4107
4209
|
}
|
|
4108
4210
|
|
|
@@ -4141,20 +4243,25 @@ async function runProxy(flags) {
|
|
|
4141
4243
|
? `; expired token ignored: ${resolved.expired.source}${resolved.expired.expires ? ` (${resolved.expired.expires})` : ""}`
|
|
4142
4244
|
: "";
|
|
4143
4245
|
const refreshHint = lastRefreshError ? `; refresh failed: ${lastRefreshError}` : "";
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
),
|
|
4153
|
-
)}\n`,
|
|
4246
|
+
writeProxyJson(
|
|
4247
|
+
jsonRpcError(
|
|
4248
|
+
requestObj,
|
|
4249
|
+
-32001,
|
|
4250
|
+
`missing auth token (set METHEUS_TOKEN or run 'metheus-governance-mcp auth login --base-url ${normalizeSiteBaseURL(
|
|
4251
|
+
args.baseURL,
|
|
4252
|
+
)}'; login: ${helpURL}${expiredHint}${refreshHint})`,
|
|
4253
|
+
),
|
|
4154
4254
|
);
|
|
4155
4255
|
return;
|
|
4156
4256
|
}
|
|
4157
4257
|
|
|
4258
|
+
if (isJsonRpcMethod(requestObj, "initialize") && shouldUseSafeToolAliasesForClient(requestObj?.params)) {
|
|
4259
|
+
sessionUseSafeToolAliases = true;
|
|
4260
|
+
}
|
|
4261
|
+
if (sessionUseSafeToolAliases) {
|
|
4262
|
+
requestObj = rewriteAliasedToolCallToCanonical(requestObj, sessionToolAliasToCanonical);
|
|
4263
|
+
}
|
|
4264
|
+
|
|
4158
4265
|
const { name: toolName, args: toolArgs } = extractToolCall(requestObj);
|
|
4159
4266
|
let strongRequestWorkspaceCandidate = "";
|
|
4160
4267
|
let weakRequestWorkspaceCandidate = "";
|
|
@@ -4239,15 +4346,11 @@ async function runProxy(flags) {
|
|
|
4239
4346
|
}),
|
|
4240
4347
|
).trim();
|
|
4241
4348
|
if (!projectID) {
|
|
4242
|
-
|
|
4243
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"))}\n`,
|
|
4244
|
-
);
|
|
4349
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"));
|
|
4245
4350
|
return;
|
|
4246
4351
|
}
|
|
4247
4352
|
if (!isUUID(projectID)) {
|
|
4248
|
-
|
|
4249
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"))}\n`,
|
|
4250
|
-
);
|
|
4353
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"));
|
|
4251
4354
|
return;
|
|
4252
4355
|
}
|
|
4253
4356
|
try {
|
|
@@ -4274,23 +4377,19 @@ async function runProxy(flags) {
|
|
|
4274
4377
|
workspaceSignalTrusted,
|
|
4275
4378
|
});
|
|
4276
4379
|
const text = buildProjectSummaryText(summary);
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
}),
|
|
4288
|
-
)}\n`,
|
|
4380
|
+
writeProxyJson(
|
|
4381
|
+
jsonRpcResult(requestObj, {
|
|
4382
|
+
content: [
|
|
4383
|
+
{
|
|
4384
|
+
type: "text",
|
|
4385
|
+
text,
|
|
4386
|
+
},
|
|
4387
|
+
],
|
|
4388
|
+
structuredContent: summary,
|
|
4389
|
+
}),
|
|
4289
4390
|
);
|
|
4290
4391
|
} catch (err) {
|
|
4291
|
-
|
|
4292
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
|
|
4293
|
-
);
|
|
4392
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
|
|
4294
4393
|
}
|
|
4295
4394
|
return;
|
|
4296
4395
|
}
|
|
@@ -4303,15 +4402,11 @@ async function runProxy(flags) {
|
|
|
4303
4402
|
}),
|
|
4304
4403
|
).trim();
|
|
4305
4404
|
if (!projectID) {
|
|
4306
|
-
|
|
4307
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"))}\n`,
|
|
4308
|
-
);
|
|
4405
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"));
|
|
4309
4406
|
return;
|
|
4310
4407
|
}
|
|
4311
4408
|
if (!isUUID(projectID)) {
|
|
4312
|
-
|
|
4313
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"))}\n`,
|
|
4314
|
-
);
|
|
4409
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"));
|
|
4315
4410
|
return;
|
|
4316
4411
|
}
|
|
4317
4412
|
|
|
@@ -4320,14 +4415,12 @@ async function runProxy(flags) {
|
|
|
4320
4415
|
const statusRaw = String(toolArgs.status || "").trim().toLowerCase();
|
|
4321
4416
|
const statusFilter = normalizeMergeStatusFilter(statusRaw);
|
|
4322
4417
|
if (statusRaw && !statusFilter) {
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
),
|
|
4330
|
-
)}\n`,
|
|
4418
|
+
writeProxyJson(
|
|
4419
|
+
jsonRpcError(
|
|
4420
|
+
requestObj,
|
|
4421
|
+
-32001,
|
|
4422
|
+
"status must be one of: open, review, approved, rejected, merged, closed, all",
|
|
4423
|
+
),
|
|
4331
4424
|
);
|
|
4332
4425
|
return;
|
|
4333
4426
|
}
|
|
@@ -4342,13 +4435,11 @@ async function runProxy(flags) {
|
|
|
4342
4435
|
workspaceDir: requestWorkspaceDir,
|
|
4343
4436
|
});
|
|
4344
4437
|
const text = buildCtxpackMergeBriefText(brief);
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
}),
|
|
4351
|
-
)}\n`,
|
|
4438
|
+
writeProxyJson(
|
|
4439
|
+
jsonRpcResult(requestObj, {
|
|
4440
|
+
content: [{ type: "text", text }],
|
|
4441
|
+
structuredContent: brief,
|
|
4442
|
+
}),
|
|
4352
4443
|
);
|
|
4353
4444
|
return;
|
|
4354
4445
|
}
|
|
@@ -4360,16 +4451,12 @@ async function runProxy(flags) {
|
|
|
4360
4451
|
toolArgs.owner_confirmation || toolArgs.ownerConfirmation || "",
|
|
4361
4452
|
).trim();
|
|
4362
4453
|
if (!mergeRequestID || !isUUID(mergeRequestID)) {
|
|
4363
|
-
|
|
4364
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "merge_request_id must be a valid UUID"))}\n`,
|
|
4365
|
-
);
|
|
4454
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "merge_request_id must be a valid UUID"));
|
|
4366
4455
|
return;
|
|
4367
4456
|
}
|
|
4368
4457
|
if (!action) {
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
jsonRpcError(requestObj, -32001, "action must be one of: review, approve, reject, close, merge"),
|
|
4372
|
-
)}\n`,
|
|
4458
|
+
writeProxyJson(
|
|
4459
|
+
jsonRpcError(requestObj, -32001, "action must be one of: review, approve, reject, close, merge"),
|
|
4373
4460
|
);
|
|
4374
4461
|
return;
|
|
4375
4462
|
}
|
|
@@ -4384,20 +4471,16 @@ async function runProxy(flags) {
|
|
|
4384
4471
|
workspaceDir: requestWorkspaceDir,
|
|
4385
4472
|
});
|
|
4386
4473
|
const text = buildCtxpackMergeExecuteText(result);
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
}),
|
|
4393
|
-
)}\n`,
|
|
4474
|
+
writeProxyJson(
|
|
4475
|
+
jsonRpcResult(requestObj, {
|
|
4476
|
+
content: [{ type: "text", text }],
|
|
4477
|
+
structuredContent: result,
|
|
4478
|
+
}),
|
|
4394
4479
|
);
|
|
4395
4480
|
return;
|
|
4396
4481
|
}
|
|
4397
4482
|
} catch (err) {
|
|
4398
|
-
|
|
4399
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
|
|
4400
|
-
);
|
|
4483
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
|
|
4401
4484
|
return;
|
|
4402
4485
|
}
|
|
4403
4486
|
}
|
|
@@ -4429,11 +4512,18 @@ async function runProxy(flags) {
|
|
|
4429
4512
|
requestWorkspaceDir,
|
|
4430
4513
|
workspaceSignalTrusted,
|
|
4431
4514
|
);
|
|
4432
|
-
|
|
4515
|
+
writeProxyJson(patched);
|
|
4433
4516
|
return;
|
|
4434
4517
|
}
|
|
4435
4518
|
if (isJsonRpcMethod(requestObj, "tools/list")) {
|
|
4436
4519
|
patched = appendLocalToolToToolsList(patched);
|
|
4520
|
+
if (sessionUseSafeToolAliases) {
|
|
4521
|
+
const tools = ensureArray(safeObject(patched.result).tools);
|
|
4522
|
+
const aliasMaps = buildToolAliasMaps(tools);
|
|
4523
|
+
sessionToolAliasToCanonical = aliasMaps.aliasToCanonical;
|
|
4524
|
+
sessionToolCanonicalToAlias = aliasMaps.canonicalToAlias;
|
|
4525
|
+
patched = applyToolAliasesToToolsListResponse(patched, sessionToolCanonicalToAlias);
|
|
4526
|
+
}
|
|
4437
4527
|
} else if (isJsonRpcMethod(requestObj, "initialize")) {
|
|
4438
4528
|
patched = appendProjectHintToInitialize(patched, args);
|
|
4439
4529
|
// Log initialize params for workspace debugging.
|
|
@@ -4445,10 +4535,12 @@ async function runProxy(flags) {
|
|
|
4445
4535
|
`[${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`,
|
|
4446
4536
|
);
|
|
4447
4537
|
} catch { /* best-effort */ }
|
|
4448
|
-
// Probe
|
|
4449
|
-
//
|
|
4450
|
-
|
|
4451
|
-
|
|
4538
|
+
// Probe roots/list only when client advertised roots capability.
|
|
4539
|
+
// Some MCP clients reject unsolicited server requests and then hide tools.
|
|
4540
|
+
if (clientSupportsRootsList(requestObj?.params)) {
|
|
4541
|
+
// Use setImmediate so the initialize response is flushed first.
|
|
4542
|
+
setImmediate(() => sendRootsListProbe());
|
|
4543
|
+
}
|
|
4452
4544
|
} else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "workitem.list") {
|
|
4453
4545
|
patched = await appendWorkitemListHints(patched, args, toolArgs, token, workspaceSignalTrusted);
|
|
4454
4546
|
} else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "ctxpack.ensure") {
|
|
@@ -4463,21 +4555,17 @@ async function runProxy(flags) {
|
|
|
4463
4555
|
if (autoSyncSummary) {
|
|
4464
4556
|
patched = appendAutoCtxpackSyncHint(patched, autoSyncSummary);
|
|
4465
4557
|
}
|
|
4466
|
-
|
|
4558
|
+
writeProxyJson(patched);
|
|
4467
4559
|
return;
|
|
4468
4560
|
}
|
|
4469
|
-
|
|
4561
|
+
writeProxyMessage(responseText);
|
|
4470
4562
|
return;
|
|
4471
4563
|
}
|
|
4472
4564
|
if (Object.prototype.hasOwnProperty.call(requestObj, "id")) {
|
|
4473
|
-
|
|
4474
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, "empty gateway response"))}\n`,
|
|
4475
|
-
);
|
|
4565
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, "empty gateway response"));
|
|
4476
4566
|
}
|
|
4477
4567
|
} catch (err) {
|
|
4478
|
-
|
|
4479
|
-
`${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
|
|
4480
|
-
);
|
|
4568
|
+
writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
|
|
4481
4569
|
}
|
|
4482
4570
|
};
|
|
4483
4571
|
|
|
@@ -4576,6 +4664,7 @@ async function runProxy(flags) {
|
|
|
4576
4664
|
if (pendingInputBuffer.length === 0) break;
|
|
4577
4665
|
|
|
4578
4666
|
if (startsWithContentLengthHeader(pendingInputBuffer)) {
|
|
4667
|
+
stdoutEnvelopeMode = "framed";
|
|
4579
4668
|
const framed = extractFramedMessage(pendingInputBuffer);
|
|
4580
4669
|
if (!framed) break;
|
|
4581
4670
|
pendingInputBuffer = framed.remaining;
|