Anubis.MCP.Error (anubis_mcp v2.2.0)

Copy Markdown

Fluent API for building MCP protocol errors.

This module provides a semantic interface for creating errors that comply with the MCP specification and JSON-RPC 2.0 error standards.

Error Categories

  • Protocol Errors: Standard JSON-RPC errors (parse, invalid request, etc.)
  • Transport Errors: Connection and communication issues
  • Resource Errors: MCP-specific resource handling errors
  • Execution Errors: Tool and operation execution failures

Error code ranges

MCP partitions the JSON-RPC implementation-defined range: -32000 to -32019 holds codes allocated before the policy existed (such as -32002 for a missing resource, and the generic -32000 used by transport and execution errors), while -32020 to -32099 is reserved for codes the specification defines.

Examples

# Protocol errors
Anubis.MCP.Error.protocol(:parse_error)
Anubis.MCP.Error.protocol(:method_not_found, %{method: "unknown"})

# Transport errors
Anubis.MCP.Error.transport(:connection_refused)
Anubis.MCP.Error.transport(:timeout, %{elapsed_ms: 30000})

# Resource errors
Anubis.MCP.Error.resource(:not_found, %{uri: "file:///missing.txt"})

# Reserved errors whose payload the specification pins down
Anubis.MCP.Error.unsupported_protocol_version("1900-01-01", ["2026-07-28"])
Anubis.MCP.Error.missing_required_client_capability(%{"elicitation" => %{}})
Anubis.MCP.Error.url_elicitation_required([%{"mode" => "url", "elicitationId" => "1", "url" => "https://example.com", "message" => "Authorize"}])

# Execution errors with custom messages
Anubis.MCP.Error.execution("Database connection failed", %{retries: 3})

# Converting from JSON-RPC
Anubis.MCP.Error.from_json_rpc(%{"code" => -32700, "message" => "Parse error"})

Summary

Functions

Creates an execution error with a custom message.

Creates an error from a JSON-RPC error object.

Creates a MissingRequiredClientCapability error for a request that needs a capability the client did not declare.

Creates a protocol-level error.

Creates a resource-specific error.

Creates a resource-not-found error for a protocol era.

Encodes the error as a JSON-RPC error response.

Creates a transport-level error.

Creates an UnsupportedProtocolVersion error for a version this peer does not implement.

Creates a URLElicitationRequiredError for a request that cannot proceed until the user completes an out-of-band interaction.

Wraps an arbitrary failure reason as an encodable MCP error.

Types

t()

@type t() :: %Anubis.MCP.Error{
  code: integer(),
  data: map(),
  message: String.t() | nil,
  reason: atom()
}

Functions

build_json_rpc(error, id \\ ID.generate_error_id())

execution(message, data \\ %{})

@spec execution(String.t(), map()) :: t()

Creates an execution error with a custom message.

Used for tool execution failures and domain-specific errors.

Examples

iex> Anubis.MCP.Error.execution("Database connection failed")
%Anubis.MCP.Error{code: -32000, reason: :execution_error, message: "Database connection failed", data: %{}}

iex> Anubis.MCP.Error.execution("API rate limit exceeded", %{retry_after: 60})
%Anubis.MCP.Error{code: -32000, reason: :execution_error, message: "API rate limit exceeded", data: %{retry_after: 60}}

from_json_rpc(error)

@spec from_json_rpc(map()) :: t()

Creates an error from a JSON-RPC error object.

Examples

iex> Anubis.MCP.Error.from_json_rpc(%{"code" => -32700, "message" => "Parse error"})
%Anubis.MCP.Error{code: -32700, reason: :parse_error, message: "Parse error", data: %{}}

iex> Anubis.MCP.Error.from_json_rpc(%{"code" => -32002, "message" => "Not found", "data" => %{"uri" => "/file"}})
%Anubis.MCP.Error{code: -32002, reason: :resource_not_found, message: "Not found", data: %{"uri" => "/file"}}

missing_required_client_capability(capabilities)

@spec missing_required_client_capability(map()) :: t()

Creates a MissingRequiredClientCapability error for a request that needs a capability the client did not declare.

capabilities is a ClientCapabilities map — the capabilities required to process the request, not a list of their names.

Examples

iex> Anubis.MCP.Error.missing_required_client_capability(%{"elicitation" => %{}})
%Anubis.MCP.Error{code: -32021, reason: :missing_required_client_capability, message: "Missing required client capability", data: %{requiredCapabilities: %{"elicitation" => %{}}}}

protocol(reason, data \\ %{})

@spec protocol(atom(), map()) :: t()

Creates a protocol-level error.

These are standard JSON-RPC errors that occur during message parsing and validation.

Supported Reasons

  • :parse_error - Invalid JSON was received
  • :invalid_request - The JSON is not a valid Request object
  • :method_not_found - The method does not exist
  • :invalid_params - Invalid method parameters
  • :internal_error - Internal JSON-RPC error
  • :header_mismatch - Transport headers disagree with the request body
  • :missing_required_client_capability - The request needs a capability the client did not declare
  • :unsupported_protocol_version - The requested protocol version is not implemented

Examples

iex> Anubis.MCP.Error.protocol(:parse_error)
%Anubis.MCP.Error{code: -32700, reason: :parse_error, message: "Parse error", data: %{}}

iex> Anubis.MCP.Error.protocol(:method_not_found, %{method: "foo"})
%Anubis.MCP.Error{code: -32601, reason: :method_not_found, message: "Method not found", data: %{method: "foo"}}

iex> Anubis.MCP.Error.protocol(:unsupported_protocol_version, %{supported: ["2026-07-28"], requested: "1900-01-01"})
%Anubis.MCP.Error{code: -32022, reason: :unsupported_protocol_version, message: "Unsupported protocol version", data: %{supported: ["2026-07-28"], requested: "1900-01-01"}}

resource(atom, data \\ %{})

@spec resource(atom(), map()) :: t()

Creates a resource-specific error.

Used for MCP resource operations.

Examples

iex> Anubis.MCP.Error.resource(:not_found, %{uri: "file:///missing.txt"})
%Anubis.MCP.Error{code: -32002, reason: :resource_not_found, message: "Resource not found", data: %{uri: "file:///missing.txt"}}

resource_not_found(data, arg2)

@spec resource_not_found(map(), Anubis.Protocol.Behaviour.era()) :: t()

Creates a resource-not-found error for a protocol era.

Revision 2026-07-28 reallocates this condition from the MCP-specific -32002 to the JSON-RPC -32602, and reserves -32002 so it is never reused. Legacy eras keep the code they shipped with. Only the code differs: the reason stays :resource_not_found in every era, so code that matches on it, such as the resource-template fallback, treats both eras alike.

Examples

iex> Anubis.MCP.Error.resource_not_found(%{uri: "file:///missing.txt"}, :legacy).code
-32002

iex> Anubis.MCP.Error.resource_not_found(%{uri: "file:///missing.txt"}, :stateless).code
-32602

to_json_rpc(error, id \\ ID.generate_error_id())

@spec to_json_rpc(t(), String.t() | integer()) :: {:ok, String.t()} | {:error, term()}

Encodes the error as a JSON-RPC error response.

Examples

iex> error = Anubis.MCP.Error.protocol(:parse_error)
iex> {:ok, encoded} = Anubis.MCP.Error.to_json_rpc(error, "req-123")
iex> String.contains?(encoded, "Parse error")
true

transport(reason, data \\ %{})

@spec transport(atom(), map()) :: t()

Creates a transport-level error.

Used for network, connection, and communication failures.

Examples

iex> Anubis.MCP.Error.transport(:connection_refused)
%Anubis.MCP.Error{code: -32000, reason: :connection_refused, message: "Connection Refused", data: %{}}

iex> Anubis.MCP.Error.transport(:timeout, %{elapsed_ms: 5000})
%Anubis.MCP.Error{code: -32000, reason: :timeout, message: "Timeout", data: %{elapsed_ms: 5000}}

unsupported_protocol_version(requested, supported)

@spec unsupported_protocol_version(String.t(), [String.t()]) :: t()

Creates an UnsupportedProtocolVersion error for a version this peer does not implement.

The specification requires the data payload to carry both the versions this peer supports and the version that was requested, so prefer this over protocol/2 to build the payload for you.

Raises when supported holds anything other than version strings — a guard cannot express that, and a non-string entry would serialize a payload the specification does not allow.

Examples

iex> Anubis.MCP.Error.unsupported_protocol_version("1900-01-01", ["2026-07-28"])
%Anubis.MCP.Error{code: -32022, reason: :unsupported_protocol_version, message: "Unsupported protocol version", data: %{supported: ["2026-07-28"], requested: "1900-01-01"}}

url_elicitation_required(elicitations)

@spec url_elicitation_required([map()]) :: t()

Creates a URLElicitationRequiredError for a request that cannot proceed until the user completes an out-of-band interaction.

elicitations is a list of URL-mode elicitation/create params, each with at least mode, elicitationId, url and message. The specification requires every entry to be URL mode, so a form-mode entry raises.

Examples

iex> Anubis.MCP.Error.url_elicitation_required([
...>   %{
...>     "mode" => "url",
...>     "elicitationId" => "550e8400",
...>     "url" => "https://example.com/authorize",
...>     "message" => "Authorization is required"
...>   }
...> ])
%Anubis.MCP.Error{code: -32042, reason: :url_elicitation_required, message: "URL elicitation required", data: %{"elicitations" => [%{"mode" => "url", "elicitationId" => "550e8400", "url" => "https://example.com/authorize", "message" => "Authorization is required"}]}}

wrap_reason(error)

@spec wrap_reason(term()) :: t()

Wraps an arbitrary failure reason as an encodable MCP error.

Passes through existing %Error{} values (including those returned from init/2). Other terms are converted to an internal error with a string message safe for JSON encoding.

Examples

iex> error = Anubis.MCP.Error.protocol(:invalid_params, %{message: "bad input"})
iex> Anubis.MCP.Error.wrap_reason(error)
%Anubis.MCP.Error{code: -32602, reason: :invalid_params, message: "Invalid params", data: %{message: "bad input"}}

iex> wrapped = Anubis.MCP.Error.wrap_reason({:init_failed, %{code: "unauthorized"}})
iex> wrapped.reason
:internal_error
iex> wrapped.data.message
"init_failed: %{code: \"unauthorized\"}"