Hermolaos.Protocol.JsonRpc (Hermolaos v0.3.0)

View Source

JSON-RPC 2.0 encoding and decoding for MCP protocol messages.

This module handles the low-level JSON-RPC 2.0 message format used by MCP. All MCP communication uses JSON-RPC 2.0 as the underlying protocol.

Message Types

  • Request: Has id, method, and optional params
  • Response: Has id and either result or error
  • Notification: Has method and optional params, but no id

Examples

# Encoding a request
iex> Hermolaos.Protocol.JsonRpc.encode_request(1, "tools/list", %{})
~s({"id":1,"jsonrpc":"2.0","method":"tools/list","params":{}})

# Decoding a response
iex> Hermolaos.Protocol.JsonRpc.decode(~s({"jsonrpc":"2.0","id":1,"result":{"tools":[]}}))
{:ok, {:response, %{"jsonrpc" => "2.0", "id" => 1, "result" => %{"tools" => []}}}}

Summary

Functions

Classifies a decoded JSON-RPC message by its type.

Decodes a JSON-RPC 2.0 message and classifies its type.

Decodes a JSON-RPC 2.0 message, raising on error.

Encodes a JSON-RPC 2.0 error response.

Encodes a JSON-RPC 2.0 notification message (no response expected).

Encodes a JSON-RPC 2.0 request message.

Encodes a JSON-RPC 2.0 success response.

Checks if a response is an error response.

Extracts the error object from an error response.

Extracts the request/response ID from a message.

Extracts the method name from a request or notification.

Extracts parameters from a request or notification.

Extracts the result from a successful response.

Returns the message type of a decoded message.

Validates that a message is a proper JSON-RPC 2.0 notification.

Validates that a message is a proper JSON-RPC 2.0 request.

Validates that a message is a proper JSON-RPC 2.0 response (success or error).

Types

decoded_message()

@type decoded_message() :: {message_type(), map()}

error_object()

@type error_object() :: %{
  :code => integer(),
  :message => String.t(),
  optional(:data) => term()
}

error_response()

@type error_response() :: %{
  jsonrpc: String.t(),
  id: id() | nil,
  error: error_object()
}

id()

@type id() :: integer() | String.t()

message_type()

@type message_type() :: :request | :notification | :response | :error_response

notification()

@type notification() :: %{
  :jsonrpc => String.t(),
  :method => String.t(),
  optional(:params) => params()
}

params()

@type params() :: map() | list()

request()

@type request() :: %{
  :jsonrpc => String.t(),
  :id => id(),
  :method => String.t(),
  optional(:params) => params()
}

response()

@type response() :: %{jsonrpc: String.t(), id: id(), result: term()}

Functions

classify_message(message)

@spec classify_message(map()) :: {:ok, decoded_message()} | {:error, :invalid_message}

Classifies a decoded JSON-RPC message by its type.

Examples

iex> Hermolaos.Protocol.JsonRpc.classify_message(%{"jsonrpc" => "2.0", "id" => 1, "method" => "ping"})
{:ok, {:request, %{"jsonrpc" => "2.0", "id" => 1, "method" => "ping"}}}

decode(json_string)

@spec decode(String.t()) ::
  {:ok, decoded_message()} | {:error, :parse_error | :invalid_message}

Decodes a JSON-RPC 2.0 message and classifies its type.

Returns {:ok, {type, message}} where type is one of:

  • :request - A request expecting a response
  • :notification - A notification (no response expected)
  • :response - A successful response
  • :error_response - An error response

Examples

iex> Hermolaos.Protocol.JsonRpc.decode(~s({"jsonrpc":"2.0","id":1,"method":"ping"}))
{:ok, {:request, %{"jsonrpc" => "2.0", "id" => 1, "method" => "ping"}}}

iex> Hermolaos.Protocol.JsonRpc.decode(~s({"jsonrpc":"2.0","method":"notifications/initialized"}))
{:ok, {:notification, %{"jsonrpc" => "2.0", "method" => "notifications/initialized"}}}

iex> Hermolaos.Protocol.JsonRpc.decode("invalid json")
{:error, :parse_error}

decode!(json_string)

@spec decode!(String.t()) :: decoded_message()

Decodes a JSON-RPC 2.0 message, raising on error.

Examples

iex> Hermolaos.Protocol.JsonRpc.decode!(~s({"jsonrpc":"2.0","id":1,"result":{}}))
{:response, %{"jsonrpc" => "2.0", "id" => 1, "result" => %{}}}

encode_error_response(id, code, message, data \\ nil)

@spec encode_error_response(id() | nil, integer(), String.t(), term()) :: String.t()

Encodes a JSON-RPC 2.0 error response.

Parameters

  • id - The request ID (can be nil if request couldn't be parsed)
  • code - Error code (integer)
  • message - Human-readable error message
  • data - Optional additional error data

Examples

iex> Hermolaos.Protocol.JsonRpc.encode_error_response(1, -32600, "Invalid Request")
~s({"error":{"code":-32600,"message":"Invalid Request"},"id":1,"jsonrpc":"2.0"})

encode_notification(method, params \\ nil)

@spec encode_notification(String.t(), params() | nil) :: String.t()

Encodes a JSON-RPC 2.0 notification message (no response expected).

Notifications are like requests but without an id field, meaning the server should not send a response.

Parameters

  • method - The RPC method name
  • params - Optional parameters (map or list)

Examples

iex> Hermolaos.Protocol.JsonRpc.encode_notification("notifications/initialized", %{})
~s({"jsonrpc":"2.0","method":"notifications/initialized","params":{}})

encode_request(id, method, params \\ nil)

@spec encode_request(id(), String.t(), params() | nil) :: String.t()

Encodes a JSON-RPC 2.0 request message.

Parameters

  • id - Unique request identifier (integer or string)
  • method - The RPC method name
  • params - Optional parameters (map or list)

Examples

iex> Hermolaos.Protocol.JsonRpc.encode_request(1, "tools/list", %{})
~s({"id":1,"jsonrpc":"2.0","method":"tools/list","params":{}})

iex> Hermolaos.Protocol.JsonRpc.encode_request("abc", "ping", nil)
~s({"id":"abc","jsonrpc":"2.0","method":"ping"})

encode_response(id, result)

@spec encode_response(id(), term()) :: String.t()

Encodes a JSON-RPC 2.0 success response.

Parameters

  • id - The request ID being responded to
  • result - The result data

Examples

iex> Hermolaos.Protocol.JsonRpc.encode_response(1, %{tools: []})
~s({"id":1,"jsonrpc":"2.0","result":{"tools":[]}})

error_response?(message)

@spec error_response?(map()) :: boolean()

Checks if a response is an error response.

get_error(message)

@spec get_error(map()) :: error_object() | nil

Extracts the error object from an error response.

get_id(message)

@spec get_id(map()) :: id() | nil

Extracts the request/response ID from a message.

Examples

iex> Hermolaos.Protocol.JsonRpc.get_id(%{"id" => 42})
42

iex> Hermolaos.Protocol.JsonRpc.get_id(%{"method" => "notify"})
nil

get_method(message)

@spec get_method(map()) :: String.t() | nil

Extracts the method name from a request or notification.

get_params(message)

@spec get_params(map()) :: params() | nil

Extracts parameters from a request or notification.

get_result(message)

@spec get_result(map()) :: term() | nil

Extracts the result from a successful response.

message_type(message)

@spec message_type(map()) :: message_type() | :unknown

Returns the message type of a decoded message.

Examples

iex> Hermolaos.Protocol.JsonRpc.message_type(%{"jsonrpc" => "2.0", "id" => 1, "method" => "ping"})
:request

valid_notification?(message)

@spec valid_notification?(map()) :: boolean()

Validates that a message is a proper JSON-RPC 2.0 notification.

valid_request?(message)

@spec valid_request?(map()) :: boolean()

Validates that a message is a proper JSON-RPC 2.0 request.

Examples

iex> Hermolaos.Protocol.JsonRpc.valid_request?(%{"jsonrpc" => "2.0", "id" => 1, "method" => "ping"})
true

iex> Hermolaos.Protocol.JsonRpc.valid_request?(%{"id" => 1, "method" => "ping"})
false

valid_response?(message)

@spec valid_response?(map()) :: boolean()

Validates that a message is a proper JSON-RPC 2.0 response (success or error).