Conformance #

The conformance suite is 23 executable checks derived from revision 2026-07-28. It runs against a running server — this one, or any other — over stdio, over HTTP, or in-process, and exits non-zero when a requirement is broken.

That last part is the point. It is a gate, not a report.

Running it #

# Against the library, in this process. Fastest; no transport involved.
uv run mcp-devserver conform --strict

# Against a real server process over a real pipe. What an editor does.
uv run mcp-devserver conform --strict \
  --stdio "uv run mcp-devserver serve --transport stdio"

# Against something already running.
uv run mcp-devserver conform --strict \
  --http http://localhost:8080/mcp --token "$MCP_HTTP__BEARER_TOKEN"

# Write a machine-readable report as well.
uv run mcp-devserver conform --report reports/conformance.json
target      stdio (uv run mcp-devserver serve --transport stdio)
checks      23/23 passed
duration    412ms

  ok    C001  [MUST]    server/discover is implemented
  ok    C002  [SHOULD]  results carry serverInfo in _meta
  ok    C003  [SHOULD]  discovery declares a cache lifetime
  ok    C004  [MUST]    every result carries resultType
  ...

CONFORMANT

MUST and SHOULD #

Each check names the level of the requirement it tests. A failed MUST is non-conformance and fails the gate. A failed SHOULD is reported and, by default, does not — a recommendation is not a requirement.

--strict promotes SHOULD failures to gate failures. CI uses it, because this server has no reason to violate a recommendation, so a new violation is a regression here even though it would not be non-conformance in general.

The checks #

Level Requirement
C001 MUST server/discover is implemented and returns supportedVersions and capabilities
C002 SHOULD Results carry io.modelcontextprotocol/serverInfo in _meta
C003 SHOULD Discovery declares ttlMs so a client can cache it
C004 MUST Every result carries a valid resultType
C005 MUST tools/list returns Tool objects with legal names and object input schemas
C006 SHOULD tools/list order is stable across calls
C007 MUST nextCursor pagination pages forward, terminates, and repeats nothing
C008 MUST A cursor the server did not issue is rejected, not ignored
C009 MUST A request without protocolVersion is -32602
C010 MUST A request without clientCapabilities is -32602
C011 MUST An unsupported version is -32022 with data.supported
C012 MUST A legacy initialize is answered, not silently accepted
C013 MUST An unknown method is -32601
C014 MUST Calling an unpublished tool is a protocol error, and not a withdrawn code
C015 MUST A malformed envelope is -32600
C016 MUST No error code invades the reserved range beyond the three defined
C017 MUST A request without an id receives no response at all
C018 MUST A tool failure is a result with isError, not a JSON-RPC error
C019 MUST A successful call returns both content and structuredContent
C020 MUST An argument the schema does not declare is refused
C021 MUST Interleaved requests on one connection do not influence each other
C022 SHOULD A reserved _meta prefix invented by a client is not honoured
C023 MUST traceparent in _meta is accepted, being exempt from the prefix rules

Portability caveat, stated plainly. C018–C020 call tools by name (read_file, project_overview), because the behaviours they check cannot be tested without invoking something. Against a different server those three need different tool names. Everything else is server-independent.

The suite is itself tested #

A conformance suite that has only ever run against a correct server proves nothing about whether it would notice an incorrect one. So tests/conformance/test_conformance_suite.py wraps the client in a mutator that breaks exactly one requirement, and asserts the corresponding check goes red:

Mutation Check that must fail
Strip resultType from every result C004
Strip _meta from every result C002
Answer notifications C017
Relabel every error as -32603 C011
Emit the withdrawn -32002 C016
Convert tool errors into JSON-RPC errors C018
Reverse the tool order on alternate calls C006
Swallow the bad-cursor error and return an empty page C008
Accept initialize C012
Accept a request with no protocol version C009

Plus: a SHOULD failure passes the default gate and fails --strict; a MUST failure fails both; and a check that raises an unexpected exception is recorded as a failure with the exception type in its detail, rather than crashing the run — a JSONDecodeError there means the server wrote something that is not JSON, which is exactly what one wants to see.

Where it runs #

in-process stdio HTTP
pytest -m conformance
CI, conformance job
CI, Container job ✓ (inside the built image)
python tasks.py conform
python tasks.py conform-stdio

Running it inside the container is what proves the shipped artifact speaks the protocol, rather than that the source does. Those are different claims, and the difference is where container defects live.

The report #

{
  "target": "stdio (uv run mcp-devserver serve --transport stdio)",
  "started_at": "2026-09-08T00:14:02Z",
  "duration_ms": 412.7,
  "strict": true,
  "total": 23,
  "passed": 23,
  "must_failures": 0,
  "should_failures": 0,
  "green": true,
  "checks": [
    { "id": "C001", "level": "MUST", "title": "server/discover is implemented",
      "passed": true, "duration_ms": 1.9, "detail": "" }
  ]
}

CI uploads it as an artifact so a change in behaviour is visible in the diff between two runs, not only in a pass or a fail.

Conforming your own server #

The suite imports nothing from this server's implementation, so:

uvx --from mcp-devserver mcp-devserver conform \
  --stdio "your-server --stdio" --strict

C018–C020 will fail unless your server publishes tools with those names; every other check applies to any 2026-07-28 server.