mcp-squared 0.3.4 → 0.4.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 +29 -0
- package/dist/index.js +229 -24
- package/dist/tui/config.js +11 -3
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -72,6 +72,9 @@ bun run build
|
|
|
72
72
|
# Run tests
|
|
73
73
|
bun test
|
|
74
74
|
|
|
75
|
+
# Evaluate tool-routing behavior (discovery-first quality check)
|
|
76
|
+
bun run eval:routing
|
|
77
|
+
|
|
75
78
|
# Dependency audit
|
|
76
79
|
bun run audit
|
|
77
80
|
```
|
|
@@ -103,6 +106,7 @@ mcp-squared config # Launch configuration TUI
|
|
|
103
106
|
mcp-squared test [upstream] # Test upstream server connections
|
|
104
107
|
mcp-squared auth <upstream> # OAuth auth for SSE/HTTP upstreams
|
|
105
108
|
mcp-squared import # Import MCP configs from other tools
|
|
109
|
+
mcp-squared migrate # Apply one-time config migrations
|
|
106
110
|
mcp-squared install # Install MCP² into other MCP clients
|
|
107
111
|
mcp-squared monitor # Launch server monitor TUI
|
|
108
112
|
mcp-squared --help # Full command reference
|
|
@@ -159,6 +163,23 @@ auth = true
|
|
|
159
163
|
|
|
160
164
|
Security policies (allow/block/confirm) live under `security.tools`. Confirmation flows return a short-lived token that must be provided to `execute` to proceed. OAuth tokens for SSE upstreams are stored under `~/.config/mcp-squared/tokens/<upstream>.json`.
|
|
161
165
|
|
|
166
|
+
`mcp-squared init` now seeds code-search routing preferences so `find_tools` prioritizes common code indexers by default:
|
|
167
|
+
|
|
168
|
+
```toml
|
|
169
|
+
[operations.findTools.preferredNamespacesByIntent]
|
|
170
|
+
codeSearch = ["auggie", "ctxdb"]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Tune this list (or set it to `[]`) if your environment uses different namespaces.
|
|
174
|
+
|
|
175
|
+
For existing configs created before this default, run:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
mcp-squared migrate
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Use `mcp-squared migrate --dry-run` to preview without writing.
|
|
182
|
+
|
|
162
183
|
## Tool API (Meta-Tools)
|
|
163
184
|
|
|
164
185
|
MCP² exposes these tools to MCP clients:
|
|
@@ -168,6 +189,14 @@ MCP² exposes these tools to MCP clients:
|
|
|
168
189
|
- `list_namespaces` - List upstream namespaces (optionally with tool names)
|
|
169
190
|
- `clear_selection_cache` - Reset co-occurrence based suggestions
|
|
170
191
|
|
|
192
|
+
Recommended workflow for LLM clients:
|
|
193
|
+
1. Call `find_tools` first to discover candidate tools for the task.
|
|
194
|
+
2. Call `describe_tools` for selected candidates to confirm exact argument schemas.
|
|
195
|
+
3. Call `execute` with a qualified tool name (`namespace:tool_name`).
|
|
196
|
+
4. Use `list_namespaces` if tool names are ambiguous or you need namespace context.
|
|
197
|
+
|
|
198
|
+
For codebase-search tasks, `find_tools` applies intent-aware ranking and may return namespace guidance (for example preferring configured code-search namespaces like `auggie`).
|
|
199
|
+
|
|
171
200
|
## Search Modes
|
|
172
201
|
|
|
173
202
|
`find_tools` supports three search modes:
|
package/dist/index.js
CHANGED
|
@@ -354,6 +354,11 @@ maxLimit = 50
|
|
|
354
354
|
defaultMode = "fast"
|
|
355
355
|
defaultDetailLevel = "L1"
|
|
356
356
|
|
|
357
|
+
[operations.findTools.preferredNamespacesByIntent]
|
|
358
|
+
# Default-on code-search namespace preference.
|
|
359
|
+
# Adjust or clear if your stack uses different code indexers.
|
|
360
|
+
codeSearch = ["auggie", "ctxdb"]
|
|
361
|
+
|
|
357
362
|
[operations.embeddings]
|
|
358
363
|
# Enable to use semantic or hybrid search modes.
|
|
359
364
|
# Requires onnxruntime shared library on the system.
|
|
@@ -455,6 +460,9 @@ function parseArgs(args) {
|
|
|
455
460
|
project: false,
|
|
456
461
|
force: false
|
|
457
462
|
},
|
|
463
|
+
migrate: {
|
|
464
|
+
dryRun: false
|
|
465
|
+
},
|
|
458
466
|
install: {
|
|
459
467
|
interactive: true,
|
|
460
468
|
dryRun: false,
|
|
@@ -511,6 +519,9 @@ function parseArgs(args) {
|
|
|
511
519
|
case "init":
|
|
512
520
|
result.mode = "init";
|
|
513
521
|
break;
|
|
522
|
+
case "migrate":
|
|
523
|
+
result.mode = "migrate";
|
|
524
|
+
break;
|
|
514
525
|
case "monitor":
|
|
515
526
|
result.mode = "monitor";
|
|
516
527
|
break;
|
|
@@ -561,6 +572,7 @@ function parseArgs(args) {
|
|
|
561
572
|
case "--dry-run":
|
|
562
573
|
result.import.dryRun = true;
|
|
563
574
|
result.install.dryRun = true;
|
|
575
|
+
result.migrate.dryRun = true;
|
|
564
576
|
break;
|
|
565
577
|
case "--no-interactive":
|
|
566
578
|
result.import.interactive = false;
|
|
@@ -685,6 +697,7 @@ Usage:
|
|
|
685
697
|
mcp-squared auth <upstream> Authenticate with an OAuth-protected upstream
|
|
686
698
|
mcp-squared import [options] Import MCP configs from other tools
|
|
687
699
|
mcp-squared init [options] Generate a starter config file with security profile
|
|
700
|
+
mcp-squared migrate [options] Apply config migrations to existing config
|
|
688
701
|
mcp-squared install [options] Install MCP\xB2 into other MCP clients
|
|
689
702
|
mcp-squared monitor [options] Launch server monitor TUI
|
|
690
703
|
mcp-squared daemon [options] Start shared MCP\xB2 daemon
|
|
@@ -698,6 +711,7 @@ Commands:
|
|
|
698
711
|
auth <name> Authenticate with an OAuth-protected upstream
|
|
699
712
|
import Import MCP server configs from other tools
|
|
700
713
|
init Generate a starter config with security profile
|
|
714
|
+
migrate Apply one-time config migrations to existing config
|
|
701
715
|
install Install MCP\xB2 as a server in other MCP clients
|
|
702
716
|
monitor Launch server monitor TUI
|
|
703
717
|
daemon Start shared daemon for multiple clients
|
|
@@ -724,6 +738,9 @@ Init Options:
|
|
|
724
738
|
--project Write to project-local mcp-squared.toml (default: user-level)
|
|
725
739
|
--force Overwrite existing config without prompting
|
|
726
740
|
|
|
741
|
+
Migrate Options:
|
|
742
|
+
--dry-run Preview migration changes without writing
|
|
743
|
+
|
|
727
744
|
Install Options:
|
|
728
745
|
--tool=<tool> Target tool (skip selection prompt)
|
|
729
746
|
--scope=<scope> Scope: user or project
|
|
@@ -760,6 +777,8 @@ Examples:
|
|
|
760
777
|
mcp-squared init Generate hardened config (confirm-all by default)
|
|
761
778
|
mcp-squared init --security=permissive Generate permissive config (allow-all)
|
|
762
779
|
mcp-squared init --project Generate project-local config
|
|
780
|
+
mcp-squared migrate Apply config migrations to current config file
|
|
781
|
+
mcp-squared migrate --dry-run Preview config migrations without writing
|
|
763
782
|
mcp-squared import --list List all discovered MCP configs
|
|
764
783
|
mcp-squared import --dry-run Preview import changes
|
|
765
784
|
mcp-squared import Import with interactive conflict resolution
|
|
@@ -1076,11 +1095,17 @@ var SecuritySchema = z.object({
|
|
|
1076
1095
|
});
|
|
1077
1096
|
var SearchModeSchema = z.enum(["fast", "semantic", "hybrid"]);
|
|
1078
1097
|
var DetailLevelSchema = z.enum(["L0", "L1", "L2"]);
|
|
1098
|
+
var PreferredNamespacesByIntentSchema = z.object({
|
|
1099
|
+
codeSearch: z.array(z.string().min(1)).default([])
|
|
1100
|
+
});
|
|
1079
1101
|
var FindToolsSchema = z.object({
|
|
1080
1102
|
defaultLimit: z.number().int().min(1).default(5),
|
|
1081
1103
|
maxLimit: z.number().int().min(1).max(200).default(50),
|
|
1082
1104
|
defaultMode: SearchModeSchema.default("fast"),
|
|
1083
|
-
defaultDetailLevel: DetailLevelSchema.default("L1")
|
|
1105
|
+
defaultDetailLevel: DetailLevelSchema.default("L1"),
|
|
1106
|
+
preferredNamespacesByIntent: PreferredNamespacesByIntentSchema.default({
|
|
1107
|
+
codeSearch: []
|
|
1108
|
+
})
|
|
1084
1109
|
});
|
|
1085
1110
|
var IndexSchema = z.object({
|
|
1086
1111
|
refreshIntervalMs: z.number().int().min(1000).default(30000)
|
|
@@ -1101,7 +1126,8 @@ var OperationsSchema = z.object({
|
|
|
1101
1126
|
defaultLimit: 5,
|
|
1102
1127
|
maxLimit: 50,
|
|
1103
1128
|
defaultMode: "fast",
|
|
1104
|
-
defaultDetailLevel: "L1"
|
|
1129
|
+
defaultDetailLevel: "L1",
|
|
1130
|
+
preferredNamespacesByIntent: { codeSearch: [] }
|
|
1105
1131
|
}),
|
|
1106
1132
|
index: IndexSchema.default({ refreshIntervalMs: 30000 }),
|
|
1107
1133
|
logging: LoggingSchema.default({ level: "info" }),
|
|
@@ -1116,7 +1142,8 @@ var OperationsSchema = z.object({
|
|
|
1116
1142
|
defaultLimit: 5,
|
|
1117
1143
|
maxLimit: 50,
|
|
1118
1144
|
defaultMode: "fast",
|
|
1119
|
-
defaultDetailLevel: "L1"
|
|
1145
|
+
defaultDetailLevel: "L1",
|
|
1146
|
+
preferredNamespacesByIntent: { codeSearch: [] }
|
|
1120
1147
|
},
|
|
1121
1148
|
index: { refreshIntervalMs: 30000 },
|
|
1122
1149
|
logging: { level: "info" },
|
|
@@ -4277,6 +4304,81 @@ Installation cancelled.`);
|
|
|
4277
4304
|
process.exit(0);
|
|
4278
4305
|
}
|
|
4279
4306
|
|
|
4307
|
+
// src/migrate/runner.ts
|
|
4308
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4309
|
+
import { parse as parseToml4 } from "smol-toml";
|
|
4310
|
+
var DEFAULT_CODE_SEARCH_NAMESPACES = ["auggie", "ctxdb"];
|
|
4311
|
+
function isRecord(value) {
|
|
4312
|
+
return typeof value === "object" && value !== null;
|
|
4313
|
+
}
|
|
4314
|
+
function hasOwn(obj, key) {
|
|
4315
|
+
return Object.hasOwn(obj, key);
|
|
4316
|
+
}
|
|
4317
|
+
function isCodeSearchExplicitlyConfigured(rawConfig) {
|
|
4318
|
+
if (!isRecord(rawConfig)) {
|
|
4319
|
+
return false;
|
|
4320
|
+
}
|
|
4321
|
+
const operations = rawConfig["operations"];
|
|
4322
|
+
if (!isRecord(operations)) {
|
|
4323
|
+
return false;
|
|
4324
|
+
}
|
|
4325
|
+
const findTools = operations["findTools"];
|
|
4326
|
+
if (!isRecord(findTools)) {
|
|
4327
|
+
return false;
|
|
4328
|
+
}
|
|
4329
|
+
const preferredNamespacesByIntent = findTools["preferredNamespacesByIntent"];
|
|
4330
|
+
if (!isRecord(preferredNamespacesByIntent)) {
|
|
4331
|
+
return false;
|
|
4332
|
+
}
|
|
4333
|
+
return hasOwn(preferredNamespacesByIntent, "codeSearch");
|
|
4334
|
+
}
|
|
4335
|
+
function applyCodeSearchPreferenceMigration(config, options) {
|
|
4336
|
+
if (options.codeSearchExplicitlyConfigured) {
|
|
4337
|
+
return { config, changed: false };
|
|
4338
|
+
}
|
|
4339
|
+
return {
|
|
4340
|
+
changed: true,
|
|
4341
|
+
config: {
|
|
4342
|
+
...config,
|
|
4343
|
+
operations: {
|
|
4344
|
+
...config.operations,
|
|
4345
|
+
findTools: {
|
|
4346
|
+
...config.operations.findTools,
|
|
4347
|
+
preferredNamespacesByIntent: {
|
|
4348
|
+
...config.operations.findTools.preferredNamespacesByIntent,
|
|
4349
|
+
codeSearch: [...DEFAULT_CODE_SEARCH_NAMESPACES]
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4356
|
+
async function runMigrate(args) {
|
|
4357
|
+
const discovered = discoverConfigPath();
|
|
4358
|
+
if (!discovered) {
|
|
4359
|
+
console.error("No configuration file found. Run 'mcp-squared init' to create one first.");
|
|
4360
|
+
process.exit(1);
|
|
4361
|
+
}
|
|
4362
|
+
const loaded = await loadConfigFromPath(discovered.path, discovered.source);
|
|
4363
|
+
const rawConfig = parseToml4(readFileSync5(discovered.path, "utf-8"));
|
|
4364
|
+
const codeSearchExplicitlyConfigured = isCodeSearchExplicitlyConfigured(rawConfig);
|
|
4365
|
+
const { config: migratedConfig, changed } = applyCodeSearchPreferenceMigration(loaded.config, {
|
|
4366
|
+
codeSearchExplicitlyConfigured
|
|
4367
|
+
});
|
|
4368
|
+
if (!changed) {
|
|
4369
|
+
console.log(`No migration needed: code-search preferences already configured in ${discovered.path}`);
|
|
4370
|
+
return;
|
|
4371
|
+
}
|
|
4372
|
+
if (args.dryRun) {
|
|
4373
|
+
console.log(`[dry-run] Would update ${discovered.path}`);
|
|
4374
|
+
console.log('[dry-run] Set operations.findTools.preferredNamespacesByIntent.codeSearch = ["auggie", "ctxdb"]');
|
|
4375
|
+
return;
|
|
4376
|
+
}
|
|
4377
|
+
await saveConfig(discovered.path, migratedConfig);
|
|
4378
|
+
console.log(`Updated ${discovered.path}`);
|
|
4379
|
+
console.log('Set operations.findTools.preferredNamespacesByIntent.codeSearch = ["auggie", "ctxdb"]');
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4280
4382
|
// src/oauth/browser.ts
|
|
4281
4383
|
import { spawn as spawn2 } from "child_process";
|
|
4282
4384
|
async function openBrowser(url) {
|
|
@@ -4664,7 +4766,7 @@ import {
|
|
|
4664
4766
|
chmodSync,
|
|
4665
4767
|
existsSync as existsSync8,
|
|
4666
4768
|
mkdirSync as mkdirSync5,
|
|
4667
|
-
readFileSync as
|
|
4769
|
+
readFileSync as readFileSync6,
|
|
4668
4770
|
unlinkSync as unlinkSync4,
|
|
4669
4771
|
writeFileSync as writeFileSync4
|
|
4670
4772
|
} from "fs";
|
|
@@ -4702,7 +4804,7 @@ class TokenStorage {
|
|
|
4702
4804
|
return;
|
|
4703
4805
|
}
|
|
4704
4806
|
try {
|
|
4705
|
-
const content =
|
|
4807
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
4706
4808
|
return JSON.parse(content);
|
|
4707
4809
|
} catch {
|
|
4708
4810
|
return;
|
|
@@ -4890,6 +4992,15 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
4890
4992
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4891
4993
|
import { z as z3 } from "zod";
|
|
4892
4994
|
|
|
4995
|
+
// agent_safety_kit/guard/errors.ts
|
|
4996
|
+
class PolicyDenied extends Error {
|
|
4997
|
+
decision;
|
|
4998
|
+
constructor(message, decision) {
|
|
4999
|
+
super(message);
|
|
5000
|
+
this.name = "PolicyDenied";
|
|
5001
|
+
this.decision = decision;
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
4893
5004
|
// agent_safety_kit/policy/matchers.ts
|
|
4894
5005
|
var GLOB_SPECIALS = /[.+^${}()|[\]\\]/g;
|
|
4895
5006
|
var PATH_KEY_RE = /(path|file|dir|cwd|workspace|root)/i;
|
|
@@ -5027,16 +5138,6 @@ function valuesConstrainedByGlob(values, allowlist) {
|
|
|
5027
5138
|
return values.every((value) => matchesAnyGlob(allowlist, value));
|
|
5028
5139
|
}
|
|
5029
5140
|
|
|
5030
|
-
// agent_safety_kit/guard/errors.ts
|
|
5031
|
-
class PolicyDenied extends Error {
|
|
5032
|
-
decision;
|
|
5033
|
-
constructor(message, decision) {
|
|
5034
|
-
super(message);
|
|
5035
|
-
this.name = "PolicyDenied";
|
|
5036
|
-
this.decision = decision;
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
5039
|
-
|
|
5040
5141
|
// agent_safety_kit/guard/ratelimit.ts
|
|
5041
5142
|
class SlidingWindowRateLimiter {
|
|
5042
5143
|
requests = new Map;
|
|
@@ -5246,7 +5347,7 @@ class Guard {
|
|
|
5246
5347
|
}
|
|
5247
5348
|
}
|
|
5248
5349
|
// agent_safety_kit/policy/load.ts
|
|
5249
|
-
import { existsSync as existsSync9, readFileSync as
|
|
5350
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
5250
5351
|
import { resolve as resolve3 } from "path";
|
|
5251
5352
|
import { parse as parseYaml } from "yaml";
|
|
5252
5353
|
|
|
@@ -5309,7 +5410,7 @@ function load_policy(options = {}) {
|
|
|
5309
5410
|
if (!existsSync9(sourcePath)) {
|
|
5310
5411
|
throw new Error(`Agent safety policy file not found at ${sourcePath}`);
|
|
5311
5412
|
}
|
|
5312
|
-
const raw =
|
|
5413
|
+
const raw = readFileSync7(sourcePath, "utf8");
|
|
5313
5414
|
const parsed = parseYaml(raw);
|
|
5314
5415
|
const policy = AgentPolicySchema.parse(parsed);
|
|
5315
5416
|
const playbookName = options.playbook ?? envConfig.playbook;
|
|
@@ -5329,6 +5430,7 @@ function load_policy(options = {}) {
|
|
|
5329
5430
|
rules: playbook.rules
|
|
5330
5431
|
};
|
|
5331
5432
|
}
|
|
5433
|
+
|
|
5332
5434
|
// agent_safety_kit/observability/sinks/null.ts
|
|
5333
5435
|
class NullSpan {
|
|
5334
5436
|
setAttributes(_attributes) {}
|
|
@@ -7767,11 +7869,80 @@ class McpSquaredServer {
|
|
|
7767
7869
|
this.registerMetaTools(this.mcpServer);
|
|
7768
7870
|
}
|
|
7769
7871
|
createMcpServer(name, version) {
|
|
7770
|
-
return new McpServer({
|
|
7872
|
+
return new McpServer({
|
|
7873
|
+
name,
|
|
7874
|
+
version,
|
|
7875
|
+
title: "MCP\xB2 Meta Router",
|
|
7876
|
+
description: "Discover and execute tools across upstream MCP servers through a compact meta-tool interface."
|
|
7877
|
+
}, {
|
|
7771
7878
|
capabilities: {
|
|
7772
7879
|
tools: {}
|
|
7880
|
+
},
|
|
7881
|
+
instructions: this.buildServerInstructions()
|
|
7882
|
+
});
|
|
7883
|
+
}
|
|
7884
|
+
buildServerInstructions() {
|
|
7885
|
+
const codeSearchNamespaces = this.getCodeSearchNamespaces();
|
|
7886
|
+
const codeSearchHint = codeSearchNamespaces.length > 0 ? ` Prefer these configured code-search namespaces when relevant: ${codeSearchNamespaces.join(", ")}.` : "";
|
|
7887
|
+
return [
|
|
7888
|
+
"Use discovery-first workflow: call `find_tools` before defaulting to local shell discovery (for example `grep`/`rg`).",
|
|
7889
|
+
`For codebase lookup tasks, start with \`find_tools\` queries such as "code search", "find symbol", or "references".${codeSearchHint}`,
|
|
7890
|
+
"After choosing a candidate, call `describe_tools` to confirm schema, then call `execute` with a qualified tool name (`namespace:tool_name`).",
|
|
7891
|
+
"If capabilities are unclear or a name is ambiguous, call `list_namespaces`."
|
|
7892
|
+
].join(" ");
|
|
7893
|
+
}
|
|
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;
|
|
7773
7929
|
}
|
|
7930
|
+
return b.score - a.score;
|
|
7774
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;
|
|
7775
7946
|
}
|
|
7776
7947
|
createSessionServer() {
|
|
7777
7948
|
const server = this.createMcpServer(this.serverName, this.serverVersion);
|
|
@@ -7797,9 +7968,14 @@ class McpSquaredServer {
|
|
|
7797
7968
|
}
|
|
7798
7969
|
registerMetaTools(server) {
|
|
7799
7970
|
server.registerTool("find_tools", {
|
|
7800
|
-
|
|
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
|
+
},
|
|
7801
7977
|
inputSchema: {
|
|
7802
|
-
query: z3.string().describe(
|
|
7978
|
+
query: z3.string().describe('Natural language query describing the task (for example: "code search", "find symbol", "create issue")'),
|
|
7803
7979
|
limit: z3.number().int().min(1).max(this.maxLimit).default(this.config.operations.findTools.defaultLimit).describe("Maximum number of results to return"),
|
|
7804
7980
|
mode: SearchModeSchema.optional().describe('Search mode: "fast" (FTS5), "semantic" (embeddings), or "hybrid" (FTS5 + rerank)'),
|
|
7805
7981
|
detail_level: DetailLevelSchema.optional().describe('Level of detail: "L0" (name only), "L1" (summary with description, default), "L2" (full schema)')
|
|
@@ -7814,8 +7990,10 @@ class McpSquaredServer {
|
|
|
7814
7990
|
mode: args.mode
|
|
7815
7991
|
});
|
|
7816
7992
|
const filteredTools = this.filterToolsByPolicy(result.tools);
|
|
7993
|
+
const rankedTools = this.rankToolsForQuery(filteredTools, args.query);
|
|
7817
7994
|
const detailLevel = args.detail_level ?? this.config.operations.findTools.defaultDetailLevel;
|
|
7818
|
-
const tools = this.formatToolsForDetailLevel(
|
|
7995
|
+
const tools = this.formatToolsForDetailLevel(rankedTools, detailLevel);
|
|
7996
|
+
const guidance = this.buildFindToolsGuidance(args.query);
|
|
7819
7997
|
const selectionCacheConfig = this.config.operations.selectionCache;
|
|
7820
7998
|
let suggestedTools;
|
|
7821
7999
|
if (selectionCacheConfig.enabled && selectionCacheConfig.maxBundleSuggestions > 0) {
|
|
@@ -7840,6 +8018,7 @@ class McpSquaredServer {
|
|
|
7840
8018
|
searchMode: result.searchMode,
|
|
7841
8019
|
embeddingsAvailable: this.retriever.hasEmbeddings(),
|
|
7842
8020
|
tools,
|
|
8021
|
+
guidance,
|
|
7843
8022
|
...suggestedTools && { suggestedTools }
|
|
7844
8023
|
})
|
|
7845
8024
|
}
|
|
@@ -7851,7 +8030,12 @@ class McpSquaredServer {
|
|
|
7851
8030
|
}
|
|
7852
8031
|
}));
|
|
7853
8032
|
server.registerTool("describe_tools", {
|
|
7854
|
-
|
|
8033
|
+
title: "Inspect Tool Schemas",
|
|
8034
|
+
description: "After find_tools, fetch full JSON schemas for selected tools to validate required arguments before execution.",
|
|
8035
|
+
annotations: {
|
|
8036
|
+
readOnlyHint: true,
|
|
8037
|
+
openWorldHint: false
|
|
8038
|
+
},
|
|
7855
8039
|
inputSchema: {
|
|
7856
8040
|
tool_names: z3.array(z3.string()).min(1).max(20).describe("List of tool names to get schemas for")
|
|
7857
8041
|
}
|
|
@@ -7897,7 +8081,13 @@ class McpSquaredServer {
|
|
|
7897
8081
|
}
|
|
7898
8082
|
}));
|
|
7899
8083
|
server.registerTool("execute", {
|
|
7900
|
-
|
|
8084
|
+
title: "Execute Upstream Tool",
|
|
8085
|
+
description: "Execute an upstream tool after find_tools/describe_tools selection. The tool must exist and the arguments must match its schema.",
|
|
8086
|
+
annotations: {
|
|
8087
|
+
readOnlyHint: false,
|
|
8088
|
+
destructiveHint: false,
|
|
8089
|
+
openWorldHint: true
|
|
8090
|
+
},
|
|
7901
8091
|
inputSchema: {
|
|
7902
8092
|
tool_name: z3.string().describe("Name of the tool to execute"),
|
|
7903
8093
|
arguments: z3.record(z3.string(), z3.unknown()).default({}).describe("Arguments to pass to the tool"),
|
|
@@ -8028,7 +8218,14 @@ class McpSquaredServer {
|
|
|
8028
8218
|
}
|
|
8029
8219
|
}));
|
|
8030
8220
|
server.registerTool("clear_selection_cache", {
|
|
8221
|
+
title: "Reset Selection Cache",
|
|
8031
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
|
+
},
|
|
8032
8229
|
inputSchema: {}
|
|
8033
8230
|
}, async () => this.runTaskSpan("clear_selection_cache", async () => {
|
|
8034
8231
|
const requestId = this.statsCollector.startRequest();
|
|
@@ -8056,7 +8253,12 @@ class McpSquaredServer {
|
|
|
8056
8253
|
}
|
|
8057
8254
|
}));
|
|
8058
8255
|
server.registerTool("list_namespaces", {
|
|
8059
|
-
|
|
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
|
+
},
|
|
8060
8262
|
inputSchema: {
|
|
8061
8263
|
include_tools: z3.boolean().default(false).describe("If true, includes the list of tool names available in each namespace")
|
|
8062
8264
|
}
|
|
@@ -8945,6 +9147,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
8945
9147
|
await runInit2(args.init);
|
|
8946
9148
|
break;
|
|
8947
9149
|
}
|
|
9150
|
+
case "migrate":
|
|
9151
|
+
await runMigrate(args.migrate);
|
|
9152
|
+
break;
|
|
8948
9153
|
case "monitor":
|
|
8949
9154
|
await runMonitor(args.monitor);
|
|
8950
9155
|
break;
|
package/dist/tui/config.js
CHANGED
|
@@ -434,11 +434,17 @@ var SecuritySchema = z.object({
|
|
|
434
434
|
});
|
|
435
435
|
var SearchModeSchema = z.enum(["fast", "semantic", "hybrid"]);
|
|
436
436
|
var DetailLevelSchema = z.enum(["L0", "L1", "L2"]);
|
|
437
|
+
var PreferredNamespacesByIntentSchema = z.object({
|
|
438
|
+
codeSearch: z.array(z.string().min(1)).default([])
|
|
439
|
+
});
|
|
437
440
|
var FindToolsSchema = z.object({
|
|
438
441
|
defaultLimit: z.number().int().min(1).default(5),
|
|
439
442
|
maxLimit: z.number().int().min(1).max(200).default(50),
|
|
440
443
|
defaultMode: SearchModeSchema.default("fast"),
|
|
441
|
-
defaultDetailLevel: DetailLevelSchema.default("L1")
|
|
444
|
+
defaultDetailLevel: DetailLevelSchema.default("L1"),
|
|
445
|
+
preferredNamespacesByIntent: PreferredNamespacesByIntentSchema.default({
|
|
446
|
+
codeSearch: []
|
|
447
|
+
})
|
|
442
448
|
});
|
|
443
449
|
var IndexSchema = z.object({
|
|
444
450
|
refreshIntervalMs: z.number().int().min(1000).default(30000)
|
|
@@ -459,7 +465,8 @@ var OperationsSchema = z.object({
|
|
|
459
465
|
defaultLimit: 5,
|
|
460
466
|
maxLimit: 50,
|
|
461
467
|
defaultMode: "fast",
|
|
462
|
-
defaultDetailLevel: "L1"
|
|
468
|
+
defaultDetailLevel: "L1",
|
|
469
|
+
preferredNamespacesByIntent: { codeSearch: [] }
|
|
463
470
|
}),
|
|
464
471
|
index: IndexSchema.default({ refreshIntervalMs: 30000 }),
|
|
465
472
|
logging: LoggingSchema.default({ level: "info" }),
|
|
@@ -474,7 +481,8 @@ var OperationsSchema = z.object({
|
|
|
474
481
|
defaultLimit: 5,
|
|
475
482
|
maxLimit: 50,
|
|
476
483
|
defaultMode: "fast",
|
|
477
|
-
defaultDetailLevel: "L1"
|
|
484
|
+
defaultDetailLevel: "L1",
|
|
485
|
+
preferredNamespacesByIntent: { codeSearch: [] }
|
|
478
486
|
},
|
|
479
487
|
index: { refreshIntervalMs: 30000 },
|
|
480
488
|
logging: { level: "info" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-squared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP² (Mercury Control Plane) - A local-first meta-server and proxy for the Model Context Protocol",
|
|
5
5
|
"author": "aditzel",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"test": "bun test",
|
|
29
29
|
"test:fast": "SKIP_SLOW_TESTS=true bun test",
|
|
30
30
|
"test:watch": "bun test --watch",
|
|
31
|
+
"coverage:check": "bun run scripts/check-line-coverage.ts coverage/coverage-summary.txt 80",
|
|
31
32
|
"typecheck": "tsc --noEmit",
|
|
32
33
|
"lint": "biome check src tests scripts AGENTS.md CLAUDE.md WARP.md README.md CHANGELOG.md package.json biome.json tsconfig.json",
|
|
33
34
|
"release:check": "bun run audit && bun test && bun run build && bun run build:verify && bun run lint && bun run typecheck && bun pm pack --dry-run",
|
|
@@ -35,7 +36,8 @@
|
|
|
35
36
|
"lint:fix": "biome check --write .",
|
|
36
37
|
"format": "biome format --write .",
|
|
37
38
|
"clean": "rm -rf dist",
|
|
38
|
-
"safety:sim": "bun run agent_safety_kit/cost_model/simulate.ts --tasks agent_safety_kit/cost_model/tasks.csv --pricing agent_safety_kit/cost_model/pricing.csv --out agent_safety_kit/cost_model/report.md"
|
|
39
|
+
"safety:sim": "bun run agent_safety_kit/cost_model/simulate.ts --tasks agent_safety_kit/cost_model/tasks.csv --pricing agent_safety_kit/cost_model/pricing.csv --out agent_safety_kit/cost_model/report.md",
|
|
40
|
+
"eval:routing": "bun run scripts/eval-tool-routing.ts"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@biomejs/biome": "^2.4.4",
|