mcp-squared 0.3.4 → 0.6.0
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 +75 -14
- package/dist/index.js +1419 -383
- package/dist/tui/config.js +385 -25
- package/package.json +10 -4
package/dist/index.js
CHANGED
|
@@ -124,6 +124,274 @@ function ensureConfigDir(configPath) {
|
|
|
124
124
|
var SOCKET_FILENAME = "mcp-squared.sock", INSTANCE_DIR_NAME = "instances", SOCKET_DIR_NAME = "sockets", DAEMON_DIR_NAME = "daemon", DAEMON_REGISTRY_FILENAME = "daemon.json", DAEMON_SOCKET_FILENAME = "daemon.sock", CONFIG_FILENAME = "mcp-squared.toml", CONFIG_DIR_NAME = ".mcp-squared", APP_NAME = "mcp-squared";
|
|
125
125
|
var init_paths = () => {};
|
|
126
126
|
|
|
127
|
+
// src/capabilities/inference.ts
|
|
128
|
+
function createEmptyScores() {
|
|
129
|
+
return CAPABILITY_IDS.reduce((acc, capability) => {
|
|
130
|
+
acc[capability] = 0;
|
|
131
|
+
return acc;
|
|
132
|
+
}, {});
|
|
133
|
+
}
|
|
134
|
+
function extractSchemaSignal(schema) {
|
|
135
|
+
if (!schema || schema.type !== "object") {
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
138
|
+
const keys = Object.keys(schema.properties ?? {});
|
|
139
|
+
const required = schema.required ?? [];
|
|
140
|
+
return `${keys.join(" ")} ${required.join(" ")}`;
|
|
141
|
+
}
|
|
142
|
+
function scoreTextSignals(scores, text) {
|
|
143
|
+
for (const capability of CAPABILITY_IDS) {
|
|
144
|
+
for (const pattern of CAPABILITY_PATTERNS[capability]) {
|
|
145
|
+
if (pattern.test(text)) {
|
|
146
|
+
scores[capability] += 4;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function getHighestScoringCapability(scores) {
|
|
152
|
+
const bestScore = Math.max(...Object.values(scores));
|
|
153
|
+
if (bestScore <= 0) {
|
|
154
|
+
return "general";
|
|
155
|
+
}
|
|
156
|
+
for (const capability of CAPABILITY_PRIORITY) {
|
|
157
|
+
if (scores[capability] === bestScore) {
|
|
158
|
+
return capability;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return "general";
|
|
162
|
+
}
|
|
163
|
+
function inferNamespaceCapability(namespace, tools, capabilityOverrides = {}) {
|
|
164
|
+
const override = capabilityOverrides[namespace];
|
|
165
|
+
if (override) {
|
|
166
|
+
return override;
|
|
167
|
+
}
|
|
168
|
+
const scores = createEmptyScores();
|
|
169
|
+
const namespaceText = namespace.toLowerCase();
|
|
170
|
+
for (const hint of NAMESPACE_HINTS) {
|
|
171
|
+
if (hint.pattern.test(namespaceText)) {
|
|
172
|
+
scores[hint.capability] += hint.score;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const tool of tools) {
|
|
176
|
+
const signal = [
|
|
177
|
+
tool.name,
|
|
178
|
+
tool.description ?? "",
|
|
179
|
+
extractSchemaSignal(tool.inputSchema)
|
|
180
|
+
].join(" ").toLowerCase();
|
|
181
|
+
scoreTextSignals(scores, signal);
|
|
182
|
+
}
|
|
183
|
+
return getHighestScoringCapability(scores);
|
|
184
|
+
}
|
|
185
|
+
function groupNamespacesByCapability(inventories, capabilityOverrides = {}) {
|
|
186
|
+
const grouped = CAPABILITY_IDS.reduce((acc, capability) => {
|
|
187
|
+
acc[capability] = [];
|
|
188
|
+
return acc;
|
|
189
|
+
}, {});
|
|
190
|
+
const byNamespace = {};
|
|
191
|
+
const sorted = [...inventories].sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
192
|
+
for (const inventory of sorted) {
|
|
193
|
+
const capability = inferNamespaceCapability(inventory.namespace, inventory.tools, capabilityOverrides);
|
|
194
|
+
byNamespace[inventory.namespace] = capability;
|
|
195
|
+
grouped[capability].push(inventory.namespace);
|
|
196
|
+
}
|
|
197
|
+
return { byNamespace, grouped };
|
|
198
|
+
}
|
|
199
|
+
var CAPABILITY_IDS, CAPABILITY_PRIORITY, NAMESPACE_HINTS, CAPABILITY_PATTERNS;
|
|
200
|
+
var init_inference = __esm(() => {
|
|
201
|
+
CAPABILITY_IDS = [
|
|
202
|
+
"code_search",
|
|
203
|
+
"docs",
|
|
204
|
+
"browser_automation",
|
|
205
|
+
"issue_tracking",
|
|
206
|
+
"cms_content",
|
|
207
|
+
"design",
|
|
208
|
+
"ai_media_generation",
|
|
209
|
+
"hosting_deploy",
|
|
210
|
+
"time_util",
|
|
211
|
+
"research",
|
|
212
|
+
"general"
|
|
213
|
+
];
|
|
214
|
+
CAPABILITY_PRIORITY = [
|
|
215
|
+
"code_search",
|
|
216
|
+
"docs",
|
|
217
|
+
"browser_automation",
|
|
218
|
+
"issue_tracking",
|
|
219
|
+
"cms_content",
|
|
220
|
+
"design",
|
|
221
|
+
"ai_media_generation",
|
|
222
|
+
"hosting_deploy",
|
|
223
|
+
"time_util",
|
|
224
|
+
"research",
|
|
225
|
+
"general"
|
|
226
|
+
];
|
|
227
|
+
NAMESPACE_HINTS = [
|
|
228
|
+
{
|
|
229
|
+
capability: "code_search",
|
|
230
|
+
pattern: /(auggie|augment|ctxdb|code|source|repo|symbol|search)/i,
|
|
231
|
+
score: 24
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
capability: "docs",
|
|
235
|
+
pattern: /(context7|ref|docs?|documentation|shadcn)/i,
|
|
236
|
+
score: 18
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
capability: "browser_automation",
|
|
240
|
+
pattern: /(chrome|devtools|browser|playwright|puppeteer|webdriver)/i,
|
|
241
|
+
score: 24
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
capability: "issue_tracking",
|
|
245
|
+
pattern: /(linear|jira|issue|ticket|project|milestone)/i,
|
|
246
|
+
score: 20
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
capability: "cms_content",
|
|
250
|
+
pattern: /(sanity|content|cms|dataset|schema|studio)/i,
|
|
251
|
+
score: 20
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
capability: "design",
|
|
255
|
+
pattern: /(pencil|figma|ui|design|artifact|visual)/i,
|
|
256
|
+
score: 20
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
capability: "hosting_deploy",
|
|
260
|
+
pattern: /(vercel|host|hosting|domain|dns|vps|deploy|infra)/i,
|
|
261
|
+
score: 22
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
capability: "time_util",
|
|
265
|
+
pattern: /(time|timezone|clock|date|utc)/i,
|
|
266
|
+
score: 22
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
capability: "ai_media_generation",
|
|
270
|
+
pattern: /(wavespeed|stability|replicate|midjourney|dall.?e|runway|imagen|flux|fal\.ai|dreamstudio)/i,
|
|
271
|
+
score: 24
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
capability: "research",
|
|
275
|
+
pattern: /(exa|perplexity|firecrawl|crawl|scrape|research|search)/i,
|
|
276
|
+
score: 16
|
|
277
|
+
}
|
|
278
|
+
];
|
|
279
|
+
CAPABILITY_PATTERNS = {
|
|
280
|
+
code_search: [
|
|
281
|
+
/\bcodebase\b/i,
|
|
282
|
+
/\bsource\b/i,
|
|
283
|
+
/\brepo(?:sitory)?\b/i,
|
|
284
|
+
/\bsymbol(?:s)?\b/i,
|
|
285
|
+
/\bdefinition(?:s)?\b/i,
|
|
286
|
+
/\breference(?:s)?\b/i,
|
|
287
|
+
/\busage(?:s)?\b/i,
|
|
288
|
+
/\bsearch_context\b/i,
|
|
289
|
+
/\bcodebase-retrieval\b/i,
|
|
290
|
+
/\bdirectory_path\b/i
|
|
291
|
+
],
|
|
292
|
+
docs: [
|
|
293
|
+
/\bdoc(?:s|umentation)?\b/i,
|
|
294
|
+
/\bread_docs\b/i,
|
|
295
|
+
/\bquery-docs\b/i,
|
|
296
|
+
/\breference\b/i,
|
|
297
|
+
/\bmanual\b/i,
|
|
298
|
+
/\bknowledge\b/i,
|
|
299
|
+
/\bregist(?:ry|ries)\b/i,
|
|
300
|
+
/\bcomponent(?:s)?\b/i,
|
|
301
|
+
/\bexample(?:s)?\b/i
|
|
302
|
+
],
|
|
303
|
+
browser_automation: [
|
|
304
|
+
/\bbrowser\b/i,
|
|
305
|
+
/\bdevtools\b/i,
|
|
306
|
+
/\bnavigate\b/i,
|
|
307
|
+
/\bclick\b/i,
|
|
308
|
+
/\bhover\b/i,
|
|
309
|
+
/\bnetwork\b/i,
|
|
310
|
+
/\bconsole\b/i,
|
|
311
|
+
/\bscreenshot\b/i,
|
|
312
|
+
/\bpage\b/i
|
|
313
|
+
],
|
|
314
|
+
issue_tracking: [
|
|
315
|
+
/\bissue(?:s)?\b/i,
|
|
316
|
+
/\bticket(?:s)?\b/i,
|
|
317
|
+
/\bmilestone(?:s)?\b/i,
|
|
318
|
+
/\bcycle(?:s)?\b/i,
|
|
319
|
+
/\bproject(?:s)?\b/i,
|
|
320
|
+
/\bcomment(?:s)?\b/i,
|
|
321
|
+
/\blinear\b/i
|
|
322
|
+
],
|
|
323
|
+
cms_content: [
|
|
324
|
+
/\bcms\b/i,
|
|
325
|
+
/\bcontent\b/i,
|
|
326
|
+
/\bdocument(?:s)?\b/i,
|
|
327
|
+
/\bdataset(?:s)?\b/i,
|
|
328
|
+
/\bschema\b/i,
|
|
329
|
+
/\bpublish\b/i,
|
|
330
|
+
/\bdraft(?:s)?\b/i,
|
|
331
|
+
/\bsanity\b/i,
|
|
332
|
+
/\bmigration\b/i
|
|
333
|
+
],
|
|
334
|
+
design: [
|
|
335
|
+
/\bdesign\b/i,
|
|
336
|
+
/\bui\b/i,
|
|
337
|
+
/\bartifact\b/i,
|
|
338
|
+
/\bstyle_guide\b/i,
|
|
339
|
+
/\bscreenshot\b/i,
|
|
340
|
+
/\bdiagram\b/i,
|
|
341
|
+
/\bimage\b/i,
|
|
342
|
+
/\blayout\b/i,
|
|
343
|
+
/\bframe\b/i
|
|
344
|
+
],
|
|
345
|
+
ai_media_generation: [
|
|
346
|
+
/\btext.to.image\b/i,
|
|
347
|
+
/\bimage.to.image\b/i,
|
|
348
|
+
/\btext.to.video\b/i,
|
|
349
|
+
/\binpaint(?:ing)?\b/i,
|
|
350
|
+
/\bupscal(?:e|ing)\b/i,
|
|
351
|
+
/\bgenerat(?:e|ion)\b.*\bimage/i,
|
|
352
|
+
/\bimage\b.*\bgenerat(?:e|ion)\b/i,
|
|
353
|
+
/\bai\b.*\b(?:image|photo|video)\b/i,
|
|
354
|
+
/\bstable.diffusion\b/i,
|
|
355
|
+
/\bdiffusion\b/i,
|
|
356
|
+
/\bprompt\b.*\b(?:image|visual)\b/i
|
|
357
|
+
],
|
|
358
|
+
hosting_deploy: [
|
|
359
|
+
/\bdeploy(?:ment)?\b/i,
|
|
360
|
+
/\bhosting\b/i,
|
|
361
|
+
/\bdomain(?:s)?\b/i,
|
|
362
|
+
/\bdns\b/i,
|
|
363
|
+
/\bvps\b/i,
|
|
364
|
+
/\bvirtual_machine(?:s)?\b/i,
|
|
365
|
+
/\bfirewall\b/i,
|
|
366
|
+
/\bwebsite\b/i,
|
|
367
|
+
/\bbilling\b/i,
|
|
368
|
+
/\bnameserver(?:s)?\b/i
|
|
369
|
+
],
|
|
370
|
+
time_util: [
|
|
371
|
+
/\btime\b/i,
|
|
372
|
+
/\btimezone\b/i,
|
|
373
|
+
/\butc\b/i,
|
|
374
|
+
/\bclock\b/i,
|
|
375
|
+
/\bdate\b/i,
|
|
376
|
+
/\bconvert_time\b/i,
|
|
377
|
+
/\bget_current_time\b/i
|
|
378
|
+
],
|
|
379
|
+
research: [
|
|
380
|
+
/\bresearch\b/i,
|
|
381
|
+
/\bcrawl\b/i,
|
|
382
|
+
/\bscrape\b/i,
|
|
383
|
+
/\bextract\b/i,
|
|
384
|
+
/\bsemantic_search\b/i,
|
|
385
|
+
/\bweb_search\b/i,
|
|
386
|
+
/\bask\b/i,
|
|
387
|
+
/\bperplexity\b/i,
|
|
388
|
+
/\bfirecrawl\b/i,
|
|
389
|
+
/\bexa\b/i
|
|
390
|
+
],
|
|
391
|
+
general: []
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
|
|
127
395
|
// src/embeddings/generator.ts
|
|
128
396
|
import {
|
|
129
397
|
env,
|
|
@@ -281,33 +549,147 @@ var init_embeddings = __esm(() => {
|
|
|
281
549
|
init_generator();
|
|
282
550
|
});
|
|
283
551
|
|
|
552
|
+
// src/capabilities/semantic-classifier.ts
|
|
553
|
+
var exports_semantic_classifier = {};
|
|
554
|
+
__export(exports_semantic_classifier, {
|
|
555
|
+
SemanticCapabilityClassifier: () => SemanticCapabilityClassifier
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
class SemanticCapabilityClassifier {
|
|
559
|
+
generator;
|
|
560
|
+
confidenceThreshold;
|
|
561
|
+
referenceEmbeddings = null;
|
|
562
|
+
constructor(generator, options) {
|
|
563
|
+
this.generator = generator;
|
|
564
|
+
this.confidenceThreshold = options?.confidenceThreshold ?? 0.45;
|
|
565
|
+
}
|
|
566
|
+
async initializeReferences() {
|
|
567
|
+
const texts = CAPABILITY_IDS.map((id) => CAPABILITY_REFERENCE_TEXTS[id]);
|
|
568
|
+
const result = await this.generator.embedBatch(texts, false);
|
|
569
|
+
this.referenceEmbeddings = new Map;
|
|
570
|
+
for (let i = 0;i < CAPABILITY_IDS.length; i++) {
|
|
571
|
+
this.referenceEmbeddings.set(CAPABILITY_IDS[i], result.embeddings[i]);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
isInitialized() {
|
|
575
|
+
return this.referenceEmbeddings !== null;
|
|
576
|
+
}
|
|
577
|
+
async classify(namespace, tools) {
|
|
578
|
+
if (!this.referenceEmbeddings) {
|
|
579
|
+
throw new Error("SemanticCapabilityClassifier not initialized. Call initializeReferences() first.");
|
|
580
|
+
}
|
|
581
|
+
const startTime = performance.now();
|
|
582
|
+
const signalText = this.buildSignalText(namespace, tools);
|
|
583
|
+
const signalResult = await this.generator.embed(signalText, true);
|
|
584
|
+
const scores = [];
|
|
585
|
+
for (const [capId, refEmb] of this.referenceEmbeddings) {
|
|
586
|
+
const similarity = EmbeddingGenerator.cosineSimilarity(signalResult.embedding, refEmb);
|
|
587
|
+
scores.push({ capability: capId, similarity });
|
|
588
|
+
}
|
|
589
|
+
scores.sort((a, b) => b.similarity - a.similarity);
|
|
590
|
+
const best = scores[0];
|
|
591
|
+
if (!best) {
|
|
592
|
+
throw new Error("No capability references available for classification");
|
|
593
|
+
}
|
|
594
|
+
const runnerUp = scores[1];
|
|
595
|
+
const inferenceMs = performance.now() - startTime;
|
|
596
|
+
return {
|
|
597
|
+
capability: best.capability,
|
|
598
|
+
confidence: best.similarity,
|
|
599
|
+
inferenceMs,
|
|
600
|
+
runnerUp: runnerUp ? { capability: runnerUp.capability, confidence: runnerUp.similarity } : undefined
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
async classifyBatch(inventories) {
|
|
604
|
+
const startTime = performance.now();
|
|
605
|
+
const overrides = {};
|
|
606
|
+
const classifications = [];
|
|
607
|
+
for (const { namespace, tools } of inventories) {
|
|
608
|
+
const result = await this.classify(namespace, tools);
|
|
609
|
+
classifications.push({ namespace, ...result });
|
|
610
|
+
if (result.confidence >= this.confidenceThreshold) {
|
|
611
|
+
overrides[namespace] = result.capability;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
overrides,
|
|
616
|
+
classifications,
|
|
617
|
+
inferenceMs: performance.now() - startTime
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
buildSignalText(namespace, tools) {
|
|
621
|
+
const parts = [namespace];
|
|
622
|
+
for (const tool of tools) {
|
|
623
|
+
parts.push(tool.name);
|
|
624
|
+
if (tool.description) {
|
|
625
|
+
parts.push(tool.description);
|
|
626
|
+
}
|
|
627
|
+
const schemaSignal = extractSchemaSignal(tool.inputSchema);
|
|
628
|
+
if (schemaSignal) {
|
|
629
|
+
parts.push(schemaSignal);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return parts.join(" ");
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
var CAPABILITY_REFERENCE_TEXTS;
|
|
636
|
+
var init_semantic_classifier = __esm(() => {
|
|
637
|
+
init_generator();
|
|
638
|
+
init_inference();
|
|
639
|
+
CAPABILITY_REFERENCE_TEXTS = {
|
|
640
|
+
code_search: "Search source code, symbols, function definitions, class references, and code context across repositories and codebases. Find implementations, usages, and navigate source files.",
|
|
641
|
+
docs: "Query technical documentation, API references, library guides, SDK docs, component registries, and code example lookups. Read documentation pages and fetch reference material.",
|
|
642
|
+
browser_automation: "Automate web browser interactions: click elements, fill forms, take screenshots, inspect DOM nodes, navigate URLs, execute JavaScript in page context, and run browser diagnostics.",
|
|
643
|
+
issue_tracking: "Manage project management tickets, kanban boards, sprints, and work items. Create, update, and track issues in project trackers like Jira, Linear, Asana, and ClickUp.",
|
|
644
|
+
cms_content: "Manage wiki pages, knowledge base articles, content documents, blog posts, editorial workflows, and structured content. Create and organize content in systems like Notion, Confluence, and Sanity.",
|
|
645
|
+
design: "Create and inspect visual design artifacts, UI mockups, wireframes, and design system components. Work with design tools like Figma, Sketch, and Pencil for visual layout and styling.",
|
|
646
|
+
ai_media_generation: "Generate and edit images, videos, and visual media using AI models. Create images from text prompts, edit existing images with AI, upscale resolution, apply style transfer, inpaint or outpaint regions, and generate sequential or consistent media. Supports text-to-image, image-to-image, and AI-powered visual content creation.",
|
|
647
|
+
hosting_deploy: "Manage server deployments, cloud infrastructure, hosting configurations, CI/CD pipelines, containers, databases, DNS records, and domain management.",
|
|
648
|
+
time_util: "Convert between timezones, get current time, format dates, calculate time differences, and resolve scheduling utilities.",
|
|
649
|
+
research: "Search the web, collect information from multiple sources, synthesize findings, and perform web research and data gathering operations.",
|
|
650
|
+
general: "General-purpose utility operations, API integrations, data transformations, notifications, messaging, payments, and miscellaneous tool actions."
|
|
651
|
+
};
|
|
652
|
+
});
|
|
653
|
+
|
|
284
654
|
// src/tui/monitor-loader.ts
|
|
285
655
|
var exports_monitor_loader = {};
|
|
286
656
|
__export(exports_monitor_loader, {
|
|
287
657
|
runMonitorTui: () => runMonitorTui
|
|
288
658
|
});
|
|
659
|
+
function getMonitorModuleSpecifier() {
|
|
660
|
+
const self = import.meta.url;
|
|
661
|
+
if (self.includes("/tui/")) {
|
|
662
|
+
return new URL("./monitor.js", self).href;
|
|
663
|
+
}
|
|
664
|
+
return "./tui/monitor.js";
|
|
665
|
+
}
|
|
289
666
|
async function loadMonitorModule() {
|
|
290
|
-
return import(
|
|
667
|
+
return import(getMonitorModuleSpecifier());
|
|
291
668
|
}
|
|
292
669
|
async function runMonitorTui(options = {}) {
|
|
293
670
|
const { runMonitorTui: runMonitor } = await loadMonitorModule();
|
|
294
671
|
return runMonitor(options);
|
|
295
672
|
}
|
|
296
|
-
var MONITOR_MODULE_SPECIFIER = "./tui/monitor.js";
|
|
297
673
|
|
|
298
674
|
// src/tui/config-loader.ts
|
|
299
675
|
var exports_config_loader = {};
|
|
300
676
|
__export(exports_config_loader, {
|
|
301
677
|
runConfigTui: () => runConfigTui
|
|
302
678
|
});
|
|
679
|
+
function getConfigModuleSpecifier() {
|
|
680
|
+
const self = import.meta.url;
|
|
681
|
+
if (self.includes("/tui/")) {
|
|
682
|
+
return new URL("./config.js", self).href;
|
|
683
|
+
}
|
|
684
|
+
return "./tui/config.js";
|
|
685
|
+
}
|
|
303
686
|
function loadConfigModule() {
|
|
304
|
-
return import(
|
|
687
|
+
return import(getConfigModuleSpecifier());
|
|
305
688
|
}
|
|
306
689
|
async function runConfigTui() {
|
|
307
690
|
const { runConfigTui: _run } = await loadConfigModule();
|
|
308
691
|
return _run();
|
|
309
692
|
}
|
|
310
|
-
var CONFIG_MODULE_SPECIFIER = "./tui/config.js";
|
|
311
693
|
|
|
312
694
|
// src/init/runner.ts
|
|
313
695
|
var exports_runner = {};
|
|
@@ -354,6 +736,20 @@ maxLimit = 50
|
|
|
354
736
|
defaultMode = "fast"
|
|
355
737
|
defaultDetailLevel = "L1"
|
|
356
738
|
|
|
739
|
+
[operations.findTools.preferredNamespacesByIntent]
|
|
740
|
+
# Default-on code-search namespace preference.
|
|
741
|
+
# Adjust or clear if your stack uses different code indexers.
|
|
742
|
+
codeSearch = ["auggie", "ctxdb"]
|
|
743
|
+
|
|
744
|
+
[operations.dynamicToolSurface]
|
|
745
|
+
# Capability-first tool surface is always enabled.
|
|
746
|
+
inference = "heuristic_with_overrides"
|
|
747
|
+
refresh = "on_connect"
|
|
748
|
+
|
|
749
|
+
[operations.dynamicToolSurface.capabilityOverrides]
|
|
750
|
+
# Optional namespace -> capability pinning.
|
|
751
|
+
# auggie = "code_search"
|
|
752
|
+
|
|
357
753
|
[operations.embeddings]
|
|
358
754
|
# Enable to use semantic or hybrid search modes.
|
|
359
755
|
# Requires onnxruntime shared library on the system.
|
|
@@ -455,6 +851,9 @@ function parseArgs(args) {
|
|
|
455
851
|
project: false,
|
|
456
852
|
force: false
|
|
457
853
|
},
|
|
854
|
+
migrate: {
|
|
855
|
+
dryRun: false
|
|
856
|
+
},
|
|
458
857
|
install: {
|
|
459
858
|
interactive: true,
|
|
460
859
|
dryRun: false,
|
|
@@ -511,9 +910,15 @@ function parseArgs(args) {
|
|
|
511
910
|
case "init":
|
|
512
911
|
result.mode = "init";
|
|
513
912
|
break;
|
|
913
|
+
case "migrate":
|
|
914
|
+
result.mode = "migrate";
|
|
915
|
+
break;
|
|
514
916
|
case "monitor":
|
|
515
917
|
result.mode = "monitor";
|
|
516
918
|
break;
|
|
919
|
+
case "status":
|
|
920
|
+
result.mode = "status";
|
|
921
|
+
break;
|
|
517
922
|
case "daemon":
|
|
518
923
|
result.mode = "daemon";
|
|
519
924
|
break;
|
|
@@ -561,6 +966,7 @@ function parseArgs(args) {
|
|
|
561
966
|
case "--dry-run":
|
|
562
967
|
result.import.dryRun = true;
|
|
563
968
|
result.install.dryRun = true;
|
|
969
|
+
result.migrate.dryRun = true;
|
|
564
970
|
break;
|
|
565
971
|
case "--no-interactive":
|
|
566
972
|
result.import.interactive = false;
|
|
@@ -685,7 +1091,9 @@ Usage:
|
|
|
685
1091
|
mcp-squared auth <upstream> Authenticate with an OAuth-protected upstream
|
|
686
1092
|
mcp-squared import [options] Import MCP configs from other tools
|
|
687
1093
|
mcp-squared init [options] Generate a starter config file with security profile
|
|
1094
|
+
mcp-squared migrate [options] Apply config migrations to existing config
|
|
688
1095
|
mcp-squared install [options] Install MCP\xB2 into other MCP clients
|
|
1096
|
+
mcp-squared status [options] Show upstream server status and capability routing
|
|
689
1097
|
mcp-squared monitor [options] Launch server monitor TUI
|
|
690
1098
|
mcp-squared daemon [options] Start shared MCP\xB2 daemon
|
|
691
1099
|
mcp-squared proxy [options] Start stdio proxy (connects to daemon)
|
|
@@ -698,7 +1106,9 @@ Commands:
|
|
|
698
1106
|
auth <name> Authenticate with an OAuth-protected upstream
|
|
699
1107
|
import Import MCP server configs from other tools
|
|
700
1108
|
init Generate a starter config with security profile
|
|
1109
|
+
migrate Apply one-time config migrations to existing config
|
|
701
1110
|
install Install MCP\xB2 as a server in other MCP clients
|
|
1111
|
+
status Show upstream status and capability routing
|
|
702
1112
|
monitor Launch server monitor TUI
|
|
703
1113
|
daemon Start shared daemon for multiple clients
|
|
704
1114
|
proxy Start stdio proxy for daemon
|
|
@@ -724,6 +1134,9 @@ Init Options:
|
|
|
724
1134
|
--project Write to project-local mcp-squared.toml (default: user-level)
|
|
725
1135
|
--force Overwrite existing config without prompting
|
|
726
1136
|
|
|
1137
|
+
Migrate Options:
|
|
1138
|
+
--dry-run Preview migration changes without writing
|
|
1139
|
+
|
|
727
1140
|
Install Options:
|
|
728
1141
|
--tool=<tool> Target tool (skip selection prompt)
|
|
729
1142
|
--scope=<scope> Scope: user or project
|
|
@@ -735,6 +1148,9 @@ Install Options:
|
|
|
735
1148
|
--dry-run Preview changes without writing
|
|
736
1149
|
--no-interactive Disable interactive prompts
|
|
737
1150
|
|
|
1151
|
+
Status Options:
|
|
1152
|
+
--verbose, -V Show schema parameters and extra detail
|
|
1153
|
+
|
|
738
1154
|
Monitor Options:
|
|
739
1155
|
(daemon-first; attaches to shared daemon monitor by default)
|
|
740
1156
|
--refresh-interval=<ms> Auto-refresh interval in milliseconds (default: 2000)
|
|
@@ -754,12 +1170,16 @@ Supported Tools:
|
|
|
754
1170
|
${VALID_TOOL_IDS.join(", ")}
|
|
755
1171
|
|
|
756
1172
|
Examples:
|
|
1173
|
+
mcp-squared status Show upstream status and capability routing table
|
|
1174
|
+
mcp-squared status --verbose Show status with schema parameters
|
|
757
1175
|
mcp-squared test github Test connection to 'github' upstream
|
|
758
1176
|
mcp-squared test Test all configured upstreams
|
|
759
1177
|
mcp-squared auth vercel-mcp Authenticate with 'vercel-mcp' upstream (OAuth)
|
|
760
1178
|
mcp-squared init Generate hardened config (confirm-all by default)
|
|
761
1179
|
mcp-squared init --security=permissive Generate permissive config (allow-all)
|
|
762
1180
|
mcp-squared init --project Generate project-local config
|
|
1181
|
+
mcp-squared migrate Apply config migrations to current config file
|
|
1182
|
+
mcp-squared migrate --dry-run Preview config migrations without writing
|
|
763
1183
|
mcp-squared import --list List all discovered MCP configs
|
|
764
1184
|
mcp-squared import --dry-run Preview import changes
|
|
765
1185
|
mcp-squared import Import with interactive conflict resolution
|
|
@@ -1020,6 +1440,7 @@ import { parse as parseToml } from "smol-toml";
|
|
|
1020
1440
|
import { ZodError } from "zod";
|
|
1021
1441
|
|
|
1022
1442
|
// src/config/schema.ts
|
|
1443
|
+
init_inference();
|
|
1023
1444
|
import { z } from "zod";
|
|
1024
1445
|
var LATEST_SCHEMA_VERSION = 1;
|
|
1025
1446
|
var LogLevelSchema = z.enum([
|
|
@@ -1076,11 +1497,29 @@ var SecuritySchema = z.object({
|
|
|
1076
1497
|
});
|
|
1077
1498
|
var SearchModeSchema = z.enum(["fast", "semantic", "hybrid"]);
|
|
1078
1499
|
var DetailLevelSchema = z.enum(["L0", "L1", "L2"]);
|
|
1500
|
+
var CapabilityIdSchema = z.enum(CAPABILITY_IDS);
|
|
1501
|
+
var DynamicToolSurfaceInferenceSchema = z.enum([
|
|
1502
|
+
"heuristic_with_overrides",
|
|
1503
|
+
"hybrid"
|
|
1504
|
+
]);
|
|
1505
|
+
var DynamicToolSurfaceRefreshSchema = z.enum(["on_connect"]);
|
|
1506
|
+
var DynamicToolSurfaceSchema = z.object({
|
|
1507
|
+
inference: DynamicToolSurfaceInferenceSchema.default("heuristic_with_overrides"),
|
|
1508
|
+
refresh: DynamicToolSurfaceRefreshSchema.default("on_connect"),
|
|
1509
|
+
capabilityOverrides: z.record(z.string().min(1), CapabilityIdSchema).default({}),
|
|
1510
|
+
semanticConfidenceThreshold: z.number().min(0).max(1).default(0.45)
|
|
1511
|
+
});
|
|
1512
|
+
var PreferredNamespacesByIntentSchema = z.object({
|
|
1513
|
+
codeSearch: z.array(z.string().min(1)).default([])
|
|
1514
|
+
});
|
|
1079
1515
|
var FindToolsSchema = z.object({
|
|
1080
1516
|
defaultLimit: z.number().int().min(1).default(5),
|
|
1081
1517
|
maxLimit: z.number().int().min(1).max(200).default(50),
|
|
1082
1518
|
defaultMode: SearchModeSchema.default("fast"),
|
|
1083
|
-
defaultDetailLevel: DetailLevelSchema.default("L1")
|
|
1519
|
+
defaultDetailLevel: DetailLevelSchema.default("L1"),
|
|
1520
|
+
preferredNamespacesByIntent: PreferredNamespacesByIntentSchema.default({
|
|
1521
|
+
codeSearch: []
|
|
1522
|
+
})
|
|
1084
1523
|
});
|
|
1085
1524
|
var IndexSchema = z.object({
|
|
1086
1525
|
refreshIntervalMs: z.number().int().min(1000).default(30000)
|
|
@@ -1101,7 +1540,8 @@ var OperationsSchema = z.object({
|
|
|
1101
1540
|
defaultLimit: 5,
|
|
1102
1541
|
maxLimit: 50,
|
|
1103
1542
|
defaultMode: "fast",
|
|
1104
|
-
defaultDetailLevel: "L1"
|
|
1543
|
+
defaultDetailLevel: "L1",
|
|
1544
|
+
preferredNamespacesByIntent: { codeSearch: [] }
|
|
1105
1545
|
}),
|
|
1106
1546
|
index: IndexSchema.default({ refreshIntervalMs: 30000 }),
|
|
1107
1547
|
logging: LoggingSchema.default({ level: "info" }),
|
|
@@ -1110,13 +1550,20 @@ var OperationsSchema = z.object({
|
|
|
1110
1550
|
enabled: true,
|
|
1111
1551
|
minCooccurrenceThreshold: 2,
|
|
1112
1552
|
maxBundleSuggestions: 3
|
|
1553
|
+
}),
|
|
1554
|
+
dynamicToolSurface: DynamicToolSurfaceSchema.default({
|
|
1555
|
+
inference: "heuristic_with_overrides",
|
|
1556
|
+
refresh: "on_connect",
|
|
1557
|
+
capabilityOverrides: {},
|
|
1558
|
+
semanticConfidenceThreshold: 0.45
|
|
1113
1559
|
})
|
|
1114
1560
|
}).default({
|
|
1115
1561
|
findTools: {
|
|
1116
1562
|
defaultLimit: 5,
|
|
1117
1563
|
maxLimit: 50,
|
|
1118
1564
|
defaultMode: "fast",
|
|
1119
|
-
defaultDetailLevel: "L1"
|
|
1565
|
+
defaultDetailLevel: "L1",
|
|
1566
|
+
preferredNamespacesByIntent: { codeSearch: [] }
|
|
1120
1567
|
},
|
|
1121
1568
|
index: { refreshIntervalMs: 30000 },
|
|
1122
1569
|
logging: { level: "info" },
|
|
@@ -1125,6 +1572,12 @@ var OperationsSchema = z.object({
|
|
|
1125
1572
|
enabled: true,
|
|
1126
1573
|
minCooccurrenceThreshold: 2,
|
|
1127
1574
|
maxBundleSuggestions: 3
|
|
1575
|
+
},
|
|
1576
|
+
dynamicToolSurface: {
|
|
1577
|
+
inference: "heuristic_with_overrides",
|
|
1578
|
+
refresh: "on_connect",
|
|
1579
|
+
capabilityOverrides: {},
|
|
1580
|
+
semanticConfidenceThreshold: 0.45
|
|
1128
1581
|
}
|
|
1129
1582
|
});
|
|
1130
1583
|
var ConfigSchema = z.object({
|
|
@@ -1178,6 +1631,34 @@ function migrateV0ToV1(config) {
|
|
|
1178
1631
|
|
|
1179
1632
|
// src/config/load.ts
|
|
1180
1633
|
init_paths();
|
|
1634
|
+
function isRecord(value) {
|
|
1635
|
+
return typeof value === "object" && value !== null;
|
|
1636
|
+
}
|
|
1637
|
+
function getDeprecatedDynamicToolSurfaceKeys(raw) {
|
|
1638
|
+
const operations = raw["operations"];
|
|
1639
|
+
if (!isRecord(operations)) {
|
|
1640
|
+
return [];
|
|
1641
|
+
}
|
|
1642
|
+
const dynamicToolSurface = operations["dynamicToolSurface"];
|
|
1643
|
+
if (!isRecord(dynamicToolSurface)) {
|
|
1644
|
+
return [];
|
|
1645
|
+
}
|
|
1646
|
+
const deprecated = [];
|
|
1647
|
+
if ("mode" in dynamicToolSurface) {
|
|
1648
|
+
deprecated.push("operations.dynamicToolSurface.mode");
|
|
1649
|
+
}
|
|
1650
|
+
if ("naming" in dynamicToolSurface) {
|
|
1651
|
+
deprecated.push("operations.dynamicToolSurface.naming");
|
|
1652
|
+
}
|
|
1653
|
+
return deprecated;
|
|
1654
|
+
}
|
|
1655
|
+
function warnDeprecatedDynamicToolSurfaceKeys(filePath, keys) {
|
|
1656
|
+
if (keys.length === 0) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
console.warn(`[mcp\xB2] Deprecated config keys in ${filePath}: ${keys.join(", ")}. Run 'mcp-squared migrate' to clean up legacy settings.`);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1181
1662
|
class ConfigError extends Error {
|
|
1182
1663
|
cause;
|
|
1183
1664
|
constructor(message, cause) {
|
|
@@ -1245,6 +1726,7 @@ async function loadConfigFromPath(filePath, source) {
|
|
|
1245
1726
|
} catch (err) {
|
|
1246
1727
|
throw new ConfigParseError(filePath, err);
|
|
1247
1728
|
}
|
|
1729
|
+
warnDeprecatedDynamicToolSurfaceKeys(filePath, getDeprecatedDynamicToolSurfaceKeys(rawConfig));
|
|
1248
1730
|
const migrated = migrateConfig(rawConfig);
|
|
1249
1731
|
let config;
|
|
1250
1732
|
try {
|
|
@@ -4277,6 +4759,233 @@ Installation cancelled.`);
|
|
|
4277
4759
|
process.exit(0);
|
|
4278
4760
|
}
|
|
4279
4761
|
|
|
4762
|
+
// src/migrate/runner.ts
|
|
4763
|
+
init_inference();
|
|
4764
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4765
|
+
import { parse as parseToml4 } from "smol-toml";
|
|
4766
|
+
var DEFAULT_CODE_SEARCH_NAMESPACES = ["auggie", "ctxdb"];
|
|
4767
|
+
var RESERVED_DESCRIBE_ACTION = "__describe_actions";
|
|
4768
|
+
function isRecord2(value) {
|
|
4769
|
+
return typeof value === "object" && value !== null;
|
|
4770
|
+
}
|
|
4771
|
+
function hasOwn(obj, key) {
|
|
4772
|
+
return Object.hasOwn(obj, key);
|
|
4773
|
+
}
|
|
4774
|
+
function toActionToken(value) {
|
|
4775
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
4776
|
+
const token = normalized.length > 0 ? normalized : "tool";
|
|
4777
|
+
const reservedNormalized = RESERVED_DESCRIBE_ACTION.replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
4778
|
+
if (token === reservedNormalized) {
|
|
4779
|
+
return `${RESERVED_DESCRIBE_ACTION}__tool`;
|
|
4780
|
+
}
|
|
4781
|
+
return token;
|
|
4782
|
+
}
|
|
4783
|
+
function isCapabilityId(value) {
|
|
4784
|
+
return CAPABILITY_IDS.includes(value);
|
|
4785
|
+
}
|
|
4786
|
+
function hasLegacyDynamicToolSurfaceKeys(rawConfig) {
|
|
4787
|
+
if (!isRecord2(rawConfig)) {
|
|
4788
|
+
return false;
|
|
4789
|
+
}
|
|
4790
|
+
const operations = rawConfig["operations"];
|
|
4791
|
+
if (!isRecord2(operations)) {
|
|
4792
|
+
return false;
|
|
4793
|
+
}
|
|
4794
|
+
const dynamicToolSurface = operations["dynamicToolSurface"];
|
|
4795
|
+
if (!isRecord2(dynamicToolSurface)) {
|
|
4796
|
+
return false;
|
|
4797
|
+
}
|
|
4798
|
+
return hasOwn(dynamicToolSurface, "mode") || hasOwn(dynamicToolSurface, "naming");
|
|
4799
|
+
}
|
|
4800
|
+
function isCodeSearchExplicitlyConfigured(rawConfig) {
|
|
4801
|
+
if (!isRecord2(rawConfig)) {
|
|
4802
|
+
return false;
|
|
4803
|
+
}
|
|
4804
|
+
const operations = rawConfig["operations"];
|
|
4805
|
+
if (!isRecord2(operations)) {
|
|
4806
|
+
return false;
|
|
4807
|
+
}
|
|
4808
|
+
const findTools = operations["findTools"];
|
|
4809
|
+
if (!isRecord2(findTools)) {
|
|
4810
|
+
return false;
|
|
4811
|
+
}
|
|
4812
|
+
const preferredNamespacesByIntent = findTools["preferredNamespacesByIntent"];
|
|
4813
|
+
if (!isRecord2(preferredNamespacesByIntent)) {
|
|
4814
|
+
return false;
|
|
4815
|
+
}
|
|
4816
|
+
return hasOwn(preferredNamespacesByIntent, "codeSearch");
|
|
4817
|
+
}
|
|
4818
|
+
function dedupePreserveOrder(values) {
|
|
4819
|
+
const seen = new Set;
|
|
4820
|
+
const result = [];
|
|
4821
|
+
for (const value of values) {
|
|
4822
|
+
if (seen.has(value)) {
|
|
4823
|
+
continue;
|
|
4824
|
+
}
|
|
4825
|
+
seen.add(value);
|
|
4826
|
+
result.push(value);
|
|
4827
|
+
}
|
|
4828
|
+
return result;
|
|
4829
|
+
}
|
|
4830
|
+
function translateSecurityPattern(pattern, config) {
|
|
4831
|
+
const [scopeRaw, actionRaw] = pattern.split(":", 2);
|
|
4832
|
+
if (!scopeRaw || !actionRaw) {
|
|
4833
|
+
return {
|
|
4834
|
+
original: pattern,
|
|
4835
|
+
translated: pattern,
|
|
4836
|
+
changed: false,
|
|
4837
|
+
unresolvedReason: "invalid pattern format"
|
|
4838
|
+
};
|
|
4839
|
+
}
|
|
4840
|
+
const translatedAction = actionRaw === "*" ? "*" : toActionToken(actionRaw);
|
|
4841
|
+
if (scopeRaw === "*") {
|
|
4842
|
+
const translated2 = `*:${translatedAction}`;
|
|
4843
|
+
return {
|
|
4844
|
+
original: pattern,
|
|
4845
|
+
translated: translated2,
|
|
4846
|
+
changed: translated2 !== pattern
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
if (isCapabilityId(scopeRaw)) {
|
|
4850
|
+
const translated2 = `${scopeRaw}:${translatedAction}`;
|
|
4851
|
+
return {
|
|
4852
|
+
original: pattern,
|
|
4853
|
+
translated: translated2,
|
|
4854
|
+
changed: translated2 !== pattern
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
const override = config.operations.dynamicToolSurface.capabilityOverrides[scopeRaw];
|
|
4858
|
+
const capability = override ?? inferNamespaceCapability(scopeRaw, [], {});
|
|
4859
|
+
const translated = `${capability}:${translatedAction}`;
|
|
4860
|
+
return {
|
|
4861
|
+
original: pattern,
|
|
4862
|
+
translated,
|
|
4863
|
+
changed: translated !== pattern
|
|
4864
|
+
};
|
|
4865
|
+
}
|
|
4866
|
+
function applyCodeSearchPreferenceMigration(config, options) {
|
|
4867
|
+
if (options.codeSearchExplicitlyConfigured) {
|
|
4868
|
+
return { config, changed: false };
|
|
4869
|
+
}
|
|
4870
|
+
return {
|
|
4871
|
+
changed: true,
|
|
4872
|
+
config: {
|
|
4873
|
+
...config,
|
|
4874
|
+
operations: {
|
|
4875
|
+
...config.operations,
|
|
4876
|
+
findTools: {
|
|
4877
|
+
...config.operations.findTools,
|
|
4878
|
+
preferredNamespacesByIntent: {
|
|
4879
|
+
...config.operations.findTools.preferredNamespacesByIntent,
|
|
4880
|
+
codeSearch: [...DEFAULT_CODE_SEARCH_NAMESPACES]
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
function applySecurityPatternMigration(config) {
|
|
4888
|
+
const report = {
|
|
4889
|
+
translated: [],
|
|
4890
|
+
unresolved: []
|
|
4891
|
+
};
|
|
4892
|
+
const migrateList = (patterns) => {
|
|
4893
|
+
const next = [];
|
|
4894
|
+
for (const pattern of patterns) {
|
|
4895
|
+
const translation = translateSecurityPattern(pattern, config);
|
|
4896
|
+
next.push(translation.translated);
|
|
4897
|
+
if (translation.unresolvedReason) {
|
|
4898
|
+
report.unresolved.push({
|
|
4899
|
+
pattern: translation.original,
|
|
4900
|
+
reason: translation.unresolvedReason
|
|
4901
|
+
});
|
|
4902
|
+
} else if (translation.changed) {
|
|
4903
|
+
report.translated.push({
|
|
4904
|
+
from: translation.original,
|
|
4905
|
+
to: translation.translated
|
|
4906
|
+
});
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
return dedupePreserveOrder(next);
|
|
4910
|
+
};
|
|
4911
|
+
const allow = migrateList(config.security.tools.allow);
|
|
4912
|
+
const block = migrateList(config.security.tools.block);
|
|
4913
|
+
const confirm = migrateList(config.security.tools.confirm);
|
|
4914
|
+
const changed = allow.join("|") !== config.security.tools.allow.join("|") || block.join("|") !== config.security.tools.block.join("|") || confirm.join("|") !== config.security.tools.confirm.join("|");
|
|
4915
|
+
if (!changed && report.unresolved.length === 0) {
|
|
4916
|
+
return { config, changed: false, report };
|
|
4917
|
+
}
|
|
4918
|
+
return {
|
|
4919
|
+
changed,
|
|
4920
|
+
report,
|
|
4921
|
+
config: {
|
|
4922
|
+
...config,
|
|
4923
|
+
security: {
|
|
4924
|
+
...config.security,
|
|
4925
|
+
tools: {
|
|
4926
|
+
allow,
|
|
4927
|
+
block,
|
|
4928
|
+
confirm
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
}
|
|
4932
|
+
};
|
|
4933
|
+
}
|
|
4934
|
+
async function runMigrate(args) {
|
|
4935
|
+
const discovered = discoverConfigPath();
|
|
4936
|
+
if (!discovered) {
|
|
4937
|
+
console.error("No configuration file found. Run 'mcp-squared init' to create one first.");
|
|
4938
|
+
process.exit(1);
|
|
4939
|
+
}
|
|
4940
|
+
const loaded = await loadConfigFromPath(discovered.path, discovered.source);
|
|
4941
|
+
const rawConfig = parseToml4(readFileSync5(discovered.path, "utf-8"));
|
|
4942
|
+
const codeSearchExplicitlyConfigured = isCodeSearchExplicitlyConfigured(rawConfig);
|
|
4943
|
+
const hasLegacySurfaceKeys = hasLegacyDynamicToolSurfaceKeys(rawConfig);
|
|
4944
|
+
const codeSearchMigration = applyCodeSearchPreferenceMigration(loaded.config, {
|
|
4945
|
+
codeSearchExplicitlyConfigured
|
|
4946
|
+
});
|
|
4947
|
+
const securityMigration = applySecurityPatternMigration(codeSearchMigration.config);
|
|
4948
|
+
const changed = codeSearchMigration.changed || securityMigration.changed || hasLegacySurfaceKeys;
|
|
4949
|
+
if (!changed && securityMigration.report.unresolved.length === 0) {
|
|
4950
|
+
console.log(`No migration needed for ${discovered.path}`);
|
|
4951
|
+
return;
|
|
4952
|
+
}
|
|
4953
|
+
if (args.dryRun) {
|
|
4954
|
+
console.log(`[dry-run] Would update ${discovered.path}`);
|
|
4955
|
+
if (codeSearchMigration.changed) {
|
|
4956
|
+
console.log('[dry-run] Set operations.findTools.preferredNamespacesByIntent.codeSearch = ["auggie", "ctxdb"]');
|
|
4957
|
+
}
|
|
4958
|
+
if (securityMigration.report.translated.length > 0) {
|
|
4959
|
+
for (const item of securityMigration.report.translated) {
|
|
4960
|
+
console.log(`[dry-run] Translate security pattern: ${item.from} -> ${item.to}`);
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
if (hasLegacySurfaceKeys) {
|
|
4964
|
+
console.log("[dry-run] Remove deprecated keys: operations.dynamicToolSurface.mode, operations.dynamicToolSurface.naming");
|
|
4965
|
+
}
|
|
4966
|
+
if (securityMigration.report.unresolved.length > 0) {
|
|
4967
|
+
for (const unresolved of securityMigration.report.unresolved) {
|
|
4968
|
+
console.warn(`[dry-run] Unresolved security pattern '${unresolved.pattern}': ${unresolved.reason}`);
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
return;
|
|
4972
|
+
}
|
|
4973
|
+
await saveConfig(discovered.path, securityMigration.config);
|
|
4974
|
+
console.log(`Updated ${discovered.path}`);
|
|
4975
|
+
if (codeSearchMigration.changed) {
|
|
4976
|
+
console.log('Set operations.findTools.preferredNamespacesByIntent.codeSearch = ["auggie", "ctxdb"]');
|
|
4977
|
+
}
|
|
4978
|
+
for (const item of securityMigration.report.translated) {
|
|
4979
|
+
console.log(`Translated security pattern: ${item.from} -> ${item.to}`);
|
|
4980
|
+
}
|
|
4981
|
+
if (hasLegacySurfaceKeys) {
|
|
4982
|
+
console.log("Removed deprecated keys: operations.dynamicToolSurface.mode, operations.dynamicToolSurface.naming");
|
|
4983
|
+
}
|
|
4984
|
+
for (const unresolved of securityMigration.report.unresolved) {
|
|
4985
|
+
console.warn(`Unresolved security pattern '${unresolved.pattern}': ${unresolved.reason}. Please review manually.`);
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4280
4989
|
// src/oauth/browser.ts
|
|
4281
4990
|
import { spawn as spawn2 } from "child_process";
|
|
4282
4991
|
async function openBrowser(url) {
|
|
@@ -4664,7 +5373,7 @@ import {
|
|
|
4664
5373
|
chmodSync,
|
|
4665
5374
|
existsSync as existsSync8,
|
|
4666
5375
|
mkdirSync as mkdirSync5,
|
|
4667
|
-
readFileSync as
|
|
5376
|
+
readFileSync as readFileSync6,
|
|
4668
5377
|
unlinkSync as unlinkSync4,
|
|
4669
5378
|
writeFileSync as writeFileSync4
|
|
4670
5379
|
} from "fs";
|
|
@@ -4702,7 +5411,7 @@ class TokenStorage {
|
|
|
4702
5411
|
return;
|
|
4703
5412
|
}
|
|
4704
5413
|
try {
|
|
4705
|
-
const content =
|
|
5414
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
4706
5415
|
return JSON.parse(content);
|
|
4707
5416
|
} catch {
|
|
4708
5417
|
return;
|
|
@@ -4890,6 +5599,15 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
4890
5599
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4891
5600
|
import { z as z3 } from "zod";
|
|
4892
5601
|
|
|
5602
|
+
// agent_safety_kit/guard/errors.ts
|
|
5603
|
+
class PolicyDenied extends Error {
|
|
5604
|
+
decision;
|
|
5605
|
+
constructor(message, decision) {
|
|
5606
|
+
super(message);
|
|
5607
|
+
this.name = "PolicyDenied";
|
|
5608
|
+
this.decision = decision;
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
4893
5611
|
// agent_safety_kit/policy/matchers.ts
|
|
4894
5612
|
var GLOB_SPECIALS = /[.+^${}()|[\]\\]/g;
|
|
4895
5613
|
var PATH_KEY_RE = /(path|file|dir|cwd|workspace|root)/i;
|
|
@@ -5027,16 +5745,6 @@ function valuesConstrainedByGlob(values, allowlist) {
|
|
|
5027
5745
|
return values.every((value) => matchesAnyGlob(allowlist, value));
|
|
5028
5746
|
}
|
|
5029
5747
|
|
|
5030
|
-
// agent_safety_kit/guard/errors.ts
|
|
5031
|
-
class PolicyDenied extends Error {
|
|
5032
|
-
decision;
|
|
5033
|
-
constructor(message, decision) {
|
|
5034
|
-
super(message);
|
|
5035
|
-
this.name = "PolicyDenied";
|
|
5036
|
-
this.decision = decision;
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
5039
|
-
|
|
5040
5748
|
// agent_safety_kit/guard/ratelimit.ts
|
|
5041
5749
|
class SlidingWindowRateLimiter {
|
|
5042
5750
|
requests = new Map;
|
|
@@ -5246,7 +5954,7 @@ class Guard {
|
|
|
5246
5954
|
}
|
|
5247
5955
|
}
|
|
5248
5956
|
// agent_safety_kit/policy/load.ts
|
|
5249
|
-
import { existsSync as existsSync9, readFileSync as
|
|
5957
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
5250
5958
|
import { resolve as resolve3 } from "path";
|
|
5251
5959
|
import { parse as parseYaml } from "yaml";
|
|
5252
5960
|
|
|
@@ -5309,7 +6017,7 @@ function load_policy(options = {}) {
|
|
|
5309
6017
|
if (!existsSync9(sourcePath)) {
|
|
5310
6018
|
throw new Error(`Agent safety policy file not found at ${sourcePath}`);
|
|
5311
6019
|
}
|
|
5312
|
-
const raw =
|
|
6020
|
+
const raw = readFileSync7(sourcePath, "utf8");
|
|
5313
6021
|
const parsed = parseYaml(raw);
|
|
5314
6022
|
const policy = AgentPolicySchema.parse(parsed);
|
|
5315
6023
|
const playbookName = options.playbook ?? envConfig.playbook;
|
|
@@ -5329,6 +6037,7 @@ function load_policy(options = {}) {
|
|
|
5329
6037
|
rules: playbook.rules
|
|
5330
6038
|
};
|
|
5331
6039
|
}
|
|
6040
|
+
|
|
5332
6041
|
// agent_safety_kit/observability/sinks/null.ts
|
|
5333
6042
|
class NullSpan {
|
|
5334
6043
|
setAttributes(_attributes) {}
|
|
@@ -5776,6 +6485,83 @@ class SelectionTracker {
|
|
|
5776
6485
|
this.sessionTools.clear();
|
|
5777
6486
|
}
|
|
5778
6487
|
}
|
|
6488
|
+
// src/server/index.ts
|
|
6489
|
+
init_inference();
|
|
6490
|
+
|
|
6491
|
+
// src/capabilities/routing.ts
|
|
6492
|
+
var DESCRIBE_ACTION = "__describe_actions";
|
|
6493
|
+
function toActionToken2(value) {
|
|
6494
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
6495
|
+
return normalized.length > 0 ? normalized : "tool";
|
|
6496
|
+
}
|
|
6497
|
+
function buildCapabilityRouters(inventories, grouping, summarize) {
|
|
6498
|
+
if (inventories.length === 0) {
|
|
6499
|
+
return [];
|
|
6500
|
+
}
|
|
6501
|
+
const defaultSummarize = (desc, cap) => {
|
|
6502
|
+
if (desc) {
|
|
6503
|
+
const firstLine = desc.split(`
|
|
6504
|
+
`)[0]?.trim();
|
|
6505
|
+
if (firstLine && firstLine.length > 0) {
|
|
6506
|
+
return firstLine.length > 120 ? `${firstLine.slice(0, 117)}...` : firstLine;
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6509
|
+
const title = cap.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
6510
|
+
return `Execute ${title} action`;
|
|
6511
|
+
};
|
|
6512
|
+
const resolveSummary = summarize ?? defaultSummarize;
|
|
6513
|
+
const candidates = [];
|
|
6514
|
+
const reservedNormalized = toActionToken2(DESCRIBE_ACTION);
|
|
6515
|
+
for (const inventory of inventories) {
|
|
6516
|
+
const capability = grouping.byNamespace[inventory.namespace] ?? "general";
|
|
6517
|
+
const sortedTools = [...inventory.tools].sort((a, b) => a.name.localeCompare(b.name));
|
|
6518
|
+
for (const tool of sortedTools) {
|
|
6519
|
+
let baseAction = toActionToken2(tool.name);
|
|
6520
|
+
if (baseAction === DESCRIBE_ACTION || baseAction === reservedNormalized) {
|
|
6521
|
+
baseAction = `${DESCRIBE_ACTION}__tool`;
|
|
6522
|
+
}
|
|
6523
|
+
candidates.push({
|
|
6524
|
+
capability,
|
|
6525
|
+
action: baseAction,
|
|
6526
|
+
baseAction,
|
|
6527
|
+
serverKey: inventory.namespace,
|
|
6528
|
+
toolName: tool.name,
|
|
6529
|
+
qualifiedName: `${inventory.namespace}:${tool.name}`,
|
|
6530
|
+
inputSchema: tool.inputSchema ?? { type: "object" },
|
|
6531
|
+
summary: resolveSummary(tool.description ?? undefined, capability)
|
|
6532
|
+
});
|
|
6533
|
+
}
|
|
6534
|
+
}
|
|
6535
|
+
const byCapabilityAction = new Map;
|
|
6536
|
+
for (const candidate of candidates) {
|
|
6537
|
+
const key = `${candidate.capability}:${candidate.baseAction}`;
|
|
6538
|
+
const existing = byCapabilityAction.get(key) ?? [];
|
|
6539
|
+
existing.push(candidate);
|
|
6540
|
+
byCapabilityAction.set(key, existing);
|
|
6541
|
+
}
|
|
6542
|
+
const resolved = [];
|
|
6543
|
+
const sortedKeys = [...byCapabilityAction.keys()].sort((a, b) => a.localeCompare(b));
|
|
6544
|
+
for (const key of sortedKeys) {
|
|
6545
|
+
const records = (byCapabilityAction.get(key) ?? []).sort((a, b) => a.qualifiedName.localeCompare(b.qualifiedName));
|
|
6546
|
+
records.forEach((record, index) => {
|
|
6547
|
+
resolved.push({
|
|
6548
|
+
...record,
|
|
6549
|
+
action: index === 0 ? record.baseAction : `${record.baseAction}__${index + 1}`
|
|
6550
|
+
});
|
|
6551
|
+
});
|
|
6552
|
+
}
|
|
6553
|
+
const byCapability = new Map;
|
|
6554
|
+
for (const route of resolved) {
|
|
6555
|
+
const existing = byCapability.get(route.capability) ?? [];
|
|
6556
|
+
existing.push(route);
|
|
6557
|
+
byCapability.set(route.capability, existing);
|
|
6558
|
+
}
|
|
6559
|
+
return [...byCapability.entries()].map(([capability, actions]) => ({
|
|
6560
|
+
capability,
|
|
6561
|
+
actions: actions.sort((a, b) => a.action.localeCompare(b.action))
|
|
6562
|
+
})).sort((a, b) => a.capability.localeCompare(b.capability));
|
|
6563
|
+
}
|
|
6564
|
+
|
|
5779
6565
|
// src/index/store.ts
|
|
5780
6566
|
import { Database } from "bun:sqlite";
|
|
5781
6567
|
function hashSchema2(schema) {
|
|
@@ -6322,6 +7108,9 @@ class Retriever {
|
|
|
6322
7108
|
clearIndex() {
|
|
6323
7109
|
this.indexStore.clear();
|
|
6324
7110
|
}
|
|
7111
|
+
getEmbeddingGenerator() {
|
|
7112
|
+
return this.embeddingGenerator;
|
|
7113
|
+
}
|
|
6325
7114
|
async initializeEmbeddings() {
|
|
6326
7115
|
if (!this.embeddingsAvailable) {
|
|
6327
7116
|
return;
|
|
@@ -6443,60 +7232,68 @@ function cleanupExpiredTokens() {
|
|
|
6443
7232
|
}
|
|
6444
7233
|
}
|
|
6445
7234
|
}
|
|
6446
|
-
function validateConfirmationToken(token,
|
|
7235
|
+
function validateConfirmationToken(token, capability, action) {
|
|
6447
7236
|
cleanupExpiredTokens();
|
|
6448
7237
|
const confirmation = pendingConfirmations.get(token);
|
|
6449
7238
|
if (!confirmation) {
|
|
6450
7239
|
return false;
|
|
6451
7240
|
}
|
|
6452
|
-
if (confirmation.
|
|
7241
|
+
if (confirmation.capability !== capability || confirmation.action !== action) {
|
|
6453
7242
|
return false;
|
|
6454
7243
|
}
|
|
6455
7244
|
pendingConfirmations.delete(token);
|
|
6456
7245
|
return true;
|
|
6457
7246
|
}
|
|
6458
|
-
function createConfirmationToken(
|
|
7247
|
+
function createConfirmationToken(capability, action) {
|
|
6459
7248
|
cleanupExpiredTokens();
|
|
6460
7249
|
const token = generateToken();
|
|
6461
7250
|
pendingConfirmations.set(token, {
|
|
6462
|
-
|
|
6463
|
-
|
|
7251
|
+
capability,
|
|
7252
|
+
action,
|
|
6464
7253
|
createdAt: Date.now()
|
|
6465
7254
|
});
|
|
6466
7255
|
return token;
|
|
6467
7256
|
}
|
|
6468
7257
|
function evaluatePolicy(context, config) {
|
|
6469
|
-
const
|
|
7258
|
+
const capability = context.capability ?? context.serverKey;
|
|
7259
|
+
const action = context.action ?? context.toolName;
|
|
7260
|
+
const confirmationToken = context.confirmationToken;
|
|
6470
7261
|
const { block, confirm, allow } = config.security.tools;
|
|
6471
|
-
if (
|
|
7262
|
+
if (!capability || !action) {
|
|
6472
7263
|
return {
|
|
6473
7264
|
decision: "block",
|
|
6474
|
-
reason:
|
|
7265
|
+
reason: "Missing capability/action in security policy context"
|
|
6475
7266
|
};
|
|
6476
7267
|
}
|
|
6477
|
-
if (matchesAnyPattern(
|
|
7268
|
+
if (matchesAnyPattern(block, capability, action)) {
|
|
7269
|
+
return {
|
|
7270
|
+
decision: "block",
|
|
7271
|
+
reason: `Action "${action}" in capability "${capability}" is blocked by security policy`
|
|
7272
|
+
};
|
|
7273
|
+
}
|
|
7274
|
+
if (matchesAnyPattern(allow, capability, action)) {
|
|
6478
7275
|
return {
|
|
6479
7276
|
decision: "allow",
|
|
6480
|
-
reason: `
|
|
7277
|
+
reason: `Action "${action}" is allowed by security policy`
|
|
6481
7278
|
};
|
|
6482
7279
|
}
|
|
6483
|
-
if (matchesAnyPattern(confirm,
|
|
6484
|
-
if (confirmationToken && validateConfirmationToken(confirmationToken,
|
|
7280
|
+
if (matchesAnyPattern(confirm, capability, action)) {
|
|
7281
|
+
if (confirmationToken && validateConfirmationToken(confirmationToken, capability, action)) {
|
|
6485
7282
|
return {
|
|
6486
7283
|
decision: "allow",
|
|
6487
|
-
reason: `
|
|
7284
|
+
reason: `Action "${action}" confirmed with valid token`
|
|
6488
7285
|
};
|
|
6489
7286
|
}
|
|
6490
|
-
const token = createConfirmationToken(
|
|
7287
|
+
const token = createConfirmationToken(capability, action);
|
|
6491
7288
|
return {
|
|
6492
7289
|
decision: "confirm",
|
|
6493
|
-
reason: `
|
|
7290
|
+
reason: `Action "${action}" in capability "${capability}" requires confirmation`,
|
|
6494
7291
|
confirmationToken: token
|
|
6495
7292
|
};
|
|
6496
7293
|
}
|
|
6497
7294
|
return {
|
|
6498
7295
|
decision: "block",
|
|
6499
|
-
reason: `
|
|
7296
|
+
reason: `Action "${action}" in capability "${capability}" is not in the allow or confirm list`
|
|
6500
7297
|
};
|
|
6501
7298
|
}
|
|
6502
7299
|
function compilePolicy(config) {
|
|
@@ -6661,6 +7458,26 @@ async function waitForProcessExit(proc, timeoutMs) {
|
|
|
6661
7458
|
}
|
|
6662
7459
|
|
|
6663
7460
|
// src/upstream/cataloger.ts
|
|
7461
|
+
var AUTH_ERROR_PATTERNS = [
|
|
7462
|
+
"invalid_token",
|
|
7463
|
+
"invalid token",
|
|
7464
|
+
"unauthorized",
|
|
7465
|
+
"no token provided",
|
|
7466
|
+
"no authorization",
|
|
7467
|
+
"api key",
|
|
7468
|
+
"api_key",
|
|
7469
|
+
"authentication required",
|
|
7470
|
+
"authentication failed",
|
|
7471
|
+
"invalid credentials",
|
|
7472
|
+
"invalid api",
|
|
7473
|
+
"forbidden",
|
|
7474
|
+
"access denied",
|
|
7475
|
+
"not authenticated"
|
|
7476
|
+
];
|
|
7477
|
+
function isAuthError(message) {
|
|
7478
|
+
const lower = message.toLowerCase();
|
|
7479
|
+
return AUTH_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
|
|
7480
|
+
}
|
|
6664
7481
|
function resolveEnvVars(env2) {
|
|
6665
7482
|
const resolved = {};
|
|
6666
7483
|
for (const [key, value] of Object.entries(env2)) {
|
|
@@ -6741,7 +7558,7 @@ class Cataloger {
|
|
|
6741
7558
|
if (err instanceof UnauthorizedError2 && connection.authProvider) {
|
|
6742
7559
|
if (connection.authProvider.isNonInteractive()) {
|
|
6743
7560
|
connection.authPending = true;
|
|
6744
|
-
connection.status = "
|
|
7561
|
+
connection.status = "needs_auth";
|
|
6745
7562
|
connection.error = `OAuth authorization required. Run: mcp-squared auth ${key}`;
|
|
6746
7563
|
return;
|
|
6747
7564
|
}
|
|
@@ -6765,8 +7582,9 @@ class Cataloger {
|
|
|
6765
7582
|
}));
|
|
6766
7583
|
connection.status = "connected";
|
|
6767
7584
|
} catch (err) {
|
|
6768
|
-
|
|
6769
|
-
connection.
|
|
7585
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
7586
|
+
connection.status = isAuthError(errorMsg) ? "needs_auth" : "error";
|
|
7587
|
+
connection.error = errorMsg;
|
|
6770
7588
|
try {
|
|
6771
7589
|
await this.cleanupConnection(connection);
|
|
6772
7590
|
} catch (_cleanupErr) {}
|
|
@@ -6928,13 +7746,14 @@ class Cataloger {
|
|
|
6928
7746
|
if (err instanceof UnauthorizedError2 && connection.authProvider) {
|
|
6929
7747
|
if (connection.authProvider.isNonInteractive()) {
|
|
6930
7748
|
connection.authPending = true;
|
|
6931
|
-
connection.status = "
|
|
7749
|
+
connection.status = "needs_auth";
|
|
6932
7750
|
connection.error = `OAuth authorization required. Run: mcp-squared auth ${key}`;
|
|
6933
7751
|
return;
|
|
6934
7752
|
}
|
|
6935
7753
|
}
|
|
6936
|
-
|
|
6937
|
-
connection.
|
|
7754
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
7755
|
+
connection.status = isAuthError(errorMsg) ? "needs_auth" : "error";
|
|
7756
|
+
connection.error = errorMsg;
|
|
6938
7757
|
}
|
|
6939
7758
|
}
|
|
6940
7759
|
async refreshAllTools() {
|
|
@@ -7239,6 +8058,37 @@ async function testUpstreamConnection(name, config, options = {}) {
|
|
|
7239
8058
|
log("Done");
|
|
7240
8059
|
}
|
|
7241
8060
|
}
|
|
8061
|
+
// src/utils/capability-meta.ts
|
|
8062
|
+
function capabilityTitle(capability) {
|
|
8063
|
+
return capability.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
8064
|
+
}
|
|
8065
|
+
function capabilitySummary(capability) {
|
|
8066
|
+
switch (capability) {
|
|
8067
|
+
case "code_search":
|
|
8068
|
+
return "Search and retrieve source-code context.";
|
|
8069
|
+
case "docs":
|
|
8070
|
+
return "Query and read technical documentation.";
|
|
8071
|
+
case "browser_automation":
|
|
8072
|
+
return "Automate browser interactions and diagnostics.";
|
|
8073
|
+
case "issue_tracking":
|
|
8074
|
+
return "Work with issues, tickets, and project tracking.";
|
|
8075
|
+
case "cms_content":
|
|
8076
|
+
return "Manage content and CMS resources.";
|
|
8077
|
+
case "design":
|
|
8078
|
+
return "Create and inspect design artifacts and visuals.";
|
|
8079
|
+
case "ai_media_generation":
|
|
8080
|
+
return "Generate and edit images and media using AI models.";
|
|
8081
|
+
case "hosting_deploy":
|
|
8082
|
+
return "Manage deployments, hosting, and infrastructure operations.";
|
|
8083
|
+
case "time_util":
|
|
8084
|
+
return "Resolve time, timezone, and date utilities.";
|
|
8085
|
+
case "research":
|
|
8086
|
+
return "Run web/research collection and synthesis operations.";
|
|
8087
|
+
default:
|
|
8088
|
+
return "Run general-purpose capability actions.";
|
|
8089
|
+
}
|
|
8090
|
+
}
|
|
8091
|
+
|
|
7242
8092
|
// src/server/monitor-server.ts
|
|
7243
8093
|
import { existsSync as existsSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
7244
8094
|
import { createServer as createServer3 } from "net";
|
|
@@ -7676,12 +8526,16 @@ class StatsCollector {
|
|
|
7676
8526
|
}
|
|
7677
8527
|
|
|
7678
8528
|
// src/server/index.ts
|
|
8529
|
+
var DESCRIBE_ACTION2 = "__describe_actions";
|
|
8530
|
+
function isRecord3(value) {
|
|
8531
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8532
|
+
}
|
|
8533
|
+
|
|
7679
8534
|
class McpSquaredServer {
|
|
7680
8535
|
mcpServer;
|
|
7681
8536
|
cataloger;
|
|
7682
8537
|
retriever;
|
|
7683
8538
|
config;
|
|
7684
|
-
maxLimit;
|
|
7685
8539
|
transport = null;
|
|
7686
8540
|
ownsCataloger;
|
|
7687
8541
|
selectionTracker;
|
|
@@ -7695,6 +8549,8 @@ class McpSquaredServer {
|
|
|
7695
8549
|
safetyAgent;
|
|
7696
8550
|
obsSink;
|
|
7697
8551
|
guard;
|
|
8552
|
+
baseToolsRegistered = false;
|
|
8553
|
+
computedCapabilityOverrides = {};
|
|
7698
8554
|
constructor(options = {}) {
|
|
7699
8555
|
const name = options.name ?? "mcp-squared";
|
|
7700
8556
|
const version = options.version ?? VERSION;
|
|
@@ -7709,11 +8565,11 @@ class McpSquaredServer {
|
|
|
7709
8565
|
}
|
|
7710
8566
|
this.config = options.config ?? DEFAULT_CONFIG;
|
|
7711
8567
|
const findToolsConfig = this.config.operations.findTools;
|
|
7712
|
-
|
|
8568
|
+
const retrieverMaxLimit = options.maxLimit ?? findToolsConfig.maxLimit;
|
|
7713
8569
|
this.retriever = new Retriever(this.cataloger, {
|
|
7714
8570
|
indexDbPath: options.indexDbPath,
|
|
7715
8571
|
defaultLimit: options.defaultLimit ?? findToolsConfig.defaultLimit,
|
|
7716
|
-
maxLimit:
|
|
8572
|
+
maxLimit: retrieverMaxLimit,
|
|
7717
8573
|
defaultMode: findToolsConfig.defaultMode
|
|
7718
8574
|
});
|
|
7719
8575
|
const safetyEnv = readSafetyEnv();
|
|
@@ -7764,20 +8620,36 @@ class McpSquaredServer {
|
|
|
7764
8620
|
});
|
|
7765
8621
|
}
|
|
7766
8622
|
});
|
|
7767
|
-
this.registerMetaTools(this.mcpServer);
|
|
7768
8623
|
}
|
|
7769
8624
|
createMcpServer(name, version) {
|
|
7770
|
-
return new McpServer({
|
|
8625
|
+
return new McpServer({
|
|
8626
|
+
name,
|
|
8627
|
+
version,
|
|
8628
|
+
title: "MCP\xB2 Capability Router",
|
|
8629
|
+
description: "Execute capability-first tools routed to connected upstream MCP servers."
|
|
8630
|
+
}, {
|
|
7771
8631
|
capabilities: {
|
|
7772
8632
|
tools: {}
|
|
7773
|
-
}
|
|
8633
|
+
},
|
|
8634
|
+
instructions: this.buildServerInstructions()
|
|
7774
8635
|
});
|
|
7775
8636
|
}
|
|
8637
|
+
buildServerInstructions() {
|
|
8638
|
+
return [
|
|
8639
|
+
"Tool surface is generated at connect time from inferred upstream capabilities.",
|
|
8640
|
+
"Each capability tool accepts `action`, `arguments`, and optional `confirmation_token`.",
|
|
8641
|
+
'Call a capability tool with `action = "__describe_actions"` to inspect available actions and schemas.',
|
|
8642
|
+
"Use returned action IDs for execution calls; if disambiguation is required, choose one candidate action and retry."
|
|
8643
|
+
].join(" ");
|
|
8644
|
+
}
|
|
7776
8645
|
createSessionServer() {
|
|
7777
8646
|
const server = this.createMcpServer(this.serverName, this.serverVersion);
|
|
7778
|
-
this.
|
|
8647
|
+
this.registerConfiguredToolSurface(server);
|
|
7779
8648
|
return server;
|
|
7780
8649
|
}
|
|
8650
|
+
registerConfiguredToolSurface(server) {
|
|
8651
|
+
this.registerCapabilityRouters(server);
|
|
8652
|
+
}
|
|
7781
8653
|
runTaskSpan(taskName, run) {
|
|
7782
8654
|
return task_span(this.obsSink, {
|
|
7783
8655
|
agent: this.safetyAgent,
|
|
@@ -7786,358 +8658,239 @@ class McpSquaredServer {
|
|
|
7786
8658
|
env: this.guard.agentEnv
|
|
7787
8659
|
}, run);
|
|
7788
8660
|
}
|
|
7789
|
-
|
|
7790
|
-
return
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
description: "Search for available tools across all connected upstream MCP servers. Returns a list of tool summaries matching the query.",
|
|
7801
|
-
inputSchema: {
|
|
7802
|
-
query: z3.string().describe("Natural language search query to find relevant tools"),
|
|
7803
|
-
limit: z3.number().int().min(1).max(this.maxLimit).default(this.config.operations.findTools.defaultLimit).describe("Maximum number of results to return"),
|
|
7804
|
-
mode: SearchModeSchema.optional().describe('Search mode: "fast" (FTS5), "semantic" (embeddings), or "hybrid" (FTS5 + rerank)'),
|
|
7805
|
-
detail_level: DetailLevelSchema.optional().describe('Level of detail: "L0" (name only), "L1" (summary with description, default), "L2" (full schema)')
|
|
7806
|
-
}
|
|
7807
|
-
}, async (args) => this.runTaskSpan("find_tools", async () => {
|
|
7808
|
-
const requestId = this.statsCollector.startRequest();
|
|
7809
|
-
const startTime = Date.now();
|
|
7810
|
-
let success = false;
|
|
7811
|
-
try {
|
|
7812
|
-
const result = await this.retriever.search(args.query, {
|
|
7813
|
-
limit: args.limit,
|
|
7814
|
-
mode: args.mode
|
|
7815
|
-
});
|
|
7816
|
-
const filteredTools = this.filterToolsByPolicy(result.tools);
|
|
7817
|
-
const detailLevel = args.detail_level ?? this.config.operations.findTools.defaultDetailLevel;
|
|
7818
|
-
const tools = this.formatToolsForDetailLevel(filteredTools, detailLevel);
|
|
7819
|
-
const selectionCacheConfig = this.config.operations.selectionCache;
|
|
7820
|
-
let suggestedTools;
|
|
7821
|
-
if (selectionCacheConfig.enabled && selectionCacheConfig.maxBundleSuggestions > 0) {
|
|
7822
|
-
const toolKeys = filteredTools.map((t) => `${t.serverKey}:${t.name}`);
|
|
7823
|
-
const suggestions = this.retriever.getIndexStore().getSuggestedBundles(toolKeys, selectionCacheConfig.minCooccurrenceThreshold, selectionCacheConfig.maxBundleSuggestions);
|
|
7824
|
-
if (suggestions.length > 0) {
|
|
7825
|
-
suggestedTools = suggestions.map((s) => ({
|
|
7826
|
-
tools: [s.toolKey],
|
|
7827
|
-
frequency: s.count
|
|
7828
|
-
}));
|
|
7829
|
-
}
|
|
7830
|
-
}
|
|
7831
|
-
success = true;
|
|
7832
|
-
return {
|
|
7833
|
-
content: [
|
|
7834
|
-
{
|
|
7835
|
-
type: "text",
|
|
7836
|
-
text: JSON.stringify({
|
|
7837
|
-
query: result.query,
|
|
7838
|
-
totalMatches: filteredTools.length,
|
|
7839
|
-
detailLevel,
|
|
7840
|
-
searchMode: result.searchMode,
|
|
7841
|
-
embeddingsAvailable: this.retriever.hasEmbeddings(),
|
|
7842
|
-
tools,
|
|
7843
|
-
...suggestedTools && { suggestedTools }
|
|
7844
|
-
})
|
|
7845
|
-
}
|
|
7846
|
-
]
|
|
7847
|
-
};
|
|
7848
|
-
} finally {
|
|
7849
|
-
const responseTime = Date.now() - startTime;
|
|
7850
|
-
this.statsCollector.endRequest(requestId, success, responseTime, "find_tools", "mcp-squared");
|
|
8661
|
+
capabilityTitle(capability) {
|
|
8662
|
+
return capabilityTitle(capability);
|
|
8663
|
+
}
|
|
8664
|
+
capabilitySummary(capability) {
|
|
8665
|
+
return capabilitySummary(capability);
|
|
8666
|
+
}
|
|
8667
|
+
actionSummary(description, capability) {
|
|
8668
|
+
if (typeof description === "string") {
|
|
8669
|
+
const singleLine = description.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
8670
|
+
if (singleLine.length > 0) {
|
|
8671
|
+
return singleLine;
|
|
7851
8672
|
}
|
|
7852
|
-
}
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
const foundQualified = new Set(result.tools.map(toolKey));
|
|
7877
|
-
const foundBare = new Set(result.tools.map((t) => t.name));
|
|
7878
|
-
const ambiguousNames = new Set(result.ambiguous.map((a) => a.name));
|
|
7879
|
-
const notFound = args.tool_names.filter((name) => !ambiguousNames.has(name) && !foundQualified.has(name) && !foundBare.has(name));
|
|
7880
|
-
success = true;
|
|
7881
|
-
return {
|
|
7882
|
-
content: [
|
|
7883
|
-
{
|
|
7884
|
-
type: "text",
|
|
7885
|
-
text: JSON.stringify({
|
|
7886
|
-
schemas,
|
|
7887
|
-
ambiguous: result.ambiguous.length > 0 ? result.ambiguous : undefined,
|
|
7888
|
-
notFound: notFound.length > 0 ? notFound : undefined,
|
|
7889
|
-
blocked: blocked.length > 0 ? blocked : undefined
|
|
7890
|
-
})
|
|
7891
|
-
}
|
|
7892
|
-
]
|
|
7893
|
-
};
|
|
7894
|
-
} finally {
|
|
7895
|
-
const responseTime = Date.now() - startTime;
|
|
7896
|
-
this.statsCollector.endRequest(requestId, success, responseTime, "describe_tools", "mcp-squared");
|
|
8673
|
+
}
|
|
8674
|
+
return `Execute ${this.capabilityTitle(capability)} action`;
|
|
8675
|
+
}
|
|
8676
|
+
buildCapabilityRouters() {
|
|
8677
|
+
const status = this.cataloger.getStatus();
|
|
8678
|
+
const inventories = [...status.entries()].filter(([, info]) => info.status === "connected").map(([namespace]) => ({
|
|
8679
|
+
namespace,
|
|
8680
|
+
tools: this.cataloger.getToolsForServer(namespace)
|
|
8681
|
+
})).sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
8682
|
+
if (inventories.length === 0) {
|
|
8683
|
+
return [];
|
|
8684
|
+
}
|
|
8685
|
+
const overrides = {
|
|
8686
|
+
...this.computedCapabilityOverrides,
|
|
8687
|
+
...this.config.operations.dynamicToolSurface.capabilityOverrides
|
|
8688
|
+
};
|
|
8689
|
+
const grouping = groupNamespacesByCapability(inventories, overrides);
|
|
8690
|
+
return buildCapabilityRouters(inventories, grouping, (desc, cap) => this.actionSummary(desc, cap));
|
|
8691
|
+
}
|
|
8692
|
+
registerCapabilityRouters(server) {
|
|
8693
|
+
const routers = this.buildCapabilityRouters();
|
|
8694
|
+
for (const router of routers) {
|
|
8695
|
+
if (router.actions.length === 0) {
|
|
8696
|
+
continue;
|
|
7897
8697
|
}
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
let toolName;
|
|
7911
|
-
let serverKey;
|
|
7912
|
-
try {
|
|
7913
|
-
const lookupResult = this.cataloger.findTool(args.tool_name);
|
|
7914
|
-
if (lookupResult.ambiguous) {
|
|
7915
|
-
return {
|
|
7916
|
-
content: [
|
|
7917
|
-
{
|
|
7918
|
-
type: "text",
|
|
7919
|
-
text: JSON.stringify({
|
|
7920
|
-
error: `Ambiguous tool name "${args.tool_name}". Use a qualified name.`,
|
|
7921
|
-
alternatives: lookupResult.alternatives
|
|
7922
|
-
})
|
|
7923
|
-
}
|
|
7924
|
-
],
|
|
7925
|
-
isError: true
|
|
7926
|
-
};
|
|
8698
|
+
server.registerTool(router.capability, {
|
|
8699
|
+
title: this.capabilityTitle(router.capability),
|
|
8700
|
+
description: this.capabilitySummary(router.capability),
|
|
8701
|
+
annotations: {
|
|
8702
|
+
readOnlyHint: false,
|
|
8703
|
+
destructiveHint: false,
|
|
8704
|
+
openWorldHint: true
|
|
8705
|
+
},
|
|
8706
|
+
inputSchema: {
|
|
8707
|
+
action: z3.string().describe(`Action ID for ${router.capability}. Use "${DESCRIBE_ACTION2}" to inspect available actions and schemas.`),
|
|
8708
|
+
arguments: z3.record(z3.string(), z3.unknown()).default({}).describe("Arguments for the selected capability action"),
|
|
8709
|
+
confirmation_token: z3.string().optional().describe("Optional confirmation token for actions that require explicit confirmation")
|
|
7927
8710
|
}
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
8711
|
+
}, async (rawArgs) => this.runTaskSpan(router.capability, async () => {
|
|
8712
|
+
const requestId = this.statsCollector.startRequest();
|
|
8713
|
+
const startTime = Date.now();
|
|
8714
|
+
let success = false;
|
|
8715
|
+
try {
|
|
8716
|
+
const parsedArgs = isRecord3(rawArgs) ? { ...rawArgs } : {};
|
|
8717
|
+
const action = typeof parsedArgs["action"] === "string" ? parsedArgs["action"] : "";
|
|
8718
|
+
const confirmationToken = typeof parsedArgs["confirmation_token"] === "string" ? parsedArgs["confirmation_token"] : undefined;
|
|
8719
|
+
const actionArgs = isRecord3(parsedArgs["arguments"]) ? parsedArgs["arguments"] : {};
|
|
8720
|
+
if (action.length === 0) {
|
|
8721
|
+
return {
|
|
8722
|
+
content: [
|
|
8723
|
+
{
|
|
8724
|
+
type: "text",
|
|
8725
|
+
text: JSON.stringify({
|
|
8726
|
+
error: "Missing required action",
|
|
8727
|
+
capability: router.capability
|
|
8728
|
+
})
|
|
8729
|
+
}
|
|
8730
|
+
],
|
|
8731
|
+
isError: true
|
|
8732
|
+
};
|
|
8733
|
+
}
|
|
8734
|
+
const visibleActions = router.actions.map((route2) => {
|
|
8735
|
+
const visibility = getToolVisibilityCompiled(router.capability, route2.action, this.compiledPolicy);
|
|
8736
|
+
if (!visibility.visible) {
|
|
8737
|
+
return null;
|
|
8738
|
+
}
|
|
8739
|
+
return {
|
|
8740
|
+
action: route2.action,
|
|
8741
|
+
summary: route2.summary,
|
|
8742
|
+
inputSchema: route2.inputSchema,
|
|
8743
|
+
requiresConfirmation: visibility.requiresConfirmation
|
|
8744
|
+
};
|
|
8745
|
+
}).filter((entry) => entry !== null).sort((a, b) => a.action.localeCompare(b.action));
|
|
8746
|
+
if (action === DESCRIBE_ACTION2) {
|
|
8747
|
+
success = true;
|
|
8748
|
+
return {
|
|
8749
|
+
content: [
|
|
8750
|
+
{
|
|
8751
|
+
type: "text",
|
|
8752
|
+
text: JSON.stringify({
|
|
8753
|
+
capability: router.capability,
|
|
8754
|
+
actions: visibleActions,
|
|
8755
|
+
totalActions: visibleActions.length
|
|
8756
|
+
})
|
|
8757
|
+
}
|
|
8758
|
+
]
|
|
8759
|
+
};
|
|
8760
|
+
}
|
|
8761
|
+
const ambiguousCandidates = router.actions.filter((route2) => route2.baseAction === action).map((route2) => route2.action).sort((a, b) => a.localeCompare(b));
|
|
8762
|
+
if (ambiguousCandidates.length > 1) {
|
|
8763
|
+
return {
|
|
8764
|
+
content: [
|
|
8765
|
+
{
|
|
8766
|
+
type: "text",
|
|
8767
|
+
text: JSON.stringify({
|
|
8768
|
+
requires_disambiguation: true,
|
|
8769
|
+
capability: router.capability,
|
|
8770
|
+
action,
|
|
8771
|
+
candidates: ambiguousCandidates
|
|
8772
|
+
})
|
|
8773
|
+
}
|
|
8774
|
+
],
|
|
8775
|
+
isError: true
|
|
8776
|
+
};
|
|
8777
|
+
}
|
|
8778
|
+
const route = router.actions.find((entry) => entry.action === action);
|
|
8779
|
+
if (!route) {
|
|
8780
|
+
return {
|
|
8781
|
+
content: [
|
|
8782
|
+
{
|
|
8783
|
+
type: "text",
|
|
8784
|
+
text: JSON.stringify({
|
|
8785
|
+
error: "Unknown action",
|
|
8786
|
+
capability: router.capability,
|
|
8787
|
+
action,
|
|
8788
|
+
availableActions: visibleActions.map((a) => a.action)
|
|
8789
|
+
})
|
|
8790
|
+
}
|
|
8791
|
+
],
|
|
8792
|
+
isError: true
|
|
8793
|
+
};
|
|
8794
|
+
}
|
|
8795
|
+
const callResult = await this.executeRoutedTool({
|
|
8796
|
+
capability: router.capability,
|
|
8797
|
+
action: route.action,
|
|
8798
|
+
qualifiedToolName: route.qualifiedName,
|
|
8799
|
+
toolNameForCall: route.qualifiedName,
|
|
8800
|
+
args: actionArgs,
|
|
8801
|
+
...confirmationToken != null ? { confirmationToken } : {}
|
|
8802
|
+
});
|
|
8803
|
+
success = !callResult.isError;
|
|
8804
|
+
return callResult;
|
|
8805
|
+
} catch {
|
|
7950
8806
|
return {
|
|
7951
8807
|
content: [
|
|
7952
8808
|
{
|
|
7953
8809
|
type: "text",
|
|
7954
8810
|
text: JSON.stringify({
|
|
7955
|
-
error:
|
|
7956
|
-
blocked: true
|
|
8811
|
+
error: "Action execution failed"
|
|
7957
8812
|
})
|
|
7958
8813
|
}
|
|
7959
8814
|
],
|
|
7960
8815
|
isError: true
|
|
7961
8816
|
};
|
|
8817
|
+
} finally {
|
|
8818
|
+
const responseTime = Date.now() - startTime;
|
|
8819
|
+
this.statsCollector.endRequest(requestId, success, responseTime, router.capability, "capability");
|
|
7962
8820
|
}
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
confirmation_token: policyResult.confirmationToken,
|
|
7971
|
-
message: policyResult.reason
|
|
7972
|
-
})
|
|
7973
|
-
}
|
|
7974
|
-
],
|
|
7975
|
-
isError: false
|
|
7976
|
-
};
|
|
7977
|
-
}
|
|
7978
|
-
const qualifiedToolName = `${tool.serverKey}:${tool.name}`;
|
|
7979
|
-
this.guard.enforce({
|
|
7980
|
-
agent: this.safetyAgent,
|
|
7981
|
-
tool: qualifiedToolName,
|
|
7982
|
-
action: "call",
|
|
7983
|
-
params: args.arguments
|
|
7984
|
-
});
|
|
7985
|
-
const result = await tool_span(this.obsSink, {
|
|
7986
|
-
agent: this.safetyAgent,
|
|
7987
|
-
tool: qualifiedToolName,
|
|
7988
|
-
action: "call",
|
|
7989
|
-
playbook: this.guard.playbook,
|
|
7990
|
-
env: this.guard.agentEnv
|
|
7991
|
-
}, () => this.cataloger.callTool(args.tool_name, args.arguments));
|
|
7992
|
-
if (!result.isError && this.config.operations.selectionCache.enabled) {
|
|
7993
|
-
const toolKey = `${tool.serverKey}:${tool.name}`;
|
|
7994
|
-
this.selectionTracker.trackToolUsage(toolKey);
|
|
7995
|
-
if (this.selectionTracker.getSessionToolCount() >= 2) {
|
|
7996
|
-
this.selectionTracker.flushToStore(this.retriever.getIndexStore());
|
|
7997
|
-
}
|
|
7998
|
-
}
|
|
7999
|
-
success = !result.isError;
|
|
8000
|
-
return {
|
|
8001
|
-
content: result.content.map((c) => {
|
|
8002
|
-
if (typeof c === "object" && c !== null && "type" in c) {
|
|
8003
|
-
return c;
|
|
8004
|
-
}
|
|
8005
|
-
return {
|
|
8006
|
-
type: "text",
|
|
8007
|
-
text: JSON.stringify(c)
|
|
8008
|
-
};
|
|
8009
|
-
}),
|
|
8010
|
-
isError: result.isError
|
|
8011
|
-
};
|
|
8012
|
-
} catch (err) {
|
|
8013
|
-
const errorMessage2 = err instanceof Error ? err.message : String(err);
|
|
8014
|
-
return {
|
|
8015
|
-
content: [
|
|
8016
|
-
{
|
|
8017
|
-
type: "text",
|
|
8018
|
-
text: JSON.stringify({
|
|
8019
|
-
error: errorMessage2
|
|
8020
|
-
})
|
|
8021
|
-
}
|
|
8022
|
-
],
|
|
8023
|
-
isError: true
|
|
8024
|
-
};
|
|
8025
|
-
} finally {
|
|
8026
|
-
const responseTime = Date.now() - startTime;
|
|
8027
|
-
this.statsCollector.endRequest(requestId, success, responseTime, toolName, serverKey);
|
|
8028
|
-
}
|
|
8029
|
-
}));
|
|
8030
|
-
server.registerTool("clear_selection_cache", {
|
|
8031
|
-
description: "Clears all learned tool co-occurrence patterns. Use this to reset the selection cache if suggestions become stale or irrelevant.",
|
|
8032
|
-
inputSchema: {}
|
|
8033
|
-
}, async () => this.runTaskSpan("clear_selection_cache", async () => {
|
|
8034
|
-
const requestId = this.statsCollector.startRequest();
|
|
8035
|
-
const startTime = Date.now();
|
|
8036
|
-
let success = false;
|
|
8037
|
-
try {
|
|
8038
|
-
const countBefore = this.retriever.getIndexStore().getCooccurrenceCount();
|
|
8039
|
-
this.retriever.getIndexStore().clearCooccurrences();
|
|
8040
|
-
this.selectionTracker.reset();
|
|
8041
|
-
success = true;
|
|
8042
|
-
return {
|
|
8043
|
-
content: [
|
|
8044
|
-
{
|
|
8045
|
-
type: "text",
|
|
8046
|
-
text: JSON.stringify({
|
|
8047
|
-
message: "Selection cache cleared",
|
|
8048
|
-
patternsRemoved: countBefore
|
|
8049
|
-
})
|
|
8050
|
-
}
|
|
8051
|
-
]
|
|
8052
|
-
};
|
|
8053
|
-
} finally {
|
|
8054
|
-
const responseTime = Date.now() - startTime;
|
|
8055
|
-
this.statsCollector.endRequest(requestId, success, responseTime, "clear_selection_cache", "mcp-squared");
|
|
8821
|
+
}));
|
|
8822
|
+
}
|
|
8823
|
+
}
|
|
8824
|
+
normalizeToolResultContent(content) {
|
|
8825
|
+
return content.map((entry) => {
|
|
8826
|
+
if (typeof entry === "object" && entry !== null && "type" in entry) {
|
|
8827
|
+
return entry;
|
|
8056
8828
|
}
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
namespace.error = info.error;
|
|
8829
|
+
return {
|
|
8830
|
+
type: "text",
|
|
8831
|
+
text: JSON.stringify(entry)
|
|
8832
|
+
};
|
|
8833
|
+
});
|
|
8834
|
+
}
|
|
8835
|
+
async executeRoutedTool(args) {
|
|
8836
|
+
const policyResult = evaluatePolicy({
|
|
8837
|
+
capability: args.capability,
|
|
8838
|
+
action: args.action,
|
|
8839
|
+
confirmationToken: args.confirmationToken
|
|
8840
|
+
}, this.config);
|
|
8841
|
+
if (policyResult.decision === "block") {
|
|
8842
|
+
return {
|
|
8843
|
+
content: [
|
|
8844
|
+
{
|
|
8845
|
+
type: "text",
|
|
8846
|
+
text: JSON.stringify({
|
|
8847
|
+
error: "Action blocked by security policy",
|
|
8848
|
+
blocked: true
|
|
8849
|
+
})
|
|
8079
8850
|
}
|
|
8080
|
-
|
|
8081
|
-
|
|
8851
|
+
],
|
|
8852
|
+
isError: true
|
|
8853
|
+
};
|
|
8854
|
+
}
|
|
8855
|
+
if (policyResult.decision === "confirm") {
|
|
8856
|
+
return {
|
|
8857
|
+
content: [
|
|
8858
|
+
{
|
|
8859
|
+
type: "text",
|
|
8860
|
+
text: JSON.stringify({
|
|
8861
|
+
requires_confirmation: true,
|
|
8862
|
+
confirmation_token: policyResult.confirmationToken,
|
|
8863
|
+
message: "Action requires confirmation by security policy"
|
|
8864
|
+
})
|
|
8082
8865
|
}
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
]
|
|
8106
|
-
};
|
|
8107
|
-
} finally {
|
|
8108
|
-
const responseTime = Date.now() - startTime;
|
|
8109
|
-
this.statsCollector.endRequest(requestId, success, responseTime, "list_namespaces", "mcp-squared");
|
|
8110
|
-
}
|
|
8111
|
-
}));
|
|
8112
|
-
}
|
|
8113
|
-
formatToolsForDetailLevel(tools, level) {
|
|
8114
|
-
switch (level) {
|
|
8115
|
-
case "L0":
|
|
8116
|
-
return tools.map((t) => ({
|
|
8117
|
-
name: t.name,
|
|
8118
|
-
serverKey: t.serverKey,
|
|
8119
|
-
...t.requiresConfirmation && { requiresConfirmation: true }
|
|
8120
|
-
}));
|
|
8121
|
-
case "L2": {
|
|
8122
|
-
return tools.map((t) => {
|
|
8123
|
-
const { tool } = this.cataloger.findTool(`${t.serverKey}:${t.name}`);
|
|
8124
|
-
return {
|
|
8125
|
-
name: t.name,
|
|
8126
|
-
description: t.description,
|
|
8127
|
-
serverKey: t.serverKey,
|
|
8128
|
-
inputSchema: tool?.inputSchema ?? { type: "object" },
|
|
8129
|
-
...t.requiresConfirmation && { requiresConfirmation: true }
|
|
8130
|
-
};
|
|
8131
|
-
});
|
|
8866
|
+
],
|
|
8867
|
+
isError: false
|
|
8868
|
+
};
|
|
8869
|
+
}
|
|
8870
|
+
this.guard.enforce({
|
|
8871
|
+
agent: this.safetyAgent,
|
|
8872
|
+
tool: `${args.capability}:${args.action}`,
|
|
8873
|
+
action: "call",
|
|
8874
|
+
params: args.args
|
|
8875
|
+
});
|
|
8876
|
+
const result = await tool_span(this.obsSink, {
|
|
8877
|
+
agent: this.safetyAgent,
|
|
8878
|
+
tool: `${args.capability}:${args.action}`,
|
|
8879
|
+
action: "call",
|
|
8880
|
+
playbook: this.guard.playbook,
|
|
8881
|
+
env: this.guard.agentEnv
|
|
8882
|
+
}, () => this.cataloger.callTool(args.toolNameForCall, args.args));
|
|
8883
|
+
if (!result.isError && this.config.operations.selectionCache.enabled) {
|
|
8884
|
+
const toolKey = `${args.capability}:${args.action}`;
|
|
8885
|
+
this.selectionTracker.trackToolUsage(toolKey);
|
|
8886
|
+
if (this.selectionTracker.getSessionToolCount() >= 2) {
|
|
8887
|
+
this.selectionTracker.flushToStore(this.retriever.getIndexStore());
|
|
8132
8888
|
}
|
|
8133
|
-
default:
|
|
8134
|
-
return tools.map((t) => ({
|
|
8135
|
-
name: t.name,
|
|
8136
|
-
description: t.description,
|
|
8137
|
-
serverKey: t.serverKey,
|
|
8138
|
-
...t.requiresConfirmation && { requiresConfirmation: true }
|
|
8139
|
-
}));
|
|
8140
8889
|
}
|
|
8890
|
+
return {
|
|
8891
|
+
content: this.normalizeToolResultContent(result.content),
|
|
8892
|
+
isError: result.isError ?? false
|
|
8893
|
+
};
|
|
8141
8894
|
}
|
|
8142
8895
|
syncIndex() {
|
|
8143
8896
|
this.retriever.syncFromCataloger();
|
|
@@ -8161,8 +8914,39 @@ class McpSquaredServer {
|
|
|
8161
8914
|
getToolStats(limit = 100) {
|
|
8162
8915
|
return this.statsCollector.getToolStats(limit);
|
|
8163
8916
|
}
|
|
8917
|
+
async classifyNamespacesSemantic() {
|
|
8918
|
+
const generator = this.retriever.getEmbeddingGenerator();
|
|
8919
|
+
if (!generator) {
|
|
8920
|
+
console.error("[mcp\xB2] Hybrid inference: embeddings not available, falling back to heuristic.");
|
|
8921
|
+
return;
|
|
8922
|
+
}
|
|
8923
|
+
try {
|
|
8924
|
+
const { SemanticCapabilityClassifier: SemanticCapabilityClassifier2 } = await Promise.resolve().then(() => (init_semantic_classifier(), exports_semantic_classifier));
|
|
8925
|
+
const threshold = this.config.operations.dynamicToolSurface.semanticConfidenceThreshold;
|
|
8926
|
+
const classifier = new SemanticCapabilityClassifier2(generator, {
|
|
8927
|
+
confidenceThreshold: threshold
|
|
8928
|
+
});
|
|
8929
|
+
await classifier.initializeReferences();
|
|
8930
|
+
const status = this.cataloger.getStatus();
|
|
8931
|
+
const inventories = [...status.entries()].filter(([, info]) => info.status === "connected").map(([namespace]) => ({
|
|
8932
|
+
namespace,
|
|
8933
|
+
tools: this.cataloger.getToolsForServer(namespace)
|
|
8934
|
+
}));
|
|
8935
|
+
const result = await classifier.classifyBatch(inventories);
|
|
8936
|
+
this.computedCapabilityOverrides = result.overrides;
|
|
8937
|
+
const count = Object.keys(result.overrides).length;
|
|
8938
|
+
console.error(`[mcp\xB2] Hybrid inference: classified ${count}/${inventories.length} namespaces semantically (${Math.round(result.inferenceMs)}ms).`);
|
|
8939
|
+
} catch (err) {
|
|
8940
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8941
|
+
console.error(`[mcp\xB2] Hybrid inference: classification failed \u2014 ${message}. Falling back to heuristic.`);
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8164
8944
|
async start() {
|
|
8165
8945
|
await this.startCore();
|
|
8946
|
+
if (!this.baseToolsRegistered) {
|
|
8947
|
+
this.registerConfiguredToolSurface(this.mcpServer);
|
|
8948
|
+
this.baseToolsRegistered = true;
|
|
8949
|
+
}
|
|
8166
8950
|
this.transport = new StdioServerTransport2;
|
|
8167
8951
|
await this.mcpServer.connect(this.transport);
|
|
8168
8952
|
this.statsCollector.incrementActiveConnections();
|
|
@@ -8200,6 +8984,9 @@ class McpSquaredServer {
|
|
|
8200
8984
|
console.error(`[mcp\xB2] Embeddings: initialization failed \u2014 ${message}. Falling back to fast (FTS5) search.`);
|
|
8201
8985
|
}
|
|
8202
8986
|
}
|
|
8987
|
+
if (this.config.operations.dynamicToolSurface.inference === "hybrid") {
|
|
8988
|
+
await this.classifyNamespacesSemantic();
|
|
8989
|
+
}
|
|
8203
8990
|
this.statsCollector.updateIndexRefreshTime(Date.now());
|
|
8204
8991
|
this.indexRefreshManager.start();
|
|
8205
8992
|
await this.monitorServer.start();
|
|
@@ -8227,6 +9014,249 @@ class McpSquaredServer {
|
|
|
8227
9014
|
}
|
|
8228
9015
|
}
|
|
8229
9016
|
|
|
9017
|
+
// src/status/runner.ts
|
|
9018
|
+
init_inference();
|
|
9019
|
+
|
|
9020
|
+
// src/utils/context-stats.ts
|
|
9021
|
+
var CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
9022
|
+
function estimateTokens(text) {
|
|
9023
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
|
|
9024
|
+
}
|
|
9025
|
+
function reconstructCapabilityToolMetadata(router) {
|
|
9026
|
+
return {
|
|
9027
|
+
name: router.capability,
|
|
9028
|
+
title: capabilityTitle(router.capability),
|
|
9029
|
+
description: capabilitySummary(router.capability),
|
|
9030
|
+
annotations: {
|
|
9031
|
+
readOnlyHint: false,
|
|
9032
|
+
destructiveHint: false,
|
|
9033
|
+
openWorldHint: true
|
|
9034
|
+
},
|
|
9035
|
+
inputSchema: {
|
|
9036
|
+
type: "object",
|
|
9037
|
+
properties: {
|
|
9038
|
+
action: {
|
|
9039
|
+
type: "string",
|
|
9040
|
+
description: `Action ID for ${router.capability}. Use "__describe_actions" to inspect available actions and schemas.`
|
|
9041
|
+
},
|
|
9042
|
+
arguments: {
|
|
9043
|
+
type: "object",
|
|
9044
|
+
additionalProperties: {},
|
|
9045
|
+
default: {},
|
|
9046
|
+
description: "Arguments for the selected capability action"
|
|
9047
|
+
},
|
|
9048
|
+
confirmation_token: {
|
|
9049
|
+
type: "string",
|
|
9050
|
+
description: "Optional confirmation token for actions that require explicit confirmation"
|
|
9051
|
+
}
|
|
9052
|
+
},
|
|
9053
|
+
required: ["action"]
|
|
9054
|
+
}
|
|
9055
|
+
};
|
|
9056
|
+
}
|
|
9057
|
+
function computeContextStats(upstreamTools, routers) {
|
|
9058
|
+
const withoutTokens = upstreamTools.reduce((sum, tool) => {
|
|
9059
|
+
const serialized = JSON.stringify({
|
|
9060
|
+
name: tool.name,
|
|
9061
|
+
description: tool.description,
|
|
9062
|
+
inputSchema: tool.inputSchema
|
|
9063
|
+
});
|
|
9064
|
+
return sum + estimateTokens(serialized);
|
|
9065
|
+
}, 0);
|
|
9066
|
+
const activeRouters = routers.filter((r) => r.actions.length > 0);
|
|
9067
|
+
const withTokens = activeRouters.reduce((sum, router) => {
|
|
9068
|
+
const meta = reconstructCapabilityToolMetadata(router);
|
|
9069
|
+
return sum + estimateTokens(JSON.stringify(meta));
|
|
9070
|
+
}, 0);
|
|
9071
|
+
const saved = withoutTokens - withTokens;
|
|
9072
|
+
const percent = withoutTokens > 0 ? saved / withoutTokens * 100 : 0;
|
|
9073
|
+
return {
|
|
9074
|
+
withoutMcp2Tokens: withoutTokens,
|
|
9075
|
+
withMcp2Tokens: withTokens,
|
|
9076
|
+
savedTokens: saved,
|
|
9077
|
+
savedPercent: Math.round(percent * 10) / 10,
|
|
9078
|
+
upstreamToolCount: upstreamTools.length,
|
|
9079
|
+
capabilityToolCount: activeRouters.length
|
|
9080
|
+
};
|
|
9081
|
+
}
|
|
9082
|
+
|
|
9083
|
+
// src/status/runner.ts
|
|
9084
|
+
var GREEN = "\x1B[32m";
|
|
9085
|
+
var YELLOW = "\x1B[33m";
|
|
9086
|
+
var RED = "\x1B[31m";
|
|
9087
|
+
var DIM = "\x1B[90m";
|
|
9088
|
+
var BOLD = "\x1B[1m";
|
|
9089
|
+
var RESET = "\x1B[0m";
|
|
9090
|
+
async function collectStatus(config) {
|
|
9091
|
+
const cataloger = new Cataloger({ connectTimeoutMs: 15000 });
|
|
9092
|
+
const upstreams = [];
|
|
9093
|
+
try {
|
|
9094
|
+
await cataloger.connectAll(config);
|
|
9095
|
+
const status = cataloger.getStatus();
|
|
9096
|
+
for (const [name, serverConfig] of Object.entries(config.upstreams)) {
|
|
9097
|
+
if (!serverConfig.enabled) {
|
|
9098
|
+
upstreams.push({
|
|
9099
|
+
name,
|
|
9100
|
+
enabled: false,
|
|
9101
|
+
status: "disconnected",
|
|
9102
|
+
toolCount: 0
|
|
9103
|
+
});
|
|
9104
|
+
continue;
|
|
9105
|
+
}
|
|
9106
|
+
const connStatus = status.get(name);
|
|
9107
|
+
const connection = cataloger.getConnection(name);
|
|
9108
|
+
upstreams.push({
|
|
9109
|
+
name,
|
|
9110
|
+
enabled: true,
|
|
9111
|
+
status: connStatus?.status ?? "disconnected",
|
|
9112
|
+
error: connStatus?.error,
|
|
9113
|
+
toolCount: connection?.tools.length ?? 0,
|
|
9114
|
+
serverName: connection?.serverName,
|
|
9115
|
+
serverVersion: connection?.serverVersion
|
|
9116
|
+
});
|
|
9117
|
+
}
|
|
9118
|
+
const inventories = [...status.entries()].filter(([, info]) => info.status === "connected").map(([namespace]) => ({
|
|
9119
|
+
namespace,
|
|
9120
|
+
tools: cataloger.getToolsForServer(namespace)
|
|
9121
|
+
})).sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
9122
|
+
let routers = [];
|
|
9123
|
+
if (inventories.length > 0) {
|
|
9124
|
+
const overrides = config.operations.dynamicToolSurface.capabilityOverrides ?? {};
|
|
9125
|
+
const grouping = groupNamespacesByCapability(inventories, overrides);
|
|
9126
|
+
routers = buildCapabilityRouters(inventories, grouping);
|
|
9127
|
+
}
|
|
9128
|
+
const allUpstreamTools = inventories.flatMap((inv) => inv.tools);
|
|
9129
|
+
const contextStats = computeContextStats(allUpstreamTools, routers);
|
|
9130
|
+
return { upstreams, routers, contextStats };
|
|
9131
|
+
} finally {
|
|
9132
|
+
await cataloger.disconnectAll();
|
|
9133
|
+
}
|
|
9134
|
+
}
|
|
9135
|
+
function formatStatus(result, options) {
|
|
9136
|
+
const lines = [];
|
|
9137
|
+
lines.push("");
|
|
9138
|
+
lines.push(`${BOLD}MCP\xB2 Status Report${RESET}`);
|
|
9139
|
+
lines.push("");
|
|
9140
|
+
lines.push(`${DIM}\u2500\u2500 Upstream Servers \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${RESET}`);
|
|
9141
|
+
if (result.upstreams.length === 0) {
|
|
9142
|
+
lines.push(` ${DIM}(no upstreams configured)${RESET}`);
|
|
9143
|
+
}
|
|
9144
|
+
for (const upstream of result.upstreams) {
|
|
9145
|
+
if (!upstream.enabled) {
|
|
9146
|
+
lines.push(` ${DIM}\u2298 ${upstream.name.padEnd(24)} disabled${RESET}`);
|
|
9147
|
+
continue;
|
|
9148
|
+
}
|
|
9149
|
+
if (upstream.status === "connected") {
|
|
9150
|
+
const toolInfo = `(${upstream.toolCount} tool${upstream.toolCount !== 1 ? "s" : ""})`;
|
|
9151
|
+
const version = upstream.serverVersion ? ` ${DIM}v${upstream.serverVersion}${RESET}` : "";
|
|
9152
|
+
lines.push(` ${GREEN}\u2713${RESET} ${upstream.name.padEnd(24)} connected ${toolInfo}${version}`);
|
|
9153
|
+
} else if (upstream.status === "needs_auth") {
|
|
9154
|
+
const errorMsg = upstream.error ?? "Authentication required";
|
|
9155
|
+
lines.push(` ${YELLOW}\u26A0${RESET} ${upstream.name.padEnd(24)} needs auth ${DIM}${errorMsg}${RESET}`);
|
|
9156
|
+
} else if (upstream.status === "error") {
|
|
9157
|
+
const errorMsg = upstream.error ?? "Unknown error";
|
|
9158
|
+
lines.push(` ${RED}\u2717${RESET} ${upstream.name.padEnd(24)} error ${DIM}${errorMsg}${RESET}`);
|
|
9159
|
+
} else {
|
|
9160
|
+
lines.push(` ${DIM}? ${upstream.name.padEnd(24)} ${upstream.status}${RESET}`);
|
|
9161
|
+
}
|
|
9162
|
+
}
|
|
9163
|
+
lines.push("");
|
|
9164
|
+
lines.push(`${DIM}\u2500\u2500 Capability Routing \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${RESET}`);
|
|
9165
|
+
if (result.routers.length === 0) {
|
|
9166
|
+
lines.push(` ${DIM}(no capabilities routed \u2014 no connected upstreams)${RESET}`);
|
|
9167
|
+
}
|
|
9168
|
+
let totalActions = 0;
|
|
9169
|
+
for (const router of result.routers) {
|
|
9170
|
+
const count = router.actions.length;
|
|
9171
|
+
totalActions += count;
|
|
9172
|
+
const plural = count !== 1 ? "s" : "";
|
|
9173
|
+
lines.push(` ${BOLD}${router.capability}${RESET} ${DIM}(${count} action${plural})${RESET}`);
|
|
9174
|
+
if (count === 0) {
|
|
9175
|
+
lines.push(` ${DIM}(no actions)${RESET}`);
|
|
9176
|
+
}
|
|
9177
|
+
for (const action of router.actions) {
|
|
9178
|
+
const actionName = action.action.padEnd(28);
|
|
9179
|
+
const mapping = `${DIM}\u2192${RESET} ${action.qualifiedName}`;
|
|
9180
|
+
if (options.verbose) {
|
|
9181
|
+
const schemaKeys = Object.keys(action.inputSchema?.properties ?? {});
|
|
9182
|
+
const params = schemaKeys.length > 0 ? ` ${DIM}(${schemaKeys.join(", ")})${RESET}` : "";
|
|
9183
|
+
lines.push(` ${actionName} ${mapping}${params}`);
|
|
9184
|
+
} else {
|
|
9185
|
+
lines.push(` ${actionName} ${mapping}`);
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
lines.push("");
|
|
9189
|
+
}
|
|
9190
|
+
if (options.verbose && result.contextStats) {
|
|
9191
|
+
const cs = result.contextStats;
|
|
9192
|
+
if (cs.upstreamToolCount > 0) {
|
|
9193
|
+
lines.push(`${DIM}\u2500\u2500 Context Savings \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${RESET}`);
|
|
9194
|
+
const fmt = (n) => n.toLocaleString("en-US");
|
|
9195
|
+
const withoutLabel = `Without MCP\xB2:`;
|
|
9196
|
+
const withLabel = `With MCP\xB2:`;
|
|
9197
|
+
const savedLabel = `Saved:`;
|
|
9198
|
+
lines.push(` ${withoutLabel.padEnd(16)} ${fmt(cs.withoutMcp2Tokens).padStart(8)} tokens ${DIM}(${cs.upstreamToolCount} tool${cs.upstreamToolCount !== 1 ? "s" : ""})${RESET}`);
|
|
9199
|
+
lines.push(` ${withLabel.padEnd(16)} ${fmt(cs.withMcp2Tokens).padStart(8)} tokens ${DIM}(${cs.capabilityToolCount} tool${cs.capabilityToolCount !== 1 ? "s" : ""})${RESET}`);
|
|
9200
|
+
if (cs.savedTokens > 0) {
|
|
9201
|
+
lines.push(` ${DIM}${"\u2500".repeat(40)}${RESET}`);
|
|
9202
|
+
lines.push(` ${GREEN}${savedLabel.padEnd(16)} ${fmt(cs.savedTokens).padStart(8)} tokens (${cs.savedPercent}%)${RESET}`);
|
|
9203
|
+
}
|
|
9204
|
+
lines.push("");
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
const connected = result.upstreams.filter((u) => u.status === "connected").length;
|
|
9208
|
+
const needsAuth = result.upstreams.filter((u) => u.status === "needs_auth").length;
|
|
9209
|
+
const errors = result.upstreams.filter((u) => u.status === "error").length;
|
|
9210
|
+
const disabled = result.upstreams.filter((u) => !u.enabled).length;
|
|
9211
|
+
const capCount = result.routers.filter((r) => r.actions.length > 0).length;
|
|
9212
|
+
const parts = [];
|
|
9213
|
+
if (connected > 0)
|
|
9214
|
+
parts.push(`${GREEN}${connected} connected${RESET}`);
|
|
9215
|
+
if (needsAuth > 0)
|
|
9216
|
+
parts.push(`${YELLOW}${needsAuth} needs auth${RESET}`);
|
|
9217
|
+
if (errors > 0)
|
|
9218
|
+
parts.push(`${RED}${errors} error${RESET}`);
|
|
9219
|
+
if (disabled > 0)
|
|
9220
|
+
parts.push(`${DIM}${disabled} disabled${RESET}`);
|
|
9221
|
+
const actionSummary = totalActions > 0 ? `${totalActions} action${totalActions !== 1 ? "s" : ""} across ${capCount} capabilit${capCount !== 1 ? "ies" : "y"}` : "no actions";
|
|
9222
|
+
lines.push(`${DIM}Summary:${RESET} ${parts.join(", ")} | ${actionSummary}`);
|
|
9223
|
+
lines.push("");
|
|
9224
|
+
return lines.join(`
|
|
9225
|
+
`);
|
|
9226
|
+
}
|
|
9227
|
+
async function runStatus(options) {
|
|
9228
|
+
let config;
|
|
9229
|
+
let configPath;
|
|
9230
|
+
try {
|
|
9231
|
+
const loaded = await loadConfig();
|
|
9232
|
+
config = loaded.config;
|
|
9233
|
+
configPath = loaded.path;
|
|
9234
|
+
} catch (err) {
|
|
9235
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9236
|
+
console.error(`Error loading configuration: ${message}`);
|
|
9237
|
+
console.error("Run 'mcp-squared config' to create or fix your configuration.");
|
|
9238
|
+
process.exit(1);
|
|
9239
|
+
}
|
|
9240
|
+
const upstreamEntries = Object.entries(config.upstreams);
|
|
9241
|
+
if (upstreamEntries.length === 0) {
|
|
9242
|
+
console.error("Error: No upstreams configured. Run 'mcp-squared config' to add one.");
|
|
9243
|
+
process.exit(1);
|
|
9244
|
+
}
|
|
9245
|
+
const validationIssues = validateConfig(config);
|
|
9246
|
+
if (validationIssues.length > 0) {
|
|
9247
|
+
console.error(formatValidationIssues(validationIssues));
|
|
9248
|
+
console.error("");
|
|
9249
|
+
}
|
|
9250
|
+
if (options.verbose && configPath) {
|
|
9251
|
+
console.log(`${DIM}Config: ${configPath}${RESET}`);
|
|
9252
|
+
}
|
|
9253
|
+
console.log("Connecting to upstream servers...");
|
|
9254
|
+
const result = await collectStatus(config);
|
|
9255
|
+
result.configPath = configPath;
|
|
9256
|
+
const output = formatStatus(result, options);
|
|
9257
|
+
console.log(output);
|
|
9258
|
+
}
|
|
9259
|
+
|
|
8230
9260
|
// src/index.ts
|
|
8231
9261
|
function logSecurityProfile(config) {
|
|
8232
9262
|
const { allow, confirm } = config.security.tools;
|
|
@@ -8945,9 +9975,15 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
8945
9975
|
await runInit2(args.init);
|
|
8946
9976
|
break;
|
|
8947
9977
|
}
|
|
9978
|
+
case "migrate":
|
|
9979
|
+
await runMigrate(args.migrate);
|
|
9980
|
+
break;
|
|
8948
9981
|
case "monitor":
|
|
8949
9982
|
await runMonitor(args.monitor);
|
|
8950
9983
|
break;
|
|
9984
|
+
case "status":
|
|
9985
|
+
await runStatus({ verbose: args.testVerbose });
|
|
9986
|
+
break;
|
|
8951
9987
|
case "daemon":
|
|
8952
9988
|
await runDaemon(args.daemon);
|
|
8953
9989
|
break;
|