Wymcp. JsonRpc
(Wymcp v0.8.3)
View Source
JSON-RPC 2.0 envelopes and MCP protocol schema validation — the wire-format floor every answer stands on.
success_response/2 and error_response/3 build the envelope every answer
is sent as; Wymcp.Response does the sending. The error map is the one home
for wymcp's JSON-RPC error codes: each error_type/0 atom pairs with its
{code, message} tuple, so a call site names the condition and never the
number. This module owns the envelope's shape; it owns no decision about
which condition applies — that is each plug's and each method module's.
The error envelope's contract
One rule, four clauses, over every JSON-RPC error this module builds — the 400s and 404s that are rejections and the 200-status method errors alike:
codecomes from the error-code table; a site names the condition, never the number.messageis the site's one-line cause. The table's string is what a site sends when it has nothing more to say —-32601Method not found.datais absent, or a map whose keys are drawn from a closed vocabulary of structured detail:errors(a validation failure's locations),header,expected,received(the header-binding check),supported,requested(the protocol version). No key carries a prose sentence, and no key carries the request or any part of it beyond the single value a condition is about.- A new structured key joins in one place — the
data_keyslist this module keeps beside its error-code table, whichWymcp.ErrorEnvelopeInvariantTestreads to hold every envelope to this vocabulary — the same discipline the rejection table keeps, a gate rather than a catalogue. Clause 3 names the keys for a reader; that list is what the gate is checked against.
The sentence belongs in message because that is the slot the MCP schemas
define for it — "A short description of the error … a concise single
sentence" — and the slot a client renders: a tools/call refused for a
wrong-typed argument showed the calling model Invalid params and nothing
else, measured 2026-09-10, while the cause sat under data unread.
Message and output-schema validation
The self-contained MCP protocol schema — priv/schema-2026-07-28.json,
JSON Schema 2020-12 — is compiled to a JSV.Root at build time through
a module attribute, so an inbound request is validated against the official
protocol definition with no runtime schema parsing. Its entry point is
JSONRPCMessage, which validate_mcp_request/1 validates against.
@external_resource on the path makes an edit to the schema file recompile
this module.
A validation failure reaches the wire as a distilled error — the
envelope's message, a one-line summary, plus data.errors, one entry per
instance location, locations sorted, each location's problems deduplicated
— never JSV.normalize_error/1's full tree. It is a shape, not one
function's return value: the producers below return the two halves as one
%{message:, errors:} map, and the site that answers splits them across the
envelope's two slots. This module
distills it on two paths: validate_mcp_request/1 on the -32600
message path, whose reader is the human debugging a client, and
validate_schema/2 on a tool's output-schema failure, which is logged and
never reaches the wire. Wymcp.Methods.ToolsCall builds a conforming one
by hand for the -32602 arguments path, whose reader is the calling LLM.
The full tree is JSON-encodable and was what previously reached the wire;
it is simply too verbose for either reader — 2353 bytes against 435 for one
garbage message, measured 2026-08-15.
Summary
Types
The atom naming one row of the error-code table: the JSON-RPC errors
wymcp answers, each pairing its atom with a {code, message} tuple.
Both error_response arities accept exactly these atoms.
Distinct from telemetry's error_kind, which classifies a tool
error's origin, not a JSON-RPC error.
Functions
The error envelope carrying the error-code table's own message and no
data — the form for a site whose condition has one wording and nothing
to locate.
The error envelope — the JSON-RPC error object wymcp answers with: code
from the error-code table row error_type names, message the site's
one-line cause, the caller's request_id as id, and data absent or a
map of structured detail from the closed vocabulary this module's contract
states — never a prose key, never the request.
Types
@type error_type() ::
:parse_error
| :invalid_request
| :method_not_found
| :invalid_params
| :internal_error
| :unsupported_protocol_version
| :header_mismatch
The atom naming one row of the error-code table: the JSON-RPC errors
wymcp answers, each pairing its atom with a {code, message} tuple.
Both error_response arities accept exactly these atoms.
Distinct from telemetry's error_kind, which classifies a tool
error's origin, not a JSON-RPC error.
Functions
The error envelope carrying the error-code table's own message and no
data — the form for a site whose condition has one wording and nothing
to locate.
The error envelope — the JSON-RPC error object wymcp answers with: code
from the error-code table row error_type names, message the site's
one-line cause, the caller's request_id as id, and data absent or a
map of structured detail from the closed vocabulary this module's contract
states — never a prose key, never the request.
opts carries :message and :data, both optional; without :message
the table's own string is sent. Any other key raises: a misspelled
:message would otherwise ship the table's generic string in place of the
site's cause, silently, which is the answer this contract exists to end.
A :message term that is not a binary is rendered rather than refused.
Wymcp.Plugs.Auth hands on a consumer's Wymcp.Auth.authenticate/1
reason as given, term() by contract, while the MCP schema types this
field as a string — so the rendering happens here, once, rather than at
each site that can receive one.