mcp-config-manager 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.js ADDED
@@ -0,0 +1,394 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { MCPConfigManager } from './config-manager.js';
5
+
6
+ const manager = new MCPConfigManager();
7
+
8
+ program
9
+ .name('mcp-manager')
10
+ .description('CLI to manage MCP configurations across different AI clients')
11
+ .version('1.0.0');
12
+
13
+ program
14
+ .command('list')
15
+ .description('List all supported clients and their config status')
16
+ .action(async () => {
17
+ try {
18
+ const clients = await manager.listClients();
19
+ console.log('\nSupported MCP Clients:\n');
20
+
21
+ for (const client of clients) {
22
+ const status = client.exists ? `✓ ${client.serverCount || 0} server(s)` : '✗ No config';
23
+ console.log(` ${client.name.padEnd(20)} ${status.padEnd(20)} ${client.configPath}`);
24
+ }
25
+ } catch (error) {
26
+ console.error('Error:', error);
27
+ process.exit(1);
28
+ }
29
+ });
30
+
31
+ program
32
+ .command('show <client>')
33
+ .description('Show MCP servers for a client')
34
+ .action(async (client) => {
35
+ try {
36
+ const config = await manager.readConfig(client);
37
+ console.log(`\nMCP Servers for ${client}:\n`);
38
+
39
+ if (Object.keys(config.servers).length === 0) {
40
+ console.log(' No servers configured');
41
+ } else {
42
+ for (const [name, server] of Object.entries(config.servers)) {
43
+ console.log(` ${name}:`);
44
+ console.log(` Command: ${server.command || 'N/A'}`);
45
+ if (server.args) console.log(` Args: ${server.args.join(' ')}`);
46
+ if (server.env) {
47
+ console.log(` Environment variables:`);
48
+ for (const [key, value] of Object.entries(server.env)) {
49
+ const displayValue = value.includes('KEY') || value.includes('SECRET')
50
+ ? value.substring(0, 4) + '***'
51
+ : value;
52
+ console.log(` ${key}: ${displayValue}`);
53
+ }
54
+ }
55
+ console.log();
56
+ }
57
+ }
58
+ } catch (error) {
59
+ console.error('Error:', error.message);
60
+ process.exit(1);
61
+ }
62
+ });
63
+
64
+ program
65
+ .command('add <client> <serverName>')
66
+ .description('Add a new MCP server to a client')
67
+ .option('-c, --command <cmd>', 'Command to run the server')
68
+ .option('-a, --args <args...>', 'Arguments for the command')
69
+ .option('-e, --env <env...>', 'Environment variables (key=value)')
70
+ .action(async (client, serverName, options) => {
71
+ try {
72
+ const serverConfig = {};
73
+
74
+ if (options.command) serverConfig.command = options.command;
75
+ if (options.args) serverConfig.args = options.args;
76
+ if (options.env) {
77
+ serverConfig.env = {};
78
+ for (const envVar of options.env) {
79
+ const [key, ...valueParts] = envVar.split('=');
80
+ serverConfig.env[key] = valueParts.join('=');
81
+ }
82
+ }
83
+
84
+ await manager.addServer(client, serverName, serverConfig);
85
+ console.log(`✓ Added server '${serverName}' to ${client}`);
86
+ } catch (error) {
87
+ console.error('Error:', error.message);
88
+ process.exit(1);
89
+ }
90
+ });
91
+
92
+ program
93
+ .command('remove <client> <serverName>')
94
+ .description('Remove an MCP server from a client (use "all" for client to remove from all)')
95
+ .action(async (client, serverName) => {
96
+ try {
97
+ if (client === 'all') {
98
+ const clients = manager.getSupportedClients();
99
+ let removedCount = 0;
100
+
101
+ for (const targetClient of clients) {
102
+ try {
103
+ const config = await manager.readConfig(targetClient.id);
104
+ if (config.servers[serverName]) {
105
+ await manager.removeServer(targetClient.id, serverName);
106
+ console.log(` ✓ Removed from ${targetClient.name}`);
107
+ removedCount++;
108
+ }
109
+ } catch (err) {
110
+ // Server doesn't exist in this client, skip
111
+ }
112
+ }
113
+
114
+ console.log(`\n✓ Removed '${serverName}' from ${removedCount} client(s)`);
115
+ } else {
116
+ await manager.removeServer(client, serverName);
117
+ console.log(`✓ Removed server '${serverName}' from ${client}`);
118
+ }
119
+ } catch (error) {
120
+ console.error('Error:', error.message);
121
+ process.exit(1);
122
+ }
123
+ });
124
+
125
+ program
126
+ .command('copy <fromClient> <serverName> <toClient>')
127
+ .description('Copy an MCP server from one client to another (use "all" for toClient to copy to all)')
128
+ .option('-n, --new-name <name>', 'New name for the copied server')
129
+ .action(async (fromClient, serverName, toClient, options) => {
130
+ try {
131
+ if (toClient === 'all') {
132
+ const clients = manager.getSupportedClients();
133
+ let copiedCount = 0;
134
+ for (const client of clients) {
135
+ if (client.id !== fromClient) {
136
+ try {
137
+ await manager.copyServer(fromClient, serverName, client.id, options.newName);
138
+ console.log(` ✓ Copied to ${client.name}`);
139
+ copiedCount++;
140
+ } catch (err) {
141
+ console.log(` ✗ Failed to copy to ${client.name}: ${err.message}`);
142
+ }
143
+ }
144
+ }
145
+ console.log(`\n✓ Copied '${serverName}' to ${copiedCount} client(s)`);
146
+ } else {
147
+ await manager.copyServer(fromClient, serverName, toClient, options.newName);
148
+ const targetName = options.newName || serverName;
149
+ console.log(`✓ Copied '${serverName}' from ${fromClient} to ${toClient} as '${targetName}'`);
150
+ }
151
+ } catch (error) {
152
+ console.error('Error:', error.message);
153
+ process.exit(1);
154
+ }
155
+ });
156
+
157
+ program
158
+ .command('env <client> <serverName> <action> [key] [value]')
159
+ .description('Manage environment variables (action: set|unset|list)')
160
+ .action(async (client, serverName, action, key, value) => {
161
+ try {
162
+ const config = await manager.readConfig(client);
163
+ const server = config.servers[serverName];
164
+
165
+ if (!server) {
166
+ console.error(`Server '${serverName}' not found in ${client}`);
167
+ process.exit(1);
168
+ }
169
+
170
+ if (action === 'list') {
171
+ console.log(`\nEnvironment variables for ${serverName}:\n`);
172
+ if (server.env) {
173
+ for (const [k, v] of Object.entries(server.env)) {
174
+ const displayValue = v.includes('KEY') || v.includes('SECRET')
175
+ ? v.substring(0, 4) + '***'
176
+ : v;
177
+ console.log(` ${k}: ${displayValue}`);
178
+ }
179
+ } else {
180
+ console.log(' No environment variables set');
181
+ }
182
+ } else if (action === 'set') {
183
+ if (!key || !value) {
184
+ console.error('Both key and value required for set action');
185
+ process.exit(1);
186
+ }
187
+ await manager.updateServerEnv(client, serverName, key, value);
188
+ console.log(`✓ Set ${key} for server '${serverName}' in ${client}`);
189
+ } else if (action === 'unset') {
190
+ if (!key) {
191
+ console.error('Key required for unset action');
192
+ process.exit(1);
193
+ }
194
+ await manager.updateServerEnv(client, serverName, key, null);
195
+ console.log(`✓ Unset ${key} for server '${serverName}' in ${client}`);
196
+ } else {
197
+ console.error('Invalid action. Use: set, unset, or list');
198
+ process.exit(1);
199
+ }
200
+ } catch (error) {
201
+ console.error('Error:', error.message);
202
+ process.exit(1);
203
+ }
204
+ });
205
+
206
+ program
207
+ .command('export <client> [output]')
208
+ .description('Export configuration for a client')
209
+ .option('-s, --server <name>', 'Export only a specific server')
210
+ .action(async (client, output, options) => {
211
+ try {
212
+ let result;
213
+ if (options.server) {
214
+ result = await manager.exportServer(client, options.server, output);
215
+ } else {
216
+ result = await manager.exportConfig(client, output);
217
+ }
218
+
219
+ if (output) {
220
+ console.log(`✓ Exported to ${output}`);
221
+ } else {
222
+ console.log(JSON.stringify(result, null, 2));
223
+ }
224
+ } catch (error) {
225
+ console.error('Error:', error.message);
226
+ process.exit(1);
227
+ }
228
+ });
229
+
230
+ program
231
+ .command('import <client> <file>')
232
+ .description('Import configuration to a client')
233
+ .action(async (client, file) => {
234
+ try {
235
+ await manager.importConfig(client, file);
236
+ console.log(`✓ Imported configuration to ${client}`);
237
+ } catch (error) {
238
+ console.error('Error:', error.message);
239
+ process.exit(1);
240
+ }
241
+ });
242
+
243
+ program
244
+ .command('env-view')
245
+ .description('View all environment variables across all configs (reverse view)')
246
+ .option('-k, --key <key>', 'Filter by specific environment variable key')
247
+ .option('-v, --show-values', 'Show actual values (default: masked)')
248
+ .action(async (options) => {
249
+ try {
250
+ const allEnvVars = await manager.getAllEnvironmentVariables();
251
+
252
+ if (allEnvVars.length === 0) {
253
+ console.log('\nNo environment variables found in any configuration.\n');
254
+ return;
255
+ }
256
+
257
+ const filteredVars = options.key
258
+ ? allEnvVars.filter(v => v.key.toLowerCase().includes(options.key.toLowerCase()))
259
+ : allEnvVars;
260
+
261
+ if (filteredVars.length === 0) {
262
+ console.log(`\nNo environment variables found matching '${options.key}'.\n`);
263
+ return;
264
+ }
265
+
266
+ console.log('\nEnvironment Variables Across All Configs:\n');
267
+
268
+ for (const envVar of filteredVars) {
269
+ console.log(` ${envVar.key}:`);
270
+
271
+ // Group by value to see which configs share the same value
272
+ const valueGroups = new Map();
273
+ for (const location of envVar.locations) {
274
+ const displayValue = options.showValues
275
+ ? location.value
276
+ : (location.value.includes('KEY') || location.value.includes('SECRET')
277
+ ? location.value.substring(0, 4) + '***'
278
+ : location.value);
279
+
280
+ if (!valueGroups.has(displayValue)) {
281
+ valueGroups.set(displayValue, []);
282
+ }
283
+ valueGroups.get(displayValue).push(location);
284
+ }
285
+
286
+ // Display grouped by value
287
+ for (const [value, locations] of valueGroups.entries()) {
288
+ console.log(` Value: ${value}`);
289
+ for (const loc of locations) {
290
+ console.log(` - ${loc.clientName} / ${loc.server}`);
291
+ }
292
+ }
293
+ console.log();
294
+ }
295
+
296
+ console.log(`Total: ${filteredVars.length} environment variable(s)\n`);
297
+ } catch (error) {
298
+ console.error('Error:', error.message);
299
+ process.exit(1);
300
+ }
301
+ });
302
+
303
+ program
304
+ .command('env-update-all <key> [value]')
305
+ .description('Update an environment variable across all configs where it exists')
306
+ .option('-c, --clients <clients...>', 'Only update in specific clients')
307
+ .option('-s, --servers <servers...>', 'Only update in specific servers')
308
+ .option('-d, --dry-run', 'Show what would be updated without making changes')
309
+ .option('--unset', 'Remove the environment variable instead of updating')
310
+ .action(async (key, value, options) => {
311
+ try {
312
+ // First, get all environment variables to see where this key exists
313
+ const allEnvVars = await manager.getAllEnvironmentVariables();
314
+ const targetEnvVar = allEnvVars.find(v => v.key === key);
315
+
316
+ if (!targetEnvVar) {
317
+ console.log(`\nEnvironment variable '${key}' not found in any configuration.\n`);
318
+ return;
319
+ }
320
+
321
+ // Filter targets based on options
322
+ let targets = targetEnvVar.locations;
323
+
324
+ if (options.clients) {
325
+ targets = targets.filter(t => options.clients.includes(t.client));
326
+ }
327
+
328
+ if (options.servers) {
329
+ targets = targets.filter(t => options.servers.includes(t.server));
330
+ }
331
+
332
+ if (targets.length === 0) {
333
+ console.log('\nNo matching configurations found with the specified filters.\n');
334
+ return;
335
+ }
336
+
337
+ // Show what will be updated
338
+ console.log(`\n${options.dryRun ? '[DRY RUN] ' : ''}Updating '${key}':\n`);
339
+ console.log(' Affected configurations:');
340
+ for (const target of targets) {
341
+ const oldDisplay = target.value.includes('KEY') || target.value.includes('SECRET')
342
+ ? target.value.substring(0, 4) + '***'
343
+ : target.value;
344
+ const newDisplay = options.unset ? '(removed)' : value;
345
+ console.log(` - ${target.clientName} / ${target.server}: ${oldDisplay} → ${newDisplay}`);
346
+ }
347
+
348
+ if (options.dryRun) {
349
+ console.log('\n[DRY RUN] No changes were made.\n');
350
+ return;
351
+ }
352
+
353
+ // Perform the update
354
+ const actualValue = options.unset ? null : value;
355
+ const results = await manager.updateEnvironmentVariableAcrossConfigs(key, actualValue, targets);
356
+
357
+ // Show results
358
+ console.log('\nUpdate Results:');
359
+ let successCount = 0;
360
+ let errorCount = 0;
361
+
362
+ for (const result of results) {
363
+ if (result.success) {
364
+ successCount++;
365
+ console.log(` ✓ ${result.clientName} / ${result.server}`);
366
+ } else {
367
+ errorCount++;
368
+ console.log(` ✗ ${result.clientName}: ${result.error}`);
369
+ }
370
+ }
371
+
372
+ console.log(`\n✓ Updated ${successCount} configuration(s)`);
373
+ if (errorCount > 0) {
374
+ console.log(`✗ Failed to update ${errorCount} configuration(s)`);
375
+ }
376
+ } catch (error) {
377
+ console.error('Error:', error.message);
378
+ process.exit(1);
379
+ }
380
+ });
381
+
382
+ program
383
+ .command('web')
384
+ .description('Start the web UI server')
385
+ .option('-p, --port <port>', 'Port to run the server on', '3456')
386
+ .action(async (options) => {
387
+ console.log(`Starting web UI server on port ${options.port}...`);
388
+ console.log(`Open http://localhost:${options.port} in your browser`);
389
+
390
+ const { startServer } = await import('./server.js');
391
+ startServer(parseInt(options.port));
392
+ });
393
+
394
+ program.parse();
package/src/clients.js ADDED
@@ -0,0 +1,113 @@
1
+ import path from 'path';
2
+ import os from 'os';
3
+
4
+ export const CLIENTS = {
5
+ claude: {
6
+ name: 'Claude Desktop',
7
+ configPaths: {
8
+ darwin: path.join(os.homedir(), 'Library/Application Support/Claude/claude_desktop_config.json'),
9
+ win32: path.join(process.env.APPDATA || '', 'Claude/claude_desktop_config.json'),
10
+ linux: path.join(os.homedir(), '.config/Claude/claude_desktop_config.json')
11
+ },
12
+ format: 'mcpServers'
13
+ },
14
+ 'claude-code': {
15
+ name: 'Claude Code',
16
+ configPaths: {
17
+ darwin: '.mcp.json',
18
+ win32: '.mcp.json',
19
+ linux: '.mcp.json'
20
+ },
21
+ format: 'mcpServers'
22
+ },
23
+ vscode: {
24
+ name: 'VS Code',
25
+ configPaths: {
26
+ darwin: '.vscode/mcp.json',
27
+ win32: '.vscode/mcp.json',
28
+ linux: '.vscode/mcp.json'
29
+ },
30
+ format: 'mcp.servers'
31
+ },
32
+ cursor: {
33
+ name: 'Cursor',
34
+ configPaths: {
35
+ darwin: '.cursor/mcp.json',
36
+ win32: '.cursor/mcp.json',
37
+ linux: '.cursor/mcp.json'
38
+ },
39
+ format: 'mcpServers'
40
+ },
41
+ 'cursor-global': {
42
+ name: 'Cursor (Global)',
43
+ configPaths: {
44
+ darwin: path.join(os.homedir(), '.cursor/mcp.json'),
45
+ win32: path.join(os.homedir(), '.cursor/mcp.json'),
46
+ linux: path.join(os.homedir(), '.cursor/mcp.json')
47
+ },
48
+ format: 'mcpServers'
49
+ },
50
+ gemini: {
51
+ name: 'Gemini',
52
+ configPaths: {
53
+ darwin: path.join(os.homedir(), '.gemini/settings.json'),
54
+ win32: path.join(os.homedir(), '.gemini/settings.json'),
55
+ linux: path.join(os.homedir(), '.gemini/settings.json')
56
+ },
57
+ format: 'mcpServers'
58
+ },
59
+ windsurf: {
60
+ name: 'Windsurf',
61
+ configPaths: {
62
+ darwin: path.join(os.homedir(), '.codeium/windsurf/mcp_config.json'),
63
+ win32: path.join(process.env.APPDATA || '', 'WindSurf/mcp_settings.json'),
64
+ linux: path.join(os.homedir(), '.codeium/windsurf/mcp_config.json')
65
+ },
66
+ format: 'mcpServers'
67
+ },
68
+ amazonq: {
69
+ name: 'Amazon Q Developer',
70
+ configPaths: {
71
+ darwin: path.join(os.homedir(), '.aws/amazonq/mcp.json'),
72
+ win32: path.join(os.homedir(), '.aws/amazonq/mcp.json'),
73
+ linux: path.join(os.homedir(), '.aws/amazonq/mcp.json')
74
+ },
75
+ format: 'mcpServers'
76
+ },
77
+ '5ire': {
78
+ name: '5ire',
79
+ configPaths: {
80
+ darwin: path.join(os.homedir(), 'Library/Application Support/5ire/mcp.json'),
81
+ win32: path.join(process.env.APPDATA || '', '5ire/mcp.json'),
82
+ linux: path.join(os.homedir(), '.config/5ire/mcp.json')
83
+ },
84
+ format: 'mcpServers'
85
+ },
86
+ 'factory-bridge': {
87
+ name: 'Factory Bridge',
88
+ configPaths: {
89
+ darwin: path.join(os.homedir(), 'Library/Application Support/Factory Bridge/mcp.json'),
90
+ win32: path.join(process.env.APPDATA || '', 'Factory Bridge/mcp.json'),
91
+ linux: path.join(os.homedir(), '.config/Factory Bridge/mcp.json')
92
+ },
93
+ format: 'mcpServers'
94
+ },
95
+ cline: {
96
+ name: 'Cline',
97
+ configPaths: {
98
+ darwin: path.join(os.homedir(), 'Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json'),
99
+ win32: path.join(process.env.APPDATA || '', 'Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json'),
100
+ linux: path.join(os.homedir(), '.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json')
101
+ },
102
+ format: 'mcpServers'
103
+ },
104
+ 'roo-code': {
105
+ name: 'Roo Code',
106
+ configPaths: {
107
+ darwin: path.join(os.homedir(), 'Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json'),
108
+ win32: path.join(process.env.APPDATA || '', 'Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json'),
109
+ linux: path.join(os.homedir(), '.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json')
110
+ },
111
+ format: 'mcpServers'
112
+ }
113
+ };