@darkiceinteractive/mcp-conductor 2.0.0-alpha.1 → 3.0.0-beta.1
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 +35 -5
- package/dist/bin/cli.d.ts +20 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +260 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/bridge/http-server.d.ts +26 -0
- package/dist/bridge/http-server.d.ts.map +1 -1
- package/dist/bridge/http-server.js +30 -2
- package/dist/bridge/http-server.js.map +1 -1
- package/dist/bridge/index.d.ts +1 -0
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +1 -0
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/pool.d.ts +95 -0
- package/dist/bridge/pool.d.ts.map +1 -0
- package/dist/bridge/pool.js +384 -0
- package/dist/bridge/pool.js.map +1 -0
- package/dist/cache/cache.d.ts +43 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +167 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cache/delta.d.ts +32 -0
- package/dist/cache/delta.d.ts.map +1 -0
- package/dist/cache/delta.js +131 -0
- package/dist/cache/delta.js.map +1 -0
- package/dist/cache/disk.d.ts +65 -0
- package/dist/cache/disk.d.ts.map +1 -0
- package/dist/cache/disk.js +238 -0
- package/dist/cache/disk.js.map +1 -0
- package/dist/cache/index.d.ts +53 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +12 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/key.d.ts +44 -0
- package/dist/cache/key.d.ts.map +1 -0
- package/dist/cache/key.js +83 -0
- package/dist/cache/key.js.map +1 -0
- package/dist/cache/lru.d.ts +57 -0
- package/dist/cache/lru.d.ts.map +1 -0
- package/dist/cache/lru.js +112 -0
- package/dist/cache/lru.js.map +1 -0
- package/dist/cache/policy.d.ts +34 -0
- package/dist/cache/policy.d.ts.map +1 -0
- package/dist/cache/policy.js +95 -0
- package/dist/cache/policy.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +33 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +135 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/export-servers.d.ts +22 -0
- package/dist/cli/commands/export-servers.d.ts.map +1 -0
- package/dist/cli/commands/export-servers.js +45 -0
- package/dist/cli/commands/export-servers.js.map +1 -0
- package/dist/cli/commands/import-servers.d.ts +57 -0
- package/dist/cli/commands/import-servers.d.ts.map +1 -0
- package/dist/cli/commands/import-servers.js +137 -0
- package/dist/cli/commands/import-servers.js.map +1 -0
- package/dist/cli/commands/routing.d.ts +34 -0
- package/dist/cli/commands/routing.d.ts.map +1 -0
- package/dist/cli/commands/routing.js +60 -0
- package/dist/cli/commands/routing.js.map +1 -0
- package/dist/cli/commands/test-server.d.ts +34 -0
- package/dist/cli/commands/test-server.d.ts.map +1 -0
- package/dist/cli/commands/test-server.js +86 -0
- package/dist/cli/commands/test-server.js.map +1 -0
- package/dist/cli/daemon.d.ts +60 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +244 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/replay.d.ts +16 -0
- package/dist/cli/replay.d.ts.map +1 -0
- package/dist/cli/replay.js +89 -0
- package/dist/cli/replay.js.map +1 -0
- package/dist/cli/wizard/setup.d.ts +12 -0
- package/dist/cli/wizard/setup.d.ts.map +1 -0
- package/dist/cli/wizard/setup.js +71 -0
- package/dist/cli/wizard/setup.js.map +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +4 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/daemon/client.d.ts +97 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +279 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/discovery.d.ts +50 -0
- package/dist/daemon/discovery.d.ts.map +1 -0
- package/dist/daemon/discovery.js +104 -0
- package/dist/daemon/discovery.js.map +1 -0
- package/dist/daemon/index.d.ts +16 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +11 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/sandbox-api.d.ts +45 -0
- package/dist/daemon/sandbox-api.d.ts.map +1 -0
- package/dist/daemon/sandbox-api.js +74 -0
- package/dist/daemon/sandbox-api.js.map +1 -0
- package/dist/daemon/server.d.ts +65 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +351 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/shared-kv.d.ts +81 -0
- package/dist/daemon/shared-kv.d.ts.map +1 -0
- package/dist/daemon/shared-kv.js +215 -0
- package/dist/daemon/shared-kv.js.map +1 -0
- package/dist/daemon/shared-lock.d.ts +71 -0
- package/dist/daemon/shared-lock.d.ts.map +1 -0
- package/dist/daemon/shared-lock.js +119 -0
- package/dist/daemon/shared-lock.js.map +1 -0
- package/dist/hub/mcp-hub.d.ts +23 -0
- package/dist/hub/mcp-hub.d.ts.map +1 -1
- package/dist/hub/mcp-hub.js +34 -1
- package/dist/hub/mcp-hub.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/observability/anomaly.d.ts +67 -0
- package/dist/observability/anomaly.d.ts.map +1 -0
- package/dist/observability/anomaly.js +141 -0
- package/dist/observability/anomaly.js.map +1 -0
- package/dist/observability/cost-predictor.d.ts +49 -0
- package/dist/observability/cost-predictor.d.ts.map +1 -0
- package/dist/observability/cost-predictor.js +145 -0
- package/dist/observability/cost-predictor.js.map +1 -0
- package/dist/observability/hot-path.d.ts +49 -0
- package/dist/observability/hot-path.d.ts.map +1 -0
- package/dist/observability/hot-path.js +125 -0
- package/dist/observability/hot-path.js.map +1 -0
- package/dist/observability/index.d.ts +10 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +10 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/replay.d.ts +104 -0
- package/dist/observability/replay.d.ts.map +1 -0
- package/dist/observability/replay.js +239 -0
- package/dist/observability/replay.js.map +1 -0
- package/dist/registry/built-in-recommendations.d.ts +54 -0
- package/dist/registry/built-in-recommendations.d.ts.map +1 -0
- package/dist/registry/built-in-recommendations.js +65 -0
- package/dist/registry/built-in-recommendations.js.map +1 -0
- package/dist/registry/events.d.ts +26 -0
- package/dist/registry/events.d.ts.map +1 -0
- package/dist/registry/events.js +22 -0
- package/dist/registry/events.js.map +1 -0
- package/dist/registry/index.d.ts +159 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +12 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/registry.d.ts +87 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +294 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/registry/snapshot.d.ts +42 -0
- package/dist/registry/snapshot.d.ts.map +1 -0
- package/dist/registry/snapshot.js +71 -0
- package/dist/registry/snapshot.js.map +1 -0
- package/dist/registry/typegen.d.ts +48 -0
- package/dist/registry/typegen.d.ts.map +1 -0
- package/dist/registry/typegen.js +200 -0
- package/dist/registry/typegen.js.map +1 -0
- package/dist/registry/validator.d.ts +23 -0
- package/dist/registry/validator.d.ts.map +1 -0
- package/dist/registry/validator.js +50 -0
- package/dist/registry/validator.js.map +1 -0
- package/dist/reliability/breaker.d.ts +57 -0
- package/dist/reliability/breaker.d.ts.map +1 -0
- package/dist/reliability/breaker.js +130 -0
- package/dist/reliability/breaker.js.map +1 -0
- package/dist/reliability/errors.d.ts +78 -0
- package/dist/reliability/errors.d.ts.map +1 -0
- package/dist/reliability/errors.js +160 -0
- package/dist/reliability/errors.js.map +1 -0
- package/dist/reliability/gateway.d.ts +88 -0
- package/dist/reliability/gateway.d.ts.map +1 -0
- package/dist/reliability/gateway.js +180 -0
- package/dist/reliability/gateway.js.map +1 -0
- package/dist/reliability/index.d.ts +20 -0
- package/dist/reliability/index.d.ts.map +1 -0
- package/dist/reliability/index.js +16 -0
- package/dist/reliability/index.js.map +1 -0
- package/dist/reliability/profile.d.ts +49 -0
- package/dist/reliability/profile.d.ts.map +1 -0
- package/dist/reliability/profile.js +58 -0
- package/dist/reliability/profile.js.map +1 -0
- package/dist/reliability/retry.d.ts +39 -0
- package/dist/reliability/retry.d.ts.map +1 -0
- package/dist/reliability/retry.js +51 -0
- package/dist/reliability/retry.js.map +1 -0
- package/dist/reliability/timeout.d.ts +34 -0
- package/dist/reliability/timeout.d.ts.map +1 -0
- package/dist/reliability/timeout.js +53 -0
- package/dist/reliability/timeout.js.map +1 -0
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +78 -13
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/findtool/embed.d.ts +28 -0
- package/dist/runtime/findtool/embed.d.ts.map +1 -0
- package/dist/runtime/findtool/embed.js +85 -0
- package/dist/runtime/findtool/embed.js.map +1 -0
- package/dist/runtime/findtool/index.d.ts +52 -0
- package/dist/runtime/findtool/index.d.ts.map +1 -0
- package/dist/runtime/findtool/index.js +78 -0
- package/dist/runtime/findtool/index.js.map +1 -0
- package/dist/runtime/findtool/vector-index.d.ts +53 -0
- package/dist/runtime/findtool/vector-index.d.ts.map +1 -0
- package/dist/runtime/findtool/vector-index.js +71 -0
- package/dist/runtime/findtool/vector-index.js.map +1 -0
- package/dist/runtime/helpers/budget.d.ts +27 -0
- package/dist/runtime/helpers/budget.d.ts.map +1 -0
- package/dist/runtime/helpers/budget.js +103 -0
- package/dist/runtime/helpers/budget.js.map +1 -0
- package/dist/runtime/helpers/compact.d.ts +32 -0
- package/dist/runtime/helpers/compact.d.ts.map +1 -0
- package/dist/runtime/helpers/compact.js +93 -0
- package/dist/runtime/helpers/compact.js.map +1 -0
- package/dist/runtime/helpers/delta.d.ts +45 -0
- package/dist/runtime/helpers/delta.d.ts.map +1 -0
- package/dist/runtime/helpers/delta.js +116 -0
- package/dist/runtime/helpers/delta.js.map +1 -0
- package/dist/runtime/helpers/index.d.ts +16 -0
- package/dist/runtime/helpers/index.d.ts.map +1 -0
- package/dist/runtime/helpers/index.js +13 -0
- package/dist/runtime/helpers/index.js.map +1 -0
- package/dist/runtime/helpers/summarize.d.ts +24 -0
- package/dist/runtime/helpers/summarize.d.ts.map +1 -0
- package/dist/runtime/helpers/summarize.js +124 -0
- package/dist/runtime/helpers/summarize.js.map +1 -0
- package/dist/runtime/helpers/worker-preload.d.ts +25 -0
- package/dist/runtime/helpers/worker-preload.d.ts.map +1 -0
- package/dist/runtime/helpers/worker-preload.js +223 -0
- package/dist/runtime/helpers/worker-preload.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/pool/index.d.ts +11 -0
- package/dist/runtime/pool/index.d.ts.map +1 -0
- package/dist/runtime/pool/index.js +8 -0
- package/dist/runtime/pool/index.js.map +1 -0
- package/dist/runtime/pool/recycle.d.ts +44 -0
- package/dist/runtime/pool/recycle.d.ts.map +1 -0
- package/dist/runtime/pool/recycle.js +50 -0
- package/dist/runtime/pool/recycle.js.map +1 -0
- package/dist/runtime/pool/worker-pool.d.ts +77 -0
- package/dist/runtime/pool/worker-pool.d.ts.map +1 -0
- package/dist/runtime/pool/worker-pool.js +216 -0
- package/dist/runtime/pool/worker-pool.js.map +1 -0
- package/dist/runtime/pool/worker.d.ts +80 -0
- package/dist/runtime/pool/worker.d.ts.map +1 -0
- package/dist/runtime/pool/worker.js +324 -0
- package/dist/runtime/pool/worker.js.map +1 -0
- package/dist/server/mcp-server.d.ts +1 -0
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +423 -2
- package/dist/server/mcp-server.js.map +1 -1
- package/dist/server/passthrough-registrar.d.ts +73 -0
- package/dist/server/passthrough-registrar.d.ts.map +1 -0
- package/dist/server/passthrough-registrar.js +110 -0
- package/dist/server/passthrough-registrar.js.map +1 -0
- package/dist/skills/skills-engine.d.ts +9 -1
- package/dist/skills/skills-engine.d.ts.map +1 -1
- package/dist/skills/skills-engine.js +20 -3
- package/dist/skills/skills-engine.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/tokenize.d.ts +55 -0
- package/dist/utils/tokenize.d.ts.map +1 -0
- package/dist/utils/tokenize.js +205 -0
- package/dist/utils/tokenize.js.map +1 -0
- package/dist/version.d.ts +3 -3
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +3 -3
- package/dist/version.js.map +1 -1
- package/package.json +12 -2
|
@@ -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,11 @@ 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';
|
|
21
29
|
/**
|
|
22
30
|
* MCP Executor Server
|
|
23
31
|
*/
|
|
@@ -32,6 +40,7 @@ export class MCPExecutorServer {
|
|
|
32
40
|
config;
|
|
33
41
|
useMockServers;
|
|
34
42
|
currentMode;
|
|
43
|
+
registry;
|
|
35
44
|
// Mock server data for testing when no real servers configured
|
|
36
45
|
mockServers = new Map();
|
|
37
46
|
constructor(config, options) {
|
|
@@ -85,6 +94,11 @@ export class MCPExecutorServer {
|
|
|
85
94
|
reconnectDelayMs: 5000,
|
|
86
95
|
maxReconnectAttempts: 3,
|
|
87
96
|
});
|
|
97
|
+
// Initialise the ToolRegistry backed by the hub.
|
|
98
|
+
// registry.refresh() + registerPassthroughTools() are called in start()
|
|
99
|
+
// after the hub has connected, so the registry is fully populated before
|
|
100
|
+
// any passthrough tool handler can be invoked.
|
|
101
|
+
this.registry = new ToolRegistry({ bridge: this.hub });
|
|
88
102
|
// Set up mock servers for fallback/testing
|
|
89
103
|
this.setupMockServers();
|
|
90
104
|
// Register tools
|
|
@@ -1306,6 +1320,373 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1306
1320
|
structuredContent: output,
|
|
1307
1321
|
};
|
|
1308
1322
|
});
|
|
1323
|
+
// Register predict_cost tool
|
|
1324
|
+
this.server.registerTool('predict_cost', {
|
|
1325
|
+
title: 'Predict Cost',
|
|
1326
|
+
description: 'Predict the token cost and latency of executing code based on historical samples for similar call patterns.',
|
|
1327
|
+
annotations: {
|
|
1328
|
+
readOnlyHint: true,
|
|
1329
|
+
idempotentHint: true,
|
|
1330
|
+
openWorldHint: false,
|
|
1331
|
+
},
|
|
1332
|
+
inputSchema: {
|
|
1333
|
+
code: z.string().describe('The code whose cost you want to estimate.'),
|
|
1334
|
+
},
|
|
1335
|
+
outputSchema: {
|
|
1336
|
+
estimatedInputTokens: z.number(),
|
|
1337
|
+
estimatedOutputTokens: z.number(),
|
|
1338
|
+
estimatedLatencyMs: z.number(),
|
|
1339
|
+
basedOn: z.number(),
|
|
1340
|
+
available: z.boolean(),
|
|
1341
|
+
},
|
|
1342
|
+
}, async ({ code }) => {
|
|
1343
|
+
const predictor = getCostPredictor();
|
|
1344
|
+
const prediction = predictor.predictFromCode(code);
|
|
1345
|
+
const output = prediction
|
|
1346
|
+
? { ...prediction, available: true }
|
|
1347
|
+
: { estimatedInputTokens: 0, estimatedOutputTokens: 0, estimatedLatencyMs: 0, basedOn: 0, available: false };
|
|
1348
|
+
return {
|
|
1349
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
1350
|
+
structuredContent: output,
|
|
1351
|
+
};
|
|
1352
|
+
});
|
|
1353
|
+
// Register get_hot_paths tool
|
|
1354
|
+
this.server.registerTool('get_hot_paths', {
|
|
1355
|
+
title: 'Get Hot Paths',
|
|
1356
|
+
description: 'Return the top-K tool call paths by total latency or p99 within a rolling time window.',
|
|
1357
|
+
annotations: {
|
|
1358
|
+
readOnlyHint: true,
|
|
1359
|
+
idempotentHint: true,
|
|
1360
|
+
openWorldHint: false,
|
|
1361
|
+
},
|
|
1362
|
+
inputSchema: {
|
|
1363
|
+
sinceMs: z.number().optional().describe('Only include calls made in the last N milliseconds.'),
|
|
1364
|
+
topK: z.number().optional().describe('Maximum number of paths to return (default: 10).'),
|
|
1365
|
+
sortBy: z.enum(['totalLatency', 'p99', 'callCount']).optional().describe('Ranking dimension (default: totalLatency).'),
|
|
1366
|
+
},
|
|
1367
|
+
outputSchema: {
|
|
1368
|
+
paths: z.array(z.object({
|
|
1369
|
+
server: z.string(),
|
|
1370
|
+
tool: z.string(),
|
|
1371
|
+
callCount: z.number(),
|
|
1372
|
+
totalLatencyMs: z.number(),
|
|
1373
|
+
meanLatencyMs: z.number(),
|
|
1374
|
+
p99LatencyMs: z.number(),
|
|
1375
|
+
})),
|
|
1376
|
+
},
|
|
1377
|
+
}, async ({ sinceMs, topK, sortBy }) => {
|
|
1378
|
+
const profiler = getHotPathProfiler();
|
|
1379
|
+
const paths = profiler.getHotPaths({
|
|
1380
|
+
sinceMs,
|
|
1381
|
+
topK: topK ?? 10,
|
|
1382
|
+
sortBy: sortBy ?? 'totalLatency',
|
|
1383
|
+
});
|
|
1384
|
+
const output = { paths };
|
|
1385
|
+
return {
|
|
1386
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
1387
|
+
structuredContent: output,
|
|
1388
|
+
};
|
|
1389
|
+
});
|
|
1390
|
+
// Register record_session tool
|
|
1391
|
+
this.server.registerTool('record_session', {
|
|
1392
|
+
title: 'Record Session',
|
|
1393
|
+
description: 'Start recording all tool calls in the current session to a replay journal.',
|
|
1394
|
+
annotations: {
|
|
1395
|
+
readOnlyHint: false,
|
|
1396
|
+
idempotentHint: false,
|
|
1397
|
+
openWorldHint: false,
|
|
1398
|
+
},
|
|
1399
|
+
inputSchema: {
|
|
1400
|
+
sessionId: z.string().optional().describe('Optional session ID. A UUID is generated if omitted.'),
|
|
1401
|
+
},
|
|
1402
|
+
outputSchema: {
|
|
1403
|
+
sessionId: z.string(),
|
|
1404
|
+
recordingPath: z.string(),
|
|
1405
|
+
},
|
|
1406
|
+
}, async ({ sessionId }) => {
|
|
1407
|
+
const recorder = getReplayRecorder();
|
|
1408
|
+
const result = recorder.startRecording(sessionId);
|
|
1409
|
+
return {
|
|
1410
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
1411
|
+
structuredContent: result,
|
|
1412
|
+
};
|
|
1413
|
+
});
|
|
1414
|
+
// Register stop_recording tool
|
|
1415
|
+
this.server.registerTool('stop_recording', {
|
|
1416
|
+
title: 'Stop Recording',
|
|
1417
|
+
description: 'Stop an active recording session and finalise the replay journal.',
|
|
1418
|
+
annotations: {
|
|
1419
|
+
readOnlyHint: false,
|
|
1420
|
+
idempotentHint: false,
|
|
1421
|
+
openWorldHint: false,
|
|
1422
|
+
},
|
|
1423
|
+
inputSchema: {
|
|
1424
|
+
sessionId: z.string().describe('Session ID returned by record_session.'),
|
|
1425
|
+
},
|
|
1426
|
+
outputSchema: {
|
|
1427
|
+
recordingPath: z.string(),
|
|
1428
|
+
eventCount: z.number(),
|
|
1429
|
+
},
|
|
1430
|
+
}, async ({ sessionId }) => {
|
|
1431
|
+
const recorder = getReplayRecorder();
|
|
1432
|
+
const result = recorder.stopRecording(sessionId);
|
|
1433
|
+
return {
|
|
1434
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
1435
|
+
structuredContent: result,
|
|
1436
|
+
};
|
|
1437
|
+
});
|
|
1438
|
+
// Register replay_session tool
|
|
1439
|
+
this.server.registerTool('replay_session', {
|
|
1440
|
+
title: 'Replay Session',
|
|
1441
|
+
description: 'Replay a recorded session, optionally applying modifications. Detects divergence when replayed result differs from recorded result.',
|
|
1442
|
+
annotations: {
|
|
1443
|
+
readOnlyHint: true,
|
|
1444
|
+
idempotentHint: true,
|
|
1445
|
+
openWorldHint: false,
|
|
1446
|
+
},
|
|
1447
|
+
inputSchema: {
|
|
1448
|
+
recordingPath: z.string().describe('Path to the .jsonl recording file.'),
|
|
1449
|
+
modifications: z.array(z.object({
|
|
1450
|
+
at: z.number().describe('Zero-based sequence index to target.'),
|
|
1451
|
+
op: z.enum(['replace', 'skip']).describe('Operation: replace the result or skip the event.'),
|
|
1452
|
+
with: z.unknown().optional().describe('Replacement value for replace operation.'),
|
|
1453
|
+
})).optional().describe('Optional list of modifications to apply during replay.'),
|
|
1454
|
+
},
|
|
1455
|
+
outputSchema: {
|
|
1456
|
+
result: z.unknown(),
|
|
1457
|
+
divergence: z.object({
|
|
1458
|
+
at: z.number(),
|
|
1459
|
+
expected: z.unknown(),
|
|
1460
|
+
actual: z.unknown(),
|
|
1461
|
+
}).optional(),
|
|
1462
|
+
},
|
|
1463
|
+
}, async ({ recordingPath, modifications }) => {
|
|
1464
|
+
const recorder = getReplayRecorder();
|
|
1465
|
+
const { result, divergence } = recorder.replay(recordingPath, modifications ?? []);
|
|
1466
|
+
const output = { result, divergence };
|
|
1467
|
+
return {
|
|
1468
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
1469
|
+
structuredContent: output,
|
|
1470
|
+
};
|
|
1471
|
+
});
|
|
1472
|
+
// ------------------------------------------------------------------
|
|
1473
|
+
// Lifecycle tools (Workstream X2)
|
|
1474
|
+
// ------------------------------------------------------------------
|
|
1475
|
+
// import_servers_from_claude
|
|
1476
|
+
this.server.registerTool('import_servers_from_claude', {
|
|
1477
|
+
title: 'Import Servers from Claude',
|
|
1478
|
+
description: `Import MCP servers from Claude config files into ~/.mcp-conductor.json.
|
|
1479
|
+
|
|
1480
|
+
Reads ~/.claude/settings.json, ~/Library/Application Support/Claude/claude_desktop_config.json and other standard paths.
|
|
1481
|
+
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.
|
|
1482
|
+
Optionally strips the imported servers from their source configs.`,
|
|
1483
|
+
annotations: {
|
|
1484
|
+
readOnlyHint: false,
|
|
1485
|
+
destructiveHint: false,
|
|
1486
|
+
idempotentHint: false,
|
|
1487
|
+
openWorldHint: false,
|
|
1488
|
+
},
|
|
1489
|
+
inputSchema: {
|
|
1490
|
+
confirm: z.boolean().optional().default(false).describe('Set true to actually perform the import. False (default) shows a dry-run diff.'),
|
|
1491
|
+
remove_originals: z.boolean().optional().default(false).describe('After import, remove the imported servers from their source Claude config files.'),
|
|
1492
|
+
},
|
|
1493
|
+
outputSchema: {
|
|
1494
|
+
dry_run: z.boolean(),
|
|
1495
|
+
sources_found: z.number(),
|
|
1496
|
+
total_imported: z.number(),
|
|
1497
|
+
total_skipped: z.number(),
|
|
1498
|
+
summary: z.string(),
|
|
1499
|
+
},
|
|
1500
|
+
}, async ({ confirm: doImport, remove_originals }) => {
|
|
1501
|
+
const sources = findClaudeConfigsWithServers();
|
|
1502
|
+
if (sources.length === 0) {
|
|
1503
|
+
const output = { dry_run: !doImport, sources_found: 0, total_imported: 0, total_skipped: 0, summary: 'No Claude config files with MCP servers found.' };
|
|
1504
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
|
|
1505
|
+
}
|
|
1506
|
+
const results = importServers({
|
|
1507
|
+
yes: true,
|
|
1508
|
+
removeOriginals: remove_originals ?? false,
|
|
1509
|
+
dryRun: !doImport,
|
|
1510
|
+
});
|
|
1511
|
+
const totalImported = results.reduce((sum, r) => sum + r.imported.length, 0);
|
|
1512
|
+
const totalSkipped = results.reduce((sum, r) => sum + r.skipped.length, 0);
|
|
1513
|
+
const summary = formatImportResults(results, !doImport);
|
|
1514
|
+
const output = {
|
|
1515
|
+
dry_run: !doImport,
|
|
1516
|
+
sources_found: sources.length,
|
|
1517
|
+
total_imported: totalImported,
|
|
1518
|
+
total_skipped: totalSkipped,
|
|
1519
|
+
summary,
|
|
1520
|
+
};
|
|
1521
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
|
|
1522
|
+
});
|
|
1523
|
+
// test_server
|
|
1524
|
+
this.server.registerTool('test_server', {
|
|
1525
|
+
title: 'Test Server',
|
|
1526
|
+
description: `Transiently connect to an MCP server, list its tools and measure latency.
|
|
1527
|
+
Does NOT persist the connection or register the server. Safe to call on any server definition.
|
|
1528
|
+
Provide either a name (looks up conductor config) or command+args directly.`,
|
|
1529
|
+
annotations: {
|
|
1530
|
+
readOnlyHint: true,
|
|
1531
|
+
idempotentHint: true,
|
|
1532
|
+
openWorldHint: true,
|
|
1533
|
+
},
|
|
1534
|
+
inputSchema: {
|
|
1535
|
+
name: z.string().optional().describe('Server name in conductor config to test.'),
|
|
1536
|
+
command: z.string().optional().describe('Direct command to run (bypasses config lookup).'),
|
|
1537
|
+
args: z.array(z.string()).optional().describe('Arguments for the command.'),
|
|
1538
|
+
env: z.record(z.string()).optional().describe('Environment variables for the command.'),
|
|
1539
|
+
timeout_ms: z.number().optional().default(15000).describe('Connection timeout in milliseconds.'),
|
|
1540
|
+
},
|
|
1541
|
+
outputSchema: {
|
|
1542
|
+
success: z.boolean(),
|
|
1543
|
+
server_name: z.string(),
|
|
1544
|
+
connected: z.boolean(),
|
|
1545
|
+
tool_count: z.number(),
|
|
1546
|
+
tools: z.array(z.object({ name: z.string(), description: z.string() })),
|
|
1547
|
+
latency_ms: z.number(),
|
|
1548
|
+
error: z.string().optional(),
|
|
1549
|
+
},
|
|
1550
|
+
}, async ({ name, command, args, env, timeout_ms }) => {
|
|
1551
|
+
const result = await testServer({ name, command, args, env, timeoutMs: timeout_ms ?? 15000 });
|
|
1552
|
+
const output = {
|
|
1553
|
+
success: result.success,
|
|
1554
|
+
server_name: result.serverName,
|
|
1555
|
+
connected: result.connected,
|
|
1556
|
+
tool_count: result.toolCount,
|
|
1557
|
+
tools: result.tools,
|
|
1558
|
+
latency_ms: result.latencyMs,
|
|
1559
|
+
...(result.error ? { error: result.error } : {}),
|
|
1560
|
+
};
|
|
1561
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
|
|
1562
|
+
});
|
|
1563
|
+
// diagnose_server
|
|
1564
|
+
this.server.registerTool('diagnose_server', {
|
|
1565
|
+
title: 'Diagnose Server',
|
|
1566
|
+
description: `Diagnose a registered MCP server: process health, connection status, recent errors, reconnect attempts, last successful call, and registry state.
|
|
1567
|
+
Returns actionable information about why a server may be failing.`,
|
|
1568
|
+
annotations: {
|
|
1569
|
+
readOnlyHint: true,
|
|
1570
|
+
idempotentHint: true,
|
|
1571
|
+
openWorldHint: false,
|
|
1572
|
+
},
|
|
1573
|
+
inputSchema: {
|
|
1574
|
+
name: z.string().describe('Server name to diagnose (must be in conductor config).'),
|
|
1575
|
+
},
|
|
1576
|
+
outputSchema: {
|
|
1577
|
+
server_name: z.string(),
|
|
1578
|
+
status: z.string(),
|
|
1579
|
+
tool_count: z.number(),
|
|
1580
|
+
connected_at: z.string().optional(),
|
|
1581
|
+
last_error: z.string().optional(),
|
|
1582
|
+
reconnect_attempts: z.number(),
|
|
1583
|
+
is_connected: z.boolean(),
|
|
1584
|
+
registry_state: z.object({
|
|
1585
|
+
in_config: z.boolean(),
|
|
1586
|
+
command: z.string().optional(),
|
|
1587
|
+
}),
|
|
1588
|
+
suggestions: z.array(z.string()),
|
|
1589
|
+
},
|
|
1590
|
+
}, async ({ name }) => {
|
|
1591
|
+
const servers = this.hub.listServers();
|
|
1592
|
+
const found = servers.find((s) => s.name === name);
|
|
1593
|
+
const conductorConfig = loadConductorConfig();
|
|
1594
|
+
const inConfig = !!(conductorConfig?.servers[name]);
|
|
1595
|
+
const configEntry = conductorConfig?.servers[name];
|
|
1596
|
+
const suggestions = [];
|
|
1597
|
+
if (!found && !inConfig) {
|
|
1598
|
+
suggestions.push(`Server '${name}' is not in conductor config. Add it with add_server or import_servers_from_claude.`);
|
|
1599
|
+
}
|
|
1600
|
+
else if (!found) {
|
|
1601
|
+
suggestions.push(`Server '${name}' is in config but not connected. Try reload_servers or restart conductor.`);
|
|
1602
|
+
}
|
|
1603
|
+
else if (found.status === 'error') {
|
|
1604
|
+
suggestions.push(`Server has error status: ${found.lastError ?? 'unknown'}. Check the command path and env vars.`);
|
|
1605
|
+
suggestions.push('Run test_server to verify connectivity.');
|
|
1606
|
+
}
|
|
1607
|
+
else if (found.status === 'disconnected') {
|
|
1608
|
+
suggestions.push('Server is disconnected. Conductor will auto-reconnect; or call reload_servers.');
|
|
1609
|
+
}
|
|
1610
|
+
const output = {
|
|
1611
|
+
server_name: name,
|
|
1612
|
+
status: found?.status ?? 'not_registered',
|
|
1613
|
+
tool_count: found?.toolCount ?? 0,
|
|
1614
|
+
connected_at: found?.connectedAt?.toISOString(),
|
|
1615
|
+
last_error: found?.lastError,
|
|
1616
|
+
reconnect_attempts: 0, // hub does not expose per-server attempt count publicly yet
|
|
1617
|
+
is_connected: found?.status === 'connected',
|
|
1618
|
+
registry_state: {
|
|
1619
|
+
in_config: inConfig,
|
|
1620
|
+
command: configEntry?.command,
|
|
1621
|
+
},
|
|
1622
|
+
suggestions,
|
|
1623
|
+
};
|
|
1624
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
|
|
1625
|
+
});
|
|
1626
|
+
// recommend_routing
|
|
1627
|
+
this.server.registerTool('recommend_routing', {
|
|
1628
|
+
title: 'Recommend Routing',
|
|
1629
|
+
description: `Apply the X1 routing heuristic to one or all configured servers.
|
|
1630
|
+
Servers whose names match lightweight-payload patterns (search, calendar, email, etc.) are recommended as "passthrough".
|
|
1631
|
+
All others default to "execute_code" (safe default). Use apply=true to write the hints into conductor config.`,
|
|
1632
|
+
annotations: {
|
|
1633
|
+
readOnlyHint: false,
|
|
1634
|
+
idempotentHint: true,
|
|
1635
|
+
openWorldHint: false,
|
|
1636
|
+
},
|
|
1637
|
+
inputSchema: {
|
|
1638
|
+
server_name: z.string().optional().describe('Analyse a single server (omit for all servers).'),
|
|
1639
|
+
apply: z.boolean().optional().default(false).describe('Write routing hints to ~/.mcp-conductor.json.'),
|
|
1640
|
+
},
|
|
1641
|
+
outputSchema: {
|
|
1642
|
+
recommendations: z.array(z.object({
|
|
1643
|
+
server_name: z.string(),
|
|
1644
|
+
recommendation: z.enum(['passthrough', 'execute_code']),
|
|
1645
|
+
reason: z.string(),
|
|
1646
|
+
})),
|
|
1647
|
+
applied: z.boolean(),
|
|
1648
|
+
config_path: z.string().optional(),
|
|
1649
|
+
},
|
|
1650
|
+
}, async ({ server_name, apply }) => {
|
|
1651
|
+
const result = getRoutingRecommendations({ serverName: server_name, apply: apply ?? false });
|
|
1652
|
+
const output = {
|
|
1653
|
+
recommendations: result.recommendations,
|
|
1654
|
+
applied: result.applied,
|
|
1655
|
+
config_path: result.configPath,
|
|
1656
|
+
};
|
|
1657
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
|
|
1658
|
+
});
|
|
1659
|
+
// export_to_claude
|
|
1660
|
+
this.server.registerTool('export_to_claude', {
|
|
1661
|
+
title: 'Export to Claude',
|
|
1662
|
+
description: `Generate a mcpServers JSON block that points Claude back at mcp-conductor stdio.
|
|
1663
|
+
This is the rollback path: paste the output into your Claude Desktop or Claude Code config to restore direct connectivity.
|
|
1664
|
+
Formats: "claude-desktop" (full wrapper object), "claude-code" (flat mcpServers), "raw" (inner object only).`,
|
|
1665
|
+
annotations: {
|
|
1666
|
+
readOnlyHint: true,
|
|
1667
|
+
idempotentHint: true,
|
|
1668
|
+
openWorldHint: false,
|
|
1669
|
+
},
|
|
1670
|
+
inputSchema: {
|
|
1671
|
+
format: z.enum(['claude-desktop', 'claude-code', 'raw']).optional().default('claude-desktop').describe('Output format.'),
|
|
1672
|
+
conductor_path: z.string().optional().describe('Override the conductor binary path (default: npx @darkiceinteractive/mcp-conductor).'),
|
|
1673
|
+
},
|
|
1674
|
+
outputSchema: {
|
|
1675
|
+
json: z.string(),
|
|
1676
|
+
format: z.string(),
|
|
1677
|
+
server_count: z.number(),
|
|
1678
|
+
instructions: z.string(),
|
|
1679
|
+
},
|
|
1680
|
+
}, async ({ format, conductor_path }) => {
|
|
1681
|
+
const result = exportToClaude({ format: format ?? 'claude-desktop', conductorPath: conductor_path });
|
|
1682
|
+
const instructions = format === 'claude-code'
|
|
1683
|
+
? 'Merge this into ~/.claude/settings.json under the mcpServers key.'
|
|
1684
|
+
: format === 'raw'
|
|
1685
|
+
? 'Add these entries under the mcpServers key in your Claude config.'
|
|
1686
|
+
: 'Merge this into ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or equivalent.';
|
|
1687
|
+
const output = { json: result.json, format: result.format, server_count: result.serverCount, instructions };
|
|
1688
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output };
|
|
1689
|
+
});
|
|
1309
1690
|
}
|
|
1310
1691
|
/**
|
|
1311
1692
|
* Start the server
|
|
@@ -1320,6 +1701,12 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1320
1701
|
connected: stats.connected,
|
|
1321
1702
|
total: stats.total,
|
|
1322
1703
|
});
|
|
1704
|
+
// Populate the registry from the now-connected hub, apply built-in
|
|
1705
|
+
// routing recommendations for known servers, then register all
|
|
1706
|
+
// `routing: "passthrough"` tools as first-class MCP tools.
|
|
1707
|
+
await this.registry.refresh();
|
|
1708
|
+
applyBuiltInRecommendations(this.registry.getAllTools(), (server, name, meta) => this.registry.annotate(server, name, meta));
|
|
1709
|
+
registerPassthroughTools(this.registry, this.server, this.hub);
|
|
1323
1710
|
}
|
|
1324
1711
|
// Set up bridge handlers
|
|
1325
1712
|
const handlers = {
|
|
@@ -1353,8 +1740,38 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1353
1740
|
throw new Error(`Unknown tool: ${serverName}.${toolName}`);
|
|
1354
1741
|
}
|
|
1355
1742
|
else {
|
|
1356
|
-
//
|
|
1357
|
-
|
|
1743
|
+
// Check registry for X4 redact annotation on this tool
|
|
1744
|
+
const toolDef = this.registry.getTool(serverName, toolName);
|
|
1745
|
+
const matchers = toolDef?.redact?.response ?? [];
|
|
1746
|
+
// Use real hub for tool calls — instrument for observability
|
|
1747
|
+
const _obsStart = Date.now();
|
|
1748
|
+
if (matchers.length > 0) {
|
|
1749
|
+
// Tokenize PII before the result reaches the sandbox
|
|
1750
|
+
const { result, reverseMap } = await this.hub.callToolTokenized(serverName, toolName, params, matchers);
|
|
1751
|
+
const _obsLatency = Date.now() - _obsStart;
|
|
1752
|
+
const _obsResultStr = JSON.stringify(result ?? '');
|
|
1753
|
+
getHotPathProfiler().record(serverName, toolName, _obsLatency);
|
|
1754
|
+
getAnomalyDetector().record(serverName, toolName, _obsLatency, _obsResultStr.length);
|
|
1755
|
+
getCostPredictor().record(serverName, toolName, params, {
|
|
1756
|
+
outputText: _obsResultStr,
|
|
1757
|
+
latencyMs: _obsLatency,
|
|
1758
|
+
});
|
|
1759
|
+
// Return TokenizedCallResult sentinel — http-server.ts unwraps it
|
|
1760
|
+
return { __x4_result: result, __x4_reverseMap: reverseMap };
|
|
1761
|
+
}
|
|
1762
|
+
const _obsResult = await this.hub.callTool(serverName, toolName, params);
|
|
1763
|
+
const _obsLatency = Date.now() - _obsStart;
|
|
1764
|
+
const _obsResultStr = JSON.stringify(_obsResult ?? '');
|
|
1765
|
+
// Feed hot-path profiler
|
|
1766
|
+
getHotPathProfiler().record(serverName, toolName, _obsLatency);
|
|
1767
|
+
// Feed anomaly detector
|
|
1768
|
+
getAnomalyDetector().record(serverName, toolName, _obsLatency, _obsResultStr.length);
|
|
1769
|
+
// Feed cost predictor
|
|
1770
|
+
getCostPredictor().record(serverName, toolName, params, {
|
|
1771
|
+
outputText: _obsResultStr,
|
|
1772
|
+
latencyMs: _obsLatency,
|
|
1773
|
+
});
|
|
1774
|
+
return _obsResult;
|
|
1358
1775
|
}
|
|
1359
1776
|
},
|
|
1360
1777
|
listServers: () => {
|
|
@@ -1432,6 +1849,10 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1432
1849
|
shutdownMetricsCollector();
|
|
1433
1850
|
shutdownModeHandler();
|
|
1434
1851
|
shutdownSkillsEngine();
|
|
1852
|
+
shutdownCostPredictor();
|
|
1853
|
+
shutdownHotPathProfiler();
|
|
1854
|
+
shutdownAnomalyDetector();
|
|
1855
|
+
shutdownReplayRecorder();
|
|
1435
1856
|
// 4. Stop HTTP bridge last (Deno processes may still be calling it during cleanup)
|
|
1436
1857
|
await this.bridge.stop();
|
|
1437
1858
|
logger.info('MCP Executor server stopped');
|