mcp-squared 0.4.0 → 0.7.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 +61 -21
- package/dist/index.js +1629 -467
- package/dist/tui/config.js +390 -23
- package/package.json +9 -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 = {};
|
|
@@ -359,6 +741,15 @@ defaultDetailLevel = "L1"
|
|
|
359
741
|
# Adjust or clear if your stack uses different code indexers.
|
|
360
742
|
codeSearch = ["auggie", "ctxdb"]
|
|
361
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
|
+
|
|
362
753
|
[operations.embeddings]
|
|
363
754
|
# Enable to use semantic or hybrid search modes.
|
|
364
755
|
# Requires onnxruntime shared library on the system.
|
|
@@ -525,6 +916,9 @@ function parseArgs(args) {
|
|
|
525
916
|
case "monitor":
|
|
526
917
|
result.mode = "monitor";
|
|
527
918
|
break;
|
|
919
|
+
case "status":
|
|
920
|
+
result.mode = "status";
|
|
921
|
+
break;
|
|
528
922
|
case "daemon":
|
|
529
923
|
result.mode = "daemon";
|
|
530
924
|
break;
|
|
@@ -699,6 +1093,7 @@ Usage:
|
|
|
699
1093
|
mcp-squared init [options] Generate a starter config file with security profile
|
|
700
1094
|
mcp-squared migrate [options] Apply config migrations to existing config
|
|
701
1095
|
mcp-squared install [options] Install MCP\xB2 into other MCP clients
|
|
1096
|
+
mcp-squared status [options] Show upstream server status and capability routing
|
|
702
1097
|
mcp-squared monitor [options] Launch server monitor TUI
|
|
703
1098
|
mcp-squared daemon [options] Start shared MCP\xB2 daemon
|
|
704
1099
|
mcp-squared proxy [options] Start stdio proxy (connects to daemon)
|
|
@@ -713,6 +1108,7 @@ Commands:
|
|
|
713
1108
|
init Generate a starter config with security profile
|
|
714
1109
|
migrate Apply one-time config migrations to existing config
|
|
715
1110
|
install Install MCP\xB2 as a server in other MCP clients
|
|
1111
|
+
status Show upstream status and capability routing
|
|
716
1112
|
monitor Launch server monitor TUI
|
|
717
1113
|
daemon Start shared daemon for multiple clients
|
|
718
1114
|
proxy Start stdio proxy for daemon
|
|
@@ -752,6 +1148,9 @@ Install Options:
|
|
|
752
1148
|
--dry-run Preview changes without writing
|
|
753
1149
|
--no-interactive Disable interactive prompts
|
|
754
1150
|
|
|
1151
|
+
Status Options:
|
|
1152
|
+
--verbose, -V Show schema parameters and extra detail
|
|
1153
|
+
|
|
755
1154
|
Monitor Options:
|
|
756
1155
|
(daemon-first; attaches to shared daemon monitor by default)
|
|
757
1156
|
--refresh-interval=<ms> Auto-refresh interval in milliseconds (default: 2000)
|
|
@@ -771,6 +1170,8 @@ Supported Tools:
|
|
|
771
1170
|
${VALID_TOOL_IDS.join(", ")}
|
|
772
1171
|
|
|
773
1172
|
Examples:
|
|
1173
|
+
mcp-squared status Show upstream status and capability routing table
|
|
1174
|
+
mcp-squared status --verbose Show status with schema parameters
|
|
774
1175
|
mcp-squared test github Test connection to 'github' upstream
|
|
775
1176
|
mcp-squared test Test all configured upstreams
|
|
776
1177
|
mcp-squared auth vercel-mcp Authenticate with 'vercel-mcp' upstream (OAuth)
|
|
@@ -1039,6 +1440,7 @@ import { parse as parseToml } from "smol-toml";
|
|
|
1039
1440
|
import { ZodError } from "zod";
|
|
1040
1441
|
|
|
1041
1442
|
// src/config/schema.ts
|
|
1443
|
+
init_inference();
|
|
1042
1444
|
import { z } from "zod";
|
|
1043
1445
|
var LATEST_SCHEMA_VERSION = 1;
|
|
1044
1446
|
var LogLevelSchema = z.enum([
|
|
@@ -1095,6 +1497,18 @@ var SecuritySchema = z.object({
|
|
|
1095
1497
|
});
|
|
1096
1498
|
var SearchModeSchema = z.enum(["fast", "semantic", "hybrid"]);
|
|
1097
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
|
+
});
|
|
1098
1512
|
var PreferredNamespacesByIntentSchema = z.object({
|
|
1099
1513
|
codeSearch: z.array(z.string().min(1)).default([])
|
|
1100
1514
|
});
|
|
@@ -1116,6 +1530,14 @@ var LoggingSchema = z.object({
|
|
|
1116
1530
|
var EmbeddingsSchema = z.object({
|
|
1117
1531
|
enabled: z.boolean().default(false)
|
|
1118
1532
|
});
|
|
1533
|
+
var ResponseResourceSchema = z.object({
|
|
1534
|
+
enabled: z.boolean().default(false),
|
|
1535
|
+
thresholdBytes: z.number().int().min(1024).default(51200),
|
|
1536
|
+
maxInlineLines: z.number().int().min(1).default(20),
|
|
1537
|
+
maxResources: z.number().int().min(1).default(100),
|
|
1538
|
+
ttlMs: z.number().int().min(0).default(600000)
|
|
1539
|
+
});
|
|
1540
|
+
var DEFAULT_RESPONSE_RESOURCE_CONFIG = ResponseResourceSchema.parse({});
|
|
1119
1541
|
var SelectionCacheSchema = z.object({
|
|
1120
1542
|
enabled: z.boolean().default(true),
|
|
1121
1543
|
minCooccurrenceThreshold: z.number().int().min(1).default(2),
|
|
@@ -1132,10 +1554,17 @@ var OperationsSchema = z.object({
|
|
|
1132
1554
|
index: IndexSchema.default({ refreshIntervalMs: 30000 }),
|
|
1133
1555
|
logging: LoggingSchema.default({ level: "info" }),
|
|
1134
1556
|
embeddings: EmbeddingsSchema.default({ enabled: false }),
|
|
1557
|
+
responseResource: ResponseResourceSchema.default(DEFAULT_RESPONSE_RESOURCE_CONFIG),
|
|
1135
1558
|
selectionCache: SelectionCacheSchema.default({
|
|
1136
1559
|
enabled: true,
|
|
1137
1560
|
minCooccurrenceThreshold: 2,
|
|
1138
1561
|
maxBundleSuggestions: 3
|
|
1562
|
+
}),
|
|
1563
|
+
dynamicToolSurface: DynamicToolSurfaceSchema.default({
|
|
1564
|
+
inference: "heuristic_with_overrides",
|
|
1565
|
+
refresh: "on_connect",
|
|
1566
|
+
capabilityOverrides: {},
|
|
1567
|
+
semanticConfidenceThreshold: 0.45
|
|
1139
1568
|
})
|
|
1140
1569
|
}).default({
|
|
1141
1570
|
findTools: {
|
|
@@ -1148,10 +1577,17 @@ var OperationsSchema = z.object({
|
|
|
1148
1577
|
index: { refreshIntervalMs: 30000 },
|
|
1149
1578
|
logging: { level: "info" },
|
|
1150
1579
|
embeddings: { enabled: false },
|
|
1580
|
+
responseResource: DEFAULT_RESPONSE_RESOURCE_CONFIG,
|
|
1151
1581
|
selectionCache: {
|
|
1152
1582
|
enabled: true,
|
|
1153
1583
|
minCooccurrenceThreshold: 2,
|
|
1154
1584
|
maxBundleSuggestions: 3
|
|
1585
|
+
},
|
|
1586
|
+
dynamicToolSurface: {
|
|
1587
|
+
inference: "heuristic_with_overrides",
|
|
1588
|
+
refresh: "on_connect",
|
|
1589
|
+
capabilityOverrides: {},
|
|
1590
|
+
semanticConfidenceThreshold: 0.45
|
|
1155
1591
|
}
|
|
1156
1592
|
});
|
|
1157
1593
|
var ConfigSchema = z.object({
|
|
@@ -1205,6 +1641,34 @@ function migrateV0ToV1(config) {
|
|
|
1205
1641
|
|
|
1206
1642
|
// src/config/load.ts
|
|
1207
1643
|
init_paths();
|
|
1644
|
+
function isRecord(value) {
|
|
1645
|
+
return typeof value === "object" && value !== null;
|
|
1646
|
+
}
|
|
1647
|
+
function getDeprecatedDynamicToolSurfaceKeys(raw) {
|
|
1648
|
+
const operations = raw["operations"];
|
|
1649
|
+
if (!isRecord(operations)) {
|
|
1650
|
+
return [];
|
|
1651
|
+
}
|
|
1652
|
+
const dynamicToolSurface = operations["dynamicToolSurface"];
|
|
1653
|
+
if (!isRecord(dynamicToolSurface)) {
|
|
1654
|
+
return [];
|
|
1655
|
+
}
|
|
1656
|
+
const deprecated = [];
|
|
1657
|
+
if ("mode" in dynamicToolSurface) {
|
|
1658
|
+
deprecated.push("operations.dynamicToolSurface.mode");
|
|
1659
|
+
}
|
|
1660
|
+
if ("naming" in dynamicToolSurface) {
|
|
1661
|
+
deprecated.push("operations.dynamicToolSurface.naming");
|
|
1662
|
+
}
|
|
1663
|
+
return deprecated;
|
|
1664
|
+
}
|
|
1665
|
+
function warnDeprecatedDynamicToolSurfaceKeys(filePath, keys) {
|
|
1666
|
+
if (keys.length === 0) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
console.warn(`[mcp\xB2] Deprecated config keys in ${filePath}: ${keys.join(", ")}. Run 'mcp-squared migrate' to clean up legacy settings.`);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1208
1672
|
class ConfigError extends Error {
|
|
1209
1673
|
cause;
|
|
1210
1674
|
constructor(message, cause) {
|
|
@@ -1272,6 +1736,7 @@ async function loadConfigFromPath(filePath, source) {
|
|
|
1272
1736
|
} catch (err) {
|
|
1273
1737
|
throw new ConfigParseError(filePath, err);
|
|
1274
1738
|
}
|
|
1739
|
+
warnDeprecatedDynamicToolSurfaceKeys(filePath, getDeprecatedDynamicToolSurfaceKeys(rawConfig));
|
|
1275
1740
|
const migrated = migrateConfig(rawConfig);
|
|
1276
1741
|
let config;
|
|
1277
1742
|
try {
|
|
@@ -4305,33 +4770,109 @@ Installation cancelled.`);
|
|
|
4305
4770
|
}
|
|
4306
4771
|
|
|
4307
4772
|
// src/migrate/runner.ts
|
|
4773
|
+
init_inference();
|
|
4308
4774
|
import { readFileSync as readFileSync5 } from "fs";
|
|
4309
4775
|
import { parse as parseToml4 } from "smol-toml";
|
|
4310
4776
|
var DEFAULT_CODE_SEARCH_NAMESPACES = ["auggie", "ctxdb"];
|
|
4311
|
-
|
|
4777
|
+
var RESERVED_DESCRIBE_ACTION = "__describe_actions";
|
|
4778
|
+
function isRecord2(value) {
|
|
4312
4779
|
return typeof value === "object" && value !== null;
|
|
4313
4780
|
}
|
|
4314
4781
|
function hasOwn(obj, key) {
|
|
4315
4782
|
return Object.hasOwn(obj, key);
|
|
4316
4783
|
}
|
|
4784
|
+
function toActionToken(value) {
|
|
4785
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
4786
|
+
const token = normalized.length > 0 ? normalized : "tool";
|
|
4787
|
+
const reservedNormalized = RESERVED_DESCRIBE_ACTION.replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
4788
|
+
if (token === reservedNormalized) {
|
|
4789
|
+
return `${RESERVED_DESCRIBE_ACTION}__tool`;
|
|
4790
|
+
}
|
|
4791
|
+
return token;
|
|
4792
|
+
}
|
|
4793
|
+
function isCapabilityId(value) {
|
|
4794
|
+
return CAPABILITY_IDS.includes(value);
|
|
4795
|
+
}
|
|
4796
|
+
function hasLegacyDynamicToolSurfaceKeys(rawConfig) {
|
|
4797
|
+
if (!isRecord2(rawConfig)) {
|
|
4798
|
+
return false;
|
|
4799
|
+
}
|
|
4800
|
+
const operations = rawConfig["operations"];
|
|
4801
|
+
if (!isRecord2(operations)) {
|
|
4802
|
+
return false;
|
|
4803
|
+
}
|
|
4804
|
+
const dynamicToolSurface = operations["dynamicToolSurface"];
|
|
4805
|
+
if (!isRecord2(dynamicToolSurface)) {
|
|
4806
|
+
return false;
|
|
4807
|
+
}
|
|
4808
|
+
return hasOwn(dynamicToolSurface, "mode") || hasOwn(dynamicToolSurface, "naming");
|
|
4809
|
+
}
|
|
4317
4810
|
function isCodeSearchExplicitlyConfigured(rawConfig) {
|
|
4318
|
-
if (!
|
|
4811
|
+
if (!isRecord2(rawConfig)) {
|
|
4319
4812
|
return false;
|
|
4320
4813
|
}
|
|
4321
4814
|
const operations = rawConfig["operations"];
|
|
4322
|
-
if (!
|
|
4815
|
+
if (!isRecord2(operations)) {
|
|
4323
4816
|
return false;
|
|
4324
4817
|
}
|
|
4325
4818
|
const findTools = operations["findTools"];
|
|
4326
|
-
if (!
|
|
4819
|
+
if (!isRecord2(findTools)) {
|
|
4327
4820
|
return false;
|
|
4328
4821
|
}
|
|
4329
4822
|
const preferredNamespacesByIntent = findTools["preferredNamespacesByIntent"];
|
|
4330
|
-
if (!
|
|
4823
|
+
if (!isRecord2(preferredNamespacesByIntent)) {
|
|
4331
4824
|
return false;
|
|
4332
4825
|
}
|
|
4333
4826
|
return hasOwn(preferredNamespacesByIntent, "codeSearch");
|
|
4334
4827
|
}
|
|
4828
|
+
function dedupePreserveOrder(values) {
|
|
4829
|
+
const seen = new Set;
|
|
4830
|
+
const result = [];
|
|
4831
|
+
for (const value of values) {
|
|
4832
|
+
if (seen.has(value)) {
|
|
4833
|
+
continue;
|
|
4834
|
+
}
|
|
4835
|
+
seen.add(value);
|
|
4836
|
+
result.push(value);
|
|
4837
|
+
}
|
|
4838
|
+
return result;
|
|
4839
|
+
}
|
|
4840
|
+
function translateSecurityPattern(pattern, config) {
|
|
4841
|
+
const [scopeRaw, actionRaw] = pattern.split(":", 2);
|
|
4842
|
+
if (!scopeRaw || !actionRaw) {
|
|
4843
|
+
return {
|
|
4844
|
+
original: pattern,
|
|
4845
|
+
translated: pattern,
|
|
4846
|
+
changed: false,
|
|
4847
|
+
unresolvedReason: "invalid pattern format"
|
|
4848
|
+
};
|
|
4849
|
+
}
|
|
4850
|
+
const translatedAction = actionRaw === "*" ? "*" : toActionToken(actionRaw);
|
|
4851
|
+
if (scopeRaw === "*") {
|
|
4852
|
+
const translated2 = `*:${translatedAction}`;
|
|
4853
|
+
return {
|
|
4854
|
+
original: pattern,
|
|
4855
|
+
translated: translated2,
|
|
4856
|
+
changed: translated2 !== pattern
|
|
4857
|
+
};
|
|
4858
|
+
}
|
|
4859
|
+
if (isCapabilityId(scopeRaw)) {
|
|
4860
|
+
const translated2 = `${scopeRaw}:${translatedAction}`;
|
|
4861
|
+
return {
|
|
4862
|
+
original: pattern,
|
|
4863
|
+
translated: translated2,
|
|
4864
|
+
changed: translated2 !== pattern
|
|
4865
|
+
};
|
|
4866
|
+
}
|
|
4867
|
+
const override = config.operations.dynamicToolSurface.capabilityOverrides[scopeRaw];
|
|
4868
|
+
const capability = override ?? inferNamespaceCapability(scopeRaw, [], {});
|
|
4869
|
+
const translated = `${capability}:${translatedAction}`;
|
|
4870
|
+
return {
|
|
4871
|
+
original: pattern,
|
|
4872
|
+
translated,
|
|
4873
|
+
changed: translated !== pattern
|
|
4874
|
+
};
|
|
4875
|
+
}
|
|
4335
4876
|
function applyCodeSearchPreferenceMigration(config, options) {
|
|
4336
4877
|
if (options.codeSearchExplicitlyConfigured) {
|
|
4337
4878
|
return { config, changed: false };
|
|
@@ -4353,6 +4894,53 @@ function applyCodeSearchPreferenceMigration(config, options) {
|
|
|
4353
4894
|
}
|
|
4354
4895
|
};
|
|
4355
4896
|
}
|
|
4897
|
+
function applySecurityPatternMigration(config) {
|
|
4898
|
+
const report = {
|
|
4899
|
+
translated: [],
|
|
4900
|
+
unresolved: []
|
|
4901
|
+
};
|
|
4902
|
+
const migrateList = (patterns) => {
|
|
4903
|
+
const next = [];
|
|
4904
|
+
for (const pattern of patterns) {
|
|
4905
|
+
const translation = translateSecurityPattern(pattern, config);
|
|
4906
|
+
next.push(translation.translated);
|
|
4907
|
+
if (translation.unresolvedReason) {
|
|
4908
|
+
report.unresolved.push({
|
|
4909
|
+
pattern: translation.original,
|
|
4910
|
+
reason: translation.unresolvedReason
|
|
4911
|
+
});
|
|
4912
|
+
} else if (translation.changed) {
|
|
4913
|
+
report.translated.push({
|
|
4914
|
+
from: translation.original,
|
|
4915
|
+
to: translation.translated
|
|
4916
|
+
});
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
return dedupePreserveOrder(next);
|
|
4920
|
+
};
|
|
4921
|
+
const allow = migrateList(config.security.tools.allow);
|
|
4922
|
+
const block = migrateList(config.security.tools.block);
|
|
4923
|
+
const confirm = migrateList(config.security.tools.confirm);
|
|
4924
|
+
const changed = allow.join("|") !== config.security.tools.allow.join("|") || block.join("|") !== config.security.tools.block.join("|") || confirm.join("|") !== config.security.tools.confirm.join("|");
|
|
4925
|
+
if (!changed && report.unresolved.length === 0) {
|
|
4926
|
+
return { config, changed: false, report };
|
|
4927
|
+
}
|
|
4928
|
+
return {
|
|
4929
|
+
changed,
|
|
4930
|
+
report,
|
|
4931
|
+
config: {
|
|
4932
|
+
...config,
|
|
4933
|
+
security: {
|
|
4934
|
+
...config.security,
|
|
4935
|
+
tools: {
|
|
4936
|
+
allow,
|
|
4937
|
+
block,
|
|
4938
|
+
confirm
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
};
|
|
4943
|
+
}
|
|
4356
4944
|
async function runMigrate(args) {
|
|
4357
4945
|
const discovered = discoverConfigPath();
|
|
4358
4946
|
if (!discovered) {
|
|
@@ -4362,21 +4950,50 @@ async function runMigrate(args) {
|
|
|
4362
4950
|
const loaded = await loadConfigFromPath(discovered.path, discovered.source);
|
|
4363
4951
|
const rawConfig = parseToml4(readFileSync5(discovered.path, "utf-8"));
|
|
4364
4952
|
const codeSearchExplicitlyConfigured = isCodeSearchExplicitlyConfigured(rawConfig);
|
|
4365
|
-
const
|
|
4953
|
+
const hasLegacySurfaceKeys = hasLegacyDynamicToolSurfaceKeys(rawConfig);
|
|
4954
|
+
const codeSearchMigration = applyCodeSearchPreferenceMigration(loaded.config, {
|
|
4366
4955
|
codeSearchExplicitlyConfigured
|
|
4367
4956
|
});
|
|
4368
|
-
|
|
4369
|
-
|
|
4957
|
+
const securityMigration = applySecurityPatternMigration(codeSearchMigration.config);
|
|
4958
|
+
const changed = codeSearchMigration.changed || securityMigration.changed || hasLegacySurfaceKeys;
|
|
4959
|
+
if (!changed && securityMigration.report.unresolved.length === 0) {
|
|
4960
|
+
console.log(`No migration needed for ${discovered.path}`);
|
|
4370
4961
|
return;
|
|
4371
4962
|
}
|
|
4372
4963
|
if (args.dryRun) {
|
|
4373
4964
|
console.log(`[dry-run] Would update ${discovered.path}`);
|
|
4374
|
-
|
|
4965
|
+
if (codeSearchMigration.changed) {
|
|
4966
|
+
console.log('[dry-run] Set operations.findTools.preferredNamespacesByIntent.codeSearch = ["auggie", "ctxdb"]');
|
|
4967
|
+
}
|
|
4968
|
+
if (securityMigration.report.translated.length > 0) {
|
|
4969
|
+
for (const item of securityMigration.report.translated) {
|
|
4970
|
+
console.log(`[dry-run] Translate security pattern: ${item.from} -> ${item.to}`);
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
if (hasLegacySurfaceKeys) {
|
|
4974
|
+
console.log("[dry-run] Remove deprecated keys: operations.dynamicToolSurface.mode, operations.dynamicToolSurface.naming");
|
|
4975
|
+
}
|
|
4976
|
+
if (securityMigration.report.unresolved.length > 0) {
|
|
4977
|
+
for (const unresolved of securityMigration.report.unresolved) {
|
|
4978
|
+
console.warn(`[dry-run] Unresolved security pattern '${unresolved.pattern}': ${unresolved.reason}`);
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4375
4981
|
return;
|
|
4376
4982
|
}
|
|
4377
|
-
await saveConfig(discovered.path,
|
|
4983
|
+
await saveConfig(discovered.path, securityMigration.config);
|
|
4378
4984
|
console.log(`Updated ${discovered.path}`);
|
|
4379
|
-
|
|
4985
|
+
if (codeSearchMigration.changed) {
|
|
4986
|
+
console.log('Set operations.findTools.preferredNamespacesByIntent.codeSearch = ["auggie", "ctxdb"]');
|
|
4987
|
+
}
|
|
4988
|
+
for (const item of securityMigration.report.translated) {
|
|
4989
|
+
console.log(`Translated security pattern: ${item.from} -> ${item.to}`);
|
|
4990
|
+
}
|
|
4991
|
+
if (hasLegacySurfaceKeys) {
|
|
4992
|
+
console.log("Removed deprecated keys: operations.dynamicToolSurface.mode, operations.dynamicToolSurface.naming");
|
|
4993
|
+
}
|
|
4994
|
+
for (const unresolved of securityMigration.report.unresolved) {
|
|
4995
|
+
console.warn(`Unresolved security pattern '${unresolved.pattern}': ${unresolved.reason}. Please review manually.`);
|
|
4996
|
+
}
|
|
4380
4997
|
}
|
|
4381
4998
|
|
|
4382
4999
|
// src/oauth/browser.ts
|
|
@@ -5878,6 +6495,159 @@ class SelectionTracker {
|
|
|
5878
6495
|
this.sessionTools.clear();
|
|
5879
6496
|
}
|
|
5880
6497
|
}
|
|
6498
|
+
// src/server/index.ts
|
|
6499
|
+
init_inference();
|
|
6500
|
+
|
|
6501
|
+
// src/capabilities/routing.ts
|
|
6502
|
+
var DESCRIBE_ACTION = "__describe_actions";
|
|
6503
|
+
function buildCanonicalRouteId(capability, instanceKey, toolName) {
|
|
6504
|
+
return `${capability}:${instanceKey}:${toolName}`;
|
|
6505
|
+
}
|
|
6506
|
+
function buildInstanceActionBase(baseAction, instanceToken) {
|
|
6507
|
+
return `${baseAction}__${instanceToken}`;
|
|
6508
|
+
}
|
|
6509
|
+
function allocateUniqueActionId(emittedActions, actionBase, index) {
|
|
6510
|
+
let candidate = index === 0 ? actionBase : `${actionBase}__${index + 1}`;
|
|
6511
|
+
const suffixMatch = actionBase.match(/^(.*)__(\d+)$/);
|
|
6512
|
+
const dedupeBase = suffixMatch?.[1] ?? actionBase;
|
|
6513
|
+
let dedupe = suffixMatch?.[2] ? Number.parseInt(suffixMatch[2], 10) + 1 : index + 2;
|
|
6514
|
+
while (emittedActions.has(candidate)) {
|
|
6515
|
+
candidate = `${dedupeBase}__${dedupe}`;
|
|
6516
|
+
dedupe += 1;
|
|
6517
|
+
}
|
|
6518
|
+
emittedActions.add(candidate);
|
|
6519
|
+
return candidate;
|
|
6520
|
+
}
|
|
6521
|
+
function resolveInstanceTokens(records) {
|
|
6522
|
+
const byToken = new Map;
|
|
6523
|
+
for (const record of records) {
|
|
6524
|
+
const instanceKey = record.instanceKey ?? record.serverKey;
|
|
6525
|
+
const token = toActionToken2(instanceKey);
|
|
6526
|
+
const existing = byToken.get(token) ?? [];
|
|
6527
|
+
if (!existing.includes(instanceKey)) {
|
|
6528
|
+
existing.push(instanceKey);
|
|
6529
|
+
existing.sort((a, b) => a.localeCompare(b));
|
|
6530
|
+
byToken.set(token, existing);
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
const resolved = new Map;
|
|
6534
|
+
for (const [token, instanceKeys] of [...byToken.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
6535
|
+
instanceKeys.forEach((instanceKey, index) => {
|
|
6536
|
+
resolved.set(instanceKey, index === 0 ? token : `${token}__${index + 1}`);
|
|
6537
|
+
});
|
|
6538
|
+
}
|
|
6539
|
+
return resolved;
|
|
6540
|
+
}
|
|
6541
|
+
function toActionToken2(value) {
|
|
6542
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
6543
|
+
return normalized.length > 0 ? normalized : "tool";
|
|
6544
|
+
}
|
|
6545
|
+
function buildCapabilityRouters(inventories, grouping, summarize) {
|
|
6546
|
+
if (inventories.length === 0) {
|
|
6547
|
+
return [];
|
|
6548
|
+
}
|
|
6549
|
+
const defaultSummarize = (desc, cap) => {
|
|
6550
|
+
if (desc) {
|
|
6551
|
+
const firstLine = desc.split(`
|
|
6552
|
+
`)[0]?.trim();
|
|
6553
|
+
if (firstLine && firstLine.length > 0) {
|
|
6554
|
+
return firstLine.length > 120 ? `${firstLine.slice(0, 117)}...` : firstLine;
|
|
6555
|
+
}
|
|
6556
|
+
}
|
|
6557
|
+
const title = cap.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
6558
|
+
return `Execute ${title} action`;
|
|
6559
|
+
};
|
|
6560
|
+
const resolveSummary = summarize ?? defaultSummarize;
|
|
6561
|
+
const candidates = [];
|
|
6562
|
+
const reservedNormalized = toActionToken2(DESCRIBE_ACTION);
|
|
6563
|
+
for (const inventory of inventories) {
|
|
6564
|
+
const capability = grouping.byNamespace[inventory.namespace] ?? "general";
|
|
6565
|
+
const instanceKey = inventory.namespace;
|
|
6566
|
+
const instanceTitle = inventory.title ?? inventory.namespace;
|
|
6567
|
+
const sortedTools = [...inventory.tools].sort((a, b) => a.name.localeCompare(b.name));
|
|
6568
|
+
for (const tool of sortedTools) {
|
|
6569
|
+
let baseAction = toActionToken2(tool.name);
|
|
6570
|
+
if (baseAction === DESCRIBE_ACTION || baseAction === reservedNormalized) {
|
|
6571
|
+
baseAction = `${DESCRIBE_ACTION}__tool`;
|
|
6572
|
+
}
|
|
6573
|
+
candidates.push({
|
|
6574
|
+
capability,
|
|
6575
|
+
action: baseAction,
|
|
6576
|
+
baseAction,
|
|
6577
|
+
serverKey: inventory.namespace,
|
|
6578
|
+
instanceKey,
|
|
6579
|
+
instanceTitle,
|
|
6580
|
+
toolName: tool.name,
|
|
6581
|
+
qualifiedName: `${inventory.namespace}:${tool.name}`,
|
|
6582
|
+
canonicalRouteId: buildCanonicalRouteId(capability, instanceKey, tool.name),
|
|
6583
|
+
collisionGroupSize: 1,
|
|
6584
|
+
inputSchema: tool.inputSchema ?? { type: "object" },
|
|
6585
|
+
summary: resolveSummary(tool.description ?? undefined, capability)
|
|
6586
|
+
});
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
const byCapabilityAction = new Map;
|
|
6590
|
+
for (const candidate of candidates) {
|
|
6591
|
+
const key = `${candidate.capability}:${candidate.baseAction}`;
|
|
6592
|
+
const existing = byCapabilityAction.get(key) ?? [];
|
|
6593
|
+
existing.push(candidate);
|
|
6594
|
+
byCapabilityAction.set(key, existing);
|
|
6595
|
+
}
|
|
6596
|
+
const resolved = [];
|
|
6597
|
+
const sortedKeys = [...byCapabilityAction.keys()].sort((a, b) => a.localeCompare(b));
|
|
6598
|
+
for (const key of sortedKeys) {
|
|
6599
|
+
const records = (byCapabilityAction.get(key) ?? []).sort((a, b) => a.qualifiedName.localeCompare(b.qualifiedName));
|
|
6600
|
+
if (records.length === 1) {
|
|
6601
|
+
const [record] = records;
|
|
6602
|
+
if (record) {
|
|
6603
|
+
resolved.push({
|
|
6604
|
+
...record,
|
|
6605
|
+
action: record.baseAction,
|
|
6606
|
+
collisionGroupSize: 1,
|
|
6607
|
+
legacyActions: []
|
|
6608
|
+
});
|
|
6609
|
+
}
|
|
6610
|
+
continue;
|
|
6611
|
+
}
|
|
6612
|
+
const legacyActionsByQualifiedName = new Map;
|
|
6613
|
+
records.forEach((record, index) => {
|
|
6614
|
+
legacyActionsByQualifiedName.set(record.qualifiedName, index === 0 ? [] : [`${record.baseAction}__${index + 1}`]);
|
|
6615
|
+
});
|
|
6616
|
+
const instanceTokens = resolveInstanceTokens(records);
|
|
6617
|
+
const byInstance = new Map;
|
|
6618
|
+
for (const record of records) {
|
|
6619
|
+
const instanceKey = record.instanceKey ?? record.serverKey;
|
|
6620
|
+
const existing = byInstance.get(instanceKey) ?? [];
|
|
6621
|
+
existing.push(record);
|
|
6622
|
+
byInstance.set(instanceKey, existing);
|
|
6623
|
+
}
|
|
6624
|
+
const emittedActions = new Set;
|
|
6625
|
+
for (const instanceKey of [...byInstance.keys()].sort((a, b) => a.localeCompare(b))) {
|
|
6626
|
+
const instanceRecords = (byInstance.get(instanceKey) ?? []).sort((a, b) => a.qualifiedName.localeCompare(b.qualifiedName));
|
|
6627
|
+
const instanceToken = instanceTokens.get(instanceKey) ?? toActionToken2(instanceKey);
|
|
6628
|
+
const actionBase = buildInstanceActionBase(records[0]?.baseAction ?? "tool", instanceToken);
|
|
6629
|
+
instanceRecords.forEach((record, index) => {
|
|
6630
|
+
resolved.push({
|
|
6631
|
+
...record,
|
|
6632
|
+
action: allocateUniqueActionId(emittedActions, actionBase, index),
|
|
6633
|
+
collisionGroupSize: records.length,
|
|
6634
|
+
legacyActions: legacyActionsByQualifiedName.get(record.qualifiedName) ?? []
|
|
6635
|
+
});
|
|
6636
|
+
});
|
|
6637
|
+
}
|
|
6638
|
+
}
|
|
6639
|
+
const byCapability = new Map;
|
|
6640
|
+
for (const route of resolved) {
|
|
6641
|
+
const existing = byCapability.get(route.capability) ?? [];
|
|
6642
|
+
existing.push(route);
|
|
6643
|
+
byCapability.set(route.capability, existing);
|
|
6644
|
+
}
|
|
6645
|
+
return [...byCapability.entries()].map(([capability, actions]) => ({
|
|
6646
|
+
capability,
|
|
6647
|
+
actions: actions.sort((a, b) => a.action.localeCompare(b.action))
|
|
6648
|
+
})).sort((a, b) => a.capability.localeCompare(b.capability));
|
|
6649
|
+
}
|
|
6650
|
+
|
|
5881
6651
|
// src/index/store.ts
|
|
5882
6652
|
import { Database } from "bun:sqlite";
|
|
5883
6653
|
function hashSchema2(schema) {
|
|
@@ -6424,6 +7194,9 @@ class Retriever {
|
|
|
6424
7194
|
clearIndex() {
|
|
6425
7195
|
this.indexStore.clear();
|
|
6426
7196
|
}
|
|
7197
|
+
getEmbeddingGenerator() {
|
|
7198
|
+
return this.embeddingGenerator;
|
|
7199
|
+
}
|
|
6427
7200
|
async initializeEmbeddings() {
|
|
6428
7201
|
if (!this.embeddingsAvailable) {
|
|
6429
7202
|
return;
|
|
@@ -6545,60 +7318,68 @@ function cleanupExpiredTokens() {
|
|
|
6545
7318
|
}
|
|
6546
7319
|
}
|
|
6547
7320
|
}
|
|
6548
|
-
function validateConfirmationToken(token,
|
|
7321
|
+
function validateConfirmationToken(token, capability, action) {
|
|
6549
7322
|
cleanupExpiredTokens();
|
|
6550
7323
|
const confirmation = pendingConfirmations.get(token);
|
|
6551
7324
|
if (!confirmation) {
|
|
6552
7325
|
return false;
|
|
6553
7326
|
}
|
|
6554
|
-
if (confirmation.
|
|
7327
|
+
if (confirmation.capability !== capability || confirmation.action !== action) {
|
|
6555
7328
|
return false;
|
|
6556
7329
|
}
|
|
6557
7330
|
pendingConfirmations.delete(token);
|
|
6558
7331
|
return true;
|
|
6559
7332
|
}
|
|
6560
|
-
function createConfirmationToken(
|
|
7333
|
+
function createConfirmationToken(capability, action) {
|
|
6561
7334
|
cleanupExpiredTokens();
|
|
6562
7335
|
const token = generateToken();
|
|
6563
7336
|
pendingConfirmations.set(token, {
|
|
6564
|
-
|
|
6565
|
-
|
|
7337
|
+
capability,
|
|
7338
|
+
action,
|
|
6566
7339
|
createdAt: Date.now()
|
|
6567
7340
|
});
|
|
6568
7341
|
return token;
|
|
6569
7342
|
}
|
|
6570
7343
|
function evaluatePolicy(context, config) {
|
|
6571
|
-
const
|
|
7344
|
+
const capability = context.capability ?? context.serverKey;
|
|
7345
|
+
const action = context.action ?? context.toolName;
|
|
7346
|
+
const confirmationToken = context.confirmationToken;
|
|
6572
7347
|
const { block, confirm, allow } = config.security.tools;
|
|
6573
|
-
if (
|
|
7348
|
+
if (!capability || !action) {
|
|
6574
7349
|
return {
|
|
6575
7350
|
decision: "block",
|
|
6576
|
-
reason:
|
|
7351
|
+
reason: "Missing capability/action in security policy context"
|
|
6577
7352
|
};
|
|
6578
7353
|
}
|
|
6579
|
-
if (matchesAnyPattern(
|
|
7354
|
+
if (matchesAnyPattern(block, capability, action)) {
|
|
7355
|
+
return {
|
|
7356
|
+
decision: "block",
|
|
7357
|
+
reason: `Action "${action}" in capability "${capability}" is blocked by security policy`
|
|
7358
|
+
};
|
|
7359
|
+
}
|
|
7360
|
+
if (matchesAnyPattern(allow, capability, action)) {
|
|
6580
7361
|
return {
|
|
6581
7362
|
decision: "allow",
|
|
6582
|
-
reason: `
|
|
7363
|
+
reason: `Action "${action}" is allowed by security policy`
|
|
6583
7364
|
};
|
|
6584
7365
|
}
|
|
6585
|
-
if (matchesAnyPattern(confirm,
|
|
6586
|
-
if (confirmationToken && validateConfirmationToken(confirmationToken,
|
|
7366
|
+
if (matchesAnyPattern(confirm, capability, action)) {
|
|
7367
|
+
if (confirmationToken && validateConfirmationToken(confirmationToken, capability, action)) {
|
|
6587
7368
|
return {
|
|
6588
7369
|
decision: "allow",
|
|
6589
|
-
reason: `
|
|
7370
|
+
reason: `Action "${action}" confirmed with valid token`
|
|
6590
7371
|
};
|
|
6591
7372
|
}
|
|
6592
|
-
const token = createConfirmationToken(
|
|
7373
|
+
const token = createConfirmationToken(capability, action);
|
|
6593
7374
|
return {
|
|
6594
7375
|
decision: "confirm",
|
|
6595
|
-
reason: `
|
|
7376
|
+
reason: `Action "${action}" in capability "${capability}" requires confirmation`,
|
|
6596
7377
|
confirmationToken: token
|
|
6597
7378
|
};
|
|
6598
7379
|
}
|
|
6599
7380
|
return {
|
|
6600
7381
|
decision: "block",
|
|
6601
|
-
reason: `
|
|
7382
|
+
reason: `Action "${action}" in capability "${capability}" is not in the allow or confirm list`
|
|
6602
7383
|
};
|
|
6603
7384
|
}
|
|
6604
7385
|
function compilePolicy(config) {
|
|
@@ -6763,6 +7544,26 @@ async function waitForProcessExit(proc, timeoutMs) {
|
|
|
6763
7544
|
}
|
|
6764
7545
|
|
|
6765
7546
|
// src/upstream/cataloger.ts
|
|
7547
|
+
var AUTH_ERROR_PATTERNS = [
|
|
7548
|
+
"invalid_token",
|
|
7549
|
+
"invalid token",
|
|
7550
|
+
"unauthorized",
|
|
7551
|
+
"no token provided",
|
|
7552
|
+
"no authorization",
|
|
7553
|
+
"api key",
|
|
7554
|
+
"api_key",
|
|
7555
|
+
"authentication required",
|
|
7556
|
+
"authentication failed",
|
|
7557
|
+
"invalid credentials",
|
|
7558
|
+
"invalid api",
|
|
7559
|
+
"forbidden",
|
|
7560
|
+
"access denied",
|
|
7561
|
+
"not authenticated"
|
|
7562
|
+
];
|
|
7563
|
+
function isAuthError(message) {
|
|
7564
|
+
const lower = message.toLowerCase();
|
|
7565
|
+
return AUTH_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
|
|
7566
|
+
}
|
|
6766
7567
|
function resolveEnvVars(env2) {
|
|
6767
7568
|
const resolved = {};
|
|
6768
7569
|
for (const [key, value] of Object.entries(env2)) {
|
|
@@ -6843,7 +7644,7 @@ class Cataloger {
|
|
|
6843
7644
|
if (err instanceof UnauthorizedError2 && connection.authProvider) {
|
|
6844
7645
|
if (connection.authProvider.isNonInteractive()) {
|
|
6845
7646
|
connection.authPending = true;
|
|
6846
|
-
connection.status = "
|
|
7647
|
+
connection.status = "needs_auth";
|
|
6847
7648
|
connection.error = `OAuth authorization required. Run: mcp-squared auth ${key}`;
|
|
6848
7649
|
return;
|
|
6849
7650
|
}
|
|
@@ -6867,8 +7668,9 @@ class Cataloger {
|
|
|
6867
7668
|
}));
|
|
6868
7669
|
connection.status = "connected";
|
|
6869
7670
|
} catch (err) {
|
|
6870
|
-
|
|
6871
|
-
connection.
|
|
7671
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
7672
|
+
connection.status = isAuthError(errorMsg) ? "needs_auth" : "error";
|
|
7673
|
+
connection.error = errorMsg;
|
|
6872
7674
|
try {
|
|
6873
7675
|
await this.cleanupConnection(connection);
|
|
6874
7676
|
} catch (_cleanupErr) {}
|
|
@@ -7002,10 +7804,15 @@ class Cataloger {
|
|
|
7002
7804
|
name: bareToolName,
|
|
7003
7805
|
arguments: args
|
|
7004
7806
|
});
|
|
7005
|
-
|
|
7807
|
+
const response = {
|
|
7006
7808
|
content: callResult.content,
|
|
7007
7809
|
isError: callResult.isError
|
|
7008
7810
|
};
|
|
7811
|
+
const sc = callResult["structuredContent"];
|
|
7812
|
+
if (sc != null && typeof sc === "object" && !Array.isArray(sc)) {
|
|
7813
|
+
response.structuredContent = sc;
|
|
7814
|
+
}
|
|
7815
|
+
return response;
|
|
7009
7816
|
}
|
|
7010
7817
|
async refreshTools(key) {
|
|
7011
7818
|
const connection = this.connections.get(key);
|
|
@@ -7030,13 +7837,14 @@ class Cataloger {
|
|
|
7030
7837
|
if (err instanceof UnauthorizedError2 && connection.authProvider) {
|
|
7031
7838
|
if (connection.authProvider.isNonInteractive()) {
|
|
7032
7839
|
connection.authPending = true;
|
|
7033
|
-
connection.status = "
|
|
7840
|
+
connection.status = "needs_auth";
|
|
7034
7841
|
connection.error = `OAuth authorization required. Run: mcp-squared auth ${key}`;
|
|
7035
7842
|
return;
|
|
7036
7843
|
}
|
|
7037
7844
|
}
|
|
7038
|
-
|
|
7039
|
-
connection.
|
|
7845
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
7846
|
+
connection.status = isAuthError(errorMsg) ? "needs_auth" : "error";
|
|
7847
|
+
connection.error = errorMsg;
|
|
7040
7848
|
}
|
|
7041
7849
|
}
|
|
7042
7850
|
async refreshAllTools() {
|
|
@@ -7341,6 +8149,37 @@ async function testUpstreamConnection(name, config, options = {}) {
|
|
|
7341
8149
|
log("Done");
|
|
7342
8150
|
}
|
|
7343
8151
|
}
|
|
8152
|
+
// src/utils/capability-meta.ts
|
|
8153
|
+
function capabilityTitle(capability) {
|
|
8154
|
+
return capability.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
8155
|
+
}
|
|
8156
|
+
function capabilitySummary(capability) {
|
|
8157
|
+
switch (capability) {
|
|
8158
|
+
case "code_search":
|
|
8159
|
+
return "Search and retrieve source-code context.";
|
|
8160
|
+
case "docs":
|
|
8161
|
+
return "Query and read technical documentation.";
|
|
8162
|
+
case "browser_automation":
|
|
8163
|
+
return "Automate browser interactions and diagnostics.";
|
|
8164
|
+
case "issue_tracking":
|
|
8165
|
+
return "Work with issues, tickets, and project tracking.";
|
|
8166
|
+
case "cms_content":
|
|
8167
|
+
return "Manage content and CMS resources.";
|
|
8168
|
+
case "design":
|
|
8169
|
+
return "Create and inspect design artifacts and visuals.";
|
|
8170
|
+
case "ai_media_generation":
|
|
8171
|
+
return "Generate and edit images and media using AI models.";
|
|
8172
|
+
case "hosting_deploy":
|
|
8173
|
+
return "Manage deployments, hosting, and infrastructure operations.";
|
|
8174
|
+
case "time_util":
|
|
8175
|
+
return "Resolve time, timezone, and date utilities.";
|
|
8176
|
+
case "research":
|
|
8177
|
+
return "Run web/research collection and synthesis operations.";
|
|
8178
|
+
default:
|
|
8179
|
+
return "Run general-purpose capability actions.";
|
|
8180
|
+
}
|
|
8181
|
+
}
|
|
8182
|
+
|
|
7344
8183
|
// src/server/monitor-server.ts
|
|
7345
8184
|
import { existsSync as existsSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
7346
8185
|
import { createServer as createServer3 } from "net";
|
|
@@ -7590,6 +8429,178 @@ class MonitorServer {
|
|
|
7590
8429
|
}
|
|
7591
8430
|
}
|
|
7592
8431
|
|
|
8432
|
+
// src/server/response-resource.ts
|
|
8433
|
+
import { randomBytes } from "crypto";
|
|
8434
|
+
class ResponseResourceManager {
|
|
8435
|
+
static MAX_PREVIEW_BYTES = 2048;
|
|
8436
|
+
config;
|
|
8437
|
+
resources = new Map;
|
|
8438
|
+
insertionOrder = [];
|
|
8439
|
+
constructor(config) {
|
|
8440
|
+
this.config = config;
|
|
8441
|
+
}
|
|
8442
|
+
isEnabled() {
|
|
8443
|
+
return this.config.enabled;
|
|
8444
|
+
}
|
|
8445
|
+
shouldOffload(content) {
|
|
8446
|
+
if (!this.config.enabled)
|
|
8447
|
+
return false;
|
|
8448
|
+
const { byteCount } = this.canonicalizeContent(content);
|
|
8449
|
+
return byteCount > this.config.thresholdBytes;
|
|
8450
|
+
}
|
|
8451
|
+
offload(content, context) {
|
|
8452
|
+
const { fullText, byteCount } = this.canonicalizeContent(content);
|
|
8453
|
+
const id = this.generateId(context);
|
|
8454
|
+
const uri = `mcp2://response/${context.capability}/${id}`;
|
|
8455
|
+
const entry = {
|
|
8456
|
+
uri,
|
|
8457
|
+
name: `${context.capability}:${context.action} response`,
|
|
8458
|
+
description: `Full response from ${context.capability}:${context.action} (${byteCount} bytes)`,
|
|
8459
|
+
mimeType: "text/plain",
|
|
8460
|
+
fullText,
|
|
8461
|
+
byteCount,
|
|
8462
|
+
createdAt: Date.now()
|
|
8463
|
+
};
|
|
8464
|
+
this.store(uri, entry);
|
|
8465
|
+
const preview = this.buildPreview(fullText);
|
|
8466
|
+
const pointer = {
|
|
8467
|
+
truncated: true,
|
|
8468
|
+
resource_uri: uri,
|
|
8469
|
+
total_bytes: byteCount,
|
|
8470
|
+
preview,
|
|
8471
|
+
instructions: "Full response available via resources/read with the resource_uri above."
|
|
8472
|
+
};
|
|
8473
|
+
return {
|
|
8474
|
+
resourceUri: uri,
|
|
8475
|
+
inlineContent: [{ type: "text", text: JSON.stringify(pointer) }]
|
|
8476
|
+
};
|
|
8477
|
+
}
|
|
8478
|
+
readResource(uri) {
|
|
8479
|
+
const entry = this.resources.get(uri);
|
|
8480
|
+
if (!entry)
|
|
8481
|
+
return null;
|
|
8482
|
+
if (Date.now() - entry.createdAt >= this.config.ttlMs) {
|
|
8483
|
+
this.deleteResource(uri);
|
|
8484
|
+
return null;
|
|
8485
|
+
}
|
|
8486
|
+
this.touchInsertionOrder(uri);
|
|
8487
|
+
return {
|
|
8488
|
+
contents: [
|
|
8489
|
+
{
|
|
8490
|
+
uri: entry.uri,
|
|
8491
|
+
mimeType: entry.mimeType,
|
|
8492
|
+
text: entry.fullText
|
|
8493
|
+
}
|
|
8494
|
+
]
|
|
8495
|
+
};
|
|
8496
|
+
}
|
|
8497
|
+
listResources() {
|
|
8498
|
+
this.evictExpired();
|
|
8499
|
+
const result = [];
|
|
8500
|
+
for (const entry of this.resources.values()) {
|
|
8501
|
+
result.push({
|
|
8502
|
+
uri: entry.uri,
|
|
8503
|
+
name: entry.name,
|
|
8504
|
+
description: entry.description,
|
|
8505
|
+
mimeType: entry.mimeType,
|
|
8506
|
+
size: entry.byteCount
|
|
8507
|
+
});
|
|
8508
|
+
}
|
|
8509
|
+
return result;
|
|
8510
|
+
}
|
|
8511
|
+
getResourceCount() {
|
|
8512
|
+
return this.resources.size;
|
|
8513
|
+
}
|
|
8514
|
+
canonicalizeContent(content) {
|
|
8515
|
+
const fullText = content.map((block) => block.text).join(`
|
|
8516
|
+
|
|
8517
|
+
---
|
|
8518
|
+
|
|
8519
|
+
`);
|
|
8520
|
+
return {
|
|
8521
|
+
fullText,
|
|
8522
|
+
byteCount: this.measureBytes(fullText)
|
|
8523
|
+
};
|
|
8524
|
+
}
|
|
8525
|
+
measureBytes(text) {
|
|
8526
|
+
return Buffer.byteLength(text, "utf8");
|
|
8527
|
+
}
|
|
8528
|
+
generateId(context) {
|
|
8529
|
+
const timestamp = Date.now().toString(36);
|
|
8530
|
+
const random = randomBytes(4).toString("hex");
|
|
8531
|
+
const actionSlug = context.action.toLowerCase().replace(/[^a-z0-9]+/g, "_").slice(0, 24);
|
|
8532
|
+
return `${actionSlug}_${timestamp}_${random}`;
|
|
8533
|
+
}
|
|
8534
|
+
buildPreview(fullText) {
|
|
8535
|
+
const lines = fullText.split(`
|
|
8536
|
+
`);
|
|
8537
|
+
let preview;
|
|
8538
|
+
if (lines.length <= this.config.maxInlineLines) {
|
|
8539
|
+
preview = fullText;
|
|
8540
|
+
} else {
|
|
8541
|
+
preview = `${lines.slice(0, this.config.maxInlineLines).join(`
|
|
8542
|
+
`)}
|
|
8543
|
+
...`;
|
|
8544
|
+
}
|
|
8545
|
+
if (this.measureBytes(preview) > ResponseResourceManager.MAX_PREVIEW_BYTES) {
|
|
8546
|
+
const truncated = this.truncateUtf8(preview, ResponseResourceManager.MAX_PREVIEW_BYTES);
|
|
8547
|
+
return `${truncated}...`;
|
|
8548
|
+
}
|
|
8549
|
+
return preview;
|
|
8550
|
+
}
|
|
8551
|
+
truncateUtf8(text, maxBytes) {
|
|
8552
|
+
const bytes = Buffer.from(text, "utf8");
|
|
8553
|
+
if (bytes.length <= maxBytes)
|
|
8554
|
+
return text;
|
|
8555
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
8556
|
+
for (let end = maxBytes;end > 0; end--) {
|
|
8557
|
+
try {
|
|
8558
|
+
return decoder.decode(bytes.subarray(0, end));
|
|
8559
|
+
} catch {}
|
|
8560
|
+
}
|
|
8561
|
+
return "";
|
|
8562
|
+
}
|
|
8563
|
+
store(uri, entry) {
|
|
8564
|
+
this.evictExpired();
|
|
8565
|
+
while (this.resources.size >= this.config.maxResources && this.insertionOrder.length > 0) {
|
|
8566
|
+
const oldest = this.insertionOrder[0];
|
|
8567
|
+
if (!oldest)
|
|
8568
|
+
break;
|
|
8569
|
+
this.deleteResource(oldest);
|
|
8570
|
+
}
|
|
8571
|
+
this.resources.set(uri, entry);
|
|
8572
|
+
this.touchInsertionOrder(uri);
|
|
8573
|
+
}
|
|
8574
|
+
evictExpired() {
|
|
8575
|
+
const now = Date.now();
|
|
8576
|
+
const toRemove = [];
|
|
8577
|
+
for (const [uri, entry] of this.resources) {
|
|
8578
|
+
if (now - entry.createdAt >= this.config.ttlMs) {
|
|
8579
|
+
toRemove.push(uri);
|
|
8580
|
+
}
|
|
8581
|
+
}
|
|
8582
|
+
for (const uri of toRemove) {
|
|
8583
|
+
this.deleteResource(uri);
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
8586
|
+
deleteResource(uri) {
|
|
8587
|
+
this.resources.delete(uri);
|
|
8588
|
+
this.removeFromInsertionOrder(uri);
|
|
8589
|
+
}
|
|
8590
|
+
removeFromInsertionOrder(uri) {
|
|
8591
|
+
const idx = this.insertionOrder.indexOf(uri);
|
|
8592
|
+
if (idx !== -1)
|
|
8593
|
+
this.insertionOrder.splice(idx, 1);
|
|
8594
|
+
}
|
|
8595
|
+
appendToInsertionOrder(uri) {
|
|
8596
|
+
this.insertionOrder.push(uri);
|
|
8597
|
+
}
|
|
8598
|
+
touchInsertionOrder(uri) {
|
|
8599
|
+
this.removeFromInsertionOrder(uri);
|
|
8600
|
+
this.appendToInsertionOrder(uri);
|
|
8601
|
+
}
|
|
8602
|
+
}
|
|
8603
|
+
|
|
7593
8604
|
// src/server/stats.ts
|
|
7594
8605
|
class StatsCollector {
|
|
7595
8606
|
indexStore;
|
|
@@ -7778,18 +8789,23 @@ class StatsCollector {
|
|
|
7778
8789
|
}
|
|
7779
8790
|
|
|
7780
8791
|
// src/server/index.ts
|
|
8792
|
+
var DESCRIBE_ACTION2 = "__describe_actions";
|
|
8793
|
+
function isRecord3(value) {
|
|
8794
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8795
|
+
}
|
|
8796
|
+
|
|
7781
8797
|
class McpSquaredServer {
|
|
7782
8798
|
mcpServer;
|
|
7783
8799
|
cataloger;
|
|
7784
8800
|
retriever;
|
|
7785
8801
|
config;
|
|
7786
|
-
maxLimit;
|
|
7787
8802
|
transport = null;
|
|
7788
8803
|
ownsCataloger;
|
|
7789
8804
|
selectionTracker;
|
|
7790
8805
|
compiledPolicy;
|
|
7791
8806
|
indexRefreshManager;
|
|
7792
8807
|
statsCollector;
|
|
8808
|
+
responseResourceManager;
|
|
7793
8809
|
monitorServer;
|
|
7794
8810
|
isCoreStarted = false;
|
|
7795
8811
|
serverName;
|
|
@@ -7797,6 +8813,8 @@ class McpSquaredServer {
|
|
|
7797
8813
|
safetyAgent;
|
|
7798
8814
|
obsSink;
|
|
7799
8815
|
guard;
|
|
8816
|
+
baseToolsRegistered = false;
|
|
8817
|
+
computedCapabilityOverrides = {};
|
|
7800
8818
|
constructor(options = {}) {
|
|
7801
8819
|
const name = options.name ?? "mcp-squared";
|
|
7802
8820
|
const version = options.version ?? VERSION;
|
|
@@ -7811,11 +8829,11 @@ class McpSquaredServer {
|
|
|
7811
8829
|
}
|
|
7812
8830
|
this.config = options.config ?? DEFAULT_CONFIG;
|
|
7813
8831
|
const findToolsConfig = this.config.operations.findTools;
|
|
7814
|
-
|
|
8832
|
+
const retrieverMaxLimit = options.maxLimit ?? findToolsConfig.maxLimit;
|
|
7815
8833
|
this.retriever = new Retriever(this.cataloger, {
|
|
7816
8834
|
indexDbPath: options.indexDbPath,
|
|
7817
8835
|
defaultLimit: options.defaultLimit ?? findToolsConfig.defaultLimit,
|
|
7818
|
-
maxLimit:
|
|
8836
|
+
maxLimit: retrieverMaxLimit,
|
|
7819
8837
|
defaultMode: findToolsConfig.defaultMode
|
|
7820
8838
|
});
|
|
7821
8839
|
const safetyEnv = readSafetyEnv();
|
|
@@ -7839,6 +8857,8 @@ class McpSquaredServer {
|
|
|
7839
8857
|
policy: loadedPolicy,
|
|
7840
8858
|
sink: this.obsSink
|
|
7841
8859
|
});
|
|
8860
|
+
const rrConfig = this.config.operations.responseResource ?? DEFAULT_RESPONSE_RESOURCE_CONFIG;
|
|
8861
|
+
this.responseResourceManager = new ResponseResourceManager(rrConfig);
|
|
7842
8862
|
this.mcpServer = this.createMcpServer(name, version);
|
|
7843
8863
|
this.selectionTracker = new SelectionTracker;
|
|
7844
8864
|
this.compiledPolicy = compilePolicy(this.config);
|
|
@@ -7866,480 +8886,341 @@ class McpSquaredServer {
|
|
|
7866
8886
|
});
|
|
7867
8887
|
}
|
|
7868
8888
|
});
|
|
7869
|
-
this.registerMetaTools(this.mcpServer);
|
|
7870
8889
|
}
|
|
7871
8890
|
createMcpServer(name, version) {
|
|
7872
8891
|
return new McpServer({
|
|
7873
8892
|
name,
|
|
7874
8893
|
version,
|
|
7875
|
-
title: "MCP\xB2
|
|
7876
|
-
description: "
|
|
8894
|
+
title: "MCP\xB2 Capability Router",
|
|
8895
|
+
description: "Execute capability-first tools routed to connected upstream MCP servers."
|
|
7877
8896
|
}, {
|
|
7878
8897
|
capabilities: {
|
|
7879
|
-
tools: {}
|
|
8898
|
+
tools: {},
|
|
8899
|
+
...this.responseResourceManager.isEnabled() ? { resources: {} } : {}
|
|
7880
8900
|
},
|
|
7881
8901
|
instructions: this.buildServerInstructions()
|
|
7882
8902
|
});
|
|
7883
8903
|
}
|
|
7884
8904
|
buildServerInstructions() {
|
|
7885
|
-
const codeSearchNamespaces = this.getCodeSearchNamespaces();
|
|
7886
|
-
const codeSearchHint = codeSearchNamespaces.length > 0 ? ` Prefer these configured code-search namespaces when relevant: ${codeSearchNamespaces.join(", ")}.` : "";
|
|
7887
8905
|
return [
|
|
7888
|
-
"
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
"
|
|
8906
|
+
"Tool surface is generated at connect time from inferred upstream capabilities.",
|
|
8907
|
+
"Each capability tool accepts `action`, `arguments`, and optional `confirmation_token`.",
|
|
8908
|
+
'Call a capability tool with `action = "__describe_actions"` to inspect available actions and schemas.',
|
|
8909
|
+
"Use returned action IDs for execution calls; if disambiguation is required, choose one candidate action and retry."
|
|
7892
8910
|
].join(" ");
|
|
7893
8911
|
}
|
|
7894
|
-
getCodeSearchNamespaces() {
|
|
7895
|
-
const configured = this.config.operations.findTools.preferredNamespacesByIntent.codeSearch;
|
|
7896
|
-
const upstreamKeys = Object.keys(this.config.upstreams);
|
|
7897
|
-
if (configured.length > 0) {
|
|
7898
|
-
const deduped = [...new Set(configured)];
|
|
7899
|
-
const present = deduped.filter((ns) => upstreamKeys.includes(ns));
|
|
7900
|
-
return present.length > 0 ? present : deduped;
|
|
7901
|
-
}
|
|
7902
|
-
return upstreamKeys.filter((key) => /(auggie|augment|code|source|repo|search)/i.test(key));
|
|
7903
|
-
}
|
|
7904
|
-
isCodeSearchQuery(query) {
|
|
7905
|
-
return /\b(codebase|source code|repository|repo)\b/i.test(query) || /\b(code search|search code|find symbol|symbol lookup|definition|references?|usages?)\b/i.test(query);
|
|
7906
|
-
}
|
|
7907
|
-
rankToolsForQuery(tools, query) {
|
|
7908
|
-
if (!this.isCodeSearchQuery(query) || tools.length < 2) {
|
|
7909
|
-
return tools;
|
|
7910
|
-
}
|
|
7911
|
-
const preferredNamespaces = new Set(this.getCodeSearchNamespaces());
|
|
7912
|
-
const scored = tools.map((tool, index) => {
|
|
7913
|
-
const haystack = `${tool.serverKey} ${tool.name} ${tool.description ?? ""}`.toLowerCase();
|
|
7914
|
-
let score = 0;
|
|
7915
|
-
if (preferredNamespaces.has(tool.serverKey))
|
|
7916
|
-
score += 100;
|
|
7917
|
-
if (/(auggie|augment)/i.test(tool.serverKey))
|
|
7918
|
-
score += 40;
|
|
7919
|
-
if (/\b(search|find|query|lookup)\b/.test(haystack))
|
|
7920
|
-
score += 15;
|
|
7921
|
-
if (/\b(code|symbol|definition|reference|repo|repository|source)\b/.test(haystack)) {
|
|
7922
|
-
score += 20;
|
|
7923
|
-
}
|
|
7924
|
-
return { tool, index, score };
|
|
7925
|
-
});
|
|
7926
|
-
scored.sort((a, b) => {
|
|
7927
|
-
if (a.score === b.score) {
|
|
7928
|
-
return a.index - b.index;
|
|
7929
|
-
}
|
|
7930
|
-
return b.score - a.score;
|
|
7931
|
-
});
|
|
7932
|
-
return scored.map((entry) => entry.tool);
|
|
7933
|
-
}
|
|
7934
|
-
buildFindToolsGuidance(query) {
|
|
7935
|
-
const guidance = {
|
|
7936
|
-
nextStep: "Use describe_tools for selected candidates, then execute with a qualified name."
|
|
7937
|
-
};
|
|
7938
|
-
if (this.isCodeSearchQuery(query)) {
|
|
7939
|
-
const preferredNamespaces = this.getCodeSearchNamespaces();
|
|
7940
|
-
if (preferredNamespaces.length > 0) {
|
|
7941
|
-
guidance.preferredNamespaces = preferredNamespaces;
|
|
7942
|
-
guidance.note = "For codebase exploration, prefer these namespaces before local shell grep/rg.";
|
|
7943
|
-
}
|
|
7944
|
-
}
|
|
7945
|
-
return guidance;
|
|
7946
|
-
}
|
|
7947
8912
|
createSessionServer() {
|
|
7948
8913
|
const server = this.createMcpServer(this.serverName, this.serverVersion);
|
|
7949
|
-
this.
|
|
8914
|
+
this.registerConfiguredToolSurface(server);
|
|
7950
8915
|
return server;
|
|
7951
8916
|
}
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
env: this.guard.agentEnv
|
|
7958
|
-
}, run);
|
|
8917
|
+
registerConfiguredToolSurface(server) {
|
|
8918
|
+
this.registerCapabilityRouters(server);
|
|
8919
|
+
if (this.responseResourceManager.isEnabled()) {
|
|
8920
|
+
this.registerResponseResources(server);
|
|
8921
|
+
}
|
|
7959
8922
|
}
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
}
|
|
7969
|
-
registerMetaTools(server) {
|
|
7970
|
-
server.registerTool("find_tools", {
|
|
7971
|
-
title: "Discover Upstream Tools",
|
|
7972
|
-
description: "Call this first for capability discovery. Search available tools across all connected upstream MCP servers and return ranked tool summaries matching the query.",
|
|
7973
|
-
annotations: {
|
|
7974
|
-
readOnlyHint: true,
|
|
7975
|
-
openWorldHint: false
|
|
7976
|
-
},
|
|
7977
|
-
inputSchema: {
|
|
7978
|
-
query: z3.string().describe('Natural language query describing the task (for example: "code search", "find symbol", "create issue")'),
|
|
7979
|
-
limit: z3.number().int().min(1).max(this.maxLimit).default(this.config.operations.findTools.defaultLimit).describe("Maximum number of results to return"),
|
|
7980
|
-
mode: SearchModeSchema.optional().describe('Search mode: "fast" (FTS5), "semantic" (embeddings), or "hybrid" (FTS5 + rerank)'),
|
|
7981
|
-
detail_level: DetailLevelSchema.optional().describe('Level of detail: "L0" (name only), "L1" (summary with description, default), "L2" (full schema)')
|
|
7982
|
-
}
|
|
7983
|
-
}, async (args) => this.runTaskSpan("find_tools", async () => {
|
|
7984
|
-
const requestId = this.statsCollector.startRequest();
|
|
7985
|
-
const startTime = Date.now();
|
|
7986
|
-
let success = false;
|
|
7987
|
-
try {
|
|
7988
|
-
const result = await this.retriever.search(args.query, {
|
|
7989
|
-
limit: args.limit,
|
|
7990
|
-
mode: args.mode
|
|
7991
|
-
});
|
|
7992
|
-
const filteredTools = this.filterToolsByPolicy(result.tools);
|
|
7993
|
-
const rankedTools = this.rankToolsForQuery(filteredTools, args.query);
|
|
7994
|
-
const detailLevel = args.detail_level ?? this.config.operations.findTools.defaultDetailLevel;
|
|
7995
|
-
const tools = this.formatToolsForDetailLevel(rankedTools, detailLevel);
|
|
7996
|
-
const guidance = this.buildFindToolsGuidance(args.query);
|
|
7997
|
-
const selectionCacheConfig = this.config.operations.selectionCache;
|
|
7998
|
-
let suggestedTools;
|
|
7999
|
-
if (selectionCacheConfig.enabled && selectionCacheConfig.maxBundleSuggestions > 0) {
|
|
8000
|
-
const toolKeys = filteredTools.map((t) => `${t.serverKey}:${t.name}`);
|
|
8001
|
-
const suggestions = this.retriever.getIndexStore().getSuggestedBundles(toolKeys, selectionCacheConfig.minCooccurrenceThreshold, selectionCacheConfig.maxBundleSuggestions);
|
|
8002
|
-
if (suggestions.length > 0) {
|
|
8003
|
-
suggestedTools = suggestions.map((s) => ({
|
|
8004
|
-
tools: [s.toolKey],
|
|
8005
|
-
frequency: s.count
|
|
8006
|
-
}));
|
|
8007
|
-
}
|
|
8008
|
-
}
|
|
8009
|
-
success = true;
|
|
8923
|
+
registerResponseResources(server) {
|
|
8924
|
+
const mgr = this.responseResourceManager;
|
|
8925
|
+
server.registerResource("response-resources", "mcp2://response/{capability}/{id}", {
|
|
8926
|
+
description: "Temporary resources containing full tool responses that exceeded the inline size threshold.",
|
|
8927
|
+
mimeType: "text/plain"
|
|
8928
|
+
}, async (uri) => {
|
|
8929
|
+
const result = mgr.readResource(uri.href);
|
|
8930
|
+
if (!result) {
|
|
8010
8931
|
return {
|
|
8011
|
-
|
|
8932
|
+
contents: [
|
|
8012
8933
|
{
|
|
8013
|
-
|
|
8934
|
+
uri: uri.href,
|
|
8935
|
+
mimeType: "text/plain",
|
|
8014
8936
|
text: JSON.stringify({
|
|
8015
|
-
|
|
8016
|
-
totalMatches: filteredTools.length,
|
|
8017
|
-
detailLevel,
|
|
8018
|
-
searchMode: result.searchMode,
|
|
8019
|
-
embeddingsAvailable: this.retriever.hasEmbeddings(),
|
|
8020
|
-
tools,
|
|
8021
|
-
guidance,
|
|
8022
|
-
...suggestedTools && { suggestedTools }
|
|
8937
|
+
error: "Resource not found or expired"
|
|
8023
8938
|
})
|
|
8024
8939
|
}
|
|
8025
8940
|
]
|
|
8026
8941
|
};
|
|
8027
|
-
} finally {
|
|
8028
|
-
const responseTime = Date.now() - startTime;
|
|
8029
|
-
this.statsCollector.endRequest(requestId, success, responseTime, "find_tools", "mcp-squared");
|
|
8030
8942
|
}
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8943
|
+
return result;
|
|
8944
|
+
});
|
|
8945
|
+
}
|
|
8946
|
+
runTaskSpan(taskName, run) {
|
|
8947
|
+
return task_span(this.obsSink, {
|
|
8948
|
+
agent: this.safetyAgent,
|
|
8949
|
+
taskName,
|
|
8950
|
+
playbook: this.guard.playbook,
|
|
8951
|
+
env: this.guard.agentEnv
|
|
8952
|
+
}, run);
|
|
8953
|
+
}
|
|
8954
|
+
capabilityTitle(capability) {
|
|
8955
|
+
return capabilityTitle(capability);
|
|
8956
|
+
}
|
|
8957
|
+
capabilitySummary(capability) {
|
|
8958
|
+
return capabilitySummary(capability);
|
|
8959
|
+
}
|
|
8960
|
+
actionSummary(description, capability) {
|
|
8961
|
+
if (typeof description === "string") {
|
|
8962
|
+
const singleLine = description.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
8963
|
+
if (singleLine.length > 0) {
|
|
8964
|
+
return singleLine;
|
|
8041
8965
|
}
|
|
8042
|
-
}
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
blocked: blocked.length > 0 ? blocked : undefined
|
|
8074
|
-
})
|
|
8075
|
-
}
|
|
8076
|
-
]
|
|
8077
|
-
};
|
|
8078
|
-
} finally {
|
|
8079
|
-
const responseTime = Date.now() - startTime;
|
|
8080
|
-
this.statsCollector.endRequest(requestId, success, responseTime, "describe_tools", "mcp-squared");
|
|
8966
|
+
}
|
|
8967
|
+
return `Execute ${this.capabilityTitle(capability)} action`;
|
|
8968
|
+
}
|
|
8969
|
+
buildCapabilityRouters() {
|
|
8970
|
+
const status = this.cataloger.getStatus();
|
|
8971
|
+
const inventories = [...status.entries()].filter(([, info]) => info.status === "connected").map(([namespace]) => ({
|
|
8972
|
+
namespace,
|
|
8973
|
+
title: this.config.upstreams[namespace]?.label ?? namespace,
|
|
8974
|
+
tools: this.cataloger.getToolsForServer(namespace)
|
|
8975
|
+
})).sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
8976
|
+
if (inventories.length === 0) {
|
|
8977
|
+
return [];
|
|
8978
|
+
}
|
|
8979
|
+
const overrides = {
|
|
8980
|
+
...this.computedCapabilityOverrides,
|
|
8981
|
+
...this.config.operations.dynamicToolSurface.capabilityOverrides
|
|
8982
|
+
};
|
|
8983
|
+
const grouping = groupNamespacesByCapability(inventories, overrides);
|
|
8984
|
+
return buildCapabilityRouters(inventories, grouping, (desc, cap) => this.actionSummary(desc, cap));
|
|
8985
|
+
}
|
|
8986
|
+
getCapabilityRouter(capability) {
|
|
8987
|
+
return this.buildCapabilityRouters().find((router) => router.capability === capability) ?? {
|
|
8988
|
+
capability,
|
|
8989
|
+
actions: []
|
|
8990
|
+
};
|
|
8991
|
+
}
|
|
8992
|
+
registerCapabilityRouters(server) {
|
|
8993
|
+
const routers = this.buildCapabilityRouters();
|
|
8994
|
+
for (const router of routers) {
|
|
8995
|
+
if (router.actions.length === 0) {
|
|
8996
|
+
continue;
|
|
8081
8997
|
}
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
}
|
|
8096
|
-
}, async (args) => this.runTaskSpan("execute", async () => {
|
|
8097
|
-
const requestId = this.statsCollector.startRequest();
|
|
8098
|
-
const startTime = Date.now();
|
|
8099
|
-
let success = false;
|
|
8100
|
-
let toolName;
|
|
8101
|
-
let serverKey;
|
|
8102
|
-
try {
|
|
8103
|
-
const lookupResult = this.cataloger.findTool(args.tool_name);
|
|
8104
|
-
if (lookupResult.ambiguous) {
|
|
8105
|
-
return {
|
|
8106
|
-
content: [
|
|
8107
|
-
{
|
|
8108
|
-
type: "text",
|
|
8109
|
-
text: JSON.stringify({
|
|
8110
|
-
error: `Ambiguous tool name "${args.tool_name}". Use a qualified name.`,
|
|
8111
|
-
alternatives: lookupResult.alternatives
|
|
8112
|
-
})
|
|
8113
|
-
}
|
|
8114
|
-
],
|
|
8115
|
-
isError: true
|
|
8116
|
-
};
|
|
8117
|
-
}
|
|
8118
|
-
if (!lookupResult.tool) {
|
|
8119
|
-
return {
|
|
8120
|
-
content: [
|
|
8121
|
-
{
|
|
8122
|
-
type: "text",
|
|
8123
|
-
text: JSON.stringify({
|
|
8124
|
-
error: `Tool not found: ${args.tool_name}`
|
|
8125
|
-
})
|
|
8126
|
-
}
|
|
8127
|
-
],
|
|
8128
|
-
isError: true
|
|
8129
|
-
};
|
|
8998
|
+
const capability = router.capability;
|
|
8999
|
+
server.registerTool(capability, {
|
|
9000
|
+
title: this.capabilityTitle(capability),
|
|
9001
|
+
description: this.capabilitySummary(capability),
|
|
9002
|
+
annotations: {
|
|
9003
|
+
readOnlyHint: false,
|
|
9004
|
+
destructiveHint: false,
|
|
9005
|
+
openWorldHint: true
|
|
9006
|
+
},
|
|
9007
|
+
inputSchema: {
|
|
9008
|
+
action: z3.string().describe(`Action ID for ${capability}. Use "${DESCRIBE_ACTION2}" to inspect available actions and schemas.`),
|
|
9009
|
+
arguments: z3.record(z3.string(), z3.unknown()).default({}).describe("Arguments for the selected capability action"),
|
|
9010
|
+
confirmation_token: z3.string().optional().describe("Optional confirmation token for actions that require explicit confirmation")
|
|
8130
9011
|
}
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
9012
|
+
}, async (rawArgs) => this.runTaskSpan(capability, async () => {
|
|
9013
|
+
const requestId = this.statsCollector.startRequest();
|
|
9014
|
+
const startTime = Date.now();
|
|
9015
|
+
let success = false;
|
|
9016
|
+
try {
|
|
9017
|
+
const liveRouter = this.getCapabilityRouter(capability);
|
|
9018
|
+
const parsedArgs = isRecord3(rawArgs) ? { ...rawArgs } : {};
|
|
9019
|
+
const action = typeof parsedArgs["action"] === "string" ? parsedArgs["action"] : "";
|
|
9020
|
+
const confirmationToken = typeof parsedArgs["confirmation_token"] === "string" ? parsedArgs["confirmation_token"] : undefined;
|
|
9021
|
+
const actionArgs = isRecord3(parsedArgs["arguments"]) ? parsedArgs["arguments"] : {};
|
|
9022
|
+
if (action.length === 0) {
|
|
9023
|
+
return {
|
|
9024
|
+
content: [
|
|
9025
|
+
{
|
|
9026
|
+
type: "text",
|
|
9027
|
+
text: JSON.stringify({
|
|
9028
|
+
error: "Missing required action",
|
|
9029
|
+
capability
|
|
9030
|
+
})
|
|
9031
|
+
}
|
|
9032
|
+
],
|
|
9033
|
+
isError: true
|
|
9034
|
+
};
|
|
9035
|
+
}
|
|
9036
|
+
const visibleRoutes = liveRouter.actions.map((route) => {
|
|
9037
|
+
const visibility = getToolVisibilityCompiled(capability, route.action, this.compiledPolicy);
|
|
9038
|
+
if (!visibility.visible) {
|
|
9039
|
+
return null;
|
|
9040
|
+
}
|
|
9041
|
+
return {
|
|
9042
|
+
route,
|
|
9043
|
+
requiresConfirmation: visibility.requiresConfirmation
|
|
9044
|
+
};
|
|
9045
|
+
}).filter((entry) => entry !== null);
|
|
9046
|
+
const visibleActions = visibleRoutes.map(({ route, requiresConfirmation }) => {
|
|
9047
|
+
const actionInfo = {
|
|
9048
|
+
action: route.action,
|
|
9049
|
+
summary: route.summary,
|
|
9050
|
+
inputSchema: route.inputSchema,
|
|
9051
|
+
requiresConfirmation
|
|
9052
|
+
};
|
|
9053
|
+
if ((route.collisionGroupSize ?? 1) > 1) {
|
|
9054
|
+
actionInfo.baseAction = route.baseAction;
|
|
9055
|
+
actionInfo.instance = route.instanceKey ?? route.serverKey;
|
|
9056
|
+
actionInfo.instanceTitle = route.instanceTitle ?? route.instanceKey ?? route.serverKey;
|
|
9057
|
+
}
|
|
9058
|
+
return actionInfo;
|
|
9059
|
+
}).sort((a, b) => a.action.localeCompare(b.action));
|
|
9060
|
+
if (action === DESCRIBE_ACTION2) {
|
|
9061
|
+
success = true;
|
|
9062
|
+
return {
|
|
9063
|
+
content: [
|
|
9064
|
+
{
|
|
9065
|
+
type: "text",
|
|
9066
|
+
text: JSON.stringify({
|
|
9067
|
+
capability,
|
|
9068
|
+
actions: visibleActions,
|
|
9069
|
+
totalActions: visibleActions.length
|
|
9070
|
+
})
|
|
9071
|
+
}
|
|
9072
|
+
]
|
|
9073
|
+
};
|
|
9074
|
+
}
|
|
9075
|
+
const exactRoute = liveRouter.actions.find((entry) => entry.action === action || (entry.legacyActions ?? []).includes(action));
|
|
9076
|
+
const ambiguousCandidates = visibleRoutes.filter(({ route }) => route.baseAction === action).map(({ route }) => route.action).sort((a, b) => a.localeCompare(b));
|
|
9077
|
+
if (ambiguousCandidates.length > 1) {
|
|
9078
|
+
return {
|
|
9079
|
+
content: [
|
|
9080
|
+
{
|
|
9081
|
+
type: "text",
|
|
9082
|
+
text: JSON.stringify({
|
|
9083
|
+
requires_disambiguation: true,
|
|
9084
|
+
capability,
|
|
9085
|
+
action,
|
|
9086
|
+
candidates: ambiguousCandidates
|
|
9087
|
+
})
|
|
9088
|
+
}
|
|
9089
|
+
],
|
|
9090
|
+
isError: true
|
|
9091
|
+
};
|
|
9092
|
+
}
|
|
9093
|
+
const selectedRoute = exactRoute ?? (ambiguousCandidates.length === 1 ? visibleRoutes.find(({ route }) => route.baseAction === action)?.route : undefined);
|
|
9094
|
+
if (selectedRoute == null) {
|
|
9095
|
+
return {
|
|
9096
|
+
content: [
|
|
9097
|
+
{
|
|
9098
|
+
type: "text",
|
|
9099
|
+
text: JSON.stringify({
|
|
9100
|
+
error: "Unknown action",
|
|
9101
|
+
capability,
|
|
9102
|
+
action,
|
|
9103
|
+
availableActions: visibleActions.map((a) => a.action)
|
|
9104
|
+
})
|
|
9105
|
+
}
|
|
9106
|
+
],
|
|
9107
|
+
isError: true
|
|
9108
|
+
};
|
|
9109
|
+
}
|
|
9110
|
+
const callResult = await this.executeRoutedTool({
|
|
9111
|
+
capability,
|
|
9112
|
+
action: selectedRoute.action,
|
|
9113
|
+
policyAction: exactRoute != null ? action : selectedRoute.action,
|
|
9114
|
+
routeId: selectedRoute.canonicalRouteId ?? `${capability}:${selectedRoute.action}`,
|
|
9115
|
+
qualifiedToolName: selectedRoute.qualifiedName,
|
|
9116
|
+
toolNameForCall: selectedRoute.qualifiedName,
|
|
9117
|
+
args: actionArgs,
|
|
9118
|
+
...confirmationToken != null ? { confirmationToken } : {}
|
|
9119
|
+
});
|
|
9120
|
+
success = !callResult.isError;
|
|
9121
|
+
return callResult;
|
|
9122
|
+
} catch {
|
|
8140
9123
|
return {
|
|
8141
9124
|
content: [
|
|
8142
9125
|
{
|
|
8143
9126
|
type: "text",
|
|
8144
9127
|
text: JSON.stringify({
|
|
8145
|
-
error:
|
|
8146
|
-
blocked: true
|
|
9128
|
+
error: "Action execution failed"
|
|
8147
9129
|
})
|
|
8148
9130
|
}
|
|
8149
9131
|
],
|
|
8150
9132
|
isError: true
|
|
8151
9133
|
};
|
|
9134
|
+
} finally {
|
|
9135
|
+
const responseTime = Date.now() - startTime;
|
|
9136
|
+
this.statsCollector.endRequest(requestId, success, responseTime, capability, "capability");
|
|
8152
9137
|
}
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
confirmation_token: policyResult.confirmationToken,
|
|
8161
|
-
message: policyResult.reason
|
|
8162
|
-
})
|
|
8163
|
-
}
|
|
8164
|
-
],
|
|
8165
|
-
isError: false
|
|
8166
|
-
};
|
|
8167
|
-
}
|
|
8168
|
-
const qualifiedToolName = `${tool.serverKey}:${tool.name}`;
|
|
8169
|
-
this.guard.enforce({
|
|
8170
|
-
agent: this.safetyAgent,
|
|
8171
|
-
tool: qualifiedToolName,
|
|
8172
|
-
action: "call",
|
|
8173
|
-
params: args.arguments
|
|
8174
|
-
});
|
|
8175
|
-
const result = await tool_span(this.obsSink, {
|
|
8176
|
-
agent: this.safetyAgent,
|
|
8177
|
-
tool: qualifiedToolName,
|
|
8178
|
-
action: "call",
|
|
8179
|
-
playbook: this.guard.playbook,
|
|
8180
|
-
env: this.guard.agentEnv
|
|
8181
|
-
}, () => this.cataloger.callTool(args.tool_name, args.arguments));
|
|
8182
|
-
if (!result.isError && this.config.operations.selectionCache.enabled) {
|
|
8183
|
-
const toolKey = `${tool.serverKey}:${tool.name}`;
|
|
8184
|
-
this.selectionTracker.trackToolUsage(toolKey);
|
|
8185
|
-
if (this.selectionTracker.getSessionToolCount() >= 2) {
|
|
8186
|
-
this.selectionTracker.flushToStore(this.retriever.getIndexStore());
|
|
8187
|
-
}
|
|
8188
|
-
}
|
|
8189
|
-
success = !result.isError;
|
|
8190
|
-
return {
|
|
8191
|
-
content: result.content.map((c) => {
|
|
8192
|
-
if (typeof c === "object" && c !== null && "type" in c) {
|
|
8193
|
-
return c;
|
|
8194
|
-
}
|
|
8195
|
-
return {
|
|
8196
|
-
type: "text",
|
|
8197
|
-
text: JSON.stringify(c)
|
|
8198
|
-
};
|
|
8199
|
-
}),
|
|
8200
|
-
isError: result.isError
|
|
8201
|
-
};
|
|
8202
|
-
} catch (err) {
|
|
8203
|
-
const errorMessage2 = err instanceof Error ? err.message : String(err);
|
|
8204
|
-
return {
|
|
8205
|
-
content: [
|
|
8206
|
-
{
|
|
8207
|
-
type: "text",
|
|
8208
|
-
text: JSON.stringify({
|
|
8209
|
-
error: errorMessage2
|
|
8210
|
-
})
|
|
8211
|
-
}
|
|
8212
|
-
],
|
|
8213
|
-
isError: true
|
|
8214
|
-
};
|
|
8215
|
-
} finally {
|
|
8216
|
-
const responseTime = Date.now() - startTime;
|
|
8217
|
-
this.statsCollector.endRequest(requestId, success, responseTime, toolName, serverKey);
|
|
8218
|
-
}
|
|
8219
|
-
}));
|
|
8220
|
-
server.registerTool("clear_selection_cache", {
|
|
8221
|
-
title: "Reset Selection Cache",
|
|
8222
|
-
description: "Clears all learned tool co-occurrence patterns. Use this to reset the selection cache if suggestions become stale or irrelevant.",
|
|
8223
|
-
annotations: {
|
|
8224
|
-
readOnlyHint: false,
|
|
8225
|
-
destructiveHint: true,
|
|
8226
|
-
idempotentHint: true,
|
|
8227
|
-
openWorldHint: false
|
|
8228
|
-
},
|
|
8229
|
-
inputSchema: {}
|
|
8230
|
-
}, async () => this.runTaskSpan("clear_selection_cache", async () => {
|
|
8231
|
-
const requestId = this.statsCollector.startRequest();
|
|
8232
|
-
const startTime = Date.now();
|
|
8233
|
-
let success = false;
|
|
8234
|
-
try {
|
|
8235
|
-
const countBefore = this.retriever.getIndexStore().getCooccurrenceCount();
|
|
8236
|
-
this.retriever.getIndexStore().clearCooccurrences();
|
|
8237
|
-
this.selectionTracker.reset();
|
|
8238
|
-
success = true;
|
|
8239
|
-
return {
|
|
8240
|
-
content: [
|
|
8241
|
-
{
|
|
8242
|
-
type: "text",
|
|
8243
|
-
text: JSON.stringify({
|
|
8244
|
-
message: "Selection cache cleared",
|
|
8245
|
-
patternsRemoved: countBefore
|
|
8246
|
-
})
|
|
8247
|
-
}
|
|
8248
|
-
]
|
|
8249
|
-
};
|
|
8250
|
-
} finally {
|
|
8251
|
-
const responseTime = Date.now() - startTime;
|
|
8252
|
-
this.statsCollector.endRequest(requestId, success, responseTime, "clear_selection_cache", "mcp-squared");
|
|
8253
|
-
}
|
|
8254
|
-
}));
|
|
8255
|
-
server.registerTool("list_namespaces", {
|
|
8256
|
-
title: "List Upstream Namespaces",
|
|
8257
|
-
description: "List available namespaces (upstream MCP servers) and optional tool names. Use this to discover routing options and disambiguate qualified names (namespace:tool_name).",
|
|
8258
|
-
annotations: {
|
|
8259
|
-
readOnlyHint: true,
|
|
8260
|
-
openWorldHint: false
|
|
8261
|
-
},
|
|
8262
|
-
inputSchema: {
|
|
8263
|
-
include_tools: z3.boolean().default(false).describe("If true, includes the list of tool names available in each namespace")
|
|
9138
|
+
}));
|
|
9139
|
+
}
|
|
9140
|
+
}
|
|
9141
|
+
normalizeToolResultContent(content) {
|
|
9142
|
+
return content.map((entry) => {
|
|
9143
|
+
if (typeof entry === "object" && entry !== null && "type" in entry) {
|
|
9144
|
+
return entry;
|
|
8264
9145
|
}
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
9146
|
+
return {
|
|
9147
|
+
type: "text",
|
|
9148
|
+
text: JSON.stringify(entry)
|
|
9149
|
+
};
|
|
9150
|
+
});
|
|
9151
|
+
}
|
|
9152
|
+
async executeRoutedTool(args) {
|
|
9153
|
+
const policyResult = evaluatePolicy({
|
|
9154
|
+
capability: args.capability,
|
|
9155
|
+
action: args.policyAction ?? args.action,
|
|
9156
|
+
confirmationToken: args.confirmationToken
|
|
9157
|
+
}, this.config);
|
|
9158
|
+
if (policyResult.decision === "block") {
|
|
9159
|
+
return {
|
|
9160
|
+
content: [
|
|
9161
|
+
{
|
|
9162
|
+
type: "text",
|
|
9163
|
+
text: JSON.stringify({
|
|
9164
|
+
error: "Action blocked by security policy",
|
|
9165
|
+
blocked: true
|
|
9166
|
+
})
|
|
8281
9167
|
}
|
|
8282
|
-
|
|
8283
|
-
|
|
9168
|
+
],
|
|
9169
|
+
isError: true
|
|
9170
|
+
};
|
|
9171
|
+
}
|
|
9172
|
+
if (policyResult.decision === "confirm") {
|
|
9173
|
+
return {
|
|
9174
|
+
content: [
|
|
9175
|
+
{
|
|
9176
|
+
type: "text",
|
|
9177
|
+
text: JSON.stringify({
|
|
9178
|
+
requires_confirmation: true,
|
|
9179
|
+
confirmation_token: policyResult.confirmationToken,
|
|
9180
|
+
message: "Action requires confirmation by security policy"
|
|
9181
|
+
})
|
|
8284
9182
|
}
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
9183
|
+
],
|
|
9184
|
+
isError: false
|
|
9185
|
+
};
|
|
9186
|
+
}
|
|
9187
|
+
this.guard.enforce({
|
|
9188
|
+
agent: this.safetyAgent,
|
|
9189
|
+
tool: args.routeId ?? `${args.capability}:${args.action}`,
|
|
9190
|
+
action: "call",
|
|
9191
|
+
params: args.args
|
|
9192
|
+
});
|
|
9193
|
+
const result = await tool_span(this.obsSink, {
|
|
9194
|
+
agent: this.safetyAgent,
|
|
9195
|
+
tool: args.routeId ?? `${args.capability}:${args.action}`,
|
|
9196
|
+
action: "call",
|
|
9197
|
+
playbook: this.guard.playbook,
|
|
9198
|
+
env: this.guard.agentEnv
|
|
9199
|
+
}, () => this.cataloger.callTool(args.toolNameForCall, args.args));
|
|
9200
|
+
if (!result.isError && this.config.operations.selectionCache.enabled) {
|
|
9201
|
+
const toolKey = args.routeId ?? `${args.capability}:${args.action}`;
|
|
9202
|
+
this.selectionTracker.trackToolUsage(toolKey);
|
|
9203
|
+
if (this.selectionTracker.getSessionToolCount() >= 2) {
|
|
9204
|
+
this.selectionTracker.flushToStore(this.retriever.getIndexStore());
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
const normalizedContent = this.normalizeToolResultContent(result.content);
|
|
9208
|
+
const structuredContent = result.structuredContent;
|
|
9209
|
+
if (!result.isError && this.responseResourceManager.isEnabled() && this.responseResourceManager.shouldOffload(normalizedContent)) {
|
|
9210
|
+
try {
|
|
9211
|
+
const offloaded = this.responseResourceManager.offload(normalizedContent, { capability: args.capability, action: args.action });
|
|
8293
9212
|
return {
|
|
8294
|
-
content:
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
text: JSON.stringify({
|
|
8298
|
-
namespaces,
|
|
8299
|
-
totalNamespaces: namespaces.length,
|
|
8300
|
-
connectedCount: namespaces.filter((n) => n.status === "connected").length,
|
|
8301
|
-
...Object.keys(conflictingTools).length > 0 && {
|
|
8302
|
-
conflictingTools,
|
|
8303
|
-
conflictNote: "These tools exist on multiple servers. Use qualified names (namespace:tool_name) to disambiguate."
|
|
8304
|
-
}
|
|
8305
|
-
})
|
|
8306
|
-
}
|
|
8307
|
-
]
|
|
9213
|
+
content: offloaded.inlineContent,
|
|
9214
|
+
isError: false,
|
|
9215
|
+
...structuredContent != null ? { structuredContent } : {}
|
|
8308
9216
|
};
|
|
8309
|
-
}
|
|
8310
|
-
const responseTime = Date.now() - startTime;
|
|
8311
|
-
this.statsCollector.endRequest(requestId, success, responseTime, "list_namespaces", "mcp-squared");
|
|
8312
|
-
}
|
|
8313
|
-
}));
|
|
8314
|
-
}
|
|
8315
|
-
formatToolsForDetailLevel(tools, level) {
|
|
8316
|
-
switch (level) {
|
|
8317
|
-
case "L0":
|
|
8318
|
-
return tools.map((t) => ({
|
|
8319
|
-
name: t.name,
|
|
8320
|
-
serverKey: t.serverKey,
|
|
8321
|
-
...t.requiresConfirmation && { requiresConfirmation: true }
|
|
8322
|
-
}));
|
|
8323
|
-
case "L2": {
|
|
8324
|
-
return tools.map((t) => {
|
|
8325
|
-
const { tool } = this.cataloger.findTool(`${t.serverKey}:${t.name}`);
|
|
8326
|
-
return {
|
|
8327
|
-
name: t.name,
|
|
8328
|
-
description: t.description,
|
|
8329
|
-
serverKey: t.serverKey,
|
|
8330
|
-
inputSchema: tool?.inputSchema ?? { type: "object" },
|
|
8331
|
-
...t.requiresConfirmation && { requiresConfirmation: true }
|
|
8332
|
-
};
|
|
8333
|
-
});
|
|
8334
|
-
}
|
|
8335
|
-
default:
|
|
8336
|
-
return tools.map((t) => ({
|
|
8337
|
-
name: t.name,
|
|
8338
|
-
description: t.description,
|
|
8339
|
-
serverKey: t.serverKey,
|
|
8340
|
-
...t.requiresConfirmation && { requiresConfirmation: true }
|
|
8341
|
-
}));
|
|
9217
|
+
} catch {}
|
|
8342
9218
|
}
|
|
9219
|
+
return {
|
|
9220
|
+
content: normalizedContent,
|
|
9221
|
+
isError: result.isError ?? false,
|
|
9222
|
+
...structuredContent != null ? { structuredContent } : {}
|
|
9223
|
+
};
|
|
8343
9224
|
}
|
|
8344
9225
|
syncIndex() {
|
|
8345
9226
|
this.retriever.syncFromCataloger();
|
|
@@ -8363,8 +9244,39 @@ class McpSquaredServer {
|
|
|
8363
9244
|
getToolStats(limit = 100) {
|
|
8364
9245
|
return this.statsCollector.getToolStats(limit);
|
|
8365
9246
|
}
|
|
9247
|
+
async classifyNamespacesSemantic() {
|
|
9248
|
+
const generator = this.retriever.getEmbeddingGenerator();
|
|
9249
|
+
if (!generator) {
|
|
9250
|
+
console.error("[mcp\xB2] Hybrid inference: embeddings not available, falling back to heuristic.");
|
|
9251
|
+
return;
|
|
9252
|
+
}
|
|
9253
|
+
try {
|
|
9254
|
+
const { SemanticCapabilityClassifier: SemanticCapabilityClassifier2 } = await Promise.resolve().then(() => (init_semantic_classifier(), exports_semantic_classifier));
|
|
9255
|
+
const threshold = this.config.operations.dynamicToolSurface.semanticConfidenceThreshold;
|
|
9256
|
+
const classifier = new SemanticCapabilityClassifier2(generator, {
|
|
9257
|
+
confidenceThreshold: threshold
|
|
9258
|
+
});
|
|
9259
|
+
await classifier.initializeReferences();
|
|
9260
|
+
const status = this.cataloger.getStatus();
|
|
9261
|
+
const inventories = [...status.entries()].filter(([, info]) => info.status === "connected").map(([namespace]) => ({
|
|
9262
|
+
namespace,
|
|
9263
|
+
tools: this.cataloger.getToolsForServer(namespace)
|
|
9264
|
+
}));
|
|
9265
|
+
const result = await classifier.classifyBatch(inventories);
|
|
9266
|
+
this.computedCapabilityOverrides = result.overrides;
|
|
9267
|
+
const count = Object.keys(result.overrides).length;
|
|
9268
|
+
console.error(`[mcp\xB2] Hybrid inference: classified ${count}/${inventories.length} namespaces semantically (${Math.round(result.inferenceMs)}ms).`);
|
|
9269
|
+
} catch (err) {
|
|
9270
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9271
|
+
console.error(`[mcp\xB2] Hybrid inference: classification failed \u2014 ${message}. Falling back to heuristic.`);
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
8366
9274
|
async start() {
|
|
8367
9275
|
await this.startCore();
|
|
9276
|
+
if (!this.baseToolsRegistered) {
|
|
9277
|
+
this.registerConfiguredToolSurface(this.mcpServer);
|
|
9278
|
+
this.baseToolsRegistered = true;
|
|
9279
|
+
}
|
|
8368
9280
|
this.transport = new StdioServerTransport2;
|
|
8369
9281
|
await this.mcpServer.connect(this.transport);
|
|
8370
9282
|
this.statsCollector.incrementActiveConnections();
|
|
@@ -8402,6 +9314,9 @@ class McpSquaredServer {
|
|
|
8402
9314
|
console.error(`[mcp\xB2] Embeddings: initialization failed \u2014 ${message}. Falling back to fast (FTS5) search.`);
|
|
8403
9315
|
}
|
|
8404
9316
|
}
|
|
9317
|
+
if (this.config.operations.dynamicToolSurface.inference === "hybrid") {
|
|
9318
|
+
await this.classifyNamespacesSemantic();
|
|
9319
|
+
}
|
|
8405
9320
|
this.statsCollector.updateIndexRefreshTime(Date.now());
|
|
8406
9321
|
this.indexRefreshManager.start();
|
|
8407
9322
|
await this.monitorServer.start();
|
|
@@ -8429,6 +9344,250 @@ class McpSquaredServer {
|
|
|
8429
9344
|
}
|
|
8430
9345
|
}
|
|
8431
9346
|
|
|
9347
|
+
// src/status/runner.ts
|
|
9348
|
+
init_inference();
|
|
9349
|
+
|
|
9350
|
+
// src/utils/context-stats.ts
|
|
9351
|
+
var CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
9352
|
+
function estimateTokens(text) {
|
|
9353
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
|
|
9354
|
+
}
|
|
9355
|
+
function reconstructCapabilityToolMetadata(router) {
|
|
9356
|
+
return {
|
|
9357
|
+
name: router.capability,
|
|
9358
|
+
title: capabilityTitle(router.capability),
|
|
9359
|
+
description: capabilitySummary(router.capability),
|
|
9360
|
+
annotations: {
|
|
9361
|
+
readOnlyHint: false,
|
|
9362
|
+
destructiveHint: false,
|
|
9363
|
+
openWorldHint: true
|
|
9364
|
+
},
|
|
9365
|
+
inputSchema: {
|
|
9366
|
+
type: "object",
|
|
9367
|
+
properties: {
|
|
9368
|
+
action: {
|
|
9369
|
+
type: "string",
|
|
9370
|
+
description: `Action ID for ${router.capability}. Use "__describe_actions" to inspect available actions and schemas.`
|
|
9371
|
+
},
|
|
9372
|
+
arguments: {
|
|
9373
|
+
type: "object",
|
|
9374
|
+
additionalProperties: {},
|
|
9375
|
+
default: {},
|
|
9376
|
+
description: "Arguments for the selected capability action"
|
|
9377
|
+
},
|
|
9378
|
+
confirmation_token: {
|
|
9379
|
+
type: "string",
|
|
9380
|
+
description: "Optional confirmation token for actions that require explicit confirmation"
|
|
9381
|
+
}
|
|
9382
|
+
},
|
|
9383
|
+
required: ["action"]
|
|
9384
|
+
}
|
|
9385
|
+
};
|
|
9386
|
+
}
|
|
9387
|
+
function computeContextStats(upstreamTools, routers) {
|
|
9388
|
+
const withoutTokens = upstreamTools.reduce((sum, tool) => {
|
|
9389
|
+
const serialized = JSON.stringify({
|
|
9390
|
+
name: tool.name,
|
|
9391
|
+
description: tool.description,
|
|
9392
|
+
inputSchema: tool.inputSchema
|
|
9393
|
+
});
|
|
9394
|
+
return sum + estimateTokens(serialized);
|
|
9395
|
+
}, 0);
|
|
9396
|
+
const activeRouters = routers.filter((r) => r.actions.length > 0);
|
|
9397
|
+
const withTokens = activeRouters.reduce((sum, router) => {
|
|
9398
|
+
const meta = reconstructCapabilityToolMetadata(router);
|
|
9399
|
+
return sum + estimateTokens(JSON.stringify(meta));
|
|
9400
|
+
}, 0);
|
|
9401
|
+
const saved = withoutTokens - withTokens;
|
|
9402
|
+
const percent = withoutTokens > 0 ? saved / withoutTokens * 100 : 0;
|
|
9403
|
+
return {
|
|
9404
|
+
withoutMcp2Tokens: withoutTokens,
|
|
9405
|
+
withMcp2Tokens: withTokens,
|
|
9406
|
+
savedTokens: saved,
|
|
9407
|
+
savedPercent: Math.round(percent * 10) / 10,
|
|
9408
|
+
upstreamToolCount: upstreamTools.length,
|
|
9409
|
+
capabilityToolCount: activeRouters.length
|
|
9410
|
+
};
|
|
9411
|
+
}
|
|
9412
|
+
|
|
9413
|
+
// src/status/runner.ts
|
|
9414
|
+
var GREEN = "\x1B[32m";
|
|
9415
|
+
var YELLOW = "\x1B[33m";
|
|
9416
|
+
var RED = "\x1B[31m";
|
|
9417
|
+
var DIM = "\x1B[90m";
|
|
9418
|
+
var BOLD = "\x1B[1m";
|
|
9419
|
+
var RESET = "\x1B[0m";
|
|
9420
|
+
async function collectStatus(config) {
|
|
9421
|
+
const cataloger = new Cataloger({ connectTimeoutMs: 15000 });
|
|
9422
|
+
const upstreams = [];
|
|
9423
|
+
try {
|
|
9424
|
+
await cataloger.connectAll(config);
|
|
9425
|
+
const status = cataloger.getStatus();
|
|
9426
|
+
for (const [name, serverConfig] of Object.entries(config.upstreams)) {
|
|
9427
|
+
if (!serverConfig.enabled) {
|
|
9428
|
+
upstreams.push({
|
|
9429
|
+
name,
|
|
9430
|
+
enabled: false,
|
|
9431
|
+
status: "disconnected",
|
|
9432
|
+
toolCount: 0
|
|
9433
|
+
});
|
|
9434
|
+
continue;
|
|
9435
|
+
}
|
|
9436
|
+
const connStatus = status.get(name);
|
|
9437
|
+
const connection = cataloger.getConnection(name);
|
|
9438
|
+
upstreams.push({
|
|
9439
|
+
name,
|
|
9440
|
+
enabled: true,
|
|
9441
|
+
status: connStatus?.status ?? "disconnected",
|
|
9442
|
+
error: connStatus?.error,
|
|
9443
|
+
toolCount: connection?.tools.length ?? 0,
|
|
9444
|
+
serverName: connection?.serverName,
|
|
9445
|
+
serverVersion: connection?.serverVersion
|
|
9446
|
+
});
|
|
9447
|
+
}
|
|
9448
|
+
const inventories = [...status.entries()].filter(([, info]) => info.status === "connected").map(([namespace]) => ({
|
|
9449
|
+
namespace,
|
|
9450
|
+
title: config.upstreams[namespace]?.label ?? namespace,
|
|
9451
|
+
tools: cataloger.getToolsForServer(namespace)
|
|
9452
|
+
})).sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
9453
|
+
let routers = [];
|
|
9454
|
+
if (inventories.length > 0) {
|
|
9455
|
+
const overrides = config.operations.dynamicToolSurface.capabilityOverrides ?? {};
|
|
9456
|
+
const grouping = groupNamespacesByCapability(inventories, overrides);
|
|
9457
|
+
routers = buildCapabilityRouters(inventories, grouping);
|
|
9458
|
+
}
|
|
9459
|
+
const allUpstreamTools = inventories.flatMap((inv) => inv.tools);
|
|
9460
|
+
const contextStats = computeContextStats(allUpstreamTools, routers);
|
|
9461
|
+
return { upstreams, routers, contextStats };
|
|
9462
|
+
} finally {
|
|
9463
|
+
await cataloger.disconnectAll();
|
|
9464
|
+
}
|
|
9465
|
+
}
|
|
9466
|
+
function formatStatus(result, options) {
|
|
9467
|
+
const lines = [];
|
|
9468
|
+
lines.push("");
|
|
9469
|
+
lines.push(`${BOLD}MCP\xB2 Status Report${RESET}`);
|
|
9470
|
+
lines.push("");
|
|
9471
|
+
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}`);
|
|
9472
|
+
if (result.upstreams.length === 0) {
|
|
9473
|
+
lines.push(` ${DIM}(no upstreams configured)${RESET}`);
|
|
9474
|
+
}
|
|
9475
|
+
for (const upstream of result.upstreams) {
|
|
9476
|
+
if (!upstream.enabled) {
|
|
9477
|
+
lines.push(` ${DIM}\u2298 ${upstream.name.padEnd(24)} disabled${RESET}`);
|
|
9478
|
+
continue;
|
|
9479
|
+
}
|
|
9480
|
+
if (upstream.status === "connected") {
|
|
9481
|
+
const toolInfo = `(${upstream.toolCount} tool${upstream.toolCount !== 1 ? "s" : ""})`;
|
|
9482
|
+
const version = upstream.serverVersion ? ` ${DIM}v${upstream.serverVersion}${RESET}` : "";
|
|
9483
|
+
lines.push(` ${GREEN}\u2713${RESET} ${upstream.name.padEnd(24)} connected ${toolInfo}${version}`);
|
|
9484
|
+
} else if (upstream.status === "needs_auth") {
|
|
9485
|
+
const errorMsg = upstream.error ?? "Authentication required";
|
|
9486
|
+
lines.push(` ${YELLOW}\u26A0${RESET} ${upstream.name.padEnd(24)} needs auth ${DIM}${errorMsg}${RESET}`);
|
|
9487
|
+
} else if (upstream.status === "error") {
|
|
9488
|
+
const errorMsg = upstream.error ?? "Unknown error";
|
|
9489
|
+
lines.push(` ${RED}\u2717${RESET} ${upstream.name.padEnd(24)} error ${DIM}${errorMsg}${RESET}`);
|
|
9490
|
+
} else {
|
|
9491
|
+
lines.push(` ${DIM}? ${upstream.name.padEnd(24)} ${upstream.status}${RESET}`);
|
|
9492
|
+
}
|
|
9493
|
+
}
|
|
9494
|
+
lines.push("");
|
|
9495
|
+
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}`);
|
|
9496
|
+
if (result.routers.length === 0) {
|
|
9497
|
+
lines.push(` ${DIM}(no capabilities routed \u2014 no connected upstreams)${RESET}`);
|
|
9498
|
+
}
|
|
9499
|
+
let totalActions = 0;
|
|
9500
|
+
for (const router of result.routers) {
|
|
9501
|
+
const count = router.actions.length;
|
|
9502
|
+
totalActions += count;
|
|
9503
|
+
const plural = count !== 1 ? "s" : "";
|
|
9504
|
+
lines.push(` ${BOLD}${router.capability}${RESET} ${DIM}(${count} action${plural})${RESET}`);
|
|
9505
|
+
if (count === 0) {
|
|
9506
|
+
lines.push(` ${DIM}(no actions)${RESET}`);
|
|
9507
|
+
}
|
|
9508
|
+
for (const action of router.actions) {
|
|
9509
|
+
const actionName = action.action.padEnd(28);
|
|
9510
|
+
const mapping = `${DIM}\u2192${RESET} ${action.qualifiedName}`;
|
|
9511
|
+
if (options.verbose) {
|
|
9512
|
+
const schemaKeys = Object.keys(action.inputSchema?.properties ?? {});
|
|
9513
|
+
const params = schemaKeys.length > 0 ? ` ${DIM}(${schemaKeys.join(", ")})${RESET}` : "";
|
|
9514
|
+
lines.push(` ${actionName} ${mapping}${params}`);
|
|
9515
|
+
} else {
|
|
9516
|
+
lines.push(` ${actionName} ${mapping}`);
|
|
9517
|
+
}
|
|
9518
|
+
}
|
|
9519
|
+
lines.push("");
|
|
9520
|
+
}
|
|
9521
|
+
if (options.verbose && result.contextStats) {
|
|
9522
|
+
const cs = result.contextStats;
|
|
9523
|
+
if (cs.upstreamToolCount > 0) {
|
|
9524
|
+
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}`);
|
|
9525
|
+
const fmt = (n) => n.toLocaleString("en-US");
|
|
9526
|
+
const withoutLabel = `Without MCP\xB2:`;
|
|
9527
|
+
const withLabel = `With MCP\xB2:`;
|
|
9528
|
+
const savedLabel = `Saved:`;
|
|
9529
|
+
lines.push(` ${withoutLabel.padEnd(16)} ${fmt(cs.withoutMcp2Tokens).padStart(8)} tokens ${DIM}(${cs.upstreamToolCount} tool${cs.upstreamToolCount !== 1 ? "s" : ""})${RESET}`);
|
|
9530
|
+
lines.push(` ${withLabel.padEnd(16)} ${fmt(cs.withMcp2Tokens).padStart(8)} tokens ${DIM}(${cs.capabilityToolCount} tool${cs.capabilityToolCount !== 1 ? "s" : ""})${RESET}`);
|
|
9531
|
+
if (cs.savedTokens > 0) {
|
|
9532
|
+
lines.push(` ${DIM}${"\u2500".repeat(40)}${RESET}`);
|
|
9533
|
+
lines.push(` ${GREEN}${savedLabel.padEnd(16)} ${fmt(cs.savedTokens).padStart(8)} tokens (${cs.savedPercent}%)${RESET}`);
|
|
9534
|
+
}
|
|
9535
|
+
lines.push("");
|
|
9536
|
+
}
|
|
9537
|
+
}
|
|
9538
|
+
const connected = result.upstreams.filter((u) => u.status === "connected").length;
|
|
9539
|
+
const needsAuth = result.upstreams.filter((u) => u.status === "needs_auth").length;
|
|
9540
|
+
const errors = result.upstreams.filter((u) => u.status === "error").length;
|
|
9541
|
+
const disabled = result.upstreams.filter((u) => !u.enabled).length;
|
|
9542
|
+
const capCount = result.routers.filter((r) => r.actions.length > 0).length;
|
|
9543
|
+
const parts = [];
|
|
9544
|
+
if (connected > 0)
|
|
9545
|
+
parts.push(`${GREEN}${connected} connected${RESET}`);
|
|
9546
|
+
if (needsAuth > 0)
|
|
9547
|
+
parts.push(`${YELLOW}${needsAuth} needs auth${RESET}`);
|
|
9548
|
+
if (errors > 0)
|
|
9549
|
+
parts.push(`${RED}${errors} error${RESET}`);
|
|
9550
|
+
if (disabled > 0)
|
|
9551
|
+
parts.push(`${DIM}${disabled} disabled${RESET}`);
|
|
9552
|
+
const actionSummary = totalActions > 0 ? `${totalActions} action${totalActions !== 1 ? "s" : ""} across ${capCount} capabilit${capCount !== 1 ? "ies" : "y"}` : "no actions";
|
|
9553
|
+
lines.push(`${DIM}Summary:${RESET} ${parts.join(", ")} | ${actionSummary}`);
|
|
9554
|
+
lines.push("");
|
|
9555
|
+
return lines.join(`
|
|
9556
|
+
`);
|
|
9557
|
+
}
|
|
9558
|
+
async function runStatus(options) {
|
|
9559
|
+
let config;
|
|
9560
|
+
let configPath;
|
|
9561
|
+
try {
|
|
9562
|
+
const loaded = await loadConfig();
|
|
9563
|
+
config = loaded.config;
|
|
9564
|
+
configPath = loaded.path;
|
|
9565
|
+
} catch (err) {
|
|
9566
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9567
|
+
console.error(`Error loading configuration: ${message}`);
|
|
9568
|
+
console.error("Run 'mcp-squared config' to create or fix your configuration.");
|
|
9569
|
+
process.exit(1);
|
|
9570
|
+
}
|
|
9571
|
+
const upstreamEntries = Object.entries(config.upstreams);
|
|
9572
|
+
if (upstreamEntries.length === 0) {
|
|
9573
|
+
console.error("Error: No upstreams configured. Run 'mcp-squared config' to add one.");
|
|
9574
|
+
process.exit(1);
|
|
9575
|
+
}
|
|
9576
|
+
const validationIssues = validateConfig(config);
|
|
9577
|
+
if (validationIssues.length > 0) {
|
|
9578
|
+
console.error(formatValidationIssues(validationIssues));
|
|
9579
|
+
console.error("");
|
|
9580
|
+
}
|
|
9581
|
+
if (options.verbose && configPath) {
|
|
9582
|
+
console.log(`${DIM}Config: ${configPath}${RESET}`);
|
|
9583
|
+
}
|
|
9584
|
+
console.log("Connecting to upstream servers...");
|
|
9585
|
+
const result = await collectStatus(config);
|
|
9586
|
+
result.configPath = configPath;
|
|
9587
|
+
const output = formatStatus(result, options);
|
|
9588
|
+
console.log(output);
|
|
9589
|
+
}
|
|
9590
|
+
|
|
8432
9591
|
// src/index.ts
|
|
8433
9592
|
function logSecurityProfile(config) {
|
|
8434
9593
|
const { allow, confirm } = config.security.tools;
|
|
@@ -9153,6 +10312,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
9153
10312
|
case "monitor":
|
|
9154
10313
|
await runMonitor(args.monitor);
|
|
9155
10314
|
break;
|
|
10315
|
+
case "status":
|
|
10316
|
+
await runStatus({ verbose: args.testVerbose });
|
|
10317
|
+
break;
|
|
9156
10318
|
case "daemon":
|
|
9157
10319
|
await runDaemon(args.daemon);
|
|
9158
10320
|
break;
|