mcp-config-manager 1.0.2 → 1.0.4

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.4",
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) {
@@ -103,10 +129,11 @@ export class MCPConfigManager {
103
129
  configHash: this.getServerConfigHash(serverConfig || {})
104
130
  };
105
131
  }
132
+ const configPath = await this.getConfigPath(clientId);
106
133
  allServers[serverName].clients.push({
107
134
  id: clientId,
108
135
  name: clientInfo.name,
109
- configPath: this.getConfigPath(clientId)
136
+ configPath
110
137
  });
111
138
  // If a server exists globally and in a client, mark it as global
112
139
  if (globalServers[serverName]) {
@@ -124,7 +151,8 @@ export class MCPConfigManager {
124
151
 
125
152
  async detectClients() {
126
153
  const detectedClients = {};
127
- for (const [id, client] of Object.entries(CLIENTS)) {
154
+ const clients = await this.getClients();
155
+ for (const [id, client] of Object.entries(clients)) {
128
156
  const configPath = client.configPaths[os.platform()];
129
157
  const absoluteConfigPath = path.isAbsolute(configPath) ? configPath : path.join(process.cwd(), configPath);
130
158
  try {
@@ -153,19 +181,21 @@ export class MCPConfigManager {
153
181
  try {
154
182
  const config = await this.readConfig(key);
155
183
  const serverCount = Object.keys(config.servers).length;
184
+ const configPath = await this.getConfigPath(key);
156
185
  clientsWithConfigs.push({
157
186
  id: key,
158
187
  name: client.name,
159
- configPath: this.getConfigPath(key),
188
+ configPath,
160
189
  serverCount,
161
190
  exists: true
162
191
  });
163
192
  } catch (error) {
164
193
  console.error(`Error processing client ${key}:`, error.message);
194
+ const configPath = await this.getConfigPath(key);
165
195
  clientsWithConfigs.push({
166
196
  id: key,
167
197
  name: client.name,
168
- configPath: this.getConfigPath(key),
198
+ configPath,
169
199
  serverCount: 0,
170
200
  exists: false
171
201
  });
@@ -175,8 +205,9 @@ export class MCPConfigManager {
175
205
  return clientsWithConfigs;
176
206
  }
177
207
 
178
- getConfigPath(client) {
179
- const clientConfig = CLIENTS[client];
208
+ async getConfigPath(client) {
209
+ const clients = await this.getClients();
210
+ const clientConfig = clients[client];
180
211
  if (!clientConfig) {
181
212
  throw new Error(`Unknown client: ${client}`);
182
213
  }
@@ -189,13 +220,14 @@ export class MCPConfigManager {
189
220
  }
190
221
 
191
222
  async readConfig(client) {
192
- const configPath = this.getConfigPath(client);
223
+ const configPath = await this.getConfigPath(client);
193
224
  let clientConfig = { servers: {} };
194
225
 
195
226
  try {
196
227
  const content = await fs.readFile(configPath, 'utf-8');
197
228
  const parsedContent = JSON.parse(content);
198
- clientConfig = this.normalizeConfig(parsedContent, CLIENTS[client].format);
229
+ const clients = await this.getClients();
230
+ clientConfig = this.normalizeConfig(parsedContent, clients[client].format);
199
231
  } catch (error) {
200
232
  if (error.code !== 'ENOENT') {
201
233
  throw error;
@@ -239,8 +271,9 @@ export class MCPConfigManager {
239
271
  }
240
272
 
241
273
  async writeConfig(client, config) {
242
- const configPath = this.getConfigPath(client);
243
- const clientConfig = CLIENTS[client];
274
+ const configPath = await this.getConfigPath(client);
275
+ const clients = await this.getClients();
276
+ const clientConfig = clients[client];
244
277
 
245
278
  let originalConfig = {};
246
279
  try {
@@ -396,8 +429,9 @@ export class MCPConfigManager {
396
429
 
397
430
  async getAllEnvironmentVariables() {
398
431
  const envVarMap = new Map();
432
+ const clients = await this.getClients();
399
433
 
400
- for (const [clientId, clientInfo] of Object.entries(CLIENTS)) {
434
+ for (const [clientId, clientInfo] of Object.entries(clients)) {
401
435
  try {
402
436
  const config = await this.readConfig(clientId);
403
437
 
@@ -430,8 +464,9 @@ export class MCPConfigManager {
430
464
 
431
465
  async updateEnvironmentVariableAcrossConfigs(envKey, newValue, targetServers = null) {
432
466
  const results = [];
467
+ const clients = await this.getClients();
433
468
 
434
- for (const [clientId, clientInfo] of Object.entries(CLIENTS)) {
469
+ for (const [clientId, clientInfo] of Object.entries(clients)) {
435
470
  try {
436
471
  const config = await this.readConfig(clientId);
437
472
  let configModified = false;