Error Handling
This guide explains how errors are handled in the Hermes MCP client.
Error Types
Hermes MCP distinguishes between three types of errors:
- Protocol errors (JSON-RPC errors): Standard errors defined by the JSON-RPC 2.0 specification
- Domain errors: Application-level errors reported by the server with
isError: true
- 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 codereason
: 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 theisError
field)id
: The request IDis_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})