@baitong-dev/execute-command-mcp 0.0.1

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.
@@ -0,0 +1,660 @@
1
+ #!/usr/bin/env bun
2
+ "use strict";
3
+ /**
4
+ * Execute Command MCP Server v1.0
5
+ * Provides direct command execution tools that bypass Claude Code's
6
+ * slow haiku-based pre-flight checks.
7
+ *
8
+ * Features:
9
+ * - execute_command: Single command execution
10
+ * - execute_command_sequence: Sequential commands in a single shell session (stateful)
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ var __importDefault = (this && this.__importDefault) || function (mod) {
46
+ return (mod && mod.__esModule) ? mod : { "default": mod };
47
+ };
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
50
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
51
+ const child_process_1 = require("child_process");
52
+ const fs = __importStar(require("fs"));
53
+ const path_1 = __importDefault(require("path"));
54
+ const os_1 = __importDefault(require("os"));
55
+ const zod_1 = __importDefault(require("zod"));
56
+ const HOME_APP_DIR = '.baitong-desktop';
57
+ function getBinaryName(name) {
58
+ if (process.platform == 'win32') {
59
+ return `${name}.exe`;
60
+ }
61
+ return name;
62
+ }
63
+ function getBinaryPath(name) {
64
+ if (!name) {
65
+ return path_1.default.join(os_1.default.homedir(), HOME_APP_DIR, 'bin');
66
+ }
67
+ const binaryName = getBinaryName(name);
68
+ const binariesDir = path_1.default.join(os_1.default.homedir(), HOME_APP_DIR, 'bin');
69
+ const binariesDirExists = fs.existsSync(binariesDir);
70
+ return binariesDirExists ? path_1.default.join(binariesDir, binaryName) : binaryName;
71
+ }
72
+ function isBinaryExists(name) {
73
+ const cmd = getBinaryPath(name);
74
+ return fs.existsSync(cmd);
75
+ }
76
+ function handleCommand(fullCommand) {
77
+ const args = fullCommand.split(' ').map(arg => arg.trim());
78
+ const command = args.shift();
79
+ if (!command) {
80
+ throw new Error('No command specified');
81
+ }
82
+ let cmd = command;
83
+ if (command === 'bun' || command === 'npx' || command === 'node') {
84
+ if (isBinaryExists('bun')) {
85
+ // Fall back to bundled bun
86
+ cmd = getBinaryPath('bun');
87
+ // Transform args for bun x format
88
+ if (args && args.length > 0) {
89
+ if (command === 'npx') {
90
+ // Transform args for npx format npx xxx -> bun x -y xxx
91
+ if (!args.includes('-y')) {
92
+ args.unshift('-y');
93
+ }
94
+ if (!args.includes('x')) {
95
+ args.unshift('x');
96
+ }
97
+ }
98
+ }
99
+ }
100
+ else {
101
+ // Neither npx nor bun available
102
+ throw new Error('npx not found in PATH and bundled bun is not available. This may indicate an installation issue.\n' +
103
+ 'Please either:\n' +
104
+ '1. Install Node.js (which includes npx) from https://nodejs.org\n' +
105
+ '2. Run the MCP dependencies installer from Settings\n' +
106
+ '3. Restart the application if you recently installed Node.js');
107
+ }
108
+ }
109
+ else if (command === 'uvx' || command === 'uv') {
110
+ if (isBinaryExists(command)) {
111
+ // Fall back to bundled version
112
+ cmd = getBinaryPath(command);
113
+ }
114
+ else {
115
+ // Neither system nor bundled available
116
+ throw new Error(`${command} not found in PATH and bundled version is not available. This may indicate an installation issue.\n` +
117
+ 'Please either:\n' +
118
+ '1. Install uv from https://github.com/astral-sh/uv\n' +
119
+ '2. Run the MCP dependencies installer from Settings\n' +
120
+ `3. Restart the application if you recently installed ${command}`);
121
+ }
122
+ }
123
+ return {
124
+ command: cmd,
125
+ args
126
+ };
127
+ }
128
+ const server = new mcp_js_1.McpServer({
129
+ name: 'execute-command-mcp',
130
+ version: '1.0.0'
131
+ }, {
132
+ capabilities: {
133
+ logging: {}
134
+ }
135
+ });
136
+ // =============================================================================
137
+ // Constants
138
+ // =============================================================================
139
+ // Default working directory: env var > process.cwd() > HOME > /tmp
140
+ const DEFAULT_CWD = process.env.FAST_BASH_DEFAULT_CWD || process.cwd() || process.env.HOME || '/tmp';
141
+ // Grace period before SIGKILL (ms)
142
+ const GRACEFUL_TIMEOUT_MS = 5000;
143
+ // Reserved characters for truncation ellipsis
144
+ const TRUNCATION_ELLIPSIS_RESERVE = 50;
145
+ // Exit code for timeout (shell convention)
146
+ const EXIT_CODE_TIMEOUT = 124;
147
+ // Sudo rejection message
148
+ const SUDO_REJECTION_MESSAGE = '[REJECTED] sudo commands cannot be executed. Please STOP and ask human user to run it for you!\n';
149
+ /**
150
+ * Check if a command contains sudo
151
+ * Matches: sudo at start, after semicolon, after &&, after ||, after |, after $(, after backtick
152
+ */
153
+ function containsSudo(command) {
154
+ // Match sudo as a standalone command (not part of another word like "pseudocode")
155
+ return /(?:^|[;&|`$()]\s*)sudo(?:\s|$)/m.test(command);
156
+ }
157
+ // Default timeout for sequence commands (5 minutes)
158
+ const DEFAULT_SEQUENCE_TIMEOUT = 300000;
159
+ // =============================================================================
160
+ // Batch 1.1: DRY Output Formatting
161
+ // =============================================================================
162
+ /**
163
+ * Format command line with $ prefix
164
+ */
165
+ function formatCommandLine(command) {
166
+ return `$ ${command}`;
167
+ }
168
+ /**
169
+ * Format timing
170
+ */
171
+ function formatTiming(durationMs) {
172
+ return `[${durationMs}ms]`;
173
+ }
174
+ /**
175
+ * Middle-truncate output to preserve beginning and end
176
+ */
177
+ function middleTruncate(text, maxLength) {
178
+ if (text.length <= maxLength)
179
+ return text;
180
+ const halfLength = Math.floor((maxLength - TRUNCATION_ELLIPSIS_RESERVE) / 2);
181
+ const start = text.slice(0, halfLength);
182
+ const end = text.slice(-halfLength);
183
+ const truncatedBytes = text.length - maxLength;
184
+ return `${start}\n\n... [truncated ${truncatedBytes} characters] ...\n\n${end}`;
185
+ }
186
+ /**
187
+ * Classify error type from exit code and error conditions
188
+ */
189
+ function classifyErrorType(exitCode, killed, forceKilled, spawnError, cwdError) {
190
+ if (cwdError)
191
+ return 'cwd_not_found';
192
+ if (spawnError) {
193
+ if (spawnError.code === 'ENOENT')
194
+ return 'spawn_error';
195
+ return 'spawn_error';
196
+ }
197
+ if (killed)
198
+ return forceKilled ? 'killed' : 'timeout';
199
+ if (exitCode === 127)
200
+ return 'command_not_found';
201
+ if (exitCode === 126)
202
+ return 'permission_denied';
203
+ return undefined;
204
+ }
205
+ /**
206
+ * Format command result for output (Batch 1.2: includes errorType)
207
+ */
208
+ function formatResult(result, options = {}) {
209
+ const { description, timeout, command, showTiming = false } = options;
210
+ let output = '';
211
+ if (description)
212
+ output += `=== ${description} ===\n`;
213
+ if (command)
214
+ output += `${formatCommandLine(command)}\n`;
215
+ if (result.stdout)
216
+ output += result.stdout;
217
+ if (result.stderr)
218
+ output += `\n[stderr]: ${result.stderr}`;
219
+ if (result.killed) {
220
+ if (result.forceKilled) {
221
+ output += `\n[timeout after ${(timeout || 30000) + GRACEFUL_TIMEOUT_MS}ms, SIGKILL]`;
222
+ }
223
+ else {
224
+ output += `\n[timeout after ${timeout || 30000}ms, SIGTERM]`;
225
+ }
226
+ }
227
+ if (result.errorType && result.errorType !== 'timeout') {
228
+ output += `\n[errorType: ${result.errorType}]`;
229
+ }
230
+ output += `\n[exit code: ${result.exitCode}]`;
231
+ if (showTiming && result.durationMs !== undefined) {
232
+ output += `\n${formatTiming(result.durationMs)}`;
233
+ }
234
+ return output.trim() || '(no output)';
235
+ }
236
+ // =============================================================================
237
+ // Batch 5.1: File Output Helper
238
+ // =============================================================================
239
+ /**
240
+ * Write output to file (before truncation)
241
+ * Returns error message on failure, undefined on success
242
+ */
243
+ function writeOutputToFile(filePath, content) {
244
+ try {
245
+ fs.writeFileSync(filePath, content, 'utf8');
246
+ return undefined;
247
+ }
248
+ catch (err) {
249
+ return err.message;
250
+ }
251
+ }
252
+ /**
253
+ * Execute commands sequentially in a single shell session (stateful)
254
+ */
255
+ async function executeSequence(options) {
256
+ var _a;
257
+ const { commands, stopOnFailure = true, continueOnCodes = [0], cwd = DEFAULT_CWD, timeout = DEFAULT_SEQUENCE_TIMEOUT
258
+ // outputFile
259
+ } = options;
260
+ // Empty commands validation
261
+ if (commands.length === 0) {
262
+ return {
263
+ results: [],
264
+ totalDurationMs: 0,
265
+ succeeded: 0,
266
+ failed: 0,
267
+ executed: 0
268
+ };
269
+ }
270
+ // CWD Validation
271
+ if (!fs.existsSync(cwd)) {
272
+ return {
273
+ results: [
274
+ {
275
+ index: 0,
276
+ command: ((_a = commands[0]) === null || _a === void 0 ? void 0 : _a.command) || '',
277
+ stdout: '',
278
+ stderr: `[error]: Working directory does not exist: ${cwd}`,
279
+ exitCode: 1,
280
+ durationMs: 0,
281
+ stopped: true,
282
+ stopReason: 'cwd_not_found'
283
+ }
284
+ ],
285
+ totalDurationMs: 0,
286
+ succeeded: 0,
287
+ failed: 1,
288
+ executed: 0
289
+ };
290
+ }
291
+ let newCwd = cwd;
292
+ let newTimeout = timeout;
293
+ const results = [];
294
+ let succeeded = 0;
295
+ let failed = 0;
296
+ let stoppedAt = undefined;
297
+ let totalDurationMs = 0;
298
+ let index = 0;
299
+ try {
300
+ for (let i = 0; i < commands.length; i++) {
301
+ index = i;
302
+ const command = commands[i].command;
303
+ if (command.startsWith('cd ')) {
304
+ // 每次执行cd命令,不执行exec了
305
+ newCwd = path_1.default.resolve(newCwd, command.replace('cd ', ''));
306
+ succeeded++;
307
+ results.push({
308
+ index,
309
+ command,
310
+ stdout: '',
311
+ stderr: '',
312
+ exitCode: 0,
313
+ durationMs: 0
314
+ });
315
+ continue;
316
+ }
317
+ // 每次执行非cd命令
318
+ const result = await executeCommand(Object.assign(Object.assign({}, options), { cwd: newCwd, timeout: newTimeout, command }));
319
+ const { stdout, stderr, exitCode, killed, durationMs } = result;
320
+ const sequenceResult = {
321
+ index,
322
+ command,
323
+ stdout,
324
+ stderr,
325
+ exitCode,
326
+ durationMs,
327
+ stopped: false
328
+ };
329
+ totalDurationMs += durationMs;
330
+ newTimeout = timeout - durationMs;
331
+ if (sequenceResult.exitCode != null && continueOnCodes.includes(sequenceResult.exitCode)) {
332
+ succeeded++;
333
+ }
334
+ else {
335
+ failed++;
336
+ if (stopOnFailure && stoppedAt === undefined) {
337
+ stoppedAt = i;
338
+ }
339
+ }
340
+ if (stoppedAt === i) {
341
+ sequenceResult.stopped = true;
342
+ sequenceResult.stopReason = killed
343
+ ? 'timeout'
344
+ : `exit code ${exitCode} not in continue_on_codes`;
345
+ }
346
+ results.push(sequenceResult);
347
+ if (stoppedAt !== undefined)
348
+ break;
349
+ }
350
+ }
351
+ catch (_) {
352
+ //
353
+ }
354
+ return {
355
+ results,
356
+ totalDurationMs,
357
+ succeeded,
358
+ failed,
359
+ executed: results.length,
360
+ stoppedAt: 0
361
+ };
362
+ }
363
+ // =============================================================================
364
+ // Tool Definitions
365
+ // =============================================================================
366
+ const bashInputSchema = zod_1.default.object({
367
+ command: zod_1.default.string().min(1).describe('The command to execute'),
368
+ cwd: zod_1.default.string().describe('Working directory'),
369
+ timeout: zod_1.default
370
+ .number()
371
+ .min(1)
372
+ .max(600000)
373
+ .optional()
374
+ .describe('Timeout in milliseconds (optional, default 30000, max 600000)'),
375
+ description: zod_1.default
376
+ .string()
377
+ .optional()
378
+ .describe('Short description of what this command does (for logging)'),
379
+ env: zod_1.default
380
+ .record(zod_1.default.string(), zod_1.default.string())
381
+ .optional()
382
+ .describe('Additional environment variables to set'),
383
+ stdin: zod_1.default.string().optional().describe("Input to pipe to the command's stdin"),
384
+ maxOutput: zod_1.default
385
+ .number()
386
+ .min(1)
387
+ .max(1000000)
388
+ .optional()
389
+ .describe('Maximum output length before middle-truncation (default 30000)'),
390
+ outputFile: zod_1.default.string().optional().describe('File path to save full stdout (before truncation)'),
391
+ stderrFile: zod_1.default.string().optional().describe('File path to save full stderr (before truncation)'),
392
+ loginShell: zod_1.default
393
+ .boolean()
394
+ .optional()
395
+ .describe('Run as login shell (-l flag) to source .profile/.bash_profile (default: false)')
396
+ });
397
+ /**
398
+ * Execute a command and return result
399
+ */
400
+ function executeCommand(options) {
401
+ const { command, cwd = DEFAULT_CWD, timeout = 30000, env, stdin, maxOutput = 30000, outputFile, stderrFile
402
+ // loginShell = false
403
+ } = options;
404
+ return new Promise(resolve => {
405
+ var _a, _b, _c, _d;
406
+ const startTime = Date.now();
407
+ // Batch 1.3: CWD Validation
408
+ if (!fs.existsSync(cwd)) {
409
+ resolve({
410
+ stdout: '',
411
+ stderr: `[error]: Working directory does not exist: ${cwd}`,
412
+ exitCode: 1,
413
+ killed: false,
414
+ forceKilled: false,
415
+ errorType: 'cwd_not_found',
416
+ durationMs: Date.now() - startTime
417
+ });
418
+ return;
419
+ }
420
+ const mergedEnv = Object.assign(Object.assign({}, process.env), env);
421
+ let proc;
422
+ try {
423
+ // Use -lc for login shell (sources .profile), -c for regular
424
+ // const shellArgs = loginShell ? ['-lc', command] : ['-c', command]
425
+ const handledCommand = handleCommand(command);
426
+ console.log(handledCommand);
427
+ proc = (0, child_process_1.spawn)(handledCommand.command, handledCommand.args, {
428
+ cwd,
429
+ env: mergedEnv,
430
+ stdio: ['pipe', 'pipe', 'pipe']
431
+ });
432
+ }
433
+ catch (err) {
434
+ resolve({
435
+ stdout: '',
436
+ stderr: `[error]: Failed to spawn process: ${err.message}`,
437
+ exitCode: 1,
438
+ killed: false,
439
+ forceKilled: false,
440
+ errorType: 'spawn_error',
441
+ durationMs: Date.now() - startTime
442
+ });
443
+ return;
444
+ }
445
+ let stdout = '';
446
+ let stderr = '';
447
+ let killed = false;
448
+ let forceKilled = false;
449
+ let completed = false;
450
+ let graceTimer;
451
+ // Batch 5.2: Graceful Timeout - SIGTERM first, then SIGKILL after grace period
452
+ const timer = setTimeout(() => {
453
+ killed = true;
454
+ proc.kill('SIGTERM');
455
+ // Grace period before SIGKILL
456
+ graceTimer = setTimeout(() => {
457
+ if (!completed) {
458
+ proc.kill('SIGKILL');
459
+ forceKilled = true;
460
+ }
461
+ }, GRACEFUL_TIMEOUT_MS);
462
+ }, timeout);
463
+ if (stdin) {
464
+ (_a = proc.stdin) === null || _a === void 0 ? void 0 : _a.write(stdin);
465
+ (_b = proc.stdin) === null || _b === void 0 ? void 0 : _b.end();
466
+ }
467
+ (_c = proc.stdout) === null || _c === void 0 ? void 0 : _c.on('data', data => {
468
+ stdout += data.toString();
469
+ });
470
+ (_d = proc.stderr) === null || _d === void 0 ? void 0 : _d.on('data', data => {
471
+ stderr += data.toString();
472
+ });
473
+ proc.on('close', code => {
474
+ completed = true;
475
+ clearTimeout(timer);
476
+ if (graceTimer)
477
+ clearTimeout(graceTimer);
478
+ const durationMs = Date.now() - startTime;
479
+ // Batch 5.1: Write to files BEFORE truncation
480
+ if (outputFile) {
481
+ writeOutputToFile(outputFile, stdout);
482
+ }
483
+ if (stderrFile) {
484
+ writeOutputToFile(stderrFile, stderr);
485
+ }
486
+ // Apply truncation for response
487
+ let truncatedStdout = stdout;
488
+ let truncatedStderr = stderr;
489
+ if (truncatedStdout.length > maxOutput) {
490
+ truncatedStdout = middleTruncate(truncatedStdout, maxOutput);
491
+ }
492
+ if (truncatedStderr.length > maxOutput) {
493
+ truncatedStderr = middleTruncate(truncatedStderr, maxOutput);
494
+ }
495
+ const errorType = classifyErrorType(code, killed, forceKilled);
496
+ resolve({
497
+ stdout: truncatedStdout,
498
+ stderr: truncatedStderr,
499
+ exitCode: killed ? EXIT_CODE_TIMEOUT : code,
500
+ killed,
501
+ forceKilled,
502
+ errorType,
503
+ durationMs
504
+ });
505
+ });
506
+ proc.on('error', err => {
507
+ completed = true;
508
+ clearTimeout(timer);
509
+ if (graceTimer)
510
+ clearTimeout(graceTimer);
511
+ const durationMs = Date.now() - startTime;
512
+ resolve({
513
+ stdout: '',
514
+ stderr: err.message,
515
+ exitCode: 1,
516
+ killed: false,
517
+ forceKilled: false,
518
+ errorType: classifyErrorType(1, false, false, err),
519
+ durationMs
520
+ });
521
+ });
522
+ });
523
+ }
524
+ server.registerTool('execute_command', {
525
+ description: 'Execute a command directly. Returns stdout, stderr, and exit code.',
526
+ inputSchema: bashInputSchema
527
+ }, async (args) => {
528
+ const command = args.command;
529
+ if (command.includes('&&')) {
530
+ return executeSequenceCallback({
531
+ commands: command.split('&&').map(cmd => ({ command: cmd.trim() })),
532
+ cwd: args.cwd,
533
+ timeout: args.timeout,
534
+ env: args.env,
535
+ maxOutput: args.maxOutput,
536
+ outputFile: args.outputFile
537
+ });
538
+ }
539
+ // Reject sudo commands
540
+ if (containsSudo(command)) {
541
+ return {
542
+ isError: true,
543
+ content: [{ type: 'text', text: `${SUDO_REJECTION_MESSAGE}$ ${command}` }]
544
+ };
545
+ }
546
+ const result = await executeCommand(args);
547
+ // Use formatFullOutput for DRY consistency
548
+ let output = formatResult(result, {
549
+ description: args.description,
550
+ timeout: args.timeout,
551
+ command,
552
+ showTiming: true
553
+ });
554
+ // Add file save notices
555
+ if (args.outputFile) {
556
+ output += `\n[stdout saved to: ${args.outputFile}]`;
557
+ }
558
+ if (args.stderrFile) {
559
+ output += `\n[stderr saved to: ${args.stderrFile}]`;
560
+ }
561
+ return {
562
+ isError: result.exitCode !== 0,
563
+ content: [{ type: 'text', text: output }]
564
+ };
565
+ });
566
+ const bashSequenceInputSchema = zod_1.default.object({
567
+ commands: zod_1.default
568
+ .array(zod_1.default.object({
569
+ command: zod_1.default.string().describe('The command to execute'),
570
+ description: zod_1.default.string().optional().describe('Short description of what this command does')
571
+ }))
572
+ .describe('Array of commands to execute sequentially in one shell'),
573
+ cwd: zod_1.default.string().describe('Initial working directory'),
574
+ stopOnFailure: zod_1.default
575
+ .boolean()
576
+ .optional()
577
+ .describe('Stop execution if a command fails (default: true)'),
578
+ continueOnCodes: zod_1.default
579
+ .array(zod_1.default.number())
580
+ .optional()
581
+ .describe('Exit codes that are considered success (default: [0])'),
582
+ timeout: zod_1.default
583
+ .number()
584
+ .optional()
585
+ .describe('Overall timeout for all commands in ms (default: 300000)'),
586
+ maxOutput: zod_1.default
587
+ .number()
588
+ .min(1)
589
+ .max(1000000)
590
+ .optional()
591
+ .describe('Maximum output length before middle-truncation (default 30000)'),
592
+ env: zod_1.default
593
+ .record(zod_1.default.string(), zod_1.default.string())
594
+ .optional()
595
+ .describe('Additional environment variables to set'),
596
+ outputFile: zod_1.default.string().optional().describe('File path to save full output (before truncation)')
597
+ });
598
+ const executeSequenceCallback = async (args) => {
599
+ var _a;
600
+ const commands = args.commands;
601
+ // Reject any command containing sudo
602
+ for (let i = 0; i < commands.length; i++) {
603
+ if (containsSudo(commands[i].command)) {
604
+ return {
605
+ isError: true,
606
+ content: [
607
+ {
608
+ type: 'text',
609
+ text: `${SUDO_REJECTION_MESSAGE}[${i + 1}] $ ${commands[i].command}`
610
+ }
611
+ ]
612
+ };
613
+ }
614
+ }
615
+ const seqResult = await executeSequence(args);
616
+ // Format output per Batch 4.4 spec
617
+ const outputParts = seqResult.results.map(r => {
618
+ const header = r.description
619
+ ? `[${r.index}] === ${r.description} ===`
620
+ : `[${r.index}] === Command ${r.index} ===`;
621
+ const cmdLine = formatCommandLine(r.command);
622
+ let body = '';
623
+ if (r.stdout)
624
+ body += r.stdout;
625
+ if (r.stderr)
626
+ body += `\n[stderr]: ${r.stderr}`;
627
+ body += `\n[exit code: ${r.exitCode}]`;
628
+ if (r.stopped && r.stopReason) {
629
+ body += `\n[stopped - ${r.stopReason}]`;
630
+ }
631
+ const timing = formatTiming(r.durationMs);
632
+ return `${header}\n${cmdLine}\n${body.trim()}\n${timing}`;
633
+ });
634
+ // Summary
635
+ let summary = `\n=== Summary ===`;
636
+ summary += `\nTotal: ${seqResult.totalDurationMs}ms | Executed: ${seqResult.executed}/${commands.length} | Succeeded: ${seqResult.succeeded} | Failed: ${seqResult.failed}`;
637
+ if (seqResult.stoppedAt !== undefined) {
638
+ const stoppedCmd = seqResult.results[seqResult.stoppedAt];
639
+ const stoppedDesc = (stoppedCmd === null || stoppedCmd === void 0 ? void 0 : stoppedCmd.description) || ((_a = stoppedCmd === null || stoppedCmd === void 0 ? void 0 : stoppedCmd.command) === null || _a === void 0 ? void 0 : _a.slice(0, 30)) || 'unknown';
640
+ summary += `\nStopped at: [${seqResult.stoppedAt + 1}] ${stoppedDesc}`;
641
+ }
642
+ let response = outputParts.join('\n\n') + summary;
643
+ if (args.outputFile) {
644
+ response += `\n[output saved to: ${args.outputFile}]`;
645
+ }
646
+ return {
647
+ isError: seqResult.failed > 0,
648
+ content: [{ type: 'text', text: response }]
649
+ };
650
+ };
651
+ server.registerTool('execute_command_sequence', {
652
+ description: 'Execute commands sequentially in a SINGLE shell session (stateful). Unlike parallel, cd/export commands persist between steps. Use for workflows where commands depend on each other. Note: stderr is combined for the whole script and assigned to the last executed command.',
653
+ inputSchema: bashSequenceInputSchema
654
+ }, executeSequenceCallback);
655
+ // =============================================================================
656
+ // Start Server
657
+ // =============================================================================
658
+ const transport = new stdio_js_1.StdioServerTransport();
659
+ server.connect(transport);
660
+ console.error('Execute Command MCP server v1.0 running');
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@baitong-dev/execute-command-mcp",
3
+ "version": "0.0.1",
4
+ "main": "ExecuteCommandMcpServer.js",
5
+ "bin": {
6
+ "@baitong-dev/skills-mcp": "./ExecuteCommandMcpServer.js"
7
+ },
8
+ "files": [
9
+ "ExecuteCommandMcpServer.js",
10
+ "README.md"
11
+ ],
12
+ "scripts": {},
13
+ "keywords": [
14
+ "mcp",
15
+ "skills"
16
+ ],
17
+ "description": "execute-command-mcp",
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.25.1",
20
+ "commander": "^14.0.3",
21
+ "zod": "^4.3.4"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "registry": "https://registry.npmjs.org"
26
+ }
27
+ }