@darkiceinteractive/mcp-conductor 1.1.0 → 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 +65 -1
- package/dist/bridge/http-server.d.ts.map +1 -1
- package/dist/bridge/http-server.js +192 -7
- 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/bridge/session-registry.d.ts +64 -0
- package/dist/bridge/session-registry.d.ts.map +1 -0
- package/dist/bridge/session-registry.js +124 -0
- package/dist/bridge/session-registry.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 +10 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +14 -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 +19 -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 +12 -0
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +148 -16
- 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 +6 -0
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +610 -45
- 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 +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +5 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/orphan-watch.d.ts +34 -0
- package/dist/utils/orphan-watch.d.ts.map +1 -0
- package/dist/utils/orphan-watch.js +54 -0
- package/dist/utils/orphan-watch.js.map +1 -0
- package/dist/utils/redact.d.ts +15 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +48 -0
- package/dist/utils/redact.js.map +1 -0
- 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 +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';
|
|
@@ -17,6 +20,12 @@ import { shutdownMetricsCollector } from '../metrics/index.js';
|
|
|
17
20
|
import { shutdownModeHandler } from '../modes/index.js';
|
|
18
21
|
import { shutdownSkillsEngine } from '../skills/index.js';
|
|
19
22
|
import { loadConductorConfig, saveConductorConfig, getDefaultConductorConfigPath } from '../config/index.js';
|
|
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';
|
|
20
29
|
/**
|
|
21
30
|
* MCP Executor Server
|
|
22
31
|
*/
|
|
@@ -31,6 +40,7 @@ export class MCPExecutorServer {
|
|
|
31
40
|
config;
|
|
32
41
|
useMockServers;
|
|
33
42
|
currentMode;
|
|
43
|
+
registry;
|
|
34
44
|
// Mock server data for testing when no real servers configured
|
|
35
45
|
mockServers = new Map();
|
|
36
46
|
constructor(config, options) {
|
|
@@ -49,7 +59,7 @@ export class MCPExecutorServer {
|
|
|
49
59
|
this.server = new McpServer({
|
|
50
60
|
name: 'mcp-conductor',
|
|
51
61
|
title: 'MCP Conductor',
|
|
52
|
-
version:
|
|
62
|
+
version: VERSION,
|
|
53
63
|
websiteUrl: 'https://github.com/darkiceinteractive/mcp-conductor',
|
|
54
64
|
icons: [
|
|
55
65
|
{
|
|
@@ -84,6 +94,11 @@ export class MCPExecutorServer {
|
|
|
84
94
|
reconnectDelayMs: 5000,
|
|
85
95
|
maxReconnectAttempts: 3,
|
|
86
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 });
|
|
87
102
|
// Set up mock servers for fallback/testing
|
|
88
103
|
this.setupMockServers();
|
|
89
104
|
// Register tools
|
|
@@ -113,6 +128,46 @@ export class MCPExecutorServer {
|
|
|
113
128
|
/**
|
|
114
129
|
* Register all MCP tools
|
|
115
130
|
*/
|
|
131
|
+
/**
|
|
132
|
+
* Record metrics + shape the execute_code tool response. Extracted so the
|
|
133
|
+
* progress/cancel wiring in the handler stays readable.
|
|
134
|
+
*/
|
|
135
|
+
finaliseExecuteCodeResult(result, code, servers, verbose) {
|
|
136
|
+
const executionMetrics = this.metricsCollector.recordExecution({
|
|
137
|
+
executionId: result.executionId,
|
|
138
|
+
code,
|
|
139
|
+
result: result.result,
|
|
140
|
+
success: result.success,
|
|
141
|
+
durationMs: result.metrics.executionTimeMs,
|
|
142
|
+
toolCalls: result.metrics.toolCalls,
|
|
143
|
+
dataProcessedBytes: result.metrics.dataProcessedBytes,
|
|
144
|
+
resultSizeBytes: result.metrics.resultSizeBytes,
|
|
145
|
+
mode: 'execution',
|
|
146
|
+
serversUsed: servers || [],
|
|
147
|
+
errorType: result.error?.type,
|
|
148
|
+
});
|
|
149
|
+
this.modeHandler.recordExecutionCall(executionMetrics.estimatedTokensSaved);
|
|
150
|
+
const output = {
|
|
151
|
+
success: result.success,
|
|
152
|
+
result: result.result,
|
|
153
|
+
error: result.error,
|
|
154
|
+
};
|
|
155
|
+
if (verbose) {
|
|
156
|
+
output.metrics = {
|
|
157
|
+
execution_time_ms: result.metrics.executionTimeMs,
|
|
158
|
+
tool_calls: result.metrics.toolCalls,
|
|
159
|
+
data_processed_bytes: result.metrics.dataProcessedBytes,
|
|
160
|
+
result_size_bytes: result.metrics.resultSizeBytes,
|
|
161
|
+
estimated_tokens_saved: executionMetrics.estimatedTokensSaved,
|
|
162
|
+
savings_percent: executionMetrics.savingsPercent,
|
|
163
|
+
};
|
|
164
|
+
output.logs = result.logs;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
168
|
+
structuredContent: output,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
116
171
|
registerTools() {
|
|
117
172
|
// Register execute_code tool
|
|
118
173
|
this.server.registerTool('execute_code', {
|
|
@@ -126,6 +181,14 @@ export class MCPExecutorServer {
|
|
|
126
181
|
**Example:** \`const files = await mcp.filesystem.call('list_directory', { path: '/src' }); return files;\`
|
|
127
182
|
|
|
128
183
|
Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
184
|
+
annotations: {
|
|
185
|
+
// execute_code proxies arbitrary code that can call any backend MCP
|
|
186
|
+
// tool, so it inherits the most permissive capability surface.
|
|
187
|
+
readOnlyHint: false,
|
|
188
|
+
destructiveHint: true,
|
|
189
|
+
idempotentHint: false,
|
|
190
|
+
openWorldHint: true,
|
|
191
|
+
},
|
|
129
192
|
inputSchema: {
|
|
130
193
|
code: z.string().describe('TypeScript/JavaScript code to execute. Must include a return statement.'),
|
|
131
194
|
servers: z.array(z.string()).optional().describe('Optional: List of MCP server names to load.'),
|
|
@@ -151,60 +214,87 @@ Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
|
151
214
|
}).optional(),
|
|
152
215
|
logs: z.array(z.string()).optional(),
|
|
153
216
|
},
|
|
154
|
-
}, async ({ code, servers, timeout_ms, stream, verbose }) => {
|
|
217
|
+
}, async ({ code, servers, timeout_ms, stream, verbose }, extra) => {
|
|
155
218
|
const timeoutMs = Math.min(timeout_ms || this.config.execution.defaultTimeoutMs, this.config.execution.maxTimeoutMs);
|
|
156
219
|
logger.info('Executing code', {
|
|
157
220
|
codeLength: code.length,
|
|
158
221
|
timeout: timeoutMs,
|
|
159
222
|
servers: servers || 'all',
|
|
160
223
|
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
error: result.error,
|
|
224
|
+
// If the client supplied a progressToken in _meta, forward sandbox
|
|
225
|
+
// progress() calls as MCP notifications/progress. We preallocate the
|
|
226
|
+
// execution id + stream so we never miss the first event.
|
|
227
|
+
const progressToken = extra?._meta?.progressToken;
|
|
228
|
+
const wantProgress = progressToken !== undefined;
|
|
229
|
+
const wantStream = stream || wantProgress;
|
|
230
|
+
const executionId = wantStream ? this.executor.generateExecutionId() : undefined;
|
|
231
|
+
const execStream = executionId ? this.bridge.createStream(executionId) : undefined;
|
|
232
|
+
const forwardProgress = (percent, message) => {
|
|
233
|
+
if (!wantProgress || !extra?.sendNotification)
|
|
234
|
+
return;
|
|
235
|
+
void extra
|
|
236
|
+
.sendNotification({
|
|
237
|
+
method: 'notifications/progress',
|
|
238
|
+
params: {
|
|
239
|
+
progressToken,
|
|
240
|
+
progress: percent,
|
|
241
|
+
total: 100,
|
|
242
|
+
...(message ? { message } : {}),
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
.catch((err) => {
|
|
246
|
+
logger.debug('Failed to forward progress notification', { error: String(err) });
|
|
247
|
+
});
|
|
186
248
|
};
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
249
|
+
if (execStream && wantProgress) {
|
|
250
|
+
execStream.on('progress', (ev) => {
|
|
251
|
+
forwardProgress(ev.data.percent, ev.data.message);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
let result;
|
|
255
|
+
try {
|
|
256
|
+
result = await this.executor.execute(code, {
|
|
257
|
+
timeoutMs,
|
|
258
|
+
bridgeUrl: this.bridge.getUrl(),
|
|
259
|
+
servers: servers || [],
|
|
260
|
+
stream: wantStream,
|
|
261
|
+
signal: extra?.signal,
|
|
262
|
+
executionId,
|
|
263
|
+
});
|
|
264
|
+
return this.finaliseExecuteCodeResult(result, code, servers, verbose);
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
if (execStream) {
|
|
268
|
+
// Flip the stream out of `running` so StreamManager's normal
|
|
269
|
+
// 5/10-min cleanup applies instead of the 15-min stuck-stream
|
|
270
|
+
// sweep. Only fire complete() if we actually got a result —
|
|
271
|
+
// if execute() threw, the stream will time out via the stuck
|
|
272
|
+
// path which is the right safety net.
|
|
273
|
+
if (result) {
|
|
274
|
+
execStream.complete({
|
|
275
|
+
success: result.success,
|
|
276
|
+
result: result.result,
|
|
277
|
+
error: result.error,
|
|
278
|
+
metrics: {
|
|
279
|
+
executionTimeMs: result.metrics.executionTimeMs,
|
|
280
|
+
toolCalls: result.metrics.toolCalls,
|
|
281
|
+
dataProcessedBytes: result.metrics.dataProcessedBytes,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
execStream.removeAllListeners('progress');
|
|
286
|
+
}
|
|
198
287
|
}
|
|
199
|
-
return {
|
|
200
|
-
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
201
|
-
structuredContent: output,
|
|
202
|
-
};
|
|
203
288
|
});
|
|
204
289
|
// Register list_servers tool
|
|
205
290
|
this.server.registerTool('list_servers', {
|
|
206
291
|
title: 'List Servers',
|
|
207
292
|
description: 'List all MCP servers connected through MCP Executor.',
|
|
293
|
+
annotations: {
|
|
294
|
+
readOnlyHint: true,
|
|
295
|
+
idempotentHint: true,
|
|
296
|
+
openWorldHint: false,
|
|
297
|
+
},
|
|
208
298
|
inputSchema: {
|
|
209
299
|
include_tools: z.boolean().optional().describe('If true, include list of tool names.'),
|
|
210
300
|
},
|
|
@@ -253,6 +343,11 @@ Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
|
253
343
|
this.server.registerTool('discover_tools', {
|
|
254
344
|
title: 'Discover Tools',
|
|
255
345
|
description: 'Search for available tools across all connected MCP servers.',
|
|
346
|
+
annotations: {
|
|
347
|
+
readOnlyHint: true,
|
|
348
|
+
idempotentHint: true,
|
|
349
|
+
openWorldHint: false,
|
|
350
|
+
},
|
|
256
351
|
inputSchema: {
|
|
257
352
|
query: z.string().optional().describe('Search query. Matches against tool names and descriptions.'),
|
|
258
353
|
server: z.string().optional().describe('Optional: limit search to a specific server.'),
|
|
@@ -334,6 +429,11 @@ Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
|
334
429
|
this.server.registerTool('get_metrics', {
|
|
335
430
|
title: 'Get Metrics',
|
|
336
431
|
description: 'Get detailed aggregated metrics for the current session including token savings, performance, and usage patterns.',
|
|
432
|
+
annotations: {
|
|
433
|
+
readOnlyHint: true,
|
|
434
|
+
idempotentHint: true,
|
|
435
|
+
openWorldHint: false,
|
|
436
|
+
},
|
|
337
437
|
inputSchema: {
|
|
338
438
|
reset: z.boolean().optional().describe('Reset metrics after returning.'),
|
|
339
439
|
include_details: z.boolean().optional().describe('Include detailed breakdowns (servers, tools, recent executions).'),
|
|
@@ -443,6 +543,13 @@ Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
|
443
543
|
- execution: All requests go through the code executor (default, maximum token savings)
|
|
444
544
|
- passthrough: Direct tool calls without code execution (for debugging/comparison)
|
|
445
545
|
- hybrid: Automatic selection based on task complexity`,
|
|
546
|
+
annotations: {
|
|
547
|
+
// Changes global server behaviour; future tool calls take the new path.
|
|
548
|
+
readOnlyHint: false,
|
|
549
|
+
destructiveHint: true,
|
|
550
|
+
idempotentHint: true,
|
|
551
|
+
openWorldHint: false,
|
|
552
|
+
},
|
|
446
553
|
inputSchema: {
|
|
447
554
|
mode: z.enum(['execution', 'passthrough', 'hybrid']).describe('The operation mode to switch to.'),
|
|
448
555
|
},
|
|
@@ -469,6 +576,11 @@ Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
|
469
576
|
this.server.registerTool('reload_servers', {
|
|
470
577
|
title: 'Reload Servers',
|
|
471
578
|
description: 'Reload MCP server configurations. Useful after modifying claude_desktop_config.json.',
|
|
579
|
+
annotations: {
|
|
580
|
+
destructiveHint: true,
|
|
581
|
+
idempotentHint: false,
|
|
582
|
+
openWorldHint: true,
|
|
583
|
+
},
|
|
472
584
|
inputSchema: {},
|
|
473
585
|
outputSchema: {
|
|
474
586
|
added: z.array(z.string()),
|
|
@@ -493,6 +605,11 @@ Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
|
493
605
|
this.server.registerTool('get_capabilities', {
|
|
494
606
|
title: 'Get Capabilities',
|
|
495
607
|
description: 'Get detailed information about MCP Executor capabilities and configuration.',
|
|
608
|
+
annotations: {
|
|
609
|
+
readOnlyHint: true,
|
|
610
|
+
idempotentHint: true,
|
|
611
|
+
openWorldHint: false,
|
|
612
|
+
},
|
|
496
613
|
inputSchema: {},
|
|
497
614
|
outputSchema: {
|
|
498
615
|
version: z.string(),
|
|
@@ -522,7 +639,7 @@ Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
|
522
639
|
? { loaded: this.skills.getSkillCount(), categories: this.skills.getCategories() }
|
|
523
640
|
: { loaded: 0, categories: [] };
|
|
524
641
|
const output = {
|
|
525
|
-
version:
|
|
642
|
+
version: VERSION,
|
|
526
643
|
current_mode: this.currentMode,
|
|
527
644
|
features: {
|
|
528
645
|
streaming: this.config.execution.streamingEnabled,
|
|
@@ -550,6 +667,11 @@ Use passthrough_call only for debugging - it has HIGH token cost.`,
|
|
|
550
667
|
title: 'Compare Modes',
|
|
551
668
|
description: `Analyse how a task would be handled in different modes.
|
|
552
669
|
Returns estimated token usage and approach for each mode.`,
|
|
670
|
+
annotations: {
|
|
671
|
+
readOnlyHint: true,
|
|
672
|
+
idempotentHint: true,
|
|
673
|
+
openWorldHint: false,
|
|
674
|
+
},
|
|
553
675
|
inputSchema: {
|
|
554
676
|
task_description: z.string().describe('Description of the task to analyse.'),
|
|
555
677
|
estimated_tool_calls: z.number().optional().describe('Estimated number of tool calls needed.'),
|
|
@@ -628,6 +750,14 @@ Returns estimated token usage and approach for each mode.`,
|
|
|
628
750
|
description: `⚠️ DEBUGGING TOOL - Direct MCP tool call. HIGH TOKEN COST (10-100x vs execute_code).
|
|
629
751
|
|
|
630
752
|
Only use for debugging raw tool input/output. Use execute_code for all normal operations.`,
|
|
753
|
+
annotations: {
|
|
754
|
+
// Proxies into a backend MCP server so the effect depends on the
|
|
755
|
+
// downstream tool. Conservative defaults: assume external + mutable.
|
|
756
|
+
readOnlyHint: false,
|
|
757
|
+
destructiveHint: true,
|
|
758
|
+
idempotentHint: false,
|
|
759
|
+
openWorldHint: true,
|
|
760
|
+
},
|
|
631
761
|
inputSchema: {
|
|
632
762
|
server: z.string().describe('Name of the MCP server to call.'),
|
|
633
763
|
tool: z.string().describe('Name of the tool to invoke.'),
|
|
@@ -773,6 +903,11 @@ Only use for debugging raw tool input/output. Use execute_code for all normal op
|
|
|
773
903
|
description: `Web search via Brave Search API. Uses 90% fewer tokens than native WebSearch.
|
|
774
904
|
|
|
775
905
|
Routes to brave-search MCP server internally. Requires brave-search server to be configured.`,
|
|
906
|
+
annotations: {
|
|
907
|
+
readOnlyHint: true,
|
|
908
|
+
idempotentHint: false, // Web results change between calls
|
|
909
|
+
openWorldHint: true,
|
|
910
|
+
},
|
|
776
911
|
inputSchema: {
|
|
777
912
|
query: z.string().describe('Search query (max 400 chars, 50 words).'),
|
|
778
913
|
count: z.number().optional().describe('Number of results (1-20, default 10).'),
|
|
@@ -834,6 +969,12 @@ Routes to brave-search MCP server internally. Requires brave-search server to be
|
|
|
834
969
|
|
|
835
970
|
Saves the server configuration to ~/.mcp-conductor.json and triggers a reload.
|
|
836
971
|
Use this to dynamically add servers without restarting Claude.`,
|
|
972
|
+
annotations: {
|
|
973
|
+
readOnlyHint: false,
|
|
974
|
+
destructiveHint: false, // Adds a new server; not destructive by itself
|
|
975
|
+
idempotentHint: false, // Re-adding the same name returns an error
|
|
976
|
+
openWorldHint: false,
|
|
977
|
+
},
|
|
837
978
|
inputSchema: {
|
|
838
979
|
name: z.string().describe('Unique server name (e.g., "github", "filesystem").'),
|
|
839
980
|
command: z.string().describe('Command to run the server (e.g., "npx", "node", "python").'),
|
|
@@ -921,6 +1062,12 @@ Use this to dynamically add servers without restarting Claude.`,
|
|
|
921
1062
|
|
|
922
1063
|
Removes the server configuration from ~/.mcp-conductor.json and triggers a reload.
|
|
923
1064
|
Use this to dynamically remove servers without restarting Claude.`,
|
|
1065
|
+
annotations: {
|
|
1066
|
+
readOnlyHint: false,
|
|
1067
|
+
destructiveHint: true,
|
|
1068
|
+
idempotentHint: true, // Removing an already-gone server is a safe no-op
|
|
1069
|
+
openWorldHint: false,
|
|
1070
|
+
},
|
|
924
1071
|
inputSchema: {
|
|
925
1072
|
name: z.string().describe('Name of the server to remove.'),
|
|
926
1073
|
},
|
|
@@ -1006,6 +1153,12 @@ Use this to dynamically remove servers without restarting Claude.`,
|
|
|
1006
1153
|
|
|
1007
1154
|
Use this to update API keys or other settings without removing and re-adding the server.
|
|
1008
1155
|
Triggers a reload to apply changes immediately.`,
|
|
1156
|
+
annotations: {
|
|
1157
|
+
readOnlyHint: false,
|
|
1158
|
+
destructiveHint: true, // Overwrites existing config; may disconnect/reconnect
|
|
1159
|
+
idempotentHint: true,
|
|
1160
|
+
openWorldHint: false,
|
|
1161
|
+
},
|
|
1009
1162
|
inputSchema: {
|
|
1010
1163
|
name: z.string().describe('Name of the server to update.'),
|
|
1011
1164
|
command: z.string().optional().describe('New command (optional, keeps existing if not provided).'),
|
|
@@ -1124,6 +1277,11 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1124
1277
|
this.server.registerTool('get_memory_stats', {
|
|
1125
1278
|
title: 'Get Memory Stats',
|
|
1126
1279
|
description: 'Returns live memory usage and resource counts for the conductor process. Use this to diagnose memory issues.',
|
|
1280
|
+
annotations: {
|
|
1281
|
+
readOnlyHint: true,
|
|
1282
|
+
idempotentHint: true,
|
|
1283
|
+
openWorldHint: false,
|
|
1284
|
+
},
|
|
1127
1285
|
inputSchema: {},
|
|
1128
1286
|
outputSchema: {
|
|
1129
1287
|
heap_used_mb: z.number(),
|
|
@@ -1162,6 +1320,373 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1162
1320
|
structuredContent: output,
|
|
1163
1321
|
};
|
|
1164
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
|
+
});
|
|
1165
1690
|
}
|
|
1166
1691
|
/**
|
|
1167
1692
|
* Start the server
|
|
@@ -1176,6 +1701,12 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1176
1701
|
connected: stats.connected,
|
|
1177
1702
|
total: stats.total,
|
|
1178
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);
|
|
1179
1710
|
}
|
|
1180
1711
|
// Set up bridge handlers
|
|
1181
1712
|
const handlers = {
|
|
@@ -1209,8 +1740,38 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1209
1740
|
throw new Error(`Unknown tool: ${serverName}.${toolName}`);
|
|
1210
1741
|
}
|
|
1211
1742
|
else {
|
|
1212
|
-
//
|
|
1213
|
-
|
|
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;
|
|
1214
1775
|
}
|
|
1215
1776
|
},
|
|
1216
1777
|
listServers: () => {
|
|
@@ -1288,6 +1849,10 @@ Triggers a reload to apply changes immediately.`,
|
|
|
1288
1849
|
shutdownMetricsCollector();
|
|
1289
1850
|
shutdownModeHandler();
|
|
1290
1851
|
shutdownSkillsEngine();
|
|
1852
|
+
shutdownCostPredictor();
|
|
1853
|
+
shutdownHotPathProfiler();
|
|
1854
|
+
shutdownAnomalyDetector();
|
|
1855
|
+
shutdownReplayRecorder();
|
|
1291
1856
|
// 4. Stop HTTP bridge last (Deno processes may still be calling it during cleanup)
|
|
1292
1857
|
await this.bridge.stop();
|
|
1293
1858
|
logger.info('MCP Executor server stopped');
|