The trace format
A trace is JSON Lines: one event per line. Each event carries a
capture-assigned seq (the only ordering authority — never inferred later), a
direction (client-to-server / server-to-client), a transport, and a
kind:
messageevents hold the JSON-RPC payload (as parsed JSON: every value, not its whitespace, member order, or the spelling of its numbers);httpevents record the conformance-relevant headers, a response’s status, and a client request’smethod— Streamable HTTP binds different obligations toPOST,GET, andDELETE, so a clause addressed to one of them is judged only where the recording says which it was; andlifecycleevents mark transport open/close.
The format is published as a JSON Schema (draft 2020-12),
trace-event.schema.json,
for recorders written in any language. It states every rule the validator’s
reader applies to one record — a test runs both over every corpus record and one
violation of each rule — and says where JSON Schema cannot follow: the
document-level rules (one record per line, seq strictly increasing), and
integers written as 1.0, which the reader refuses. Unknown members are ignored,
so producers may add their own.
The redaction rules that keep credential-bearing headers out of a trace by
construction are in
05-security-model.md.
One worked example
The example below is embedded verbatim from the
README, where
a test (readme_examples.rs) pins it to the validator’s real output — so what
you read here cannot drift from what the tool actually produces. It is a session
that reuses a request ID, and the verdict that catches it:
{"seq":0,"direction":"client-to-server","transport":"stdio","kind":"message","payload":{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-host","version":"1.0.0"}}}}
{"seq":1,"direction":"server-to-client","transport":"stdio","kind":"message","payload":{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{}},"serverInfo":{"name":"my-server","version":"1.0.0"}}}}
{"seq":2,"direction":"client-to-server","transport":"stdio","kind":"message","payload":{"jsonrpc":"2.0","method":"notifications/initialized"}}
{"seq":3,"direction":"client-to-server","transport":"stdio","kind":"message","payload":{"jsonrpc":"2.0","id":1,"method":"tools/list"}}
and the validator answers with the violated clause, addressed to the offending event:
FAIL BASE-003 (MUST NOT)
seq 3: request "tools/list" reuses id 1, already used by the same party at seq 0
spec: "The request ID MUST NOT have been previously used by the requestor within the same session."
see: https://modelcontextprotocol.io/specification/2025-11-25/basic#requests
totals: 17 pass, 1 fail, 0 warn, 86 excluded, 0 unsupported, 6 not applicable, 32 not observed
verdict: fail
The totals line distinguishes the verdict’s components, and two of them are
not passes. The not-applicable rows are capability-gated requirements this
session never negotiated (the resources and prompts clauses); the
not-observed rows are clauses whose subject matter never appeared at all —
nothing was paginated, no binary content was sent, no error was returned. A
four-line session earns 17 passes, not a hundred. See
Architecture for why that distinction is load-bearing.