metheus-governance-mcp-cli 0.2.35 → 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.
Files changed (2) hide show
  1. package/cli.mjs +79 -84
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -4048,6 +4048,28 @@ async function runProxy(flags) {
4048
4048
  // Proxy-initiated requests (e.g., roots/list) pending client responses.
4049
4049
  const pendingProxyRequests = new Map(); // id → callback(responseObj)
4050
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
+ }
4051
4073
 
4052
4074
  /**
4053
4075
  * Probe the MCP client for workspace roots via the standard roots/list request.
@@ -4093,7 +4115,7 @@ async function runProxy(flags) {
4093
4115
  } catch { /* best-effort */ }
4094
4116
  }
4095
4117
  }, 10000);
4096
- process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, method: "roots/list" })}\n`);
4118
+ writeProxyJson({ jsonrpc: "2.0", id, method: "roots/list" });
4097
4119
  }
4098
4120
 
4099
4121
  const handleIncomingMessage = async (lineRaw) => {
@@ -4102,7 +4124,7 @@ async function runProxy(flags) {
4102
4124
 
4103
4125
  const requestObj = tryJsonParse(line);
4104
4126
  if (requestObj == null) {
4105
- process.stdout.write(`${JSON.stringify(jsonRpcError(null, -32700, "parse error"))}\n`);
4127
+ writeProxyJson(jsonRpcError(null, -32700, "parse error"));
4106
4128
  return;
4107
4129
  }
4108
4130
 
@@ -4141,16 +4163,14 @@ async function runProxy(flags) {
4141
4163
  ? `; expired token ignored: ${resolved.expired.source}${resolved.expired.expires ? ` (${resolved.expired.expires})` : ""}`
4142
4164
  : "";
4143
4165
  const refreshHint = lastRefreshError ? `; refresh failed: ${lastRefreshError}` : "";
4144
- process.stdout.write(
4145
- `${JSON.stringify(
4146
- jsonRpcError(
4147
- requestObj,
4148
- -32001,
4149
- `missing auth token (set METHEUS_TOKEN or run 'metheus-governance-mcp auth login --base-url ${normalizeSiteBaseURL(
4150
- args.baseURL,
4151
- )}'; login: ${helpURL}${expiredHint}${refreshHint})`,
4152
- ),
4153
- )}\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
+ ),
4154
4174
  );
4155
4175
  return;
4156
4176
  }
@@ -4239,15 +4259,11 @@ async function runProxy(flags) {
4239
4259
  }),
4240
4260
  ).trim();
4241
4261
  if (!projectID) {
4242
- process.stdout.write(
4243
- `${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"))}\n`,
4244
- );
4262
+ writeProxyJson(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"));
4245
4263
  return;
4246
4264
  }
4247
4265
  if (!isUUID(projectID)) {
4248
- process.stdout.write(
4249
- `${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"))}\n`,
4250
- );
4266
+ writeProxyJson(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"));
4251
4267
  return;
4252
4268
  }
4253
4269
  try {
@@ -4274,23 +4290,19 @@ async function runProxy(flags) {
4274
4290
  workspaceSignalTrusted,
4275
4291
  });
4276
4292
  const text = buildProjectSummaryText(summary);
4277
- process.stdout.write(
4278
- `${JSON.stringify(
4279
- jsonRpcResult(requestObj, {
4280
- content: [
4281
- {
4282
- type: "text",
4283
- text,
4284
- },
4285
- ],
4286
- structuredContent: summary,
4287
- }),
4288
- )}\n`,
4293
+ writeProxyJson(
4294
+ jsonRpcResult(requestObj, {
4295
+ content: [
4296
+ {
4297
+ type: "text",
4298
+ text,
4299
+ },
4300
+ ],
4301
+ structuredContent: summary,
4302
+ }),
4289
4303
  );
4290
4304
  } catch (err) {
4291
- process.stdout.write(
4292
- `${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
4293
- );
4305
+ writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
4294
4306
  }
4295
4307
  return;
4296
4308
  }
@@ -4303,15 +4315,11 @@ async function runProxy(flags) {
4303
4315
  }),
4304
4316
  ).trim();
4305
4317
  if (!projectID) {
4306
- process.stdout.write(
4307
- `${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"))}\n`,
4308
- );
4318
+ writeProxyJson(jsonRpcError(requestObj, -32001, "project_id is required (or set --project-id during setup)"));
4309
4319
  return;
4310
4320
  }
4311
4321
  if (!isUUID(projectID)) {
4312
- process.stdout.write(
4313
- `${JSON.stringify(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"))}\n`,
4314
- );
4322
+ writeProxyJson(jsonRpcError(requestObj, -32001, "project_id must be a valid UUID"));
4315
4323
  return;
4316
4324
  }
4317
4325
 
@@ -4320,14 +4328,12 @@ async function runProxy(flags) {
4320
4328
  const statusRaw = String(toolArgs.status || "").trim().toLowerCase();
4321
4329
  const statusFilter = normalizeMergeStatusFilter(statusRaw);
4322
4330
  if (statusRaw && !statusFilter) {
4323
- process.stdout.write(
4324
- `${JSON.stringify(
4325
- jsonRpcError(
4326
- requestObj,
4327
- -32001,
4328
- "status must be one of: open, review, approved, rejected, merged, closed, all",
4329
- ),
4330
- )}\n`,
4331
+ writeProxyJson(
4332
+ jsonRpcError(
4333
+ requestObj,
4334
+ -32001,
4335
+ "status must be one of: open, review, approved, rejected, merged, closed, all",
4336
+ ),
4331
4337
  );
4332
4338
  return;
4333
4339
  }
@@ -4342,13 +4348,11 @@ async function runProxy(flags) {
4342
4348
  workspaceDir: requestWorkspaceDir,
4343
4349
  });
4344
4350
  const text = buildCtxpackMergeBriefText(brief);
4345
- process.stdout.write(
4346
- `${JSON.stringify(
4347
- jsonRpcResult(requestObj, {
4348
- content: [{ type: "text", text }],
4349
- structuredContent: brief,
4350
- }),
4351
- )}\n`,
4351
+ writeProxyJson(
4352
+ jsonRpcResult(requestObj, {
4353
+ content: [{ type: "text", text }],
4354
+ structuredContent: brief,
4355
+ }),
4352
4356
  );
4353
4357
  return;
4354
4358
  }
@@ -4360,16 +4364,12 @@ async function runProxy(flags) {
4360
4364
  toolArgs.owner_confirmation || toolArgs.ownerConfirmation || "",
4361
4365
  ).trim();
4362
4366
  if (!mergeRequestID || !isUUID(mergeRequestID)) {
4363
- process.stdout.write(
4364
- `${JSON.stringify(jsonRpcError(requestObj, -32001, "merge_request_id must be a valid UUID"))}\n`,
4365
- );
4367
+ writeProxyJson(jsonRpcError(requestObj, -32001, "merge_request_id must be a valid UUID"));
4366
4368
  return;
4367
4369
  }
4368
4370
  if (!action) {
4369
- process.stdout.write(
4370
- `${JSON.stringify(
4371
- jsonRpcError(requestObj, -32001, "action must be one of: review, approve, reject, close, merge"),
4372
- )}\n`,
4371
+ writeProxyJson(
4372
+ jsonRpcError(requestObj, -32001, "action must be one of: review, approve, reject, close, merge"),
4373
4373
  );
4374
4374
  return;
4375
4375
  }
@@ -4384,20 +4384,16 @@ async function runProxy(flags) {
4384
4384
  workspaceDir: requestWorkspaceDir,
4385
4385
  });
4386
4386
  const text = buildCtxpackMergeExecuteText(result);
4387
- process.stdout.write(
4388
- `${JSON.stringify(
4389
- jsonRpcResult(requestObj, {
4390
- content: [{ type: "text", text }],
4391
- structuredContent: result,
4392
- }),
4393
- )}\n`,
4387
+ writeProxyJson(
4388
+ jsonRpcResult(requestObj, {
4389
+ content: [{ type: "text", text }],
4390
+ structuredContent: result,
4391
+ }),
4394
4392
  );
4395
4393
  return;
4396
4394
  }
4397
4395
  } catch (err) {
4398
- process.stdout.write(
4399
- `${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
4400
- );
4396
+ writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
4401
4397
  return;
4402
4398
  }
4403
4399
  }
@@ -4429,7 +4425,7 @@ async function runProxy(flags) {
4429
4425
  requestWorkspaceDir,
4430
4426
  workspaceSignalTrusted,
4431
4427
  );
4432
- process.stdout.write(`${JSON.stringify(patched)}\n`);
4428
+ writeProxyJson(patched);
4433
4429
  return;
4434
4430
  }
4435
4431
  if (isJsonRpcMethod(requestObj, "tools/list")) {
@@ -4445,10 +4441,12 @@ async function runProxy(flags) {
4445
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`,
4446
4442
  );
4447
4443
  } catch { /* best-effort */ }
4448
- // Probe client workspace roots via MCP roots/list protocol.
4449
- // Use setImmediate so the initialize response is flushed to stdout first;
4450
- // the client must see the response before it can handle server-initiated requests.
4451
- setImmediate(() => sendRootsListProbe());
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
+ }
4452
4450
  } else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "workitem.list") {
4453
4451
  patched = await appendWorkitemListHints(patched, args, toolArgs, token, workspaceSignalTrusted);
4454
4452
  } else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "ctxpack.ensure") {
@@ -4463,21 +4461,17 @@ async function runProxy(flags) {
4463
4461
  if (autoSyncSummary) {
4464
4462
  patched = appendAutoCtxpackSyncHint(patched, autoSyncSummary);
4465
4463
  }
4466
- process.stdout.write(`${JSON.stringify(patched)}\n`);
4464
+ writeProxyJson(patched);
4467
4465
  return;
4468
4466
  }
4469
- process.stdout.write(`${responseText}\n`);
4467
+ writeProxyMessage(responseText);
4470
4468
  return;
4471
4469
  }
4472
4470
  if (Object.prototype.hasOwnProperty.call(requestObj, "id")) {
4473
- process.stdout.write(
4474
- `${JSON.stringify(jsonRpcError(requestObj, -32001, "empty gateway response"))}\n`,
4475
- );
4471
+ writeProxyJson(jsonRpcError(requestObj, -32001, "empty gateway response"));
4476
4472
  }
4477
4473
  } catch (err) {
4478
- process.stdout.write(
4479
- `${JSON.stringify(jsonRpcError(requestObj, -32001, String(err?.message || err)))}\n`,
4480
- );
4474
+ writeProxyJson(jsonRpcError(requestObj, -32001, String(err?.message || err)));
4481
4475
  }
4482
4476
  };
4483
4477
 
@@ -4576,6 +4570,7 @@ async function runProxy(flags) {
4576
4570
  if (pendingInputBuffer.length === 0) break;
4577
4571
 
4578
4572
  if (startsWithContentLengthHeader(pendingInputBuffer)) {
4573
+ stdoutEnvelopeMode = "framed";
4579
4574
  const framed = extractFramedMessage(pendingInputBuffer);
4580
4575
  if (!framed) break;
4581
4576
  pendingInputBuffer = framed.remaining;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.35",
3
+ "version": "0.2.36",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [