metheus-governance-mcp-cli 0.2.37 → 0.2.38
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 +28 -0
- package/cli.mjs +138 -3
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -103,6 +103,11 @@ Antigravity note:
|
|
|
103
103
|
Cursor note:
|
|
104
104
|
- this CLI manages MCP registration via Cursor global MCP config (`~/.cursor/mcp.json`).
|
|
105
105
|
|
|
106
|
+
Tool naming compatibility:
|
|
107
|
+
- Claude/Codex/Gemini keep canonical MCP tool names (`project.summary`, `ctxpack.merge.brief`, ...).
|
|
108
|
+
- Cursor/Antigravity sessions may receive safe aliases (`project_summary`, `ctxpack_merge_brief`, ...).
|
|
109
|
+
- Proxy maps alias calls back to canonical names automatically.
|
|
110
|
+
|
|
106
111
|
Local bootstrap tools exposed by proxy:
|
|
107
112
|
|
|
108
113
|
- `project.summary`
|
|
@@ -239,6 +244,7 @@ npm publish --access public
|
|
|
239
244
|
|
|
240
245
|
```bash
|
|
241
246
|
npm run check
|
|
247
|
+
npm run test:compat
|
|
242
248
|
npm run publish:dry
|
|
243
249
|
```
|
|
244
250
|
|
|
@@ -255,3 +261,25 @@ If npm account uses 2FA, pass OTP:
|
|
|
255
261
|
```bash
|
|
256
262
|
node release.mjs --otp <6-digit-code>
|
|
257
263
|
```
|
|
264
|
+
|
|
265
|
+
## Regression and smoke checks
|
|
266
|
+
|
|
267
|
+
Local compatibility selftest (no network):
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
npm run test:compat
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Proxy smoke test (initialize -> tools/list -> project summary -> ctxpack local sync):
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
npm run smoke:proxy -- --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --workspace-dir <workspace_path>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Optional:
|
|
280
|
+
- `--client cursor-vscode` (default) or `--client antigravity`
|
|
281
|
+
- `--base-url https://metheus.gesiaplatform.com/governance/mcp`
|
|
282
|
+
|
|
283
|
+
Workspace fallback guardrail:
|
|
284
|
+
- If `METHEUS_WORKSPACE_DIR` is accidentally set to a non-existing boolean suffix path like `C:\code_test\true`,
|
|
285
|
+
proxy now recovers to the parent workspace (`C:\code_test`) instead of pinning to the bad path.
|
package/cli.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import process from "node:process";
|
|
6
7
|
import readline from "node:readline";
|
|
@@ -42,6 +43,7 @@ function printUsage() {
|
|
|
42
43
|
` ${cmd} setup [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path|auto>] [--workspace-fallback-dir <path>] [--name <server_name>]`,
|
|
43
44
|
` ${cmd} doctor [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--timeout-seconds <n>]`,
|
|
44
45
|
` ${cmd} proxy [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path|auto>] [--include-drafts <true|false>] [--auto-pull-on-conflict <true|false>] [--timeout-seconds <n>]`,
|
|
46
|
+
` ${cmd} selftest [--json <true|false>]`,
|
|
45
47
|
` ${cmd} ctxpack pull [--project-id <uuid>] [--base-url <url>] [--workspace-dir <path|auto>] [--paths <csv>] [--timeout-seconds <n>]`,
|
|
46
48
|
` ${cmd} auth status`,
|
|
47
49
|
` ${cmd} auth login [--base-url <url>] [--flow <auto|device|callback|manual>] [--keycloak-url <url>] [--realm <name>] [--client-id <id>] [--open-browser <true|false>] [--callback-port <n>] [--timeout-seconds <n>] [--manual <true|false>]`,
|
|
@@ -604,7 +606,7 @@ function extractWeakWorkspaceCandidateFromRequest(requestObj) {
|
|
|
604
606
|
}
|
|
605
607
|
|
|
606
608
|
function extractStrongWorkspaceCandidateFromEnv() {
|
|
607
|
-
const
|
|
609
|
+
const rawCandidates = [
|
|
608
610
|
process.env.METHEUS_WORKSPACE_DIR,
|
|
609
611
|
process.env.METHEUS_WORKSPACE_URI,
|
|
610
612
|
process.env.CODEX_WORKSPACE_DIR,
|
|
@@ -612,8 +614,12 @@ function extractStrongWorkspaceCandidateFromEnv() {
|
|
|
612
614
|
process.env.CLAUDE_WORKSPACE_DIR,
|
|
613
615
|
process.env.CLAUDE_WORKSPACE_URI,
|
|
614
616
|
process.env.CLAUDE_PROJECT_DIR,
|
|
615
|
-
]
|
|
616
|
-
|
|
617
|
+
];
|
|
618
|
+
for (const rawCandidate of rawCandidates) {
|
|
619
|
+
const normalized = sanitizeWorkspaceFallbackEnvCandidate(rawCandidate);
|
|
620
|
+
if (normalized) return normalized;
|
|
621
|
+
}
|
|
622
|
+
return "";
|
|
617
623
|
}
|
|
618
624
|
|
|
619
625
|
function extractWorkspaceCandidateFromEnv() {
|
|
@@ -635,6 +641,35 @@ function extractWeakWorkspaceCandidateFromEnv() {
|
|
|
635
641
|
return sanitizeWorkspaceCandidate(rawCandidate);
|
|
636
642
|
}
|
|
637
643
|
|
|
644
|
+
function sanitizeWorkspaceFallbackEnvCandidate(rawCandidate) {
|
|
645
|
+
const input = String(rawCandidate || "").trim();
|
|
646
|
+
if (!input) return "";
|
|
647
|
+
|
|
648
|
+
const direct = sanitizeWorkspaceCandidate(input);
|
|
649
|
+
if (direct && fs.existsSync(direct)) {
|
|
650
|
+
return direct;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const fileCandidate = fileURIToLocalPath(input);
|
|
654
|
+
const candidate = firstNonEmptyString([fileCandidate, input]);
|
|
655
|
+
if (!candidate) return direct;
|
|
656
|
+
|
|
657
|
+
const resolved = resolveWorkspaceDir(candidate);
|
|
658
|
+
if (!resolved || isEditorInstallDirectory(resolved)) {
|
|
659
|
+
return direct;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const leaf = path.basename(resolved).trim().toLowerCase();
|
|
663
|
+
if ((leaf === "true" || leaf === "false") && !fs.existsSync(resolved)) {
|
|
664
|
+
const parent = sanitizeWorkspaceCandidate(path.dirname(resolved));
|
|
665
|
+
if (parent && fs.existsSync(parent)) {
|
|
666
|
+
return parent;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return direct;
|
|
671
|
+
}
|
|
672
|
+
|
|
638
673
|
function resolveWorkspaceDirForRequest(defaultWorkspaceDir, requestObj, toolArgs) {
|
|
639
674
|
const strongRequestCandidate = extractStrongWorkspaceCandidateFromRequest(requestObj, toolArgs);
|
|
640
675
|
const strongEnvCandidate = extractStrongWorkspaceCandidateFromEnv();
|
|
@@ -5099,6 +5134,102 @@ function runSetup(flags) {
|
|
|
5099
5134
|
runSetupInternal(flags, { ensureOnly: false });
|
|
5100
5135
|
}
|
|
5101
5136
|
|
|
5137
|
+
function runSelftest(flags = {}) {
|
|
5138
|
+
const jsonMode = boolFromRaw(flags.json, false);
|
|
5139
|
+
const checks = [];
|
|
5140
|
+
const push = (name, ok, detail = "") => checks.push({ name, ok: Boolean(ok), detail: String(detail || "") });
|
|
5141
|
+
|
|
5142
|
+
const aliasMaps = buildToolAliasMaps([
|
|
5143
|
+
{ name: "project.summary" },
|
|
5144
|
+
{ name: "ctxpack.merge.brief" },
|
|
5145
|
+
{ name: "workitem.list" },
|
|
5146
|
+
]);
|
|
5147
|
+
const summaryAlias = String(aliasMaps.canonicalToAlias.get("project.summary") || "");
|
|
5148
|
+
const roundTrip = rewriteAliasedToolCallToCanonical(
|
|
5149
|
+
{
|
|
5150
|
+
jsonrpc: "2.0",
|
|
5151
|
+
id: 1,
|
|
5152
|
+
method: "tools/call",
|
|
5153
|
+
params: {
|
|
5154
|
+
name: summaryAlias,
|
|
5155
|
+
arguments: {},
|
|
5156
|
+
},
|
|
5157
|
+
},
|
|
5158
|
+
aliasMaps.aliasToCanonical,
|
|
5159
|
+
);
|
|
5160
|
+
const roundTripOk = summaryAlias === "project_summary" && String(roundTrip?.params?.name || "") === "project.summary";
|
|
5161
|
+
push(
|
|
5162
|
+
"alias_roundtrip_project_summary",
|
|
5163
|
+
roundTripOk,
|
|
5164
|
+
`alias=${summaryAlias || "(none)"} canonical=${String(roundTrip?.params?.name || "(none)")}`,
|
|
5165
|
+
);
|
|
5166
|
+
|
|
5167
|
+
const collisionMaps = buildToolAliasMaps([{ name: "a.b" }, { name: "a_b" }]);
|
|
5168
|
+
const aliasDot = String(collisionMaps.canonicalToAlias.get("a.b") || "");
|
|
5169
|
+
const aliasUnderscore = String(collisionMaps.canonicalToAlias.get("a_b") || "");
|
|
5170
|
+
const collisionOk = aliasDot === "a_b_2" && aliasUnderscore === "";
|
|
5171
|
+
push(
|
|
5172
|
+
"alias_collision_safe_suffix",
|
|
5173
|
+
collisionOk,
|
|
5174
|
+
`a.b->${aliasDot || "(none)"} a_b->${aliasUnderscore || "(none)"}`,
|
|
5175
|
+
);
|
|
5176
|
+
|
|
5177
|
+
let tempRoot = "";
|
|
5178
|
+
try {
|
|
5179
|
+
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-selftest-"));
|
|
5180
|
+
const parent = path.join(tempRoot, "workspace");
|
|
5181
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
5182
|
+
const suspicious = path.join(parent, "true");
|
|
5183
|
+
const recovered = sanitizeWorkspaceFallbackEnvCandidate(suspicious);
|
|
5184
|
+
const recoveredOk = normalizedPathForCompare(recovered) === normalizedPathForCompare(parent);
|
|
5185
|
+
push(
|
|
5186
|
+
"workspace_env_true_suffix_recovery",
|
|
5187
|
+
recoveredOk,
|
|
5188
|
+
`input=${suspicious} recovered=${recovered || "(none)"}`,
|
|
5189
|
+
);
|
|
5190
|
+
|
|
5191
|
+
fs.mkdirSync(suspicious, { recursive: true });
|
|
5192
|
+
const keptExisting = sanitizeWorkspaceFallbackEnvCandidate(suspicious);
|
|
5193
|
+
const keptExistingOk = normalizedPathForCompare(keptExisting) === normalizedPathForCompare(suspicious);
|
|
5194
|
+
push(
|
|
5195
|
+
"workspace_env_existing_true_dir_kept",
|
|
5196
|
+
keptExistingOk,
|
|
5197
|
+
`input=${suspicious} recovered=${keptExisting || "(none)"}`,
|
|
5198
|
+
);
|
|
5199
|
+
} catch (err) {
|
|
5200
|
+
push("workspace_env_guard_test_setup", false, String(err?.message || err));
|
|
5201
|
+
} finally {
|
|
5202
|
+
if (tempRoot) {
|
|
5203
|
+
try {
|
|
5204
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
5205
|
+
} catch {
|
|
5206
|
+
// ignore selftest cleanup error
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
|
|
5211
|
+
const failed = checks.filter((row) => !row.ok);
|
|
5212
|
+
const payload = {
|
|
5213
|
+
ok: failed.length === 0,
|
|
5214
|
+
pass_count: checks.length - failed.length,
|
|
5215
|
+
fail_count: failed.length,
|
|
5216
|
+
checks,
|
|
5217
|
+
};
|
|
5218
|
+
|
|
5219
|
+
if (jsonMode) {
|
|
5220
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
5221
|
+
} else {
|
|
5222
|
+
process.stdout.write(`Selftest: ${payload.ok ? "PASS" : "FAIL"} (${payload.pass_count}/${checks.length})\n`);
|
|
5223
|
+
for (const row of checks) {
|
|
5224
|
+
process.stdout.write(`- ${row.ok ? "PASS" : "FAIL"} ${row.name}${row.detail ? ` :: ${row.detail}` : ""}\n`);
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
if (!payload.ok) {
|
|
5229
|
+
process.exitCode = 1;
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5102
5233
|
async function runBootstrap(flags) {
|
|
5103
5234
|
process.stdout.write("Bootstrap start.\n");
|
|
5104
5235
|
let resolved = resolveCurrentAccessToken();
|
|
@@ -5159,6 +5290,10 @@ async function main() {
|
|
|
5159
5290
|
await runDoctor(flags);
|
|
5160
5291
|
return;
|
|
5161
5292
|
}
|
|
5293
|
+
if (command === "selftest") {
|
|
5294
|
+
runSelftest(flags);
|
|
5295
|
+
return;
|
|
5296
|
+
}
|
|
5162
5297
|
if (command === "auth") {
|
|
5163
5298
|
await runAuth(rest);
|
|
5164
5299
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metheus-governance-mcp-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.38",
|
|
4
4
|
"description": "Metheus Governance MCP CLI (setup + stdio proxy)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"check": "node --check cli.mjs && node --check release.mjs",
|
|
14
|
+
"test:compat": "node cli.mjs selftest --json",
|
|
15
|
+
"smoke:proxy": "node scripts/smoke-proxy.mjs",
|
|
14
16
|
"pack:dry": "npm pack --dry-run",
|
|
15
17
|
"publish:dry": "node release.mjs --dry-run",
|
|
16
18
|
"publish:public": "node release.mjs"
|