@cubis/foundry 0.3.40 → 0.3.42
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 +67 -3
- package/bin/cubis.js +360 -26
- package/mcp/README.md +72 -8
- package/mcp/config.json +3 -0
- package/mcp/dist/index.js +315 -68
- package/mcp/src/config/index.test.ts +1 -0
- package/mcp/src/config/schema.ts +5 -0
- package/mcp/src/index.ts +40 -9
- package/mcp/src/server.ts +66 -10
- package/mcp/src/telemetry/tokenBudget.ts +114 -0
- package/mcp/src/tools/index.ts +7 -0
- package/mcp/src/tools/skillBrowseCategory.ts +22 -5
- package/mcp/src/tools/skillBudgetReport.ts +128 -0
- package/mcp/src/tools/skillGet.ts +18 -0
- package/mcp/src/tools/skillListCategories.ts +19 -6
- package/mcp/src/tools/skillSearch.ts +22 -5
- package/mcp/src/tools/skillTools.test.ts +61 -9
- package/mcp/src/vault/manifest.test.ts +19 -1
- package/mcp/src/vault/manifest.ts +12 -1
- package/mcp/src/vault/scanner.test.ts +1 -0
- package/mcp/src/vault/scanner.ts +1 -0
- package/mcp/src/vault/types.ts +6 -0
- package/package.json +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md +28 -0
- package/workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md +31 -2
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +28 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +28 -0
- package/workflows/workflows/agent-environment-setup/platforms/cursor/rules/.cursorrules +28 -0
- 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";
|
|
@@ -167,7 +177,7 @@ const MCP_UPDATE_POLICIES = new Set(["pinned", "latest"]);
|
|
|
167
177
|
const DEFAULT_MCP_RUNTIME = "docker";
|
|
168
178
|
const DEFAULT_MCP_FALLBACK = "local";
|
|
169
179
|
const DEFAULT_MCP_UPDATE_POLICY = "pinned";
|
|
170
|
-
const DEFAULT_MCP_DOCKER_IMAGE =
|
|
180
|
+
const DEFAULT_MCP_DOCKER_IMAGE = `ghcr.io/cubetiq/foundry-mcp:${CLI_VERSION}`;
|
|
171
181
|
const DEFAULT_MCP_DOCKER_CONTAINER_NAME = "cbx-mcp";
|
|
172
182
|
const DEFAULT_MCP_DOCKER_HOST_PORT = 3310;
|
|
173
183
|
const MCP_DOCKER_CONTAINER_PORT = 3100;
|
|
@@ -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);
|
|
@@ -2485,7 +2512,7 @@ function escapeRegExp(value) {
|
|
|
2485
2512
|
|
|
2486
2513
|
function rewriteCodexWorkflowAgentReferences(sourceBody, agentIds) {
|
|
2487
2514
|
if (!sourceBody || !Array.isArray(agentIds) || agentIds.length === 0)
|
|
2488
|
-
return sourceBody;
|
|
2515
|
+
return normalizeCodexWrapperMentions(sourceBody);
|
|
2489
2516
|
|
|
2490
2517
|
let rewritten = sourceBody;
|
|
2491
2518
|
const sortedAgentIds = unique(agentIds.filter(Boolean)).sort(
|
|
@@ -2503,14 +2530,23 @@ function rewriteCodexWorkflowAgentReferences(sourceBody, agentIds) {
|
|
|
2503
2530
|
);
|
|
2504
2531
|
}
|
|
2505
2532
|
|
|
2506
|
-
return rewritten;
|
|
2533
|
+
return normalizeCodexWrapperMentions(rewritten);
|
|
2507
2534
|
}
|
|
2508
2535
|
|
|
2509
2536
|
function rewriteCodexAgentSkillReferences(sourceBody) {
|
|
2510
2537
|
if (!sourceBody) return sourceBody;
|
|
2511
2538
|
// Agent source files live under platforms/*/agents, but wrapper skills live
|
|
2512
2539
|
// under .agents/skills/agent-*. Rebase ../skills/<id> links accordingly.
|
|
2513
|
-
|
|
2540
|
+
const rebased = sourceBody.replace(/\(\.\.\/skills\//g, "(../");
|
|
2541
|
+
return normalizeCodexWrapperMentions(rebased);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
function normalizeCodexWrapperMentions(sourceBody) {
|
|
2545
|
+
if (!sourceBody) return sourceBody;
|
|
2546
|
+
return sourceBody.replace(
|
|
2547
|
+
/`\$(workflow|agent)-([A-Za-z0-9_-]+|\*)`/g,
|
|
2548
|
+
(_match, kind, id) => `$${kind}-${id}`,
|
|
2549
|
+
);
|
|
2514
2550
|
}
|
|
2515
2551
|
|
|
2516
2552
|
async function parseWorkflowMetadata(filePath) {
|
|
@@ -2821,8 +2857,11 @@ function buildManagedWorkflowBlock(platformId, workflows) {
|
|
|
2821
2857
|
lines.push("## CBX Workflow Routing (auto-managed)");
|
|
2822
2858
|
if (platformId === "codex") {
|
|
2823
2859
|
lines.push("Use Codex callable wrappers:");
|
|
2824
|
-
lines.push("- Workflows:
|
|
2825
|
-
lines.push("- Agents:
|
|
2860
|
+
lines.push("- Workflows: $workflow-<name>");
|
|
2861
|
+
lines.push("- Agents: $agent-<name>");
|
|
2862
|
+
lines.push(
|
|
2863
|
+
"- Use raw $workflow-* / $agent-* tokens (no backticks) so Codex can render icons.",
|
|
2864
|
+
);
|
|
2826
2865
|
lines.push("");
|
|
2827
2866
|
|
|
2828
2867
|
if (workflows.length === 0) {
|
|
@@ -2832,18 +2871,16 @@ function buildManagedWorkflowBlock(platformId, workflows) {
|
|
|
2832
2871
|
const triggerPreview = workflow.triggers.slice(0, 2).join(", ");
|
|
2833
2872
|
const hint = triggerPreview ? ` (${triggerPreview})` : "";
|
|
2834
2873
|
lines.push(
|
|
2835
|
-
`-
|
|
2874
|
+
`- ${workflow.command} -> $${CODEX_WORKFLOW_SKILL_PREFIX}${workflow.id}${hint}`,
|
|
2836
2875
|
);
|
|
2837
2876
|
}
|
|
2838
2877
|
}
|
|
2839
2878
|
|
|
2840
2879
|
lines.push("");
|
|
2841
2880
|
lines.push("Selection policy:");
|
|
2842
|
-
lines.push("1. If user names a
|
|
2881
|
+
lines.push("1. If user names a $workflow-*, use it directly.");
|
|
2843
2882
|
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
|
-
);
|
|
2883
|
+
lines.push("3. Use $agent-* wrappers only when role specialization is needed.");
|
|
2847
2884
|
lines.push("");
|
|
2848
2885
|
lines.push("<!-- cbx:workflows:auto:end -->");
|
|
2849
2886
|
return lines.join("\n");
|
|
@@ -3316,6 +3353,47 @@ function buildVsCodePostmanServer({
|
|
|
3316
3353
|
};
|
|
3317
3354
|
}
|
|
3318
3355
|
|
|
3356
|
+
function buildFoundryServeArgs({ scope = "auto" } = {}) {
|
|
3357
|
+
const normalizedScope =
|
|
3358
|
+
scope === "global" || scope === "project" || scope === "auto"
|
|
3359
|
+
? scope
|
|
3360
|
+
: "auto";
|
|
3361
|
+
return ["mcp", "serve", "--transport", "stdio", "--scope", normalizedScope];
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
function buildVsCodeFoundryServer({ scope = "auto" } = {}) {
|
|
3365
|
+
return {
|
|
3366
|
+
type: "stdio",
|
|
3367
|
+
command: FOUNDRY_MCP_COMMAND,
|
|
3368
|
+
args: buildFoundryServeArgs({ scope }),
|
|
3369
|
+
env: {},
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
function buildCopilotCliPostmanServer({
|
|
3374
|
+
apiKeyEnvVar = POSTMAN_API_KEY_ENV_VAR,
|
|
3375
|
+
mcpUrl = POSTMAN_MCP_URL,
|
|
3376
|
+
}) {
|
|
3377
|
+
return {
|
|
3378
|
+
type: "http",
|
|
3379
|
+
url: mcpUrl,
|
|
3380
|
+
headers: {
|
|
3381
|
+
Authorization: buildPostmanAuthHeader({ apiKeyEnvVar }),
|
|
3382
|
+
},
|
|
3383
|
+
tools: ["*"],
|
|
3384
|
+
};
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
function buildCopilotCliFoundryServer({ scope = "auto" } = {}) {
|
|
3388
|
+
return {
|
|
3389
|
+
type: "stdio",
|
|
3390
|
+
command: FOUNDRY_MCP_COMMAND,
|
|
3391
|
+
args: buildFoundryServeArgs({ scope }),
|
|
3392
|
+
env: {},
|
|
3393
|
+
tools: ["*"],
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3319
3397
|
function buildGeminiPostmanServer({
|
|
3320
3398
|
apiKeyEnvVar = POSTMAN_API_KEY_ENV_VAR,
|
|
3321
3399
|
mcpUrl = POSTMAN_MCP_URL,
|
|
@@ -3328,12 +3406,19 @@ function buildGeminiPostmanServer({
|
|
|
3328
3406
|
};
|
|
3329
3407
|
}
|
|
3330
3408
|
|
|
3409
|
+
function buildGeminiFoundryServer({ scope = "auto" } = {}) {
|
|
3410
|
+
return {
|
|
3411
|
+
command: FOUNDRY_MCP_COMMAND,
|
|
3412
|
+
args: buildFoundryServeArgs({ scope }),
|
|
3413
|
+
env: {},
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3331
3417
|
function buildGeminiStitchServer({
|
|
3332
3418
|
apiKeyEnvVar = STITCH_API_KEY_ENV_VAR,
|
|
3333
3419
|
mcpUrl = STITCH_MCP_URL,
|
|
3334
3420
|
}) {
|
|
3335
3421
|
return {
|
|
3336
|
-
$typeName: "exa.cascade_plugins_pb.CascadePluginCommandTemplate",
|
|
3337
3422
|
command: "npx",
|
|
3338
3423
|
args: [
|
|
3339
3424
|
"-y",
|
|
@@ -3752,6 +3837,7 @@ async function applyPostmanMcpForPlatform({
|
|
|
3752
3837
|
stitchApiKeyEnvVar,
|
|
3753
3838
|
stitchMcpUrl,
|
|
3754
3839
|
includeStitchMcp = false,
|
|
3840
|
+
includeFoundryMcp = true,
|
|
3755
3841
|
dryRun = false,
|
|
3756
3842
|
cwd = process.cwd(),
|
|
3757
3843
|
}) {
|
|
@@ -3777,6 +3863,13 @@ async function applyPostmanMcpForPlatform({
|
|
|
3777
3863
|
apiKeyEnvVar,
|
|
3778
3864
|
mcpUrl,
|
|
3779
3865
|
});
|
|
3866
|
+
if (includeFoundryMcp) {
|
|
3867
|
+
mcpServers[FOUNDRY_MCP_SERVER_ID] = buildGeminiFoundryServer({
|
|
3868
|
+
scope: "auto",
|
|
3869
|
+
});
|
|
3870
|
+
} else {
|
|
3871
|
+
delete mcpServers[FOUNDRY_MCP_SERVER_ID];
|
|
3872
|
+
}
|
|
3780
3873
|
if (includeStitchMcp) {
|
|
3781
3874
|
mcpServers[STITCH_MCP_SERVER_ID] = buildGeminiStitchServer({
|
|
3782
3875
|
apiKeyEnvVar: stitchApiKeyEnvVar,
|
|
@@ -3806,6 +3899,28 @@ async function applyPostmanMcpForPlatform({
|
|
|
3806
3899
|
targetPath: configPath,
|
|
3807
3900
|
updater: (existing) => {
|
|
3808
3901
|
const next = { ...existing };
|
|
3902
|
+
if (mcpScope === "global") {
|
|
3903
|
+
const mcpServers =
|
|
3904
|
+
next.mcpServers &&
|
|
3905
|
+
typeof next.mcpServers === "object" &&
|
|
3906
|
+
!Array.isArray(next.mcpServers)
|
|
3907
|
+
? { ...next.mcpServers }
|
|
3908
|
+
: {};
|
|
3909
|
+
mcpServers[POSTMAN_SKILL_ID] = buildCopilotCliPostmanServer({
|
|
3910
|
+
apiKeyEnvVar,
|
|
3911
|
+
mcpUrl,
|
|
3912
|
+
});
|
|
3913
|
+
if (includeFoundryMcp) {
|
|
3914
|
+
mcpServers[FOUNDRY_MCP_SERVER_ID] = buildCopilotCliFoundryServer({
|
|
3915
|
+
scope: "auto",
|
|
3916
|
+
});
|
|
3917
|
+
} else {
|
|
3918
|
+
delete mcpServers[FOUNDRY_MCP_SERVER_ID];
|
|
3919
|
+
}
|
|
3920
|
+
next.mcpServers = mcpServers;
|
|
3921
|
+
return next;
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3809
3924
|
const servers =
|
|
3810
3925
|
next.servers &&
|
|
3811
3926
|
typeof next.servers === "object" &&
|
|
@@ -3816,6 +3931,13 @@ async function applyPostmanMcpForPlatform({
|
|
|
3816
3931
|
apiKeyEnvVar,
|
|
3817
3932
|
mcpUrl,
|
|
3818
3933
|
});
|
|
3934
|
+
if (includeFoundryMcp) {
|
|
3935
|
+
servers[FOUNDRY_MCP_SERVER_ID] = buildVsCodeFoundryServer({
|
|
3936
|
+
scope: "auto",
|
|
3937
|
+
});
|
|
3938
|
+
} else {
|
|
3939
|
+
delete servers[FOUNDRY_MCP_SERVER_ID];
|
|
3940
|
+
}
|
|
3819
3941
|
next.servers = servers;
|
|
3820
3942
|
return next;
|
|
3821
3943
|
},
|
|
@@ -3847,6 +3969,13 @@ async function applyPostmanMcpForPlatform({
|
|
|
3847
3969
|
apiKeyEnvVar,
|
|
3848
3970
|
mcpUrl,
|
|
3849
3971
|
});
|
|
3972
|
+
if (includeFoundryMcp) {
|
|
3973
|
+
servers[FOUNDRY_MCP_SERVER_ID] = buildVsCodeFoundryServer({
|
|
3974
|
+
scope: "auto",
|
|
3975
|
+
});
|
|
3976
|
+
} else {
|
|
3977
|
+
delete servers[FOUNDRY_MCP_SERVER_ID];
|
|
3978
|
+
}
|
|
3850
3979
|
next.servers = servers;
|
|
3851
3980
|
return next;
|
|
3852
3981
|
},
|
|
@@ -3877,6 +4006,13 @@ async function applyPostmanMcpForPlatform({
|
|
|
3877
4006
|
} catch {
|
|
3878
4007
|
// Best effort. Add will still run and becomes source of truth.
|
|
3879
4008
|
}
|
|
4009
|
+
try {
|
|
4010
|
+
await execFile("codex", ["mcp", "remove", FOUNDRY_MCP_SERVER_ID], {
|
|
4011
|
+
cwd,
|
|
4012
|
+
});
|
|
4013
|
+
} catch {
|
|
4014
|
+
// Best effort. Add will still run and becomes source of truth.
|
|
4015
|
+
}
|
|
3880
4016
|
|
|
3881
4017
|
try {
|
|
3882
4018
|
await execFile(
|
|
@@ -3905,6 +4041,27 @@ async function applyPostmanMcpForPlatform({
|
|
|
3905
4041
|
};
|
|
3906
4042
|
}
|
|
3907
4043
|
|
|
4044
|
+
if (includeFoundryMcp) {
|
|
4045
|
+
try {
|
|
4046
|
+
await execFile(
|
|
4047
|
+
"codex",
|
|
4048
|
+
[
|
|
4049
|
+
"mcp",
|
|
4050
|
+
"add",
|
|
4051
|
+
FOUNDRY_MCP_SERVER_ID,
|
|
4052
|
+
"--",
|
|
4053
|
+
FOUNDRY_MCP_COMMAND,
|
|
4054
|
+
...FOUNDRY_MCP_DEFAULT_ARGS,
|
|
4055
|
+
],
|
|
4056
|
+
{ cwd },
|
|
4057
|
+
);
|
|
4058
|
+
} catch (error) {
|
|
4059
|
+
warnings.push(
|
|
4060
|
+
`Failed to register ${FOUNDRY_MCP_SERVER_ID} MCP via Codex CLI. Ensure 'cbx' and 'codex' are installed and rerun. (${error.message})`,
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
|
|
3908
4065
|
return {
|
|
3909
4066
|
kind: "codex-cli",
|
|
3910
4067
|
scope: mcpScope,
|
|
@@ -4001,6 +4158,7 @@ async function resolvePostmanInstallSelection({
|
|
|
4001
4158
|
);
|
|
4002
4159
|
const mcpBuildLocal = Boolean(options.mcpBuildLocal);
|
|
4003
4160
|
const mcpToolSync = options.mcpToolSync !== false;
|
|
4161
|
+
const foundryMcpEnabled = options.foundryMcp !== false;
|
|
4004
4162
|
|
|
4005
4163
|
const canPrompt = !options.yes && process.stdin.isTTY;
|
|
4006
4164
|
if (canPrompt && !hasWorkspaceOption) {
|
|
@@ -4228,6 +4386,7 @@ async function resolvePostmanInstallSelection({
|
|
|
4228
4386
|
mcpBuildLocal,
|
|
4229
4387
|
dockerImageAction,
|
|
4230
4388
|
mcpToolSync,
|
|
4389
|
+
foundryMcpEnabled,
|
|
4231
4390
|
runtimeSkipped,
|
|
4232
4391
|
defaultWorkspaceId: defaultWorkspaceId ?? null,
|
|
4233
4392
|
workspaceSelectionSource,
|
|
@@ -4457,6 +4616,7 @@ async function configurePostmanInstallArtifacts({
|
|
|
4457
4616
|
stitchApiKeyEnvVar: effectiveStitchApiKeyEnvVar,
|
|
4458
4617
|
stitchMcpUrl: effectiveStitchMcpUrl,
|
|
4459
4618
|
includeStitchMcp: shouldInstallStitch,
|
|
4619
|
+
includeFoundryMcp: postmanSelection.foundryMcpEnabled,
|
|
4460
4620
|
dryRun,
|
|
4461
4621
|
cwd,
|
|
4462
4622
|
});
|
|
@@ -4495,6 +4655,7 @@ async function configurePostmanInstallArtifacts({
|
|
|
4495
4655
|
mcpBuildLocal: postmanSelection.mcpBuildLocal,
|
|
4496
4656
|
dockerImageAction: postmanSelection.dockerImageAction,
|
|
4497
4657
|
mcpToolSync: postmanSelection.mcpToolSync,
|
|
4658
|
+
foundryMcpEnabled: postmanSelection.foundryMcpEnabled,
|
|
4498
4659
|
apiKeySource: effectiveApiKeySource,
|
|
4499
4660
|
stitchApiKeySource: effectiveStitchApiKeySource,
|
|
4500
4661
|
defaultWorkspaceId: effectiveDefaultWorkspaceId,
|
|
@@ -5454,6 +5615,9 @@ function printPostmanSetupSummary({ postmanSetup }) {
|
|
|
5454
5615
|
console.log(`- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`);
|
|
5455
5616
|
console.log(`- MCP image prepare: ${postmanSetup.dockerImageAction}`);
|
|
5456
5617
|
console.log(`- MCP tool sync: ${postmanSetup.mcpToolSync ? "enabled" : "disabled"}`);
|
|
5618
|
+
console.log(
|
|
5619
|
+
`- Foundry MCP side-by-side: ${postmanSetup.foundryMcpEnabled ? "enabled" : "disabled"}`,
|
|
5620
|
+
);
|
|
5457
5621
|
console.log(`- Postman API key source: ${postmanSetup.apiKeySource}`);
|
|
5458
5622
|
if (postmanSetup.stitchApiKeySource) {
|
|
5459
5623
|
console.log(`- Stitch API key source: ${postmanSetup.stitchApiKeySource}`);
|
|
@@ -5971,6 +6135,10 @@ function withInstallOptions(command) {
|
|
|
5971
6135
|
"enable automatic MCP tool catalog sync (default: enabled)",
|
|
5972
6136
|
)
|
|
5973
6137
|
.option("--no-mcp-tool-sync", "disable automatic MCP tool catalog sync")
|
|
6138
|
+
.option(
|
|
6139
|
+
"--no-foundry-mcp",
|
|
6140
|
+
"disable side-by-side cubis-foundry MCP registration during --postman setup",
|
|
6141
|
+
)
|
|
5974
6142
|
.option(
|
|
5975
6143
|
"--terminal-integration",
|
|
5976
6144
|
"Antigravity only: enable terminal verification integration (prompts for verifier when interactive)",
|
|
@@ -7933,6 +8101,111 @@ async function runMcpToolsList(options) {
|
|
|
7933
8101
|
}
|
|
7934
8102
|
}
|
|
7935
8103
|
|
|
8104
|
+
function normalizeMcpServeTransport(value, fallback = "stdio") {
|
|
8105
|
+
const normalized = String(value || fallback)
|
|
8106
|
+
.trim()
|
|
8107
|
+
.toLowerCase();
|
|
8108
|
+
if (normalized === "stdio") return "stdio";
|
|
8109
|
+
if (normalized === "http" || normalized === "streamable-http") return "http";
|
|
8110
|
+
throw new Error(
|
|
8111
|
+
`Unknown MCP transport '${value}'. Use stdio|http.`,
|
|
8112
|
+
);
|
|
8113
|
+
}
|
|
8114
|
+
|
|
8115
|
+
function normalizeMcpServeScope(value, fallback = "auto") {
|
|
8116
|
+
const normalized = String(value || fallback)
|
|
8117
|
+
.trim()
|
|
8118
|
+
.toLowerCase();
|
|
8119
|
+
if (
|
|
8120
|
+
normalized === "auto" ||
|
|
8121
|
+
normalized === "global" ||
|
|
8122
|
+
normalized === "project"
|
|
8123
|
+
) {
|
|
8124
|
+
return normalized;
|
|
8125
|
+
}
|
|
8126
|
+
throw new Error(
|
|
8127
|
+
`Unknown MCP scope '${value}'. Use auto|global|project.`,
|
|
8128
|
+
);
|
|
8129
|
+
}
|
|
8130
|
+
|
|
8131
|
+
function resolveBundledMcpEntryPath() {
|
|
8132
|
+
return path.join(packageRoot(), "mcp", "dist", "index.js");
|
|
8133
|
+
}
|
|
8134
|
+
|
|
8135
|
+
async function runMcpServe(options) {
|
|
8136
|
+
try {
|
|
8137
|
+
const opts = resolveActionOptions(options);
|
|
8138
|
+
const cwd = process.cwd();
|
|
8139
|
+
const entryPath = resolveBundledMcpEntryPath();
|
|
8140
|
+
if (!(await pathExists(entryPath))) {
|
|
8141
|
+
throw new Error(
|
|
8142
|
+
`Bundled MCP server not found at ${entryPath}. Install @cubis/foundry with bundled mcp/dist assets.`,
|
|
8143
|
+
);
|
|
8144
|
+
}
|
|
8145
|
+
|
|
8146
|
+
const transport = normalizeMcpServeTransport(opts.transport, "stdio");
|
|
8147
|
+
const scope = normalizeMcpServeScope(opts.scope, "auto");
|
|
8148
|
+
const args = [entryPath, "--transport", transport, "--scope", scope];
|
|
8149
|
+
|
|
8150
|
+
if (opts.host) {
|
|
8151
|
+
args.push("--host", String(opts.host));
|
|
8152
|
+
}
|
|
8153
|
+
if (opts.port !== undefined && opts.port !== null && opts.port !== "") {
|
|
8154
|
+
args.push("--port", String(normalizePortNumber(opts.port, 3100)));
|
|
8155
|
+
}
|
|
8156
|
+
if (opts.scanOnly) {
|
|
8157
|
+
args.push("--scan-only");
|
|
8158
|
+
}
|
|
8159
|
+
if (opts.config) {
|
|
8160
|
+
args.push("--config", expandPath(String(opts.config), cwd));
|
|
8161
|
+
}
|
|
8162
|
+
if (opts.debug) {
|
|
8163
|
+
args.push("--debug");
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
await new Promise((resolve, reject) => {
|
|
8167
|
+
const child = spawn(process.execPath, args, {
|
|
8168
|
+
cwd,
|
|
8169
|
+
stdio: "inherit",
|
|
8170
|
+
env: process.env,
|
|
8171
|
+
});
|
|
8172
|
+
|
|
8173
|
+
const forwardSignal = (signal) => {
|
|
8174
|
+
if (!child.killed) {
|
|
8175
|
+
child.kill(signal);
|
|
8176
|
+
}
|
|
8177
|
+
};
|
|
8178
|
+
const onSigInt = () => forwardSignal("SIGINT");
|
|
8179
|
+
const onSigTerm = () => forwardSignal("SIGTERM");
|
|
8180
|
+
process.on("SIGINT", onSigInt);
|
|
8181
|
+
process.on("SIGTERM", onSigTerm);
|
|
8182
|
+
|
|
8183
|
+
child.once("error", (error) => {
|
|
8184
|
+
process.off("SIGINT", onSigInt);
|
|
8185
|
+
process.off("SIGTERM", onSigTerm);
|
|
8186
|
+
reject(error);
|
|
8187
|
+
});
|
|
8188
|
+
|
|
8189
|
+
child.once("exit", (code, signal) => {
|
|
8190
|
+
process.off("SIGINT", onSigInt);
|
|
8191
|
+
process.off("SIGTERM", onSigTerm);
|
|
8192
|
+
if (signal) {
|
|
8193
|
+
reject(new Error(`MCP server terminated by signal ${signal}.`));
|
|
8194
|
+
return;
|
|
8195
|
+
}
|
|
8196
|
+
if (code && code !== 0) {
|
|
8197
|
+
reject(new Error(`MCP server exited with status ${code}.`));
|
|
8198
|
+
return;
|
|
8199
|
+
}
|
|
8200
|
+
resolve(null);
|
|
8201
|
+
});
|
|
8202
|
+
});
|
|
8203
|
+
} catch (error) {
|
|
8204
|
+
console.error(`\nError: ${error.message}`);
|
|
8205
|
+
process.exit(1);
|
|
8206
|
+
}
|
|
8207
|
+
}
|
|
8208
|
+
|
|
7936
8209
|
function resolveCbxRootPath({ scope, cwd = process.cwd() }) {
|
|
7937
8210
|
if (scope === "global") {
|
|
7938
8211
|
return path.join(os.homedir(), ".cbx");
|
|
@@ -7998,6 +8271,8 @@ async function runMcpRuntimeStatus(options) {
|
|
|
7998
8271
|
const container = dockerAvailable
|
|
7999
8272
|
? await inspectDockerContainerByName({ name: containerName, cwd })
|
|
8000
8273
|
: null;
|
|
8274
|
+
const skillsRoot = path.join(os.homedir(), ".agents", "skills");
|
|
8275
|
+
const skillsRootExists = await pathExists(skillsRoot);
|
|
8001
8276
|
|
|
8002
8277
|
console.log(`Scope: ${scope}`);
|
|
8003
8278
|
console.log(`Config file: ${defaults.configPath}`);
|
|
@@ -8007,6 +8282,9 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8007
8282
|
console.log(`Configured image: ${defaults.defaults.image}`);
|
|
8008
8283
|
console.log(`Configured update policy: ${defaults.defaults.updatePolicy}`);
|
|
8009
8284
|
console.log(`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`);
|
|
8285
|
+
console.log(
|
|
8286
|
+
`Host skills root: ${skillsRoot} (${skillsRootExists ? "present" : "missing"})`,
|
|
8287
|
+
);
|
|
8010
8288
|
console.log(`Docker available: ${dockerAvailable ? "yes" : "no"}`);
|
|
8011
8289
|
console.log(`Container name: ${containerName}`);
|
|
8012
8290
|
if (!dockerAvailable) {
|
|
@@ -8015,11 +8293,34 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8015
8293
|
}
|
|
8016
8294
|
if (!container) {
|
|
8017
8295
|
console.log("Container status: not found");
|
|
8296
|
+
if (!skillsRootExists) {
|
|
8297
|
+
console.log(
|
|
8298
|
+
"Hint: ~/.agents/skills is missing. Create/populate it before starting runtime to enable vault discovery.",
|
|
8299
|
+
);
|
|
8300
|
+
}
|
|
8018
8301
|
return;
|
|
8019
8302
|
}
|
|
8020
8303
|
const isRunning = container.status.toLowerCase().startsWith("up ");
|
|
8021
8304
|
console.log(`Container status: ${container.status}`);
|
|
8022
8305
|
console.log(`Container image: ${container.image}`);
|
|
8306
|
+
const mounts = await inspectDockerContainerMounts({ name: containerName, cwd });
|
|
8307
|
+
const skillsMount = mounts.find(
|
|
8308
|
+
(mount) => String(mount.Destination || "") === "/workflows/skills",
|
|
8309
|
+
);
|
|
8310
|
+
if (skillsMount) {
|
|
8311
|
+
const source = String(skillsMount.Source || "(unknown)");
|
|
8312
|
+
console.log(`Skills mount: ${source} -> /workflows/skills`);
|
|
8313
|
+
if (!(await pathExists(source))) {
|
|
8314
|
+
console.log(
|
|
8315
|
+
"Hint: mounted skill source path is missing on host; vault discovery may return zero skills.",
|
|
8316
|
+
);
|
|
8317
|
+
}
|
|
8318
|
+
} else {
|
|
8319
|
+
console.log("Skills mount: missing");
|
|
8320
|
+
console.log(
|
|
8321
|
+
"Hint: recreate runtime with a skills mount (host ~/.agents/skills -> /workflows/skills).",
|
|
8322
|
+
);
|
|
8323
|
+
}
|
|
8023
8324
|
if (isRunning) {
|
|
8024
8325
|
const hostPort =
|
|
8025
8326
|
(await resolveDockerContainerHostPort({
|
|
@@ -8082,22 +8383,32 @@ async function runMcpRuntimeUp(options) {
|
|
|
8082
8383
|
}
|
|
8083
8384
|
|
|
8084
8385
|
const cbxRoot = resolveCbxRootPath({ scope, cwd });
|
|
8386
|
+
const skillsRoot = path.join(os.homedir(), ".agents", "skills");
|
|
8387
|
+
const skillsRootExists = await pathExists(skillsRoot);
|
|
8388
|
+
const runtimeWarnings = [];
|
|
8389
|
+
if (!skillsRootExists) {
|
|
8390
|
+
runtimeWarnings.push(
|
|
8391
|
+
`Skill mount source is missing: ${skillsRoot}. Runtime will start without /workflows/skills and vault discovery can return zero skills.`,
|
|
8392
|
+
);
|
|
8393
|
+
}
|
|
8085
8394
|
await mkdir(cbxRoot, { recursive: true });
|
|
8395
|
+
const dockerArgs = [
|
|
8396
|
+
"run",
|
|
8397
|
+
"-d",
|
|
8398
|
+
"--name",
|
|
8399
|
+
containerName,
|
|
8400
|
+
"-p",
|
|
8401
|
+
`${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`,
|
|
8402
|
+
"-v",
|
|
8403
|
+
`${cbxRoot}:/root/.cbx`,
|
|
8404
|
+
];
|
|
8405
|
+
if (skillsRootExists) {
|
|
8406
|
+
dockerArgs.push("-v", `${skillsRoot}:/workflows/skills:ro`);
|
|
8407
|
+
}
|
|
8408
|
+
dockerArgs.push("-e", "CBX_MCP_TRANSPORT=streamable-http", image);
|
|
8086
8409
|
await execFile(
|
|
8087
8410
|
"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
|
-
],
|
|
8411
|
+
dockerArgs,
|
|
8101
8412
|
{ cwd },
|
|
8102
8413
|
);
|
|
8103
8414
|
|
|
@@ -8112,9 +8423,20 @@ async function runMcpRuntimeUp(options) {
|
|
|
8112
8423
|
console.log(`Update policy: ${updatePolicy}`);
|
|
8113
8424
|
console.log(`Build local: ${buildLocal ? "yes" : "no"}`);
|
|
8114
8425
|
console.log(`Mount: ${cbxRoot} -> /root/.cbx`);
|
|
8426
|
+
if (skillsRootExists) {
|
|
8427
|
+
console.log(`Mount: ${skillsRoot} -> /workflows/skills (ro)`);
|
|
8428
|
+
} else {
|
|
8429
|
+
console.log("Mount: /workflows/skills (not mounted - source missing)");
|
|
8430
|
+
}
|
|
8115
8431
|
console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
|
|
8116
8432
|
console.log(`Status: ${running ? running.status : "started"}`);
|
|
8117
8433
|
console.log(`Endpoint: http://127.0.0.1:${hostPort}/mcp`);
|
|
8434
|
+
if (runtimeWarnings.length > 0) {
|
|
8435
|
+
console.log("Warnings:");
|
|
8436
|
+
for (const warning of runtimeWarnings) {
|
|
8437
|
+
console.log(`- ${warning}`);
|
|
8438
|
+
}
|
|
8439
|
+
}
|
|
8118
8440
|
} catch (error) {
|
|
8119
8441
|
console.error(`\nError: ${error.message}`);
|
|
8120
8442
|
process.exit(1);
|
|
@@ -8563,6 +8885,18 @@ const mcpCommand = program
|
|
|
8563
8885
|
.command("mcp")
|
|
8564
8886
|
.description("Manage Cubis MCP runtime catalogs and tool discovery");
|
|
8565
8887
|
|
|
8888
|
+
mcpCommand
|
|
8889
|
+
.command("serve")
|
|
8890
|
+
.description("Launch bundled Cubis Foundry MCP server (canonical local entrypoint)")
|
|
8891
|
+
.option("--transport <transport>", "stdio|http", "stdio")
|
|
8892
|
+
.option("--scope <scope>", "auto|global|project", "auto")
|
|
8893
|
+
.option("--port <port>", "HTTP port override")
|
|
8894
|
+
.option("--host <host>", "HTTP host override")
|
|
8895
|
+
.option("--scan-only", "scan vault and exit")
|
|
8896
|
+
.option("--config <path>", "explicit MCP server config file path")
|
|
8897
|
+
.option("--debug", "enable debug logging in MCP server")
|
|
8898
|
+
.action(runMcpServe);
|
|
8899
|
+
|
|
8566
8900
|
const mcpToolsCommand = mcpCommand
|
|
8567
8901
|
.command("tools")
|
|
8568
8902
|
.description("Discover and inspect upstream MCP tool catalogs");
|