@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 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 the full content of a specific skill by ID. Returns the complete SKILL.md file content.";
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 content = await readFullSkillContent(skill.path);
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 path3 from "path";
824
+ import path4 from "path";
646
825
  import os from "os";
647
826
  import { existsSync } from "fs";
648
827
  function globalConfigPath() {
649
- return path3.join(os.homedir(), ".cbx", "cbx_config.json");
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 path3.join(root, "cbx_config.json");
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 path4 from "path";
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 = path4.dirname(configPath);
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 path5 from "path";
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 = path5.dirname(configPath);
1190
- if (path5.basename(configDir) === ".cbx") {
1191
- return path5.join(configDir, "mcp", "catalog");
1489
+ const configDir = path6.dirname(configPath);
1490
+ if (path6.basename(configDir) === ".cbx") {
1491
+ return path6.join(configDir, "mcp", "catalog");
1192
1492
  }
1193
- return path5.join(configDir, ".cbx", "mcp", "catalog");
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 = path5.join(catalogDir, `${catalog.service}.json`);
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 maxLen = config.vault.summaryMaxLength;
1424
- const charsPerToken = config.telemetry?.charsPerToken ?? 4;
1425
- const withDefaultScope = (args) => {
1426
- const safeArgs = args ?? {};
1427
- return {
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
- server.tool(
1433
- skillListCategoriesName,
1434
- skillListCategoriesDescription,
1435
- skillListCategoriesSchema.shape,
1436
- async () => handleSkillListCategories(manifest, charsPerToken)
1437
- );
1438
- server.tool(
1439
- skillBrowseCategoryName,
1440
- skillBrowseCategoryDescription,
1441
- skillBrowseCategorySchema.shape,
1442
- async (args) => handleSkillBrowseCategory(args, manifest, maxLen, charsPerToken)
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 path6 from "path";
1808
+ import path7 from "path";
1577
1809
  import { fileURLToPath as fileURLToPath2 } from "url";
1578
- var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
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 = path6.resolve(__dirname2, "..");
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);