@maplezzk/mcps 1.0.14 → 1.0.24

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
@@ -25,11 +25,7 @@ mcps 支持守护进程模式,可以保持与 MCP 服务的长连接,显著
25
25
 
26
26
  **启动守护进程:**
27
27
  ```bash
28
- mcps daemon
29
- ```
30
- 或者
31
- ```bash
32
- mcps daemon start
28
+ mcps start
33
29
  ```
34
30
 
35
31
  **重启连接:**
@@ -37,49 +33,68 @@ mcps daemon start
37
33
 
38
34
  ```bash
39
35
  # 重置所有连接
40
- mcps daemon restart
36
+ mcps restart
41
37
 
42
38
  # 仅重置特定服务的连接
43
- mcps daemon restart my-server
39
+ mcps restart my-server
44
40
  ```
45
41
 
46
42
  **停止守护进程:**
47
43
  ```bash
48
- mcps daemon stop
44
+ mcps stop
45
+ ```
46
+
47
+ **查看守护进程状态:**
48
+ ```bash
49
+ mcps status
49
50
  ```
50
51
 
52
+ > **注意**:旧的三词命令(如 `mcps daemon start`)仍然可用,保持向后兼容。
53
+
51
54
  ### 2. 服务管理 (Server Management)
52
55
 
53
56
  **查看所有服务:**
54
57
  ```bash
55
- mcps server list
58
+ mcps ls
56
59
  ```
57
60
 
58
61
  **添加 Stdio 服务:**
59
62
  ```bash
60
63
  # 添加本地 Node.js 服务
61
- mcps server add my-server --command node --args ./build/index.js
64
+ mcps add my-server --command node --args ./build/index.js
62
65
 
63
66
  # 使用 npx/uvx 添加服务
64
- mcps server add fetch --command uvx --args mcp-server-fetch
67
+ mcps add fetch --command uvx --args mcp-server-fetch
65
68
  ```
66
69
 
67
70
  **添加 SSE 服务:**
68
71
  ```bash
69
- mcps server add remote-server --type sse --url http://localhost:8000/sse
72
+ mcps add remote-server --type sse --url http://localhost:8000/sse
70
73
  ```
71
74
 
72
75
  **添加 Streamable HTTP 服务:**
73
76
  ```bash
74
- mcps server add my-http-server --type http --url http://localhost:8000/mcp
77
+ mcps add my-http-server --type http --url http://localhost:8000/mcp
75
78
  ```
76
79
 
77
80
  **移除服务:**
78
81
  ```bash
79
- mcps server remove my-server
82
+ mcps rm my-server
83
+ ```
84
+
85
+ **更新服务:**
86
+ ```bash
87
+ # 刷新所有服务连接
88
+ mcps update
89
+
90
+ # 更新特定服务的命令
91
+ mcps update my-server --command new-command
92
+
93
+ # 更新特定服务的参数
94
+ mcps update my-server --args arg1 arg2
80
95
  ```
81
96
 
82
- ### 2. 工具交互 (Tool Interaction)
97
+ ### 3. 工具交互 (Tool Interaction)
83
98
 
84
99
  **查看服务下的可用工具:**
85
100
  ```bash
@@ -1,128 +1,240 @@
1
1
  import { spawn } from 'child_process';
2
2
  import http from 'http';
3
+ import net from 'net';
3
4
  import chalk from 'chalk';
4
5
  import { connectionPool } from '../core/pool.js';
5
6
  import { createRequire } from 'module';
6
7
  import { DAEMON_PORT } from '../core/constants.js';
7
8
  const require = createRequire(import.meta.url);
8
9
  const pkg = require('../../package.json');
9
- export const registerDaemonCommand = (program) => {
10
- const daemonCmd = program.command('daemon')
11
- .description('Manage the mcps daemon')
12
- .usage('start|stop|restart|status'); // Simplify usage display
13
- daemonCmd.command('start', { isDefault: true, hidden: true }) // Hide default start from help but keep functionality
14
- .description('Start the daemon (default)')
15
- .option('-p, --port <number>', 'Port to listen on', String(DAEMON_PORT))
16
- .action(async (options) => {
17
- const port = parseInt(options.port);
18
- // Check if already running
10
+ // Check if a port is in use
11
+ function isPortInUse(port) {
12
+ return new Promise((resolve) => {
13
+ const server = net.createServer();
14
+ server.once('error', () => {
15
+ resolve(true); // Port is in use
16
+ });
17
+ server.once('listening', () => {
18
+ server.once('close', () => {
19
+ resolve(false); // Port is available
20
+ });
21
+ server.close();
22
+ });
23
+ server.listen(port, '127.0.0.1');
24
+ });
25
+ }
26
+ // Action functions for daemon commands
27
+ const startAction = async (options) => {
28
+ const port = parseInt(options.port || process.env.MCPS_PORT || DAEMON_PORT);
29
+ // Check if port is in use (more reliable than HTTP check)
30
+ const portInUse = await isPortInUse(port);
31
+ if (portInUse) {
32
+ // Try to check if it's our daemon via HTTP
19
33
  try {
20
34
  const res = await fetch(`http://localhost:${port}/status`);
21
35
  if (res.ok) {
22
36
  console.log(chalk.yellow(`Daemon is already running on port ${port}.`));
37
+ process.exit(0);
23
38
  return;
24
39
  }
25
40
  }
26
41
  catch {
27
- // Not running, safe to start
28
- // If we are already detached (indicated by env var), run the server
29
- if (process.env.MCPS_DAEMON_DETACHED === 'true') {
30
- startDaemon(port);
42
+ // Port is in use but not our daemon
43
+ console.error(chalk.red(`Port ${port} is already in use by another process.`));
44
+ process.exit(1);
45
+ return;
46
+ }
47
+ }
48
+ // If we are already detached (indicated by env var), run the server
49
+ if (process.env.MCPS_DAEMON_DETACHED === 'true') {
50
+ startDaemon(port);
51
+ return;
52
+ }
53
+ // Otherwise, spawn a detached process
54
+ console.log(chalk.cyan('Starting daemon in background...'));
55
+ let childFailed = false;
56
+ const subprocess = spawn(process.execPath, [process.argv[1], 'daemon', 'start'], {
57
+ detached: true,
58
+ // Pipe stdout/stderr so we can see initialization logs
59
+ stdio: ['ignore', 'pipe', 'pipe'],
60
+ env: {
61
+ ...process.env,
62
+ MCPS_DAEMON_DETACHED: 'true',
63
+ MCPS_VERBOSE: options.verbose ? 'true' : 'false'
64
+ }
65
+ });
66
+ // Stream logs to current console while waiting for ready
67
+ if (subprocess.stdout) {
68
+ subprocess.stdout.on('data', (data) => {
69
+ process.stdout.write(`${data}`);
70
+ });
71
+ }
72
+ if (subprocess.stderr) {
73
+ subprocess.stderr.on('data', (data) => {
74
+ const msg = data.toString();
75
+ // Detect port conflict in child process
76
+ if (msg.includes('Port') && msg.includes('is already in use')) {
77
+ childFailed = true;
78
+ }
79
+ // Only show error output if it contains critical errors
80
+ if (msg.includes('Error') || msg.includes('EADDRINUSE')) {
81
+ process.stderr.write(chalk.red(`[Daemon] ${msg}`));
82
+ }
83
+ });
84
+ }
85
+ subprocess.unref();
86
+ // Wait briefly to ensure it started (optional but good UX)
87
+ // We can poll status for a second
88
+ const start = Date.now();
89
+ // Increased timeout to allow for connection initialization
90
+ while (Date.now() - start < 30000) {
91
+ // If child reported port conflict, check if daemon is actually running
92
+ if (childFailed) {
93
+ const stillRunning = await isPortInUse(port);
94
+ if (stillRunning) {
95
+ // Another daemon is running
96
+ console.log(chalk.yellow(`\nDaemon is already running on port ${port}.`));
97
+ process.exit(0);
31
98
  return;
32
99
  }
33
- // Otherwise, spawn a detached process
34
- console.log(chalk.gray('Starting daemon in background...'));
35
- const subprocess = spawn(process.execPath, [process.argv[1], 'daemon', 'start'], {
36
- detached: true,
37
- // Pipe stdout/stderr so we can see initialization logs
38
- stdio: ['ignore', 'pipe', 'pipe'],
39
- env: {
40
- ...process.env,
41
- MCPS_DAEMON_DETACHED: 'true'
100
+ }
101
+ try {
102
+ const res = await fetch(`http://localhost:${port}/status`);
103
+ if (res.ok) {
104
+ const data = await res.json();
105
+ if (data.initialized) {
106
+ console.log(chalk.green(`Daemon started successfully on port ${port}.`));
107
+ process.exit(0);
42
108
  }
43
- });
44
- // Stream logs to current console while waiting for ready
45
- if (subprocess.stdout) {
46
- subprocess.stdout.on('data', (data) => {
47
- process.stdout.write(chalk.gray(`[Daemon] ${data}`));
48
- });
49
- }
50
- if (subprocess.stderr) {
51
- subprocess.stderr.on('data', (data) => {
52
- process.stderr.write(chalk.red(`[Daemon] ${data}`));
53
- });
54
109
  }
55
- subprocess.unref();
56
- // Wait briefly to ensure it started (optional but good UX)
57
- // We can poll status for a second
58
- const start = Date.now();
59
- // Increased timeout to allow for connection initialization
60
- while (Date.now() - start < 10000) {
61
- try {
62
- const res = await fetch(`http://localhost:${port}/status`);
63
- if (res.ok) {
64
- const data = await res.json();
65
- if (data.initialized) {
66
- console.log(chalk.green(`Daemon started successfully on port ${port}.`));
67
- process.exit(0);
68
- }
110
+ }
111
+ catch { }
112
+ await new Promise(r => setTimeout(r, 200));
113
+ }
114
+ console.log(chalk.yellow('Daemon started (async check timeout, but likely running).'));
115
+ process.exit(0);
116
+ };
117
+ const stopAction = async (options) => {
118
+ try {
119
+ const port = parseInt(options?.port || process.env.MCPS_PORT || DAEMON_PORT);
120
+ await fetch(`http://localhost:${port}/stop`, { method: 'POST' });
121
+ console.log(chalk.green('Daemon stopped successfully.'));
122
+ }
123
+ catch (e) {
124
+ console.error(chalk.red('Failed to stop daemon. Is it running?'));
125
+ }
126
+ };
127
+ const statusAction = async (options) => {
128
+ try {
129
+ const port = parseInt(options?.port || process.env.MCPS_PORT || DAEMON_PORT);
130
+ const res = await fetch(`http://localhost:${port}/status`);
131
+ const data = await res.json();
132
+ console.log('');
133
+ console.log(chalk.green(`Daemon is running (v${data.version})`));
134
+ if (data.connections && data.connections.length > 0) {
135
+ // Helper function to calculate display width (Chinese chars count as 2)
136
+ const getDisplayWidth = (str) => {
137
+ let width = 0;
138
+ for (const char of str) {
139
+ if (char.charCodeAt(0) > 127) {
140
+ width += 2;
141
+ }
142
+ else {
143
+ width += 1;
69
144
  }
70
145
  }
71
- catch { }
72
- await new Promise(r => setTimeout(r, 200));
73
- }
74
- console.log(chalk.yellow('Daemon started (async check timeout, but likely running).'));
146
+ return width;
147
+ };
148
+ // Helper function to pad string considering Chinese characters
149
+ const padEndWidth = (str, targetWidth) => {
150
+ const displayWidth = getDisplayWidth(str);
151
+ const padding = Math.max(0, targetWidth - displayWidth);
152
+ return str + ' '.repeat(padding);
153
+ };
154
+ // Build table rows
155
+ const rows = data.connections.map((conn) => {
156
+ const statusText = conn.status === 'error' ? 'Error' : 'Connected';
157
+ const statusColor = conn.status === 'error' ? chalk.red : chalk.green;
158
+ const toolsCount = conn.toolsCount !== null ? conn.toolsCount : '-';
159
+ return {
160
+ name: conn.name,
161
+ status: statusColor(statusText),
162
+ tools: toolsCount
163
+ };
164
+ });
165
+ // Calculate column widths
166
+ const nameWidth = Math.max(4, ...rows.map((r) => getDisplayWidth(r.name)));
167
+ const statusWidth = 10;
168
+ const toolsWidth = 6;
169
+ // Print table header
170
+ console.log(chalk.bold('\nActive Connections:'));
171
+ console.log(chalk.bold(`${'NAME'.padEnd(nameWidth)} ${'STATUS'.padEnd(statusWidth)} ${'TOOLS'}`));
172
+ console.log(chalk.cyan('─'.repeat(nameWidth) + ' ' + '─'.repeat(statusWidth) + ' ' + '─'.repeat(toolsWidth)));
173
+ // Print table rows
174
+ rows.forEach((row) => {
175
+ console.log(`${padEndWidth(row.name, nameWidth)} ${String(row.status).padEnd(statusWidth)} ${String(row.tools)}`);
176
+ });
177
+ console.log(chalk.cyan(`Total: ${data.connections.length} connection(s)`));
75
178
  }
76
- });
179
+ else {
180
+ console.log(chalk.yellow('\nNo active connections.'));
181
+ }
182
+ console.log('');
183
+ }
184
+ catch (e) {
185
+ console.error(chalk.red('Daemon is not running.'));
186
+ }
187
+ };
188
+ const restartAction = async (serverName, options) => {
189
+ try {
190
+ const port = parseInt(options?.port || process.env.MCPS_PORT || DAEMON_PORT);
191
+ const res = await fetch(`http://localhost:${port}/restart`, {
192
+ method: 'POST',
193
+ body: JSON.stringify({ server: serverName })
194
+ });
195
+ const data = await res.json();
196
+ console.log(chalk.green(data.message));
197
+ }
198
+ catch (e) {
199
+ console.error(chalk.red('Failed to restart. Is the daemon running?'));
200
+ }
201
+ };
202
+ export const registerDaemonCommand = (program) => {
203
+ // ===== Top-level commands (new, simplified) =====
204
+ program.command('start')
205
+ .description('Start the daemon')
206
+ .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT))
207
+ .option('-v, --verbose', 'Show detailed logs')
208
+ .action((options) => startAction(options));
209
+ program.command('stop')
210
+ .description('Stop the daemon')
211
+ .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT))
212
+ .action((options) => stopAction(options));
213
+ program.command('status')
214
+ .description('Check daemon status')
215
+ .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT))
216
+ .action((options) => statusAction(options));
217
+ program.command('restart [server]')
218
+ .description('Restart the daemon or a specific server connection')
219
+ .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT))
220
+ .action((serverName, options) => restartAction(serverName, options));
221
+ // ===== Legacy daemon subcommands (for backward compatibility) =====
222
+ const daemonCmd = program.command('daemon')
223
+ .description('Manage the mcps daemon (legacy, use top-level commands)')
224
+ .usage('start|stop|restart|status')
225
+ .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT));
226
+ daemonCmd.command('start', { isDefault: true, hidden: true })
227
+ .description('Start the daemon (default)')
228
+ .action((options) => startAction(options));
77
229
  daemonCmd.command('stop')
78
230
  .description('Stop the running daemon')
79
- .action(async () => {
80
- try {
81
- await fetch(`http://localhost:${DAEMON_PORT}/stop`, { method: 'POST' });
82
- console.log(chalk.green('Daemon stopped successfully.'));
83
- }
84
- catch (e) {
85
- console.error(chalk.red('Failed to stop daemon. Is it running?'));
86
- }
87
- });
231
+ .action((options) => stopAction(options));
88
232
  daemonCmd.command('status')
89
233
  .description('Check daemon status')
90
- .action(async () => {
91
- try {
92
- const res = await fetch(`http://localhost:${DAEMON_PORT}/status`);
93
- const data = await res.json();
94
- console.log(chalk.green(`Daemon is running (v${data.version})`));
95
- if (data.connections && data.connections.length > 0) {
96
- console.log(chalk.bold('\nActive Connections:'));
97
- data.connections.forEach((conn) => {
98
- const count = conn.toolsCount !== null ? `(${conn.toolsCount} tools)` : (data.initializing ? '(initializing)' : '(error listing tools)');
99
- const status = conn.status === 'error' ? chalk.red('[Error]') : '';
100
- console.log(chalk.cyan(`- ${conn.name} ${chalk.gray(count)} ${status}`));
101
- });
102
- }
103
- else {
104
- console.log(chalk.gray('No active connections.'));
105
- }
106
- }
107
- catch (e) {
108
- console.error(chalk.red('Daemon is not running.'));
109
- }
110
- });
234
+ .action((options) => statusAction(options));
111
235
  daemonCmd.command('restart [server]')
112
236
  .description('Restart the daemon or a specific server connection')
113
- .action(async (serverName) => {
114
- try {
115
- const res = await fetch(`http://localhost:${DAEMON_PORT}/restart`, {
116
- method: 'POST',
117
- body: JSON.stringify({ server: serverName })
118
- });
119
- const data = await res.json();
120
- console.log(chalk.green(data.message));
121
- }
122
- catch (e) {
123
- console.error(chalk.red('Failed to restart. Is the daemon running?'));
124
- }
125
- });
237
+ .action((serverName, options) => restartAction(serverName, options));
126
238
  };
127
239
  const startDaemon = (port) => {
128
240
  const server = http.createServer(async (req, res) => {
@@ -238,12 +350,18 @@ const startDaemon = (port) => {
238
350
  });
239
351
  server.listen(port, async () => {
240
352
  // Initialize all connections eagerly
241
- await connectionPool.initializeAll();
353
+ try {
354
+ await connectionPool.initializeAll();
355
+ }
356
+ catch (error) {
357
+ console.error('[Daemon] Error during initialization:', error.message);
358
+ // Don't exit, continue running with partial connections
359
+ }
242
360
  });
243
361
  server.on('error', (e) => {
244
362
  if (e.code === 'EADDRINUSE') {
245
- console.log(chalk.yellow(`Port ${port} is already in use.`));
246
- process.exit(0); // Exit gracefully if already running
363
+ console.error(chalk.red(`Port ${port} is already in use by another daemon.`));
364
+ process.exit(1); // Exit with error if port is in use
247
365
  }
248
366
  else {
249
367
  console.error('[Daemon] Server error:', e);
@@ -258,4 +376,8 @@ const startDaemon = (port) => {
258
376
  };
259
377
  process.on('SIGINT', shutdown);
260
378
  process.on('SIGTERM', shutdown);
379
+ // Prevent unhandled rejections from crashing the daemon
380
+ process.on('unhandledRejection', (reason, promise) => {
381
+ console.error('[Daemon] Unhandled Rejection at:', promise, 'reason:', reason);
382
+ });
261
383
  };
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { configManager } from '../core/config.js';
3
+ import { DaemonClient } from '../core/daemon-client.js';
3
4
  export const registerServerCommands = (program) => {
4
5
  const listServersAction = () => {
5
6
  const servers = configManager.listServers();
@@ -7,39 +8,64 @@ export const registerServerCommands = (program) => {
7
8
  console.log(chalk.yellow('No servers configured.'));
8
9
  return;
9
10
  }
10
- console.log(chalk.bold('\nConfigured Servers:'));
11
- servers.forEach(s => {
12
- console.log(`- ${chalk.cyan(s.name)} [${chalk.magenta(s.type)}]`);
13
- if (s.type === 'stdio') {
14
- console.log(` Command: ${s.command} ${s.args.join(' ')}`);
15
- if (s.env)
16
- console.log(` Env: ${Object.keys(s.env).join(', ')}`);
11
+ // Helper function to calculate display width (Chinese chars count as 2)
12
+ const getDisplayWidth = (str) => {
13
+ let width = 0;
14
+ for (const char of str) {
15
+ if (char.charCodeAt(0) > 127) {
16
+ width += 2;
17
+ }
18
+ else {
19
+ width += 1;
20
+ }
21
+ }
22
+ return width;
23
+ };
24
+ // Helper function to pad string considering Chinese characters
25
+ const padEndWidth = (str, targetWidth) => {
26
+ const displayWidth = getDisplayWidth(str);
27
+ const padding = Math.max(0, targetWidth - displayWidth);
28
+ return str + ' '.repeat(padding);
29
+ };
30
+ // Build table rows
31
+ const rows = servers.map(server => {
32
+ const disabled = server.disabled === true;
33
+ const typeColor = server.type === 'stdio' ? chalk.cyan : chalk.yellow;
34
+ const enabledMark = disabled ? chalk.red('✗') : chalk.green('✓');
35
+ // Build command/URL string
36
+ let command = '';
37
+ if (server.type === 'stdio') {
38
+ command = `${server.command} ${server.args.join(' ')}`;
17
39
  }
18
40
  else {
19
- console.log(` URL: ${s.url}`);
41
+ command = server.url || '';
20
42
  }
21
- console.log('');
43
+ return {
44
+ name: server.name,
45
+ type: typeColor(server.type),
46
+ enabled: enabledMark,
47
+ command: command,
48
+ disabled
49
+ };
22
50
  });
51
+ // Calculate column widths
52
+ const nameWidth = Math.max(4, ...rows.map(r => getDisplayWidth(r.name)));
53
+ const typeWidth = 6;
54
+ const enabledWidth = 7;
55
+ const commandWidth = Math.max(7, ...rows.map(r => getDisplayWidth(r.command)));
56
+ // Print table header
57
+ console.log('');
58
+ console.log(chalk.bold(`${'NAME'.padEnd(nameWidth)} ${'TYPE'.padEnd(typeWidth)} ${'ENABLED'.padEnd(enabledWidth)} ${'COMMAND/URL'}`));
59
+ console.log(chalk.gray('─'.repeat(nameWidth) + ' ' + '─'.repeat(typeWidth) + ' ' + '─'.repeat(enabledWidth) + ' ' + '─'.repeat(commandWidth)));
60
+ // Print table rows
61
+ rows.forEach(row => {
62
+ console.log(`${padEndWidth(row.name, nameWidth)} ${String(row.type).padEnd(typeWidth)} ${String(row.enabled).padEnd(enabledWidth)} ${row.command}`);
63
+ });
64
+ console.log('');
65
+ console.log(chalk.cyan(`Total: ${servers.length} server(s)`));
66
+ console.log('');
23
67
  };
24
- // Register top-level list command
25
- program.command('list')
26
- .alias('ls')
27
- .description('List all configured servers')
28
- .action(listServersAction);
29
- const serverCmd = program.command('server')
30
- .description('Manage MCP servers');
31
- serverCmd.command('list')
32
- .alias('ls')
33
- .description('List all configured servers')
34
- .action(listServersAction);
35
- serverCmd.command('add <name>')
36
- .description('Add a new MCP server')
37
- .option('--type <type>', 'Server type (stdio, sse, or http)', 'stdio')
38
- .option('--command <command>', 'Command to execute (for stdio)')
39
- .option('--args [args...]', 'Arguments for the command', [])
40
- .option('--url <url>', 'URL for SSE/HTTP connection')
41
- .option('--env <env...>', 'Environment variables (KEY=VALUE)', [])
42
- .action((name, options) => {
68
+ const addServerAction = (name, options) => {
43
69
  try {
44
70
  if (options.type === 'sse' || options.type === 'http') {
45
71
  if (!options.url)
@@ -76,11 +102,8 @@ export const registerServerCommands = (program) => {
76
102
  catch (error) {
77
103
  console.error(chalk.red(`Error adding server: ${error.message}`));
78
104
  }
79
- });
80
- serverCmd.command('remove <name>')
81
- .alias('rm')
82
- .description('Remove a server')
83
- .action((name) => {
105
+ };
106
+ const removeServerAction = (name) => {
84
107
  try {
85
108
  configManager.removeServer(name);
86
109
  console.log(chalk.green(`Server "${name}" removed.`));
@@ -88,13 +111,35 @@ export const registerServerCommands = (program) => {
88
111
  catch (error) {
89
112
  console.error(chalk.red(error.message));
90
113
  }
91
- });
92
- serverCmd.command('update <name>')
93
- .description('Update a server')
94
- .option('--command <command>', 'New command')
95
- .option('--args [args...]', 'New arguments for the command')
96
- .option('--url <url>', 'New URL')
97
- .action((name, options) => {
114
+ };
115
+ const updateServerAction = async (name, options) => {
116
+ // If no server name provided, refresh all connections
117
+ if (!name) {
118
+ try {
119
+ await DaemonClient.ensureDaemon();
120
+ // Call daemon restart API to refresh all connections
121
+ const port = parseInt(process.env.MCPS_PORT || '4100');
122
+ const res = await fetch(`http://localhost:${port}/restart`, {
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({})
126
+ });
127
+ if (res.ok) {
128
+ const data = await res.json();
129
+ console.log(chalk.green(data.message));
130
+ console.log(chalk.gray('All servers will be reconnected on next use.'));
131
+ }
132
+ else {
133
+ throw new Error('Failed to refresh connections');
134
+ }
135
+ }
136
+ catch (error) {
137
+ console.error(chalk.red(`Failed to refresh all servers: ${error.message}`));
138
+ console.error(chalk.yellow('Make sure the daemon is running (use: mcps start)'));
139
+ }
140
+ return;
141
+ }
142
+ // Update specific server configuration
98
143
  try {
99
144
  const updates = {};
100
145
  if (options.command)
@@ -105,13 +150,67 @@ export const registerServerCommands = (program) => {
105
150
  updates.url = options.url;
106
151
  if (Object.keys(updates).length === 0) {
107
152
  console.log(chalk.yellow('No updates provided.'));
153
+ console.log(chalk.gray('Use: mcps update <server> --command <cmd> --args <args>'));
108
154
  return;
109
155
  }
110
156
  configManager.updateServer(name, updates);
111
157
  console.log(chalk.green(`Server "${name}" updated.`));
158
+ console.log(chalk.gray('Note: Restart the daemon to apply changes: mcps restart'));
112
159
  }
113
160
  catch (error) {
114
161
  console.error(chalk.red(`Error updating server: ${error.message}`));
115
162
  }
116
- });
163
+ };
164
+ // ===== Top-level commands (new, simplified) =====
165
+ // List command (already exists, keeping as-is)
166
+ program.command('list')
167
+ .alias('ls')
168
+ .description('List all configured servers')
169
+ .action(listServersAction);
170
+ // Add server command
171
+ program.command('add <name>')
172
+ .description('Add a new MCP server')
173
+ .option('--type <type>', 'Server type (stdio, sse, or http)', 'stdio')
174
+ .option('--command <command>', 'Command to execute (for stdio)')
175
+ .option('--args [args...]', 'Arguments for the command', [])
176
+ .option('--url <url>', 'URL for SSE/HTTP connection')
177
+ .option('--env <env...>', 'Environment variables (KEY=VALUE)', [])
178
+ .action(addServerAction);
179
+ // Remove server command
180
+ program.command('remove <name>')
181
+ .alias('rm')
182
+ .description('Remove a server')
183
+ .action(removeServerAction);
184
+ // Update server command
185
+ program.command('update [name]')
186
+ .description('Update a server configuration or refresh all servers')
187
+ .option('--command <command>', 'New command')
188
+ .option('--args [args...]', 'New arguments for the command')
189
+ .option('--url <url>', 'New URL')
190
+ .action(updateServerAction);
191
+ // ===== Legacy server subcommands (for backward compatibility) =====
192
+ const serverCmd = program.command('server')
193
+ .description('Manage MCP servers (legacy, use top-level commands)');
194
+ serverCmd.command('list')
195
+ .alias('ls')
196
+ .description('List all configured servers')
197
+ .action(listServersAction);
198
+ serverCmd.command('add <name>')
199
+ .description('Add a new MCP server')
200
+ .option('--type <type>', 'Server type (stdio, sse, or http)', 'stdio')
201
+ .option('--command <command>', 'Command to execute (for stdio)')
202
+ .option('--args [args...]', 'Arguments for the command', [])
203
+ .option('--url <url>', 'URL for SSE/HTTP connection')
204
+ .option('--env <env...>', 'Environment variables (KEY=VALUE)', [])
205
+ .action(addServerAction);
206
+ serverCmd.command('remove <name>')
207
+ .alias('rm')
208
+ .description('Remove a server')
209
+ .action(removeServerAction);
210
+ serverCmd.command('update [name]')
211
+ .description('Update a server configuration or refresh all servers')
212
+ .option('--command <command>', 'New command')
213
+ .option('--args [args...]', 'New arguments for the command')
214
+ .option('--url <url>', 'New URL')
215
+ .action(updateServerAction);
117
216
  };
@@ -1,2 +1,3 @@
1
- export const DAEMON_PORT = 4100;
1
+ // Support custom port via environment variable
2
+ export const DAEMON_PORT = parseInt(process.env.MCPS_PORT || '4100');
2
3
  export const DAEMON_BASE_URL = `http://localhost:${DAEMON_PORT}`;
package/dist/core/pool.js CHANGED
@@ -56,10 +56,11 @@ export class ConnectionPool {
56
56
  const servers = configManager.listServers();
57
57
  this.initializing = true;
58
58
  this.initialized = false;
59
+ const verbose = process.env.MCPS_VERBOSE === 'true';
59
60
  // 过滤掉 disabled 的服务器
60
61
  const enabledServers = servers.filter(server => {
61
62
  const disabled = server.disabled === true;
62
- if (disabled) {
63
+ if (verbose && disabled) {
63
64
  console.log(`[Daemon] Skipping disabled server: ${server.name}`);
64
65
  }
65
66
  return !disabled;
@@ -70,20 +71,38 @@ export class ConnectionPool {
70
71
  this.initialized = true;
71
72
  return;
72
73
  }
73
- console.log(`[Daemon] Initializing ${enabledServers.length} connection(s)...`);
74
+ console.log(`[Daemon] Connecting to ${enabledServers.length} server(s)...`);
75
+ const results = [];
74
76
  for (const server of enabledServers) {
77
+ if (verbose) {
78
+ process.stdout.write(`[Daemon] - ${server.name}... `);
79
+ }
75
80
  try {
76
- console.log(`[Daemon] Connecting to server: ${server.name}...`);
77
81
  await this.getClient(server.name, { timeoutMs: 8000 });
78
- console.log(`[Daemon] Connected to ${server.name}`);
82
+ results.push({ name: server.name, success: true });
83
+ if (verbose) {
84
+ console.log('✓');
85
+ }
79
86
  }
80
87
  catch (error) {
81
- console.error(`[Daemon] Failed to connect to ${server.name}:`, error.message);
88
+ results.push({ name: server.name, success: false, error: error.message });
89
+ if (verbose) {
90
+ console.log('✗');
91
+ console.error(`[Daemon] Error: ${error.message}`);
92
+ }
82
93
  }
83
94
  }
95
+ // Print summary
96
+ const successCount = results.filter(r => r.success).length;
97
+ const failed = results.filter(r => !r.success);
98
+ console.log(`[Daemon] Connected: ${successCount}/${enabledServers.length}`);
99
+ if (!verbose && failed.length > 0) {
100
+ failed.forEach(f => {
101
+ console.error(`[Daemon] ✗ ${f.name}: ${f.error}`);
102
+ });
103
+ }
84
104
  this.initializing = false;
85
105
  this.initialized = true;
86
- console.log('[Daemon] Initialization complete.');
87
106
  }
88
107
  getInitStatus() {
89
108
  return { initializing: this.initializing, initialized: this.initialized };
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maplezzk/mcps",
3
- "version": "1.0.14",
3
+ "version": "1.0.24",
4
4
  "description": "A CLI to manage and use MCP servers",
5
5
  "publishConfig": {
6
6
  "access": "public"