@cubis/foundry 0.3.40 → 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 (29) hide show
  1. package/README.md +64 -0
  2. package/bin/cubis.js +347 -22
  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/README.md CHANGED
@@ -193,6 +193,17 @@ cbx workflows install --platform codex --bundle agent-environment-setup --postma
193
193
 
194
194
  If active Postman env var (for example `POSTMAN_API_KEY_DEFAULT`) is available and `--yes` is not used, installer can show workspace chooser and save selected `workspaceId` in active Postman profile.
195
195
 
196
+ `--postman` now installs side-by-side MCP topology by default:
197
+ - direct Postman MCP server (`postman`)
198
+ - direct Stitch MCP server where applicable (`StitchMCP` for Antigravity)
199
+ - local Foundry MCP command server (`cubis-foundry` via `cbx mcp serve --transport stdio --scope auto`)
200
+
201
+ To opt out of Foundry MCP registration during install:
202
+
203
+ ```bash
204
+ cbx workflows install --platform codex --bundle agent-environment-setup --postman --no-foundry-mcp
205
+ ```
206
+
196
207
  ### Manual workspace ID
197
208
 
198
209
  ```bash
@@ -245,14 +256,17 @@ Runtime target patching:
245
256
  Codex:
246
257
  - Global MCP runtime target: `~/.codex/config.toml` (via `codex mcp add/remove`)
247
258
  - Project MCP runtime target: `<workspace>/.vscode/mcp.json`
259
+ - Foundry side-by-side server id: `cubis-foundry` (command: `cbx mcp serve --transport stdio --scope auto`)
248
260
 
249
261
  Antigravity:
250
262
  - Global runtime target: `~/.gemini/settings.json` (`mcpServers`)
251
263
  - Project runtime target: `<workspace>/.gemini/settings.json` (`mcpServers`)
264
+ - Foundry side-by-side server id: `cubis-foundry` (command template)
252
265
 
253
266
  Copilot:
254
267
  - Global runtime target: `~/.copilot/mcp-config.json` (`servers`)
255
268
  - Project runtime target: `<workspace>/.vscode/mcp.json` (`servers`)
269
+ - Foundry side-by-side server id: `cubis-foundry` (stdio command server)
256
270
 
257
271
  ## Command Reference
258
272
 
@@ -289,6 +303,19 @@ cbx mcp tools list --service postman --scope global
289
303
  cbx mcp tools list --service stitch --scope global
290
304
  ```
291
305
 
306
+ Foundry local serve command (canonical entrypoint for MCP client registration):
307
+
308
+ ```bash
309
+ # stdio (default)
310
+ cbx mcp serve --transport stdio --scope auto
311
+
312
+ # http for local smoke/debug
313
+ cbx mcp serve --transport http --scope auto --host 127.0.0.1 --port 3100
314
+
315
+ # verify vault only
316
+ cbx mcp serve --scan-only
317
+ ```
318
+
292
319
  MCP Docker runtime commands:
293
320
 
294
321
  ```bash
@@ -317,6 +344,18 @@ Optional strict key mode:
317
344
  CBX_MCP_REQUIRE_KEYS=1 npm run test:mcp:docker
318
345
  ```
319
346
 
347
+ Context budget reporting (from MCP skill tools):
348
+
349
+ - Skill tools now include `structuredContent.metrics` with deterministic estimates.
350
+ - Metrics include:
351
+ - `fullCatalogEstimatedTokens`
352
+ - `responseEstimatedTokens`
353
+ - `selectedSkillsEstimatedTokens` or `loadedSkillEstimatedTokens`
354
+ - `estimatedSavingsVsFullCatalog`
355
+ - `estimatedSavingsVsFullCatalogPercent`
356
+ - New rollup tool: `skill_budget_report` for consolidated Skill Log + Context Budget.
357
+ - All token values are estimates using `ceil(char_count / charsPerToken)` (default `charsPerToken=4`), not provider billing tokens.
358
+
320
359
  Install profile flags:
321
360
 
322
361
  ```bash
@@ -398,6 +437,31 @@ If installer says config was skipped:
398
437
  - Re-run with `--overwrite`, or
399
438
  - Use `cbx workflows config` / `cbx workflows config keys ...` to mutate existing config.
400
439
 
440
+ ### Docker runtime starts but skill discovery is zero
441
+
442
+ Cause:
443
+ - Runtime container has no skill mount at `/workflows/skills`.
444
+
445
+ Fix:
446
+ ```bash
447
+ # Ensure host skill vault exists
448
+ ls ~/.agents/skills
449
+
450
+ # Recreate runtime
451
+ cbx mcp runtime up --scope global --name cbx-mcp --replace
452
+
453
+ # Check mount hint
454
+ cbx mcp runtime status --scope global --name cbx-mcp
455
+ ```
456
+
457
+ If `~/.agents/skills` is missing, runtime still starts but will warn and skill discovery may return zero.
458
+
459
+ ### Docker vs stdio behavior
460
+
461
+ - `cbx mcp runtime up` runs HTTP transport in Docker for shared local endpoint (`http://127.0.0.1:<port>/mcp`).
462
+ - `cbx mcp serve --transport stdio` runs local stdio transport for command-based MCP clients.
463
+ - Prefer stdio command server entries (`cubis-foundry`) for direct client integrations; use Docker runtime for explicit HTTP endpoint use cases.
464
+
401
465
  ### Duplicate skills shown in UI after older installs
402
466
 
403
467
  Installer now auto-cleans nested duplicate skills (for example duplicates under `postman/*`).
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);
@@ -2821,8 +2848,11 @@ function buildManagedWorkflowBlock(platformId, workflows) {
2821
2848
  lines.push("## CBX Workflow Routing (auto-managed)");
2822
2849
  if (platformId === "codex") {
2823
2850
  lines.push("Use Codex callable wrappers:");
2824
- lines.push("- Workflows: `$workflow-<name>`");
2825
- 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
+ );
2826
2856
  lines.push("");
2827
2857
 
2828
2858
  if (workflows.length === 0) {
@@ -2832,18 +2862,16 @@ function buildManagedWorkflowBlock(platformId, workflows) {
2832
2862
  const triggerPreview = workflow.triggers.slice(0, 2).join(", ");
2833
2863
  const hint = triggerPreview ? ` (${triggerPreview})` : "";
2834
2864
  lines.push(
2835
- `- \`${workflow.command}\` -> \`$${CODEX_WORKFLOW_SKILL_PREFIX}${workflow.id}\`${hint}`,
2865
+ `- ${workflow.command} -> $${CODEX_WORKFLOW_SKILL_PREFIX}${workflow.id}${hint}`,
2836
2866
  );
2837
2867
  }
2838
2868
  }
2839
2869
 
2840
2870
  lines.push("");
2841
2871
  lines.push("Selection policy:");
2842
- lines.push("1. If user names a `$workflow-*`, use it directly.");
2872
+ lines.push("1. If user names a $workflow-*, use it directly.");
2843
2873
  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
- );
2874
+ lines.push("3. Use $agent-* wrappers only when role specialization is needed.");
2847
2875
  lines.push("");
2848
2876
  lines.push("<!-- cbx:workflows:auto:end -->");
2849
2877
  return lines.join("\n");
@@ -3316,6 +3344,47 @@ function buildVsCodePostmanServer({
3316
3344
  };
3317
3345
  }
3318
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
+
3319
3388
  function buildGeminiPostmanServer({
3320
3389
  apiKeyEnvVar = POSTMAN_API_KEY_ENV_VAR,
3321
3390
  mcpUrl = POSTMAN_MCP_URL,
@@ -3328,12 +3397,19 @@ function buildGeminiPostmanServer({
3328
3397
  };
3329
3398
  }
3330
3399
 
3400
+ function buildGeminiFoundryServer({ scope = "auto" } = {}) {
3401
+ return {
3402
+ command: FOUNDRY_MCP_COMMAND,
3403
+ args: buildFoundryServeArgs({ scope }),
3404
+ env: {},
3405
+ };
3406
+ }
3407
+
3331
3408
  function buildGeminiStitchServer({
3332
3409
  apiKeyEnvVar = STITCH_API_KEY_ENV_VAR,
3333
3410
  mcpUrl = STITCH_MCP_URL,
3334
3411
  }) {
3335
3412
  return {
3336
- $typeName: "exa.cascade_plugins_pb.CascadePluginCommandTemplate",
3337
3413
  command: "npx",
3338
3414
  args: [
3339
3415
  "-y",
@@ -3752,6 +3828,7 @@ async function applyPostmanMcpForPlatform({
3752
3828
  stitchApiKeyEnvVar,
3753
3829
  stitchMcpUrl,
3754
3830
  includeStitchMcp = false,
3831
+ includeFoundryMcp = true,
3755
3832
  dryRun = false,
3756
3833
  cwd = process.cwd(),
3757
3834
  }) {
@@ -3777,6 +3854,13 @@ async function applyPostmanMcpForPlatform({
3777
3854
  apiKeyEnvVar,
3778
3855
  mcpUrl,
3779
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
+ }
3780
3864
  if (includeStitchMcp) {
3781
3865
  mcpServers[STITCH_MCP_SERVER_ID] = buildGeminiStitchServer({
3782
3866
  apiKeyEnvVar: stitchApiKeyEnvVar,
@@ -3806,6 +3890,28 @@ async function applyPostmanMcpForPlatform({
3806
3890
  targetPath: configPath,
3807
3891
  updater: (existing) => {
3808
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
+
3809
3915
  const servers =
3810
3916
  next.servers &&
3811
3917
  typeof next.servers === "object" &&
@@ -3816,6 +3922,13 @@ async function applyPostmanMcpForPlatform({
3816
3922
  apiKeyEnvVar,
3817
3923
  mcpUrl,
3818
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
+ }
3819
3932
  next.servers = servers;
3820
3933
  return next;
3821
3934
  },
@@ -3847,6 +3960,13 @@ async function applyPostmanMcpForPlatform({
3847
3960
  apiKeyEnvVar,
3848
3961
  mcpUrl,
3849
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
+ }
3850
3970
  next.servers = servers;
3851
3971
  return next;
3852
3972
  },
@@ -3877,6 +3997,13 @@ async function applyPostmanMcpForPlatform({
3877
3997
  } catch {
3878
3998
  // Best effort. Add will still run and becomes source of truth.
3879
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
+ }
3880
4007
 
3881
4008
  try {
3882
4009
  await execFile(
@@ -3905,6 +4032,27 @@ async function applyPostmanMcpForPlatform({
3905
4032
  };
3906
4033
  }
3907
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
+
3908
4056
  return {
3909
4057
  kind: "codex-cli",
3910
4058
  scope: mcpScope,
@@ -4001,6 +4149,7 @@ async function resolvePostmanInstallSelection({
4001
4149
  );
4002
4150
  const mcpBuildLocal = Boolean(options.mcpBuildLocal);
4003
4151
  const mcpToolSync = options.mcpToolSync !== false;
4152
+ const foundryMcpEnabled = options.foundryMcp !== false;
4004
4153
 
4005
4154
  const canPrompt = !options.yes && process.stdin.isTTY;
4006
4155
  if (canPrompt && !hasWorkspaceOption) {
@@ -4228,6 +4377,7 @@ async function resolvePostmanInstallSelection({
4228
4377
  mcpBuildLocal,
4229
4378
  dockerImageAction,
4230
4379
  mcpToolSync,
4380
+ foundryMcpEnabled,
4231
4381
  runtimeSkipped,
4232
4382
  defaultWorkspaceId: defaultWorkspaceId ?? null,
4233
4383
  workspaceSelectionSource,
@@ -4457,6 +4607,7 @@ async function configurePostmanInstallArtifacts({
4457
4607
  stitchApiKeyEnvVar: effectiveStitchApiKeyEnvVar,
4458
4608
  stitchMcpUrl: effectiveStitchMcpUrl,
4459
4609
  includeStitchMcp: shouldInstallStitch,
4610
+ includeFoundryMcp: postmanSelection.foundryMcpEnabled,
4460
4611
  dryRun,
4461
4612
  cwd,
4462
4613
  });
@@ -4495,6 +4646,7 @@ async function configurePostmanInstallArtifacts({
4495
4646
  mcpBuildLocal: postmanSelection.mcpBuildLocal,
4496
4647
  dockerImageAction: postmanSelection.dockerImageAction,
4497
4648
  mcpToolSync: postmanSelection.mcpToolSync,
4649
+ foundryMcpEnabled: postmanSelection.foundryMcpEnabled,
4498
4650
  apiKeySource: effectiveApiKeySource,
4499
4651
  stitchApiKeySource: effectiveStitchApiKeySource,
4500
4652
  defaultWorkspaceId: effectiveDefaultWorkspaceId,
@@ -5454,6 +5606,9 @@ function printPostmanSetupSummary({ postmanSetup }) {
5454
5606
  console.log(`- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`);
5455
5607
  console.log(`- MCP image prepare: ${postmanSetup.dockerImageAction}`);
5456
5608
  console.log(`- MCP tool sync: ${postmanSetup.mcpToolSync ? "enabled" : "disabled"}`);
5609
+ console.log(
5610
+ `- Foundry MCP side-by-side: ${postmanSetup.foundryMcpEnabled ? "enabled" : "disabled"}`,
5611
+ );
5457
5612
  console.log(`- Postman API key source: ${postmanSetup.apiKeySource}`);
5458
5613
  if (postmanSetup.stitchApiKeySource) {
5459
5614
  console.log(`- Stitch API key source: ${postmanSetup.stitchApiKeySource}`);
@@ -5971,6 +6126,10 @@ function withInstallOptions(command) {
5971
6126
  "enable automatic MCP tool catalog sync (default: enabled)",
5972
6127
  )
5973
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
+ )
5974
6133
  .option(
5975
6134
  "--terminal-integration",
5976
6135
  "Antigravity only: enable terminal verification integration (prompts for verifier when interactive)",
@@ -7933,6 +8092,111 @@ async function runMcpToolsList(options) {
7933
8092
  }
7934
8093
  }
7935
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
+
7936
8200
  function resolveCbxRootPath({ scope, cwd = process.cwd() }) {
7937
8201
  if (scope === "global") {
7938
8202
  return path.join(os.homedir(), ".cbx");
@@ -7998,6 +8262,8 @@ async function runMcpRuntimeStatus(options) {
7998
8262
  const container = dockerAvailable
7999
8263
  ? await inspectDockerContainerByName({ name: containerName, cwd })
8000
8264
  : null;
8265
+ const skillsRoot = path.join(os.homedir(), ".agents", "skills");
8266
+ const skillsRootExists = await pathExists(skillsRoot);
8001
8267
 
8002
8268
  console.log(`Scope: ${scope}`);
8003
8269
  console.log(`Config file: ${defaults.configPath}`);
@@ -8007,6 +8273,9 @@ async function runMcpRuntimeStatus(options) {
8007
8273
  console.log(`Configured image: ${defaults.defaults.image}`);
8008
8274
  console.log(`Configured update policy: ${defaults.defaults.updatePolicy}`);
8009
8275
  console.log(`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`);
8276
+ console.log(
8277
+ `Host skills root: ${skillsRoot} (${skillsRootExists ? "present" : "missing"})`,
8278
+ );
8010
8279
  console.log(`Docker available: ${dockerAvailable ? "yes" : "no"}`);
8011
8280
  console.log(`Container name: ${containerName}`);
8012
8281
  if (!dockerAvailable) {
@@ -8015,11 +8284,34 @@ async function runMcpRuntimeStatus(options) {
8015
8284
  }
8016
8285
  if (!container) {
8017
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
+ }
8018
8292
  return;
8019
8293
  }
8020
8294
  const isRunning = container.status.toLowerCase().startsWith("up ");
8021
8295
  console.log(`Container status: ${container.status}`);
8022
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
+ }
8023
8315
  if (isRunning) {
8024
8316
  const hostPort =
8025
8317
  (await resolveDockerContainerHostPort({
@@ -8082,22 +8374,32 @@ async function runMcpRuntimeUp(options) {
8082
8374
  }
8083
8375
 
8084
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
+ }
8085
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);
8086
8400
  await execFile(
8087
8401
  "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
- ],
8402
+ dockerArgs,
8101
8403
  { cwd },
8102
8404
  );
8103
8405
 
@@ -8112,9 +8414,20 @@ async function runMcpRuntimeUp(options) {
8112
8414
  console.log(`Update policy: ${updatePolicy}`);
8113
8415
  console.log(`Build local: ${buildLocal ? "yes" : "no"}`);
8114
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
+ }
8115
8422
  console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
8116
8423
  console.log(`Status: ${running ? running.status : "started"}`);
8117
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
+ }
8118
8431
  } catch (error) {
8119
8432
  console.error(`\nError: ${error.message}`);
8120
8433
  process.exit(1);
@@ -8563,6 +8876,18 @@ const mcpCommand = program
8563
8876
  .command("mcp")
8564
8877
  .description("Manage Cubis MCP runtime catalogs and tool discovery");
8565
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
+
8566
8891
  const mcpToolsCommand = mcpCommand
8567
8892
  .command("tools")
8568
8893
  .description("Discover and inspect upstream MCP tool catalogs");