GenMCP.Validator (gen_mcp v2.0.0)

Copy Markdown View Source

Validates and casts decoded JSON-RPC messages into the GenMCP.MCP.V2607 struct vocabulary.

The transport decodes an incoming HTTP body into a plain map, then hands that map to validate_request/1 before any dispatch happens. The validator checks the message against the JSON Schema of the method it names, and on success returns the matching protocol struct (for example a GenMCP.MCP.V2607.CallToolRequest) together with whether the message is a request or a notification. A message whose method is not recognized, or whose body does not conform to the schema, is rejected before it reaches a handler.

The set of recognized methods is the request and notification surface of the 2026-07-28 protocol that the library currently serves. The full JSON Schema root is assembled once at compile time, so validation at runtime is a lookup by method followed by a single JSV.validate/3 call.

This module is used internally by GenMCP.Transport.StreamableHTTP. The single public entry point is validate_request/1.

Summary

Functions

Validates a decoded JSON-RPC message and casts it to its protocol struct.

Functions

validate_request(request)

Validates a decoded JSON-RPC message and casts it to its protocol struct.

The argument is the full message map as decoded from the request body, carrying at least a "method" key, and usually "jsonrpc", "id", and "params". The method selects the schema to validate against, and the rest of the map is the body that gets validated and cast.

On success the call returns {:ok, kind, cast}:

  • kind is :request or :notification, telling the caller how to treat the message.
  • cast is the validated GenMCP.MCP.V2607 struct, with its fields already coerced (atom keys, decoded byte formats, nested structs).

Two errors are possible:

  • {:error, {:unknown_method, method}} when no schema is registered for the method. This is checked before the body, so a map carrying only a "method" is enough to trigger it.
  • {:error, {:invalid_body, jsv_error}} when the method is known but the body fails schema validation. The jsv_error is a JSV.ValidationError whose message explains which property did not conform.

Examples

A tools/list request validates and casts to a GenMCP.MCP.V2607.ListToolsRequest. The body is what a client sends, with the protocol version and client info travelling in _meta as the protocol requires (the cast struct is omitted here because it is deeply nested):

iex> GenMCP.Validator.validate_request(%{
...>   "jsonrpc" => "2.0",
...>   "id" => 1,
...>   "method" => "tools/list",
...>   "params" => %{
...>     "_meta" => %{
...>       "io.modelcontextprotocol/protocolVersion" => "2026-07-28",
...>       "io.modelcontextprotocol/clientCapabilities" => %{},
...>       "io.modelcontextprotocol/clientInfo" => %{
...>         "name" => "my-client",
...>         "version" => "1.0.0"
...>       }
...>     }
...>   }
...> })
{:ok, :request,
%GenMCP.MCP.V2607.ListToolsRequest{
  id: 1,
  params: %GenMCP.MCP.V2607.PaginatedRequestParams{
    _meta: %GenMCP.MCP.V2607.RequestMetaObject{
      "io.modelcontextprotocol/clientCapabilities": %GenMCP.MCP.V2607.ClientCapabilities{
        elicitation: nil,
        experimental: nil,
        extensions: nil,
        roots: nil,
        sampling: nil
      },
      "io.modelcontextprotocol/clientInfo": %GenMCP.MCP.V2607.Implementation{
        description: nil,
        icons: nil,
        name: "my-client",
        title: nil,
        version: "1.0.0",
        websiteUrl: nil
      },
      "io.modelcontextprotocol/logLevel": nil,
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      progressToken: nil
    },
    cursor: nil
  }
}}

An unrecognized method is rejected before its body is even looked at:

iex> GenMCP.Validator.validate_request(%{"method" => "bogus/method"})
{:error, {:unknown_method, "bogus/method"}}