Optional JSV schema validation

This package provides Snodo.Schema.Validator.JSV, an opt-in implementation of the core validator boundary using JSV. It adds no runtime dependencies to the snodo core and is not an MCP protocol extension. Basic remains a useful, explicitly partial dependency-free option; installing this package does not silently change any server's validation policy.

Use from an application

Add snodo_jsv next to snodo:

{:snodo_jsv, "~> 0.1.0"}

Then select the backend on the server:

defmodule MyApp.MCPServer do
use Snodo.Server,
name: "my-application",
version: "0.1.0",
protocols: [Snodo.Protocol.V2026_07_28],
schema_validator: Snodo.Schema.Validator.JSV
tool(MyApp.Search)
end

Snodo.Router.dispatch/5 also accepts schema_validator: Snodo.Schema.Validator.JSV. Input schemas and output schemas remain the original JSON-decoded maps. The backend validates data but never inserts defaults, converts keys to atoms, returns cast values, or rewrites the tool definitions advertised by the server.

Policy and scope

The backend brings the broader 2020-12 validation vocabulary (composition, conditionals, references, evaluated-property/item tracking, and more) through a bounded application policy. It is not an unqualified whole-protocol conformance claim. Elicitation's protocol-specific restricted form schema remains a separate boundary.

Compiling a fixed catalog once

The default validate/2 compiles each supplied schema on each call. This is a correctness-first path with no global state or unbounded cache. For a fixed catalog, an application can retain compiled roots in an ordinary module:

defmodule MyApp.SchemaValidator do
@behaviour Snodo.Schema.Validator
alias Snodo.Schema.Validator.JSV, as: Backend
@schema MyApp.Search.input_schema()
@compiled case Backend.compile(@schema, formats: :assertion) do
{:ok, compiled} -> compiled
{:error, error} -> raise error
end
@impl true
def validate(instance, schema) when schema === @schema,
do: Backend.validate_compiled(instance, @compiled)
def validate(instance, schema), do: Backend.validate(instance, schema)
end

Use schema_validator: MyApp.SchemaValidator on the server. Compile all input and output schemas that need the same policy; the fallback above deliberately uses the default policy. compile/1 and compile/2 return {:ok, compiled} | {:error, BuildError.t()}. Compiled values are opaque immutable roots; do not fabricate or modify them. Recompile them when schemas or backend versions change. A catalog cache, if later needed, must have application-owned bounds and a lifecycle.

Validation of attacker-controlled data is still work: put input-size, execution, and concurrency limits at the transport/application boundary. This package has no independent validation timeout and is not an untrusted-schema sandbox.

Version choice and alternatives

The package allows jsv ~> 0.22. The checked-in lock resolves 0.25.0, and the tests pass on both 0.22.0 and 0.25.0. JSV supplies runtime compilation and documents 2020-12/Draft 7 support, vocabularies, and per-call casting controls. Its own tests pin the JSON Schema test suite; this package's tests verify the adapter policy and router behavior, not a new independent rerun of that entire upstream suite.

JSONSchex was a credible alternative, but its documented unresolved custom meta-schema fallback calls for additional fail-closed admission. Exonerate's compile-time code generation and documented dialect limitations are less suited to this dynamic boundary. ExJsonSchema's documented Draft 4/6/7 support does not meet the 2020-12 requirement. These are reasons for this integration choice, not claims that the alternatives cannot suit other applications.

Primary references checked on 2026-09-14:

Verification

From this package directory:

ERL_FLAGS='+S 4:4' mix deps.get
ERL_FLAGS='+S 4:4' mix quality
ERL_FLAGS='+S 4:4' mix quality.types

mix quality checks formatting, compilation warnings, strict Credo, and tests. The tests cover extended keywords, offline references, recursive/dynamic refs, boolean schemas, explicit Draft 7, malformed schema admission, format policy, casting/module-hook rejection, exact handler arguments and advertised schemas, and correct input/output/configuration error routing. Core tests and official client/schema evidence run in their separate lanes.