Wymcp.JsonRpc (Wymcp v0.6.2)

View Source

JSON-RPC 2.0 envelopes and MCP protocol schema validation — the wire-format floor both eras share.

success_response/2 and the three error_response arities 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. The four-arity form overrides that message and nothing else — the code still comes from the table — for a condition whose wording has to name what it refused. 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.

Message and output-schema validation

One self-contained MCP protocol schema per era — priv/schema-2025-11-25.json and 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. Both schemas name the same entry point, JSONRPCMessage; validate_mcp_request/2 selects the era's root from the era Wymcp.Plugs.Validate passes it. @external_resource on both paths makes an edit to either schema file recompile this module.

A rejection is answered as a distilled map — a one-line message plus an errors list of %{at:, problems:} entries, one 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. This module distills it on two paths: validate_mcp_request/2 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. All three 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.

Types

error_type()

@type error_type() ::
  :parse_error
  | :invalid_request
  | :method_not_found
  | :invalid_params
  | :internal_error
  | :session_not_found
  | :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. All three 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

error_response(error_type, request_id)

error_response(error_type, request_id, data)

error_response(error_type, request_id, data, message)

The error envelope with a caller-supplied message in place of the error-code table's fixed one.

A fixed message is right where one condition has one wording, which is every row but one. Wymcp.Plugs.HeaderBinding refuses four different headers under three conditions and its message names which and what to do about it, so the wording cannot live in the table — but the code still does, which is what keeps that call site naming the condition and never the number.

success_response(request_id, result)

validate_mcp_request(atom, data)

validate_schema(schema, data)