@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.
- package/README.md +22 -1
- package/bin/cubis.js +449 -7
- 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
|
-
|
|
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
|
});
|