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 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 readFileSync5,
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 = readFileSync5(filePath, "utf-8");
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 readFileSync6 } from "fs";
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 = readFileSync6(sourcePath, "utf8");
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({ name, version }, {
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
- description: "Search for available tools across all connected upstream MCP servers. Returns a list of tool summaries matching the query.",
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("Natural language search query to find relevant tools"),
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(filteredTools, detailLevel);
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
- description: "Get full JSON schemas for the specified tools. Use this after find_tools to get detailed parameter information before calling a tool.",
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
- description: "Execute a tool on an upstream MCP server. The tool must exist and the arguments must match its schema.",
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
- description: "Lists all available namespaces (upstream MCP servers). Use this to discover available servers and understand which namespaces are available when disambiguating tool names with qualified format (namespace:tool_name).",
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;
@@ -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.4",
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",