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.
@@ -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 (runProcessArgs?.cwd) {
14
- options.cwd = String(runProcessArgs.cwd);
68
+ if (args.cwd) {
69
+ options.cwd = args.cwd;
15
70
  }
16
- const stdin = runProcessArgs?.stdin ? String(runProcessArgs.stdin) : undefined;
17
- // ---------------------------------------------------------------------
18
- // RunProcess argument handling – determine the actual command and args.
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
- execCommand = String(runProcessArgs.command_line);
34
- execArgs = [];
75
+ spawnCommand = String(args.commandLine);
76
+ spawnArgs = [];
35
77
  }
36
78
  else {
37
79
  options.shell = false;
38
- const argv = runProcessArgs?.argv;
39
- execCommand = argv[0];
40
- execArgs = argv.slice(1);
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, execCommand, execArgs);
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 (!stdin) {
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(execCommand, execArgs, options);
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 && stdin) {
83
- child.stdin.write(stdin);
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
- stdin: {
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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-commands",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "An MCP server to run arbitrary commands",
5
5
  "private": false,
6
6
  "type": "module",