@cubis/foundry 0.3.46 → 0.3.48
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 +19 -0
- package/mcp/dist/index.js +337 -105
- package/mcp/src/server.ts +41 -179
- package/mcp/src/tools/future/README.md +3 -3
- package/mcp/src/tools/index.ts +14 -0
- package/mcp/src/tools/registry.test.ts +121 -0
- package/mcp/src/tools/registry.ts +318 -0
- package/mcp/src/tools/skillGet.ts +36 -5
- package/mcp/src/tools/skillTools.test.ts +143 -1
- package/mcp/src/vault/manifest.test.ts +71 -1
- package/mcp/src/vault/manifest.ts +128 -3
- package/mcp/src/vault/scanner.test.ts +35 -0
- package/mcp/src/vault/scanner.ts +91 -1
- package/package.json +17 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md +189 -36
- package/workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md +195 -40
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +189 -36
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +189 -35
package/README.md
CHANGED
|
@@ -307,6 +307,25 @@ Notes:
|
|
|
307
307
|
- `cbx mcp tools sync` requires `POSTMAN_API_KEY_DEFAULT`.
|
|
308
308
|
- For `--service stitch` or `--service all`, it also requires `STITCH_API_KEY_DEFAULT`.
|
|
309
309
|
|
|
310
|
+
MCP manifest + managed rules block maintenance:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
# Generate/refresh MCP manifest snapshot
|
|
314
|
+
npm run generate:mcp-manifest
|
|
315
|
+
|
|
316
|
+
# Validate MCP skill catalog + rule references
|
|
317
|
+
npm run validate:mcp-skills
|
|
318
|
+
npm run validate:mcp-manifest
|
|
319
|
+
|
|
320
|
+
# Inject/check managed MCP block in platform rule files
|
|
321
|
+
npm run inject:mcp-rules:all
|
|
322
|
+
npm run check:mcp-rules:all
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Generated MCP artifacts:
|
|
326
|
+
- `mcp/generated/mcp-manifest.json` (catalog snapshot used by managed rule blocks)
|
|
327
|
+
- `mcp/generated/README.md` (artifact notes)
|
|
328
|
+
|
|
310
329
|
Foundry local serve command (canonical entrypoint for MCP client registration):
|
|
311
330
|
|
|
312
331
|
```bash
|
package/mcp/dist/index.js
CHANGED
|
@@ -127,7 +127,7 @@ function loadServerConfig(configPath) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// src/vault/scanner.ts
|
|
130
|
-
import { readdir, stat } from "fs/promises";
|
|
130
|
+
import { open, readdir, stat } from "fs/promises";
|
|
131
131
|
import path2 from "path";
|
|
132
132
|
async function scanVaultRoots(roots, basePath) {
|
|
133
133
|
const skills = [];
|
|
@@ -148,6 +148,13 @@ async function scanVaultRoots(roots, basePath) {
|
|
|
148
148
|
const skillFile = path2.join(entryPath, "SKILL.md");
|
|
149
149
|
const skillStat = await stat(skillFile).catch(() => null);
|
|
150
150
|
if (!skillStat?.isFile()) continue;
|
|
151
|
+
const wrapperKind = await detectWrapperKind(entry, skillFile);
|
|
152
|
+
if (wrapperKind) {
|
|
153
|
+
logger.debug(
|
|
154
|
+
`Skipping wrapper skill ${entry} (${wrapperKind}) at ${skillFile}`
|
|
155
|
+
);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
151
158
|
skills.push({
|
|
152
159
|
id: entry,
|
|
153
160
|
category: deriveCategory(entry),
|
|
@@ -159,6 +166,69 @@ async function scanVaultRoots(roots, basePath) {
|
|
|
159
166
|
logger.info(`Vault scan complete: ${skills.length} skills discovered`);
|
|
160
167
|
return skills;
|
|
161
168
|
}
|
|
169
|
+
var WRAPPER_PREFIXES = ["workflow-", "agent-"];
|
|
170
|
+
var WRAPPER_KINDS = /* @__PURE__ */ new Set(["workflow", "agent"]);
|
|
171
|
+
var FRONTMATTER_PREVIEW_BYTES = 8192;
|
|
172
|
+
function extractWrapperKindFromId(skillId) {
|
|
173
|
+
const lower = skillId.toLowerCase();
|
|
174
|
+
if (lower.startsWith(WRAPPER_PREFIXES[0])) return "workflow";
|
|
175
|
+
if (lower.startsWith(WRAPPER_PREFIXES[1])) return "agent";
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
async function readFrontmatterPreview(skillFile) {
|
|
179
|
+
const handle = await open(skillFile, "r").catch(() => null);
|
|
180
|
+
if (!handle) return null;
|
|
181
|
+
try {
|
|
182
|
+
const buffer = Buffer.alloc(FRONTMATTER_PREVIEW_BYTES);
|
|
183
|
+
const { bytesRead } = await handle.read(
|
|
184
|
+
buffer,
|
|
185
|
+
0,
|
|
186
|
+
FRONTMATTER_PREVIEW_BYTES,
|
|
187
|
+
0
|
|
188
|
+
);
|
|
189
|
+
return buffer.toString("utf8", 0, bytesRead);
|
|
190
|
+
} finally {
|
|
191
|
+
await handle.close();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function parseFrontmatter(rawPreview) {
|
|
195
|
+
const match = rawPreview.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
196
|
+
return match?.[1] ?? null;
|
|
197
|
+
}
|
|
198
|
+
function extractMetadataWrapper(frontmatter) {
|
|
199
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
200
|
+
let inMetadata = false;
|
|
201
|
+
for (const line of lines) {
|
|
202
|
+
if (!inMetadata) {
|
|
203
|
+
if (/^\s*metadata\s*:\s*$/.test(line)) {
|
|
204
|
+
inMetadata = true;
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (!line.trim()) continue;
|
|
209
|
+
if (!/^\s+/.test(line)) break;
|
|
210
|
+
const match = line.match(/^\s+wrapper\s*:\s*(.+)\s*$/);
|
|
211
|
+
if (!match) continue;
|
|
212
|
+
const value = match[1].trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
213
|
+
if (WRAPPER_KINDS.has(value)) {
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
async function detectWrapperKind(skillId, skillFile) {
|
|
220
|
+
const byId = extractWrapperKindFromId(skillId);
|
|
221
|
+
if (byId) return byId;
|
|
222
|
+
const rawPreview = await readFrontmatterPreview(skillFile);
|
|
223
|
+
if (!rawPreview) return null;
|
|
224
|
+
const frontmatter = parseFrontmatter(rawPreview);
|
|
225
|
+
if (!frontmatter) return null;
|
|
226
|
+
const byMetadata = extractMetadataWrapper(frontmatter);
|
|
227
|
+
if (byMetadata === "workflow" || byMetadata === "agent") {
|
|
228
|
+
return byMetadata;
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
162
232
|
function deriveCategory(skillId) {
|
|
163
233
|
const categoryMap = {
|
|
164
234
|
flutter: "mobile",
|
|
@@ -244,7 +314,8 @@ function deriveCategory(skillId) {
|
|
|
244
314
|
}
|
|
245
315
|
|
|
246
316
|
// src/vault/manifest.ts
|
|
247
|
-
import { readFile } from "fs/promises";
|
|
317
|
+
import { readdir as readdir2, readFile } from "fs/promises";
|
|
318
|
+
import path3 from "path";
|
|
248
319
|
|
|
249
320
|
// src/telemetry/tokenBudget.ts
|
|
250
321
|
var TOKEN_ESTIMATOR_VERSION = "char-estimator-v1";
|
|
@@ -348,6 +419,90 @@ function parseDescriptionFromFrontmatter(content, maxLength) {
|
|
|
348
419
|
async function readFullSkillContent(skillPath) {
|
|
349
420
|
return readFile(skillPath, "utf8");
|
|
350
421
|
}
|
|
422
|
+
var MARKDOWN_LINK_RE = /\[[^\]]+\]\(([^)]+)\)/g;
|
|
423
|
+
var MAX_REFERENCED_FILES = 25;
|
|
424
|
+
function normalizeLinkTarget(rawTarget) {
|
|
425
|
+
let target = String(rawTarget || "").trim();
|
|
426
|
+
if (!target) return null;
|
|
427
|
+
if (target.startsWith("<") && target.endsWith(">")) {
|
|
428
|
+
target = target.slice(1, -1).trim();
|
|
429
|
+
}
|
|
430
|
+
const firstSpace = target.search(/\s/);
|
|
431
|
+
if (firstSpace > 0) {
|
|
432
|
+
target = target.slice(0, firstSpace).trim();
|
|
433
|
+
}
|
|
434
|
+
target = target.split("#")[0].split("?")[0].trim();
|
|
435
|
+
if (!target) return null;
|
|
436
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(target)) return null;
|
|
437
|
+
if (/^[a-zA-Z]:[\\/]/.test(target)) return null;
|
|
438
|
+
if (path3.isAbsolute(target)) return null;
|
|
439
|
+
return target;
|
|
440
|
+
}
|
|
441
|
+
function collectReferencedMarkdownTargets(skillContent) {
|
|
442
|
+
const targets = [];
|
|
443
|
+
const seen = /* @__PURE__ */ new Set();
|
|
444
|
+
for (const match of skillContent.matchAll(MARKDOWN_LINK_RE)) {
|
|
445
|
+
const raw = match[1];
|
|
446
|
+
const normalized = normalizeLinkTarget(raw);
|
|
447
|
+
if (!normalized) continue;
|
|
448
|
+
if (!normalized.toLowerCase().endsWith(".md")) continue;
|
|
449
|
+
if (seen.has(normalized)) continue;
|
|
450
|
+
seen.add(normalized);
|
|
451
|
+
targets.push(normalized);
|
|
452
|
+
if (targets.length >= MAX_REFERENCED_FILES) break;
|
|
453
|
+
}
|
|
454
|
+
return targets;
|
|
455
|
+
}
|
|
456
|
+
async function readReferencedMarkdownFiles(skillPath, skillContent) {
|
|
457
|
+
const skillDir = path3.dirname(skillPath);
|
|
458
|
+
let targets = collectReferencedMarkdownTargets(skillContent);
|
|
459
|
+
if (targets.length === 0) {
|
|
460
|
+
targets = await collectSiblingMarkdownTargets(skillDir);
|
|
461
|
+
}
|
|
462
|
+
const references = [];
|
|
463
|
+
for (const target of targets) {
|
|
464
|
+
const resolved = path3.resolve(skillDir, target);
|
|
465
|
+
const relative = path3.relative(skillDir, resolved);
|
|
466
|
+
if (relative.startsWith("..") || path3.isAbsolute(relative)) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (path3.basename(resolved).toLowerCase() === "skill.md") continue;
|
|
470
|
+
try {
|
|
471
|
+
const content = await readFile(resolved, "utf8");
|
|
472
|
+
references.push({
|
|
473
|
+
relativePath: relative.split(path3.sep).join("/"),
|
|
474
|
+
content
|
|
475
|
+
});
|
|
476
|
+
} catch (err) {
|
|
477
|
+
logger.debug(`Failed to read referenced markdown ${resolved}: ${err}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return references;
|
|
481
|
+
}
|
|
482
|
+
async function collectSiblingMarkdownTargets(skillDir) {
|
|
483
|
+
const entries = await readdir2(skillDir, { withFileTypes: true }).catch(
|
|
484
|
+
() => []
|
|
485
|
+
);
|
|
486
|
+
const targets = [];
|
|
487
|
+
for (const entry of entries) {
|
|
488
|
+
if (!entry.isFile()) continue;
|
|
489
|
+
if (entry.name.startsWith(".")) continue;
|
|
490
|
+
if (!entry.name.toLowerCase().endsWith(".md")) continue;
|
|
491
|
+
if (entry.name.toLowerCase() === "skill.md") continue;
|
|
492
|
+
targets.push(entry.name);
|
|
493
|
+
if (targets.length >= MAX_REFERENCED_FILES) break;
|
|
494
|
+
}
|
|
495
|
+
targets.sort((a, b) => a.localeCompare(b));
|
|
496
|
+
return targets;
|
|
497
|
+
}
|
|
498
|
+
async function readSkillContentWithReferences(skillPath, includeReferences = true) {
|
|
499
|
+
const skillContent = await readFullSkillContent(skillPath);
|
|
500
|
+
if (!includeReferences) {
|
|
501
|
+
return { skillContent, references: [] };
|
|
502
|
+
}
|
|
503
|
+
const references = await readReferencedMarkdownFiles(skillPath, skillContent);
|
|
504
|
+
return { skillContent, references };
|
|
505
|
+
}
|
|
351
506
|
async function enrichWithDescriptions(skills, maxLength) {
|
|
352
507
|
return Promise.all(
|
|
353
508
|
skills.map(async (skill) => {
|
|
@@ -521,17 +676,40 @@ async function handleSkillSearch(args, manifest, summaryMaxLength, charsPerToken
|
|
|
521
676
|
// src/tools/skillGet.ts
|
|
522
677
|
import { z as z5 } from "zod";
|
|
523
678
|
var skillGetName = "skill_get";
|
|
524
|
-
var skillGetDescription = "Get
|
|
679
|
+
var skillGetDescription = "Get full content of a specific skill by ID. Returns SKILL.md content and optionally direct referenced markdown files.";
|
|
525
680
|
var skillGetSchema = z5.object({
|
|
526
|
-
id: z5.string().describe("The skill ID (directory name) to retrieve")
|
|
681
|
+
id: z5.string().describe("The skill ID (directory name) to retrieve"),
|
|
682
|
+
includeReferences: z5.boolean().optional().describe(
|
|
683
|
+
"Whether to include direct local markdown references from SKILL.md (default: true)"
|
|
684
|
+
)
|
|
527
685
|
});
|
|
528
686
|
async function handleSkillGet(args, manifest, charsPerToken) {
|
|
529
|
-
const { id } = args;
|
|
687
|
+
const { id, includeReferences = true } = args;
|
|
688
|
+
if (id.startsWith("workflow-") || id.startsWith("agent-")) {
|
|
689
|
+
invalidInput(
|
|
690
|
+
`Skill id "${id}" appears to be a wrapper id. Use workflow/agent routing (for example $workflow-implement-track or $agent-backend-specialist) and call skill_get only for concrete skill ids.`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
530
693
|
const skill = manifest.skills.find((s) => s.id === id);
|
|
531
694
|
if (!skill) {
|
|
532
695
|
notFound("Skill", id);
|
|
533
696
|
}
|
|
534
|
-
const
|
|
697
|
+
const { skillContent, references } = await readSkillContentWithReferences(
|
|
698
|
+
skill.path,
|
|
699
|
+
includeReferences
|
|
700
|
+
);
|
|
701
|
+
const referenceSection = references.length > 0 ? [
|
|
702
|
+
"",
|
|
703
|
+
"## Referenced Files",
|
|
704
|
+
"",
|
|
705
|
+
...references.flatMap((ref) => [
|
|
706
|
+
`### ${ref.relativePath}`,
|
|
707
|
+
"",
|
|
708
|
+
ref.content.trimEnd(),
|
|
709
|
+
""
|
|
710
|
+
])
|
|
711
|
+
].join("\n") : "";
|
|
712
|
+
const content = `${skillContent}${referenceSection}`;
|
|
535
713
|
const loadedSkillEstimatedTokens = estimateTokensFromText(
|
|
536
714
|
content,
|
|
537
715
|
charsPerToken
|
|
@@ -550,6 +728,7 @@ async function handleSkillGet(args, manifest, charsPerToken) {
|
|
|
550
728
|
}
|
|
551
729
|
],
|
|
552
730
|
structuredContent: {
|
|
731
|
+
references: references.map((ref) => ({ path: ref.relativePath })),
|
|
553
732
|
metrics
|
|
554
733
|
}
|
|
555
734
|
};
|
|
@@ -642,15 +821,15 @@ function handleSkillBudgetReport(args, manifest, charsPerToken) {
|
|
|
642
821
|
import { z as z7 } from "zod";
|
|
643
822
|
|
|
644
823
|
// src/cbxConfig/paths.ts
|
|
645
|
-
import
|
|
824
|
+
import path4 from "path";
|
|
646
825
|
import os from "os";
|
|
647
826
|
import { existsSync } from "fs";
|
|
648
827
|
function globalConfigPath() {
|
|
649
|
-
return
|
|
828
|
+
return path4.join(os.homedir(), ".cbx", "cbx_config.json");
|
|
650
829
|
}
|
|
651
830
|
function projectConfigPath(workspaceRoot) {
|
|
652
831
|
const root = workspaceRoot ?? process.cwd();
|
|
653
|
-
return
|
|
832
|
+
return path4.join(root, "cbx_config.json");
|
|
654
833
|
}
|
|
655
834
|
function resolveConfigPath(scope, workspaceRoot) {
|
|
656
835
|
if (scope === "global") {
|
|
@@ -726,7 +905,7 @@ import {
|
|
|
726
905
|
mkdirSync,
|
|
727
906
|
existsSync as existsSync3
|
|
728
907
|
} from "fs";
|
|
729
|
-
import
|
|
908
|
+
import path5 from "path";
|
|
730
909
|
import { parse as parseJsonc2 } from "jsonc-parser";
|
|
731
910
|
function writeConfigField(fieldPath, value, scope, workspaceRoot) {
|
|
732
911
|
const resolved = resolveConfigPath(scope, workspaceRoot);
|
|
@@ -752,7 +931,7 @@ function writeConfigField(fieldPath, value, scope, workspaceRoot) {
|
|
|
752
931
|
current = current[key];
|
|
753
932
|
}
|
|
754
933
|
current[parts[parts.length - 1]] = value;
|
|
755
|
-
const dir =
|
|
934
|
+
const dir = path5.dirname(configPath);
|
|
756
935
|
if (!existsSync3(dir)) {
|
|
757
936
|
mkdirSync(dir, { recursive: true });
|
|
758
937
|
}
|
|
@@ -1180,17 +1359,138 @@ function handleStitchGetStatus(args) {
|
|
|
1180
1359
|
};
|
|
1181
1360
|
}
|
|
1182
1361
|
|
|
1362
|
+
// src/tools/registry.ts
|
|
1363
|
+
function withDefaultScope(args, defaultScope) {
|
|
1364
|
+
const safeArgs = args && typeof args === "object" ? args : {};
|
|
1365
|
+
return {
|
|
1366
|
+
...safeArgs,
|
|
1367
|
+
scope: typeof safeArgs.scope === "string" ? safeArgs.scope : defaultScope
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
var TOOL_REGISTRY = [
|
|
1371
|
+
// ── Skill vault tools ─────────────────────────────────────
|
|
1372
|
+
{
|
|
1373
|
+
name: skillListCategoriesName,
|
|
1374
|
+
description: skillListCategoriesDescription,
|
|
1375
|
+
schema: skillListCategoriesSchema,
|
|
1376
|
+
category: "skill",
|
|
1377
|
+
createHandler: (ctx) => async () => handleSkillListCategories(ctx.manifest, ctx.charsPerToken)
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
name: skillBrowseCategoryName,
|
|
1381
|
+
description: skillBrowseCategoryDescription,
|
|
1382
|
+
schema: skillBrowseCategorySchema,
|
|
1383
|
+
category: "skill",
|
|
1384
|
+
createHandler: (ctx) => async (args) => handleSkillBrowseCategory(
|
|
1385
|
+
args,
|
|
1386
|
+
ctx.manifest,
|
|
1387
|
+
ctx.summaryMaxLength,
|
|
1388
|
+
ctx.charsPerToken
|
|
1389
|
+
)
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
name: skillSearchName,
|
|
1393
|
+
description: skillSearchDescription,
|
|
1394
|
+
schema: skillSearchSchema,
|
|
1395
|
+
category: "skill",
|
|
1396
|
+
createHandler: (ctx) => async (args) => handleSkillSearch(
|
|
1397
|
+
args,
|
|
1398
|
+
ctx.manifest,
|
|
1399
|
+
ctx.summaryMaxLength,
|
|
1400
|
+
ctx.charsPerToken
|
|
1401
|
+
)
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
name: skillGetName,
|
|
1405
|
+
description: skillGetDescription,
|
|
1406
|
+
schema: skillGetSchema,
|
|
1407
|
+
category: "skill",
|
|
1408
|
+
createHandler: (ctx) => async (args) => handleSkillGet(
|
|
1409
|
+
args,
|
|
1410
|
+
ctx.manifest,
|
|
1411
|
+
ctx.charsPerToken
|
|
1412
|
+
)
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: skillBudgetReportName,
|
|
1416
|
+
description: skillBudgetReportDescription,
|
|
1417
|
+
schema: skillBudgetReportSchema,
|
|
1418
|
+
category: "skill",
|
|
1419
|
+
createHandler: (ctx) => async (args) => handleSkillBudgetReport(
|
|
1420
|
+
args,
|
|
1421
|
+
ctx.manifest,
|
|
1422
|
+
ctx.charsPerToken
|
|
1423
|
+
)
|
|
1424
|
+
},
|
|
1425
|
+
// ── Postman tools ─────────────────────────────────────────
|
|
1426
|
+
{
|
|
1427
|
+
name: postmanGetModeName,
|
|
1428
|
+
description: postmanGetModeDescription,
|
|
1429
|
+
schema: postmanGetModeSchema,
|
|
1430
|
+
category: "postman",
|
|
1431
|
+
createHandler: (ctx) => async (args) => handlePostmanGetMode(
|
|
1432
|
+
withDefaultScope(args, ctx.defaultConfigScope)
|
|
1433
|
+
)
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
name: postmanSetModeName,
|
|
1437
|
+
description: postmanSetModeDescription,
|
|
1438
|
+
schema: postmanSetModeSchema,
|
|
1439
|
+
category: "postman",
|
|
1440
|
+
createHandler: (ctx) => async (args) => handlePostmanSetMode(
|
|
1441
|
+
withDefaultScope(args, ctx.defaultConfigScope)
|
|
1442
|
+
)
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
name: postmanGetStatusName,
|
|
1446
|
+
description: postmanGetStatusDescription,
|
|
1447
|
+
schema: postmanGetStatusSchema,
|
|
1448
|
+
category: "postman",
|
|
1449
|
+
createHandler: (ctx) => async (args) => handlePostmanGetStatus(
|
|
1450
|
+
withDefaultScope(args, ctx.defaultConfigScope)
|
|
1451
|
+
)
|
|
1452
|
+
},
|
|
1453
|
+
// ── Stitch tools ──────────────────────────────────────────
|
|
1454
|
+
{
|
|
1455
|
+
name: stitchGetModeName,
|
|
1456
|
+
description: stitchGetModeDescription,
|
|
1457
|
+
schema: stitchGetModeSchema,
|
|
1458
|
+
category: "stitch",
|
|
1459
|
+
createHandler: (ctx) => async (args) => handleStitchGetMode(
|
|
1460
|
+
withDefaultScope(args, ctx.defaultConfigScope)
|
|
1461
|
+
)
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
name: stitchSetProfileName,
|
|
1465
|
+
description: stitchSetProfileDescription,
|
|
1466
|
+
schema: stitchSetProfileSchema,
|
|
1467
|
+
category: "stitch",
|
|
1468
|
+
createHandler: (ctx) => async (args) => handleStitchSetProfile(
|
|
1469
|
+
withDefaultScope(args, ctx.defaultConfigScope)
|
|
1470
|
+
)
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: stitchGetStatusName,
|
|
1474
|
+
description: stitchGetStatusDescription,
|
|
1475
|
+
schema: stitchGetStatusSchema,
|
|
1476
|
+
category: "stitch",
|
|
1477
|
+
createHandler: (ctx) => async (args) => handleStitchGetStatus(
|
|
1478
|
+
withDefaultScope(args, ctx.defaultConfigScope)
|
|
1479
|
+
)
|
|
1480
|
+
}
|
|
1481
|
+
];
|
|
1482
|
+
|
|
1183
1483
|
// src/upstream/passthrough.ts
|
|
1184
1484
|
import { mkdir, writeFile } from "fs/promises";
|
|
1185
|
-
import
|
|
1485
|
+
import path6 from "path";
|
|
1186
1486
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1187
1487
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1188
1488
|
function resolveCatalogDir(configPath) {
|
|
1189
|
-
const configDir =
|
|
1190
|
-
if (
|
|
1191
|
-
return
|
|
1489
|
+
const configDir = path6.dirname(configPath);
|
|
1490
|
+
if (path6.basename(configDir) === ".cbx") {
|
|
1491
|
+
return path6.join(configDir, "mcp", "catalog");
|
|
1192
1492
|
}
|
|
1193
|
-
return
|
|
1493
|
+
return path6.join(configDir, ".cbx", "mcp", "catalog");
|
|
1194
1494
|
}
|
|
1195
1495
|
function getServiceAuth(config, service) {
|
|
1196
1496
|
if (service === "postman") {
|
|
@@ -1265,7 +1565,7 @@ async function withUpstreamClient({
|
|
|
1265
1565
|
async function persistCatalog(catalog) {
|
|
1266
1566
|
if (!catalog.configPath) return;
|
|
1267
1567
|
const catalogDir = resolveCatalogDir(catalog.configPath);
|
|
1268
|
-
const catalogPath =
|
|
1568
|
+
const catalogPath = path6.join(catalogDir, `${catalog.service}.json`);
|
|
1269
1569
|
const payload = {
|
|
1270
1570
|
schemaVersion: 1,
|
|
1271
1571
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1420,92 +1720,23 @@ async function createServer({
|
|
|
1420
1720
|
name: config.server.name,
|
|
1421
1721
|
version: config.server.version
|
|
1422
1722
|
});
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
...safeArgs,
|
|
1429
|
-
scope: typeof safeArgs.scope === "string" ? safeArgs.scope : defaultConfigScope
|
|
1430
|
-
};
|
|
1723
|
+
const runtimeCtx = {
|
|
1724
|
+
manifest,
|
|
1725
|
+
charsPerToken: config.telemetry?.charsPerToken ?? 4,
|
|
1726
|
+
summaryMaxLength: config.vault.summaryMaxLength,
|
|
1727
|
+
defaultConfigScope
|
|
1431
1728
|
};
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
);
|
|
1444
|
-
server.tool(
|
|
1445
|
-
skillSearchName,
|
|
1446
|
-
skillSearchDescription,
|
|
1447
|
-
skillSearchSchema.shape,
|
|
1448
|
-
async (args) => handleSkillSearch(args, manifest, maxLen, charsPerToken)
|
|
1449
|
-
);
|
|
1450
|
-
server.tool(
|
|
1451
|
-
skillGetName,
|
|
1452
|
-
skillGetDescription,
|
|
1453
|
-
skillGetSchema.shape,
|
|
1454
|
-
async (args) => handleSkillGet(args, manifest, charsPerToken)
|
|
1455
|
-
);
|
|
1456
|
-
server.tool(
|
|
1457
|
-
skillBudgetReportName,
|
|
1458
|
-
skillBudgetReportDescription,
|
|
1459
|
-
skillBudgetReportSchema.shape,
|
|
1460
|
-
async (args) => handleSkillBudgetReport(args, manifest, charsPerToken)
|
|
1461
|
-
);
|
|
1462
|
-
server.tool(
|
|
1463
|
-
postmanGetModeName,
|
|
1464
|
-
postmanGetModeDescription,
|
|
1465
|
-
postmanGetModeSchema.shape,
|
|
1466
|
-
async (args) => handlePostmanGetMode(
|
|
1467
|
-
withDefaultScope(args)
|
|
1468
|
-
)
|
|
1469
|
-
);
|
|
1470
|
-
server.tool(
|
|
1471
|
-
postmanSetModeName,
|
|
1472
|
-
postmanSetModeDescription,
|
|
1473
|
-
postmanSetModeSchema.shape,
|
|
1474
|
-
async (args) => handlePostmanSetMode(
|
|
1475
|
-
withDefaultScope(args)
|
|
1476
|
-
)
|
|
1477
|
-
);
|
|
1478
|
-
server.tool(
|
|
1479
|
-
postmanGetStatusName,
|
|
1480
|
-
postmanGetStatusDescription,
|
|
1481
|
-
postmanGetStatusSchema.shape,
|
|
1482
|
-
async (args) => handlePostmanGetStatus(
|
|
1483
|
-
withDefaultScope(args)
|
|
1484
|
-
)
|
|
1485
|
-
);
|
|
1486
|
-
server.tool(
|
|
1487
|
-
stitchGetModeName,
|
|
1488
|
-
stitchGetModeDescription,
|
|
1489
|
-
stitchGetModeSchema.shape,
|
|
1490
|
-
async (args) => handleStitchGetMode(
|
|
1491
|
-
withDefaultScope(args)
|
|
1492
|
-
)
|
|
1493
|
-
);
|
|
1494
|
-
server.tool(
|
|
1495
|
-
stitchSetProfileName,
|
|
1496
|
-
stitchSetProfileDescription,
|
|
1497
|
-
stitchSetProfileSchema.shape,
|
|
1498
|
-
async (args) => handleStitchSetProfile(
|
|
1499
|
-
withDefaultScope(args)
|
|
1500
|
-
)
|
|
1501
|
-
);
|
|
1502
|
-
server.tool(
|
|
1503
|
-
stitchGetStatusName,
|
|
1504
|
-
stitchGetStatusDescription,
|
|
1505
|
-
stitchGetStatusSchema.shape,
|
|
1506
|
-
async (args) => handleStitchGetStatus(
|
|
1507
|
-
withDefaultScope(args)
|
|
1508
|
-
)
|
|
1729
|
+
for (const entry of TOOL_REGISTRY) {
|
|
1730
|
+
const handler = entry.createHandler(runtimeCtx);
|
|
1731
|
+
server.tool(
|
|
1732
|
+
entry.name,
|
|
1733
|
+
entry.description,
|
|
1734
|
+
entry.schema.shape,
|
|
1735
|
+
handler
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
logger.debug(
|
|
1739
|
+
`Registered ${TOOL_REGISTRY.length} built-in tools from registry`
|
|
1509
1740
|
);
|
|
1510
1741
|
const upstreamCatalogs = await discoverUpstreamCatalogs();
|
|
1511
1742
|
const dynamicArgsShape = z13.object({}).passthrough().shape;
|
|
@@ -1524,6 +1755,7 @@ async function createServer({
|
|
|
1524
1755
|
argumentsValue: args && typeof args === "object" ? args : {}
|
|
1525
1756
|
});
|
|
1526
1757
|
return {
|
|
1758
|
+
// SDK content is typed broadly; cast to the expected array shape.
|
|
1527
1759
|
content: result.content ?? [],
|
|
1528
1760
|
structuredContent: result.structuredContent,
|
|
1529
1761
|
isError: Boolean(result.isError)
|
|
@@ -1573,9 +1805,9 @@ function createStreamableHttpTransport(options) {
|
|
|
1573
1805
|
}
|
|
1574
1806
|
|
|
1575
1807
|
// src/index.ts
|
|
1576
|
-
import
|
|
1808
|
+
import path7 from "path";
|
|
1577
1809
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1578
|
-
var __dirname2 =
|
|
1810
|
+
var __dirname2 = path7.dirname(fileURLToPath2(import.meta.url));
|
|
1579
1811
|
function parseArgs(argv) {
|
|
1580
1812
|
let transport = "stdio";
|
|
1581
1813
|
let scope = "auto";
|
|
@@ -1670,7 +1902,7 @@ async function main() {
|
|
|
1670
1902
|
setLogLevel("debug");
|
|
1671
1903
|
}
|
|
1672
1904
|
const serverConfig = loadServerConfig(args.configPath);
|
|
1673
|
-
const basePath =
|
|
1905
|
+
const basePath = path7.resolve(__dirname2, "..");
|
|
1674
1906
|
const skills = await scanVaultRoots(serverConfig.vault.roots, basePath);
|
|
1675
1907
|
const charsPerToken = serverConfig.telemetry.charsPerToken;
|
|
1676
1908
|
const manifest = buildManifest(skills, charsPerToken);
|