@maplezzk/mcps 1.0.18 → 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.
@@ -24,8 +24,8 @@ function isPortInUse(port) {
24
24
  });
25
25
  }
26
26
  // Action functions for daemon commands
27
- const startAction = async (options, parentCmd) => {
28
- const port = parseInt(options.port || DAEMON_PORT);
27
+ const startAction = async (options) => {
28
+ const port = parseInt(options.port || process.env.MCPS_PORT || DAEMON_PORT);
29
29
  // Check if port is in use (more reliable than HTTP check)
30
30
  const portInUse = await isPortInUse(port);
31
31
  if (portInUse) {
@@ -51,7 +51,7 @@ const startAction = async (options, parentCmd) => {
51
51
  return;
52
52
  }
53
53
  // Otherwise, spawn a detached process
54
- console.log(chalk.gray('Starting daemon in background...'));
54
+ console.log(chalk.cyan('Starting daemon in background...'));
55
55
  let childFailed = false;
56
56
  const subprocess = spawn(process.execPath, [process.argv[1], 'daemon', 'start'], {
57
57
  detached: true,
@@ -59,23 +59,27 @@ const startAction = async (options, parentCmd) => {
59
59
  stdio: ['ignore', 'pipe', 'pipe'],
60
60
  env: {
61
61
  ...process.env,
62
- MCPS_DAEMON_DETACHED: 'true'
62
+ MCPS_DAEMON_DETACHED: 'true',
63
+ MCPS_VERBOSE: options.verbose ? 'true' : 'false'
63
64
  }
64
65
  });
65
66
  // Stream logs to current console while waiting for ready
66
67
  if (subprocess.stdout) {
67
68
  subprocess.stdout.on('data', (data) => {
68
- process.stdout.write(chalk.gray(`[Daemon] ${data}`));
69
+ process.stdout.write(`${data}`);
69
70
  });
70
71
  }
71
72
  if (subprocess.stderr) {
72
73
  subprocess.stderr.on('data', (data) => {
73
74
  const msg = data.toString();
74
- process.stderr.write(chalk.red(`[Daemon] ${msg}`));
75
75
  // Detect port conflict in child process
76
76
  if (msg.includes('Port') && msg.includes('is already in use')) {
77
77
  childFailed = true;
78
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
+ }
79
83
  });
80
84
  }
81
85
  subprocess.unref();
@@ -83,7 +87,7 @@ const startAction = async (options, parentCmd) => {
83
87
  // We can poll status for a second
84
88
  const start = Date.now();
85
89
  // Increased timeout to allow for connection initialization
86
- while (Date.now() - start < 10000) {
90
+ while (Date.now() - start < 30000) {
87
91
  // If child reported port conflict, check if daemon is actually running
88
92
  if (childFailed) {
89
93
  const stillRunning = await isPortInUse(port);
@@ -110,9 +114,9 @@ const startAction = async (options, parentCmd) => {
110
114
  console.log(chalk.yellow('Daemon started (async check timeout, but likely running).'));
111
115
  process.exit(0);
112
116
  };
113
- const stopAction = async (parentCmd) => {
117
+ const stopAction = async (options) => {
114
118
  try {
115
- const port = parseInt(parentCmd.opts().port || DAEMON_PORT);
119
+ const port = parseInt(options?.port || process.env.MCPS_PORT || DAEMON_PORT);
116
120
  await fetch(`http://localhost:${port}/stop`, { method: 'POST' });
117
121
  console.log(chalk.green('Daemon stopped successfully.'));
118
122
  }
@@ -120,31 +124,70 @@ const stopAction = async (parentCmd) => {
120
124
  console.error(chalk.red('Failed to stop daemon. Is it running?'));
121
125
  }
122
126
  };
123
- const statusAction = async (parentCmd) => {
127
+ const statusAction = async (options) => {
124
128
  try {
125
- const port = parseInt(parentCmd.opts().port || DAEMON_PORT);
129
+ const port = parseInt(options?.port || process.env.MCPS_PORT || DAEMON_PORT);
126
130
  const res = await fetch(`http://localhost:${port}/status`);
127
131
  const data = await res.json();
132
+ console.log('');
128
133
  console.log(chalk.green(`Daemon is running (v${data.version})`));
129
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;
144
+ }
145
+ }
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
130
170
  console.log(chalk.bold('\nActive Connections:'));
131
- data.connections.forEach((conn) => {
132
- const count = conn.toolsCount !== null ? `(${conn.toolsCount} tools)` : (data.initializing ? '(initializing)' : '(error listing tools)');
133
- const status = conn.status === 'error' ? chalk.red('[Error]') : '';
134
- console.log(chalk.cyan(`- ${conn.name} ${chalk.gray(count)} ${status}`));
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)}`);
135
176
  });
177
+ console.log(chalk.cyan(`Total: ${data.connections.length} connection(s)`));
136
178
  }
137
179
  else {
138
- console.log(chalk.gray('No active connections.'));
180
+ console.log(chalk.yellow('\nNo active connections.'));
139
181
  }
182
+ console.log('');
140
183
  }
141
184
  catch (e) {
142
185
  console.error(chalk.red('Daemon is not running.'));
143
186
  }
144
187
  };
145
- const restartAction = async (serverName, parentCmd) => {
188
+ const restartAction = async (serverName, options) => {
146
189
  try {
147
- const port = parseInt(parentCmd.opts().port || DAEMON_PORT);
190
+ const port = parseInt(options?.port || process.env.MCPS_PORT || DAEMON_PORT);
148
191
  const res = await fetch(`http://localhost:${port}/restart`, {
149
192
  method: 'POST',
150
193
  body: JSON.stringify({ server: serverName })
@@ -161,19 +204,20 @@ export const registerDaemonCommand = (program) => {
161
204
  program.command('start')
162
205
  .description('Start the daemon')
163
206
  .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT))
164
- .action((options) => startAction(options, program));
207
+ .option('-v, --verbose', 'Show detailed logs')
208
+ .action((options) => startAction(options));
165
209
  program.command('stop')
166
210
  .description('Stop the daemon')
167
211
  .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT))
168
- .action(() => stopAction(program));
212
+ .action((options) => stopAction(options));
169
213
  program.command('status')
170
214
  .description('Check daemon status')
171
215
  .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT))
172
- .action(() => statusAction(program));
216
+ .action((options) => statusAction(options));
173
217
  program.command('restart [server]')
174
218
  .description('Restart the daemon or a specific server connection')
175
219
  .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT))
176
- .action((serverName) => restartAction(serverName, program));
220
+ .action((serverName, options) => restartAction(serverName, options));
177
221
  // ===== Legacy daemon subcommands (for backward compatibility) =====
178
222
  const daemonCmd = program.command('daemon')
179
223
  .description('Manage the mcps daemon (legacy, use top-level commands)')
@@ -181,16 +225,16 @@ export const registerDaemonCommand = (program) => {
181
225
  .option('-p, --port <number>', 'Daemon port', String(DAEMON_PORT));
182
226
  daemonCmd.command('start', { isDefault: true, hidden: true })
183
227
  .description('Start the daemon (default)')
184
- .action((options) => startAction(options, daemonCmd));
228
+ .action((options) => startAction(options));
185
229
  daemonCmd.command('stop')
186
230
  .description('Stop the running daemon')
187
- .action(() => stopAction(daemonCmd));
231
+ .action((options) => stopAction(options));
188
232
  daemonCmd.command('status')
189
233
  .description('Check daemon status')
190
- .action(() => statusAction(daemonCmd));
234
+ .action((options) => statusAction(options));
191
235
  daemonCmd.command('restart [server]')
192
236
  .description('Restart the daemon or a specific server connection')
193
- .action((serverName) => restartAction(serverName, daemonCmd));
237
+ .action((serverName, options) => restartAction(serverName, options));
194
238
  };
195
239
  const startDaemon = (port) => {
196
240
  const server = http.createServer(async (req, res) => {
@@ -8,19 +8,62 @@ export const registerServerCommands = (program) => {
8
8
  console.log(chalk.yellow('No servers configured.'));
9
9
  return;
10
10
  }
11
- console.log(chalk.bold('\nConfigured Servers:'));
12
- servers.forEach(s => {
13
- console.log(`- ${chalk.cyan(s.name)} [${chalk.magenta(s.type)}]`);
14
- if (s.type === 'stdio') {
15
- console.log(` Command: ${s.command} ${s.args.join(' ')}`);
16
- if (s.env)
17
- 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(' ')}`;
18
39
  }
19
40
  else {
20
- console.log(` URL: ${s.url}`);
41
+ command = server.url || '';
21
42
  }
22
- console.log('');
43
+ return {
44
+ name: server.name,
45
+ type: typeColor(server.type),
46
+ enabled: enabledMark,
47
+ command: command,
48
+ disabled
49
+ };
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}`);
23
63
  });
64
+ console.log('');
65
+ console.log(chalk.cyan(`Total: ${servers.length} server(s)`));
66
+ console.log('');
24
67
  };
25
68
  const addServerAction = (name, options) => {
26
69
  try {
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.18",
3
+ "version": "1.0.24",
4
4
  "description": "A CLI to manage and use MCP servers",
5
5
  "publishConfig": {
6
6
  "access": "public"