@darkiceinteractive/mcp-conductor 2.0.0-alpha.1 → 3.0.0-beta.2

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.
Files changed (276) hide show
  1. package/README.md +35 -5
  2. package/dist/bin/cli.d.ts +20 -0
  3. package/dist/bin/cli.d.ts.map +1 -0
  4. package/dist/bin/cli.js +260 -0
  5. package/dist/bin/cli.js.map +1 -0
  6. package/dist/bridge/http-server.d.ts +35 -0
  7. package/dist/bridge/http-server.d.ts.map +1 -1
  8. package/dist/bridge/http-server.js +51 -2
  9. package/dist/bridge/http-server.js.map +1 -1
  10. package/dist/bridge/index.d.ts +1 -0
  11. package/dist/bridge/index.d.ts.map +1 -1
  12. package/dist/bridge/index.js +1 -0
  13. package/dist/bridge/index.js.map +1 -1
  14. package/dist/bridge/pool.d.ts +95 -0
  15. package/dist/bridge/pool.d.ts.map +1 -0
  16. package/dist/bridge/pool.js +384 -0
  17. package/dist/bridge/pool.js.map +1 -0
  18. package/dist/cache/cache.d.ts +64 -0
  19. package/dist/cache/cache.d.ts.map +1 -0
  20. package/dist/cache/cache.js +209 -0
  21. package/dist/cache/cache.js.map +1 -0
  22. package/dist/cache/delta.d.ts +32 -0
  23. package/dist/cache/delta.d.ts.map +1 -0
  24. package/dist/cache/delta.js +131 -0
  25. package/dist/cache/delta.js.map +1 -0
  26. package/dist/cache/disk.d.ts +65 -0
  27. package/dist/cache/disk.d.ts.map +1 -0
  28. package/dist/cache/disk.js +238 -0
  29. package/dist/cache/disk.js.map +1 -0
  30. package/dist/cache/index.d.ts +53 -0
  31. package/dist/cache/index.d.ts.map +1 -0
  32. package/dist/cache/index.js +12 -0
  33. package/dist/cache/index.js.map +1 -0
  34. package/dist/cache/key.d.ts +44 -0
  35. package/dist/cache/key.d.ts.map +1 -0
  36. package/dist/cache/key.js +83 -0
  37. package/dist/cache/key.js.map +1 -0
  38. package/dist/cache/lru.d.ts +57 -0
  39. package/dist/cache/lru.d.ts.map +1 -0
  40. package/dist/cache/lru.js +112 -0
  41. package/dist/cache/lru.js.map +1 -0
  42. package/dist/cache/policy.d.ts +34 -0
  43. package/dist/cache/policy.d.ts.map +1 -0
  44. package/dist/cache/policy.js +95 -0
  45. package/dist/cache/policy.js.map +1 -0
  46. package/dist/cli/commands/doctor.d.ts +33 -0
  47. package/dist/cli/commands/doctor.d.ts.map +1 -0
  48. package/dist/cli/commands/doctor.js +135 -0
  49. package/dist/cli/commands/doctor.js.map +1 -0
  50. package/dist/cli/commands/export-servers.d.ts +22 -0
  51. package/dist/cli/commands/export-servers.d.ts.map +1 -0
  52. package/dist/cli/commands/export-servers.js +45 -0
  53. package/dist/cli/commands/export-servers.js.map +1 -0
  54. package/dist/cli/commands/import-servers.d.ts +57 -0
  55. package/dist/cli/commands/import-servers.d.ts.map +1 -0
  56. package/dist/cli/commands/import-servers.js +137 -0
  57. package/dist/cli/commands/import-servers.js.map +1 -0
  58. package/dist/cli/commands/routing.d.ts +34 -0
  59. package/dist/cli/commands/routing.d.ts.map +1 -0
  60. package/dist/cli/commands/routing.js +60 -0
  61. package/dist/cli/commands/routing.js.map +1 -0
  62. package/dist/cli/commands/test-server.d.ts +34 -0
  63. package/dist/cli/commands/test-server.d.ts.map +1 -0
  64. package/dist/cli/commands/test-server.js +86 -0
  65. package/dist/cli/commands/test-server.js.map +1 -0
  66. package/dist/cli/daemon.d.ts +60 -0
  67. package/dist/cli/daemon.d.ts.map +1 -0
  68. package/dist/cli/daemon.js +244 -0
  69. package/dist/cli/daemon.js.map +1 -0
  70. package/dist/cli/replay.d.ts +16 -0
  71. package/dist/cli/replay.d.ts.map +1 -0
  72. package/dist/cli/replay.js +89 -0
  73. package/dist/cli/replay.js.map +1 -0
  74. package/dist/cli/wizard/setup.d.ts +12 -0
  75. package/dist/cli/wizard/setup.d.ts.map +1 -0
  76. package/dist/cli/wizard/setup.js +71 -0
  77. package/dist/cli/wizard/setup.js.map +1 -0
  78. package/dist/config/defaults.d.ts.map +1 -1
  79. package/dist/config/defaults.js +4 -1
  80. package/dist/config/defaults.js.map +1 -1
  81. package/dist/config/schema.d.ts +34 -0
  82. package/dist/config/schema.d.ts.map +1 -1
  83. package/dist/daemon/client.d.ts +99 -0
  84. package/dist/daemon/client.d.ts.map +1 -0
  85. package/dist/daemon/client.js +292 -0
  86. package/dist/daemon/client.js.map +1 -0
  87. package/dist/daemon/discovery.d.ts +50 -0
  88. package/dist/daemon/discovery.d.ts.map +1 -0
  89. package/dist/daemon/discovery.js +104 -0
  90. package/dist/daemon/discovery.js.map +1 -0
  91. package/dist/daemon/index.d.ts +16 -0
  92. package/dist/daemon/index.d.ts.map +1 -0
  93. package/dist/daemon/index.js +11 -0
  94. package/dist/daemon/index.js.map +1 -0
  95. package/dist/daemon/sandbox-api.d.ts +45 -0
  96. package/dist/daemon/sandbox-api.d.ts.map +1 -0
  97. package/dist/daemon/sandbox-api.js +74 -0
  98. package/dist/daemon/sandbox-api.js.map +1 -0
  99. package/dist/daemon/server.d.ts +65 -0
  100. package/dist/daemon/server.d.ts.map +1 -0
  101. package/dist/daemon/server.js +373 -0
  102. package/dist/daemon/server.js.map +1 -0
  103. package/dist/daemon/shared-kv.d.ts +81 -0
  104. package/dist/daemon/shared-kv.d.ts.map +1 -0
  105. package/dist/daemon/shared-kv.js +215 -0
  106. package/dist/daemon/shared-kv.js.map +1 -0
  107. package/dist/daemon/shared-lock.d.ts +71 -0
  108. package/dist/daemon/shared-lock.d.ts.map +1 -0
  109. package/dist/daemon/shared-lock.js +119 -0
  110. package/dist/daemon/shared-lock.js.map +1 -0
  111. package/dist/hub/mcp-hub.d.ts +23 -0
  112. package/dist/hub/mcp-hub.d.ts.map +1 -1
  113. package/dist/hub/mcp-hub.js +34 -1
  114. package/dist/hub/mcp-hub.js.map +1 -1
  115. package/dist/index.js +7 -0
  116. package/dist/index.js.map +1 -1
  117. package/dist/observability/anomaly.d.ts +67 -0
  118. package/dist/observability/anomaly.d.ts.map +1 -0
  119. package/dist/observability/anomaly.js +141 -0
  120. package/dist/observability/anomaly.js.map +1 -0
  121. package/dist/observability/cost-predictor.d.ts +49 -0
  122. package/dist/observability/cost-predictor.d.ts.map +1 -0
  123. package/dist/observability/cost-predictor.js +145 -0
  124. package/dist/observability/cost-predictor.js.map +1 -0
  125. package/dist/observability/hot-path.d.ts +49 -0
  126. package/dist/observability/hot-path.d.ts.map +1 -0
  127. package/dist/observability/hot-path.js +125 -0
  128. package/dist/observability/hot-path.js.map +1 -0
  129. package/dist/observability/index.d.ts +10 -0
  130. package/dist/observability/index.d.ts.map +1 -0
  131. package/dist/observability/index.js +10 -0
  132. package/dist/observability/index.js.map +1 -0
  133. package/dist/observability/replay.d.ts +104 -0
  134. package/dist/observability/replay.d.ts.map +1 -0
  135. package/dist/observability/replay.js +239 -0
  136. package/dist/observability/replay.js.map +1 -0
  137. package/dist/registry/built-in-recommendations.d.ts +54 -0
  138. package/dist/registry/built-in-recommendations.d.ts.map +1 -0
  139. package/dist/registry/built-in-recommendations.js +65 -0
  140. package/dist/registry/built-in-recommendations.js.map +1 -0
  141. package/dist/registry/events.d.ts +26 -0
  142. package/dist/registry/events.d.ts.map +1 -0
  143. package/dist/registry/events.js +22 -0
  144. package/dist/registry/events.js.map +1 -0
  145. package/dist/registry/index.d.ts +159 -0
  146. package/dist/registry/index.d.ts.map +1 -0
  147. package/dist/registry/index.js +12 -0
  148. package/dist/registry/index.js.map +1 -0
  149. package/dist/registry/registry.d.ts +87 -0
  150. package/dist/registry/registry.d.ts.map +1 -0
  151. package/dist/registry/registry.js +294 -0
  152. package/dist/registry/registry.js.map +1 -0
  153. package/dist/registry/snapshot.d.ts +42 -0
  154. package/dist/registry/snapshot.d.ts.map +1 -0
  155. package/dist/registry/snapshot.js +71 -0
  156. package/dist/registry/snapshot.js.map +1 -0
  157. package/dist/registry/typegen.d.ts +48 -0
  158. package/dist/registry/typegen.d.ts.map +1 -0
  159. package/dist/registry/typegen.js +200 -0
  160. package/dist/registry/typegen.js.map +1 -0
  161. package/dist/registry/validator.d.ts +23 -0
  162. package/dist/registry/validator.d.ts.map +1 -0
  163. package/dist/registry/validator.js +50 -0
  164. package/dist/registry/validator.js.map +1 -0
  165. package/dist/reliability/breaker.d.ts +57 -0
  166. package/dist/reliability/breaker.d.ts.map +1 -0
  167. package/dist/reliability/breaker.js +130 -0
  168. package/dist/reliability/breaker.js.map +1 -0
  169. package/dist/reliability/errors.d.ts +78 -0
  170. package/dist/reliability/errors.d.ts.map +1 -0
  171. package/dist/reliability/errors.js +160 -0
  172. package/dist/reliability/errors.js.map +1 -0
  173. package/dist/reliability/gateway.d.ts +88 -0
  174. package/dist/reliability/gateway.d.ts.map +1 -0
  175. package/dist/reliability/gateway.js +180 -0
  176. package/dist/reliability/gateway.js.map +1 -0
  177. package/dist/reliability/index.d.ts +20 -0
  178. package/dist/reliability/index.d.ts.map +1 -0
  179. package/dist/reliability/index.js +16 -0
  180. package/dist/reliability/index.js.map +1 -0
  181. package/dist/reliability/profile.d.ts +49 -0
  182. package/dist/reliability/profile.d.ts.map +1 -0
  183. package/dist/reliability/profile.js +58 -0
  184. package/dist/reliability/profile.js.map +1 -0
  185. package/dist/reliability/retry.d.ts +39 -0
  186. package/dist/reliability/retry.d.ts.map +1 -0
  187. package/dist/reliability/retry.js +51 -0
  188. package/dist/reliability/retry.js.map +1 -0
  189. package/dist/reliability/timeout.d.ts +34 -0
  190. package/dist/reliability/timeout.d.ts.map +1 -0
  191. package/dist/reliability/timeout.js +53 -0
  192. package/dist/reliability/timeout.js.map +1 -0
  193. package/dist/runtime/executor.d.ts.map +1 -1
  194. package/dist/runtime/executor.js +122 -14
  195. package/dist/runtime/executor.js.map +1 -1
  196. package/dist/runtime/findtool/embed.d.ts +28 -0
  197. package/dist/runtime/findtool/embed.d.ts.map +1 -0
  198. package/dist/runtime/findtool/embed.js +85 -0
  199. package/dist/runtime/findtool/embed.js.map +1 -0
  200. package/dist/runtime/findtool/index.d.ts +52 -0
  201. package/dist/runtime/findtool/index.d.ts.map +1 -0
  202. package/dist/runtime/findtool/index.js +78 -0
  203. package/dist/runtime/findtool/index.js.map +1 -0
  204. package/dist/runtime/findtool/vector-index.d.ts +53 -0
  205. package/dist/runtime/findtool/vector-index.d.ts.map +1 -0
  206. package/dist/runtime/findtool/vector-index.js +71 -0
  207. package/dist/runtime/findtool/vector-index.js.map +1 -0
  208. package/dist/runtime/helpers/budget.d.ts +27 -0
  209. package/dist/runtime/helpers/budget.d.ts.map +1 -0
  210. package/dist/runtime/helpers/budget.js +103 -0
  211. package/dist/runtime/helpers/budget.js.map +1 -0
  212. package/dist/runtime/helpers/compact.d.ts +32 -0
  213. package/dist/runtime/helpers/compact.d.ts.map +1 -0
  214. package/dist/runtime/helpers/compact.js +93 -0
  215. package/dist/runtime/helpers/compact.js.map +1 -0
  216. package/dist/runtime/helpers/delta.d.ts +45 -0
  217. package/dist/runtime/helpers/delta.d.ts.map +1 -0
  218. package/dist/runtime/helpers/delta.js +116 -0
  219. package/dist/runtime/helpers/delta.js.map +1 -0
  220. package/dist/runtime/helpers/index.d.ts +16 -0
  221. package/dist/runtime/helpers/index.d.ts.map +1 -0
  222. package/dist/runtime/helpers/index.js +13 -0
  223. package/dist/runtime/helpers/index.js.map +1 -0
  224. package/dist/runtime/helpers/summarize.d.ts +24 -0
  225. package/dist/runtime/helpers/summarize.d.ts.map +1 -0
  226. package/dist/runtime/helpers/summarize.js +124 -0
  227. package/dist/runtime/helpers/summarize.js.map +1 -0
  228. package/dist/runtime/helpers/worker-preload.d.ts +25 -0
  229. package/dist/runtime/helpers/worker-preload.d.ts.map +1 -0
  230. package/dist/runtime/helpers/worker-preload.js +223 -0
  231. package/dist/runtime/helpers/worker-preload.js.map +1 -0
  232. package/dist/runtime/index.d.ts +1 -0
  233. package/dist/runtime/index.d.ts.map +1 -1
  234. package/dist/runtime/index.js +1 -0
  235. package/dist/runtime/index.js.map +1 -1
  236. package/dist/runtime/pool/index.d.ts +11 -0
  237. package/dist/runtime/pool/index.d.ts.map +1 -0
  238. package/dist/runtime/pool/index.js +8 -0
  239. package/dist/runtime/pool/index.js.map +1 -0
  240. package/dist/runtime/pool/recycle.d.ts +44 -0
  241. package/dist/runtime/pool/recycle.d.ts.map +1 -0
  242. package/dist/runtime/pool/recycle.js +50 -0
  243. package/dist/runtime/pool/recycle.js.map +1 -0
  244. package/dist/runtime/pool/worker-pool.d.ts +77 -0
  245. package/dist/runtime/pool/worker-pool.d.ts.map +1 -0
  246. package/dist/runtime/pool/worker-pool.js +216 -0
  247. package/dist/runtime/pool/worker-pool.js.map +1 -0
  248. package/dist/runtime/pool/worker.d.ts +80 -0
  249. package/dist/runtime/pool/worker.d.ts.map +1 -0
  250. package/dist/runtime/pool/worker.js +324 -0
  251. package/dist/runtime/pool/worker.js.map +1 -0
  252. package/dist/server/mcp-server.d.ts +3 -0
  253. package/dist/server/mcp-server.d.ts.map +1 -1
  254. package/dist/server/mcp-server.js +457 -3
  255. package/dist/server/mcp-server.js.map +1 -1
  256. package/dist/server/passthrough-registrar.d.ts +123 -0
  257. package/dist/server/passthrough-registrar.d.ts.map +1 -0
  258. package/dist/server/passthrough-registrar.js +199 -0
  259. package/dist/server/passthrough-registrar.js.map +1 -0
  260. package/dist/skills/skills-engine.d.ts +9 -1
  261. package/dist/skills/skills-engine.d.ts.map +1 -1
  262. package/dist/skills/skills-engine.js +20 -3
  263. package/dist/skills/skills-engine.js.map +1 -1
  264. package/dist/utils/index.d.ts +1 -0
  265. package/dist/utils/index.d.ts.map +1 -1
  266. package/dist/utils/index.js +1 -0
  267. package/dist/utils/index.js.map +1 -1
  268. package/dist/utils/tokenize.d.ts +55 -0
  269. package/dist/utils/tokenize.d.ts.map +1 -0
  270. package/dist/utils/tokenize.js +205 -0
  271. package/dist/utils/tokenize.js.map +1 -0
  272. package/dist/version.d.ts +3 -3
  273. package/dist/version.d.ts.map +1 -1
  274. package/dist/version.js +3 -3
  275. package/dist/version.js.map +1 -1
  276. package/package.json +13 -3
@@ -10,6 +10,9 @@ import { logger } from '../utils/index.js';
10
10
  import { HttpBridge } from '../bridge/index.js';
11
11
  import { DenoExecutor } from '../runtime/index.js';
12
12
  import { MCPHub } from '../hub/index.js';
13
+ import { ToolRegistry } from '../registry/registry.js';
14
+ import { applyBuiltInRecommendations } from '../registry/built-in-recommendations.js';
15
+ import { registerPassthroughTools } from './passthrough-registrar.js';
13
16
  import { ModeHandler } from '../modes/index.js';
14
17
  import { MetricsCollector } from '../metrics/index.js';
15
18
  import { shutdownStreamManager } from '../streaming/index.js';
@@ -18,6 +21,13 @@ import { shutdownModeHandler } from '../modes/index.js';
18
21
  import { shutdownSkillsEngine } from '../skills/index.js';
19
22
  import { loadConductorConfig, saveConductorConfig, getDefaultConductorConfigPath } from '../config/index.js';
20
23
  import { VERSION } from '../version.js';
24
+ import { getCostPredictor, getHotPathProfiler, getAnomalyDetector, getReplayRecorder, shutdownCostPredictor, shutdownHotPathProfiler, shutdownAnomalyDetector, shutdownReplayRecorder, } from '../observability/index.js';
25
+ import { findClaudeConfigsWithServers, importServers, formatImportResults, } from '../cli/commands/import-servers.js';
26
+ import { exportToClaude } from '../cli/commands/export-servers.js';
27
+ import { testServer } from '../cli/commands/test-server.js';
28
+ import { getRoutingRecommendations } from '../cli/commands/routing.js';
29
+ import { CacheLayer } from '../cache/index.js';
30
+ import { ReliabilityGateway } from '../reliability/index.js';
21
31
  /**
22
32
  * MCP Executor Server
23
33
  */
@@ -32,6 +42,9 @@ export class MCPExecutorServer {
32
42
  config;
33
43
  useMockServers;
34
44
  currentMode;
45
+ registry;
46
+ cache;
47
+ gateway;
35
48
  // Mock server data for testing when no real servers configured
36
49
  mockServers = new Map();
37
50
  constructor(config, options) {
@@ -85,6 +98,15 @@ export class MCPExecutorServer {
85
98
  reconnectDelayMs: 5000,
86
99
  maxReconnectAttempts: 3,
87
100
  });
101
+ // Initialise the ToolRegistry backed by the hub.
102
+ // registry.refresh() + registerPassthroughTools() are called in start()
103
+ // after the hub has connected, so the registry is fully populated before
104
+ // any passthrough tool handler can be invoked.
105
+ this.registry = new ToolRegistry({ bridge: this.hub });
106
+ // Initialise cache layer (three-tier: memory LRU + disk CBOR + delta).
107
+ this.cache = new CacheLayer({ registry: this.registry });
108
+ // Initialise reliability gateway (timeout + retry + circuit breaker).
109
+ this.gateway = new ReliabilityGateway({});
88
110
  // Set up mock servers for fallback/testing
89
111
  this.setupMockServers();
90
112
  // Register tools
@@ -1306,6 +1328,373 @@ Triggers a reload to apply changes immediately.`,
1306
1328
  structuredContent: output,
1307
1329
  };
1308
1330
  });
1331
+ // Register predict_cost tool
1332
+ this.server.registerTool('predict_cost', {
1333
+ title: 'Predict Cost',
1334
+ description: 'Predict the token cost and latency of executing code based on historical samples for similar call patterns.',
1335
+ annotations: {
1336
+ readOnlyHint: true,
1337
+ idempotentHint: true,
1338
+ openWorldHint: false,
1339
+ },
1340
+ inputSchema: {
1341
+ code: z.string().describe('The code whose cost you want to estimate.'),
1342
+ },
1343
+ outputSchema: {
1344
+ estimatedInputTokens: z.number(),
1345
+ estimatedOutputTokens: z.number(),
1346
+ estimatedLatencyMs: z.number(),
1347
+ basedOn: z.number(),
1348
+ available: z.boolean(),
1349
+ },
1350
+ }, async ({ code }) => {
1351
+ const predictor = getCostPredictor();
1352
+ const prediction = predictor.predictFromCode(code);
1353
+ const output = prediction
1354
+ ? { ...prediction, available: true }
1355
+ : { estimatedInputTokens: 0, estimatedOutputTokens: 0, estimatedLatencyMs: 0, basedOn: 0, available: false };
1356
+ return {
1357
+ content: [{ type: 'text', text: JSON.stringify(output) }],
1358
+ structuredContent: output,
1359
+ };
1360
+ });
1361
+ // Register get_hot_paths tool
1362
+ this.server.registerTool('get_hot_paths', {
1363
+ title: 'Get Hot Paths',
1364
+ description: 'Return the top-K tool call paths by total latency or p99 within a rolling time window.',
1365
+ annotations: {
1366
+ readOnlyHint: true,
1367
+ idempotentHint: true,
1368
+ openWorldHint: false,
1369
+ },
1370
+ inputSchema: {
1371
+ sinceMs: z.number().optional().describe('Only include calls made in the last N milliseconds.'),
1372
+ topK: z.number().optional().describe('Maximum number of paths to return (default: 10).'),
1373
+ sortBy: z.enum(['totalLatency', 'p99', 'callCount']).optional().describe('Ranking dimension (default: totalLatency).'),
1374
+ },
1375
+ outputSchema: {
1376
+ paths: z.array(z.object({
1377
+ server: z.string(),
1378
+ tool: z.string(),
1379
+ callCount: z.number(),
1380
+ totalLatencyMs: z.number(),
1381
+ meanLatencyMs: z.number(),
1382
+ p99LatencyMs: z.number(),
1383
+ })),
1384
+ },
1385
+ }, async ({ sinceMs, topK, sortBy }) => {
1386
+ const profiler = getHotPathProfiler();
1387
+ const paths = profiler.getHotPaths({
1388
+ sinceMs,
1389
+ topK: topK ?? 10,
1390
+ sortBy: sortBy ?? 'totalLatency',
1391
+ });
1392
+ const output = { paths };
1393
+ return {
1394
+ content: [{ type: 'text', text: JSON.stringify(output) }],
1395
+ structuredContent: output,
1396
+ };
1397
+ });
1398
+ // Register record_session tool
1399
+ this.server.registerTool('record_session', {
1400
+ title: 'Record Session',
1401
+ description: 'Start recording all tool calls in the current session to a replay journal.',
1402
+ annotations: {
1403
+ readOnlyHint: false,
1404
+ idempotentHint: false,
1405
+ openWorldHint: false,
1406
+ },
1407
+ inputSchema: {
1408
+ sessionId: z.string().optional().describe('Optional session ID. A UUID is generated if omitted.'),
1409
+ },
1410
+ outputSchema: {
1411
+ sessionId: z.string(),
1412
+ recordingPath: z.string(),
1413
+ },
1414
+ }, async ({ sessionId }) => {
1415
+ const recorder = getReplayRecorder();
1416
+ const result = recorder.startRecording(sessionId);
1417
+ return {
1418
+ content: [{ type: 'text', text: JSON.stringify(result) }],
1419
+ structuredContent: result,
1420
+ };
1421
+ });
1422
+ // Register stop_recording tool
1423
+ this.server.registerTool('stop_recording', {
1424
+ title: 'Stop Recording',
1425
+ description: 'Stop an active recording session and finalise the replay journal.',
1426
+ annotations: {
1427
+ readOnlyHint: false,
1428
+ idempotentHint: false,
1429
+ openWorldHint: false,
1430
+ },
1431
+ inputSchema: {
1432
+ sessionId: z.string().describe('Session ID returned by record_session.'),
1433
+ },
1434
+ outputSchema: {
1435
+ recordingPath: z.string(),
1436
+ eventCount: z.number(),
1437
+ },
1438
+ }, async ({ sessionId }) => {
1439
+ const recorder = getReplayRecorder();
1440
+ const result = recorder.stopRecording(sessionId);
1441
+ return {
1442
+ content: [{ type: 'text', text: JSON.stringify(result) }],
1443
+ structuredContent: result,
1444
+ };
1445
+ });
1446
+ // Register replay_session tool
1447
+ this.server.registerTool('replay_session', {
1448
+ title: 'Replay Session',
1449
+ description: 'Replay a recorded session, optionally applying modifications. Detects divergence when replayed result differs from recorded result.',
1450
+ annotations: {
1451
+ readOnlyHint: true,
1452
+ idempotentHint: true,
1453
+ openWorldHint: false,
1454
+ },
1455
+ inputSchema: {
1456
+ recordingPath: z.string().describe('Path to the .jsonl recording file.'),
1457
+ modifications: z.array(z.object({
1458
+ at: z.number().describe('Zero-based sequence index to target.'),
1459
+ op: z.enum(['replace', 'skip']).describe('Operation: replace the result or skip the event.'),
1460
+ with: z.unknown().optional().describe('Replacement value for replace operation.'),
1461
+ })).optional().describe('Optional list of modifications to apply during replay.'),
1462
+ },
1463
+ outputSchema: {
1464
+ result: z.unknown(),
1465
+ divergence: z.object({
1466
+ at: z.number(),
1467
+ expected: z.unknown(),
1468
+ actual: z.unknown(),
1469
+ }).optional(),
1470
+ },
1471
+ }, async ({ recordingPath, modifications }) => {
1472
+ const recorder = getReplayRecorder();
1473
+ const { result, divergence } = recorder.replay(recordingPath, modifications ?? []);
1474
+ const output = { result, divergence };
1475
+ return {
1476
+ content: [{ type: 'text', text: JSON.stringify(output) }],
1477
+ structuredContent: output,
1478
+ };
1479
+ });
1480
+ // ------------------------------------------------------------------
1481
+ // Lifecycle tools (Workstream X2)
1482
+ // ------------------------------------------------------------------
1483
+ // import_servers_from_claude
1484
+ this.server.registerTool('import_servers_from_claude', {
1485
+ title: 'Import Servers from Claude',
1486
+ description: `Import MCP servers from Claude config files into ~/.mcp-conductor.json.
1487
+
1488
+ Reads ~/.claude/settings.json, ~/Library/Application Support/Claude/claude_desktop_config.json and other standard paths.
1489
+ Shows a diff of what will be imported. On confirm=true, copies entries into the conductor config and writes .bak backups of each source file.
1490
+ Optionally strips the imported servers from their source configs.`,
1491
+ annotations: {
1492
+ readOnlyHint: false,
1493
+ destructiveHint: false,
1494
+ idempotentHint: false,
1495
+ openWorldHint: false,
1496
+ },
1497
+ inputSchema: {
1498
+ confirm: z.boolean().optional().default(false).describe('Set true to actually perform the import. False (default) shows a dry-run diff.'),
1499
+ remove_originals: z.boolean().optional().default(false).describe('After import, remove the imported servers from their source Claude config files.'),
1500
+ },
1501
+ outputSchema: {
1502
+ dry_run: z.boolean(),
1503
+ sources_found: z.number(),
1504
+ total_imported: z.number(),
1505
+ total_skipped: z.number(),
1506
+ summary: z.string(),
1507
+ },
1508
+ }, async ({ confirm: doImport, remove_originals }) => {
1509
+ const sources = findClaudeConfigsWithServers();
1510
+ if (sources.length === 0) {
1511
+ const output = { dry_run: !doImport, sources_found: 0, total_imported: 0, total_skipped: 0, summary: 'No Claude config files with MCP servers found.' };
1512
+ return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
1513
+ }
1514
+ // Pass removeOriginals only when actually importing (doImport=true).
1515
+ // Even though importServers() guards the strip on !dryRun internally,
1516
+ // being explicit here prevents accidental destruction if the function
1517
+ // contract ever changes (HIGH-4 defense-in-depth).
1518
+ const results = importServers({
1519
+ yes: true,
1520
+ removeOriginals: (remove_originals ?? false) && doImport,
1521
+ dryRun: !doImport,
1522
+ });
1523
+ const totalImported = results.reduce((sum, r) => sum + r.imported.length, 0);
1524
+ const totalSkipped = results.reduce((sum, r) => sum + r.skipped.length, 0);
1525
+ const summary = formatImportResults(results, !doImport);
1526
+ const output = {
1527
+ dry_run: !doImport,
1528
+ sources_found: sources.length,
1529
+ total_imported: totalImported,
1530
+ total_skipped: totalSkipped,
1531
+ summary,
1532
+ };
1533
+ return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
1534
+ });
1535
+ // test_server
1536
+ this.server.registerTool('test_server', {
1537
+ title: 'Test Server',
1538
+ description: `Transiently connect to a named MCP server from conductor config, list its tools and measure latency.
1539
+ Does NOT persist the connection or register the server. The server must be present in ~/.mcp-conductor.json.`,
1540
+ annotations: {
1541
+ readOnlyHint: true,
1542
+ idempotentHint: true,
1543
+ openWorldHint: false,
1544
+ },
1545
+ inputSchema: {
1546
+ name: z.string().describe('Server name in conductor config to test.'),
1547
+ timeout_ms: z.number().optional().default(15000).describe('Connection timeout in milliseconds.'),
1548
+ },
1549
+ outputSchema: {
1550
+ success: z.boolean(),
1551
+ server_name: z.string(),
1552
+ connected: z.boolean(),
1553
+ tool_count: z.number(),
1554
+ tools: z.array(z.object({ name: z.string(), description: z.string() })),
1555
+ latency_ms: z.number(),
1556
+ error: z.string().optional(),
1557
+ },
1558
+ }, async ({ name, timeout_ms }) => {
1559
+ const result = await testServer({ name, timeoutMs: timeout_ms ?? 15000 });
1560
+ const output = {
1561
+ success: result.success,
1562
+ server_name: result.serverName,
1563
+ connected: result.connected,
1564
+ tool_count: result.toolCount,
1565
+ tools: result.tools,
1566
+ latency_ms: result.latencyMs,
1567
+ ...(result.error ? { error: result.error } : {}),
1568
+ };
1569
+ return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
1570
+ });
1571
+ // diagnose_server
1572
+ this.server.registerTool('diagnose_server', {
1573
+ title: 'Diagnose Server',
1574
+ description: `Diagnose a registered MCP server: process health, connection status, recent errors, reconnect attempts, last successful call, and registry state.
1575
+ Returns actionable information about why a server may be failing.`,
1576
+ annotations: {
1577
+ readOnlyHint: true,
1578
+ idempotentHint: true,
1579
+ openWorldHint: false,
1580
+ },
1581
+ inputSchema: {
1582
+ name: z.string().describe('Server name to diagnose (must be in conductor config).'),
1583
+ },
1584
+ outputSchema: {
1585
+ server_name: z.string(),
1586
+ status: z.string(),
1587
+ tool_count: z.number(),
1588
+ connected_at: z.string().optional(),
1589
+ last_error: z.string().optional(),
1590
+ reconnect_attempts: z.number(),
1591
+ is_connected: z.boolean(),
1592
+ registry_state: z.object({
1593
+ in_config: z.boolean(),
1594
+ command: z.string().optional(),
1595
+ }),
1596
+ suggestions: z.array(z.string()),
1597
+ },
1598
+ }, async ({ name }) => {
1599
+ const servers = this.hub.listServers();
1600
+ const found = servers.find((s) => s.name === name);
1601
+ const conductorConfig = loadConductorConfig();
1602
+ const inConfig = !!(conductorConfig?.servers[name]);
1603
+ const configEntry = conductorConfig?.servers[name];
1604
+ const suggestions = [];
1605
+ if (!found && !inConfig) {
1606
+ suggestions.push(`Server '${name}' is not in conductor config. Add it with add_server or import_servers_from_claude.`);
1607
+ }
1608
+ else if (!found) {
1609
+ suggestions.push(`Server '${name}' is in config but not connected. Try reload_servers or restart conductor.`);
1610
+ }
1611
+ else if (found.status === 'error') {
1612
+ suggestions.push(`Server has error status: ${found.lastError ?? 'unknown'}. Check the command path and env vars.`);
1613
+ suggestions.push('Run test_server to verify connectivity.');
1614
+ }
1615
+ else if (found.status === 'disconnected') {
1616
+ suggestions.push('Server is disconnected. Conductor will auto-reconnect; or call reload_servers.');
1617
+ }
1618
+ const output = {
1619
+ server_name: name,
1620
+ status: found?.status ?? 'not_registered',
1621
+ tool_count: found?.toolCount ?? 0,
1622
+ connected_at: found?.connectedAt?.toISOString(),
1623
+ last_error: found?.lastError,
1624
+ reconnect_attempts: 0, // hub does not expose per-server attempt count publicly yet
1625
+ is_connected: found?.status === 'connected',
1626
+ registry_state: {
1627
+ in_config: inConfig,
1628
+ command: configEntry?.command,
1629
+ },
1630
+ suggestions,
1631
+ };
1632
+ return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
1633
+ });
1634
+ // recommend_routing
1635
+ this.server.registerTool('recommend_routing', {
1636
+ title: 'Recommend Routing',
1637
+ description: `Apply the X1 routing heuristic to one or all configured servers.
1638
+ Servers whose names match lightweight-payload patterns (search, calendar, email, etc.) are recommended as "passthrough".
1639
+ All others default to "execute_code" (safe default). Use apply=true to write the hints into conductor config.`,
1640
+ annotations: {
1641
+ readOnlyHint: false,
1642
+ idempotentHint: true,
1643
+ openWorldHint: false,
1644
+ },
1645
+ inputSchema: {
1646
+ server_name: z.string().optional().describe('Analyse a single server (omit for all servers).'),
1647
+ apply: z.boolean().optional().default(false).describe('Write routing hints to ~/.mcp-conductor.json.'),
1648
+ },
1649
+ outputSchema: {
1650
+ recommendations: z.array(z.object({
1651
+ server_name: z.string(),
1652
+ recommendation: z.enum(['passthrough', 'execute_code']),
1653
+ reason: z.string(),
1654
+ })),
1655
+ applied: z.boolean(),
1656
+ config_path: z.string().optional(),
1657
+ },
1658
+ }, async ({ server_name, apply }) => {
1659
+ const result = getRoutingRecommendations({ serverName: server_name, apply: apply ?? false });
1660
+ const output = {
1661
+ recommendations: result.recommendations,
1662
+ applied: result.applied,
1663
+ config_path: result.configPath,
1664
+ };
1665
+ return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
1666
+ });
1667
+ // export_to_claude
1668
+ this.server.registerTool('export_to_claude', {
1669
+ title: 'Export to Claude',
1670
+ description: `Generate a mcpServers JSON block that points Claude back at mcp-conductor stdio.
1671
+ This is the rollback path: paste the output into your Claude Desktop or Claude Code config to restore direct connectivity.
1672
+ Formats: "claude-desktop" (full wrapper object), "claude-code" (flat mcpServers), "raw" (inner object only).`,
1673
+ annotations: {
1674
+ readOnlyHint: true,
1675
+ idempotentHint: true,
1676
+ openWorldHint: false,
1677
+ },
1678
+ inputSchema: {
1679
+ format: z.enum(['claude-desktop', 'claude-code', 'raw']).optional().default('claude-desktop').describe('Output format.'),
1680
+ conductor_path: z.string().optional().describe('Override the conductor binary path (default: npx @darkiceinteractive/mcp-conductor).'),
1681
+ },
1682
+ outputSchema: {
1683
+ json: z.string(),
1684
+ format: z.string(),
1685
+ server_count: z.number(),
1686
+ instructions: z.string(),
1687
+ },
1688
+ }, async ({ format, conductor_path }) => {
1689
+ const result = exportToClaude({ format: format ?? 'claude-desktop', conductorPath: conductor_path });
1690
+ const instructions = format === 'claude-code'
1691
+ ? 'Merge this into ~/.claude/settings.json under the mcpServers key.'
1692
+ : format === 'raw'
1693
+ ? 'Add these entries under the mcpServers key in your Claude config.'
1694
+ : 'Merge this into ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or equivalent.';
1695
+ const output = { json: result.json, format: result.format, server_count: result.serverCount, instructions };
1696
+ return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
1697
+ });
1309
1698
  }
1310
1699
  /**
1311
1700
  * Start the server
@@ -1320,6 +1709,12 @@ Triggers a reload to apply changes immediately.`,
1320
1709
  connected: stats.connected,
1321
1710
  total: stats.total,
1322
1711
  });
1712
+ // Populate the registry from the now-connected hub, apply built-in
1713
+ // routing recommendations for known servers, then register all
1714
+ // `routing: "passthrough"` tools as first-class MCP tools.
1715
+ await this.registry.refresh();
1716
+ applyBuiltInRecommendations(this.registry.getAllTools(), (server, name, meta) => this.registry.annotate(server, name, meta));
1717
+ registerPassthroughTools(this.registry, this.server, this.hub);
1323
1718
  }
1324
1719
  // Set up bridge handlers
1325
1720
  const handlers = {
@@ -1353,8 +1748,61 @@ Triggers a reload to apply changes immediately.`,
1353
1748
  throw new Error(`Unknown tool: ${serverName}.${toolName}`);
1354
1749
  }
1355
1750
  else {
1356
- // Use real hub for tool calls
1357
- return await this.hub.callTool(serverName, toolName, params);
1751
+ // Check registry for X4 redact annotation on this tool
1752
+ const toolDef = this.registry.getTool(serverName, toolName);
1753
+ const matchers = toolDef?.redact?.response ?? [];
1754
+ // X4 tokenization path: PII-redacted results use per-call nonces so
1755
+ // caching would store tokenized values that cannot be reverse-mapped
1756
+ // after a cache eviction. Skip cache for this path.
1757
+ if (matchers.length > 0) {
1758
+ const _obsStart = Date.now();
1759
+ const { result, reverseMap } = await this.hub.callToolTokenized(serverName, toolName, params, matchers);
1760
+ const _obsLatency = Date.now() - _obsStart;
1761
+ const _obsResultStr = JSON.stringify(result ?? '');
1762
+ getHotPathProfiler().record(serverName, toolName, _obsLatency);
1763
+ getAnomalyDetector().record(serverName, toolName, _obsLatency, _obsResultStr.length);
1764
+ getCostPredictor().record(serverName, toolName, params, {
1765
+ outputText: _obsResultStr,
1766
+ latencyMs: _obsLatency,
1767
+ });
1768
+ // Return TokenizedCallResult sentinel — http-server.ts unwraps it
1769
+ return { __x4_result: result, __x4_reverseMap: reverseMap };
1770
+ }
1771
+ // Standard path: cache check → reliability gateway → backend → cache set.
1772
+ const cacheable = this.cache.wouldCache(serverName, toolName);
1773
+ if (cacheable) {
1774
+ const hit = await this.cache.get(serverName, toolName, params);
1775
+ if (hit) {
1776
+ if (!hit.needsRevalidation) {
1777
+ // Fresh cache hit — return immediately
1778
+ return hit.value;
1779
+ }
1780
+ // Stale-while-revalidate: return stale value, kick off background refresh
1781
+ this.cache.refreshInBackground(serverName, toolName, params, () => this.gateway.call(serverName, toolName, () => this.hub.callTool(serverName, toolName, params))).catch((err) => logger.warn('SWR background refresh failed', {
1782
+ serverName,
1783
+ toolName,
1784
+ err: String(err),
1785
+ }));
1786
+ return hit.value;
1787
+ }
1788
+ }
1789
+ // Cache miss (or non-cacheable): call through reliability gateway
1790
+ const _obsStart = Date.now();
1791
+ const _obsResult = await this.gateway.call(serverName, toolName, () => this.hub.callTool(serverName, toolName, params));
1792
+ const _obsLatency = Date.now() - _obsStart;
1793
+ const _obsResultStr = JSON.stringify(_obsResult ?? '');
1794
+ // Feed observability singletons
1795
+ getHotPathProfiler().record(serverName, toolName, _obsLatency);
1796
+ getAnomalyDetector().record(serverName, toolName, _obsLatency, _obsResultStr.length);
1797
+ getCostPredictor().record(serverName, toolName, params, {
1798
+ outputText: _obsResultStr,
1799
+ latencyMs: _obsLatency,
1800
+ });
1801
+ // Populate cache for future calls
1802
+ if (cacheable) {
1803
+ await this.cache.set(serverName, toolName, params, _obsResult);
1804
+ }
1805
+ return _obsResult;
1358
1806
  }
1359
1807
  },
1360
1808
  listServers: () => {
@@ -1427,11 +1875,17 @@ Triggers a reload to apply changes immediately.`,
1427
1875
  if (!this.useMockServers) {
1428
1876
  await this.hub.shutdown();
1429
1877
  }
1430
- // 3. Shutdown global singletons (intervals, listeners, caches)
1878
+ // 3. Destroy cache layer (unsubscribes registry watcher)
1879
+ this.cache.destroy();
1880
+ // 4. Shutdown global singletons (intervals, listeners, caches)
1431
1881
  shutdownStreamManager();
1432
1882
  shutdownMetricsCollector();
1433
1883
  shutdownModeHandler();
1434
1884
  shutdownSkillsEngine();
1885
+ shutdownCostPredictor();
1886
+ shutdownHotPathProfiler();
1887
+ shutdownAnomalyDetector();
1888
+ shutdownReplayRecorder();
1435
1889
  // 4. Stop HTTP bridge last (Deno processes may still be calling it during cleanup)
1436
1890
  await this.bridge.stop();
1437
1891
  logger.info('MCP Executor server stopped');