@cubis/foundry 0.3.42 → 0.3.44
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 +31 -2
- package/bin/cubis.js +215 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -303,6 +303,10 @@ cbx mcp tools list --service postman --scope global
|
|
|
303
303
|
cbx mcp tools list --service stitch --scope global
|
|
304
304
|
```
|
|
305
305
|
|
|
306
|
+
Notes:
|
|
307
|
+
- `cbx mcp tools sync` requires `POSTMAN_API_KEY_DEFAULT`.
|
|
308
|
+
- For `--service stitch` or `--service all`, it also requires `STITCH_API_KEY_DEFAULT`.
|
|
309
|
+
|
|
306
310
|
Foundry local serve command (canonical entrypoint for MCP client registration):
|
|
307
311
|
|
|
308
312
|
```bash
|
|
@@ -323,10 +327,10 @@ MCP Docker runtime commands:
|
|
|
323
327
|
cbx mcp runtime status --scope global --name cbx-mcp
|
|
324
328
|
|
|
325
329
|
# Start runtime container (pull/build image first as needed)
|
|
326
|
-
cbx mcp runtime up --scope global --name cbx-mcp --port 3310
|
|
330
|
+
cbx mcp runtime up --scope global --name cbx-mcp --port 3310 --fallback local
|
|
327
331
|
|
|
328
332
|
# Recreate existing container
|
|
329
|
-
cbx mcp runtime up --scope global --name cbx-mcp --replace
|
|
333
|
+
cbx mcp runtime up --scope global --name cbx-mcp --replace --fallback local
|
|
330
334
|
|
|
331
335
|
# Stop/remove runtime container
|
|
332
336
|
cbx mcp runtime down --name cbx-mcp
|
|
@@ -338,6 +342,12 @@ Docker E2E MCP check (single command):
|
|
|
338
342
|
npm run test:mcp:docker
|
|
339
343
|
```
|
|
340
344
|
|
|
345
|
+
If port `3310` is already in use (for example by an existing `cbx-mcp` runtime), use a different port:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
CBX_MCP_PORT=3999 npm run test:mcp:docker
|
|
349
|
+
```
|
|
350
|
+
|
|
341
351
|
Optional strict key mode:
|
|
342
352
|
|
|
343
353
|
```bash
|
|
@@ -382,6 +392,10 @@ cbx workflows config --scope global --show
|
|
|
382
392
|
cbx workflows config --scope global --edit
|
|
383
393
|
cbx workflows config --scope global --workspace-id "<workspace-id>"
|
|
384
394
|
cbx workflows config --scope global --clear-workspace-id
|
|
395
|
+
|
|
396
|
+
# Switch MCP runtime preference quickly
|
|
397
|
+
cbx workflows config --scope project --mcp-runtime local
|
|
398
|
+
cbx workflows config --scope project --mcp-runtime docker --mcp-fallback local
|
|
385
399
|
```
|
|
386
400
|
|
|
387
401
|
`--show` now includes computed `status`:
|
|
@@ -462,6 +476,21 @@ If `~/.agents/skills` is missing, runtime still starts but will warn and skill d
|
|
|
462
476
|
- `cbx mcp serve --transport stdio` runs local stdio transport for command-based MCP clients.
|
|
463
477
|
- Prefer stdio command server entries (`cubis-foundry`) for direct client integrations; use Docker runtime for explicit HTTP endpoint use cases.
|
|
464
478
|
|
|
479
|
+
### Docker endpoint resets at `127.0.0.1:<port>/mcp`
|
|
480
|
+
|
|
481
|
+
If Docker runtime starts but MCP endpoint is unreachable:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
# Check health and hints
|
|
485
|
+
cbx mcp runtime status --scope project --name cbx-mcp
|
|
486
|
+
|
|
487
|
+
# Switch this project to local runtime
|
|
488
|
+
cbx workflows config --scope project --mcp-runtime local
|
|
489
|
+
|
|
490
|
+
# Use direct local server path
|
|
491
|
+
cbx mcp serve --transport stdio --scope auto
|
|
492
|
+
```
|
|
493
|
+
|
|
465
494
|
### Duplicate skills shown in UI after older installs
|
|
466
495
|
|
|
467
496
|
Installer now auto-cleans nested duplicate skills (for example duplicates under `postman/*`).
|
package/bin/cubis.js
CHANGED
|
@@ -7112,6 +7112,49 @@ async function writeConfigFile({
|
|
|
7112
7112
|
: "created";
|
|
7113
7113
|
}
|
|
7114
7114
|
|
|
7115
|
+
async function persistMcpRuntimePreference({
|
|
7116
|
+
scope,
|
|
7117
|
+
runtime,
|
|
7118
|
+
fallback = null,
|
|
7119
|
+
cwd = process.cwd(),
|
|
7120
|
+
generatedBy = "cbx mcp runtime up",
|
|
7121
|
+
}) {
|
|
7122
|
+
const { configPath, existing, existingValue } = await loadConfigForScope({
|
|
7123
|
+
scope,
|
|
7124
|
+
cwd,
|
|
7125
|
+
});
|
|
7126
|
+
if (!existing.exists || !existingValue) {
|
|
7127
|
+
return {
|
|
7128
|
+
action: "skipped",
|
|
7129
|
+
configPath,
|
|
7130
|
+
reason: "config-missing",
|
|
7131
|
+
};
|
|
7132
|
+
}
|
|
7133
|
+
const next = prepareConfigDocument(existingValue, {
|
|
7134
|
+
scope,
|
|
7135
|
+
generatedBy,
|
|
7136
|
+
});
|
|
7137
|
+
if (!next.mcp || typeof next.mcp !== "object" || Array.isArray(next.mcp)) {
|
|
7138
|
+
next.mcp = {};
|
|
7139
|
+
}
|
|
7140
|
+
next.mcp.runtime = runtime;
|
|
7141
|
+
next.mcp.effectiveRuntime = runtime;
|
|
7142
|
+
if (fallback) {
|
|
7143
|
+
next.mcp.fallback = fallback;
|
|
7144
|
+
}
|
|
7145
|
+
const action = await writeConfigFile({
|
|
7146
|
+
configPath,
|
|
7147
|
+
nextConfig: next,
|
|
7148
|
+
existingExists: existing.exists,
|
|
7149
|
+
dryRun: false,
|
|
7150
|
+
});
|
|
7151
|
+
return {
|
|
7152
|
+
action,
|
|
7153
|
+
configPath,
|
|
7154
|
+
reason: null,
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7157
|
+
|
|
7115
7158
|
function toProfileEnvSuffix(profileName) {
|
|
7116
7159
|
const normalized =
|
|
7117
7160
|
normalizeCredentialProfileName(profileName) || DEFAULT_CREDENTIAL_PROFILE_NAME;
|
|
@@ -7642,6 +7685,8 @@ async function runWorkflowConfig(options) {
|
|
|
7642
7685
|
const hasWorkspaceIdOption = opts.workspaceId !== undefined;
|
|
7643
7686
|
const wantsClearWorkspaceId = Boolean(opts.clearWorkspaceId);
|
|
7644
7687
|
const wantsInteractiveEdit = Boolean(opts.edit);
|
|
7688
|
+
const hasMcpRuntimeOption = opts.mcpRuntime !== undefined;
|
|
7689
|
+
const hasMcpFallbackOption = opts.mcpFallback !== undefined;
|
|
7645
7690
|
|
|
7646
7691
|
if (hasWorkspaceIdOption && wantsClearWorkspaceId) {
|
|
7647
7692
|
throw new Error(
|
|
@@ -7650,7 +7695,11 @@ async function runWorkflowConfig(options) {
|
|
|
7650
7695
|
}
|
|
7651
7696
|
|
|
7652
7697
|
const wantsMutation =
|
|
7653
|
-
hasWorkspaceIdOption ||
|
|
7698
|
+
hasWorkspaceIdOption ||
|
|
7699
|
+
wantsClearWorkspaceId ||
|
|
7700
|
+
wantsInteractiveEdit ||
|
|
7701
|
+
hasMcpRuntimeOption ||
|
|
7702
|
+
hasMcpFallbackOption;
|
|
7654
7703
|
const showOnly = Boolean(opts.show) || !wantsMutation;
|
|
7655
7704
|
const { configPath, existing, existingValue } = await loadConfigForScope({
|
|
7656
7705
|
scope,
|
|
@@ -7692,6 +7741,18 @@ async function runWorkflowConfig(options) {
|
|
|
7692
7741
|
if (wantsClearWorkspaceId) {
|
|
7693
7742
|
workspaceId = null;
|
|
7694
7743
|
}
|
|
7744
|
+
const mcpRuntime = hasMcpRuntimeOption
|
|
7745
|
+
? normalizeMcpRuntime(
|
|
7746
|
+
opts.mcpRuntime,
|
|
7747
|
+
normalizeMcpRuntime(next.mcp?.runtime, DEFAULT_MCP_RUNTIME),
|
|
7748
|
+
)
|
|
7749
|
+
: null;
|
|
7750
|
+
const mcpFallback = hasMcpFallbackOption
|
|
7751
|
+
? normalizeMcpFallback(
|
|
7752
|
+
opts.mcpFallback,
|
|
7753
|
+
normalizeMcpFallback(next.mcp?.fallback, DEFAULT_MCP_FALLBACK),
|
|
7754
|
+
)
|
|
7755
|
+
: null;
|
|
7695
7756
|
|
|
7696
7757
|
activeProfile.workspaceId = workspaceId;
|
|
7697
7758
|
const updatedProfiles = postmanState.profiles.map((profile) =>
|
|
@@ -7713,6 +7774,17 @@ async function runWorkflowConfig(options) {
|
|
|
7713
7774
|
});
|
|
7714
7775
|
upsertNormalizedPostmanConfig(next, updatedPostmanState);
|
|
7715
7776
|
|
|
7777
|
+
if (!next.mcp || typeof next.mcp !== "object" || Array.isArray(next.mcp)) {
|
|
7778
|
+
next.mcp = {};
|
|
7779
|
+
}
|
|
7780
|
+
if (hasMcpRuntimeOption) {
|
|
7781
|
+
next.mcp.runtime = mcpRuntime;
|
|
7782
|
+
next.mcp.effectiveRuntime = mcpRuntime;
|
|
7783
|
+
}
|
|
7784
|
+
if (hasMcpFallbackOption) {
|
|
7785
|
+
next.mcp.fallback = mcpFallback;
|
|
7786
|
+
}
|
|
7787
|
+
|
|
7716
7788
|
if (parseStoredStitchConfig(next)) {
|
|
7717
7789
|
upsertNormalizedStitchConfig(next, parseStoredStitchConfig(next));
|
|
7718
7790
|
}
|
|
@@ -7729,6 +7801,13 @@ async function runWorkflowConfig(options) {
|
|
|
7729
7801
|
console.log(
|
|
7730
7802
|
`postman.defaultWorkspaceId: ${workspaceId === null ? "null" : workspaceId}`,
|
|
7731
7803
|
);
|
|
7804
|
+
if (hasMcpRuntimeOption) {
|
|
7805
|
+
console.log(`mcp.runtime: ${mcpRuntime}`);
|
|
7806
|
+
console.log(`mcp.effectiveRuntime: ${mcpRuntime}`);
|
|
7807
|
+
}
|
|
7808
|
+
if (hasMcpFallbackOption) {
|
|
7809
|
+
console.log(`mcp.fallback: ${mcpFallback}`);
|
|
7810
|
+
}
|
|
7732
7811
|
if (Boolean(opts.showAfter)) {
|
|
7733
7812
|
const payload = buildConfigShowPayload(next);
|
|
7734
7813
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -7801,8 +7880,13 @@ async function sendMcpJsonRpcRequest({
|
|
|
7801
7880
|
});
|
|
7802
7881
|
const text = await response.text();
|
|
7803
7882
|
if (!response.ok) {
|
|
7883
|
+
const parsedError = parseMcpJsonRpcResponse(text);
|
|
7884
|
+
const serverMessage =
|
|
7885
|
+
normalizePostmanApiKey(parsedError?.error?.message) ||
|
|
7886
|
+
normalizePostmanApiKey(parsedError?.message);
|
|
7887
|
+
const detail = serverMessage ? ` (${serverMessage})` : "";
|
|
7804
7888
|
throw new Error(
|
|
7805
|
-
`MCP request failed (${method}): HTTP ${response.status} ${response.statusText}`,
|
|
7889
|
+
`MCP request failed (${method}): HTTP ${response.status} ${response.statusText}${detail}`,
|
|
7806
7890
|
);
|
|
7807
7891
|
}
|
|
7808
7892
|
const parsed = parseMcpJsonRpcResponse(text);
|
|
@@ -7815,6 +7899,49 @@ async function sendMcpJsonRpcRequest({
|
|
|
7815
7899
|
};
|
|
7816
7900
|
}
|
|
7817
7901
|
|
|
7902
|
+
function sleepMs(ms) {
|
|
7903
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7904
|
+
}
|
|
7905
|
+
|
|
7906
|
+
async function waitForMcpEndpointReady({
|
|
7907
|
+
url,
|
|
7908
|
+
headers = {},
|
|
7909
|
+
timeoutMs = 15000,
|
|
7910
|
+
intervalMs = 500,
|
|
7911
|
+
}) {
|
|
7912
|
+
const startedAt = Date.now();
|
|
7913
|
+
let lastError = null;
|
|
7914
|
+
|
|
7915
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
7916
|
+
try {
|
|
7917
|
+
await sendMcpJsonRpcRequest({
|
|
7918
|
+
url,
|
|
7919
|
+
method: "tools/list",
|
|
7920
|
+
id: `cbx-runtime-ready-${Date.now()}`,
|
|
7921
|
+
params: {},
|
|
7922
|
+
headers,
|
|
7923
|
+
});
|
|
7924
|
+
return true;
|
|
7925
|
+
} catch (error) {
|
|
7926
|
+
const message = String(error?.message || "").toLowerCase();
|
|
7927
|
+
if (
|
|
7928
|
+
message.includes("server not initialized") ||
|
|
7929
|
+
message.includes("mcp-session-id header is required") ||
|
|
7930
|
+
message.includes("already initialized")
|
|
7931
|
+
) {
|
|
7932
|
+
return true;
|
|
7933
|
+
}
|
|
7934
|
+
lastError = error;
|
|
7935
|
+
}
|
|
7936
|
+
await sleepMs(intervalMs);
|
|
7937
|
+
}
|
|
7938
|
+
|
|
7939
|
+
const suffix = lastError ? ` (${lastError.message})` : "";
|
|
7940
|
+
throw new Error(
|
|
7941
|
+
`MCP endpoint readiness check timed out after ${timeoutMs}ms${suffix}`,
|
|
7942
|
+
);
|
|
7943
|
+
}
|
|
7944
|
+
|
|
7818
7945
|
async function discoverUpstreamTools({
|
|
7819
7946
|
service,
|
|
7820
7947
|
url,
|
|
@@ -8328,9 +8455,23 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8328
8455
|
containerPort: MCP_DOCKER_CONTAINER_PORT,
|
|
8329
8456
|
cwd,
|
|
8330
8457
|
})) || DEFAULT_MCP_DOCKER_HOST_PORT;
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8458
|
+
const endpoint = `http://127.0.0.1:${hostPort}/mcp`;
|
|
8459
|
+
console.log(`Endpoint: ${endpoint}`);
|
|
8460
|
+
try {
|
|
8461
|
+
await waitForMcpEndpointReady({
|
|
8462
|
+
url: endpoint,
|
|
8463
|
+
timeoutMs: 5000,
|
|
8464
|
+
intervalMs: 500,
|
|
8465
|
+
});
|
|
8466
|
+
console.log("Endpoint health: ready");
|
|
8467
|
+
} catch (error) {
|
|
8468
|
+
console.log(`Endpoint health: unreachable (${error.message})`);
|
|
8469
|
+
if (defaults.defaults.fallback === "local") {
|
|
8470
|
+
console.log(
|
|
8471
|
+
`Hint: switch config to local runtime: cbx workflows config --scope ${scope} --mcp-runtime local`,
|
|
8472
|
+
);
|
|
8473
|
+
}
|
|
8474
|
+
}
|
|
8334
8475
|
}
|
|
8335
8476
|
} catch (error) {
|
|
8336
8477
|
console.error(`\nError: ${error.message}`);
|
|
@@ -8352,6 +8493,10 @@ async function runMcpRuntimeUp(options) {
|
|
|
8352
8493
|
opts.updatePolicy,
|
|
8353
8494
|
defaults.defaults.updatePolicy,
|
|
8354
8495
|
);
|
|
8496
|
+
const fallback = normalizeMcpFallback(
|
|
8497
|
+
opts.fallback,
|
|
8498
|
+
defaults.defaults.fallback,
|
|
8499
|
+
);
|
|
8355
8500
|
const buildLocal = hasCliFlag("--build-local")
|
|
8356
8501
|
? true
|
|
8357
8502
|
: defaults.defaults.buildLocal;
|
|
@@ -8405,7 +8550,17 @@ async function runMcpRuntimeUp(options) {
|
|
|
8405
8550
|
if (skillsRootExists) {
|
|
8406
8551
|
dockerArgs.push("-v", `${skillsRoot}:/workflows/skills:ro`);
|
|
8407
8552
|
}
|
|
8408
|
-
dockerArgs.push(
|
|
8553
|
+
dockerArgs.push(
|
|
8554
|
+
image,
|
|
8555
|
+
"--transport",
|
|
8556
|
+
"http",
|
|
8557
|
+
"--host",
|
|
8558
|
+
"0.0.0.0",
|
|
8559
|
+
"--port",
|
|
8560
|
+
String(MCP_DOCKER_CONTAINER_PORT),
|
|
8561
|
+
"--scope",
|
|
8562
|
+
"global",
|
|
8563
|
+
);
|
|
8409
8564
|
await execFile(
|
|
8410
8565
|
"docker",
|
|
8411
8566
|
dockerArgs,
|
|
@@ -8421,6 +8576,7 @@ async function runMcpRuntimeUp(options) {
|
|
|
8421
8576
|
console.log(`Image: ${image}`);
|
|
8422
8577
|
console.log(`Image prepare: ${prepared.action}`);
|
|
8423
8578
|
console.log(`Update policy: ${updatePolicy}`);
|
|
8579
|
+
console.log(`Fallback: ${fallback}`);
|
|
8424
8580
|
console.log(`Build local: ${buildLocal ? "yes" : "no"}`);
|
|
8425
8581
|
console.log(`Mount: ${cbxRoot} -> /root/.cbx`);
|
|
8426
8582
|
if (skillsRootExists) {
|
|
@@ -8430,7 +8586,51 @@ async function runMcpRuntimeUp(options) {
|
|
|
8430
8586
|
}
|
|
8431
8587
|
console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
|
|
8432
8588
|
console.log(`Status: ${running ? running.status : "started"}`);
|
|
8433
|
-
|
|
8589
|
+
const endpoint = `http://127.0.0.1:${hostPort}/mcp`;
|
|
8590
|
+
console.log(`Endpoint: ${endpoint}`);
|
|
8591
|
+
try {
|
|
8592
|
+
await waitForMcpEndpointReady({
|
|
8593
|
+
url: endpoint,
|
|
8594
|
+
timeoutMs: 20000,
|
|
8595
|
+
intervalMs: 500,
|
|
8596
|
+
});
|
|
8597
|
+
console.log("Endpoint health: ready");
|
|
8598
|
+
} catch (error) {
|
|
8599
|
+
if (fallback === "skip") {
|
|
8600
|
+
runtimeWarnings.push(
|
|
8601
|
+
`Endpoint health check failed but continuing because --fallback=skip. (${error.message})`,
|
|
8602
|
+
);
|
|
8603
|
+
} else if (fallback === "local") {
|
|
8604
|
+
await execFile("docker", ["rm", "-f", containerName], { cwd }).catch(
|
|
8605
|
+
() => {},
|
|
8606
|
+
);
|
|
8607
|
+
runtimeWarnings.push(
|
|
8608
|
+
`Docker endpoint was unreachable and runtime fell back to local. (${error.message})`,
|
|
8609
|
+
);
|
|
8610
|
+
const persisted = await persistMcpRuntimePreference({
|
|
8611
|
+
scope,
|
|
8612
|
+
runtime: "local",
|
|
8613
|
+
fallback,
|
|
8614
|
+
cwd,
|
|
8615
|
+
generatedBy: "cbx mcp runtime up",
|
|
8616
|
+
});
|
|
8617
|
+
if (persisted.reason === "config-missing") {
|
|
8618
|
+
runtimeWarnings.push(
|
|
8619
|
+
`No cbx config found at ${persisted.configPath}; runtime preference was not persisted.`,
|
|
8620
|
+
);
|
|
8621
|
+
} else {
|
|
8622
|
+
runtimeWarnings.push(
|
|
8623
|
+
`Updated runtime preference in ${persisted.configPath} (${persisted.action}).`,
|
|
8624
|
+
);
|
|
8625
|
+
}
|
|
8626
|
+
console.log("Endpoint health: fallback-to-local");
|
|
8627
|
+
console.log("Local command: cbx mcp serve --transport stdio --scope auto");
|
|
8628
|
+
} else {
|
|
8629
|
+
throw new Error(
|
|
8630
|
+
`MCP endpoint is unreachable at ${endpoint}. ${error.message}`,
|
|
8631
|
+
);
|
|
8632
|
+
}
|
|
8633
|
+
}
|
|
8434
8634
|
if (runtimeWarnings.length > 0) {
|
|
8435
8635
|
console.log("Warnings:");
|
|
8436
8636
|
for (const warning of runtimeWarnings) {
|
|
@@ -8768,6 +8968,8 @@ const workflowsConfigCommand = workflowsCommand
|
|
|
8768
8968
|
.option("--edit", "edit Postman default workspace ID interactively")
|
|
8769
8969
|
.option("--workspace-id <id|null>", "set postman.defaultWorkspaceId")
|
|
8770
8970
|
.option("--clear-workspace-id", "set postman.defaultWorkspaceId to null")
|
|
8971
|
+
.option("--mcp-runtime <runtime>", "set mcp.runtime: docker|local")
|
|
8972
|
+
.option("--mcp-fallback <fallback>", "set mcp.fallback: local|fail|skip")
|
|
8771
8973
|
.option("--show-after", "print JSON after update")
|
|
8772
8974
|
.option("--dry-run", "preview changes without writing files")
|
|
8773
8975
|
.action(runWorkflowConfig);
|
|
@@ -8868,6 +9070,8 @@ const skillsConfigCommand = skillsCommand
|
|
|
8868
9070
|
.option("--edit", "edit Postman default workspace ID interactively")
|
|
8869
9071
|
.option("--workspace-id <id|null>", "set postman.defaultWorkspaceId")
|
|
8870
9072
|
.option("--clear-workspace-id", "set postman.defaultWorkspaceId to null")
|
|
9073
|
+
.option("--mcp-runtime <runtime>", "set mcp.runtime: docker|local")
|
|
9074
|
+
.option("--mcp-fallback <fallback>", "set mcp.fallback: local|fail|skip")
|
|
8871
9075
|
.option("--show-after", "print JSON after update")
|
|
8872
9076
|
.option("--dry-run", "preview changes without writing files")
|
|
8873
9077
|
.action(async (options) => {
|
|
@@ -8958,6 +9162,10 @@ mcpRuntimeCommand
|
|
|
8958
9162
|
)
|
|
8959
9163
|
.option("--image <image:tag>", "docker image to run")
|
|
8960
9164
|
.option("--update-policy <policy>", "pinned|latest")
|
|
9165
|
+
.option(
|
|
9166
|
+
"--fallback <fallback>",
|
|
9167
|
+
"when endpoint is unreachable: local|fail|skip",
|
|
9168
|
+
)
|
|
8961
9169
|
.option(
|
|
8962
9170
|
"--build-local",
|
|
8963
9171
|
"build MCP Docker image from local package mcp/ directory instead of pulling",
|