ConduitMcp.Protocol (ConduitMCP v0.11.0)

Copy Markdown View Source

JSON-RPC 2.0 message construction and MCP protocol versions.

ConduitMCP is dual-era. It speaks MCP 2026-07-28 (the modern era: version and capabilities travel in every request's _meta, no handshake) and 2025-11-25 / 2025-06-18 (the legacy era, negotiated by initialize). supported_versions/0 lists all three, newest first — the list server/discover advertises. modern_versions/0 and legacy_versions/0 split it by era; negotiate_version/1 resolves an initialize request and only ever picks a legacy version. protocol_version/0 is the preferred legacy version.

Building responses

Helpers success_response/2, error_response/3,4, and notification/2 produce the right wire shape — string-keyed maps with "jsonrpc", "id", and either "result" or "error". validate_request/1 performs lightweight shape checking on incoming messages.

Error codes

Standard JSON-RPC 2.0 codes and MCP-specific codes are exposed as delegating functions to ConduitMcp.Errors:

Use these instead of hardcoded integers so error code constants stay in one place.

Examples

iex> ConduitMcp.Protocol.success_response(1, %{"value" => 42})
%{"jsonrpc" => "2.0", "id" => 1, "result" => %{"value" => 42}}

iex> ConduitMcp.Protocol.error_response(1, -32601, "no such method")
%{"jsonrpc" => "2.0", "id" => 1, "error" => %{"code" => -32601, "message" => "no such method"}}

Summary

Functions

The legacy-era (initialize-negotiated) versions: ["2025-11-25", "2025-06-18"].

Every MCP method the library routes, mapped to its internal name.

The modern-era (per-request _meta) versions: ["2026-07-28"].

Resolves the version an initialize request asks for: the version itself when it is a legacy version, otherwise nil. A modern version is never negotiated by initialize; modern clients send it per request instead.

Creates a notification message.

The preferred legacy protocol version, 2025-11-25.

Creates a success response.

Every supported protocol version, newest first: ["2026-07-28", "2025-11-25", "2025-06-18"].

Validates if a message is a valid JSON-RPC 2.0 notification.

Validates if a message is a valid JSON-RPC 2.0 request.

Validates if a message is a JSON-RPC 2.0 response: an id, no method, and exactly one of result and error.

Types

error_object()

@type error_object() :: %{code: integer(), message: String.t(), data: any() | nil}

error_response()

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

json_rpc_id()

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

method()

@type method() :: String.t()

notification()

@type notification() :: %{jsonrpc: String.t(), method: method(), params: map() | nil}

request()

@type request() :: %{
  jsonrpc: String.t(),
  id: json_rpc_id(),
  method: method(),
  params: map() | nil
}

response()

@type response() :: success_response() | error_response()

success_response()

@type success_response() :: %{jsonrpc: String.t(), id: json_rpc_id(), result: any()}

Functions

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

Creates an error response.

internal_error()

See ConduitMcp.Errors.internal_error/0.

invalid_params()

See ConduitMcp.Errors.invalid_params/0.

invalid_request()

See ConduitMcp.Errors.invalid_request/0.

legacy_versions()

@spec legacy_versions() :: [String.t()]

The legacy-era (initialize-negotiated) versions: ["2025-11-25", "2025-06-18"].

method_not_found()

See ConduitMcp.Errors.method_not_found/0.

methods()

@spec methods() :: %{optional(String.t()) => atom()}

Every MCP method the library routes, mapped to its internal name.

Derived from ConduitMcp.Handler's routing table rather than being a second hand-maintained copy: the previous list was missing six routed methods (resources/templates/list, all four tasks/*, and notifications/cancelled).

modern_versions()

@spec modern_versions() :: [String.t()]

The modern-era (per-request _meta) versions: ["2026-07-28"].

negotiate_version(client_version)

@spec negotiate_version(term()) :: String.t() | nil

Resolves the version an initialize request asks for: the version itself when it is a legacy version, otherwise nil. A modern version is never negotiated by initialize; modern clients send it per request instead.

notification(method, params \\ nil)

Creates a notification message.

parse_error()

See ConduitMcp.Errors.parse_error/0.

protocol_version()

@spec protocol_version() :: String.t()

The preferred legacy protocol version, 2025-11-25.

request_cancelled()

See ConduitMcp.Errors.request_cancelled/0.

server_error()

See ConduitMcp.Errors.server_error/0.

success_response(id, result)

Creates a success response.

supported_versions()

@spec supported_versions() :: [String.t()]

Every supported protocol version, newest first: ["2026-07-28", "2025-11-25", "2025-06-18"].

task_not_ready()

See ConduitMcp.Errors.task_not_ready/0.

valid_notification?(message)

Validates if a message is a valid JSON-RPC 2.0 notification.

valid_request?(message)

Validates if a message is a valid JSON-RPC 2.0 request.

valid_response?(message)

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

Validates if a message is a JSON-RPC 2.0 response: an id, no method, and exactly one of result and error.