metheus-governance-mcp-cli 0.2.15 → 0.2.16

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 (3) hide show
  1. package/README.md +23 -1
  2. package/cli.mjs +72 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -38,6 +38,25 @@ metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctx
38
38
 
39
39
  `project-id` can be omitted if your current folder (or parent) has `.metheus_ctxpack_sync.json`.
40
40
 
41
+ Recommended for Codex/Claude multi-workspace sessions:
42
+
43
+ ```bash
44
+ metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com --workspace-dir auto
45
+ ```
46
+
47
+ Guardrail note:
48
+ - By default, CLI blocks reading/writing ctxpack sync metadata when workspace root resolves to the home directory.
49
+ - Override only when intentional: `METHEUS_ALLOW_HOME_WORKSPACE=1`.
50
+
51
+ ## Project ID behavior (verified)
52
+
53
+ - `setup --project-id <A>` sets default proxy scope to project `A`.
54
+ - Proxy forwards `project_id` to gateway query, and gateway sets `MCP_PROJECT_ID` for MCP runtime.
55
+ - If a tool call includes `project_id` in its arguments, that value overrides setup-time default scope.
56
+ - If tool arguments omit `project_id`, server falls back to `MCP_PROJECT_ID`.
57
+ - If both are missing, the call fails with `project_id is required`.
58
+ - For single-project sessions, keep one pinned `--project-id` and avoid sending a different `project_id` unless intentional.
59
+
41
60
  ## Doctor
42
61
 
43
62
  ```bash
@@ -74,7 +93,8 @@ These tools accept `project_id` and return:
74
93
  - missing local cache -> download
75
94
  - same version -> keep current
76
95
  - newer server version -> update local cache
77
- - workspace path -> follows active client workspace/root when provided by Codex/Claude
96
+ - workspace path -> pinned to setup/proxy working directory by default
97
+ - use `--workspace-dir auto` to follow active client workspace/root dynamically
78
98
 
79
99
  Ctxpack merge safety flow:
80
100
  - call `ctxpack.merge.brief` first
@@ -200,6 +220,8 @@ Actual publish:
200
220
  npm run publish:public
201
221
  ```
202
222
 
223
+ If npm returns `You cannot publish over the previously published versions`, bump `package.json` version and retry.
224
+
203
225
  If npm account uses 2FA, pass OTP:
204
226
 
205
227
  ```bash
package/cli.mjs CHANGED
@@ -25,6 +25,7 @@ const SELF_UPDATE_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
25
25
  const SELF_UPDATE_TIMEOUT_SECONDS = 6;
26
26
  const SELF_UPDATE_ENV_KEY = "METHEUS_CLI_AUTO_UPDATE";
27
27
  const SELF_UPDATE_GUARD_ENV_KEY = "METHEUS_CLI_SELF_UPDATE_ATTEMPTED";
28
+ const ALLOW_HOME_WORKSPACE_ENV_KEY = "METHEUS_ALLOW_HOME_WORKSPACE";
28
29
  const AUTO_CTXPACK_SYNC_INTERVAL_MS = 60 * 1000;
29
30
  const autoCtxpackSyncTracker = new Map();
30
31
 
@@ -50,6 +51,7 @@ function printUsage() {
50
51
  "Environment:",
51
52
  " METHEUS_TOKEN or MCP_AUTH_TOKEN is used first for proxy requests.",
52
53
  ` ${SELF_UPDATE_ENV_KEY}=0 to disable startup auto-update check.`,
54
+ ` ${ALLOW_HOME_WORKSPACE_ENV_KEY}=1 to allow using home directory as workspace root (disabled by default).`,
53
55
  " If env is missing, stored token file is used:",
54
56
  ` ${AUTH_STORE_RELATIVE_PATH}`,
55
57
  "",
@@ -423,6 +425,29 @@ function resolveWorkspaceDirForRequest(defaultWorkspaceDir, requestObj, toolArgs
423
425
  return resolveWorkspaceDir(process.cwd());
424
426
  }
425
427
 
428
+ function normalizedPathForCompare(rawPath) {
429
+ const candidate = sanitizeWorkspaceCandidate(rawPath);
430
+ if (!candidate) return "";
431
+ const normalized = path.normalize(candidate);
432
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
433
+ }
434
+
435
+ function homeWorkspaceRoot() {
436
+ return sanitizeWorkspaceCandidate(firstNonEmptyString([process.env.USERPROFILE, process.env.HOME]));
437
+ }
438
+
439
+ function isHomeWorkspaceRoot(workspaceDir) {
440
+ const home = normalizedPathForCompare(homeWorkspaceRoot());
441
+ if (!home) return false;
442
+ const current = normalizedPathForCompare(workspaceDir);
443
+ if (!current) return false;
444
+ return current === home;
445
+ }
446
+
447
+ function allowHomeWorkspaceRoot() {
448
+ return boolFromRaw(process.env[ALLOW_HOME_WORKSPACE_ENV_KEY], false);
449
+ }
450
+
426
451
  function resolveProjectIDForRequest({
427
452
  toolArgs,
428
453
  args,
@@ -437,8 +462,8 @@ function resolveProjectIDForRequest({
437
462
  toolArgs?.projectID,
438
463
  responseProjectID,
439
464
  envelopeProjectID,
440
- workspaceMeta.project_id,
441
465
  args?.projectID,
466
+ workspaceMeta.project_id,
442
467
  ]);
443
468
  }
444
469
 
@@ -1172,7 +1197,11 @@ function findSyncMetaFile(startDir) {
1172
1197
  }
1173
1198
 
1174
1199
  function loadWorkspaceMeta(startDir) {
1175
- const metaFile = findSyncMetaFile(startDir);
1200
+ const resolvedStart = resolveWorkspaceDir(startDir || process.cwd());
1201
+ if (isHomeWorkspaceRoot(resolvedStart) && !allowHomeWorkspaceRoot()) {
1202
+ return {};
1203
+ }
1204
+ const metaFile = findSyncMetaFile(resolvedStart);
1176
1205
  if (!metaFile) return {};
1177
1206
  try {
1178
1207
  const raw = fs.readFileSync(metaFile, "utf8");
@@ -2418,6 +2447,17 @@ function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack, workspaceDir
2418
2447
  const metaPath = path.join(cacheDir, CTXPACK_META_FILENAME);
2419
2448
  const workspaceMetaPath = path.join(resolvedWorkspaceDir, CTXPACK_META_FILENAME);
2420
2449
 
2450
+ if (isHomeWorkspaceRoot(resolvedWorkspaceDir) && !allowHomeWorkspaceRoot()) {
2451
+ return {
2452
+ sync_status: "guarded",
2453
+ sync_message:
2454
+ "Workspace root resolved to home directory. Guardrail blocked ctxpack local write. Use --workspace-dir <project_path> or --workspace-dir auto.",
2455
+ local_path: cacheDir,
2456
+ workspace_path: resolvedWorkspaceDir,
2457
+ local_file_count: 0,
2458
+ };
2459
+ }
2460
+
2421
2461
  if (!ctxpackID || !version || files.length === 0) {
2422
2462
  return {
2423
2463
  sync_status: "not_available",
@@ -3622,13 +3662,20 @@ async function appendCtxpackConflictHintToErrorResponse(responseObj, args, toolN
3622
3662
  }
3623
3663
 
3624
3664
  async function runProxy(flags) {
3625
- const explicitWorkspaceDirRaw = String(flags["workspace-dir"] || "").trim();
3626
- const explicitWorkspaceDir = explicitWorkspaceDirRaw ? resolveWorkspaceDir(explicitWorkspaceDirRaw) : "";
3665
+ const workspaceDirRaw = String(flags["workspace-dir"] || "").trim();
3666
+ const hasWorkspaceDirFlag = Object.prototype.hasOwnProperty.call(flags, "workspace-dir");
3667
+ const workspaceAutoMode = hasWorkspaceDirFlag && workspaceDirRaw.length > 0 && isAutoWorkspaceMode(workspaceDirRaw);
3668
+ const explicitPinnedWorkspace = hasWorkspaceDirFlag && !workspaceAutoMode;
3669
+ // Keep pinned workspace only when user explicitly set --workspace-dir.
3670
+ // If not explicitly set, resolve per-request from client metadata/env first.
3671
+ const pinnedWorkspaceDir = explicitPinnedWorkspace ? resolveWorkspaceDir(workspaceDirRaw || process.cwd()) : "";
3627
3672
  const args = {
3628
3673
  baseURL: flags["base-url"] || DEFAULT_BASE_URL,
3629
3674
  projectID: String(flags["project-id"] || "").trim(),
3630
3675
  ctxpackKey: String(flags["ctxpack-key"] || "").trim(),
3631
- workspaceDir: explicitWorkspaceDir,
3676
+ workspaceDir: pinnedWorkspaceDir,
3677
+ workspaceAutoMode,
3678
+ explicitPinnedWorkspace,
3632
3679
  includeDrafts: boolFromRaw(flags["include-drafts"], true),
3633
3680
  autoPullOnConflict: boolFromRaw(flags["auto-pull-on-conflict"], true),
3634
3681
  timeoutSeconds: intFromRaw(flags["timeout-seconds"], 30),
@@ -3694,18 +3741,22 @@ async function runProxy(flags) {
3694
3741
  }
3695
3742
 
3696
3743
  const { name: toolName, args: toolArgs } = extractToolCall(requestObj);
3697
- const requestWorkspaceCandidate = extractWorkspaceCandidateFromRequest(requestObj, toolArgs);
3698
- const envWorkspaceCandidate = extractWorkspaceCandidateFromEnv();
3699
- if (requestWorkspaceCandidate) {
3700
- sessionWorkspaceDir = requestWorkspaceCandidate;
3701
- } else if (envWorkspaceCandidate) {
3702
- sessionWorkspaceDir = envWorkspaceCandidate;
3744
+ if (!args.explicitPinnedWorkspace) {
3745
+ const requestWorkspaceCandidate = extractWorkspaceCandidateFromRequest(requestObj, toolArgs);
3746
+ const envWorkspaceCandidate = extractWorkspaceCandidateFromEnv();
3747
+ if (requestWorkspaceCandidate) {
3748
+ sessionWorkspaceDir = requestWorkspaceCandidate;
3749
+ } else if (envWorkspaceCandidate) {
3750
+ sessionWorkspaceDir = envWorkspaceCandidate;
3751
+ }
3703
3752
  }
3704
- const requestWorkspaceDir = resolveWorkspaceDirForRequest(
3705
- firstNonEmptyString([sessionWorkspaceDir, args.workspaceDir, process.cwd()]),
3706
- requestObj,
3707
- toolArgs,
3708
- );
3753
+ const requestWorkspaceDir = args.explicitPinnedWorkspace
3754
+ ? resolveWorkspaceDir(args.workspaceDir || process.cwd())
3755
+ : resolveWorkspaceDirForRequest(
3756
+ firstNonEmptyString([sessionWorkspaceDir, args.workspaceDir, process.cwd()]),
3757
+ requestObj,
3758
+ toolArgs,
3759
+ );
3709
3760
  let autoSyncSummary = null;
3710
3761
  if (isJsonRpcMethod(requestObj, "tools/call")) {
3711
3762
  autoSyncSummary = await maybeAutoSyncCtxpackForCall({
@@ -4013,16 +4064,15 @@ function resolveSetupContext(flags) {
4013
4064
  const workspaceDirRaw = String(flags["workspace-dir"] || "").trim();
4014
4065
  const hasWorkspaceDirFlag = workspaceDirRaw.length > 0;
4015
4066
  const workspaceAutoMode = hasWorkspaceDirFlag && isAutoWorkspaceMode(workspaceDirRaw);
4016
- // Default: auto workspace detection at runtime.
4017
- // Pin only when user explicitly passes --workspace-dir <path>.
4018
- const shouldPinWorkspaceDir = hasWorkspaceDirFlag && !workspaceAutoMode;
4067
+ // Default: pin to current working directory.
4068
+ // Use --workspace-dir auto for dynamic runtime workspace detection.
4069
+ const shouldPinWorkspaceDir = !workspaceAutoMode;
4019
4070
  const workspaceDir = resolveWorkspaceDir(
4020
- shouldPinWorkspaceDir ? workspaceDirRaw : process.cwd(),
4071
+ shouldPinWorkspaceDir ? workspaceDirRaw || process.cwd() : process.cwd(),
4021
4072
  );
4022
4073
  const serverName = String(flags.name || DEFAULT_SERVER_NAME).trim() || DEFAULT_SERVER_NAME;
4023
4074
  const proxyArgs = ["--base-url", `${baseURL}/governance/mcp`];
4024
- // Runtime auto-detection is default.
4025
- // Use --workspace-dir <path> when deterministic pinning is preferred.
4075
+ // Pin workspace by default to avoid spreading ctxpack cache across mixed client roots.
4026
4076
  if (shouldPinWorkspaceDir) {
4027
4077
  proxyArgs.push("--workspace-dir", workspaceDir);
4028
4078
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [