diff --git a/server/mcp/stdio.js b/server/mcp/stdio.js index 563ef30ce..c89407be3 100644 --- a/server/mcp/stdio.js +++ b/server/mcp/stdio.js @@ -54,7 +54,11 @@ async function respond(server, line, log) { } const described = describeRequest(message); if (described) log(described); - return server.handle(message); + const response = await server.handle(message); + // A failed tool call answers the client, not the log; say why here too. + if (response?.result?.isError) + log(` ${described} failed: ${response.result.content?.[0]?.text}`); + return response; } /** Read `--api-base ` from command-line arguments. */ diff --git a/src/tools/localServer.test.mjs b/src/tools/localServer.test.mjs index 61aa5c024..c9b901bf0 100644 --- a/src/tools/localServer.test.mjs +++ b/src/tools/localServer.test.mjs @@ -104,3 +104,30 @@ test('the stdio log names methods, tools and resources but never arguments', asy ); assert.equal(describeRequest({ result: {} }), null); }); + +test('a failed tool call is logged with its reason', async () => { + const server = { + handle: async (message) => ({ + jsonrpc: '2.0', + id: message.id, + result: { isError: true, content: [{ type: 'text', text: 'bad area' }] }, + }), + }; + const input = new PassThrough(); + const output = new PassThrough(); + output.resume(); + const logged = []; + const served = serveStdio(server, { + input, + output, + log: (line) => logged.push(line), + }); + input.end( + '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"show_in_gods_eye_view"}}\n', + ); + await served; + assert.deepEqual(logged, [ + 'tools/call show_in_gods_eye_view', + ' tools/call show_in_gods_eye_view failed: bad area', + ]); +});