Error Handling

This guide explains how errors are handled in the Hermes MCP client.

Error Types

Hermes MCP distinguishes between three types of errors:

  1. Protocol errors (JSON-RPC errors): Standard errors defined by the JSON-RPC 2.0 specification
  2. Domain errors: Application-level errors reported by the server with isError: true
  3. Client-side errors: Errors generated by the client before reaching the server

Error Representation

Protocol and client-side errors in Hermes MCP are represented as {:error, %Hermes.MCP.Error{}} tuples, where Error is a struct with:

  • code: The numeric error code
  • reason: The atom reason (e.g., :parse_error, :method_not_found)
  • data: Additional error context

Domain errors (with isError: true) are returned as {:ok, %Hermes.MCP.Response{}} tuples, where the response includes:

  • result: The original result from the server (including the isError field)
  • id: The request ID
  • is_error: A boolean flag indicating if this is a domain error

Protocol Errors

Protocol errors (JSON-RPC errors) use the standard JSON-RPC error codes with specific reason atoms:

{:error, %Hermes.MCP.Error{
  code: -32601,
  reason: :method_not_found,
  data: %{original_message: "Method not found"}
}}

Standard JSON-RPC error codes:

  • -32700: Parse error (:parse_error)
  • -32600: Invalid request (:invalid_request)
  • -32601: Method not found (:method_not_found)
  • -32602: Invalid params (:invalid_params)
  • -32603: Internal error (:internal_error)
  • -32000: Server error (:server_error)

Domain Errors

Domain errors (application-level errors) are valid JSON-RPC responses with isError: true and are represented as:

{:ok, %Hermes.MCP.Response{
  result: %{
    "isError" => true,
    "reason" => "tool_not_found",
    "message" => "Tool 'unknown' not found",
    "details" => %{"toolName" => "unknown"}
  },
  id: "req_123",
  is_error: true
}}

Client-Side Errors

Client-specific errors follow the same error struct pattern with appropriate reason atoms:

# Transport error
{:error, %Hermes.MCP.Error{
  code: -32000,
  reason: :connection_refused,  
  data: %{type: :transport}
}}

# Capability error
{:error, %Hermes.MCP.Error{
  code: -32601,
  reason: :method_not_found,
  data: %{method: "resources/list"}
}}

# Timeout error
{:error, %Hermes.MCP.Error{
  code: -32000, 
  reason: :request_timeout, 
  data: %{type: :client, message: "Request timed out after 30000ms"}
}}

Handling Errors

The client automatically categorizes errors, returning them in consistent formats.

You can pattern match on the response structure to handle different error types:

case Hermes.Client.call_tool("search", %{query: "example"}) do
  {:ok, %Hermes.MCP.Response{is_error: false, result: result}} ->
    # Handle success
    handle_success(result)
    
  {:ok, %Hermes.MCP.Response{is_error: true, result: result}} ->
    # Handle domain/application error
    case result do
      %{"reason" => "not_found"} -> 
        Logger.warning("Resource not found: #{result["message"]}")
      
      %{"reason" => "permission_denied"} ->
        Logger.error("Permission denied: #{result["message"]}")
        
      _ ->
        Logger.error("Other domain error: #{inspect(result)}")
    end
    
  {:error, %Hermes.MCP.Error{reason: :method_not_found}} ->
    # Handle unsupported method
    Logger.warning("Method not supported by this server")
    
  {:error, %Hermes.MCP.Error{reason: reason}} when reason in [:connection_refused, :timeout] ->
    # Handle transport errors
    Logger.error("Transport error: #{reason}")
    
  {:error, %Hermes.MCP.Error{} = error} ->
    # Handle any other error
    Logger.error("Unexpected error: #{inspect(error)}")
end

Error Inspection

For better debugging, errors implement a custom Inspect protocol:

#MCP.Error<method_not_found %{method: "unknown_method"}>

Creating Custom Errors

If you need to create your own errors in your application:

# Protocol errors
Hermes.MCP.Error.parse_error()
Hermes.MCP.Error.method_not_found(%{method: "unknown_method"})

# Transport errors
Hermes.MCP.Error.transport_error(:connection_refused)

# Client errors
Hermes.MCP.Error.client_error(:request_timeout, %{elapsed_ms: 30000})