mcp-squared 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -123,6 +123,274 @@ function ensureConfigDir(configPath) {
123
123
  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";
124
124
  var init_paths = () => {};
125
125
 
126
+ // src/capabilities/inference.ts
127
+ function createEmptyScores() {
128
+ return CAPABILITY_IDS.reduce((acc, capability) => {
129
+ acc[capability] = 0;
130
+ return acc;
131
+ }, {});
132
+ }
133
+ function extractSchemaSignal(schema) {
134
+ if (!schema || schema.type !== "object") {
135
+ return "";
136
+ }
137
+ const keys = Object.keys(schema.properties ?? {});
138
+ const required = schema.required ?? [];
139
+ return `${keys.join(" ")} ${required.join(" ")}`;
140
+ }
141
+ function scoreTextSignals(scores, text) {
142
+ for (const capability of CAPABILITY_IDS) {
143
+ for (const pattern of CAPABILITY_PATTERNS[capability]) {
144
+ if (pattern.test(text)) {
145
+ scores[capability] += 4;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ function getHighestScoringCapability(scores) {
151
+ const bestScore = Math.max(...Object.values(scores));
152
+ if (bestScore <= 0) {
153
+ return "general";
154
+ }
155
+ for (const capability of CAPABILITY_PRIORITY) {
156
+ if (scores[capability] === bestScore) {
157
+ return capability;
158
+ }
159
+ }
160
+ return "general";
161
+ }
162
+ function inferNamespaceCapability(namespace, tools, capabilityOverrides = {}) {
163
+ const override = capabilityOverrides[namespace];
164
+ if (override) {
165
+ return override;
166
+ }
167
+ const scores = createEmptyScores();
168
+ const namespaceText = namespace.toLowerCase();
169
+ for (const hint of NAMESPACE_HINTS) {
170
+ if (hint.pattern.test(namespaceText)) {
171
+ scores[hint.capability] += hint.score;
172
+ }
173
+ }
174
+ for (const tool of tools) {
175
+ const signal = [
176
+ tool.name,
177
+ tool.description ?? "",
178
+ extractSchemaSignal(tool.inputSchema)
179
+ ].join(" ").toLowerCase();
180
+ scoreTextSignals(scores, signal);
181
+ }
182
+ return getHighestScoringCapability(scores);
183
+ }
184
+ function groupNamespacesByCapability(inventories, capabilityOverrides = {}) {
185
+ const grouped = CAPABILITY_IDS.reduce((acc, capability) => {
186
+ acc[capability] = [];
187
+ return acc;
188
+ }, {});
189
+ const byNamespace = {};
190
+ const sorted = [...inventories].sort((a, b) => a.namespace.localeCompare(b.namespace));
191
+ for (const inventory of sorted) {
192
+ const capability = inferNamespaceCapability(inventory.namespace, inventory.tools, capabilityOverrides);
193
+ byNamespace[inventory.namespace] = capability;
194
+ grouped[capability].push(inventory.namespace);
195
+ }
196
+ return { byNamespace, grouped };
197
+ }
198
+ var CAPABILITY_IDS, CAPABILITY_PRIORITY, NAMESPACE_HINTS, CAPABILITY_PATTERNS;
199
+ var init_inference = __esm(() => {
200
+ CAPABILITY_IDS = [
201
+ "code_search",
202
+ "docs",
203
+ "browser_automation",
204
+ "issue_tracking",
205
+ "cms_content",
206
+ "design",
207
+ "ai_media_generation",
208
+ "hosting_deploy",
209
+ "time_util",
210
+ "research",
211
+ "general"
212
+ ];
213
+ CAPABILITY_PRIORITY = [
214
+ "code_search",
215
+ "docs",
216
+ "browser_automation",
217
+ "issue_tracking",
218
+ "cms_content",
219
+ "design",
220
+ "ai_media_generation",
221
+ "hosting_deploy",
222
+ "time_util",
223
+ "research",
224
+ "general"
225
+ ];
226
+ NAMESPACE_HINTS = [
227
+ {
228
+ capability: "code_search",
229
+ pattern: /(auggie|augment|ctxdb|code|source|repo|symbol|search)/i,
230
+ score: 24
231
+ },
232
+ {
233
+ capability: "docs",
234
+ pattern: /(context7|ref|docs?|documentation|shadcn)/i,
235
+ score: 18
236
+ },
237
+ {
238
+ capability: "browser_automation",
239
+ pattern: /(chrome|devtools|browser|playwright|puppeteer|webdriver)/i,
240
+ score: 24
241
+ },
242
+ {
243
+ capability: "issue_tracking",
244
+ pattern: /(linear|jira|issue|ticket|project|milestone)/i,
245
+ score: 20
246
+ },
247
+ {
248
+ capability: "cms_content",
249
+ pattern: /(sanity|content|cms|dataset|schema|studio)/i,
250
+ score: 20
251
+ },
252
+ {
253
+ capability: "design",
254
+ pattern: /(pencil|figma|ui|design|artifact|visual)/i,
255
+ score: 20
256
+ },
257
+ {
258
+ capability: "hosting_deploy",
259
+ pattern: /(vercel|host|hosting|domain|dns|vps|deploy|infra)/i,
260
+ score: 22
261
+ },
262
+ {
263
+ capability: "time_util",
264
+ pattern: /(time|timezone|clock|date|utc)/i,
265
+ score: 22
266
+ },
267
+ {
268
+ capability: "ai_media_generation",
269
+ pattern: /(wavespeed|stability|replicate|midjourney|dall.?e|runway|imagen|flux|fal\.ai|dreamstudio)/i,
270
+ score: 24
271
+ },
272
+ {
273
+ capability: "research",
274
+ pattern: /(exa|perplexity|firecrawl|crawl|scrape|research|search)/i,
275
+ score: 16
276
+ }
277
+ ];
278
+ CAPABILITY_PATTERNS = {
279
+ code_search: [
280
+ /\bcodebase\b/i,
281
+ /\bsource\b/i,
282
+ /\brepo(?:sitory)?\b/i,
283
+ /\bsymbol(?:s)?\b/i,
284
+ /\bdefinition(?:s)?\b/i,
285
+ /\breference(?:s)?\b/i,
286
+ /\busage(?:s)?\b/i,
287
+ /\bsearch_context\b/i,
288
+ /\bcodebase-retrieval\b/i,
289
+ /\bdirectory_path\b/i
290
+ ],
291
+ docs: [
292
+ /\bdoc(?:s|umentation)?\b/i,
293
+ /\bread_docs\b/i,
294
+ /\bquery-docs\b/i,
295
+ /\breference\b/i,
296
+ /\bmanual\b/i,
297
+ /\bknowledge\b/i,
298
+ /\bregist(?:ry|ries)\b/i,
299
+ /\bcomponent(?:s)?\b/i,
300
+ /\bexample(?:s)?\b/i
301
+ ],
302
+ browser_automation: [
303
+ /\bbrowser\b/i,
304
+ /\bdevtools\b/i,
305
+ /\bnavigate\b/i,
306
+ /\bclick\b/i,
307
+ /\bhover\b/i,
308
+ /\bnetwork\b/i,
309
+ /\bconsole\b/i,
310
+ /\bscreenshot\b/i,
311
+ /\bpage\b/i
312
+ ],
313
+ issue_tracking: [
314
+ /\bissue(?:s)?\b/i,
315
+ /\bticket(?:s)?\b/i,
316
+ /\bmilestone(?:s)?\b/i,
317
+ /\bcycle(?:s)?\b/i,
318
+ /\bproject(?:s)?\b/i,
319
+ /\bcomment(?:s)?\b/i,
320
+ /\blinear\b/i
321
+ ],
322
+ cms_content: [
323
+ /\bcms\b/i,
324
+ /\bcontent\b/i,
325
+ /\bdocument(?:s)?\b/i,
326
+ /\bdataset(?:s)?\b/i,
327
+ /\bschema\b/i,
328
+ /\bpublish\b/i,
329
+ /\bdraft(?:s)?\b/i,
330
+ /\bsanity\b/i,
331
+ /\bmigration\b/i
332
+ ],
333
+ design: [
334
+ /\bdesign\b/i,
335
+ /\bui\b/i,
336
+ /\bartifact\b/i,
337
+ /\bstyle_guide\b/i,
338
+ /\bscreenshot\b/i,
339
+ /\bdiagram\b/i,
340
+ /\bimage\b/i,
341
+ /\blayout\b/i,
342
+ /\bframe\b/i
343
+ ],
344
+ ai_media_generation: [
345
+ /\btext.to.image\b/i,
346
+ /\bimage.to.image\b/i,
347
+ /\btext.to.video\b/i,
348
+ /\binpaint(?:ing)?\b/i,
349
+ /\bupscal(?:e|ing)\b/i,
350
+ /\bgenerat(?:e|ion)\b.*\bimage/i,
351
+ /\bimage\b.*\bgenerat(?:e|ion)\b/i,
352
+ /\bai\b.*\b(?:image|photo|video)\b/i,
353
+ /\bstable.diffusion\b/i,
354
+ /\bdiffusion\b/i,
355
+ /\bprompt\b.*\b(?:image|visual)\b/i
356
+ ],
357
+ hosting_deploy: [
358
+ /\bdeploy(?:ment)?\b/i,
359
+ /\bhosting\b/i,
360
+ /\bdomain(?:s)?\b/i,
361
+ /\bdns\b/i,
362
+ /\bvps\b/i,
363
+ /\bvirtual_machine(?:s)?\b/i,
364
+ /\bfirewall\b/i,
365
+ /\bwebsite\b/i,
366
+ /\bbilling\b/i,
367
+ /\bnameserver(?:s)?\b/i
368
+ ],
369
+ time_util: [
370
+ /\btime\b/i,
371
+ /\btimezone\b/i,
372
+ /\butc\b/i,
373
+ /\bclock\b/i,
374
+ /\bdate\b/i,
375
+ /\bconvert_time\b/i,
376
+ /\bget_current_time\b/i
377
+ ],
378
+ research: [
379
+ /\bresearch\b/i,
380
+ /\bcrawl\b/i,
381
+ /\bscrape\b/i,
382
+ /\bextract\b/i,
383
+ /\bsemantic_search\b/i,
384
+ /\bweb_search\b/i,
385
+ /\bask\b/i,
386
+ /\bperplexity\b/i,
387
+ /\bfirecrawl\b/i,
388
+ /\bexa\b/i
389
+ ],
390
+ general: []
391
+ };
392
+ });
393
+
126
394
  // src/tui/config.ts
127
395
  import {
128
396
  ASCIIFontRenderable,
@@ -378,6 +646,7 @@ import { parse as parseToml } from "smol-toml";
378
646
  import { ZodError } from "zod";
379
647
 
380
648
  // src/config/schema.ts
649
+ init_inference();
381
650
  import { z } from "zod";
382
651
  var LATEST_SCHEMA_VERSION = 1;
383
652
  var LogLevelSchema = z.enum([
@@ -434,6 +703,18 @@ var SecuritySchema = z.object({
434
703
  });
435
704
  var SearchModeSchema = z.enum(["fast", "semantic", "hybrid"]);
436
705
  var DetailLevelSchema = z.enum(["L0", "L1", "L2"]);
706
+ var CapabilityIdSchema = z.enum(CAPABILITY_IDS);
707
+ var DynamicToolSurfaceInferenceSchema = z.enum([
708
+ "heuristic_with_overrides",
709
+ "hybrid"
710
+ ]);
711
+ var DynamicToolSurfaceRefreshSchema = z.enum(["on_connect"]);
712
+ var DynamicToolSurfaceSchema = z.object({
713
+ inference: DynamicToolSurfaceInferenceSchema.default("heuristic_with_overrides"),
714
+ refresh: DynamicToolSurfaceRefreshSchema.default("on_connect"),
715
+ capabilityOverrides: z.record(z.string().min(1), CapabilityIdSchema).default({}),
716
+ semanticConfidenceThreshold: z.number().min(0).max(1).default(0.45)
717
+ });
437
718
  var PreferredNamespacesByIntentSchema = z.object({
438
719
  codeSearch: z.array(z.string().min(1)).default([])
439
720
  });
@@ -475,6 +756,12 @@ var OperationsSchema = z.object({
475
756
  enabled: true,
476
757
  minCooccurrenceThreshold: 2,
477
758
  maxBundleSuggestions: 3
759
+ }),
760
+ dynamicToolSurface: DynamicToolSurfaceSchema.default({
761
+ inference: "heuristic_with_overrides",
762
+ refresh: "on_connect",
763
+ capabilityOverrides: {},
764
+ semanticConfidenceThreshold: 0.45
478
765
  })
479
766
  }).default({
480
767
  findTools: {
@@ -491,6 +778,12 @@ var OperationsSchema = z.object({
491
778
  enabled: true,
492
779
  minCooccurrenceThreshold: 2,
493
780
  maxBundleSuggestions: 3
781
+ },
782
+ dynamicToolSurface: {
783
+ inference: "heuristic_with_overrides",
784
+ refresh: "on_connect",
785
+ capabilityOverrides: {},
786
+ semanticConfidenceThreshold: 0.45
494
787
  }
495
788
  });
496
789
  var ConfigSchema = z.object({
@@ -544,6 +837,34 @@ function migrateV0ToV1(config) {
544
837
 
545
838
  // src/config/load.ts
546
839
  init_paths();
840
+ function isRecord(value) {
841
+ return typeof value === "object" && value !== null;
842
+ }
843
+ function getDeprecatedDynamicToolSurfaceKeys(raw) {
844
+ const operations = raw["operations"];
845
+ if (!isRecord(operations)) {
846
+ return [];
847
+ }
848
+ const dynamicToolSurface = operations["dynamicToolSurface"];
849
+ if (!isRecord(dynamicToolSurface)) {
850
+ return [];
851
+ }
852
+ const deprecated = [];
853
+ if ("mode" in dynamicToolSurface) {
854
+ deprecated.push("operations.dynamicToolSurface.mode");
855
+ }
856
+ if ("naming" in dynamicToolSurface) {
857
+ deprecated.push("operations.dynamicToolSurface.naming");
858
+ }
859
+ return deprecated;
860
+ }
861
+ function warnDeprecatedDynamicToolSurfaceKeys(filePath, keys) {
862
+ if (keys.length === 0) {
863
+ return;
864
+ }
865
+ console.warn(`[mcp\xB2] Deprecated config keys in ${filePath}: ${keys.join(", ")}. Run 'mcp-squared migrate' to clean up legacy settings.`);
866
+ }
867
+
547
868
  class ConfigError extends Error {
548
869
  cause;
549
870
  constructor(message, cause) {
@@ -611,6 +932,7 @@ async function loadConfigFromPath(filePath, source) {
611
932
  } catch (err) {
612
933
  throw new ConfigParseError(filePath, err);
613
934
  }
935
+ warnDeprecatedDynamicToolSurfaceKeys(filePath, getDeprecatedDynamicToolSurfaceKeys(rawConfig));
614
936
  const migrated = migrateConfig(rawConfig);
615
937
  let config;
616
938
  try {
@@ -1473,60 +1795,68 @@ function cleanupExpiredTokens() {
1473
1795
  }
1474
1796
  }
1475
1797
  }
1476
- function validateConfirmationToken(token, serverKey, toolName) {
1798
+ function validateConfirmationToken(token, capability, action) {
1477
1799
  cleanupExpiredTokens();
1478
1800
  const confirmation = pendingConfirmations.get(token);
1479
1801
  if (!confirmation) {
1480
1802
  return false;
1481
1803
  }
1482
- if (confirmation.serverKey !== serverKey || confirmation.toolName !== toolName) {
1804
+ if (confirmation.capability !== capability || confirmation.action !== action) {
1483
1805
  return false;
1484
1806
  }
1485
1807
  pendingConfirmations.delete(token);
1486
1808
  return true;
1487
1809
  }
1488
- function createConfirmationToken(serverKey, toolName) {
1810
+ function createConfirmationToken(capability, action) {
1489
1811
  cleanupExpiredTokens();
1490
1812
  const token = generateToken();
1491
1813
  pendingConfirmations.set(token, {
1492
- serverKey,
1493
- toolName,
1814
+ capability,
1815
+ action,
1494
1816
  createdAt: Date.now()
1495
1817
  });
1496
1818
  return token;
1497
1819
  }
1498
1820
  function evaluatePolicy(context, config) {
1499
- const { serverKey, toolName, confirmationToken } = context;
1821
+ const capability = context.capability ?? context.serverKey;
1822
+ const action = context.action ?? context.toolName;
1823
+ const confirmationToken = context.confirmationToken;
1500
1824
  const { block, confirm, allow } = config.security.tools;
1501
- if (matchesAnyPattern(block, serverKey, toolName)) {
1825
+ if (!capability || !action) {
1502
1826
  return {
1503
1827
  decision: "block",
1504
- reason: `Tool "${toolName}" on server "${serverKey}" is blocked by security policy`
1828
+ reason: "Missing capability/action in security policy context"
1505
1829
  };
1506
1830
  }
1507
- if (matchesAnyPattern(allow, serverKey, toolName)) {
1831
+ if (matchesAnyPattern(block, capability, action)) {
1832
+ return {
1833
+ decision: "block",
1834
+ reason: `Action "${action}" in capability "${capability}" is blocked by security policy`
1835
+ };
1836
+ }
1837
+ if (matchesAnyPattern(allow, capability, action)) {
1508
1838
  return {
1509
1839
  decision: "allow",
1510
- reason: `Tool "${toolName}" is allowed by security policy`
1840
+ reason: `Action "${action}" is allowed by security policy`
1511
1841
  };
1512
1842
  }
1513
- if (matchesAnyPattern(confirm, serverKey, toolName)) {
1514
- if (confirmationToken && validateConfirmationToken(confirmationToken, serverKey, toolName)) {
1843
+ if (matchesAnyPattern(confirm, capability, action)) {
1844
+ if (confirmationToken && validateConfirmationToken(confirmationToken, capability, action)) {
1515
1845
  return {
1516
1846
  decision: "allow",
1517
- reason: `Tool "${toolName}" confirmed with valid token`
1847
+ reason: `Action "${action}" confirmed with valid token`
1518
1848
  };
1519
1849
  }
1520
- const token = createConfirmationToken(serverKey, toolName);
1850
+ const token = createConfirmationToken(capability, action);
1521
1851
  return {
1522
1852
  decision: "confirm",
1523
- reason: `Tool "${toolName}" on server "${serverKey}" requires confirmation`,
1853
+ reason: `Action "${action}" in capability "${capability}" requires confirmation`,
1524
1854
  confirmationToken: token
1525
1855
  };
1526
1856
  }
1527
1857
  return {
1528
1858
  decision: "block",
1529
- reason: `Tool "${toolName}" on server "${serverKey}" is not in the allow or confirm list`
1859
+ reason: `Action "${action}" in capability "${capability}" is not in the allow or confirm list`
1530
1860
  };
1531
1861
  }
1532
1862
  function compilePolicy(config) {
@@ -1685,6 +2015,26 @@ async function waitForProcessExit(proc, timeoutMs) {
1685
2015
  }
1686
2016
 
1687
2017
  // src/upstream/cataloger.ts
2018
+ var AUTH_ERROR_PATTERNS = [
2019
+ "invalid_token",
2020
+ "invalid token",
2021
+ "unauthorized",
2022
+ "no token provided",
2023
+ "no authorization",
2024
+ "api key",
2025
+ "api_key",
2026
+ "authentication required",
2027
+ "authentication failed",
2028
+ "invalid credentials",
2029
+ "invalid api",
2030
+ "forbidden",
2031
+ "access denied",
2032
+ "not authenticated"
2033
+ ];
2034
+ function isAuthError(message) {
2035
+ const lower = message.toLowerCase();
2036
+ return AUTH_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
2037
+ }
1688
2038
  function resolveEnvVars(env) {
1689
2039
  const resolved = {};
1690
2040
  for (const [key, value] of Object.entries(env)) {
@@ -1765,7 +2115,7 @@ class Cataloger {
1765
2115
  if (err instanceof UnauthorizedError2 && connection.authProvider) {
1766
2116
  if (connection.authProvider.isNonInteractive()) {
1767
2117
  connection.authPending = true;
1768
- connection.status = "error";
2118
+ connection.status = "needs_auth";
1769
2119
  connection.error = `OAuth authorization required. Run: mcp-squared auth ${key}`;
1770
2120
  return;
1771
2121
  }
@@ -1789,8 +2139,9 @@ class Cataloger {
1789
2139
  }));
1790
2140
  connection.status = "connected";
1791
2141
  } catch (err) {
1792
- connection.status = "error";
1793
- connection.error = err instanceof Error ? err.message : String(err);
2142
+ const errorMsg = err instanceof Error ? err.message : String(err);
2143
+ connection.status = isAuthError(errorMsg) ? "needs_auth" : "error";
2144
+ connection.error = errorMsg;
1794
2145
  try {
1795
2146
  await this.cleanupConnection(connection);
1796
2147
  } catch (_cleanupErr) {}
@@ -1952,13 +2303,14 @@ class Cataloger {
1952
2303
  if (err instanceof UnauthorizedError2 && connection.authProvider) {
1953
2304
  if (connection.authProvider.isNonInteractive()) {
1954
2305
  connection.authPending = true;
1955
- connection.status = "error";
2306
+ connection.status = "needs_auth";
1956
2307
  connection.error = `OAuth authorization required. Run: mcp-squared auth ${key}`;
1957
2308
  return;
1958
2309
  }
1959
2310
  }
1960
- connection.status = "error";
1961
- connection.error = err instanceof Error ? err.message : String(err);
2311
+ const errorMsg = err instanceof Error ? err.message : String(err);
2312
+ connection.status = isAuthError(errorMsg) ? "needs_auth" : "error";
2313
+ connection.error = errorMsg;
1962
2314
  }
1963
2315
  }
1964
2316
  async refreshAllTools() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-squared",
3
- "version": "0.4.0",
3
+ "version": "0.6.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",
@@ -60,10 +60,13 @@
60
60
  "url": "https://github.com/aditzel/mcp-squared"
61
61
  },
62
62
  "dependencies": {
63
+ "@hono/node-server": "^1.19.11",
63
64
  "@huggingface/transformers": "^3.8.1",
64
65
  "@modelcontextprotocol/sdk": "^1.27.1",
65
66
  "@opentui/core": "^0.1.82",
67
+ "hono": "^4.12.5",
66
68
  "smol-toml": "^1.6.0",
69
+ "tar": "^7.5.10",
67
70
  "yaml": "^2.8.2",
68
71
  "zod": "^4.3.6"
69
72
  },
@@ -72,10 +75,11 @@
72
75
  "@opentelemetry/sdk-trace-base": "^1.30.1"
73
76
  },
74
77
  "overrides": {
78
+ "@hono/node-server": "^1.19.11",
75
79
  "ajv": "^8.18.0",
76
80
  "diff": "8.0.3",
77
- "hono": "^4.11.10",
81
+ "hono": "^4.12.5",
78
82
  "qs": "6.15.0",
79
- "tar": "7.5.9"
83
+ "tar": "^7.5.10"
80
84
  }
81
85
  }