Skip to content

Commit 32986ea

Browse files
authored
Merge pull request #5834 from continuedev/nate/mcp-debug
show stderr in the error message for failed MCP servers
2 parents 2c33885 + bfce8cb commit 32986ea

File tree

4 files changed

+646
-33
lines changed

4 files changed

+646
-33
lines changed

core/context/mcp/MCPConnection.test.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import MCPConnection from "./MCPConnection";
44

55
describe("MCPConnection", () => {
66
beforeEach(() => {
7-
jest.clearAllMocks();
7+
jest.restoreAllMocks();
88
});
99

1010
describe("constructor", () => {
@@ -173,11 +173,11 @@ describe("MCPConnection", () => {
173173
});
174174

175175
it("should handle custom connection timeout", async () => {
176-
const conn = new MCPConnection({ ...options, timeout: 11 });
176+
const conn = new MCPConnection({ ...options, timeout: 1500 });
177177
const mockConnect = jest
178178
.spyOn(Client.prototype, "connect")
179179
.mockImplementation(
180-
() => new Promise((resolve) => setTimeout(resolve, 10)),
180+
() => new Promise((resolve) => setTimeout(resolve, 1000)),
181181
);
182182

183183
const abortController = new AbortController();
@@ -229,6 +229,38 @@ describe("MCPConnection", () => {
229229
expect(conn.errors[0]).toContain('command "test-cmd" not found');
230230
expect(mockConnect).toHaveBeenCalled();
231231
});
232+
233+
it.skip("should include stderr output in error message when stdio command fails", async () => {
234+
// Clear any existing mocks to ensure we get real behavior
235+
jest.restoreAllMocks();
236+
237+
// Use a command that will definitely fail and produce stderr output
238+
const failingOptions = {
239+
name: "failing-mcp",
240+
id: "failing-id",
241+
transport: {
242+
type: "stdio" as const,
243+
command: "node",
244+
args: [
245+
"-e",
246+
"console.error('Custom error message from stderr'); process.exit(1);",
247+
],
248+
},
249+
timeout: 5000, // Give enough time for the command to run and fail
250+
};
251+
252+
const conn = new MCPConnection(failingOptions);
253+
const abortController = new AbortController();
254+
255+
await conn.connectClient(false, abortController.signal);
256+
257+
expect(conn.status).toBe("error");
258+
expect(conn.errors).toHaveLength(1);
259+
expect(conn.errors[0]).toContain("Failed to connect");
260+
expect(conn.errors[0]).toContain("Process output:");
261+
expect(conn.errors[0]).toContain("STDERR:");
262+
expect(conn.errors[0]).toContain("Custom error message from stderr");
263+
});
232264
});
233265

234266
describe.skip("actually connect to Filesystem MCP", () => {

core/context/mcp/MCPConnection.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ class MCPConnection {
3838
public resources: MCPResource[] = [];
3939
private transport: Transport;
4040
private connectionPromise: Promise<unknown> | null = null;
41+
private stdioOutput: { stdout: string; stderr: string } = {
42+
stdout: "",
43+
stderr: "",
44+
};
4145

4246
constructor(public options: MCPOptions) {
4347
this.transport = this.constructTransport(options);
@@ -91,6 +95,7 @@ class MCPConnection {
9195
this.prompts = [];
9296
this.resources = [];
9397
this.errors = [];
98+
this.stdioOutput = { stdout: "", stderr: "" };
9499

95100
this.abortController.abort();
96101
this.abortController = new AbortController();
@@ -215,6 +220,20 @@ class MCPConnection {
215220
}
216221
}
217222

223+
// Include stdio output if available for stdio transport
224+
if (
225+
this.options.transport.type === "stdio" &&
226+
(this.stdioOutput.stdout || this.stdioOutput.stderr)
227+
) {
228+
errorMessage += "\n\nProcess output:";
229+
if (this.stdioOutput.stdout) {
230+
errorMessage += `\nSTDOUT:\n${this.stdioOutput.stdout}`;
231+
}
232+
if (this.stdioOutput.stderr) {
233+
errorMessage += `\nSTDERR:\n${this.stdioOutput.stderr}`;
234+
}
235+
}
236+
218237
this.status = "error";
219238
this.errors.push(errorMessage);
220239
} finally {
@@ -284,11 +303,20 @@ class MCPConnection {
284303
options.transport.args || [],
285304
);
286305

287-
return new StdioClientTransport({
306+
const transport = new StdioClientTransport({
288307
command,
289308
args,
290309
env,
310+
stderr: "pipe",
291311
});
312+
313+
// Capture stdio output for better error reporting
314+
315+
transport.stderr?.on("data", (data: Buffer) => {
316+
this.stdioOutput.stderr += data.toString();
317+
});
318+
319+
return transport;
292320
case "websocket":
293321
return new WebSocketClientTransport(new URL(options.transport.url));
294322
case "sse":

0 commit comments

Comments
 (0)