mcp-config-manager 1.0.2 → 1.0.3

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 CHANGED
@@ -101,11 +101,32 @@ npm run web
101
101
 
102
102
  ### Web UI
103
103
 
104
- Start the web server:
104
+ #### Development (when cloning repository)
105
105
  ```bash
106
106
  npm run web
107
- # or
108
- mcp-manager web --port 3456
107
+ ```
108
+
109
+ #### Global Installation
110
+ After installing globally with `npm install -g mcp-config-manager`:
111
+
112
+ **Start web server in foreground:**
113
+ ```bash
114
+ mcp-config-manager web --port 3456
115
+ ```
116
+
117
+ **Start web server as background daemon:**
118
+ ```bash
119
+ mcp-config-manager start --port 3456
120
+ ```
121
+
122
+ **Check daemon status:**
123
+ ```bash
124
+ mcp-config-manager status
125
+ ```
126
+
127
+ **Stop daemon:**
128
+ ```bash
129
+ mcp-config-manager stop
109
130
  ```
110
131
 
111
132
  Then open http://localhost:3456 in your browser.
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "mcp-config-manager",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Simple CLI and web UI to manage MCP configs across multiple AI clients",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
7
+ "mcp-config-manager": "src/cli.js",
7
8
  "mcp-manager": "src/cli.js"
8
9
  },
9
10
  "scripts": {
package/src/cli.js CHANGED
@@ -6,7 +6,7 @@ import { MCPConfigManager } from './config-manager.js';
6
6
  const manager = new MCPConfigManager();
7
7
 
8
8
  program
9
- .name('mcp-manager')
9
+ .name('mcp-config-manager')
10
10
  .description('CLI to manage MCP configurations across different AI clients')
11
11
  .version('1.0.0');
12
12
 
@@ -383,12 +383,108 @@ program
383
383
  .command('web')
384
384
  .description('Start the web UI server')
385
385
  .option('-p, --port <port>', 'Port to run the server on', '3456')
386
+ .option('-d, --daemon', 'Run in background as daemon')
386
387
  .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`);
388
+ if (options.daemon) {
389
+ const { spawn } = await import('child_process');
390
+ const fs = await import('fs/promises');
391
+ const path = await import('path');
392
+ const os = await import('os');
393
+
394
+ const pidFile = path.join(os.tmpdir(), 'mcp-config-manager.pid');
395
+
396
+ try {
397
+ // Check if daemon is already running
398
+ const existingPid = await fs.readFile(pidFile, 'utf8');
399
+ const { execSync } = await import('child_process');
400
+ try {
401
+ execSync(`kill -0 ${existingPid}`, { stdio: 'ignore' });
402
+ console.log(`Web UI server already running on port ${options.port} (PID: ${existingPid})`);
403
+ console.log(`Open http://localhost:${options.port} in your browser`);
404
+ return;
405
+ } catch {
406
+ // Process not running, remove stale pid file
407
+ await fs.unlink(pidFile);
408
+ }
409
+ } catch {
410
+ // PID file doesn't exist
411
+ }
412
+
413
+ // Start daemon
414
+ const daemon = spawn(process.execPath, [process.argv[1], 'web', '--port', options.port], {
415
+ detached: true,
416
+ stdio: 'ignore'
417
+ });
418
+
419
+ daemon.unref();
420
+ await fs.writeFile(pidFile, daemon.pid.toString());
421
+
422
+ console.log(`Web UI server started as daemon on port ${options.port} (PID: ${daemon.pid})`);
423
+ console.log(`Open http://localhost:${options.port} in your browser`);
424
+ console.log(`Use 'mcp-config-manager stop' to stop the server`);
425
+ } else {
426
+ console.log(`Starting web UI server on port ${options.port}...`);
427
+ console.log(`Open http://localhost:${options.port} in your browser`);
428
+
429
+ const { startServer } = await import('./server.js');
430
+ startServer(parseInt(options.port));
431
+ }
432
+ });
389
433
 
390
- const { startServer } = await import('./server.js');
391
- startServer(parseInt(options.port));
434
+ program
435
+ .command('start')
436
+ .description('Start the web UI server in background')
437
+ .option('-p, --port <port>', 'Port to run the server on', '3456')
438
+ .action(async (options) => {
439
+ // Call web command with daemon option
440
+ program.parse([process.argv[0], process.argv[1], 'web', '--daemon', '--port', options.port]);
441
+ });
442
+
443
+ program
444
+ .command('stop')
445
+ .description('Stop the background web UI server')
446
+ .action(async () => {
447
+ const fs = await import('fs/promises');
448
+ const path = await import('path');
449
+ const os = await import('os');
450
+ const { execSync } = await import('child_process');
451
+
452
+ const pidFile = path.join(os.tmpdir(), 'mcp-config-manager.pid');
453
+
454
+ try {
455
+ const pid = await fs.readFile(pidFile, 'utf8');
456
+ execSync(`kill ${pid}`);
457
+ await fs.unlink(pidFile);
458
+ console.log(`Web UI server stopped (PID: ${pid})`);
459
+ } catch (error) {
460
+ console.log('No running web UI server found');
461
+ }
462
+ });
463
+
464
+ program
465
+ .command('status')
466
+ .description('Check status of background web UI server')
467
+ .action(async () => {
468
+ const fs = await import('fs/promises');
469
+ const path = await import('path');
470
+ const os = await import('os');
471
+ const { execSync } = await import('child_process');
472
+
473
+ const pidFile = path.join(os.tmpdir(), 'mcp-config-manager.pid');
474
+
475
+ try {
476
+ const pid = await fs.readFile(pidFile, 'utf8');
477
+ try {
478
+ execSync(`kill -0 ${pid}`, { stdio: 'ignore' });
479
+ console.log(`Web UI server is running (PID: ${pid})`);
480
+ console.log(`Open http://localhost:3456 in your browser`);
481
+ } catch {
482
+ console.log('Web UI server is not running (stale PID file found)');
483
+ await fs.unlink(pidFile);
484
+ }
485
+ } catch {
486
+ console.log('Web UI server is not running');
487
+ }
392
488
  });
393
489
 
394
490
  program.parse();
@@ -3,21 +3,46 @@ import path from 'path';
3
3
  import os from 'os';
4
4
 
5
5
  import { CLIENTS as PROD_CLIENTS } from './clients.js';
6
- import { MOCK_CLIENTS, MOCK_GLOBAL_SERVERS_PATH } from '../test/mock-clients.js';
7
6
 
8
7
  const USE_MOCK_CLIENTS = process.env.MCP_USE_MOCK_CLIENTS === 'true';
9
- const CLIENTS = USE_MOCK_CLIENTS ? MOCK_CLIENTS : PROD_CLIENTS;
10
- const GLOBAL_SERVERS_PATH = USE_MOCK_CLIENTS ? MOCK_GLOBAL_SERVERS_PATH : path.join(os.homedir(), '.mcp-global-servers.json');
11
8
 
12
9
  export class MCPConfigManager {
13
10
  constructor() {
14
11
  this.platform = os.platform();
15
12
  this.availableClients = {};
13
+ this.clients = PROD_CLIENTS;
14
+ this.globalServersPath = path.join(os.homedir(), '.mcp-global-servers.json');
15
+ this.mockClientsInitialized = false;
16
+ }
17
+
18
+ async initializeMockClients() {
19
+ if (USE_MOCK_CLIENTS && !this.mockClientsInitialized) {
20
+ try {
21
+ const mockModule = await import('../test/mock-clients.js');
22
+ this.clients = mockModule.MOCK_CLIENTS;
23
+ this.globalServersPath = mockModule.MOCK_GLOBAL_SERVERS_PATH;
24
+ this.mockClientsInitialized = true;
25
+ } catch (error) {
26
+ console.warn('Mock clients not available, using production clients instead');
27
+ // Fall back to production clients if mock clients are not available
28
+ }
29
+ }
30
+ }
31
+
32
+ async getClients() {
33
+ await this.initializeMockClients();
34
+ return this.clients;
35
+ }
36
+
37
+ async getGlobalServersPath() {
38
+ await this.initializeMockClients();
39
+ return this.globalServersPath;
16
40
  }
17
41
 
18
42
  async readGlobalServers() {
19
43
  try {
20
- const content = await fs.readFile(GLOBAL_SERVERS_PATH, 'utf-8');
44
+ const globalPath = await this.getGlobalServersPath();
45
+ const content = await fs.readFile(globalPath, 'utf-8');
21
46
  return JSON.parse(content);
22
47
  } catch (error) {
23
48
  if (error.code !== 'ENOENT') {
@@ -29,7 +54,8 @@ export class MCPConfigManager {
29
54
  }
30
55
 
31
56
  async writeGlobalServers(globalServers) {
32
- await fs.writeFile(GLOBAL_SERVERS_PATH, JSON.stringify(globalServers, null, 2));
57
+ const globalPath = await this.getGlobalServersPath();
58
+ await fs.writeFile(globalPath, JSON.stringify(globalServers, null, 2));
33
59
  }
34
60
 
35
61
  async addGlobalServer(serverName, serverConfig) {
@@ -124,7 +150,8 @@ export class MCPConfigManager {
124
150
 
125
151
  async detectClients() {
126
152
  const detectedClients = {};
127
- for (const [id, client] of Object.entries(CLIENTS)) {
153
+ const clients = await this.getClients();
154
+ for (const [id, client] of Object.entries(clients)) {
128
155
  const configPath = client.configPaths[os.platform()];
129
156
  const absoluteConfigPath = path.isAbsolute(configPath) ? configPath : path.join(process.cwd(), configPath);
130
157
  try {
@@ -175,8 +202,9 @@ export class MCPConfigManager {
175
202
  return clientsWithConfigs;
176
203
  }
177
204
 
178
- getConfigPath(client) {
179
- const clientConfig = CLIENTS[client];
205
+ async getConfigPath(client) {
206
+ const clients = await this.getClients();
207
+ const clientConfig = clients[client];
180
208
  if (!clientConfig) {
181
209
  throw new Error(`Unknown client: ${client}`);
182
210
  }
@@ -189,13 +217,14 @@ export class MCPConfigManager {
189
217
  }
190
218
 
191
219
  async readConfig(client) {
192
- const configPath = this.getConfigPath(client);
220
+ const configPath = await this.getConfigPath(client);
193
221
  let clientConfig = { servers: {} };
194
222
 
195
223
  try {
196
224
  const content = await fs.readFile(configPath, 'utf-8');
197
225
  const parsedContent = JSON.parse(content);
198
- clientConfig = this.normalizeConfig(parsedContent, CLIENTS[client].format);
226
+ const clients = await this.getClients();
227
+ clientConfig = this.normalizeConfig(parsedContent, clients[client].format);
199
228
  } catch (error) {
200
229
  if (error.code !== 'ENOENT') {
201
230
  throw error;
@@ -239,8 +268,9 @@ export class MCPConfigManager {
239
268
  }
240
269
 
241
270
  async writeConfig(client, config) {
242
- const configPath = this.getConfigPath(client);
243
- const clientConfig = CLIENTS[client];
271
+ const configPath = await this.getConfigPath(client);
272
+ const clients = await this.getClients();
273
+ const clientConfig = clients[client];
244
274
 
245
275
  let originalConfig = {};
246
276
  try {
@@ -396,8 +426,9 @@ export class MCPConfigManager {
396
426
 
397
427
  async getAllEnvironmentVariables() {
398
428
  const envVarMap = new Map();
429
+ const clients = await this.getClients();
399
430
 
400
- for (const [clientId, clientInfo] of Object.entries(CLIENTS)) {
431
+ for (const [clientId, clientInfo] of Object.entries(clients)) {
401
432
  try {
402
433
  const config = await this.readConfig(clientId);
403
434
 
@@ -430,8 +461,9 @@ export class MCPConfigManager {
430
461
 
431
462
  async updateEnvironmentVariableAcrossConfigs(envKey, newValue, targetServers = null) {
432
463
  const results = [];
464
+ const clients = await this.getClients();
433
465
 
434
- for (const [clientId, clientInfo] of Object.entries(CLIENTS)) {
466
+ for (const [clientId, clientInfo] of Object.entries(clients)) {
435
467
  try {
436
468
  const config = await this.readConfig(clientId);
437
469
  let configModified = false;