mcp-server-commands 0.8.0 → 0.8.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.
- package/build/exec-utils.js +71 -33
- package/build/tools.js +1 -1
- package/package.json +1 -1
package/build/exec-utils.js
CHANGED
|
@@ -4,50 +4,92 @@ import { performance } from "perf_hooks";
|
|
|
4
4
|
import { is_verbose, verbose_log } from "./logging.js";
|
|
5
5
|
import { resultFor } from "./messages.js";
|
|
6
6
|
import { errorResult } from "./messages.js";
|
|
7
|
+
/**
|
|
8
|
+
* Helper class that provides typed getters for the keys accepted by
|
|
9
|
+
* {@link runProcess}. It wraps a {@link RunProcessArgs} object and casts the
|
|
10
|
+
* values to the expected runtime types.
|
|
11
|
+
*/
|
|
12
|
+
export class RunProcessArgsHelper {
|
|
13
|
+
raw;
|
|
14
|
+
constructor(raw) {
|
|
15
|
+
this.raw = raw ?? {};
|
|
16
|
+
}
|
|
17
|
+
/** Working directory – string if supplied, otherwise undefined */
|
|
18
|
+
get cwd() {
|
|
19
|
+
const v = this.raw.cwd;
|
|
20
|
+
return v == null ? undefined : String(v);
|
|
21
|
+
}
|
|
22
|
+
/** Text to write to STDIN – string if supplied, otherwise undefined */
|
|
23
|
+
get stdin_text() {
|
|
24
|
+
const v = this.raw.stdin_text;
|
|
25
|
+
return v == null ? undefined : String(v);
|
|
26
|
+
}
|
|
27
|
+
/** Shell command line – string if supplied, otherwise undefined */
|
|
28
|
+
get commandLine() {
|
|
29
|
+
const v = this.raw.command_line;
|
|
30
|
+
return v == null ? undefined : String(v);
|
|
31
|
+
}
|
|
32
|
+
/** Executable argv – array of strings if supplied, otherwise undefined */
|
|
33
|
+
get argv() {
|
|
34
|
+
const v = this.raw.argv;
|
|
35
|
+
if (!Array.isArray(v))
|
|
36
|
+
return undefined;
|
|
37
|
+
return v.map((item) => String(item));
|
|
38
|
+
}
|
|
39
|
+
/** Timeout in milliseconds – number if supplied, otherwise undefined */
|
|
40
|
+
/** Timeout in milliseconds – always a number (default 30_000) */
|
|
41
|
+
get timeoutMs() {
|
|
42
|
+
const v = this.raw.timeout_ms;
|
|
43
|
+
const n = Number(v);
|
|
44
|
+
return Number.isNaN(n) ? 30_000 : n;
|
|
45
|
+
}
|
|
46
|
+
/** True if a shell command line is provided */
|
|
47
|
+
get isShellMode() {
|
|
48
|
+
return Boolean(this.commandLine);
|
|
49
|
+
}
|
|
50
|
+
/** True if an argv array with at least one element is provided */
|
|
51
|
+
get isExecutableMode() {
|
|
52
|
+
return Array.isArray(this.raw.argv) && (this.argv?.length ?? 0) > 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
7
55
|
export function runProcess(runProcessArgs) {
|
|
8
56
|
const startTime = performance.now();
|
|
57
|
+
const args = new RunProcessArgsHelper(runProcessArgs);
|
|
58
|
+
if (args.isShellMode && args.isExecutableMode) {
|
|
59
|
+
return Promise.resolve(errorResult("Cannot pass both 'command_line' and 'argv'. Use one or the other."));
|
|
60
|
+
}
|
|
61
|
+
if (!args.isShellMode && !args.isExecutableMode) {
|
|
62
|
+
return Promise.resolve(errorResult("Either 'command_line' (string) or 'argv' (array) is required."));
|
|
63
|
+
}
|
|
9
64
|
const options = {
|
|
10
65
|
// spawn options: https://nodejs.org/api/child_process.html#child_processspawncommand-args-options
|
|
11
66
|
encoding: "utf8"
|
|
12
67
|
};
|
|
13
|
-
if (
|
|
14
|
-
options.cwd =
|
|
68
|
+
if (args.cwd) {
|
|
69
|
+
options.cwd = args.cwd;
|
|
15
70
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------
|
|
20
|
-
const isShellMode = Boolean(runProcessArgs?.command_line);
|
|
21
|
-
const isExecutableMode = Array.isArray(runProcessArgs?.argv) && (runProcessArgs?.argv).length > 0;
|
|
22
|
-
if (isShellMode && isExecutableMode) {
|
|
23
|
-
return Promise.resolve(errorResult("Cannot pass both 'command_line' and 'argv'. Use one or the other."));
|
|
24
|
-
}
|
|
25
|
-
if (!isShellMode && !isExecutableMode) {
|
|
26
|
-
return Promise.resolve(errorResult("Either 'command_line' (string) or 'argv' (array) is required."));
|
|
27
|
-
}
|
|
28
|
-
// Resolve the command/args based on the mode.
|
|
29
|
-
let execCommand = "";
|
|
30
|
-
let execArgs = [];
|
|
31
|
-
if (isShellMode) {
|
|
71
|
+
let spawnCommand = "";
|
|
72
|
+
let spawnArgs = [];
|
|
73
|
+
if (args.isShellMode) {
|
|
32
74
|
options.shell = true;
|
|
33
|
-
|
|
34
|
-
|
|
75
|
+
spawnCommand = String(args.commandLine);
|
|
76
|
+
spawnArgs = [];
|
|
35
77
|
}
|
|
36
78
|
else {
|
|
37
79
|
options.shell = false;
|
|
38
|
-
const argv =
|
|
39
|
-
|
|
40
|
-
|
|
80
|
+
const argv = args.argv;
|
|
81
|
+
spawnCommand = argv[0];
|
|
82
|
+
spawnArgs = argv.slice(1);
|
|
41
83
|
}
|
|
42
84
|
const logWithElapsedTime = (msg, ...rest) => {
|
|
43
85
|
if (!is_verbose)
|
|
44
86
|
return;
|
|
45
87
|
const elapsed = ((performance.now() - startTime) / 1000).toFixed(3);
|
|
46
|
-
verbose_log(`[${elapsed}s] ${msg}`, ...rest,
|
|
88
|
+
verbose_log(`[${elapsed}s] ${msg}`, ...rest, spawnCommand, spawnArgs);
|
|
47
89
|
};
|
|
48
90
|
let child_pid;
|
|
49
91
|
const promise = new Promise((resolve, reject) => {
|
|
50
|
-
if (!
|
|
92
|
+
if (!args.stdin_text) {
|
|
51
93
|
// PRN windowsHide on Windows, signal, killSignal
|
|
52
94
|
// FYI spawn_options.stdio => default is perfect ['pipe', 'pipe', 'pipe']
|
|
53
95
|
// order: [STDIN, STDOUT, STDERR]
|
|
@@ -74,13 +116,13 @@ export function runProcess(runProcessArgs) {
|
|
|
74
116
|
resolve(resultFor(result));
|
|
75
117
|
}
|
|
76
118
|
};
|
|
77
|
-
const child = spawn(
|
|
119
|
+
const child = spawn(spawnCommand, spawnArgs, options);
|
|
78
120
|
logWithElapsedTime(`START SPAWN child.pid: ${child.pid}`);
|
|
79
121
|
child_pid = child.pid;
|
|
80
122
|
let stdout = "";
|
|
81
123
|
let stderr = "";
|
|
82
|
-
if (child.stdin &&
|
|
83
|
-
child.stdin.write(
|
|
124
|
+
if (child.stdin && args.stdin_text) {
|
|
125
|
+
child.stdin.write(args.stdin_text);
|
|
84
126
|
child.stdin.end();
|
|
85
127
|
}
|
|
86
128
|
if (child.stdout) {
|
|
@@ -107,10 +149,6 @@ export function runProcess(runProcessArgs) {
|
|
|
107
149
|
});
|
|
108
150
|
// Timeout handling – kill the whole process group after the supplied timeout.
|
|
109
151
|
let timer = null;
|
|
110
|
-
let timeoutMs = Number(runProcessArgs?.timeout_ms);
|
|
111
|
-
if (Number.isNaN(timeoutMs)) {
|
|
112
|
-
timeoutMs = 30_000;
|
|
113
|
-
}
|
|
114
152
|
timer = setTimeout(() => {
|
|
115
153
|
if (process.platform !== "win32") {
|
|
116
154
|
if (child.pid) {
|
|
@@ -139,7 +177,7 @@ export function runProcess(runProcessArgs) {
|
|
|
139
177
|
const clearKill = () => clearTimeout(killTimeout);
|
|
140
178
|
child.once("exit", clearKill);
|
|
141
179
|
child.once("close", clearKill);
|
|
142
|
-
}, timeoutMs);
|
|
180
|
+
}, args.timeoutMs);
|
|
143
181
|
child.on("error", (err) => {
|
|
144
182
|
logWithElapsedTime("ERROR");
|
|
145
183
|
// ChildProcess 'error' docs: https://nodejs.org/api/child_process.html#event-error
|
package/build/tools.js
CHANGED
|
@@ -36,7 +36,7 @@ export function registerTools(server) {
|
|
|
36
36
|
type: "string",
|
|
37
37
|
description: "Optional to set working directory",
|
|
38
38
|
},
|
|
39
|
-
|
|
39
|
+
stdin_text: {
|
|
40
40
|
type: "string",
|
|
41
41
|
description: "Optional text written to STDIN (written fully, then closed). Useful for heredoc-style input or file contents."
|
|
42
42
|
},
|