@cubis/foundry 0.3.39 → 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.
- package/README.md +64 -0
- package/bin/cubis.js +382 -25
- 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/skills/code-documenter/references/images/create-key.png +0 -0
- package/workflows/skills/code-documenter/references/images/dashboard-annotated.png +0 -0
- package/workflows/skills/documentation-templates/docs/api.md +16 -0
- package/workflows/skills/documentation-templates/docs/architecture.md +23 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md +35 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md +38 -3
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +35 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +35 -1
- 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";
|
|
@@ -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);
|
|
@@ -2506,6 +2533,13 @@ function rewriteCodexWorkflowAgentReferences(sourceBody, agentIds) {
|
|
|
2506
2533
|
return rewritten;
|
|
2507
2534
|
}
|
|
2508
2535
|
|
|
2536
|
+
function rewriteCodexAgentSkillReferences(sourceBody) {
|
|
2537
|
+
if (!sourceBody) return sourceBody;
|
|
2538
|
+
// Agent source files live under platforms/*/agents, but wrapper skills live
|
|
2539
|
+
// under .agents/skills/agent-*. Rebase ../skills/<id> links accordingly.
|
|
2540
|
+
return sourceBody.replace(/\(\.\.\/skills\//g, "(../");
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2509
2543
|
async function parseWorkflowMetadata(filePath) {
|
|
2510
2544
|
const raw = await readFile(filePath, "utf8");
|
|
2511
2545
|
const { frontmatter, body } = extractFrontmatter(raw);
|
|
@@ -2730,11 +2764,15 @@ async function generateCodexWrapperSkills({
|
|
|
2730
2764
|
}
|
|
2731
2765
|
|
|
2732
2766
|
const metadata = await parseAgentMetadata(source);
|
|
2767
|
+
const rewrittenBody = rewriteCodexAgentSkillReferences(metadata.body);
|
|
2733
2768
|
const wrapperSkillId = `${CODEX_AGENT_SKILL_PREFIX}${metadata.id}`;
|
|
2734
2769
|
const destinationDir = path.join(skillsDir, wrapperSkillId);
|
|
2735
2770
|
const content = buildCodexAgentWrapperSkillMarkdown(
|
|
2736
2771
|
wrapperSkillId,
|
|
2737
|
-
|
|
2772
|
+
{
|
|
2773
|
+
...metadata,
|
|
2774
|
+
body: rewrittenBody,
|
|
2775
|
+
},
|
|
2738
2776
|
);
|
|
2739
2777
|
|
|
2740
2778
|
const result = await writeGeneratedSkillArtifact({
|
|
@@ -2759,6 +2797,22 @@ async function generateCodexWrapperSkills({
|
|
|
2759
2797
|
};
|
|
2760
2798
|
}
|
|
2761
2799
|
|
|
2800
|
+
async function resolvePlatformAgentSkillDependencies({
|
|
2801
|
+
platformRoot,
|
|
2802
|
+
platformSpec,
|
|
2803
|
+
}) {
|
|
2804
|
+
const dependencyIds = [];
|
|
2805
|
+
|
|
2806
|
+
for (const agentFile of platformSpec.agents || []) {
|
|
2807
|
+
const source = path.join(platformRoot, "agents", agentFile);
|
|
2808
|
+
if (!(await pathExists(source))) continue;
|
|
2809
|
+
const metadata = await parseAgentMetadata(source);
|
|
2810
|
+
dependencyIds.push(...metadata.skills);
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
return unique(dependencyIds.filter(Boolean));
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2762
2816
|
async function collectInstalledWorkflows(
|
|
2763
2817
|
profileId,
|
|
2764
2818
|
scope,
|
|
@@ -2794,8 +2848,11 @@ function buildManagedWorkflowBlock(platformId, workflows) {
|
|
|
2794
2848
|
lines.push("## CBX Workflow Routing (auto-managed)");
|
|
2795
2849
|
if (platformId === "codex") {
|
|
2796
2850
|
lines.push("Use Codex callable wrappers:");
|
|
2797
|
-
lines.push("- Workflows:
|
|
2798
|
-
lines.push("- Agents:
|
|
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
|
+
);
|
|
2799
2856
|
lines.push("");
|
|
2800
2857
|
|
|
2801
2858
|
if (workflows.length === 0) {
|
|
@@ -2805,18 +2862,16 @@ function buildManagedWorkflowBlock(platformId, workflows) {
|
|
|
2805
2862
|
const triggerPreview = workflow.triggers.slice(0, 2).join(", ");
|
|
2806
2863
|
const hint = triggerPreview ? ` (${triggerPreview})` : "";
|
|
2807
2864
|
lines.push(
|
|
2808
|
-
`-
|
|
2865
|
+
`- ${workflow.command} -> $${CODEX_WORKFLOW_SKILL_PREFIX}${workflow.id}${hint}`,
|
|
2809
2866
|
);
|
|
2810
2867
|
}
|
|
2811
2868
|
}
|
|
2812
2869
|
|
|
2813
2870
|
lines.push("");
|
|
2814
2871
|
lines.push("Selection policy:");
|
|
2815
|
-
lines.push("1. If user names a
|
|
2872
|
+
lines.push("1. If user names a $workflow-*, use it directly.");
|
|
2816
2873
|
lines.push("2. Else map intent to one primary workflow.");
|
|
2817
|
-
lines.push(
|
|
2818
|
-
"3. Use `$agent-*` wrappers only when role specialization is needed.",
|
|
2819
|
-
);
|
|
2874
|
+
lines.push("3. Use $agent-* wrappers only when role specialization is needed.");
|
|
2820
2875
|
lines.push("");
|
|
2821
2876
|
lines.push("<!-- cbx:workflows:auto:end -->");
|
|
2822
2877
|
return lines.join("\n");
|
|
@@ -3289,6 +3344,47 @@ function buildVsCodePostmanServer({
|
|
|
3289
3344
|
};
|
|
3290
3345
|
}
|
|
3291
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
|
+
|
|
3292
3388
|
function buildGeminiPostmanServer({
|
|
3293
3389
|
apiKeyEnvVar = POSTMAN_API_KEY_ENV_VAR,
|
|
3294
3390
|
mcpUrl = POSTMAN_MCP_URL,
|
|
@@ -3301,12 +3397,19 @@ function buildGeminiPostmanServer({
|
|
|
3301
3397
|
};
|
|
3302
3398
|
}
|
|
3303
3399
|
|
|
3400
|
+
function buildGeminiFoundryServer({ scope = "auto" } = {}) {
|
|
3401
|
+
return {
|
|
3402
|
+
command: FOUNDRY_MCP_COMMAND,
|
|
3403
|
+
args: buildFoundryServeArgs({ scope }),
|
|
3404
|
+
env: {},
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3304
3408
|
function buildGeminiStitchServer({
|
|
3305
3409
|
apiKeyEnvVar = STITCH_API_KEY_ENV_VAR,
|
|
3306
3410
|
mcpUrl = STITCH_MCP_URL,
|
|
3307
3411
|
}) {
|
|
3308
3412
|
return {
|
|
3309
|
-
$typeName: "exa.cascade_plugins_pb.CascadePluginCommandTemplate",
|
|
3310
3413
|
command: "npx",
|
|
3311
3414
|
args: [
|
|
3312
3415
|
"-y",
|
|
@@ -3725,6 +3828,7 @@ async function applyPostmanMcpForPlatform({
|
|
|
3725
3828
|
stitchApiKeyEnvVar,
|
|
3726
3829
|
stitchMcpUrl,
|
|
3727
3830
|
includeStitchMcp = false,
|
|
3831
|
+
includeFoundryMcp = true,
|
|
3728
3832
|
dryRun = false,
|
|
3729
3833
|
cwd = process.cwd(),
|
|
3730
3834
|
}) {
|
|
@@ -3750,6 +3854,13 @@ async function applyPostmanMcpForPlatform({
|
|
|
3750
3854
|
apiKeyEnvVar,
|
|
3751
3855
|
mcpUrl,
|
|
3752
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
|
+
}
|
|
3753
3864
|
if (includeStitchMcp) {
|
|
3754
3865
|
mcpServers[STITCH_MCP_SERVER_ID] = buildGeminiStitchServer({
|
|
3755
3866
|
apiKeyEnvVar: stitchApiKeyEnvVar,
|
|
@@ -3779,6 +3890,28 @@ async function applyPostmanMcpForPlatform({
|
|
|
3779
3890
|
targetPath: configPath,
|
|
3780
3891
|
updater: (existing) => {
|
|
3781
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
|
+
|
|
3782
3915
|
const servers =
|
|
3783
3916
|
next.servers &&
|
|
3784
3917
|
typeof next.servers === "object" &&
|
|
@@ -3789,6 +3922,13 @@ async function applyPostmanMcpForPlatform({
|
|
|
3789
3922
|
apiKeyEnvVar,
|
|
3790
3923
|
mcpUrl,
|
|
3791
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
|
+
}
|
|
3792
3932
|
next.servers = servers;
|
|
3793
3933
|
return next;
|
|
3794
3934
|
},
|
|
@@ -3820,6 +3960,13 @@ async function applyPostmanMcpForPlatform({
|
|
|
3820
3960
|
apiKeyEnvVar,
|
|
3821
3961
|
mcpUrl,
|
|
3822
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
|
+
}
|
|
3823
3970
|
next.servers = servers;
|
|
3824
3971
|
return next;
|
|
3825
3972
|
},
|
|
@@ -3850,6 +3997,13 @@ async function applyPostmanMcpForPlatform({
|
|
|
3850
3997
|
} catch {
|
|
3851
3998
|
// Best effort. Add will still run and becomes source of truth.
|
|
3852
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
|
+
}
|
|
3853
4007
|
|
|
3854
4008
|
try {
|
|
3855
4009
|
await execFile(
|
|
@@ -3878,6 +4032,27 @@ async function applyPostmanMcpForPlatform({
|
|
|
3878
4032
|
};
|
|
3879
4033
|
}
|
|
3880
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
|
+
|
|
3881
4056
|
return {
|
|
3882
4057
|
kind: "codex-cli",
|
|
3883
4058
|
scope: mcpScope,
|
|
@@ -3974,6 +4149,7 @@ async function resolvePostmanInstallSelection({
|
|
|
3974
4149
|
);
|
|
3975
4150
|
const mcpBuildLocal = Boolean(options.mcpBuildLocal);
|
|
3976
4151
|
const mcpToolSync = options.mcpToolSync !== false;
|
|
4152
|
+
const foundryMcpEnabled = options.foundryMcp !== false;
|
|
3977
4153
|
|
|
3978
4154
|
const canPrompt = !options.yes && process.stdin.isTTY;
|
|
3979
4155
|
if (canPrompt && !hasWorkspaceOption) {
|
|
@@ -4201,6 +4377,7 @@ async function resolvePostmanInstallSelection({
|
|
|
4201
4377
|
mcpBuildLocal,
|
|
4202
4378
|
dockerImageAction,
|
|
4203
4379
|
mcpToolSync,
|
|
4380
|
+
foundryMcpEnabled,
|
|
4204
4381
|
runtimeSkipped,
|
|
4205
4382
|
defaultWorkspaceId: defaultWorkspaceId ?? null,
|
|
4206
4383
|
workspaceSelectionSource,
|
|
@@ -4430,6 +4607,7 @@ async function configurePostmanInstallArtifacts({
|
|
|
4430
4607
|
stitchApiKeyEnvVar: effectiveStitchApiKeyEnvVar,
|
|
4431
4608
|
stitchMcpUrl: effectiveStitchMcpUrl,
|
|
4432
4609
|
includeStitchMcp: shouldInstallStitch,
|
|
4610
|
+
includeFoundryMcp: postmanSelection.foundryMcpEnabled,
|
|
4433
4611
|
dryRun,
|
|
4434
4612
|
cwd,
|
|
4435
4613
|
});
|
|
@@ -4468,6 +4646,7 @@ async function configurePostmanInstallArtifacts({
|
|
|
4468
4646
|
mcpBuildLocal: postmanSelection.mcpBuildLocal,
|
|
4469
4647
|
dockerImageAction: postmanSelection.dockerImageAction,
|
|
4470
4648
|
mcpToolSync: postmanSelection.mcpToolSync,
|
|
4649
|
+
foundryMcpEnabled: postmanSelection.foundryMcpEnabled,
|
|
4471
4650
|
apiKeySource: effectiveApiKeySource,
|
|
4472
4651
|
stitchApiKeySource: effectiveStitchApiKeySource,
|
|
4473
4652
|
defaultWorkspaceId: effectiveDefaultWorkspaceId,
|
|
@@ -4902,9 +5081,13 @@ async function installBundleArtifacts({
|
|
|
4902
5081
|
skipped.push(destination);
|
|
4903
5082
|
else installed.push(destination);
|
|
4904
5083
|
}
|
|
5084
|
+
const agentSkillDependencies = await resolvePlatformAgentSkillDependencies({
|
|
5085
|
+
platformRoot,
|
|
5086
|
+
platformSpec,
|
|
5087
|
+
});
|
|
4905
5088
|
const skillIds = await resolveInstallSkillIds({
|
|
4906
5089
|
platformSpec,
|
|
4907
|
-
extraSkillIds,
|
|
5090
|
+
extraSkillIds: [...extraSkillIds, ...agentSkillDependencies],
|
|
4908
5091
|
skillProfile,
|
|
4909
5092
|
});
|
|
4910
5093
|
for (const skillId of skillIds) {
|
|
@@ -5423,6 +5606,9 @@ function printPostmanSetupSummary({ postmanSetup }) {
|
|
|
5423
5606
|
console.log(`- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`);
|
|
5424
5607
|
console.log(`- MCP image prepare: ${postmanSetup.dockerImageAction}`);
|
|
5425
5608
|
console.log(`- MCP tool sync: ${postmanSetup.mcpToolSync ? "enabled" : "disabled"}`);
|
|
5609
|
+
console.log(
|
|
5610
|
+
`- Foundry MCP side-by-side: ${postmanSetup.foundryMcpEnabled ? "enabled" : "disabled"}`,
|
|
5611
|
+
);
|
|
5426
5612
|
console.log(`- Postman API key source: ${postmanSetup.apiKeySource}`);
|
|
5427
5613
|
if (postmanSetup.stitchApiKeySource) {
|
|
5428
5614
|
console.log(`- Stitch API key source: ${postmanSetup.stitchApiKeySource}`);
|
|
@@ -5940,6 +6126,10 @@ function withInstallOptions(command) {
|
|
|
5940
6126
|
"enable automatic MCP tool catalog sync (default: enabled)",
|
|
5941
6127
|
)
|
|
5942
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
|
+
)
|
|
5943
6133
|
.option(
|
|
5944
6134
|
"--terminal-integration",
|
|
5945
6135
|
"Antigravity only: enable terminal verification integration (prompts for verifier when interactive)",
|
|
@@ -7047,7 +7237,8 @@ async function collectInlineHeaderFindings({
|
|
|
7047
7237
|
const scanFile = async (filePath) => {
|
|
7048
7238
|
if (!(await pathExists(filePath))) return;
|
|
7049
7239
|
const raw = await readFile(filePath, "utf8");
|
|
7050
|
-
const unsafeStitchHeader =
|
|
7240
|
+
const unsafeStitchHeader =
|
|
7241
|
+
/X-Goog-Api-Key:(?!\s*\$\{[A-Za-z_][A-Za-z0-9_]*\})\s*[^"\n]+/i;
|
|
7051
7242
|
const unsafeBearerHeader = /"Authorization"\s*:\s*"Bearer\s+(?!\$\{)[^"]+/i;
|
|
7052
7243
|
if (unsafeStitchHeader.test(raw) || unsafeBearerHeader.test(raw)) {
|
|
7053
7244
|
findings.push(filePath);
|
|
@@ -7901,6 +8092,111 @@ async function runMcpToolsList(options) {
|
|
|
7901
8092
|
}
|
|
7902
8093
|
}
|
|
7903
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
|
+
|
|
7904
8200
|
function resolveCbxRootPath({ scope, cwd = process.cwd() }) {
|
|
7905
8201
|
if (scope === "global") {
|
|
7906
8202
|
return path.join(os.homedir(), ".cbx");
|
|
@@ -7966,6 +8262,8 @@ async function runMcpRuntimeStatus(options) {
|
|
|
7966
8262
|
const container = dockerAvailable
|
|
7967
8263
|
? await inspectDockerContainerByName({ name: containerName, cwd })
|
|
7968
8264
|
: null;
|
|
8265
|
+
const skillsRoot = path.join(os.homedir(), ".agents", "skills");
|
|
8266
|
+
const skillsRootExists = await pathExists(skillsRoot);
|
|
7969
8267
|
|
|
7970
8268
|
console.log(`Scope: ${scope}`);
|
|
7971
8269
|
console.log(`Config file: ${defaults.configPath}`);
|
|
@@ -7975,6 +8273,9 @@ async function runMcpRuntimeStatus(options) {
|
|
|
7975
8273
|
console.log(`Configured image: ${defaults.defaults.image}`);
|
|
7976
8274
|
console.log(`Configured update policy: ${defaults.defaults.updatePolicy}`);
|
|
7977
8275
|
console.log(`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`);
|
|
8276
|
+
console.log(
|
|
8277
|
+
`Host skills root: ${skillsRoot} (${skillsRootExists ? "present" : "missing"})`,
|
|
8278
|
+
);
|
|
7978
8279
|
console.log(`Docker available: ${dockerAvailable ? "yes" : "no"}`);
|
|
7979
8280
|
console.log(`Container name: ${containerName}`);
|
|
7980
8281
|
if (!dockerAvailable) {
|
|
@@ -7983,11 +8284,34 @@ async function runMcpRuntimeStatus(options) {
|
|
|
7983
8284
|
}
|
|
7984
8285
|
if (!container) {
|
|
7985
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
|
+
}
|
|
7986
8292
|
return;
|
|
7987
8293
|
}
|
|
7988
8294
|
const isRunning = container.status.toLowerCase().startsWith("up ");
|
|
7989
8295
|
console.log(`Container status: ${container.status}`);
|
|
7990
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
|
+
}
|
|
7991
8315
|
if (isRunning) {
|
|
7992
8316
|
const hostPort =
|
|
7993
8317
|
(await resolveDockerContainerHostPort({
|
|
@@ -8050,22 +8374,32 @@ async function runMcpRuntimeUp(options) {
|
|
|
8050
8374
|
}
|
|
8051
8375
|
|
|
8052
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
|
+
}
|
|
8053
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);
|
|
8054
8400
|
await execFile(
|
|
8055
8401
|
"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
|
-
],
|
|
8402
|
+
dockerArgs,
|
|
8069
8403
|
{ cwd },
|
|
8070
8404
|
);
|
|
8071
8405
|
|
|
@@ -8080,9 +8414,20 @@ async function runMcpRuntimeUp(options) {
|
|
|
8080
8414
|
console.log(`Update policy: ${updatePolicy}`);
|
|
8081
8415
|
console.log(`Build local: ${buildLocal ? "yes" : "no"}`);
|
|
8082
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
|
+
}
|
|
8083
8422
|
console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
|
|
8084
8423
|
console.log(`Status: ${running ? running.status : "started"}`);
|
|
8085
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
|
+
}
|
|
8086
8431
|
} catch (error) {
|
|
8087
8432
|
console.error(`\nError: ${error.message}`);
|
|
8088
8433
|
process.exit(1);
|
|
@@ -8531,6 +8876,18 @@ const mcpCommand = program
|
|
|
8531
8876
|
.command("mcp")
|
|
8532
8877
|
.description("Manage Cubis MCP runtime catalogs and tool discovery");
|
|
8533
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
|
+
|
|
8534
8891
|
const mcpToolsCommand = mcpCommand
|
|
8535
8892
|
.command("tools")
|
|
8536
8893
|
.description("Discover and inspect upstream MCP tool catalogs");
|