Skip to content

Protocol errors still returned as tool execution errors (invalid arguments, server errors) #284

@sqrlington

Description

@sqrlington

Describe the bug
Two of the three protocol error cases defined by the spec are still returned as tool execution errors (isError: true results) instead of JSON-RPC error responses:

  1. Invalid arguments — missing required arguments and schema validation failures both return isError: true
  2. Server errors — internal exceptions during tool execution return isError: true

This is the same class of bug as #228, which was fixed for unknown tool calls in PR #231. The other two cases were not addressed.

To Reproduce

  1. Define a tool with a required argument
  2. Call it with the argument missing
server = MCP::Server.new(tools: [MyTool])
response = server.handle({
  jsonrpc: "2.0",
  id: 1,
  method: "tools/call",
  params: { name: "my_tool", arguments: {} }
})
  1. Response is a successful JSON-RPC result with isError: true:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{"type": "text", "text": "Missing required arguments: required_argument"}],
    "isError": true
  }
}

Same behavior for invalid schema validation and internal server errors.

Expected behavior

A JSON-RPC protocol error, consistent with how unknown tool calls are handled:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": "Missing required arguments: required_argument"
  }
}

Additional context

The MCP spec draws a clear line between protocol errors and tool execution errors:

Protocol Errors: Standard JSON-RPC errors for issues like:

  • Unknown tools
  • Invalid arguments
  • Server errors

Tool Execution Errors: Reported in tool results with isError: true:

  • API failures
  • Invalid input data
  • Business logic errors

A note on server errors: #159 raised a valid concern that tool execution errors like ActiveRecord::RecordNotFound shouldn't be protocol errors. But the fix for that is for the tool to catch those exceptions and return Tool::Response.new(content, error: true) — that's the tool reporting a known failure. If an exception bubbles up uncaught, the server can't distinguish a business error from an actual crash, so treating it as a server error (protocol error) is the correct default.

The trail:

  1. PR Fix tool error handling to follow MCP spec #165 moved all three protocol error cases from RequestHandlerError (JSON-RPC errors) to error_tool_response (isError: true)
  2. Wrong kinds of MCP errors returned in some cases #228 flagged this as a spec violation
  3. PR Return JSON-RPC protocol errors for unknown tool calls #231 fixed unknown tools only — invalid arguments and server errors still return isError: true

The remaining two cases in Server#call_tool need to raise RequestHandlerError instead of returning error_tool_response, matching the pattern PR #231 restored for unknown tools.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions