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 +24 -3
- package/package.json +2 -1
- package/src/cli.js +101 -5
- package/src/config-manager.js +46 -14
package/README.md
CHANGED
|
@@ -101,11 +101,32 @@ npm run web
|
|
|
101
101
|
|
|
102
102
|
### Web UI
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
#### Development (when cloning repository)
|
|
105
105
|
```bash
|
|
106
106
|
npm run web
|
|
107
|
-
|
|
108
|
-
|
|
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.
|
|
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
|
-
|
|
388
|
-
|
|
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
|
-
|
|
391
|
-
|
|
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();
|
package/src/config-manager.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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;
|