@darkiceinteractive/mcp-conductor 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -432
- package/dist/bridge/http-server.d.ts.map +1 -1
- package/dist/bridge/http-server.js +25 -5
- package/dist/bridge/http-server.js.map +1 -1
- package/dist/bridge/index.d.ts +4 -1
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +4 -1
- package/dist/bridge/index.js.map +1 -1
- package/dist/config/defaults.d.ts +48 -2
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +51 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +3 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/loader.d.ts +3 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +29 -6
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +2 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/hub/index.d.ts +3 -1
- package/dist/hub/index.d.ts.map +1 -1
- package/dist/hub/index.js +3 -1
- package/dist/hub/index.js.map +1 -1
- package/dist/hub/mcp-hub.d.ts +1 -0
- package/dist/hub/mcp-hub.d.ts.map +1 -1
- package/dist/hub/mcp-hub.js +47 -14
- package/dist/hub/mcp-hub.js.map +1 -1
- package/dist/index.d.ts +21 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -7
- package/dist/index.js.map +1 -1
- package/dist/metrics/index.d.ts +3 -1
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +3 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/metrics/metrics-collector.d.ts.map +1 -1
- package/dist/metrics/metrics-collector.js +1 -1
- package/dist/metrics/metrics-collector.js.map +1 -1
- package/dist/modes/index.d.ts +3 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +3 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/runtime/executor.d.ts +24 -1
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +175 -13
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +56 -1
- package/dist/server/mcp-server.js.map +1 -1
- package/dist/skills/index.d.ts +3 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +3 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/streaming/execution-stream.d.ts.map +1 -1
- package/dist/streaming/execution-stream.js +22 -5
- package/dist/streaming/execution-stream.js.map +1 -1
- package/dist/streaming/index.d.ts +3 -1
- package/dist/streaming/index.d.ts.map +1 -1
- package/dist/streaming/index.js +3 -1
- package/dist/streaming/index.js.map +1 -1
- package/dist/utils/env.d.ts +20 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +33 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/errors.d.ts +17 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +16 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/index.d.ts +4 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +8 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/rate-limiter.d.ts.map +1 -1
- package/dist/utils/rate-limiter.js +6 -0
- package/dist/utils/rate-limiter.js.map +1 -1
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/dist/watcher/index.d.ts +3 -1
- package/dist/watcher/index.d.ts.map +1 -1
- package/dist/watcher/index.js +3 -1
- package/dist/watcher/index.js.map +1 -1
- package/package.json +3 -4
- package/dist/bin/cli.d.ts +0 -8
- package/dist/bin/cli.d.ts.map +0 -1
- package/dist/bin/cli.js +0 -940
- 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
|