@cubis/foundry 0.3.36 → 0.3.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +22 -1
  2. package/bin/cubis.js +449 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -273,9 +273,14 @@ cbx workflows install --platform codex --bundle agent-environment-setup --postma
273
273
  --mcp-runtime docker \
274
274
  --mcp-fallback local \
275
275
  --mcp-image ghcr.io/cubis/foundry-mcp:0.1.0 \
276
- --mcp-update-policy pinned
276
+ --mcp-update-policy pinned \
277
+ --mcp-build-local # optional: build image locally instead of docker pull
277
278
  ```
278
279
 
280
+ When `--mcp-runtime docker` is selected and Docker is available, install now prepares the image automatically:
281
+ - Pulls the image by default (`docker pull`)
282
+ - Or builds locally when `--mcp-build-local` is set
283
+
279
284
  MCP tool catalog commands:
280
285
 
281
286
  ```bash
@@ -284,6 +289,22 @@ cbx mcp tools list --service postman --scope global
284
289
  cbx mcp tools list --service stitch --scope global
285
290
  ```
286
291
 
292
+ MCP Docker runtime commands:
293
+
294
+ ```bash
295
+ # Inspect runtime/container state
296
+ cbx mcp runtime status --scope global --name cbx-mcp
297
+
298
+ # Start runtime container (pull/build image first as needed)
299
+ cbx mcp runtime up --scope global --name cbx-mcp --port 3310
300
+
301
+ # Recreate existing container
302
+ cbx mcp runtime up --scope global --name cbx-mcp --replace
303
+
304
+ # Stop/remove runtime container
305
+ cbx mcp runtime down --name cbx-mcp
306
+ ```
307
+
287
308
  Docker E2E MCP check (single command):
288
309
 
289
310
  ```bash
package/bin/cubis.js CHANGED
@@ -168,6 +168,9 @@ const DEFAULT_MCP_RUNTIME = "docker";
168
168
  const DEFAULT_MCP_FALLBACK = "local";
169
169
  const DEFAULT_MCP_UPDATE_POLICY = "pinned";
170
170
  const DEFAULT_MCP_DOCKER_IMAGE = "ghcr.io/cubis/foundry-mcp:0.1.0";
171
+ const DEFAULT_MCP_DOCKER_CONTAINER_NAME = "cbx-mcp";
172
+ const DEFAULT_MCP_DOCKER_HOST_PORT = 3310;
173
+ const MCP_DOCKER_CONTAINER_PORT = 3100;
171
174
  const TECH_SCAN_MAX_FILES = 5000;
172
175
  const TECH_SCAN_IGNORED_DIRS = new Set([
173
176
  ".git",
@@ -405,6 +408,139 @@ function normalizeMcpUpdatePolicy(value, fallback = DEFAULT_MCP_UPDATE_POLICY) {
405
408
  return normalized;
406
409
  }
407
410
 
411
+ function normalizePortNumber(value, fallback = DEFAULT_MCP_DOCKER_HOST_PORT) {
412
+ if (value === undefined || value === null || value === "") return fallback;
413
+ const parsed = Number.parseInt(String(value).trim(), 10);
414
+ if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
415
+ throw new Error(
416
+ `Invalid port '${value}'. Use an integer between 1 and 65535.`,
417
+ );
418
+ }
419
+ return parsed;
420
+ }
421
+
422
+ function parseDockerPsRow(line) {
423
+ const [id, image, status, names] = String(line || "").split("\t");
424
+ if (!id || !names) return null;
425
+ return {
426
+ id: id.trim(),
427
+ image: String(image || "").trim(),
428
+ status: String(status || "").trim(),
429
+ name: String(names || "").trim(),
430
+ };
431
+ }
432
+
433
+ async function checkDockerAvailable({ cwd = process.cwd() } = {}) {
434
+ try {
435
+ await execFile("docker", ["ps"], { cwd });
436
+ return true;
437
+ } catch {
438
+ return false;
439
+ }
440
+ }
441
+
442
+ async function checkDockerImageExists({ image, cwd = process.cwd() }) {
443
+ try {
444
+ await execFile("docker", ["image", "inspect", image], { cwd });
445
+ return true;
446
+ } catch {
447
+ return false;
448
+ }
449
+ }
450
+
451
+ async function pullMcpDockerImage({ image, cwd = process.cwd() }) {
452
+ await execFile("docker", ["pull", image], { cwd });
453
+ return {
454
+ action: "pulled",
455
+ image,
456
+ };
457
+ }
458
+
459
+ async function buildMcpDockerImageLocal({ image, cwd = process.cwd() }) {
460
+ const dockerContext = path.join(packageRoot(), "mcp");
461
+ if (!(await pathExists(path.join(dockerContext, "Dockerfile")))) {
462
+ throw new Error(`MCP Dockerfile is missing at ${dockerContext}.`);
463
+ }
464
+ await execFile("docker", ["build", "-t", image, dockerContext], { cwd });
465
+ return {
466
+ action: "built-local",
467
+ image,
468
+ context: dockerContext,
469
+ };
470
+ }
471
+
472
+ async function ensureMcpDockerImage({
473
+ image,
474
+ updatePolicy,
475
+ buildLocal = false,
476
+ cwd = process.cwd(),
477
+ }) {
478
+ if (buildLocal) {
479
+ return buildMcpDockerImageLocal({ image, cwd });
480
+ }
481
+ if (updatePolicy === "pinned") {
482
+ const exists = await checkDockerImageExists({ image, cwd });
483
+ if (exists) {
484
+ return {
485
+ action: "already-present",
486
+ image,
487
+ };
488
+ }
489
+ }
490
+ return pullMcpDockerImage({ image, cwd });
491
+ }
492
+
493
+ async function inspectDockerContainerByName({
494
+ name,
495
+ cwd = process.cwd(),
496
+ }) {
497
+ try {
498
+ const { stdout } = await execFile(
499
+ "docker",
500
+ [
501
+ "ps",
502
+ "-a",
503
+ "--filter",
504
+ `name=^/${name}$`,
505
+ "--format",
506
+ "{{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}",
507
+ ],
508
+ { cwd },
509
+ );
510
+ const row = String(stdout || "")
511
+ .trim()
512
+ .split(/\r?\n/)
513
+ .map((line) => parseDockerPsRow(line))
514
+ .find(Boolean);
515
+ if (!row || row.name !== name) return null;
516
+ return row;
517
+ } catch {
518
+ return null;
519
+ }
520
+ }
521
+
522
+ async function resolveDockerContainerHostPort({
523
+ name,
524
+ containerPort = MCP_DOCKER_CONTAINER_PORT,
525
+ cwd = process.cwd(),
526
+ }) {
527
+ try {
528
+ const { stdout } = await execFile(
529
+ "docker",
530
+ ["port", name, `${containerPort}/tcp`],
531
+ { cwd },
532
+ );
533
+ const line = String(stdout || "")
534
+ .trim()
535
+ .split(/\r?\n/)[0];
536
+ if (!line) return null;
537
+ const match = line.match(/:(\d+)\s*$/);
538
+ return match ? Number.parseInt(match[1], 10) : null;
539
+ } catch {
540
+ return null;
541
+ }
542
+ }
543
+
408
544
  function isPathInsideRoot(targetPath, rootPath) {
409
545
  const target = path.resolve(targetPath);
410
546
  const root = path.resolve(rootPath);
@@ -3836,6 +3972,7 @@ async function resolvePostmanInstallSelection({
3836
3972
  options.mcpUpdatePolicy,
3837
3973
  DEFAULT_MCP_UPDATE_POLICY,
3838
3974
  );
3975
+ const mcpBuildLocal = Boolean(options.mcpBuildLocal);
3839
3976
  const mcpToolSync = options.mcpToolSync !== false;
3840
3977
 
3841
3978
  const canPrompt = !options.yes && process.stdin.isTTY;
@@ -3927,14 +4064,9 @@ async function resolvePostmanInstallSelection({
3927
4064
 
3928
4065
  let effectiveRuntime = requestedRuntime;
3929
4066
  let runtimeSkipped = false;
4067
+ let dockerImageAction = "not-requested";
3930
4068
  if (requestedRuntime === "docker") {
3931
- let dockerAvailable = false;
3932
- try {
3933
- await execFile("docker", ["ps"], { cwd });
3934
- dockerAvailable = true;
3935
- } catch {
3936
- dockerAvailable = false;
3937
- }
4069
+ const dockerAvailable = await checkDockerAvailable({ cwd });
3938
4070
  if (!dockerAvailable) {
3939
4071
  if (requestedFallback === "fail") {
3940
4072
  throw new Error(
@@ -3952,6 +4084,35 @@ async function resolvePostmanInstallSelection({
3952
4084
  "Docker runtime unavailable; falling back to local runtime (--mcp-fallback=local).",
3953
4085
  );
3954
4086
  }
4087
+ } else {
4088
+ try {
4089
+ const ensured = await ensureMcpDockerImage({
4090
+ image: requestedImage,
4091
+ updatePolicy: requestedUpdatePolicy,
4092
+ buildLocal: mcpBuildLocal,
4093
+ cwd,
4094
+ });
4095
+ dockerImageAction = ensured.action;
4096
+ } catch (error) {
4097
+ if (requestedFallback === "fail") {
4098
+ throw new Error(
4099
+ `Docker runtime requested but MCP image preparation failed (${error.message}).`,
4100
+ );
4101
+ }
4102
+ if (requestedFallback === "skip") {
4103
+ runtimeSkipped = true;
4104
+ dockerImageAction = "failed";
4105
+ warnings.push(
4106
+ `MCP Docker image preparation failed; skipping MCP runtime patch because --mcp-fallback=skip. (${error.message})`,
4107
+ );
4108
+ } else {
4109
+ effectiveRuntime = "local";
4110
+ dockerImageAction = "failed";
4111
+ warnings.push(
4112
+ `MCP Docker image preparation failed; falling back to local runtime (--mcp-fallback=local). (${error.message})`,
4113
+ );
4114
+ }
4115
+ }
3955
4116
  }
3956
4117
  }
3957
4118
 
@@ -3993,6 +4154,7 @@ async function resolvePostmanInstallSelection({
3993
4154
  docker: {
3994
4155
  image: requestedImage,
3995
4156
  updatePolicy: requestedUpdatePolicy,
4157
+ buildLocal: mcpBuildLocal,
3996
4158
  },
3997
4159
  catalog: {
3998
4160
  toolSync: mcpToolSync,
@@ -4036,6 +4198,8 @@ async function resolvePostmanInstallSelection({
4036
4198
  mcpFallback: requestedFallback,
4037
4199
  mcpImage: requestedImage,
4038
4200
  mcpUpdatePolicy: requestedUpdatePolicy,
4201
+ mcpBuildLocal,
4202
+ dockerImageAction,
4039
4203
  mcpToolSync,
4040
4204
  runtimeSkipped,
4041
4205
  defaultWorkspaceId: defaultWorkspaceId ?? null,
@@ -4301,6 +4465,8 @@ async function configurePostmanInstallArtifacts({
4301
4465
  mcpFallback: postmanSelection.mcpFallback,
4302
4466
  mcpImage: postmanSelection.mcpImage,
4303
4467
  mcpUpdatePolicy: postmanSelection.mcpUpdatePolicy,
4468
+ mcpBuildLocal: postmanSelection.mcpBuildLocal,
4469
+ dockerImageAction: postmanSelection.dockerImageAction,
4304
4470
  mcpToolSync: postmanSelection.mcpToolSync,
4305
4471
  apiKeySource: effectiveApiKeySource,
4306
4472
  stitchApiKeySource: effectiveStitchApiKeySource,
@@ -5254,6 +5420,8 @@ function printPostmanSetupSummary({ postmanSetup }) {
5254
5420
  console.log(`- MCP fallback: ${postmanSetup.mcpFallback}`);
5255
5421
  console.log(`- MCP image: ${postmanSetup.mcpImage}`);
5256
5422
  console.log(`- MCP update policy: ${postmanSetup.mcpUpdatePolicy}`);
5423
+ console.log(`- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`);
5424
+ console.log(`- MCP image prepare: ${postmanSetup.dockerImageAction}`);
5257
5425
  console.log(`- MCP tool sync: ${postmanSetup.mcpToolSync ? "enabled" : "disabled"}`);
5258
5426
  console.log(`- Postman API key source: ${postmanSetup.apiKeySource}`);
5259
5427
  if (postmanSetup.stitchApiKeySource) {
@@ -5763,6 +5931,10 @@ function withInstallOptions(command) {
5763
5931
  "MCP image update policy: pinned|latest",
5764
5932
  DEFAULT_MCP_UPDATE_POLICY,
5765
5933
  )
5934
+ .option(
5935
+ "--mcp-build-local",
5936
+ "build MCP Docker image from local package mcp/ directory instead of pulling",
5937
+ )
5766
5938
  .option(
5767
5939
  "--mcp-tool-sync",
5768
5940
  "enable automatic MCP tool catalog sync (default: enabled)",
@@ -7729,6 +7901,220 @@ async function runMcpToolsList(options) {
7729
7901
  }
7730
7902
  }
7731
7903
 
7904
+ function resolveCbxRootPath({ scope, cwd = process.cwd() }) {
7905
+ if (scope === "global") {
7906
+ return path.join(os.homedir(), ".cbx");
7907
+ }
7908
+ const workspaceRoot = findWorkspaceRoot(cwd);
7909
+ return path.join(workspaceRoot, ".cbx");
7910
+ }
7911
+
7912
+ function resolveMcpRuntimeDefaultsFromConfig(configValue) {
7913
+ const mcp =
7914
+ configValue && typeof configValue.mcp === "object" && !Array.isArray(configValue.mcp)
7915
+ ? configValue.mcp
7916
+ : {};
7917
+ const docker =
7918
+ mcp && typeof mcp.docker === "object" && !Array.isArray(mcp.docker)
7919
+ ? mcp.docker
7920
+ : {};
7921
+
7922
+ const runtime = normalizeMcpRuntime(mcp.runtime, DEFAULT_MCP_RUNTIME);
7923
+ const fallback = normalizeMcpFallback(mcp.fallback, DEFAULT_MCP_FALLBACK);
7924
+ const image =
7925
+ normalizePostmanApiKey(docker.image) || DEFAULT_MCP_DOCKER_IMAGE;
7926
+ const updatePolicy = normalizeMcpUpdatePolicy(
7927
+ docker.updatePolicy,
7928
+ DEFAULT_MCP_UPDATE_POLICY,
7929
+ );
7930
+ const buildLocal = Boolean(docker.buildLocal);
7931
+
7932
+ return {
7933
+ runtime,
7934
+ fallback,
7935
+ image,
7936
+ updatePolicy,
7937
+ buildLocal,
7938
+ };
7939
+ }
7940
+
7941
+ async function loadMcpRuntimeDefaults({ scope, cwd = process.cwd() }) {
7942
+ const configPath = resolveCbxConfigPath({ scope, cwd });
7943
+ const existing = await readJsonFileIfExists(configPath);
7944
+ const configValue =
7945
+ existing.value &&
7946
+ typeof existing.value === "object" &&
7947
+ !Array.isArray(existing.value)
7948
+ ? existing.value
7949
+ : {};
7950
+ return {
7951
+ configPath,
7952
+ defaults: resolveMcpRuntimeDefaultsFromConfig(configValue),
7953
+ hasConfig: existing.exists,
7954
+ };
7955
+ }
7956
+
7957
+ async function runMcpRuntimeStatus(options) {
7958
+ try {
7959
+ const opts = resolveActionOptions(options);
7960
+ const cwd = process.cwd();
7961
+ const scope = normalizeMcpScope(opts.scope, "global");
7962
+ const defaults = await loadMcpRuntimeDefaults({ scope, cwd });
7963
+ const containerName =
7964
+ normalizePostmanApiKey(opts.name) || DEFAULT_MCP_DOCKER_CONTAINER_NAME;
7965
+ const dockerAvailable = await checkDockerAvailable({ cwd });
7966
+ const container = dockerAvailable
7967
+ ? await inspectDockerContainerByName({ name: containerName, cwd })
7968
+ : null;
7969
+
7970
+ console.log(`Scope: ${scope}`);
7971
+ console.log(`Config file: ${defaults.configPath}`);
7972
+ console.log(`Config present: ${defaults.hasConfig ? "yes" : "no"}`);
7973
+ console.log(`Configured runtime: ${defaults.defaults.runtime}`);
7974
+ console.log(`Configured fallback: ${defaults.defaults.fallback}`);
7975
+ console.log(`Configured image: ${defaults.defaults.image}`);
7976
+ console.log(`Configured update policy: ${defaults.defaults.updatePolicy}`);
7977
+ console.log(`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`);
7978
+ console.log(`Docker available: ${dockerAvailable ? "yes" : "no"}`);
7979
+ console.log(`Container name: ${containerName}`);
7980
+ if (!dockerAvailable) {
7981
+ console.log("Container status: unavailable (docker not reachable)");
7982
+ return;
7983
+ }
7984
+ if (!container) {
7985
+ console.log("Container status: not found");
7986
+ return;
7987
+ }
7988
+ const isRunning = container.status.toLowerCase().startsWith("up ");
7989
+ console.log(`Container status: ${container.status}`);
7990
+ console.log(`Container image: ${container.image}`);
7991
+ if (isRunning) {
7992
+ const hostPort =
7993
+ (await resolveDockerContainerHostPort({
7994
+ name: containerName,
7995
+ containerPort: MCP_DOCKER_CONTAINER_PORT,
7996
+ cwd,
7997
+ })) || DEFAULT_MCP_DOCKER_HOST_PORT;
7998
+ console.log(
7999
+ `Endpoint: http://127.0.0.1:${hostPort}/mcp`,
8000
+ );
8001
+ }
8002
+ } catch (error) {
8003
+ console.error(`\nError: ${error.message}`);
8004
+ process.exit(1);
8005
+ }
8006
+ }
8007
+
8008
+ async function runMcpRuntimeUp(options) {
8009
+ try {
8010
+ const opts = resolveActionOptions(options);
8011
+ const cwd = process.cwd();
8012
+ const scope = normalizeMcpScope(opts.scope, "global");
8013
+ const defaults = await loadMcpRuntimeDefaults({ scope, cwd });
8014
+ const containerName =
8015
+ normalizePostmanApiKey(opts.name) || DEFAULT_MCP_DOCKER_CONTAINER_NAME;
8016
+ const image =
8017
+ normalizePostmanApiKey(opts.image) || defaults.defaults.image;
8018
+ const updatePolicy = normalizeMcpUpdatePolicy(
8019
+ opts.updatePolicy,
8020
+ defaults.defaults.updatePolicy,
8021
+ );
8022
+ const buildLocal = hasCliFlag("--build-local")
8023
+ ? true
8024
+ : defaults.defaults.buildLocal;
8025
+ const hostPort = normalizePortNumber(opts.port, DEFAULT_MCP_DOCKER_HOST_PORT);
8026
+ const replace = Boolean(opts.replace);
8027
+ const dockerAvailable = await checkDockerAvailable({ cwd });
8028
+ if (!dockerAvailable) {
8029
+ throw new Error("Docker is unavailable. Start OrbStack/Docker and retry.");
8030
+ }
8031
+
8032
+ const prepared = await ensureMcpDockerImage({
8033
+ image,
8034
+ updatePolicy,
8035
+ buildLocal,
8036
+ cwd,
8037
+ });
8038
+
8039
+ const existing = await inspectDockerContainerByName({
8040
+ name: containerName,
8041
+ cwd,
8042
+ });
8043
+ if (existing && !replace) {
8044
+ throw new Error(
8045
+ `Container '${containerName}' already exists (${existing.status}). Use --replace to recreate it.`,
8046
+ );
8047
+ }
8048
+ if (existing && replace) {
8049
+ await execFile("docker", ["rm", "-f", containerName], { cwd });
8050
+ }
8051
+
8052
+ const cbxRoot = resolveCbxRootPath({ scope, cwd });
8053
+ await mkdir(cbxRoot, { recursive: true });
8054
+ await execFile(
8055
+ "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
+ ],
8069
+ { cwd },
8070
+ );
8071
+
8072
+ const running = await inspectDockerContainerByName({
8073
+ name: containerName,
8074
+ cwd,
8075
+ });
8076
+ console.log(`Scope: ${scope}`);
8077
+ console.log(`Container: ${containerName}`);
8078
+ console.log(`Image: ${image}`);
8079
+ console.log(`Image prepare: ${prepared.action}`);
8080
+ console.log(`Update policy: ${updatePolicy}`);
8081
+ console.log(`Build local: ${buildLocal ? "yes" : "no"}`);
8082
+ console.log(`Mount: ${cbxRoot} -> /root/.cbx`);
8083
+ console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
8084
+ console.log(`Status: ${running ? running.status : "started"}`);
8085
+ console.log(`Endpoint: http://127.0.0.1:${hostPort}/mcp`);
8086
+ } catch (error) {
8087
+ console.error(`\nError: ${error.message}`);
8088
+ process.exit(1);
8089
+ }
8090
+ }
8091
+
8092
+ async function runMcpRuntimeDown(options) {
8093
+ try {
8094
+ const opts = resolveActionOptions(options);
8095
+ const cwd = process.cwd();
8096
+ const containerName =
8097
+ normalizePostmanApiKey(opts.name) || DEFAULT_MCP_DOCKER_CONTAINER_NAME;
8098
+ const dockerAvailable = await checkDockerAvailable({ cwd });
8099
+ if (!dockerAvailable) {
8100
+ throw new Error("Docker is unavailable. Start OrbStack/Docker and retry.");
8101
+ }
8102
+ const existing = await inspectDockerContainerByName({
8103
+ name: containerName,
8104
+ cwd,
8105
+ });
8106
+ if (!existing) {
8107
+ console.log(`Container '${containerName}' is not present.`);
8108
+ return;
8109
+ }
8110
+ await execFile("docker", ["rm", "-f", containerName], { cwd });
8111
+ console.log(`Removed container '${containerName}'.`);
8112
+ } catch (error) {
8113
+ console.error(`\nError: ${error.message}`);
8114
+ process.exit(1);
8115
+ }
8116
+ }
8117
+
7732
8118
  function printRulesInitSummary({
7733
8119
  platform,
7734
8120
  scope,
@@ -8172,6 +8558,62 @@ mcpToolsCommand
8172
8558
  )
8173
8559
  .action(runMcpToolsList);
8174
8560
 
8561
+ const mcpRuntimeCommand = mcpCommand
8562
+ .command("runtime")
8563
+ .description("Manage local Docker runtime container for Cubis MCP gateway");
8564
+
8565
+ mcpRuntimeCommand
8566
+ .command("status")
8567
+ .description("Show Docker runtime status and configured MCP runtime defaults")
8568
+ .option(
8569
+ "--scope <scope>",
8570
+ "config scope: project|workspace|global|user",
8571
+ "global",
8572
+ )
8573
+ .option(
8574
+ "--name <name>",
8575
+ "container name",
8576
+ DEFAULT_MCP_DOCKER_CONTAINER_NAME,
8577
+ )
8578
+ .action(runMcpRuntimeStatus);
8579
+
8580
+ mcpRuntimeCommand
8581
+ .command("up")
8582
+ .description("Start Docker runtime container for Cubis MCP gateway")
8583
+ .option(
8584
+ "--scope <scope>",
8585
+ "config scope: project|workspace|global|user",
8586
+ "global",
8587
+ )
8588
+ .option(
8589
+ "--name <name>",
8590
+ "container name",
8591
+ DEFAULT_MCP_DOCKER_CONTAINER_NAME,
8592
+ )
8593
+ .option("--image <image:tag>", "docker image to run")
8594
+ .option("--update-policy <policy>", "pinned|latest")
8595
+ .option(
8596
+ "--build-local",
8597
+ "build MCP Docker image from local package mcp/ directory instead of pulling",
8598
+ )
8599
+ .option("--port <port>", "host port to map to container :3100")
8600
+ .option("--replace", "remove existing container with same name before start")
8601
+ .action(runMcpRuntimeUp);
8602
+
8603
+ mcpRuntimeCommand
8604
+ .command("down")
8605
+ .description("Stop and remove Docker runtime container")
8606
+ .option(
8607
+ "--name <name>",
8608
+ "container name",
8609
+ DEFAULT_MCP_DOCKER_CONTAINER_NAME,
8610
+ )
8611
+ .action(runMcpRuntimeDown);
8612
+
8613
+ mcpRuntimeCommand.action(() => {
8614
+ mcpRuntimeCommand.help();
8615
+ });
8616
+
8175
8617
  mcpCommand.action(() => {
8176
8618
  mcpCommand.help();
8177
8619
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubis/foundry",
3
- "version": "0.3.36",
3
+ "version": "0.3.37",
4
4
  "description": "Cubis Foundry CLI for workflow-first AI agent environments",
5
5
  "type": "module",
6
6
  "bin": {