@cubis/foundry 0.3.40 → 0.3.42

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 (29) hide show
  1. package/README.md +67 -3
  2. package/bin/cubis.js +360 -26
  3. package/mcp/README.md +72 -8
  4. package/mcp/config.json +3 -0
  5. package/mcp/dist/index.js +315 -68
  6. package/mcp/src/config/index.test.ts +1 -0
  7. package/mcp/src/config/schema.ts +5 -0
  8. package/mcp/src/index.ts +40 -9
  9. package/mcp/src/server.ts +66 -10
  10. package/mcp/src/telemetry/tokenBudget.ts +114 -0
  11. package/mcp/src/tools/index.ts +7 -0
  12. package/mcp/src/tools/skillBrowseCategory.ts +22 -5
  13. package/mcp/src/tools/skillBudgetReport.ts +128 -0
  14. package/mcp/src/tools/skillGet.ts +18 -0
  15. package/mcp/src/tools/skillListCategories.ts +19 -6
  16. package/mcp/src/tools/skillSearch.ts +22 -5
  17. package/mcp/src/tools/skillTools.test.ts +61 -9
  18. package/mcp/src/vault/manifest.test.ts +19 -1
  19. package/mcp/src/vault/manifest.ts +12 -1
  20. package/mcp/src/vault/scanner.test.ts +1 -0
  21. package/mcp/src/vault/scanner.ts +1 -0
  22. package/mcp/src/vault/types.ts +6 -0
  23. package/package.json +1 -1
  24. package/workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md +28 -0
  25. package/workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md +31 -2
  26. package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +28 -0
  27. package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +28 -0
  28. package/workflows/workflows/agent-environment-setup/platforms/cursor/rules/.cursorrules +28 -0
  29. package/workflows/workflows/agent-environment-setup/platforms/windsurf/rules/.windsurfrules +28 -0
package/bin/cubis.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  writeFile,
15
15
  } from "node:fs/promises";
16
16
  import { createRequire } from "node:module";
17
- import { execFile as execFileCallback } from "node:child_process";
17
+ import { spawn, execFile as execFileCallback } from "node:child_process";
18
18
  import os from "node:os";
19
19
  import path from "node:path";
20
20
  import process from "node:process";
@@ -151,6 +151,16 @@ const POSTMAN_API_KEY_ENV_VAR = "POSTMAN_API_KEY";
151
151
  const POSTMAN_MCP_URL = "https://mcp.postman.com/minimal";
152
152
  const POSTMAN_API_BASE_URL = "https://api.getpostman.com";
153
153
  const POSTMAN_SKILL_ID = "postman";
154
+ const FOUNDRY_MCP_SERVER_ID = "cubis-foundry";
155
+ const FOUNDRY_MCP_COMMAND = "cbx";
156
+ const FOUNDRY_MCP_DEFAULT_ARGS = [
157
+ "mcp",
158
+ "serve",
159
+ "--transport",
160
+ "stdio",
161
+ "--scope",
162
+ "auto",
163
+ ];
154
164
  const STITCH_SKILL_ID = "stitch";
155
165
  const STITCH_MCP_SERVER_ID = "StitchMCP";
156
166
  const STITCH_API_KEY_ENV_VAR = "STITCH_API_KEY";
@@ -167,7 +177,7 @@ const MCP_UPDATE_POLICIES = new Set(["pinned", "latest"]);
167
177
  const DEFAULT_MCP_RUNTIME = "docker";
168
178
  const DEFAULT_MCP_FALLBACK = "local";
169
179
  const DEFAULT_MCP_UPDATE_POLICY = "pinned";
170
- const DEFAULT_MCP_DOCKER_IMAGE = "ghcr.io/cubis/foundry-mcp:0.1.0";
180
+ const DEFAULT_MCP_DOCKER_IMAGE = `ghcr.io/cubetiq/foundry-mcp:${CLI_VERSION}`;
171
181
  const DEFAULT_MCP_DOCKER_CONTAINER_NAME = "cbx-mcp";
172
182
  const DEFAULT_MCP_DOCKER_HOST_PORT = 3310;
173
183
  const MCP_DOCKER_CONTAINER_PORT = 3100;
@@ -541,6 +551,23 @@ async function resolveDockerContainerHostPort({
541
551
  }
542
552
  }
543
553
 
554
+ async function inspectDockerContainerMounts({
555
+ name,
556
+ cwd = process.cwd(),
557
+ }) {
558
+ try {
559
+ const { stdout } = await execFile(
560
+ "docker",
561
+ ["inspect", "--format", "{{json .Mounts}}", name],
562
+ { cwd },
563
+ );
564
+ const parsed = JSON.parse(String(stdout || "").trim());
565
+ return Array.isArray(parsed) ? parsed : [];
566
+ } catch {
567
+ return [];
568
+ }
569
+ }
570
+
544
571
  function isPathInsideRoot(targetPath, rootPath) {
545
572
  const target = path.resolve(targetPath);
546
573
  const root = path.resolve(rootPath);
@@ -2485,7 +2512,7 @@ function escapeRegExp(value) {
2485
2512
 
2486
2513
  function rewriteCodexWorkflowAgentReferences(sourceBody, agentIds) {
2487
2514
  if (!sourceBody || !Array.isArray(agentIds) || agentIds.length === 0)
2488
- return sourceBody;
2515
+ return normalizeCodexWrapperMentions(sourceBody);
2489
2516
 
2490
2517
  let rewritten = sourceBody;
2491
2518
  const sortedAgentIds = unique(agentIds.filter(Boolean)).sort(
@@ -2503,14 +2530,23 @@ function rewriteCodexWorkflowAgentReferences(sourceBody, agentIds) {
2503
2530
  );
2504
2531
  }
2505
2532
 
2506
- return rewritten;
2533
+ return normalizeCodexWrapperMentions(rewritten);
2507
2534
  }
2508
2535
 
2509
2536
  function rewriteCodexAgentSkillReferences(sourceBody) {
2510
2537
  if (!sourceBody) return sourceBody;
2511
2538
  // Agent source files live under platforms/*/agents, but wrapper skills live
2512
2539
  // under .agents/skills/agent-*. Rebase ../skills/<id> links accordingly.
2513
- return sourceBody.replace(/\(\.\.\/skills\//g, "(../");
2540
+ const rebased = sourceBody.replace(/\(\.\.\/skills\//g, "(../");
2541
+ return normalizeCodexWrapperMentions(rebased);
2542
+ }
2543
+
2544
+ function normalizeCodexWrapperMentions(sourceBody) {
2545
+ if (!sourceBody) return sourceBody;
2546
+ return sourceBody.replace(
2547
+ /`\$(workflow|agent)-([A-Za-z0-9_-]+|\*)`/g,
2548
+ (_match, kind, id) => `$${kind}-${id}`,
2549
+ );
2514
2550
  }
2515
2551
 
2516
2552
  async function parseWorkflowMetadata(filePath) {
@@ -2821,8 +2857,11 @@ function buildManagedWorkflowBlock(platformId, workflows) {
2821
2857
  lines.push("## CBX Workflow Routing (auto-managed)");
2822
2858
  if (platformId === "codex") {
2823
2859
  lines.push("Use Codex callable wrappers:");
2824
- lines.push("- Workflows: `$workflow-<name>`");
2825
- lines.push("- Agents: `$agent-<name>`");
2860
+ lines.push("- Workflows: $workflow-<name>");
2861
+ lines.push("- Agents: $agent-<name>");
2862
+ lines.push(
2863
+ "- Use raw $workflow-* / $agent-* tokens (no backticks) so Codex can render icons.",
2864
+ );
2826
2865
  lines.push("");
2827
2866
 
2828
2867
  if (workflows.length === 0) {
@@ -2832,18 +2871,16 @@ function buildManagedWorkflowBlock(platformId, workflows) {
2832
2871
  const triggerPreview = workflow.triggers.slice(0, 2).join(", ");
2833
2872
  const hint = triggerPreview ? ` (${triggerPreview})` : "";
2834
2873
  lines.push(
2835
- `- \`${workflow.command}\` -> \`$${CODEX_WORKFLOW_SKILL_PREFIX}${workflow.id}\`${hint}`,
2874
+ `- ${workflow.command} -> $${CODEX_WORKFLOW_SKILL_PREFIX}${workflow.id}${hint}`,
2836
2875
  );
2837
2876
  }
2838
2877
  }
2839
2878
 
2840
2879
  lines.push("");
2841
2880
  lines.push("Selection policy:");
2842
- lines.push("1. If user names a `$workflow-*`, use it directly.");
2881
+ lines.push("1. If user names a $workflow-*, use it directly.");
2843
2882
  lines.push("2. Else map intent to one primary workflow.");
2844
- lines.push(
2845
- "3. Use `$agent-*` wrappers only when role specialization is needed.",
2846
- );
2883
+ lines.push("3. Use $agent-* wrappers only when role specialization is needed.");
2847
2884
  lines.push("");
2848
2885
  lines.push("<!-- cbx:workflows:auto:end -->");
2849
2886
  return lines.join("\n");
@@ -3316,6 +3353,47 @@ function buildVsCodePostmanServer({
3316
3353
  };
3317
3354
  }
3318
3355
 
3356
+ function buildFoundryServeArgs({ scope = "auto" } = {}) {
3357
+ const normalizedScope =
3358
+ scope === "global" || scope === "project" || scope === "auto"
3359
+ ? scope
3360
+ : "auto";
3361
+ return ["mcp", "serve", "--transport", "stdio", "--scope", normalizedScope];
3362
+ }
3363
+
3364
+ function buildVsCodeFoundryServer({ scope = "auto" } = {}) {
3365
+ return {
3366
+ type: "stdio",
3367
+ command: FOUNDRY_MCP_COMMAND,
3368
+ args: buildFoundryServeArgs({ scope }),
3369
+ env: {},
3370
+ };
3371
+ }
3372
+
3373
+ function buildCopilotCliPostmanServer({
3374
+ apiKeyEnvVar = POSTMAN_API_KEY_ENV_VAR,
3375
+ mcpUrl = POSTMAN_MCP_URL,
3376
+ }) {
3377
+ return {
3378
+ type: "http",
3379
+ url: mcpUrl,
3380
+ headers: {
3381
+ Authorization: buildPostmanAuthHeader({ apiKeyEnvVar }),
3382
+ },
3383
+ tools: ["*"],
3384
+ };
3385
+ }
3386
+
3387
+ function buildCopilotCliFoundryServer({ scope = "auto" } = {}) {
3388
+ return {
3389
+ type: "stdio",
3390
+ command: FOUNDRY_MCP_COMMAND,
3391
+ args: buildFoundryServeArgs({ scope }),
3392
+ env: {},
3393
+ tools: ["*"],
3394
+ };
3395
+ }
3396
+
3319
3397
  function buildGeminiPostmanServer({
3320
3398
  apiKeyEnvVar = POSTMAN_API_KEY_ENV_VAR,
3321
3399
  mcpUrl = POSTMAN_MCP_URL,
@@ -3328,12 +3406,19 @@ function buildGeminiPostmanServer({
3328
3406
  };
3329
3407
  }
3330
3408
 
3409
+ function buildGeminiFoundryServer({ scope = "auto" } = {}) {
3410
+ return {
3411
+ command: FOUNDRY_MCP_COMMAND,
3412
+ args: buildFoundryServeArgs({ scope }),
3413
+ env: {},
3414
+ };
3415
+ }
3416
+
3331
3417
  function buildGeminiStitchServer({
3332
3418
  apiKeyEnvVar = STITCH_API_KEY_ENV_VAR,
3333
3419
  mcpUrl = STITCH_MCP_URL,
3334
3420
  }) {
3335
3421
  return {
3336
- $typeName: "exa.cascade_plugins_pb.CascadePluginCommandTemplate",
3337
3422
  command: "npx",
3338
3423
  args: [
3339
3424
  "-y",
@@ -3752,6 +3837,7 @@ async function applyPostmanMcpForPlatform({
3752
3837
  stitchApiKeyEnvVar,
3753
3838
  stitchMcpUrl,
3754
3839
  includeStitchMcp = false,
3840
+ includeFoundryMcp = true,
3755
3841
  dryRun = false,
3756
3842
  cwd = process.cwd(),
3757
3843
  }) {
@@ -3777,6 +3863,13 @@ async function applyPostmanMcpForPlatform({
3777
3863
  apiKeyEnvVar,
3778
3864
  mcpUrl,
3779
3865
  });
3866
+ if (includeFoundryMcp) {
3867
+ mcpServers[FOUNDRY_MCP_SERVER_ID] = buildGeminiFoundryServer({
3868
+ scope: "auto",
3869
+ });
3870
+ } else {
3871
+ delete mcpServers[FOUNDRY_MCP_SERVER_ID];
3872
+ }
3780
3873
  if (includeStitchMcp) {
3781
3874
  mcpServers[STITCH_MCP_SERVER_ID] = buildGeminiStitchServer({
3782
3875
  apiKeyEnvVar: stitchApiKeyEnvVar,
@@ -3806,6 +3899,28 @@ async function applyPostmanMcpForPlatform({
3806
3899
  targetPath: configPath,
3807
3900
  updater: (existing) => {
3808
3901
  const next = { ...existing };
3902
+ if (mcpScope === "global") {
3903
+ const mcpServers =
3904
+ next.mcpServers &&
3905
+ typeof next.mcpServers === "object" &&
3906
+ !Array.isArray(next.mcpServers)
3907
+ ? { ...next.mcpServers }
3908
+ : {};
3909
+ mcpServers[POSTMAN_SKILL_ID] = buildCopilotCliPostmanServer({
3910
+ apiKeyEnvVar,
3911
+ mcpUrl,
3912
+ });
3913
+ if (includeFoundryMcp) {
3914
+ mcpServers[FOUNDRY_MCP_SERVER_ID] = buildCopilotCliFoundryServer({
3915
+ scope: "auto",
3916
+ });
3917
+ } else {
3918
+ delete mcpServers[FOUNDRY_MCP_SERVER_ID];
3919
+ }
3920
+ next.mcpServers = mcpServers;
3921
+ return next;
3922
+ }
3923
+
3809
3924
  const servers =
3810
3925
  next.servers &&
3811
3926
  typeof next.servers === "object" &&
@@ -3816,6 +3931,13 @@ async function applyPostmanMcpForPlatform({
3816
3931
  apiKeyEnvVar,
3817
3932
  mcpUrl,
3818
3933
  });
3934
+ if (includeFoundryMcp) {
3935
+ servers[FOUNDRY_MCP_SERVER_ID] = buildVsCodeFoundryServer({
3936
+ scope: "auto",
3937
+ });
3938
+ } else {
3939
+ delete servers[FOUNDRY_MCP_SERVER_ID];
3940
+ }
3819
3941
  next.servers = servers;
3820
3942
  return next;
3821
3943
  },
@@ -3847,6 +3969,13 @@ async function applyPostmanMcpForPlatform({
3847
3969
  apiKeyEnvVar,
3848
3970
  mcpUrl,
3849
3971
  });
3972
+ if (includeFoundryMcp) {
3973
+ servers[FOUNDRY_MCP_SERVER_ID] = buildVsCodeFoundryServer({
3974
+ scope: "auto",
3975
+ });
3976
+ } else {
3977
+ delete servers[FOUNDRY_MCP_SERVER_ID];
3978
+ }
3850
3979
  next.servers = servers;
3851
3980
  return next;
3852
3981
  },
@@ -3877,6 +4006,13 @@ async function applyPostmanMcpForPlatform({
3877
4006
  } catch {
3878
4007
  // Best effort. Add will still run and becomes source of truth.
3879
4008
  }
4009
+ try {
4010
+ await execFile("codex", ["mcp", "remove", FOUNDRY_MCP_SERVER_ID], {
4011
+ cwd,
4012
+ });
4013
+ } catch {
4014
+ // Best effort. Add will still run and becomes source of truth.
4015
+ }
3880
4016
 
3881
4017
  try {
3882
4018
  await execFile(
@@ -3905,6 +4041,27 @@ async function applyPostmanMcpForPlatform({
3905
4041
  };
3906
4042
  }
3907
4043
 
4044
+ if (includeFoundryMcp) {
4045
+ try {
4046
+ await execFile(
4047
+ "codex",
4048
+ [
4049
+ "mcp",
4050
+ "add",
4051
+ FOUNDRY_MCP_SERVER_ID,
4052
+ "--",
4053
+ FOUNDRY_MCP_COMMAND,
4054
+ ...FOUNDRY_MCP_DEFAULT_ARGS,
4055
+ ],
4056
+ { cwd },
4057
+ );
4058
+ } catch (error) {
4059
+ warnings.push(
4060
+ `Failed to register ${FOUNDRY_MCP_SERVER_ID} MCP via Codex CLI. Ensure 'cbx' and 'codex' are installed and rerun. (${error.message})`,
4061
+ );
4062
+ }
4063
+ }
4064
+
3908
4065
  return {
3909
4066
  kind: "codex-cli",
3910
4067
  scope: mcpScope,
@@ -4001,6 +4158,7 @@ async function resolvePostmanInstallSelection({
4001
4158
  );
4002
4159
  const mcpBuildLocal = Boolean(options.mcpBuildLocal);
4003
4160
  const mcpToolSync = options.mcpToolSync !== false;
4161
+ const foundryMcpEnabled = options.foundryMcp !== false;
4004
4162
 
4005
4163
  const canPrompt = !options.yes && process.stdin.isTTY;
4006
4164
  if (canPrompt && !hasWorkspaceOption) {
@@ -4228,6 +4386,7 @@ async function resolvePostmanInstallSelection({
4228
4386
  mcpBuildLocal,
4229
4387
  dockerImageAction,
4230
4388
  mcpToolSync,
4389
+ foundryMcpEnabled,
4231
4390
  runtimeSkipped,
4232
4391
  defaultWorkspaceId: defaultWorkspaceId ?? null,
4233
4392
  workspaceSelectionSource,
@@ -4457,6 +4616,7 @@ async function configurePostmanInstallArtifacts({
4457
4616
  stitchApiKeyEnvVar: effectiveStitchApiKeyEnvVar,
4458
4617
  stitchMcpUrl: effectiveStitchMcpUrl,
4459
4618
  includeStitchMcp: shouldInstallStitch,
4619
+ includeFoundryMcp: postmanSelection.foundryMcpEnabled,
4460
4620
  dryRun,
4461
4621
  cwd,
4462
4622
  });
@@ -4495,6 +4655,7 @@ async function configurePostmanInstallArtifacts({
4495
4655
  mcpBuildLocal: postmanSelection.mcpBuildLocal,
4496
4656
  dockerImageAction: postmanSelection.dockerImageAction,
4497
4657
  mcpToolSync: postmanSelection.mcpToolSync,
4658
+ foundryMcpEnabled: postmanSelection.foundryMcpEnabled,
4498
4659
  apiKeySource: effectiveApiKeySource,
4499
4660
  stitchApiKeySource: effectiveStitchApiKeySource,
4500
4661
  defaultWorkspaceId: effectiveDefaultWorkspaceId,
@@ -5454,6 +5615,9 @@ function printPostmanSetupSummary({ postmanSetup }) {
5454
5615
  console.log(`- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`);
5455
5616
  console.log(`- MCP image prepare: ${postmanSetup.dockerImageAction}`);
5456
5617
  console.log(`- MCP tool sync: ${postmanSetup.mcpToolSync ? "enabled" : "disabled"}`);
5618
+ console.log(
5619
+ `- Foundry MCP side-by-side: ${postmanSetup.foundryMcpEnabled ? "enabled" : "disabled"}`,
5620
+ );
5457
5621
  console.log(`- Postman API key source: ${postmanSetup.apiKeySource}`);
5458
5622
  if (postmanSetup.stitchApiKeySource) {
5459
5623
  console.log(`- Stitch API key source: ${postmanSetup.stitchApiKeySource}`);
@@ -5971,6 +6135,10 @@ function withInstallOptions(command) {
5971
6135
  "enable automatic MCP tool catalog sync (default: enabled)",
5972
6136
  )
5973
6137
  .option("--no-mcp-tool-sync", "disable automatic MCP tool catalog sync")
6138
+ .option(
6139
+ "--no-foundry-mcp",
6140
+ "disable side-by-side cubis-foundry MCP registration during --postman setup",
6141
+ )
5974
6142
  .option(
5975
6143
  "--terminal-integration",
5976
6144
  "Antigravity only: enable terminal verification integration (prompts for verifier when interactive)",
@@ -7933,6 +8101,111 @@ async function runMcpToolsList(options) {
7933
8101
  }
7934
8102
  }
7935
8103
 
8104
+ function normalizeMcpServeTransport(value, fallback = "stdio") {
8105
+ const normalized = String(value || fallback)
8106
+ .trim()
8107
+ .toLowerCase();
8108
+ if (normalized === "stdio") return "stdio";
8109
+ if (normalized === "http" || normalized === "streamable-http") return "http";
8110
+ throw new Error(
8111
+ `Unknown MCP transport '${value}'. Use stdio|http.`,
8112
+ );
8113
+ }
8114
+
8115
+ function normalizeMcpServeScope(value, fallback = "auto") {
8116
+ const normalized = String(value || fallback)
8117
+ .trim()
8118
+ .toLowerCase();
8119
+ if (
8120
+ normalized === "auto" ||
8121
+ normalized === "global" ||
8122
+ normalized === "project"
8123
+ ) {
8124
+ return normalized;
8125
+ }
8126
+ throw new Error(
8127
+ `Unknown MCP scope '${value}'. Use auto|global|project.`,
8128
+ );
8129
+ }
8130
+
8131
+ function resolveBundledMcpEntryPath() {
8132
+ return path.join(packageRoot(), "mcp", "dist", "index.js");
8133
+ }
8134
+
8135
+ async function runMcpServe(options) {
8136
+ try {
8137
+ const opts = resolveActionOptions(options);
8138
+ const cwd = process.cwd();
8139
+ const entryPath = resolveBundledMcpEntryPath();
8140
+ if (!(await pathExists(entryPath))) {
8141
+ throw new Error(
8142
+ `Bundled MCP server not found at ${entryPath}. Install @cubis/foundry with bundled mcp/dist assets.`,
8143
+ );
8144
+ }
8145
+
8146
+ const transport = normalizeMcpServeTransport(opts.transport, "stdio");
8147
+ const scope = normalizeMcpServeScope(opts.scope, "auto");
8148
+ const args = [entryPath, "--transport", transport, "--scope", scope];
8149
+
8150
+ if (opts.host) {
8151
+ args.push("--host", String(opts.host));
8152
+ }
8153
+ if (opts.port !== undefined && opts.port !== null && opts.port !== "") {
8154
+ args.push("--port", String(normalizePortNumber(opts.port, 3100)));
8155
+ }
8156
+ if (opts.scanOnly) {
8157
+ args.push("--scan-only");
8158
+ }
8159
+ if (opts.config) {
8160
+ args.push("--config", expandPath(String(opts.config), cwd));
8161
+ }
8162
+ if (opts.debug) {
8163
+ args.push("--debug");
8164
+ }
8165
+
8166
+ await new Promise((resolve, reject) => {
8167
+ const child = spawn(process.execPath, args, {
8168
+ cwd,
8169
+ stdio: "inherit",
8170
+ env: process.env,
8171
+ });
8172
+
8173
+ const forwardSignal = (signal) => {
8174
+ if (!child.killed) {
8175
+ child.kill(signal);
8176
+ }
8177
+ };
8178
+ const onSigInt = () => forwardSignal("SIGINT");
8179
+ const onSigTerm = () => forwardSignal("SIGTERM");
8180
+ process.on("SIGINT", onSigInt);
8181
+ process.on("SIGTERM", onSigTerm);
8182
+
8183
+ child.once("error", (error) => {
8184
+ process.off("SIGINT", onSigInt);
8185
+ process.off("SIGTERM", onSigTerm);
8186
+ reject(error);
8187
+ });
8188
+
8189
+ child.once("exit", (code, signal) => {
8190
+ process.off("SIGINT", onSigInt);
8191
+ process.off("SIGTERM", onSigTerm);
8192
+ if (signal) {
8193
+ reject(new Error(`MCP server terminated by signal ${signal}.`));
8194
+ return;
8195
+ }
8196
+ if (code && code !== 0) {
8197
+ reject(new Error(`MCP server exited with status ${code}.`));
8198
+ return;
8199
+ }
8200
+ resolve(null);
8201
+ });
8202
+ });
8203
+ } catch (error) {
8204
+ console.error(`\nError: ${error.message}`);
8205
+ process.exit(1);
8206
+ }
8207
+ }
8208
+
7936
8209
  function resolveCbxRootPath({ scope, cwd = process.cwd() }) {
7937
8210
  if (scope === "global") {
7938
8211
  return path.join(os.homedir(), ".cbx");
@@ -7998,6 +8271,8 @@ async function runMcpRuntimeStatus(options) {
7998
8271
  const container = dockerAvailable
7999
8272
  ? await inspectDockerContainerByName({ name: containerName, cwd })
8000
8273
  : null;
8274
+ const skillsRoot = path.join(os.homedir(), ".agents", "skills");
8275
+ const skillsRootExists = await pathExists(skillsRoot);
8001
8276
 
8002
8277
  console.log(`Scope: ${scope}`);
8003
8278
  console.log(`Config file: ${defaults.configPath}`);
@@ -8007,6 +8282,9 @@ async function runMcpRuntimeStatus(options) {
8007
8282
  console.log(`Configured image: ${defaults.defaults.image}`);
8008
8283
  console.log(`Configured update policy: ${defaults.defaults.updatePolicy}`);
8009
8284
  console.log(`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`);
8285
+ console.log(
8286
+ `Host skills root: ${skillsRoot} (${skillsRootExists ? "present" : "missing"})`,
8287
+ );
8010
8288
  console.log(`Docker available: ${dockerAvailable ? "yes" : "no"}`);
8011
8289
  console.log(`Container name: ${containerName}`);
8012
8290
  if (!dockerAvailable) {
@@ -8015,11 +8293,34 @@ async function runMcpRuntimeStatus(options) {
8015
8293
  }
8016
8294
  if (!container) {
8017
8295
  console.log("Container status: not found");
8296
+ if (!skillsRootExists) {
8297
+ console.log(
8298
+ "Hint: ~/.agents/skills is missing. Create/populate it before starting runtime to enable vault discovery.",
8299
+ );
8300
+ }
8018
8301
  return;
8019
8302
  }
8020
8303
  const isRunning = container.status.toLowerCase().startsWith("up ");
8021
8304
  console.log(`Container status: ${container.status}`);
8022
8305
  console.log(`Container image: ${container.image}`);
8306
+ const mounts = await inspectDockerContainerMounts({ name: containerName, cwd });
8307
+ const skillsMount = mounts.find(
8308
+ (mount) => String(mount.Destination || "") === "/workflows/skills",
8309
+ );
8310
+ if (skillsMount) {
8311
+ const source = String(skillsMount.Source || "(unknown)");
8312
+ console.log(`Skills mount: ${source} -> /workflows/skills`);
8313
+ if (!(await pathExists(source))) {
8314
+ console.log(
8315
+ "Hint: mounted skill source path is missing on host; vault discovery may return zero skills.",
8316
+ );
8317
+ }
8318
+ } else {
8319
+ console.log("Skills mount: missing");
8320
+ console.log(
8321
+ "Hint: recreate runtime with a skills mount (host ~/.agents/skills -> /workflows/skills).",
8322
+ );
8323
+ }
8023
8324
  if (isRunning) {
8024
8325
  const hostPort =
8025
8326
  (await resolveDockerContainerHostPort({
@@ -8082,22 +8383,32 @@ async function runMcpRuntimeUp(options) {
8082
8383
  }
8083
8384
 
8084
8385
  const cbxRoot = resolveCbxRootPath({ scope, cwd });
8386
+ const skillsRoot = path.join(os.homedir(), ".agents", "skills");
8387
+ const skillsRootExists = await pathExists(skillsRoot);
8388
+ const runtimeWarnings = [];
8389
+ if (!skillsRootExists) {
8390
+ runtimeWarnings.push(
8391
+ `Skill mount source is missing: ${skillsRoot}. Runtime will start without /workflows/skills and vault discovery can return zero skills.`,
8392
+ );
8393
+ }
8085
8394
  await mkdir(cbxRoot, { recursive: true });
8395
+ const dockerArgs = [
8396
+ "run",
8397
+ "-d",
8398
+ "--name",
8399
+ containerName,
8400
+ "-p",
8401
+ `${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`,
8402
+ "-v",
8403
+ `${cbxRoot}:/root/.cbx`,
8404
+ ];
8405
+ if (skillsRootExists) {
8406
+ dockerArgs.push("-v", `${skillsRoot}:/workflows/skills:ro`);
8407
+ }
8408
+ dockerArgs.push("-e", "CBX_MCP_TRANSPORT=streamable-http", image);
8086
8409
  await execFile(
8087
8410
  "docker",
8088
- [
8089
- "run",
8090
- "-d",
8091
- "--name",
8092
- containerName,
8093
- "-p",
8094
- `${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`,
8095
- "-v",
8096
- `${cbxRoot}:/root/.cbx`,
8097
- "-e",
8098
- "CBX_MCP_TRANSPORT=streamable-http",
8099
- image,
8100
- ],
8411
+ dockerArgs,
8101
8412
  { cwd },
8102
8413
  );
8103
8414
 
@@ -8112,9 +8423,20 @@ async function runMcpRuntimeUp(options) {
8112
8423
  console.log(`Update policy: ${updatePolicy}`);
8113
8424
  console.log(`Build local: ${buildLocal ? "yes" : "no"}`);
8114
8425
  console.log(`Mount: ${cbxRoot} -> /root/.cbx`);
8426
+ if (skillsRootExists) {
8427
+ console.log(`Mount: ${skillsRoot} -> /workflows/skills (ro)`);
8428
+ } else {
8429
+ console.log("Mount: /workflows/skills (not mounted - source missing)");
8430
+ }
8115
8431
  console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
8116
8432
  console.log(`Status: ${running ? running.status : "started"}`);
8117
8433
  console.log(`Endpoint: http://127.0.0.1:${hostPort}/mcp`);
8434
+ if (runtimeWarnings.length > 0) {
8435
+ console.log("Warnings:");
8436
+ for (const warning of runtimeWarnings) {
8437
+ console.log(`- ${warning}`);
8438
+ }
8439
+ }
8118
8440
  } catch (error) {
8119
8441
  console.error(`\nError: ${error.message}`);
8120
8442
  process.exit(1);
@@ -8563,6 +8885,18 @@ const mcpCommand = program
8563
8885
  .command("mcp")
8564
8886
  .description("Manage Cubis MCP runtime catalogs and tool discovery");
8565
8887
 
8888
+ mcpCommand
8889
+ .command("serve")
8890
+ .description("Launch bundled Cubis Foundry MCP server (canonical local entrypoint)")
8891
+ .option("--transport <transport>", "stdio|http", "stdio")
8892
+ .option("--scope <scope>", "auto|global|project", "auto")
8893
+ .option("--port <port>", "HTTP port override")
8894
+ .option("--host <host>", "HTTP host override")
8895
+ .option("--scan-only", "scan vault and exit")
8896
+ .option("--config <path>", "explicit MCP server config file path")
8897
+ .option("--debug", "enable debug logging in MCP server")
8898
+ .action(runMcpServe);
8899
+
8566
8900
  const mcpToolsCommand = mcpCommand
8567
8901
  .command("tools")
8568
8902
  .description("Discover and inspect upstream MCP tool catalogs");