@cubis/foundry 0.3.39 → 0.3.41

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 (33) hide show
  1. package/README.md +64 -0
  2. package/bin/cubis.js +382 -25
  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/skills/code-documenter/references/images/create-key.png +0 -0
  25. package/workflows/skills/code-documenter/references/images/dashboard-annotated.png +0 -0
  26. package/workflows/skills/documentation-templates/docs/api.md +16 -0
  27. package/workflows/skills/documentation-templates/docs/architecture.md +23 -0
  28. package/workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md +35 -1
  29. package/workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md +38 -3
  30. package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +35 -1
  31. package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +35 -1
  32. package/workflows/workflows/agent-environment-setup/platforms/cursor/rules/.cursorrules +28 -0
  33. 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";
@@ -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);
@@ -2506,6 +2533,13 @@ function rewriteCodexWorkflowAgentReferences(sourceBody, agentIds) {
2506
2533
  return rewritten;
2507
2534
  }
2508
2535
 
2536
+ function rewriteCodexAgentSkillReferences(sourceBody) {
2537
+ if (!sourceBody) return sourceBody;
2538
+ // Agent source files live under platforms/*/agents, but wrapper skills live
2539
+ // under .agents/skills/agent-*. Rebase ../skills/<id> links accordingly.
2540
+ return sourceBody.replace(/\(\.\.\/skills\//g, "(../");
2541
+ }
2542
+
2509
2543
  async function parseWorkflowMetadata(filePath) {
2510
2544
  const raw = await readFile(filePath, "utf8");
2511
2545
  const { frontmatter, body } = extractFrontmatter(raw);
@@ -2730,11 +2764,15 @@ async function generateCodexWrapperSkills({
2730
2764
  }
2731
2765
 
2732
2766
  const metadata = await parseAgentMetadata(source);
2767
+ const rewrittenBody = rewriteCodexAgentSkillReferences(metadata.body);
2733
2768
  const wrapperSkillId = `${CODEX_AGENT_SKILL_PREFIX}${metadata.id}`;
2734
2769
  const destinationDir = path.join(skillsDir, wrapperSkillId);
2735
2770
  const content = buildCodexAgentWrapperSkillMarkdown(
2736
2771
  wrapperSkillId,
2737
- metadata,
2772
+ {
2773
+ ...metadata,
2774
+ body: rewrittenBody,
2775
+ },
2738
2776
  );
2739
2777
 
2740
2778
  const result = await writeGeneratedSkillArtifact({
@@ -2759,6 +2797,22 @@ async function generateCodexWrapperSkills({
2759
2797
  };
2760
2798
  }
2761
2799
 
2800
+ async function resolvePlatformAgentSkillDependencies({
2801
+ platformRoot,
2802
+ platformSpec,
2803
+ }) {
2804
+ const dependencyIds = [];
2805
+
2806
+ for (const agentFile of platformSpec.agents || []) {
2807
+ const source = path.join(platformRoot, "agents", agentFile);
2808
+ if (!(await pathExists(source))) continue;
2809
+ const metadata = await parseAgentMetadata(source);
2810
+ dependencyIds.push(...metadata.skills);
2811
+ }
2812
+
2813
+ return unique(dependencyIds.filter(Boolean));
2814
+ }
2815
+
2762
2816
  async function collectInstalledWorkflows(
2763
2817
  profileId,
2764
2818
  scope,
@@ -2794,8 +2848,11 @@ function buildManagedWorkflowBlock(platformId, workflows) {
2794
2848
  lines.push("## CBX Workflow Routing (auto-managed)");
2795
2849
  if (platformId === "codex") {
2796
2850
  lines.push("Use Codex callable wrappers:");
2797
- lines.push("- Workflows: `$workflow-<name>`");
2798
- lines.push("- Agents: `$agent-<name>`");
2851
+ lines.push("- Workflows: $workflow-<name>");
2852
+ lines.push("- Agents: $agent-<name>");
2853
+ lines.push(
2854
+ "- Use raw $workflow-* / $agent-* tokens (no backticks) so Codex can render icons.",
2855
+ );
2799
2856
  lines.push("");
2800
2857
 
2801
2858
  if (workflows.length === 0) {
@@ -2805,18 +2862,16 @@ function buildManagedWorkflowBlock(platformId, workflows) {
2805
2862
  const triggerPreview = workflow.triggers.slice(0, 2).join(", ");
2806
2863
  const hint = triggerPreview ? ` (${triggerPreview})` : "";
2807
2864
  lines.push(
2808
- `- \`${workflow.command}\` -> \`$${CODEX_WORKFLOW_SKILL_PREFIX}${workflow.id}\`${hint}`,
2865
+ `- ${workflow.command} -> $${CODEX_WORKFLOW_SKILL_PREFIX}${workflow.id}${hint}`,
2809
2866
  );
2810
2867
  }
2811
2868
  }
2812
2869
 
2813
2870
  lines.push("");
2814
2871
  lines.push("Selection policy:");
2815
- lines.push("1. If user names a `$workflow-*`, use it directly.");
2872
+ lines.push("1. If user names a $workflow-*, use it directly.");
2816
2873
  lines.push("2. Else map intent to one primary workflow.");
2817
- lines.push(
2818
- "3. Use `$agent-*` wrappers only when role specialization is needed.",
2819
- );
2874
+ lines.push("3. Use $agent-* wrappers only when role specialization is needed.");
2820
2875
  lines.push("");
2821
2876
  lines.push("<!-- cbx:workflows:auto:end -->");
2822
2877
  return lines.join("\n");
@@ -3289,6 +3344,47 @@ function buildVsCodePostmanServer({
3289
3344
  };
3290
3345
  }
3291
3346
 
3347
+ function buildFoundryServeArgs({ scope = "auto" } = {}) {
3348
+ const normalizedScope =
3349
+ scope === "global" || scope === "project" || scope === "auto"
3350
+ ? scope
3351
+ : "auto";
3352
+ return ["mcp", "serve", "--transport", "stdio", "--scope", normalizedScope];
3353
+ }
3354
+
3355
+ function buildVsCodeFoundryServer({ scope = "auto" } = {}) {
3356
+ return {
3357
+ type: "stdio",
3358
+ command: FOUNDRY_MCP_COMMAND,
3359
+ args: buildFoundryServeArgs({ scope }),
3360
+ env: {},
3361
+ };
3362
+ }
3363
+
3364
+ function buildCopilotCliPostmanServer({
3365
+ apiKeyEnvVar = POSTMAN_API_KEY_ENV_VAR,
3366
+ mcpUrl = POSTMAN_MCP_URL,
3367
+ }) {
3368
+ return {
3369
+ type: "http",
3370
+ url: mcpUrl,
3371
+ headers: {
3372
+ Authorization: buildPostmanAuthHeader({ apiKeyEnvVar }),
3373
+ },
3374
+ tools: ["*"],
3375
+ };
3376
+ }
3377
+
3378
+ function buildCopilotCliFoundryServer({ scope = "auto" } = {}) {
3379
+ return {
3380
+ type: "stdio",
3381
+ command: FOUNDRY_MCP_COMMAND,
3382
+ args: buildFoundryServeArgs({ scope }),
3383
+ env: {},
3384
+ tools: ["*"],
3385
+ };
3386
+ }
3387
+
3292
3388
  function buildGeminiPostmanServer({
3293
3389
  apiKeyEnvVar = POSTMAN_API_KEY_ENV_VAR,
3294
3390
  mcpUrl = POSTMAN_MCP_URL,
@@ -3301,12 +3397,19 @@ function buildGeminiPostmanServer({
3301
3397
  };
3302
3398
  }
3303
3399
 
3400
+ function buildGeminiFoundryServer({ scope = "auto" } = {}) {
3401
+ return {
3402
+ command: FOUNDRY_MCP_COMMAND,
3403
+ args: buildFoundryServeArgs({ scope }),
3404
+ env: {},
3405
+ };
3406
+ }
3407
+
3304
3408
  function buildGeminiStitchServer({
3305
3409
  apiKeyEnvVar = STITCH_API_KEY_ENV_VAR,
3306
3410
  mcpUrl = STITCH_MCP_URL,
3307
3411
  }) {
3308
3412
  return {
3309
- $typeName: "exa.cascade_plugins_pb.CascadePluginCommandTemplate",
3310
3413
  command: "npx",
3311
3414
  args: [
3312
3415
  "-y",
@@ -3725,6 +3828,7 @@ async function applyPostmanMcpForPlatform({
3725
3828
  stitchApiKeyEnvVar,
3726
3829
  stitchMcpUrl,
3727
3830
  includeStitchMcp = false,
3831
+ includeFoundryMcp = true,
3728
3832
  dryRun = false,
3729
3833
  cwd = process.cwd(),
3730
3834
  }) {
@@ -3750,6 +3854,13 @@ async function applyPostmanMcpForPlatform({
3750
3854
  apiKeyEnvVar,
3751
3855
  mcpUrl,
3752
3856
  });
3857
+ if (includeFoundryMcp) {
3858
+ mcpServers[FOUNDRY_MCP_SERVER_ID] = buildGeminiFoundryServer({
3859
+ scope: "auto",
3860
+ });
3861
+ } else {
3862
+ delete mcpServers[FOUNDRY_MCP_SERVER_ID];
3863
+ }
3753
3864
  if (includeStitchMcp) {
3754
3865
  mcpServers[STITCH_MCP_SERVER_ID] = buildGeminiStitchServer({
3755
3866
  apiKeyEnvVar: stitchApiKeyEnvVar,
@@ -3779,6 +3890,28 @@ async function applyPostmanMcpForPlatform({
3779
3890
  targetPath: configPath,
3780
3891
  updater: (existing) => {
3781
3892
  const next = { ...existing };
3893
+ if (mcpScope === "global") {
3894
+ const mcpServers =
3895
+ next.mcpServers &&
3896
+ typeof next.mcpServers === "object" &&
3897
+ !Array.isArray(next.mcpServers)
3898
+ ? { ...next.mcpServers }
3899
+ : {};
3900
+ mcpServers[POSTMAN_SKILL_ID] = buildCopilotCliPostmanServer({
3901
+ apiKeyEnvVar,
3902
+ mcpUrl,
3903
+ });
3904
+ if (includeFoundryMcp) {
3905
+ mcpServers[FOUNDRY_MCP_SERVER_ID] = buildCopilotCliFoundryServer({
3906
+ scope: "auto",
3907
+ });
3908
+ } else {
3909
+ delete mcpServers[FOUNDRY_MCP_SERVER_ID];
3910
+ }
3911
+ next.mcpServers = mcpServers;
3912
+ return next;
3913
+ }
3914
+
3782
3915
  const servers =
3783
3916
  next.servers &&
3784
3917
  typeof next.servers === "object" &&
@@ -3789,6 +3922,13 @@ async function applyPostmanMcpForPlatform({
3789
3922
  apiKeyEnvVar,
3790
3923
  mcpUrl,
3791
3924
  });
3925
+ if (includeFoundryMcp) {
3926
+ servers[FOUNDRY_MCP_SERVER_ID] = buildVsCodeFoundryServer({
3927
+ scope: "auto",
3928
+ });
3929
+ } else {
3930
+ delete servers[FOUNDRY_MCP_SERVER_ID];
3931
+ }
3792
3932
  next.servers = servers;
3793
3933
  return next;
3794
3934
  },
@@ -3820,6 +3960,13 @@ async function applyPostmanMcpForPlatform({
3820
3960
  apiKeyEnvVar,
3821
3961
  mcpUrl,
3822
3962
  });
3963
+ if (includeFoundryMcp) {
3964
+ servers[FOUNDRY_MCP_SERVER_ID] = buildVsCodeFoundryServer({
3965
+ scope: "auto",
3966
+ });
3967
+ } else {
3968
+ delete servers[FOUNDRY_MCP_SERVER_ID];
3969
+ }
3823
3970
  next.servers = servers;
3824
3971
  return next;
3825
3972
  },
@@ -3850,6 +3997,13 @@ async function applyPostmanMcpForPlatform({
3850
3997
  } catch {
3851
3998
  // Best effort. Add will still run and becomes source of truth.
3852
3999
  }
4000
+ try {
4001
+ await execFile("codex", ["mcp", "remove", FOUNDRY_MCP_SERVER_ID], {
4002
+ cwd,
4003
+ });
4004
+ } catch {
4005
+ // Best effort. Add will still run and becomes source of truth.
4006
+ }
3853
4007
 
3854
4008
  try {
3855
4009
  await execFile(
@@ -3878,6 +4032,27 @@ async function applyPostmanMcpForPlatform({
3878
4032
  };
3879
4033
  }
3880
4034
 
4035
+ if (includeFoundryMcp) {
4036
+ try {
4037
+ await execFile(
4038
+ "codex",
4039
+ [
4040
+ "mcp",
4041
+ "add",
4042
+ FOUNDRY_MCP_SERVER_ID,
4043
+ "--",
4044
+ FOUNDRY_MCP_COMMAND,
4045
+ ...FOUNDRY_MCP_DEFAULT_ARGS,
4046
+ ],
4047
+ { cwd },
4048
+ );
4049
+ } catch (error) {
4050
+ warnings.push(
4051
+ `Failed to register ${FOUNDRY_MCP_SERVER_ID} MCP via Codex CLI. Ensure 'cbx' and 'codex' are installed and rerun. (${error.message})`,
4052
+ );
4053
+ }
4054
+ }
4055
+
3881
4056
  return {
3882
4057
  kind: "codex-cli",
3883
4058
  scope: mcpScope,
@@ -3974,6 +4149,7 @@ async function resolvePostmanInstallSelection({
3974
4149
  );
3975
4150
  const mcpBuildLocal = Boolean(options.mcpBuildLocal);
3976
4151
  const mcpToolSync = options.mcpToolSync !== false;
4152
+ const foundryMcpEnabled = options.foundryMcp !== false;
3977
4153
 
3978
4154
  const canPrompt = !options.yes && process.stdin.isTTY;
3979
4155
  if (canPrompt && !hasWorkspaceOption) {
@@ -4201,6 +4377,7 @@ async function resolvePostmanInstallSelection({
4201
4377
  mcpBuildLocal,
4202
4378
  dockerImageAction,
4203
4379
  mcpToolSync,
4380
+ foundryMcpEnabled,
4204
4381
  runtimeSkipped,
4205
4382
  defaultWorkspaceId: defaultWorkspaceId ?? null,
4206
4383
  workspaceSelectionSource,
@@ -4430,6 +4607,7 @@ async function configurePostmanInstallArtifacts({
4430
4607
  stitchApiKeyEnvVar: effectiveStitchApiKeyEnvVar,
4431
4608
  stitchMcpUrl: effectiveStitchMcpUrl,
4432
4609
  includeStitchMcp: shouldInstallStitch,
4610
+ includeFoundryMcp: postmanSelection.foundryMcpEnabled,
4433
4611
  dryRun,
4434
4612
  cwd,
4435
4613
  });
@@ -4468,6 +4646,7 @@ async function configurePostmanInstallArtifacts({
4468
4646
  mcpBuildLocal: postmanSelection.mcpBuildLocal,
4469
4647
  dockerImageAction: postmanSelection.dockerImageAction,
4470
4648
  mcpToolSync: postmanSelection.mcpToolSync,
4649
+ foundryMcpEnabled: postmanSelection.foundryMcpEnabled,
4471
4650
  apiKeySource: effectiveApiKeySource,
4472
4651
  stitchApiKeySource: effectiveStitchApiKeySource,
4473
4652
  defaultWorkspaceId: effectiveDefaultWorkspaceId,
@@ -4902,9 +5081,13 @@ async function installBundleArtifacts({
4902
5081
  skipped.push(destination);
4903
5082
  else installed.push(destination);
4904
5083
  }
5084
+ const agentSkillDependencies = await resolvePlatformAgentSkillDependencies({
5085
+ platformRoot,
5086
+ platformSpec,
5087
+ });
4905
5088
  const skillIds = await resolveInstallSkillIds({
4906
5089
  platformSpec,
4907
- extraSkillIds,
5090
+ extraSkillIds: [...extraSkillIds, ...agentSkillDependencies],
4908
5091
  skillProfile,
4909
5092
  });
4910
5093
  for (const skillId of skillIds) {
@@ -5423,6 +5606,9 @@ function printPostmanSetupSummary({ postmanSetup }) {
5423
5606
  console.log(`- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`);
5424
5607
  console.log(`- MCP image prepare: ${postmanSetup.dockerImageAction}`);
5425
5608
  console.log(`- MCP tool sync: ${postmanSetup.mcpToolSync ? "enabled" : "disabled"}`);
5609
+ console.log(
5610
+ `- Foundry MCP side-by-side: ${postmanSetup.foundryMcpEnabled ? "enabled" : "disabled"}`,
5611
+ );
5426
5612
  console.log(`- Postman API key source: ${postmanSetup.apiKeySource}`);
5427
5613
  if (postmanSetup.stitchApiKeySource) {
5428
5614
  console.log(`- Stitch API key source: ${postmanSetup.stitchApiKeySource}`);
@@ -5940,6 +6126,10 @@ function withInstallOptions(command) {
5940
6126
  "enable automatic MCP tool catalog sync (default: enabled)",
5941
6127
  )
5942
6128
  .option("--no-mcp-tool-sync", "disable automatic MCP tool catalog sync")
6129
+ .option(
6130
+ "--no-foundry-mcp",
6131
+ "disable side-by-side cubis-foundry MCP registration during --postman setup",
6132
+ )
5943
6133
  .option(
5944
6134
  "--terminal-integration",
5945
6135
  "Antigravity only: enable terminal verification integration (prompts for verifier when interactive)",
@@ -7047,7 +7237,8 @@ async function collectInlineHeaderFindings({
7047
7237
  const scanFile = async (filePath) => {
7048
7238
  if (!(await pathExists(filePath))) return;
7049
7239
  const raw = await readFile(filePath, "utf8");
7050
- const unsafeStitchHeader = /X-Goog-Api-Key:\s*(?!\$\{)[^"\n]+/i;
7240
+ const unsafeStitchHeader =
7241
+ /X-Goog-Api-Key:(?!\s*\$\{[A-Za-z_][A-Za-z0-9_]*\})\s*[^"\n]+/i;
7051
7242
  const unsafeBearerHeader = /"Authorization"\s*:\s*"Bearer\s+(?!\$\{)[^"]+/i;
7052
7243
  if (unsafeStitchHeader.test(raw) || unsafeBearerHeader.test(raw)) {
7053
7244
  findings.push(filePath);
@@ -7901,6 +8092,111 @@ async function runMcpToolsList(options) {
7901
8092
  }
7902
8093
  }
7903
8094
 
8095
+ function normalizeMcpServeTransport(value, fallback = "stdio") {
8096
+ const normalized = String(value || fallback)
8097
+ .trim()
8098
+ .toLowerCase();
8099
+ if (normalized === "stdio") return "stdio";
8100
+ if (normalized === "http" || normalized === "streamable-http") return "http";
8101
+ throw new Error(
8102
+ `Unknown MCP transport '${value}'. Use stdio|http.`,
8103
+ );
8104
+ }
8105
+
8106
+ function normalizeMcpServeScope(value, fallback = "auto") {
8107
+ const normalized = String(value || fallback)
8108
+ .trim()
8109
+ .toLowerCase();
8110
+ if (
8111
+ normalized === "auto" ||
8112
+ normalized === "global" ||
8113
+ normalized === "project"
8114
+ ) {
8115
+ return normalized;
8116
+ }
8117
+ throw new Error(
8118
+ `Unknown MCP scope '${value}'. Use auto|global|project.`,
8119
+ );
8120
+ }
8121
+
8122
+ function resolveBundledMcpEntryPath() {
8123
+ return path.join(packageRoot(), "mcp", "dist", "index.js");
8124
+ }
8125
+
8126
+ async function runMcpServe(options) {
8127
+ try {
8128
+ const opts = resolveActionOptions(options);
8129
+ const cwd = process.cwd();
8130
+ const entryPath = resolveBundledMcpEntryPath();
8131
+ if (!(await pathExists(entryPath))) {
8132
+ throw new Error(
8133
+ `Bundled MCP server not found at ${entryPath}. Install @cubis/foundry with bundled mcp/dist assets.`,
8134
+ );
8135
+ }
8136
+
8137
+ const transport = normalizeMcpServeTransport(opts.transport, "stdio");
8138
+ const scope = normalizeMcpServeScope(opts.scope, "auto");
8139
+ const args = [entryPath, "--transport", transport, "--scope", scope];
8140
+
8141
+ if (opts.host) {
8142
+ args.push("--host", String(opts.host));
8143
+ }
8144
+ if (opts.port !== undefined && opts.port !== null && opts.port !== "") {
8145
+ args.push("--port", String(normalizePortNumber(opts.port, 3100)));
8146
+ }
8147
+ if (opts.scanOnly) {
8148
+ args.push("--scan-only");
8149
+ }
8150
+ if (opts.config) {
8151
+ args.push("--config", expandPath(String(opts.config), cwd));
8152
+ }
8153
+ if (opts.debug) {
8154
+ args.push("--debug");
8155
+ }
8156
+
8157
+ await new Promise((resolve, reject) => {
8158
+ const child = spawn(process.execPath, args, {
8159
+ cwd,
8160
+ stdio: "inherit",
8161
+ env: process.env,
8162
+ });
8163
+
8164
+ const forwardSignal = (signal) => {
8165
+ if (!child.killed) {
8166
+ child.kill(signal);
8167
+ }
8168
+ };
8169
+ const onSigInt = () => forwardSignal("SIGINT");
8170
+ const onSigTerm = () => forwardSignal("SIGTERM");
8171
+ process.on("SIGINT", onSigInt);
8172
+ process.on("SIGTERM", onSigTerm);
8173
+
8174
+ child.once("error", (error) => {
8175
+ process.off("SIGINT", onSigInt);
8176
+ process.off("SIGTERM", onSigTerm);
8177
+ reject(error);
8178
+ });
8179
+
8180
+ child.once("exit", (code, signal) => {
8181
+ process.off("SIGINT", onSigInt);
8182
+ process.off("SIGTERM", onSigTerm);
8183
+ if (signal) {
8184
+ reject(new Error(`MCP server terminated by signal ${signal}.`));
8185
+ return;
8186
+ }
8187
+ if (code && code !== 0) {
8188
+ reject(new Error(`MCP server exited with status ${code}.`));
8189
+ return;
8190
+ }
8191
+ resolve(null);
8192
+ });
8193
+ });
8194
+ } catch (error) {
8195
+ console.error(`\nError: ${error.message}`);
8196
+ process.exit(1);
8197
+ }
8198
+ }
8199
+
7904
8200
  function resolveCbxRootPath({ scope, cwd = process.cwd() }) {
7905
8201
  if (scope === "global") {
7906
8202
  return path.join(os.homedir(), ".cbx");
@@ -7966,6 +8262,8 @@ async function runMcpRuntimeStatus(options) {
7966
8262
  const container = dockerAvailable
7967
8263
  ? await inspectDockerContainerByName({ name: containerName, cwd })
7968
8264
  : null;
8265
+ const skillsRoot = path.join(os.homedir(), ".agents", "skills");
8266
+ const skillsRootExists = await pathExists(skillsRoot);
7969
8267
 
7970
8268
  console.log(`Scope: ${scope}`);
7971
8269
  console.log(`Config file: ${defaults.configPath}`);
@@ -7975,6 +8273,9 @@ async function runMcpRuntimeStatus(options) {
7975
8273
  console.log(`Configured image: ${defaults.defaults.image}`);
7976
8274
  console.log(`Configured update policy: ${defaults.defaults.updatePolicy}`);
7977
8275
  console.log(`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`);
8276
+ console.log(
8277
+ `Host skills root: ${skillsRoot} (${skillsRootExists ? "present" : "missing"})`,
8278
+ );
7978
8279
  console.log(`Docker available: ${dockerAvailable ? "yes" : "no"}`);
7979
8280
  console.log(`Container name: ${containerName}`);
7980
8281
  if (!dockerAvailable) {
@@ -7983,11 +8284,34 @@ async function runMcpRuntimeStatus(options) {
7983
8284
  }
7984
8285
  if (!container) {
7985
8286
  console.log("Container status: not found");
8287
+ if (!skillsRootExists) {
8288
+ console.log(
8289
+ "Hint: ~/.agents/skills is missing. Create/populate it before starting runtime to enable vault discovery.",
8290
+ );
8291
+ }
7986
8292
  return;
7987
8293
  }
7988
8294
  const isRunning = container.status.toLowerCase().startsWith("up ");
7989
8295
  console.log(`Container status: ${container.status}`);
7990
8296
  console.log(`Container image: ${container.image}`);
8297
+ const mounts = await inspectDockerContainerMounts({ name: containerName, cwd });
8298
+ const skillsMount = mounts.find(
8299
+ (mount) => String(mount.Destination || "") === "/workflows/skills",
8300
+ );
8301
+ if (skillsMount) {
8302
+ const source = String(skillsMount.Source || "(unknown)");
8303
+ console.log(`Skills mount: ${source} -> /workflows/skills`);
8304
+ if (!(await pathExists(source))) {
8305
+ console.log(
8306
+ "Hint: mounted skill source path is missing on host; vault discovery may return zero skills.",
8307
+ );
8308
+ }
8309
+ } else {
8310
+ console.log("Skills mount: missing");
8311
+ console.log(
8312
+ "Hint: recreate runtime with a skills mount (host ~/.agents/skills -> /workflows/skills).",
8313
+ );
8314
+ }
7991
8315
  if (isRunning) {
7992
8316
  const hostPort =
7993
8317
  (await resolveDockerContainerHostPort({
@@ -8050,22 +8374,32 @@ async function runMcpRuntimeUp(options) {
8050
8374
  }
8051
8375
 
8052
8376
  const cbxRoot = resolveCbxRootPath({ scope, cwd });
8377
+ const skillsRoot = path.join(os.homedir(), ".agents", "skills");
8378
+ const skillsRootExists = await pathExists(skillsRoot);
8379
+ const runtimeWarnings = [];
8380
+ if (!skillsRootExists) {
8381
+ runtimeWarnings.push(
8382
+ `Skill mount source is missing: ${skillsRoot}. Runtime will start without /workflows/skills and vault discovery can return zero skills.`,
8383
+ );
8384
+ }
8053
8385
  await mkdir(cbxRoot, { recursive: true });
8386
+ const dockerArgs = [
8387
+ "run",
8388
+ "-d",
8389
+ "--name",
8390
+ containerName,
8391
+ "-p",
8392
+ `${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`,
8393
+ "-v",
8394
+ `${cbxRoot}:/root/.cbx`,
8395
+ ];
8396
+ if (skillsRootExists) {
8397
+ dockerArgs.push("-v", `${skillsRoot}:/workflows/skills:ro`);
8398
+ }
8399
+ dockerArgs.push("-e", "CBX_MCP_TRANSPORT=streamable-http", image);
8054
8400
  await execFile(
8055
8401
  "docker",
8056
- [
8057
- "run",
8058
- "-d",
8059
- "--name",
8060
- containerName,
8061
- "-p",
8062
- `${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`,
8063
- "-v",
8064
- `${cbxRoot}:/root/.cbx`,
8065
- "-e",
8066
- "CBX_MCP_TRANSPORT=streamable-http",
8067
- image,
8068
- ],
8402
+ dockerArgs,
8069
8403
  { cwd },
8070
8404
  );
8071
8405
 
@@ -8080,9 +8414,20 @@ async function runMcpRuntimeUp(options) {
8080
8414
  console.log(`Update policy: ${updatePolicy}`);
8081
8415
  console.log(`Build local: ${buildLocal ? "yes" : "no"}`);
8082
8416
  console.log(`Mount: ${cbxRoot} -> /root/.cbx`);
8417
+ if (skillsRootExists) {
8418
+ console.log(`Mount: ${skillsRoot} -> /workflows/skills (ro)`);
8419
+ } else {
8420
+ console.log("Mount: /workflows/skills (not mounted - source missing)");
8421
+ }
8083
8422
  console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
8084
8423
  console.log(`Status: ${running ? running.status : "started"}`);
8085
8424
  console.log(`Endpoint: http://127.0.0.1:${hostPort}/mcp`);
8425
+ if (runtimeWarnings.length > 0) {
8426
+ console.log("Warnings:");
8427
+ for (const warning of runtimeWarnings) {
8428
+ console.log(`- ${warning}`);
8429
+ }
8430
+ }
8086
8431
  } catch (error) {
8087
8432
  console.error(`\nError: ${error.message}`);
8088
8433
  process.exit(1);
@@ -8531,6 +8876,18 @@ const mcpCommand = program
8531
8876
  .command("mcp")
8532
8877
  .description("Manage Cubis MCP runtime catalogs and tool discovery");
8533
8878
 
8879
+ mcpCommand
8880
+ .command("serve")
8881
+ .description("Launch bundled Cubis Foundry MCP server (canonical local entrypoint)")
8882
+ .option("--transport <transport>", "stdio|http", "stdio")
8883
+ .option("--scope <scope>", "auto|global|project", "auto")
8884
+ .option("--port <port>", "HTTP port override")
8885
+ .option("--host <host>", "HTTP host override")
8886
+ .option("--scan-only", "scan vault and exit")
8887
+ .option("--config <path>", "explicit MCP server config file path")
8888
+ .option("--debug", "enable debug logging in MCP server")
8889
+ .action(runMcpServe);
8890
+
8534
8891
  const mcpToolsCommand = mcpCommand
8535
8892
  .command("tools")
8536
8893
  .description("Discover and inspect upstream MCP tool catalogs");