metheus-governance-mcp-cli 0.2.3 → 0.2.5

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 +107 -14
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -26,12 +26,12 @@ function printUsage() {
26
26
  "Metheus Governance MCP CLI",
27
27
  "",
28
28
  "Usage:",
29
- " metheus-governance-mcp [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--flow <auto|device|callback|manual>]",
29
+ " metheus-governance-mcp [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--flow <auto|device|callback|manual>]",
30
30
  " metheus-governance-mcp init [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--flow <auto|device|callback|manual>]",
31
- " metheus-governance-mcp setup [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--name <server_name>]",
31
+ " metheus-governance-mcp setup [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--name <server_name>]",
32
32
  " metheus-governance-mcp doctor [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--timeout-seconds <n>]",
33
- " metheus-governance-mcp proxy [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--include-drafts <true|false>] [--timeout-seconds <n>]",
34
- " metheus-governance-mcp ctxpack pull [--project-id <uuid>] [--base-url <url>] [--timeout-seconds <n>]",
33
+ " metheus-governance-mcp proxy [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--include-drafts <true|false>] [--timeout-seconds <n>]",
34
+ " metheus-governance-mcp ctxpack pull [--project-id <uuid>] [--base-url <url>] [--workspace-dir <path>] [--timeout-seconds <n>]",
35
35
  " metheus-governance-mcp auth status",
36
36
  " metheus-governance-mcp 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>]",
37
37
  " metheus-governance-mcp auth set --token <jwt> [--refresh-token <token>] [--base-url <url>]",
@@ -101,7 +101,15 @@ function authStoreFilePath() {
101
101
  return path.join(home, AUTH_STORE_RELATIVE_PATH);
102
102
  }
103
103
 
104
- function ctxpackCacheRootDir() {
104
+ function resolveWorkspaceDir(rawPath) {
105
+ const input = String(rawPath || "").trim();
106
+ if (input) {
107
+ return path.resolve(input);
108
+ }
109
+ return path.resolve(process.cwd());
110
+ }
111
+
112
+ function homeCtxpackCacheRootDir() {
105
113
  const home = String(process.env.USERPROFILE || process.env.HOME || "").trim();
106
114
  if (!home) {
107
115
  return path.resolve(process.cwd(), CTXPACK_CACHE_RELATIVE_DIR);
@@ -109,6 +117,14 @@ function ctxpackCacheRootDir() {
109
117
  return path.join(home, CTXPACK_CACHE_RELATIVE_DIR);
110
118
  }
111
119
 
120
+ function ctxpackCacheRootDir(workspaceDir) {
121
+ const root = resolveWorkspaceDir(workspaceDir);
122
+ if (!root) {
123
+ return homeCtxpackCacheRootDir();
124
+ }
125
+ return path.join(root, CTXPACK_CACHE_RELATIVE_DIR);
126
+ }
127
+
112
128
  function ensureParentDir(filePath) {
113
129
  const parent = path.dirname(filePath);
114
130
  fs.mkdirSync(parent, { recursive: true });
@@ -1241,6 +1257,7 @@ async function resolveAccessTokenForCommand(baseURL, timeoutSeconds) {
1241
1257
  async function runCtxpackPull(flags) {
1242
1258
  const workspaceMeta = loadWorkspaceMeta(process.cwd());
1243
1259
  const projectID = String(flags["project-id"] || workspaceMeta.project_id || "").trim();
1260
+ const workspaceDir = resolveWorkspaceDir(flags["workspace-dir"] || process.cwd());
1244
1261
  if (!projectID) {
1245
1262
  process.stderr.write("Missing project_id. Use --project-id <uuid> or run inside a synced ctxpack workspace.\n");
1246
1263
  process.exitCode = 1;
@@ -1271,6 +1288,7 @@ async function runCtxpackPull(flags) {
1271
1288
  timeoutSeconds,
1272
1289
  includeCtxpack: true,
1273
1290
  syncCtxpackLocal: true,
1291
+ workspaceDir,
1274
1292
  });
1275
1293
  process.stdout.write(`${buildProjectSummaryText(summary)}\n`);
1276
1294
 
@@ -1457,6 +1475,7 @@ async function runDoctor(flags) {
1457
1475
  timeoutSeconds,
1458
1476
  includeCtxpack: true,
1459
1477
  syncCtxpackLocal: true,
1478
+ workspaceDir: context.workspaceDir,
1460
1479
  });
1461
1480
  if (String(summary.access || "") !== "granted") {
1462
1481
  addDoctorCheck(
@@ -1832,19 +1851,22 @@ function hasAllCtxpackFiles(baseDir, files) {
1832
1851
  return true;
1833
1852
  }
1834
1853
 
1835
- function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack }) {
1854
+ function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack, workspaceDir }) {
1836
1855
  const ctxpackID = String(ctxpack?.ctxpack_id || "").trim();
1837
1856
  const version = String(ctxpack?.version || "").trim();
1838
1857
  const status = String(ctxpack?.status || "").trim() || "draft";
1839
1858
  const files = normalizeCtxpackFiles(ctxpack?.files);
1840
- const cacheDir = path.join(ctxpackCacheRootDir(), projectID);
1859
+ const resolvedWorkspaceDir = resolveWorkspaceDir(workspaceDir);
1860
+ const cacheDir = path.join(ctxpackCacheRootDir(resolvedWorkspaceDir), projectID);
1841
1861
  const metaPath = path.join(cacheDir, CTXPACK_META_FILENAME);
1862
+ const workspaceMetaPath = path.join(resolvedWorkspaceDir, CTXPACK_META_FILENAME);
1842
1863
 
1843
1864
  if (!ctxpackID || !version || files.length === 0) {
1844
1865
  return {
1845
1866
  sync_status: "not_available",
1846
1867
  sync_message: "Ctxpack is missing or has no files on server.",
1847
1868
  local_path: cacheDir,
1869
+ workspace_path: resolvedWorkspaceDir,
1848
1870
  local_file_count: 0,
1849
1871
  };
1850
1872
  }
@@ -1860,6 +1882,7 @@ function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack }) {
1860
1882
  sync_status: "current",
1861
1883
  sync_message: "Local ctxpack cache is up to date.",
1862
1884
  local_path: cacheDir,
1885
+ workspace_path: resolvedWorkspaceDir,
1863
1886
  local_file_count: files.length,
1864
1887
  };
1865
1888
  }
@@ -1885,6 +1908,7 @@ function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack }) {
1885
1908
  synced_at: new Date().toISOString(),
1886
1909
  };
1887
1910
  fs.writeFileSync(metaPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
1911
+ fs.writeFileSync(workspaceMetaPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
1888
1912
 
1889
1913
  return {
1890
1914
  sync_status: previousMeta ? "updated" : "downloaded",
@@ -1892,6 +1916,7 @@ function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack }) {
1892
1916
  ? "Ctxpack updated to latest server version."
1893
1917
  : "Ctxpack downloaded to local cache.",
1894
1918
  local_path: cacheDir,
1919
+ workspace_path: resolvedWorkspaceDir,
1895
1920
  local_file_count: files.length,
1896
1921
  };
1897
1922
  } catch (err) {
@@ -1899,6 +1924,7 @@ function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack }) {
1899
1924
  sync_status: "error",
1900
1925
  sync_message: String(err?.message || err),
1901
1926
  local_path: cacheDir,
1927
+ workspace_path: resolvedWorkspaceDir,
1902
1928
  local_file_count: 0,
1903
1929
  };
1904
1930
  }
@@ -1911,6 +1937,7 @@ async function loadProjectSummaryForTool({
1911
1937
  timeoutSeconds,
1912
1938
  includeCtxpack,
1913
1939
  syncCtxpackLocal,
1940
+ workspaceDir,
1914
1941
  }) {
1915
1942
  const encodedProjectID = encodeURIComponent(projectID);
1916
1943
  let projectRaw = null;
@@ -1968,6 +1995,7 @@ async function loadProjectSummaryForTool({
1968
1995
  siteBaseURL,
1969
1996
  projectID,
1970
1997
  ctxpack,
1998
+ workspaceDir,
1971
1999
  });
1972
2000
  ctxpackSyncStatus = syncResult.sync_status || "error";
1973
2001
  ctxpackSyncMessage = String(syncResult.sync_message || "").trim();
@@ -2008,6 +2036,7 @@ async function loadProjectSummaryForTool({
2008
2036
  ctxpack_sync_message: ctxpackSyncMessage,
2009
2037
  ctxpack_local_path: ctxpackLocalPath,
2010
2038
  ctxpack_local_file_count: ctxpackLocalFileCount,
2039
+ ctxpack_workspace_path: String(workspaceDir || "").trim(),
2011
2040
  agenda,
2012
2041
  agenda_source: agendaSource,
2013
2042
  };
@@ -2149,6 +2178,7 @@ async function appendWorkitemListHints(responseObj, args, toolArgs, token) {
2149
2178
  timeoutSeconds: args.timeoutSeconds,
2150
2179
  includeCtxpack: isEmptyBody,
2151
2180
  syncCtxpackLocal: isEmptyBody,
2181
+ workspaceDir: args.workspaceDir,
2152
2182
  });
2153
2183
  if (String(summary.access || "") === "granted") {
2154
2184
  nextLines.push("Project context:");
@@ -2186,12 +2216,66 @@ async function appendWorkitemListHints(responseObj, args, toolArgs, token) {
2186
2216
  return responseObj;
2187
2217
  }
2188
2218
 
2219
+ function appendCtxpackEnsureSyncHints(responseObj, args, toolArgs) {
2220
+ const result = safeObject(responseObj.result);
2221
+ const content = ensureArray(result.content);
2222
+ if (!content.length) return responseObj;
2223
+
2224
+ const first = safeObject(content[0]);
2225
+ if (String(first.type || "") !== "text") return responseObj;
2226
+ const text = String(first.text || "").trim();
2227
+ if (!text) return responseObj;
2228
+
2229
+ const envelope = parseToolEnvelopeFromRPCResult(result);
2230
+ if (!envelope) return responseObj;
2231
+ if (String(envelope.tool || "").trim() !== "ctxpack.ensure") return responseObj;
2232
+ if (!Boolean(envelope.ok) || Number(envelope.status || 0) !== 200) return responseObj;
2233
+
2234
+ const body = safeObject(envelope.body);
2235
+ if (!Object.keys(body).length) return responseObj;
2236
+
2237
+ const projectID = String(
2238
+ toolArgs?.project_id || toolArgs?.projectID || envelope.project_id || args.projectID || "",
2239
+ ).trim();
2240
+ if (!projectID || !isUUID(projectID)) return responseObj;
2241
+
2242
+ const syncResult = syncCtxpackToLocalCache({
2243
+ siteBaseURL: normalizeSiteBaseURL(args.baseURL),
2244
+ projectID,
2245
+ ctxpack: body,
2246
+ workspaceDir: args.workspaceDir,
2247
+ });
2248
+
2249
+ const lines = [
2250
+ "",
2251
+ `ctxpack_local_sync_status: ${String(syncResult.sync_status || "error")}`,
2252
+ ];
2253
+ if (syncResult.sync_message) {
2254
+ lines.push(`ctxpack_local_sync_message: ${String(syncResult.sync_message)}`);
2255
+ }
2256
+ if (syncResult.local_path) {
2257
+ lines.push(`ctxpack_local_path: ${String(syncResult.local_path)}`);
2258
+ }
2259
+ if (syncResult.workspace_path) {
2260
+ lines.push(`ctxpack_workspace_path: ${String(syncResult.workspace_path)}`);
2261
+ }
2262
+
2263
+ content[0] = {
2264
+ ...first,
2265
+ text: `${text}\n${lines.join("\n")}`,
2266
+ };
2267
+ result.content = content;
2268
+ responseObj.result = result;
2269
+ return responseObj;
2270
+ }
2271
+
2189
2272
  async function runProxy(flags) {
2190
2273
  const workspaceMeta = loadWorkspaceMeta(process.cwd());
2191
2274
  const args = {
2192
2275
  baseURL: flags["base-url"] || DEFAULT_BASE_URL,
2193
2276
  projectID: String(flags["project-id"] || workspaceMeta.project_id || "").trim(),
2194
2277
  ctxpackKey: String(flags["ctxpack-key"] || buildCtxpackKeyFromMeta(workspaceMeta) || "").trim(),
2278
+ workspaceDir: resolveWorkspaceDir(flags["workspace-dir"] || process.cwd()),
2195
2279
  includeDrafts: boolFromRaw(flags["include-drafts"], true),
2196
2280
  timeoutSeconds: intFromRaw(flags["timeout-seconds"], 30),
2197
2281
  };
@@ -2289,6 +2373,7 @@ async function runProxy(flags) {
2289
2373
  timeoutSeconds: args.timeoutSeconds,
2290
2374
  includeCtxpack,
2291
2375
  syncCtxpackLocal,
2376
+ workspaceDir: args.workspaceDir,
2292
2377
  });
2293
2378
  const text = buildProjectSummaryText(summary);
2294
2379
  process.stdout.write(
@@ -2324,6 +2409,8 @@ async function runProxy(flags) {
2324
2409
  patched = appendProjectHintToInitialize(patched, args);
2325
2410
  } else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "workitem.list") {
2326
2411
  patched = await appendWorkitemListHints(patched, args, toolArgs, token);
2412
+ } else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "ctxpack.ensure") {
2413
+ patched = appendCtxpackEnsureSyncHints(patched, args, toolArgs);
2327
2414
  }
2328
2415
  process.stdout.write(`${JSON.stringify(patched)}\n`);
2329
2416
  return;
@@ -2408,11 +2495,13 @@ function resolveSetupContext(flags) {
2408
2495
  const projectID = String(flags["project-id"] || workspaceMeta.project_id || "").trim();
2409
2496
  const ctxpackKey = String(flags["ctxpack-key"] || buildCtxpackKeyFromMeta(workspaceMeta) || "").trim();
2410
2497
  const baseURL = String(flags["base-url"] || DEFAULT_SITE_URL).trim().replace(/\/+$/, "");
2498
+ const workspaceDir = resolveWorkspaceDir(flags["workspace-dir"] || process.cwd());
2411
2499
  const serverName = String(flags.name || DEFAULT_SERVER_NAME).trim() || DEFAULT_SERVER_NAME;
2412
2500
  const proxyArgs = ["--base-url", `${baseURL}/governance/mcp`];
2501
+ proxyArgs.push("--workspace-dir", workspaceDir);
2413
2502
  if (projectID) proxyArgs.push("--project-id", projectID);
2414
2503
  if (ctxpackKey) proxyArgs.push("--ctxpack-key", ctxpackKey);
2415
- return { projectID, ctxpackKey, baseURL, serverName, proxyArgs };
2504
+ return { projectID, ctxpackKey, baseURL, workspaceDir, serverName, proxyArgs };
2416
2505
  }
2417
2506
 
2418
2507
  function runSetupInternal(flags, options = {}) {
@@ -2424,20 +2513,24 @@ function runSetupInternal(flags, options = {}) {
2424
2513
  for (const cliBin of clients) {
2425
2514
  if (!commandExists(cliBin)) continue;
2426
2515
  const alreadyRegistered = isRegistered(cliBin, context.serverName);
2427
- if (ensureOnly && alreadyRegistered) {
2428
- results.push({ cliBin, ok: true, action: "already-registered" });
2429
- continue;
2430
- }
2431
- if (!ensureOnly) {
2516
+ if (!ensureOnly || alreadyRegistered) {
2432
2517
  runRemove(cliBin, context.serverName);
2433
2518
  }
2434
2519
  const ok = tryRegister(cliBin, context.serverName, context.proxyArgs);
2435
- results.push({ cliBin, ok, action: alreadyRegistered ? "re-registered" : "registered" });
2520
+ const action = ensureOnly
2521
+ ? alreadyRegistered
2522
+ ? "updated"
2523
+ : "registered"
2524
+ : alreadyRegistered
2525
+ ? "re-registered"
2526
+ : "registered";
2527
+ results.push({ cliBin, ok, action });
2436
2528
  }
2437
2529
 
2438
2530
  process.stdout.write(ensureOnly ? "\nEnsure complete.\n" : "\nInstall complete.\n");
2439
2531
  process.stdout.write(`Server: ${context.serverName}\n`);
2440
2532
  process.stdout.write(`Gateway: ${context.baseURL}/governance/mcp\n`);
2533
+ process.stdout.write(`Workspace: ${context.workspaceDir}\n`);
2441
2534
  process.stdout.write(`Project: ${context.projectID || "auto-detect from .metheus_ctxpack_sync.json"}\n`);
2442
2535
  if (context.ctxpackKey) {
2443
2536
  process.stdout.write(`Ctxpack: ${context.ctxpackKey}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [