Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/agentics-maintenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
#
# This file defines the generated agentic maintenance workflow for this repository.
# It runs scheduled cleanup for expiring safe outputs and supports manual maintenance operations.
#
#
Comment thread
pelikhan marked this conversation as resolved.
# This workflow is generated automatically when workflows use expiring safe outputs
# or when repository maintenance features are enabled in .github/workflows/aw.json.
#
#
Comment thread
pelikhan marked this conversation as resolved.
# To disable maintenance workflow generation, set in .github/workflows/aw.json:
# {"maintenance": false}
#
#
Comment thread
pelikhan marked this conversation as resolved.
# Agentic maintenance docs:
# https://github.github.com/gh-aw/reference/ephemerals/#manual-maintenance-operations
#
Expand Down
6 changes: 5 additions & 1 deletion actions/setup/js/mcp_server_core.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,11 @@ async function handleMessage(server, req, defaultHandler) {
server.replyError(id, -32601, `Method not found: ${method}`);
}
} catch (e) {
server.replyError(id, -32603, e instanceof Error ? e.message : String(e));
// Use the error code only if it's a valid JSON-RPC error code (must be a negative integer).
// Subprocess exit codes (positive integers like 1, 2, etc.) must not be used as JSON-RPC
// error codes, as that would produce non-conformant responses (e.g. "code=1").
const code = e && typeof e === "object" && Number.isInteger(e.code) && e.code < 0 ? e.code : -32603;
server.replyError(id, code, e && e.message ? String(e.message) : "Internal error");
}
}

Expand Down
121 changes: 121 additions & 0 deletions actions/setup/js/mcp_server_core.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,127 @@ describe("mcp_server_core.cjs", () => {
expect(results).toHaveLength(1);
expect(results[0].result.content[0].text).toBe("default handler for no_handler_tool");
});

it("should return the code and message from a thrown plain object (not Error instance)", async () => {
const { registerTool, handleMessage } = await import("./mcp_server_core.cjs");

registerTool(server, {
name: "plain_throw_tool",
description: "A tool that throws a plain object",
inputSchema: { type: "object", properties: { input: { type: "string" } }, required: ["input"] },
handler: () => {
throw { code: -32602, message: "ERR_VALIDATION: body required" };
},
});

await handleMessage(server, {
jsonrpc: "2.0",
id: 99,
method: "tools/call",
params: { name: "plain_throw_tool", arguments: { input: "x" } },
});

expect(results).toHaveLength(1);
expect(results[0].error.code).toBe(-32602);
expect(results[0].error.message).toBe("ERR_VALIDATION: body required");
expect(results[0].error.message).not.toContain("[object Object]");
Comment thread
pelikhan marked this conversation as resolved.
});

it("should fall back to -32603 when thrown plain object has no valid error code", async () => {
const { registerTool, handleMessage } = await import("./mcp_server_core.cjs");

registerTool(server, {
name: "no_code_throw_tool",
description: "A tool that throws a plain object without a code",
inputSchema: { type: "object", properties: { input: { type: "string" } }, required: ["input"] },
handler: () => {
throw { message: "something went wrong" };
},
});

await handleMessage(server, {
jsonrpc: "2.0",
id: 100,
method: "tools/call",
params: { name: "no_code_throw_tool", arguments: { input: "x" } },
});

expect(results).toHaveLength(1);
expect(results[0].error.code).toBe(-32603);
expect(results[0].error.message).toBe("something went wrong");
Comment thread
pelikhan marked this conversation as resolved.
});
Comment thread
pelikhan marked this conversation as resolved.

it("should fall back to -32603 when thrown plain object has a positive code", async () => {
const { registerTool, handleMessage } = await import("./mcp_server_core.cjs");

registerTool(server, {
name: "positive_code_throw_tool",
description: "A tool that throws a plain object with a positive code",
inputSchema: { type: "object", properties: { input: { type: "string" } }, required: ["input"] },
handler: () => {
throw { code: 1, message: "process exited with code 1" };
},
});

await handleMessage(server, {
jsonrpc: "2.0",
id: 101,
method: "tools/call",
params: { name: "positive_code_throw_tool", arguments: { input: "x" } },
});

expect(results).toHaveLength(1);
expect(results[0].error.code).toBe(-32603);
expect(results[0].error.message).toBe("process exited with code 1");
});

it("should use Internal error when thrown plain object has no message property", async () => {
const { registerTool, handleMessage } = await import("./mcp_server_core.cjs");

registerTool(server, {
name: "no_message_throw_tool",
description: "A tool that throws a plain object without a message",
inputSchema: { type: "object", properties: { input: { type: "string" } }, required: ["input"] },
handler: () => {
throw { code: -32602 };
},
});

await handleMessage(server, {
jsonrpc: "2.0",
id: 102,
method: "tools/call",
params: { name: "no_message_throw_tool", arguments: { input: "x" } },
});

expect(results).toHaveLength(1);
expect(results[0].error.code).toBe(-32602);
expect(results[0].error.message).toBe("Internal error");
});

it("should fall back to -32603 when thrown plain object has a non-integer negative code", async () => {
const { registerTool, handleMessage } = await import("./mcp_server_core.cjs");

registerTool(server, {
name: "non_integer_code_throw_tool",
description: "A tool that throws a plain object with a non-integer code",
inputSchema: { type: "object", properties: { input: { type: "string" } }, required: ["input"] },
handler: () => {
throw { code: -1.5, message: "fractional code should be ignored" };
},
});

await handleMessage(server, {
jsonrpc: "2.0",
id: 103,
method: "tools/call",
params: { name: "non_integer_code_throw_tool", arguments: { input: "x" } },
});

expect(results).toHaveLength(1);
expect(results[0].error.code).toBe(-32603);
expect(results[0].error.message).toBe("fractional code should be ignored");
});
});

describe("handleRequest", () => {
Expand Down
Loading