@darkiceinteractive/mcp-conductor 1.0.0 → 2.0.0-alpha.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.
Files changed (117) hide show
  1. package/README.md +263 -432
  2. package/dist/bridge/http-server.d.ts +39 -1
  3. package/dist/bridge/http-server.d.ts.map +1 -1
  4. package/dist/bridge/http-server.js +187 -10
  5. package/dist/bridge/http-server.js.map +1 -1
  6. package/dist/bridge/index.d.ts +4 -1
  7. package/dist/bridge/index.d.ts.map +1 -1
  8. package/dist/bridge/index.js +4 -1
  9. package/dist/bridge/index.js.map +1 -1
  10. package/dist/bridge/session-registry.d.ts +64 -0
  11. package/dist/bridge/session-registry.d.ts.map +1 -0
  12. package/dist/bridge/session-registry.js +124 -0
  13. package/dist/bridge/session-registry.js.map +1 -0
  14. package/dist/config/defaults.d.ts +58 -2
  15. package/dist/config/defaults.d.ts.map +1 -1
  16. package/dist/config/defaults.js +61 -3
  17. package/dist/config/defaults.js.map +1 -1
  18. package/dist/config/index.d.ts +3 -1
  19. package/dist/config/index.d.ts.map +1 -1
  20. package/dist/config/index.js +3 -1
  21. package/dist/config/index.js.map +1 -1
  22. package/dist/config/loader.d.ts +3 -0
  23. package/dist/config/loader.d.ts.map +1 -1
  24. package/dist/config/loader.js +29 -6
  25. package/dist/config/loader.js.map +1 -1
  26. package/dist/config/schema.d.ts +2 -0
  27. package/dist/config/schema.d.ts.map +1 -1
  28. package/dist/hub/index.d.ts +3 -1
  29. package/dist/hub/index.d.ts.map +1 -1
  30. package/dist/hub/index.js +3 -1
  31. package/dist/hub/index.js.map +1 -1
  32. package/dist/hub/mcp-hub.d.ts +1 -0
  33. package/dist/hub/mcp-hub.d.ts.map +1 -1
  34. package/dist/hub/mcp-hub.js +47 -14
  35. package/dist/hub/mcp-hub.js.map +1 -1
  36. package/dist/index.d.ts +21 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +71 -7
  39. package/dist/index.js.map +1 -1
  40. package/dist/metrics/index.d.ts +3 -1
  41. package/dist/metrics/index.d.ts.map +1 -1
  42. package/dist/metrics/index.js +3 -1
  43. package/dist/metrics/index.js.map +1 -1
  44. package/dist/metrics/metrics-collector.d.ts.map +1 -1
  45. package/dist/metrics/metrics-collector.js +1 -1
  46. package/dist/metrics/metrics-collector.js.map +1 -1
  47. package/dist/modes/index.d.ts +3 -1
  48. package/dist/modes/index.d.ts.map +1 -1
  49. package/dist/modes/index.js +3 -1
  50. package/dist/modes/index.js.map +1 -1
  51. package/dist/runtime/executor.d.ts +36 -1
  52. package/dist/runtime/executor.d.ts.map +1 -1
  53. package/dist/runtime/executor.js +245 -16
  54. package/dist/runtime/executor.js.map +1 -1
  55. package/dist/runtime/index.d.ts +3 -1
  56. package/dist/runtime/index.d.ts.map +1 -1
  57. package/dist/runtime/index.js +3 -1
  58. package/dist/runtime/index.js.map +1 -1
  59. package/dist/server/index.d.ts +3 -1
  60. package/dist/server/index.d.ts.map +1 -1
  61. package/dist/server/index.js +3 -1
  62. package/dist/server/index.js.map +1 -1
  63. package/dist/server/mcp-server.d.ts +5 -0
  64. package/dist/server/mcp-server.d.ts.map +1 -1
  65. package/dist/server/mcp-server.js +243 -44
  66. package/dist/server/mcp-server.js.map +1 -1
  67. package/dist/skills/index.d.ts +3 -1
  68. package/dist/skills/index.d.ts.map +1 -1
  69. package/dist/skills/index.js +3 -1
  70. package/dist/skills/index.js.map +1 -1
  71. package/dist/streaming/execution-stream.d.ts.map +1 -1
  72. package/dist/streaming/execution-stream.js +22 -5
  73. package/dist/streaming/execution-stream.js.map +1 -1
  74. package/dist/streaming/index.d.ts +3 -1
  75. package/dist/streaming/index.d.ts.map +1 -1
  76. package/dist/streaming/index.js +3 -1
  77. package/dist/streaming/index.js.map +1 -1
  78. package/dist/utils/env.d.ts +20 -0
  79. package/dist/utils/env.d.ts.map +1 -0
  80. package/dist/utils/env.js +33 -0
  81. package/dist/utils/env.js.map +1 -0
  82. package/dist/utils/errors.d.ts +17 -1
  83. package/dist/utils/errors.d.ts.map +1 -1
  84. package/dist/utils/errors.js +16 -1
  85. package/dist/utils/errors.js.map +1 -1
  86. package/dist/utils/index.d.ts +6 -1
  87. package/dist/utils/index.d.ts.map +1 -1
  88. package/dist/utils/index.js +6 -1
  89. package/dist/utils/index.js.map +1 -1
  90. package/dist/utils/logger.d.ts +8 -1
  91. package/dist/utils/logger.d.ts.map +1 -1
  92. package/dist/utils/logger.js +13 -2
  93. package/dist/utils/logger.js.map +1 -1
  94. package/dist/utils/orphan-watch.d.ts +34 -0
  95. package/dist/utils/orphan-watch.d.ts.map +1 -0
  96. package/dist/utils/orphan-watch.js +54 -0
  97. package/dist/utils/orphan-watch.js.map +1 -0
  98. package/dist/utils/rate-limiter.d.ts.map +1 -1
  99. package/dist/utils/rate-limiter.js +6 -0
  100. package/dist/utils/rate-limiter.js.map +1 -1
  101. package/dist/utils/redact.d.ts +15 -0
  102. package/dist/utils/redact.d.ts.map +1 -0
  103. package/dist/utils/redact.js +48 -0
  104. package/dist/utils/redact.js.map +1 -0
  105. package/dist/version.d.ts +4 -0
  106. package/dist/version.d.ts.map +1 -0
  107. package/dist/version.js +4 -0
  108. package/dist/version.js.map +1 -0
  109. package/dist/watcher/index.d.ts +3 -1
  110. package/dist/watcher/index.d.ts.map +1 -1
  111. package/dist/watcher/index.js +3 -1
  112. package/dist/watcher/index.js.map +1 -1
  113. package/package.json +4 -5
  114. package/dist/bin/cli.d.ts +0 -8
  115. package/dist/bin/cli.d.ts.map +0 -1
  116. package/dist/bin/cli.js +0 -940
  117. package/dist/bin/cli.js.map +0 -1
package/dist/bin/cli.js DELETED
@@ -1,940 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * MCP Conductor CLI
4
- *
5
- * Command-line interface for managing MCP Conductor.
6
- */
7
- import { Command } from 'commander';
8
- import { spawn } from 'node:child_process';
9
- import { MCPExecutorServer } from '../server/index.js';
10
- import { loadConfig, findClaudeConfig, getClaudeConfigPaths, loadClaudeConfig, loadConductorConfig, saveConductorConfig, findConductorConfig, getDefaultConductorConfigPath, DEFAULT_CONDUCTOR_CONFIG, } from '../config/index.js';
11
- import { logger, generatePermissionEntries, loadClaudeSettings, saveClaudeSettings, getExistingPermissions, comparePermissions, addPermissionsToSettings, formatPermissionsForDisplay, formatPermissionsAsJson, getSettingsPath, } from '../utils/index.js';
12
- import { MCPHub } from '../hub/index.js';
13
- const program = new Command();
14
- program
15
- .name('mcp-conductor')
16
- .description('MCP server that orchestrates code execution in a sandboxed environment with access to all MCP servers')
17
- .version('1.0.0');
18
- program
19
- .command('serve')
20
- .description('Start the MCP Conductor server')
21
- .option('-p, --port <port>', 'Bridge port (0 for dynamic allocation)', '0')
22
- .option('-m, --mode <mode>', 'Execution mode (execution|passthrough|hybrid)', 'execution')
23
- .option('-t, --timeout <ms>', 'Default timeout in milliseconds', '30000')
24
- .option('-v, --verbose', 'Enable verbose logging')
25
- .action(async (options) => {
26
- if (options.verbose) {
27
- logger.setLevel('debug');
28
- }
29
- // Override config from CLI options
30
- process.env['MCP_EXECUTOR_PORT'] = options.port;
31
- process.env['MCP_EXECUTOR_MODE'] = options.mode;
32
- process.env['MCP_EXECUTOR_TIMEOUT'] = options.timeout;
33
- const config = loadConfig();
34
- const server = new MCPExecutorServer(config);
35
- const shutdown = async () => {
36
- await server.stop();
37
- process.exit(0);
38
- };
39
- process.on('SIGINT', shutdown);
40
- process.on('SIGTERM', shutdown);
41
- try {
42
- await server.start();
43
- }
44
- catch (error) {
45
- logger.error('Failed to start server', { error: String(error) });
46
- process.exit(1);
47
- }
48
- });
49
- program
50
- .command('status')
51
- .description('Show MCP Conductor status and configuration paths')
52
- .action(() => {
53
- const claudeConfig = findClaudeConfig();
54
- console.log('MCP Conductor Status');
55
- console.log('====================');
56
- console.log();
57
- console.log('Claude Config:', claudeConfig || 'Not found');
58
- console.log();
59
- console.log('Search paths:');
60
- for (const path of getClaudeConfigPaths()) {
61
- console.log(` - ${path}`);
62
- }
63
- });
64
- program
65
- .command('servers')
66
- .description('Show runtime status of connected MCP servers (like /mcp in Claude)')
67
- .option('--json', 'Output as JSON')
68
- .option('--tools', 'Show tool names for each server')
69
- .action(async (options) => {
70
- // Import MCPHub dynamically to connect to servers
71
- const { MCPHub } = await import('../hub/index.js');
72
- const { loadConfig } = await import('../config/index.js');
73
- const config = loadConfig();
74
- const hub = new MCPHub({
75
- servers: config.servers,
76
- autoReconnect: false,
77
- reconnectDelayMs: 5000,
78
- maxReconnectAttempts: 1,
79
- });
80
- console.log('Connecting to MCP servers...');
81
- console.log();
82
- try {
83
- await hub.initialise();
84
- const servers = hub.listServers();
85
- const stats = hub.getStats();
86
- if (options.json) {
87
- const output = {
88
- summary: {
89
- total: stats.total,
90
- connected: stats.connected,
91
- error: stats.error,
92
- disconnected: stats.disconnected,
93
- },
94
- servers: servers.map(s => ({
95
- name: s.name,
96
- status: s.status,
97
- toolCount: s.toolCount,
98
- tools: options.tools ? hub.getServerTools(s.name).map(t => t.name) : undefined,
99
- })),
100
- };
101
- console.log(JSON.stringify(output, null, 2));
102
- }
103
- else {
104
- // Pretty output like /mcp
105
- const connectedCount = stats.connected;
106
- const errorCount = stats.error;
107
- let summary = `MCP Servers (${c.success(String(connectedCount))} connected`;
108
- if (errorCount > 0) {
109
- summary += `, ${c.error(String(errorCount))} error`;
110
- }
111
- summary += ')';
112
- console.log(summary);
113
- console.log();
114
- if (servers.length === 0) {
115
- console.log('No servers configured.');
116
- console.log();
117
- console.log('Run `mcp-conductor enable-exclusive` to migrate servers from Claude config.');
118
- }
119
- else {
120
- for (let i = 0; i < servers.length; i++) {
121
- const server = servers[i];
122
- if (!server)
123
- continue;
124
- const isLast = i === servers.length - 1;
125
- const prefix = isLast ? '└──' : '├──';
126
- let statusIcon;
127
- let statusText;
128
- if (server.status === 'connected') {
129
- statusIcon = c.success('✓');
130
- statusText = 'connected';
131
- }
132
- else if (server.status === 'error') {
133
- statusIcon = c.error('✗');
134
- statusText = 'error';
135
- }
136
- else {
137
- statusIcon = c.warning('○');
138
- statusText = 'disconnected';
139
- }
140
- const toolInfo = `(${server.toolCount} tools)`;
141
- console.log(`${prefix} ${c.highlight(server.name.padEnd(20))} ${statusIcon} ${statusText.padEnd(12)} ${toolInfo}`);
142
- if (options.tools && server.status === 'connected') {
143
- const tools = hub.getServerTools(server.name);
144
- const toolPrefix = isLast ? ' ' : '│ ';
145
- for (const tool of tools) {
146
- console.log(`${toolPrefix} • ${tool.name}`);
147
- }
148
- }
149
- }
150
- }
151
- }
152
- await hub.shutdown();
153
- }
154
- catch (error) {
155
- console.error(c.error('Failed to connect to servers:'), String(error));
156
- process.exit(1);
157
- }
158
- });
159
- program
160
- .command('check')
161
- .description('Check if Deno is installed and show system requirements')
162
- .action(async () => {
163
- console.log('MCP Conductor - System Check');
164
- console.log('=============================');
165
- console.log();
166
- // Check Node.js
167
- console.log(`Node.js: v${process.version.slice(1)} ✓`);
168
- // Check Deno
169
- console.log();
170
- console.log('Checking Deno installation...');
171
- const proc = spawn('deno', ['--version'], { stdio: 'pipe' });
172
- let output = '';
173
- let handled = false;
174
- const showDenoNotInstalled = () => {
175
- if (handled)
176
- return;
177
- handled = true;
178
- console.log('Deno: Not installed ✗');
179
- console.log();
180
- console.log('To install Deno:');
181
- console.log(' curl -fsSL https://deno.land/install.sh | sh');
182
- console.log(' # Or on macOS with Homebrew:');
183
- console.log(' brew install deno');
184
- };
185
- proc.stdout?.on('data', (data) => {
186
- output += data.toString();
187
- });
188
- proc.on('close', (code) => {
189
- if (code === 0) {
190
- handled = true;
191
- const lines = output.trim().split('\n');
192
- console.log(`Deno: ${lines[0]?.replace('deno ', 'v') || 'installed'} ✓`);
193
- console.log();
194
- console.log('All requirements met! ✓');
195
- }
196
- else {
197
- showDenoNotInstalled();
198
- }
199
- });
200
- proc.on('error', showDenoNotInstalled);
201
- });
202
- program
203
- .command('init')
204
- .description('Add MCP Conductor to your Claude Desktop configuration')
205
- .option('--dry-run', 'Show what would be added without making changes')
206
- .action(async (options) => {
207
- const configPath = findClaudeConfig();
208
- if (!configPath) {
209
- console.log('Claude Desktop configuration not found.');
210
- console.log('');
211
- console.log('Create one at:');
212
- for (const path of getClaudeConfigPaths()) {
213
- console.log(` ${path}`);
214
- }
215
- return;
216
- }
217
- const fs = await import('node:fs');
218
- const path = await import('node:path');
219
- const configContent = fs.readFileSync(configPath, 'utf-8');
220
- const config = JSON.parse(configContent);
221
- if (!config.mcpServers) {
222
- config.mcpServers = {};
223
- }
224
- // Get the path to this package's dist/index.js
225
- const pkgDir = path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname)));
226
- const serverPath = path.join(pkgDir, 'dist', 'index.js');
227
- const mcpConductorConfig = {
228
- command: 'node',
229
- args: [serverPath],
230
- };
231
- if (options.dryRun) {
232
- console.log('Would add to', configPath + ':');
233
- console.log();
234
- console.log(JSON.stringify({ 'mcp-conductor': mcpConductorConfig }, null, 2));
235
- return;
236
- }
237
- config.mcpServers['mcp-conductor'] = mcpConductorConfig;
238
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
239
- console.log('Added MCP Conductor to', configPath);
240
- console.log();
241
- console.log('Restart Claude Desktop to load the new server.');
242
- });
243
- // Permissions command group
244
- const permissions = program
245
- .command('permissions')
246
- .description('Manage Claude Code MCP tool permissions');
247
- permissions
248
- .command('discover')
249
- .description('Discover all MCP tools and generate permission entries')
250
- .option('--json', 'Output as JSON array (for copying to settings)')
251
- .option('--new-only', 'Only show permissions not already in settings')
252
- .option('-s, --scope <scope>', 'Settings scope to check (user|project)', 'user')
253
- .action(async (options) => {
254
- console.log('Discovering MCP tools...');
255
- console.log();
256
- const hub = new MCPHub();
257
- try {
258
- await hub.initialise();
259
- const allTools = hub.getAllTools();
260
- if (allTools.length === 0) {
261
- console.log('No tools found. Ensure MCP servers are configured in Claude config.');
262
- await hub.shutdown();
263
- return;
264
- }
265
- const entries = generatePermissionEntries(allTools.map((t) => ({ server: t.server, tool: t.tool.name })));
266
- // Load existing permissions if checking for new only
267
- let result = { all: entries, new: entries, existing: [] };
268
- if (options.newOnly) {
269
- const settingsPath = getSettingsPath(options.scope);
270
- const settings = loadClaudeSettings(settingsPath);
271
- if (settings) {
272
- const existing = getExistingPermissions(settings);
273
- result = comparePermissions(entries, existing);
274
- }
275
- }
276
- const toShow = options.newOnly ? result.new : result.all;
277
- if (toShow.length === 0) {
278
- console.log('All permissions are already configured!');
279
- await hub.shutdown();
280
- return;
281
- }
282
- console.log(`Found ${toShow.length} permission${toShow.length === 1 ? '' : 's'}:`);
283
- if (options.json) {
284
- console.log();
285
- console.log(formatPermissionsAsJson(toShow));
286
- }
287
- else {
288
- console.log(formatPermissionsForDisplay(toShow));
289
- }
290
- await hub.shutdown();
291
- }
292
- catch (error) {
293
- console.error('Error discovering tools:', String(error));
294
- await hub.shutdown();
295
- process.exit(1);
296
- }
297
- });
298
- permissions
299
- .command('add')
300
- .description('Add discovered MCP tool permissions to Claude settings')
301
- .option('-s, --scope <scope>', 'Settings scope (user|project)', 'user')
302
- .option('--dry-run', 'Show what would be added without making changes')
303
- .option('--all', 'Add permissions for all discovered tools (default: only new)')
304
- .action(async (options) => {
305
- const scope = options.scope;
306
- const settingsPath = getSettingsPath(scope);
307
- console.log(`Discovering MCP tools to add to ${scope} settings...`);
308
- console.log(`Settings file: ${settingsPath}`);
309
- console.log();
310
- const hub = new MCPHub();
311
- try {
312
- await hub.initialise();
313
- const allTools = hub.getAllTools();
314
- if (allTools.length === 0) {
315
- console.log('No tools found. Ensure MCP servers are configured.');
316
- await hub.shutdown();
317
- return;
318
- }
319
- const entries = generatePermissionEntries(allTools.map((t) => ({ server: t.server, tool: t.tool.name })));
320
- // Load existing settings
321
- let settings = loadClaudeSettings(settingsPath);
322
- if (!settings) {
323
- settings = { permissions: { allow: [], deny: [], ask: [] } };
324
- console.log('Creating new settings file...');
325
- }
326
- // Determine which permissions to add
327
- const existing = getExistingPermissions(settings);
328
- const comparison = comparePermissions(entries, existing);
329
- const toAdd = options.all ? comparison.all : comparison.new;
330
- if (toAdd.length === 0) {
331
- console.log('All permissions are already configured!');
332
- await hub.shutdown();
333
- return;
334
- }
335
- console.log(`Adding ${toAdd.length} permission${toAdd.length === 1 ? '' : 's'}:`);
336
- console.log(formatPermissionsForDisplay(toAdd));
337
- console.log();
338
- if (options.dryRun) {
339
- console.log('(Dry run - no changes made)');
340
- console.log();
341
- console.log('JSON to add to permissions.allow:');
342
- console.log(formatPermissionsAsJson(toAdd));
343
- await hub.shutdown();
344
- return;
345
- }
346
- // Update and save settings
347
- const permStrings = toAdd.map((e) => e.permission);
348
- const updated = addPermissionsToSettings(settings, permStrings);
349
- if (saveClaudeSettings(settingsPath, updated)) {
350
- console.log(`✓ Updated ${settingsPath}`);
351
- console.log();
352
- console.log('Restart Claude Code to apply the new permissions.');
353
- }
354
- else {
355
- console.error('Failed to save settings file.');
356
- process.exit(1);
357
- }
358
- await hub.shutdown();
359
- }
360
- catch (error) {
361
- console.error('Error adding permissions:', String(error));
362
- await hub.shutdown();
363
- process.exit(1);
364
- }
365
- });
366
- permissions
367
- .command('list')
368
- .description('List current MCP permissions from Claude settings')
369
- .option('-s, --scope <scope>', 'Settings scope (user|project)', 'user')
370
- .action((options) => {
371
- const scope = options.scope;
372
- const settingsPath = getSettingsPath(scope);
373
- console.log(`Reading ${scope} settings from: ${settingsPath}`);
374
- console.log();
375
- const settings = loadClaudeSettings(settingsPath);
376
- if (!settings) {
377
- console.log('Settings file not found.');
378
- return;
379
- }
380
- const existing = getExistingPermissions(settings);
381
- if (existing.size === 0) {
382
- console.log('No MCP permissions configured.');
383
- return;
384
- }
385
- console.log(`Found ${existing.size} MCP permission${existing.size === 1 ? '' : 's'}:`);
386
- console.log();
387
- // Group by server
388
- const byServer = new Map();
389
- for (const perm of existing) {
390
- const match = perm.match(/^mcp__([^_]+)__(.+)$/);
391
- if (match && match[1] && match[2]) {
392
- const server = match[1];
393
- const tool = match[2];
394
- const tools = byServer.get(server) || [];
395
- tools.push(tool);
396
- byServer.set(server, tools);
397
- }
398
- }
399
- for (const [server, tools] of byServer) {
400
- console.log(`${server}:`);
401
- for (const tool of tools.sort()) {
402
- console.log(` - ${tool}`);
403
- }
404
- console.log();
405
- }
406
- });
407
- program
408
- .command('install-skill')
409
- .description('Install MCP Conductor skill to ~/.claude/skills/ for automatic guidance')
410
- .option('--dry-run', 'Show what would be created without making changes')
411
- .option('--force', 'Overwrite existing skill if present')
412
- .action(async (options) => {
413
- const fs = await import('node:fs');
414
- const path = await import('node:path');
415
- const os = await import('node:os');
416
- const skillsDir = path.join(os.homedir(), '.claude', 'skills');
417
- const targetPath = path.join(skillsDir, 'mcp-conductor.md');
418
- // Get the template path
419
- const pkgDir = path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname)));
420
- const templatePath = path.join(pkgDir, 'templates', 'skill-mcp-conductor.md');
421
- // Read the template
422
- let template;
423
- try {
424
- template = fs.readFileSync(templatePath, 'utf-8');
425
- }
426
- catch {
427
- console.error('Skill template not found. Please ensure mcp-conductor is properly installed.');
428
- process.exit(1);
429
- }
430
- const exists = fs.existsSync(targetPath);
431
- if (options.dryRun) {
432
- console.log('MCP Conductor - Install Skill (Dry Run)');
433
- console.log('=======================================');
434
- console.log();
435
- console.log(`Target: ${targetPath}`);
436
- console.log(`Exists: ${exists ? 'Yes' : 'No'}`);
437
- console.log(`Action: ${exists ? (options.force ? 'Overwrite' : 'Skip') : 'Create'}`);
438
- return;
439
- }
440
- if (exists && !options.force) {
441
- console.log(`Skill already exists at ${targetPath}`);
442
- console.log();
443
- console.log('Use --force to overwrite.');
444
- return;
445
- }
446
- try {
447
- // Ensure skills directory exists
448
- if (!fs.existsSync(skillsDir)) {
449
- fs.mkdirSync(skillsDir, { recursive: true });
450
- }
451
- fs.writeFileSync(targetPath, template);
452
- console.log(`Created skill at ${targetPath}`);
453
- console.log();
454
- console.log('The skill "mcp-conductor" is now available in Claude Code.');
455
- console.log();
456
- console.log('Usage: When Claude is about to do multi-step MCP operations,');
457
- console.log('it can invoke the skill for guidance on using execute_code.');
458
- console.log();
459
- console.log('To make it automatic, add to your ~/.claude/CLAUDE.md:');
460
- console.log();
461
- console.log(' ## MCP Operations');
462
- console.log(' When doing multi-step MCP operations, invoke the mcp-conductor skill first.');
463
- }
464
- catch (error) {
465
- console.error('Failed to write skill:', String(error));
466
- process.exit(1);
467
- }
468
- });
469
- program
470
- .command('install-instructions')
471
- .description('Install MCP Conductor project instructions (CLAUDE.md) to help Claude use execute_code')
472
- .option('-d, --dir <directory>', 'Target directory for CLAUDE.md', '.')
473
- .option('--force', 'Overwrite existing CLAUDE.md if present')
474
- .option('--append', 'Append to existing CLAUDE.md instead of creating new')
475
- .option('--dry-run', 'Show what would be created without making changes')
476
- .action(async (options) => {
477
- const fs = await import('node:fs');
478
- const path = await import('node:path');
479
- const targetDir = path.resolve(options.dir);
480
- const targetPath = path.join(targetDir, 'CLAUDE.md');
481
- // Get the template path relative to this script
482
- const pkgDir = path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname)));
483
- const templatePath = path.join(pkgDir, 'templates', 'CLAUDE.md');
484
- // Read the template
485
- let template;
486
- try {
487
- template = fs.readFileSync(templatePath, 'utf-8');
488
- }
489
- catch {
490
- console.error('Template file not found. Please ensure mcp-conductor is properly installed.');
491
- process.exit(1);
492
- }
493
- // Check if target exists
494
- const exists = fs.existsSync(targetPath);
495
- if (options.dryRun) {
496
- console.log('MCP Conductor - Install Instructions (Dry Run)');
497
- console.log('=============================================');
498
- console.log();
499
- console.log(`Target: ${targetPath}`);
500
- console.log(`Exists: ${exists ? 'Yes' : 'No'}`);
501
- console.log(`Action: ${exists ? (options.append ? 'Append' : (options.force ? 'Overwrite' : 'Skip')) : 'Create'}`);
502
- console.log();
503
- if (!exists || options.force || options.append) {
504
- console.log('Content to write:');
505
- console.log('─'.repeat(50));
506
- if (options.append && exists) {
507
- console.log('[Existing content preserved]');
508
- console.log();
509
- }
510
- console.log(template);
511
- }
512
- return;
513
- }
514
- if (exists && !options.force && !options.append) {
515
- console.log(`CLAUDE.md already exists at ${targetPath}`);
516
- console.log();
517
- console.log('Options:');
518
- console.log(' --force Overwrite the existing file');
519
- console.log(' --append Append instructions to the existing file');
520
- return;
521
- }
522
- try {
523
- // Ensure directory exists
524
- if (!fs.existsSync(targetDir)) {
525
- fs.mkdirSync(targetDir, { recursive: true });
526
- }
527
- if (options.append && exists) {
528
- const existing = fs.readFileSync(targetPath, 'utf-8');
529
- const separator = '\n\n---\n\n';
530
- fs.writeFileSync(targetPath, existing + separator + template);
531
- console.log(`Appended MCP Conductor instructions to ${targetPath}`);
532
- }
533
- else {
534
- fs.writeFileSync(targetPath, template);
535
- console.log(`Created ${targetPath}`);
536
- }
537
- console.log();
538
- console.log('Claude Code will now see these instructions and prefer using execute_code');
539
- console.log('for multi-step MCP operations, providing significant token savings.');
540
- console.log();
541
- console.log('Next steps:');
542
- console.log(' 1. Restart Claude Code in this project');
543
- console.log(' 2. Claude will automatically use execute_code for batch operations');
544
- }
545
- catch (error) {
546
- console.error('Failed to write file:', String(error));
547
- process.exit(1);
548
- }
549
- });
550
- // =============================================================================
551
- // Colour helpers for CLI output
552
- // =============================================================================
553
- const colours = {
554
- reset: '\x1b[0m',
555
- bright: '\x1b[1m',
556
- dim: '\x1b[2m',
557
- red: '\x1b[31m',
558
- green: '\x1b[32m',
559
- yellow: '\x1b[33m',
560
- blue: '\x1b[34m',
561
- magenta: '\x1b[35m',
562
- cyan: '\x1b[36m',
563
- };
564
- const c = {
565
- error: (text) => `${colours.bright}${colours.red}${text}${colours.reset}`,
566
- warning: (text) => `${colours.bright}${colours.yellow}${text}${colours.reset}`,
567
- success: (text) => `${colours.bright}${colours.green}${text}${colours.reset}`,
568
- info: (text) => `${colours.bright}${colours.cyan}${text}${colours.reset}`,
569
- dim: (text) => `${colours.dim}${text}${colours.reset}`,
570
- highlight: (text) => `${colours.bright}${colours.magenta}${text}${colours.reset}`,
571
- };
572
- // =============================================================================
573
- // Exclusive Mode Commands
574
- // =============================================================================
575
- program
576
- .command('enable-exclusive')
577
- .description('Enable exclusive mode - Claude only sees mcp-conductor, other servers are managed internally')
578
- .option('--dry-run', 'Show what would happen without making changes')
579
- .action(async (options) => {
580
- const fs = await import('node:fs');
581
- const path = await import('node:path');
582
- console.log(c.info('MCP Conductor - Enable Exclusive Mode'));
583
- console.log('======================================');
584
- console.log();
585
- // Check if Claude config exists
586
- const claudeConfigPath = findClaudeConfig();
587
- if (!claudeConfigPath) {
588
- console.log(c.error('Error:'), 'No Claude configuration file found.');
589
- console.log();
590
- console.log('Expected locations:');
591
- for (const p of getClaudeConfigPaths()) {
592
- console.log(` - ${p}`);
593
- }
594
- process.exit(1);
595
- }
596
- // Load Claude config
597
- const claudeConfig = loadClaudeConfig(claudeConfigPath);
598
- if (!claudeConfig?.mcpServers) {
599
- console.log(c.error('Error:'), 'No MCP servers found in Claude config.');
600
- process.exit(1);
601
- }
602
- // Get servers to migrate (excluding mcp-conductor itself)
603
- const serversToMigrate = Object.entries(claudeConfig.mcpServers).filter(([name]) => name !== 'mcp-conductor' && name !== 'mcp-executor');
604
- if (serversToMigrate.length === 0) {
605
- console.log('No servers to migrate (only mcp-conductor found).');
606
- console.log();
607
- console.log(c.success('Exclusive mode is already effectively enabled!'));
608
- return;
609
- }
610
- // Print clear warning
611
- console.log(c.warning('⚠️ WARNING: This will modify your configuration files!'));
612
- console.log();
613
- console.log('The following changes will be made:');
614
- console.log();
615
- console.log(c.highlight('1. Create/update:'), getDefaultConductorConfigPath());
616
- console.log(' This file will store your MCP server configurations internally.');
617
- console.log();
618
- console.log(c.highlight('2. Modify:'), claudeConfigPath);
619
- console.log(' Remove all MCP servers except mcp-conductor.');
620
- console.log();
621
- console.log(c.info('Servers to migrate:'));
622
- for (const [name] of serversToMigrate) {
623
- console.log(` - ${name}`);
624
- }
625
- console.log();
626
- console.log(c.warning('What this means:'));
627
- console.log(' • Claude will ONLY see mcp-conductor');
628
- console.log(' • Claude MUST use execute_code for all MCP operations');
629
- console.log(' • This maximises token savings (90%+)');
630
- console.log();
631
- console.log(c.info('To undo this change:'));
632
- console.log(` node dist/bin/cli.js disable-exclusive`);
633
- console.log();
634
- if (options.dryRun) {
635
- console.log(c.dim('(Dry run - no changes made)'));
636
- console.log();
637
- // Show what conductor config would look like
638
- const conductorConfig = {
639
- exclusive: true,
640
- servers: {},
641
- };
642
- for (const [name, config] of serversToMigrate) {
643
- conductorConfig.servers[name] = config;
644
- }
645
- console.log('Would create conductor config:');
646
- console.log(JSON.stringify(conductorConfig, null, 2));
647
- console.log();
648
- // Show what Claude config would look like
649
- console.log('Would update Claude config mcpServers to:');
650
- const remaining = Object.fromEntries(Object.entries(claudeConfig.mcpServers).filter(([name]) => name === 'mcp-conductor' || name === 'mcp-executor'));
651
- // Ensure mcp-conductor is present
652
- if (!remaining['mcp-conductor']) {
653
- const pkgDir = path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname)));
654
- const serverPath = path.join(pkgDir, 'dist', 'index.js');
655
- remaining['mcp-conductor'] = { command: 'node', args: [serverPath] };
656
- }
657
- console.log(JSON.stringify(remaining, null, 2));
658
- return;
659
- }
660
- // Create conductor config
661
- const conductorConfig = {
662
- exclusive: true,
663
- servers: {},
664
- };
665
- for (const [name, config] of serversToMigrate) {
666
- conductorConfig.servers[name] = config;
667
- }
668
- const saveResult = saveConductorConfig(conductorConfig);
669
- if (!saveResult.success) {
670
- console.log(c.error('Error:'), `Failed to save conductor config: ${saveResult.error}`);
671
- process.exit(1);
672
- }
673
- console.log(c.success('✓'), `Created ${saveResult.path}`);
674
- // Update Claude config - remove migrated servers, ensure mcp-conductor exists
675
- const pkgDir = path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname)));
676
- const serverPath = path.join(pkgDir, 'dist', 'index.js');
677
- const newMcpServers = {
678
- 'mcp-conductor': claudeConfig.mcpServers['mcp-conductor'] || {
679
- command: 'node',
680
- args: [serverPath],
681
- },
682
- };
683
- // Read and update the full Claude config (preserving other settings)
684
- const fullClaudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf-8'));
685
- fullClaudeConfig.mcpServers = newMcpServers;
686
- // Backup original
687
- const backupPath = claudeConfigPath + '.backup-' + Date.now();
688
- fs.writeFileSync(backupPath, fs.readFileSync(claudeConfigPath, 'utf-8'));
689
- console.log(c.success('✓'), `Backed up original config to ${path.basename(backupPath)}`);
690
- // Write updated config
691
- fs.writeFileSync(claudeConfigPath, JSON.stringify(fullClaudeConfig, null, 2));
692
- console.log(c.success('✓'), `Updated ${claudeConfigPath}`);
693
- console.log();
694
- console.log(c.success('Exclusive mode enabled!'));
695
- console.log();
696
- console.log('Next steps:');
697
- console.log(' 1. Restart Claude Desktop/Claude Code');
698
- console.log(' 2. Claude will now only see mcp-conductor');
699
- console.log(' 3. All MCP operations will go through execute_code');
700
- console.log();
701
- console.log('To undo:');
702
- console.log(` node dist/bin/cli.js disable-exclusive`);
703
- });
704
- program
705
- .command('disable-exclusive')
706
- .description('Disable exclusive mode - restore servers back to Claude config')
707
- .option('--dry-run', 'Show what would happen without making changes')
708
- .action(async (options) => {
709
- const fs = await import('node:fs');
710
- const path = await import('node:path');
711
- console.log(c.info('MCP Conductor - Disable Exclusive Mode'));
712
- console.log('=======================================');
713
- console.log();
714
- // Check if conductor config exists
715
- const conductorConfigPath = findConductorConfig();
716
- if (!conductorConfigPath) {
717
- console.log('No conductor config found - exclusive mode is not enabled.');
718
- return;
719
- }
720
- const conductorConfig = loadConductorConfig(conductorConfigPath);
721
- if (!conductorConfig || Object.keys(conductorConfig.servers).length === 0) {
722
- console.log('Conductor config has no servers to restore.');
723
- return;
724
- }
725
- // Check Claude config
726
- const claudeConfigPath = findClaudeConfig();
727
- if (!claudeConfigPath) {
728
- console.log(c.error('Error:'), 'No Claude configuration file found.');
729
- process.exit(1);
730
- }
731
- const serversToRestore = Object.keys(conductorConfig.servers);
732
- console.log(c.warning('⚠️ WARNING: This will modify your configuration files!'));
733
- console.log();
734
- console.log('The following changes will be made:');
735
- console.log();
736
- console.log(c.highlight('1. Update:'), claudeConfigPath);
737
- console.log(' Restore MCP servers from conductor config.');
738
- console.log();
739
- console.log(c.highlight('2. Update:'), conductorConfigPath);
740
- console.log(' Set exclusive=false and clear servers list.');
741
- console.log();
742
- console.log(c.info('Servers to restore:'));
743
- for (const name of serversToRestore) {
744
- console.log(` - ${name}`);
745
- }
746
- console.log();
747
- console.log(c.warning('What this means:'));
748
- console.log(' • Claude will see ALL MCP servers again');
749
- console.log(' • Claude MAY bypass mcp-conductor for some operations');
750
- console.log(' • Consider using CLAUDE.md to encourage execute_code usage');
751
- console.log();
752
- if (options.dryRun) {
753
- console.log(c.dim('(Dry run - no changes made)'));
754
- return;
755
- }
756
- // Load and update Claude config
757
- const fullClaudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf-8'));
758
- if (!fullClaudeConfig.mcpServers) {
759
- fullClaudeConfig.mcpServers = {};
760
- }
761
- // Restore servers
762
- for (const [name, config] of Object.entries(conductorConfig.servers)) {
763
- fullClaudeConfig.mcpServers[name] = config;
764
- }
765
- // Backup and update Claude config
766
- const backupPath = claudeConfigPath + '.backup-' + Date.now();
767
- fs.writeFileSync(backupPath, fs.readFileSync(claudeConfigPath, 'utf-8'));
768
- console.log(c.success('✓'), `Backed up original config to ${path.basename(backupPath)}`);
769
- fs.writeFileSync(claudeConfigPath, JSON.stringify(fullClaudeConfig, null, 2));
770
- console.log(c.success('✓'), `Restored ${serversToRestore.length} servers to Claude config`);
771
- // Update conductor config (set exclusive=false, clear servers)
772
- const updatedConductorConfig = {
773
- exclusive: false,
774
- servers: {},
775
- };
776
- saveConductorConfig(updatedConductorConfig, conductorConfigPath);
777
- console.log(c.success('✓'), `Cleared conductor config`);
778
- console.log();
779
- console.log(c.success('Exclusive mode disabled!'));
780
- console.log();
781
- console.log('Next steps:');
782
- console.log(' 1. Restart Claude Desktop/Claude Code');
783
- console.log(' 2. Claude will now see all MCP servers');
784
- console.log();
785
- console.log('Tip: Use install-instructions to add CLAUDE.md guidance for token-efficient operations.');
786
- });
787
- // Config command group
788
- const config = program
789
- .command('config')
790
- .description('Manage MCP Conductor configuration');
791
- config
792
- .command('show')
793
- .description('Show current configuration mode and server sources')
794
- .action(async () => {
795
- console.log(c.info('MCP Conductor - Configuration Status'));
796
- console.log('=====================================');
797
- console.log();
798
- // Check conductor config
799
- const conductorConfigPath = findConductorConfig();
800
- const conductorConfig = conductorConfigPath ? loadConductorConfig(conductorConfigPath) : null;
801
- const conductorServerCount = Object.keys(conductorConfig?.servers || {}).length;
802
- // Check Claude config
803
- const claudeConfigPath = findClaudeConfig();
804
- const claudeConfig = claudeConfigPath ? loadClaudeConfig(claudeConfigPath) : null;
805
- const claudeServerCount = Object.keys(claudeConfig?.mcpServers || {}).filter(n => n !== 'mcp-conductor' && n !== 'mcp-executor').length;
806
- // Determine mode
807
- let mode;
808
- if (conductorConfig?.exclusive && conductorServerCount > 0) {
809
- mode = c.success('EXCLUSIVE');
810
- }
811
- else if (conductorServerCount > 0) {
812
- mode = c.info('SHARED');
813
- }
814
- else {
815
- mode = c.dim('CLAUDE-ONLY');
816
- }
817
- console.log('Mode:', mode);
818
- console.log();
819
- console.log(c.highlight('Conductor Config:'));
820
- if (conductorConfigPath) {
821
- console.log(` Path: ${conductorConfigPath}`);
822
- console.log(` Exclusive: ${conductorConfig?.exclusive ? 'Yes' : 'No'}`);
823
- console.log(` Servers: ${conductorServerCount}`);
824
- if (conductorServerCount > 0) {
825
- for (const name of Object.keys(conductorConfig?.servers || {})) {
826
- console.log(` - ${name}`);
827
- }
828
- }
829
- }
830
- else {
831
- console.log(' Not found');
832
- }
833
- console.log();
834
- console.log(c.highlight('Claude Config:'));
835
- if (claudeConfigPath) {
836
- console.log(` Path: ${claudeConfigPath}`);
837
- console.log(` Servers (excluding mcp-conductor): ${claudeServerCount}`);
838
- if (claudeServerCount > 0) {
839
- for (const name of Object.keys(claudeConfig?.mcpServers || {})) {
840
- if (name !== 'mcp-conductor' && name !== 'mcp-executor') {
841
- console.log(` - ${name}`);
842
- }
843
- }
844
- }
845
- }
846
- else {
847
- console.log(' Not found');
848
- }
849
- console.log();
850
- if (conductorConfig?.exclusive && conductorServerCount > 0) {
851
- console.log(c.success('Claude only sees mcp-conductor. Maximum token savings enabled.'));
852
- }
853
- else if (claudeServerCount > 0) {
854
- console.log(c.warning('Claude sees multiple servers. Consider enable-exclusive for maximum token savings.'));
855
- }
856
- });
857
- config
858
- .command('servers')
859
- .description('List servers in conductor config')
860
- .action(async () => {
861
- const conductorConfig = loadConductorConfig();
862
- if (!conductorConfig || Object.keys(conductorConfig.servers).length === 0) {
863
- console.log('No servers in conductor config.');
864
- console.log();
865
- console.log('Run `mcp-conductor enable-exclusive` to migrate servers from Claude config.');
866
- return;
867
- }
868
- console.log(c.info('Conductor Config Servers'));
869
- console.log('========================');
870
- console.log();
871
- for (const [name, config] of Object.entries(conductorConfig.servers)) {
872
- console.log(`${c.highlight(name)}:`);
873
- console.log(` Command: ${config.command}`);
874
- if (config.args && config.args.length > 0) {
875
- console.log(` Args: ${config.args.join(' ')}`);
876
- }
877
- if (config.env) {
878
- console.log(` Env: ${Object.keys(config.env).join(', ')}`);
879
- }
880
- console.log();
881
- }
882
- });
883
- config
884
- .command('add <name> <command> [args...]')
885
- .description('Add a server to conductor config')
886
- .action(async (name, command, args) => {
887
- const fs = await import('node:fs');
888
- // Load or create conductor config
889
- let conductorConfig = loadConductorConfig();
890
- if (!conductorConfig) {
891
- conductorConfig = { ...DEFAULT_CONDUCTOR_CONFIG };
892
- }
893
- if (conductorConfig.servers[name]) {
894
- console.log(c.warning('Warning:'), `Server "${name}" already exists. Use config remove first to update.`);
895
- return;
896
- }
897
- conductorConfig.servers[name] = {
898
- command,
899
- args: args.length > 0 ? args : undefined,
900
- };
901
- const result = saveConductorConfig(conductorConfig);
902
- if (result.success) {
903
- console.log(c.success('✓'), `Added server "${name}" to conductor config`);
904
- console.log();
905
- console.log('Server configuration:');
906
- console.log(` Command: ${command}`);
907
- if (args.length > 0) {
908
- console.log(` Args: ${args.join(' ')}`);
909
- }
910
- }
911
- else {
912
- console.log(c.error('Error:'), result.error);
913
- process.exit(1);
914
- }
915
- });
916
- config
917
- .command('remove <name>')
918
- .description('Remove a server from conductor config')
919
- .action(async (name) => {
920
- const conductorConfig = loadConductorConfig();
921
- if (!conductorConfig) {
922
- console.log('No conductor config found.');
923
- return;
924
- }
925
- if (!conductorConfig.servers[name]) {
926
- console.log(`Server "${name}" not found in conductor config.`);
927
- return;
928
- }
929
- delete conductorConfig.servers[name];
930
- const result = saveConductorConfig(conductorConfig);
931
- if (result.success) {
932
- console.log(c.success('✓'), `Removed server "${name}" from conductor config`);
933
- }
934
- else {
935
- console.log(c.error('Error:'), result.error);
936
- process.exit(1);
937
- }
938
- });
939
- program.parse();
940
- //# sourceMappingURL=cli.js.map