Skip to content

Commit dafc9f1

Browse files
Fix: Prevent external process output from contaminating JSON communication (#1)
1 parent c24150b commit dafc9f1

1 file changed

Lines changed: 47 additions & 4 deletions

File tree

src/index.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
33
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4-
import { promisify } from "util";
5-
import { exec as execCallback } from "child_process";
6-
7-
const exec = promisify(execCallback);
4+
import { spawn } from "node:child_process";
85

96
/**
107
* Strip ANSI escape sequences from a string to make it human-readable.
@@ -46,6 +43,52 @@ function escapeShellArg(arg: string): string {
4643
return `'${arg.replace(/'/g, "'\"'\"'")}'`;
4744
}
4845

46+
/**
47+
* Execute a command with isolated streams to prevent external processes
48+
* from interfering with the output.
49+
*/
50+
function exec(command: string): Promise<{ stdout: string; stderr: string }> {
51+
return new Promise((resolve, reject) => {
52+
const parts = command.split(" ");
53+
const program = parts[0];
54+
const args = parts.slice(1).filter(arg => arg.length > 0);
55+
56+
// Use spawn with explicit stdio control
57+
const child = spawn(program, args, {
58+
shell: true, // Use shell to handle quotes and escaping
59+
});
60+
61+
let stdout = "";
62+
let stderr = "";
63+
64+
child.stdout.setEncoding("utf8");
65+
child.stderr.setEncoding("utf8");
66+
67+
child.stdout.on("data", (data) => {
68+
stdout += data;
69+
});
70+
71+
child.stderr.on("data", (data) => {
72+
stderr += data;
73+
});
74+
75+
child.on("close", (code) => {
76+
if (code === 0 || code === 1) { // Code 1 is "no matches" for ripgrep
77+
resolve({ stdout, stderr });
78+
} else {
79+
const error = new Error(`Command failed with exit code ${code}`);
80+
Object.assign(error, { code, stdout, stderr });
81+
reject(error);
82+
}
83+
});
84+
85+
// Handle process errors
86+
child.on("error", (error) => {
87+
reject(error);
88+
});
89+
});
90+
}
91+
4992
// List available tools
5093
server.setRequestHandler(ListToolsRequestSchema, async () => {
5194
return {

0 commit comments

Comments
 (0)