barrel_mcp_protocol (barrel_mcp v2.0.2)

View Source

MCP protocol implementation over JSON-RPC 2.0.

Handles encoding/decoding and routing of MCP methods.

Summary

Functions

Decode a JSON-RPC request body. The spec includes list() in the success type so the HTTP transport can detect (and reject) JSON-RPC batches.

Classify a decoded JSON-RPC envelope.

Drive an {async, AsyncPlan} from handle/2 to completion on the calling process and return a JSON-RPC response map.

Encode a JSON-RPC response.

Build a JSON-RPC error response. Alias of error_response/3.

Build a JSON-RPC notification envelope (no id).

Build a JSON-RPC request envelope.

Build a JSON-RPC success response.

Create an error response.

Format a tool handler's plain return value into the MCP content-block list shape. Public so transports driving async tool calls (HTTP / stdio) can produce identical envelopes.

Handle a JSON-RPC request with default state.

Handle a JSON-RPC request with state.

Return a marker for no response (notifications).

Functions

decode(Binary)

-spec decode(binary()) -> {ok, map() | list()} | {error, term()}.

Decode a JSON-RPC request body. The spec includes list() in the success type so the HTTP transport can detect (and reject) JSON-RPC batches.

decode_envelope(L)

-spec decode_envelope(map()) ->
                         {request, Id :: term(), Method :: binary(), Params :: map()} |
                         {notification, Method :: binary(), Params :: map()} |
                         {response, Id :: term(), Result :: term()} |
                         {error, Id :: term(), Code :: integer(), Message :: binary(), Data :: term()} |
                         {invalid, term()}.

Classify a decoded JSON-RPC envelope.

Returns the kind so client and server agree on routing without each having to peek at the same keys.

drive_async_plan(Plan, Timeout)

-spec drive_async_plan(map(), timeout()) -> map().

Drive an {async, AsyncPlan} from handle/2 to completion on the calling process and return a JSON-RPC response map.

Used by transports that don't have their own request/wait machinery (stdio, legacy HTTP). The Streamable HTTP transport drives async plans itself because it needs to record per-session in-flight entries for cancellation routing.

encode(Response)

-spec encode(map()) -> binary().

Encode a JSON-RPC response.

encode_error(Id, Code, Message)

-spec encode_error(term(), integer(), binary()) -> map().

Build a JSON-RPC error response. Alias of error_response/3.

encode_notification(Method, Params)

-spec encode_notification(binary(), map()) -> map().

Build a JSON-RPC notification envelope (no id).

encode_request(Id, Method, Params)

-spec encode_request(term(), binary(), map()) -> map().

Build a JSON-RPC request envelope.

encode_response(Id, Result)

-spec encode_response(term(), term()) -> map().

Build a JSON-RPC success response.

error_response(Id, Code, Message)

-spec error_response(term(), integer(), binary()) -> map().

Create an error response.

error_response(Id, Code, Message, Meta)

-spec error_response(term(), integer(), binary(), map()) -> map().

format_tool_result_external(Result)

-spec format_tool_result_external(term()) -> [map()].

Format a tool handler's plain return value into the MCP content-block list shape. Public so transports driving async tool calls (HTTP / stdio) can produce identical envelopes.

handle(Request)

-spec handle(map() | list()) -> map() | no_response | {async, map()}.

Handle a JSON-RPC request with default state.

handle(L, State)

-spec handle(map() | list(), map()) -> map() | no_response | {async, map()}.

Handle a JSON-RPC request with state.

Returns one of:

  • map() — a JSON-RPC response envelope ready to encode.
  • no_response — for inbound notifications.
  • {async, AsyncPlan} — for tools/call. The transport spawns the worker via (maps:get(spawn, AsyncPlan))(Ctx) and waits on its mailbox for a tool_result / tool_error / tool_failed / tool_validation_failed / cancelled message.

MCP forbids JSON-RPC batches (a top-level JSON array) — they are rejected here with Invalid Request so non-HTTP callers see the same error as the HTTP transport.

notification_response()

-spec notification_response() -> no_response.

Return a marker for no response (notifications).

success_response(Id, Result)

success_response(Id, Result, Meta)