@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/mcp/dist/index.js
CHANGED
|
@@ -17,6 +17,9 @@ var ServerConfigSchema = z.object({
|
|
|
17
17
|
roots: z.array(z.string()).min(1),
|
|
18
18
|
summaryMaxLength: z.number().int().positive().default(200)
|
|
19
19
|
}),
|
|
20
|
+
telemetry: z.object({
|
|
21
|
+
charsPerToken: z.number().positive().default(4)
|
|
22
|
+
}).default({ charsPerToken: 4 }),
|
|
20
23
|
transport: z.object({
|
|
21
24
|
default: z.enum(["stdio", "streamable-http"]).default("stdio"),
|
|
22
25
|
http: z.object({
|
|
@@ -148,7 +151,8 @@ async function scanVaultRoots(roots, basePath) {
|
|
|
148
151
|
skills.push({
|
|
149
152
|
id: entry,
|
|
150
153
|
category: deriveCategory(entry),
|
|
151
|
-
path: skillFile
|
|
154
|
+
path: skillFile,
|
|
155
|
+
fileBytes: skillStat.size
|
|
152
156
|
});
|
|
153
157
|
}
|
|
154
158
|
}
|
|
@@ -241,14 +245,80 @@ function deriveCategory(skillId) {
|
|
|
241
245
|
|
|
242
246
|
// src/vault/manifest.ts
|
|
243
247
|
import { readFile } from "fs/promises";
|
|
244
|
-
|
|
248
|
+
|
|
249
|
+
// src/telemetry/tokenBudget.ts
|
|
250
|
+
var TOKEN_ESTIMATOR_VERSION = "char-estimator-v1";
|
|
251
|
+
function normalizeCharsPerToken(value) {
|
|
252
|
+
if (!Number.isFinite(value) || value <= 0) return 4;
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
function estimateTokensFromCharCount(charCount, charsPerToken) {
|
|
256
|
+
const safeChars = Math.max(0, Math.ceil(charCount));
|
|
257
|
+
const ratio = normalizeCharsPerToken(charsPerToken);
|
|
258
|
+
return Math.ceil(safeChars / ratio);
|
|
259
|
+
}
|
|
260
|
+
function estimateTokensFromText(text, charsPerToken) {
|
|
261
|
+
return estimateTokensFromCharCount(text.length, charsPerToken);
|
|
262
|
+
}
|
|
263
|
+
function estimateTokensFromBytes(byteCount, charsPerToken) {
|
|
264
|
+
return estimateTokensFromCharCount(byteCount, charsPerToken);
|
|
265
|
+
}
|
|
266
|
+
function estimateSavings(fullCatalogEstimatedTokens, usedEstimatedTokens) {
|
|
267
|
+
const full = Math.max(0, Math.ceil(fullCatalogEstimatedTokens));
|
|
268
|
+
const used = Math.max(0, Math.ceil(usedEstimatedTokens));
|
|
269
|
+
if (full <= 0) {
|
|
270
|
+
return {
|
|
271
|
+
estimatedSavingsTokens: 0,
|
|
272
|
+
estimatedSavingsPercent: 0
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const estimatedSavingsTokens = Math.max(0, full - used);
|
|
276
|
+
const estimatedSavingsPercent = Number(
|
|
277
|
+
(estimatedSavingsTokens / full * 100).toFixed(2)
|
|
278
|
+
);
|
|
279
|
+
return {
|
|
280
|
+
estimatedSavingsTokens,
|
|
281
|
+
estimatedSavingsPercent
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function buildSkillToolMetrics({
|
|
285
|
+
charsPerToken,
|
|
286
|
+
fullCatalogEstimatedTokens,
|
|
287
|
+
responseEstimatedTokens,
|
|
288
|
+
selectedSkillsEstimatedTokens = null,
|
|
289
|
+
loadedSkillEstimatedTokens = null
|
|
290
|
+
}) {
|
|
291
|
+
const usedEstimatedTokens = loadedSkillEstimatedTokens ?? selectedSkillsEstimatedTokens ?? responseEstimatedTokens;
|
|
292
|
+
const savings = estimateSavings(fullCatalogEstimatedTokens, usedEstimatedTokens);
|
|
293
|
+
return {
|
|
294
|
+
estimatorVersion: TOKEN_ESTIMATOR_VERSION,
|
|
295
|
+
charsPerToken: normalizeCharsPerToken(charsPerToken),
|
|
296
|
+
fullCatalogEstimatedTokens: Math.max(0, fullCatalogEstimatedTokens),
|
|
297
|
+
responseEstimatedTokens: Math.max(0, responseEstimatedTokens),
|
|
298
|
+
selectedSkillsEstimatedTokens: selectedSkillsEstimatedTokens === null ? null : Math.max(0, selectedSkillsEstimatedTokens),
|
|
299
|
+
loadedSkillEstimatedTokens: loadedSkillEstimatedTokens === null ? null : Math.max(0, loadedSkillEstimatedTokens),
|
|
300
|
+
estimatedSavingsVsFullCatalog: savings.estimatedSavingsTokens,
|
|
301
|
+
estimatedSavingsVsFullCatalogPercent: savings.estimatedSavingsPercent,
|
|
302
|
+
estimated: true
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/vault/manifest.ts
|
|
307
|
+
function buildManifest(skills, charsPerToken) {
|
|
245
308
|
const categorySet = /* @__PURE__ */ new Set();
|
|
309
|
+
let fullCatalogBytes = 0;
|
|
246
310
|
for (const skill of skills) {
|
|
247
311
|
categorySet.add(skill.category);
|
|
312
|
+
fullCatalogBytes += skill.fileBytes;
|
|
248
313
|
}
|
|
249
314
|
return {
|
|
250
315
|
categories: [...categorySet].sort(),
|
|
251
|
-
skills
|
|
316
|
+
skills,
|
|
317
|
+
fullCatalogBytes,
|
|
318
|
+
fullCatalogEstimatedTokens: estimateTokensFromBytes(
|
|
319
|
+
fullCatalogBytes,
|
|
320
|
+
charsPerToken
|
|
321
|
+
)
|
|
252
322
|
};
|
|
253
323
|
}
|
|
254
324
|
async function extractDescription(skillPath, maxLength) {
|
|
@@ -290,14 +360,14 @@ async function enrichWithDescriptions(skills, maxLength) {
|
|
|
290
360
|
|
|
291
361
|
// src/server.ts
|
|
292
362
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
293
|
-
import { z as
|
|
363
|
+
import { z as z13 } from "zod";
|
|
294
364
|
|
|
295
365
|
// src/tools/skillListCategories.ts
|
|
296
366
|
import { z as z2 } from "zod";
|
|
297
367
|
var skillListCategoriesName = "skill_list_categories";
|
|
298
368
|
var skillListCategoriesDescription = "List all skill categories available in the vault. Returns category names and skill counts.";
|
|
299
369
|
var skillListCategoriesSchema = z2.object({});
|
|
300
|
-
function handleSkillListCategories(manifest) {
|
|
370
|
+
function handleSkillListCategories(manifest, charsPerToken) {
|
|
301
371
|
const categoryCounts = {};
|
|
302
372
|
for (const skill of manifest.skills) {
|
|
303
373
|
categoryCounts[skill.category] = (categoryCounts[skill.category] ?? 0) + 1;
|
|
@@ -306,17 +376,23 @@ function handleSkillListCategories(manifest) {
|
|
|
306
376
|
category: cat,
|
|
307
377
|
skillCount: categoryCounts[cat] ?? 0
|
|
308
378
|
}));
|
|
379
|
+
const payload = { categories, totalSkills: manifest.skills.length };
|
|
380
|
+
const text = JSON.stringify(payload, null, 2);
|
|
381
|
+
const metrics = buildSkillToolMetrics({
|
|
382
|
+
charsPerToken,
|
|
383
|
+
fullCatalogEstimatedTokens: manifest.fullCatalogEstimatedTokens,
|
|
384
|
+
responseEstimatedTokens: estimateTokensFromText(text, charsPerToken)
|
|
385
|
+
});
|
|
309
386
|
return {
|
|
310
387
|
content: [
|
|
311
388
|
{
|
|
312
389
|
type: "text",
|
|
313
|
-
text
|
|
314
|
-
{ categories, totalSkills: manifest.skills.length },
|
|
315
|
-
null,
|
|
316
|
-
2
|
|
317
|
-
)
|
|
390
|
+
text
|
|
318
391
|
}
|
|
319
|
-
]
|
|
392
|
+
],
|
|
393
|
+
structuredContent: {
|
|
394
|
+
metrics
|
|
395
|
+
}
|
|
320
396
|
};
|
|
321
397
|
}
|
|
322
398
|
|
|
@@ -350,7 +426,7 @@ var skillBrowseCategoryDescription = "Browse skills within a specific category.
|
|
|
350
426
|
var skillBrowseCategorySchema = z3.object({
|
|
351
427
|
category: z3.string().describe("The category name to browse (from skill_list_categories)")
|
|
352
428
|
});
|
|
353
|
-
async function handleSkillBrowseCategory(args, manifest, summaryMaxLength) {
|
|
429
|
+
async function handleSkillBrowseCategory(args, manifest, summaryMaxLength, charsPerToken) {
|
|
354
430
|
const { category } = args;
|
|
355
431
|
if (!manifest.categories.includes(category)) {
|
|
356
432
|
notFound("Category", category);
|
|
@@ -361,17 +437,28 @@ async function handleSkillBrowseCategory(args, manifest, summaryMaxLength) {
|
|
|
361
437
|
id: s.id,
|
|
362
438
|
description: s.description ?? "(no description)"
|
|
363
439
|
}));
|
|
440
|
+
const payload = { category, skills, count: skills.length };
|
|
441
|
+
const text = JSON.stringify(payload, null, 2);
|
|
442
|
+
const selectedSkillsEstimatedTokens = matching.reduce(
|
|
443
|
+
(sum, skill) => sum + estimateTokensFromBytes(skill.fileBytes, charsPerToken),
|
|
444
|
+
0
|
|
445
|
+
);
|
|
446
|
+
const metrics = buildSkillToolMetrics({
|
|
447
|
+
charsPerToken,
|
|
448
|
+
fullCatalogEstimatedTokens: manifest.fullCatalogEstimatedTokens,
|
|
449
|
+
responseEstimatedTokens: estimateTokensFromText(text, charsPerToken),
|
|
450
|
+
selectedSkillsEstimatedTokens
|
|
451
|
+
});
|
|
364
452
|
return {
|
|
365
453
|
content: [
|
|
366
454
|
{
|
|
367
455
|
type: "text",
|
|
368
|
-
text
|
|
369
|
-
{ category, skills, count: skills.length },
|
|
370
|
-
null,
|
|
371
|
-
2
|
|
372
|
-
)
|
|
456
|
+
text
|
|
373
457
|
}
|
|
374
|
-
]
|
|
458
|
+
],
|
|
459
|
+
structuredContent: {
|
|
460
|
+
metrics
|
|
461
|
+
}
|
|
375
462
|
};
|
|
376
463
|
}
|
|
377
464
|
|
|
@@ -384,7 +471,7 @@ var skillSearchSchema = z4.object({
|
|
|
384
471
|
"Search keyword or phrase to match against skill IDs and descriptions"
|
|
385
472
|
)
|
|
386
473
|
});
|
|
387
|
-
async function handleSkillSearch(args, manifest, summaryMaxLength) {
|
|
474
|
+
async function handleSkillSearch(args, manifest, summaryMaxLength, charsPerToken) {
|
|
388
475
|
const { query } = args;
|
|
389
476
|
const lower = query.toLowerCase();
|
|
390
477
|
let matches = manifest.skills.filter(
|
|
@@ -406,17 +493,28 @@ async function handleSkillSearch(args, manifest, summaryMaxLength) {
|
|
|
406
493
|
category: s.category,
|
|
407
494
|
description: s.description ?? "(no description)"
|
|
408
495
|
}));
|
|
496
|
+
const payload = { query, results, count: results.length };
|
|
497
|
+
const text = JSON.stringify(payload, null, 2);
|
|
498
|
+
const selectedSkillsEstimatedTokens = matches.reduce(
|
|
499
|
+
(sum, skill) => sum + estimateTokensFromBytes(skill.fileBytes, charsPerToken),
|
|
500
|
+
0
|
|
501
|
+
);
|
|
502
|
+
const metrics = buildSkillToolMetrics({
|
|
503
|
+
charsPerToken,
|
|
504
|
+
fullCatalogEstimatedTokens: manifest.fullCatalogEstimatedTokens,
|
|
505
|
+
responseEstimatedTokens: estimateTokensFromText(text, charsPerToken),
|
|
506
|
+
selectedSkillsEstimatedTokens
|
|
507
|
+
});
|
|
409
508
|
return {
|
|
410
509
|
content: [
|
|
411
510
|
{
|
|
412
511
|
type: "text",
|
|
413
|
-
text
|
|
414
|
-
{ query, results, count: results.length },
|
|
415
|
-
null,
|
|
416
|
-
2
|
|
417
|
-
)
|
|
512
|
+
text
|
|
418
513
|
}
|
|
419
|
-
]
|
|
514
|
+
],
|
|
515
|
+
structuredContent: {
|
|
516
|
+
metrics
|
|
517
|
+
}
|
|
420
518
|
};
|
|
421
519
|
}
|
|
422
520
|
|
|
@@ -427,25 +525,121 @@ var skillGetDescription = "Get the full content of a specific skill by ID. Retur
|
|
|
427
525
|
var skillGetSchema = z5.object({
|
|
428
526
|
id: z5.string().describe("The skill ID (directory name) to retrieve")
|
|
429
527
|
});
|
|
430
|
-
async function handleSkillGet(args, manifest) {
|
|
528
|
+
async function handleSkillGet(args, manifest, charsPerToken) {
|
|
431
529
|
const { id } = args;
|
|
432
530
|
const skill = manifest.skills.find((s) => s.id === id);
|
|
433
531
|
if (!skill) {
|
|
434
532
|
notFound("Skill", id);
|
|
435
533
|
}
|
|
436
534
|
const content = await readFullSkillContent(skill.path);
|
|
535
|
+
const loadedSkillEstimatedTokens = estimateTokensFromText(
|
|
536
|
+
content,
|
|
537
|
+
charsPerToken
|
|
538
|
+
);
|
|
539
|
+
const metrics = buildSkillToolMetrics({
|
|
540
|
+
charsPerToken,
|
|
541
|
+
fullCatalogEstimatedTokens: manifest.fullCatalogEstimatedTokens,
|
|
542
|
+
responseEstimatedTokens: loadedSkillEstimatedTokens,
|
|
543
|
+
loadedSkillEstimatedTokens
|
|
544
|
+
});
|
|
437
545
|
return {
|
|
438
546
|
content: [
|
|
439
547
|
{
|
|
440
548
|
type: "text",
|
|
441
549
|
text: content
|
|
442
550
|
}
|
|
443
|
-
]
|
|
551
|
+
],
|
|
552
|
+
structuredContent: {
|
|
553
|
+
metrics
|
|
554
|
+
}
|
|
444
555
|
};
|
|
445
556
|
}
|
|
446
557
|
|
|
447
|
-
// src/tools/
|
|
558
|
+
// src/tools/skillBudgetReport.ts
|
|
448
559
|
import { z as z6 } from "zod";
|
|
560
|
+
var skillBudgetReportName = "skill_budget_report";
|
|
561
|
+
var skillBudgetReportDescription = "Report estimated context/token budget for selected and loaded skills compared to the full skill catalog.";
|
|
562
|
+
var skillBudgetReportSchema = z6.object({
|
|
563
|
+
selectedSkillIds: z6.array(z6.string()).default([]).describe("Skill IDs selected after search/browse."),
|
|
564
|
+
loadedSkillIds: z6.array(z6.string()).default([]).describe("Skill IDs loaded via skill_get.")
|
|
565
|
+
});
|
|
566
|
+
function uniqueStrings(values) {
|
|
567
|
+
return [...new Set(values.map((value) => String(value)))];
|
|
568
|
+
}
|
|
569
|
+
function handleSkillBudgetReport(args, manifest, charsPerToken) {
|
|
570
|
+
const selectedSkillIds = uniqueStrings(args.selectedSkillIds ?? []);
|
|
571
|
+
const loadedSkillIds = uniqueStrings(args.loadedSkillIds ?? []);
|
|
572
|
+
const skillById = new Map(manifest.skills.map((skill) => [skill.id, skill]));
|
|
573
|
+
const selectedSkills = selectedSkillIds.map((id) => {
|
|
574
|
+
const skill = skillById.get(id);
|
|
575
|
+
if (!skill) return null;
|
|
576
|
+
return {
|
|
577
|
+
id: skill.id,
|
|
578
|
+
category: skill.category,
|
|
579
|
+
estimatedTokens: estimateTokensFromBytes(skill.fileBytes, charsPerToken)
|
|
580
|
+
};
|
|
581
|
+
}).filter((item) => Boolean(item));
|
|
582
|
+
const loadedSkills = loadedSkillIds.map((id) => {
|
|
583
|
+
const skill = skillById.get(id);
|
|
584
|
+
if (!skill) return null;
|
|
585
|
+
return {
|
|
586
|
+
id: skill.id,
|
|
587
|
+
category: skill.category,
|
|
588
|
+
estimatedTokens: estimateTokensFromBytes(skill.fileBytes, charsPerToken)
|
|
589
|
+
};
|
|
590
|
+
}).filter((item) => Boolean(item));
|
|
591
|
+
const unknownSelectedSkillIds = selectedSkillIds.filter(
|
|
592
|
+
(id) => !skillById.has(id)
|
|
593
|
+
);
|
|
594
|
+
const unknownLoadedSkillIds = loadedSkillIds.filter((id) => !skillById.has(id));
|
|
595
|
+
const selectedSkillsEstimatedTokens = selectedSkills.reduce(
|
|
596
|
+
(sum, skill) => sum + skill.estimatedTokens,
|
|
597
|
+
0
|
|
598
|
+
);
|
|
599
|
+
const loadedSkillsEstimatedTokens = loadedSkills.reduce(
|
|
600
|
+
(sum, skill) => sum + skill.estimatedTokens,
|
|
601
|
+
0
|
|
602
|
+
);
|
|
603
|
+
const usedEstimatedTokens = loadedSkills.length > 0 ? loadedSkillsEstimatedTokens : selectedSkillsEstimatedTokens;
|
|
604
|
+
const savings = estimateSavings(
|
|
605
|
+
manifest.fullCatalogEstimatedTokens,
|
|
606
|
+
usedEstimatedTokens
|
|
607
|
+
);
|
|
608
|
+
const selectedIdSet = new Set(selectedSkills.map((skill) => skill.id));
|
|
609
|
+
const loadedIdSet = new Set(loadedSkills.map((skill) => skill.id));
|
|
610
|
+
const skippedSkills = manifest.skills.filter((skill) => !selectedIdSet.has(skill.id) && !loadedIdSet.has(skill.id)).map((skill) => skill.id).sort((a, b) => a.localeCompare(b));
|
|
611
|
+
const payload = {
|
|
612
|
+
skillLog: {
|
|
613
|
+
selectedSkills,
|
|
614
|
+
loadedSkills,
|
|
615
|
+
skippedSkills,
|
|
616
|
+
unknownSelectedSkillIds,
|
|
617
|
+
unknownLoadedSkillIds
|
|
618
|
+
},
|
|
619
|
+
contextBudget: {
|
|
620
|
+
estimatorVersion: TOKEN_ESTIMATOR_VERSION,
|
|
621
|
+
charsPerToken,
|
|
622
|
+
fullCatalogEstimatedTokens: manifest.fullCatalogEstimatedTokens,
|
|
623
|
+
selectedSkillsEstimatedTokens,
|
|
624
|
+
loadedSkillsEstimatedTokens,
|
|
625
|
+
estimatedSavingsTokens: savings.estimatedSavingsTokens,
|
|
626
|
+
estimatedSavingsPercent: savings.estimatedSavingsPercent,
|
|
627
|
+
estimated: true
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
return {
|
|
631
|
+
content: [
|
|
632
|
+
{
|
|
633
|
+
type: "text",
|
|
634
|
+
text: JSON.stringify(payload, null, 2)
|
|
635
|
+
}
|
|
636
|
+
],
|
|
637
|
+
structuredContent: payload
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/tools/postmanGetMode.ts
|
|
642
|
+
import { z as z7 } from "zod";
|
|
449
643
|
|
|
450
644
|
// src/cbxConfig/paths.ts
|
|
451
645
|
import path3 from "path";
|
|
@@ -705,8 +899,8 @@ function isValidMode(mode) {
|
|
|
705
899
|
// src/tools/postmanGetMode.ts
|
|
706
900
|
var postmanGetModeName = "postman_get_mode";
|
|
707
901
|
var postmanGetModeDescription = "Get the current Postman MCP mode from cbx_config.json. Returns the friendly mode name and URL.";
|
|
708
|
-
var postmanGetModeSchema =
|
|
709
|
-
scope:
|
|
902
|
+
var postmanGetModeSchema = z7.object({
|
|
903
|
+
scope: z7.enum(["global", "project", "auto"]).optional().describe(
|
|
710
904
|
"Config scope to read. Default: auto (project if exists, else global)"
|
|
711
905
|
)
|
|
712
906
|
});
|
|
@@ -761,12 +955,12 @@ function handlePostmanGetMode(args) {
|
|
|
761
955
|
}
|
|
762
956
|
|
|
763
957
|
// src/tools/postmanSetMode.ts
|
|
764
|
-
import { z as
|
|
958
|
+
import { z as z8 } from "zod";
|
|
765
959
|
var postmanSetModeName = "postman_set_mode";
|
|
766
960
|
var postmanSetModeDescription = "Set the Postman MCP mode in cbx_config.json. Modes: minimal, code, full.";
|
|
767
|
-
var postmanSetModeSchema =
|
|
768
|
-
mode:
|
|
769
|
-
scope:
|
|
961
|
+
var postmanSetModeSchema = z8.object({
|
|
962
|
+
mode: z8.enum(["minimal", "code", "full"]).describe("Postman MCP mode to set: minimal, code, or full"),
|
|
963
|
+
scope: z8.enum(["global", "project", "auto"]).optional().describe(
|
|
770
964
|
"Config scope to write. Default: auto (project if exists, else global)"
|
|
771
965
|
)
|
|
772
966
|
});
|
|
@@ -805,11 +999,11 @@ function handlePostmanSetMode(args) {
|
|
|
805
999
|
}
|
|
806
1000
|
|
|
807
1001
|
// src/tools/postmanGetStatus.ts
|
|
808
|
-
import { z as
|
|
1002
|
+
import { z as z9 } from "zod";
|
|
809
1003
|
var postmanGetStatusName = "postman_get_status";
|
|
810
1004
|
var postmanGetStatusDescription = "Get full Postman configuration status including mode, URL, and workspace ID.";
|
|
811
|
-
var postmanGetStatusSchema =
|
|
812
|
-
scope:
|
|
1005
|
+
var postmanGetStatusSchema = z9.object({
|
|
1006
|
+
scope: z9.enum(["global", "project", "auto"]).optional().describe(
|
|
813
1007
|
"Config scope to read. Default: auto (project if exists, else global)"
|
|
814
1008
|
)
|
|
815
1009
|
});
|
|
@@ -849,11 +1043,11 @@ function handlePostmanGetStatus(args) {
|
|
|
849
1043
|
}
|
|
850
1044
|
|
|
851
1045
|
// src/tools/stitchGetMode.ts
|
|
852
|
-
import { z as
|
|
1046
|
+
import { z as z10 } from "zod";
|
|
853
1047
|
var stitchGetModeName = "stitch_get_mode";
|
|
854
1048
|
var stitchGetModeDescription = "Get the active Stitch profile name and URL from cbx_config.json. Never exposes API keys.";
|
|
855
|
-
var stitchGetModeSchema =
|
|
856
|
-
scope:
|
|
1049
|
+
var stitchGetModeSchema = z10.object({
|
|
1050
|
+
scope: z10.enum(["global", "project", "auto"]).optional().describe(
|
|
857
1051
|
"Config scope to read. Default: auto (project if exists, else global)"
|
|
858
1052
|
)
|
|
859
1053
|
});
|
|
@@ -888,12 +1082,12 @@ function handleStitchGetMode(args) {
|
|
|
888
1082
|
}
|
|
889
1083
|
|
|
890
1084
|
// src/tools/stitchSetProfile.ts
|
|
891
|
-
import { z as
|
|
1085
|
+
import { z as z11 } from "zod";
|
|
892
1086
|
var stitchSetProfileName = "stitch_set_profile";
|
|
893
1087
|
var stitchSetProfileDescription = "Set the active Stitch profile in cbx_config.json. The profile must already exist in the config.";
|
|
894
|
-
var stitchSetProfileSchema =
|
|
895
|
-
profileName:
|
|
896
|
-
scope:
|
|
1088
|
+
var stitchSetProfileSchema = z11.object({
|
|
1089
|
+
profileName: z11.string().min(1).describe("Name of the Stitch profile to activate"),
|
|
1090
|
+
scope: z11.enum(["global", "project", "auto"]).optional().describe(
|
|
897
1091
|
"Config scope to write. Default: auto (project if exists, else global)"
|
|
898
1092
|
)
|
|
899
1093
|
});
|
|
@@ -938,11 +1132,11 @@ function handleStitchSetProfile(args) {
|
|
|
938
1132
|
}
|
|
939
1133
|
|
|
940
1134
|
// src/tools/stitchGetStatus.ts
|
|
941
|
-
import { z as
|
|
1135
|
+
import { z as z12 } from "zod";
|
|
942
1136
|
var stitchGetStatusName = "stitch_get_status";
|
|
943
1137
|
var stitchGetStatusDescription = "Get full Stitch configuration status including active profile, all profile names, and URLs. Never exposes API keys.";
|
|
944
|
-
var stitchGetStatusSchema =
|
|
945
|
-
scope:
|
|
1138
|
+
var stitchGetStatusSchema = z12.object({
|
|
1139
|
+
scope: z12.enum(["global", "project", "auto"]).optional().describe(
|
|
946
1140
|
"Config scope to read. Default: auto (project if exists, else global)"
|
|
947
1141
|
)
|
|
948
1142
|
});
|
|
@@ -1219,75 +1413,102 @@ function toolCallErrorResult({
|
|
|
1219
1413
|
}
|
|
1220
1414
|
async function createServer({
|
|
1221
1415
|
config,
|
|
1222
|
-
manifest
|
|
1416
|
+
manifest,
|
|
1417
|
+
defaultConfigScope = "auto"
|
|
1223
1418
|
}) {
|
|
1224
1419
|
const server = new McpServer({
|
|
1225
1420
|
name: config.server.name,
|
|
1226
1421
|
version: config.server.version
|
|
1227
1422
|
});
|
|
1228
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
|
+
};
|
|
1431
|
+
};
|
|
1229
1432
|
server.tool(
|
|
1230
1433
|
skillListCategoriesName,
|
|
1231
1434
|
skillListCategoriesDescription,
|
|
1232
1435
|
skillListCategoriesSchema.shape,
|
|
1233
|
-
async () => handleSkillListCategories(manifest)
|
|
1436
|
+
async () => handleSkillListCategories(manifest, charsPerToken)
|
|
1234
1437
|
);
|
|
1235
1438
|
server.tool(
|
|
1236
1439
|
skillBrowseCategoryName,
|
|
1237
1440
|
skillBrowseCategoryDescription,
|
|
1238
1441
|
skillBrowseCategorySchema.shape,
|
|
1239
|
-
async (args) => handleSkillBrowseCategory(args, manifest, maxLen)
|
|
1442
|
+
async (args) => handleSkillBrowseCategory(args, manifest, maxLen, charsPerToken)
|
|
1240
1443
|
);
|
|
1241
1444
|
server.tool(
|
|
1242
1445
|
skillSearchName,
|
|
1243
1446
|
skillSearchDescription,
|
|
1244
1447
|
skillSearchSchema.shape,
|
|
1245
|
-
async (args) => handleSkillSearch(args, manifest, maxLen)
|
|
1448
|
+
async (args) => handleSkillSearch(args, manifest, maxLen, charsPerToken)
|
|
1246
1449
|
);
|
|
1247
1450
|
server.tool(
|
|
1248
1451
|
skillGetName,
|
|
1249
1452
|
skillGetDescription,
|
|
1250
1453
|
skillGetSchema.shape,
|
|
1251
|
-
async (args) => handleSkillGet(args, manifest)
|
|
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)
|
|
1252
1461
|
);
|
|
1253
1462
|
server.tool(
|
|
1254
1463
|
postmanGetModeName,
|
|
1255
1464
|
postmanGetModeDescription,
|
|
1256
1465
|
postmanGetModeSchema.shape,
|
|
1257
|
-
async (args) => handlePostmanGetMode(
|
|
1466
|
+
async (args) => handlePostmanGetMode(
|
|
1467
|
+
withDefaultScope(args)
|
|
1468
|
+
)
|
|
1258
1469
|
);
|
|
1259
1470
|
server.tool(
|
|
1260
1471
|
postmanSetModeName,
|
|
1261
1472
|
postmanSetModeDescription,
|
|
1262
1473
|
postmanSetModeSchema.shape,
|
|
1263
|
-
async (args) => handlePostmanSetMode(
|
|
1474
|
+
async (args) => handlePostmanSetMode(
|
|
1475
|
+
withDefaultScope(args)
|
|
1476
|
+
)
|
|
1264
1477
|
);
|
|
1265
1478
|
server.tool(
|
|
1266
1479
|
postmanGetStatusName,
|
|
1267
1480
|
postmanGetStatusDescription,
|
|
1268
1481
|
postmanGetStatusSchema.shape,
|
|
1269
|
-
async (args) => handlePostmanGetStatus(
|
|
1482
|
+
async (args) => handlePostmanGetStatus(
|
|
1483
|
+
withDefaultScope(args)
|
|
1484
|
+
)
|
|
1270
1485
|
);
|
|
1271
1486
|
server.tool(
|
|
1272
1487
|
stitchGetModeName,
|
|
1273
1488
|
stitchGetModeDescription,
|
|
1274
1489
|
stitchGetModeSchema.shape,
|
|
1275
|
-
async (args) => handleStitchGetMode(
|
|
1490
|
+
async (args) => handleStitchGetMode(
|
|
1491
|
+
withDefaultScope(args)
|
|
1492
|
+
)
|
|
1276
1493
|
);
|
|
1277
1494
|
server.tool(
|
|
1278
1495
|
stitchSetProfileName,
|
|
1279
1496
|
stitchSetProfileDescription,
|
|
1280
1497
|
stitchSetProfileSchema.shape,
|
|
1281
|
-
async (args) => handleStitchSetProfile(
|
|
1498
|
+
async (args) => handleStitchSetProfile(
|
|
1499
|
+
withDefaultScope(args)
|
|
1500
|
+
)
|
|
1282
1501
|
);
|
|
1283
1502
|
server.tool(
|
|
1284
1503
|
stitchGetStatusName,
|
|
1285
1504
|
stitchGetStatusDescription,
|
|
1286
1505
|
stitchGetStatusSchema.shape,
|
|
1287
|
-
async (args) => handleStitchGetStatus(
|
|
1506
|
+
async (args) => handleStitchGetStatus(
|
|
1507
|
+
withDefaultScope(args)
|
|
1508
|
+
)
|
|
1288
1509
|
);
|
|
1289
1510
|
const upstreamCatalogs = await discoverUpstreamCatalogs();
|
|
1290
|
-
const dynamicArgsShape =
|
|
1511
|
+
const dynamicArgsShape = z13.object({}).passthrough().shape;
|
|
1291
1512
|
for (const catalog of [upstreamCatalogs.postman, upstreamCatalogs.stitch]) {
|
|
1292
1513
|
for (const tool of catalog.tools) {
|
|
1293
1514
|
const namespaced = tool.namespacedName;
|
|
@@ -1357,8 +1578,11 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
1357
1578
|
var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1358
1579
|
function parseArgs(argv) {
|
|
1359
1580
|
let transport = "stdio";
|
|
1581
|
+
let scope = "auto";
|
|
1360
1582
|
let scanOnly = false;
|
|
1361
1583
|
let debug = false;
|
|
1584
|
+
let port;
|
|
1585
|
+
let host;
|
|
1362
1586
|
let configPath;
|
|
1363
1587
|
for (let i = 2; i < argv.length; i++) {
|
|
1364
1588
|
const arg = argv[i];
|
|
@@ -1372,6 +1596,23 @@ function parseArgs(argv) {
|
|
|
1372
1596
|
logger.error(`Unknown transport: ${val}. Use "stdio" or "http".`);
|
|
1373
1597
|
process.exit(1);
|
|
1374
1598
|
}
|
|
1599
|
+
} else if (arg === "--scope" && argv[i + 1]) {
|
|
1600
|
+
const val = argv[++i];
|
|
1601
|
+
if (val === "auto" || val === "global" || val === "project") {
|
|
1602
|
+
scope = val;
|
|
1603
|
+
} else {
|
|
1604
|
+
logger.error(`Unknown scope: ${val}. Use "auto", "global", or "project".`);
|
|
1605
|
+
process.exit(1);
|
|
1606
|
+
}
|
|
1607
|
+
} else if (arg === "--port" && argv[i + 1]) {
|
|
1608
|
+
const val = Number.parseInt(argv[++i], 10);
|
|
1609
|
+
if (!Number.isInteger(val) || val <= 0 || val > 65535) {
|
|
1610
|
+
logger.error(`Invalid port: ${argv[i]}. Use an integer from 1 to 65535.`);
|
|
1611
|
+
process.exit(1);
|
|
1612
|
+
}
|
|
1613
|
+
port = val;
|
|
1614
|
+
} else if (arg === "--host" && argv[i + 1]) {
|
|
1615
|
+
host = argv[++i];
|
|
1375
1616
|
} else if (arg === "--scan-only") {
|
|
1376
1617
|
scanOnly = true;
|
|
1377
1618
|
} else if (arg === "--debug") {
|
|
@@ -1380,7 +1621,7 @@ function parseArgs(argv) {
|
|
|
1380
1621
|
configPath = argv[++i];
|
|
1381
1622
|
}
|
|
1382
1623
|
}
|
|
1383
|
-
return { transport, scanOnly, debug, configPath };
|
|
1624
|
+
return { transport, scope, scanOnly, debug, port, host, configPath };
|
|
1384
1625
|
}
|
|
1385
1626
|
function printStartupBanner(skillCount, categoryCount, transportName) {
|
|
1386
1627
|
logger.raw("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
@@ -1392,9 +1633,9 @@ function printStartupBanner(skillCount, categoryCount, transportName) {
|
|
|
1392
1633
|
logger.raw(`\u2502 Transport: ${transportName.padEnd(33)}\u2502`);
|
|
1393
1634
|
logger.raw("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1394
1635
|
}
|
|
1395
|
-
function printConfigStatus() {
|
|
1636
|
+
function printConfigStatus(scope) {
|
|
1396
1637
|
try {
|
|
1397
|
-
const effective = readEffectiveConfig(
|
|
1638
|
+
const effective = readEffectiveConfig(scope);
|
|
1398
1639
|
if (!effective) {
|
|
1399
1640
|
logger.warn(
|
|
1400
1641
|
"cbx_config.json not found. Postman/Stitch tools will return config-not-found errors."
|
|
@@ -1431,7 +1672,8 @@ async function main() {
|
|
|
1431
1672
|
const serverConfig = loadServerConfig(args.configPath);
|
|
1432
1673
|
const basePath = path6.resolve(__dirname2, "..");
|
|
1433
1674
|
const skills = await scanVaultRoots(serverConfig.vault.roots, basePath);
|
|
1434
|
-
const
|
|
1675
|
+
const charsPerToken = serverConfig.telemetry.charsPerToken;
|
|
1676
|
+
const manifest = buildManifest(skills, charsPerToken);
|
|
1435
1677
|
await enrichWithDescriptions(
|
|
1436
1678
|
manifest.skills,
|
|
1437
1679
|
serverConfig.vault.summaryMaxLength
|
|
@@ -1446,18 +1688,23 @@ async function main() {
|
|
|
1446
1688
|
}
|
|
1447
1689
|
process.exit(0);
|
|
1448
1690
|
}
|
|
1449
|
-
const
|
|
1691
|
+
const resolvedHttpPort = args.port ?? serverConfig.transport.http?.port ?? 3100;
|
|
1692
|
+
const transportName = args.transport === "http" ? `Streamable HTTP :${resolvedHttpPort}` : "stdio";
|
|
1450
1693
|
printStartupBanner(
|
|
1451
1694
|
manifest.skills.length,
|
|
1452
1695
|
manifest.categories.length,
|
|
1453
1696
|
transportName
|
|
1454
1697
|
);
|
|
1455
|
-
printConfigStatus();
|
|
1456
|
-
const mcpServer = await createServer({
|
|
1698
|
+
printConfigStatus(args.scope);
|
|
1699
|
+
const mcpServer = await createServer({
|
|
1700
|
+
config: serverConfig,
|
|
1701
|
+
manifest,
|
|
1702
|
+
defaultConfigScope: args.scope
|
|
1703
|
+
});
|
|
1457
1704
|
if (args.transport === "http") {
|
|
1458
1705
|
const httpOpts = {
|
|
1459
|
-
port:
|
|
1460
|
-
host: serverConfig.transport.http?.host ?? "127.0.0.1"
|
|
1706
|
+
port: resolvedHttpPort,
|
|
1707
|
+
host: args.host ?? serverConfig.transport.http?.host ?? "127.0.0.1"
|
|
1461
1708
|
};
|
|
1462
1709
|
const { transport, httpServer } = createStreamableHttpTransport(httpOpts);
|
|
1463
1710
|
await mcpServer.connect(transport);
|
|
@@ -40,6 +40,7 @@ describe("config loading", () => {
|
|
|
40
40
|
const config = loadServerConfig(configPath);
|
|
41
41
|
expect(config.server.name).toBe("cubis-foundry-mcp");
|
|
42
42
|
expect(config.vault.summaryMaxLength).toBe(200);
|
|
43
|
+
expect(config.telemetry.charsPerToken).toBe(4);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
it("throws for a missing config file", () => {
|
package/mcp/src/config/schema.ts
CHANGED
|
@@ -16,6 +16,11 @@ export const ServerConfigSchema = z.object({
|
|
|
16
16
|
roots: z.array(z.string()).min(1),
|
|
17
17
|
summaryMaxLength: z.number().int().positive().default(200),
|
|
18
18
|
}),
|
|
19
|
+
telemetry: z
|
|
20
|
+
.object({
|
|
21
|
+
charsPerToken: z.number().positive().default(4),
|
|
22
|
+
})
|
|
23
|
+
.default({ charsPerToken: 4 }),
|
|
19
24
|
transport: z.object({
|
|
20
25
|
default: z.enum(["stdio", "streamable-http"]).default("stdio"),
|
|
21
26
|
http: z
|