java-sdk/conformance-tests/client-jdk-http-client
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Dariusz Jędrzejczyk 22a0efa577 feat!: enforce required MCP spec fields in McpSchema; lenient wire deserialization (#928)
Every wire-serialized record in McpSchema now validates spec-required
fields at construction time. Wire deserialization is intentionally
lenient: a missing required field is replaced with a documented default
and a WARN is logged, instead of failing the parse. A required-first
builder convention is introduced across the schema so it is no longer
possible to obtain a builder that is missing required state.

Required-field guards
---------------------
Every wire record's compact constructor asserts non-null (and non-empty
for String identifiers like name, uri, uriTemplate, version) on its
spec-required components. Passing null now throws
IllegalArgumentException at construction time instead of silently
producing invalid JSON via @JsonInclude(NON_ABSENT). This applies to
the JSON-RPC envelopes, lifecycle types, resource/prompt/tool requests
and results, sampling and elicitation, content records, root, complete,
logging and progress notifications, and the two CompleteReference
implementations. See MIGRATION-2.0.md for the full list.

Lenient wire deserialization
----------------------------
For each of those records (except JSONRPCResponse.JSONRPCError, which
still fails fast) a @JsonCreator static `fromJson` factory substitutes
a documented default for any absent required field — "" for strings,
[] for collections, {} for maps, 0 / 0.0 for numerics, INFO for
LoggingLevel, USER for SamplingMessage.role, ASSISTANT for
CreateMessageResult.role, CANCEL for ElicitResult.action, {values=[]}
for CompleteResult.completion — and logs a WARN naming the field and
the value used. Application code can still observe a malformed message
and react, but the SDK no longer halts the conversation.

Builder convention
------------------
Records that have a builder gain a required-first factory method
`builder(req1, req2, …)`; setters for required fields are removed from
the builder so it cannot be left in an invalid state. Existing no-arg
`builder()` factories and required-field setters are kept where source
compatibility demands it but are marked @Deprecated. New builders are
also added for several records that previously had none (ProgressNotification,
JSONRPCError, CompleteRequest, list/result types, content records, ...).

JSON-RPC envelope ergonomics
----------------------------
Previously every JSON-RPC envelope had to be constructed via the
canonical record constructor and the literal "2.0" string had to be
threaded through every call site, e.g.

    new JSONRPCRequest("2.0", "tools/call", id, params)
    new JSONRPCResponse("2.0", id, result, null)
    new JSONRPCResponse("2.0", id, null, new JSONRPCError(code, message, null))

Now:

    new JSONRPCRequest("tools/call", id, params)              // jsonrpc defaulted
    new JSONRPCNotification("notifications/initialized")       // params optional
    JSONRPCResponse.result(id, result)                        // factory
    JSONRPCResponse.error(id, new JSONRPCError(code, message)) // 2-arg error

JSONRPCResponse's compact constructor additionally enforces the
JSON-RPC invariant that exactly one of `result` / `error` is set —
previously the SDK could build envelopes that violated the protocol.

CompleteReference changes
-------------------------
- PromptReference.equals/hashCode now key on `name` only (previously
  derived from identifier()+type()). Two refs with the same name but
  different titles now compare equal — code using PromptReference as a
  map/set key should be audited.
- PromptReference's compact constructor pins `type` to "ref/prompt"
  and logs a WARN if the caller supplies a different non-null value.
  The legacy two-arg `PromptReference(String type, String name)`
  constructor remains @Deprecated.
- ResourceReference's record components are reduced from (type, uri)
  to (uri) — positional construction breaks. The legacy
  `ResourceReference(String type, String uri)` constructor stays
  @Deprecated and ignores `type`.
- CompleteReference.identifier() is @Deprecated and now returns null
  via a default method on the interface.

Tests / refactor
----------------
Conformance harness, integration tests, sample apps, and internal
callers were migrated to the new builder factories and convenience
constructors. No semantic changes to client/server runtime behaviour
beyond the McpSchema changes above.

Docs
----
- MIGRATION-2.0.md: required-field section now covers the broader
  record set and the lenient-deserialization behaviour; PromptReference
  WARN behaviour and ResourceReference component reduction are
  documented; the JSON-RPC envelope section is rewritten to compare the
  pre-2.0 surface with the new one.
- CONTRIBUTING.md: the "Evolving wire-serialized records" recipe is
  split into two cases — adding a new optional field (existing rules)
  and adding/maintaining a spec-required field (new rules covering
  Assert in compact constructor, @JsonCreator fromJson with defaults
  and WARN, required-first builder factory).

Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
2026-05-04 21:01:46 +02:00
..
src/main feat!: enforce required MCP spec fields in McpSchema; lenient wire deserialization (#928) 2026-05-04 21:01:46 +02:00
pom.xml Next development version 2026-03-13 13:31:03 +01:00
README.md Fix instructions for running conformance tests 2026-03-02 11:56:39 +01:00

MCP Conformance Tests - JDK HTTP Client

This module provides a conformance test client implementation for the Java MCP SDK using the JDK HTTP Client with Streamable HTTP transport.

Overview

The conformance test client is designed to work with the MCP Conformance Test Framework. It validates that the Java MCP SDK client properly implements the MCP specification.

Architecture

The client reads test scenarios from environment variables and accepts the server URL as a command-line argument, following the conformance framework's conventions:

  • MCP_CONFORMANCE_SCENARIO: Environment variable specifying which test scenario to run
  • Server URL: Passed as the last command-line argument

Supported Scenarios

Currently implemented scenarios:

  • initialize: Tests the MCP client initialization handshake only

    • ✅ Validates protocol version negotiation
    • ✅ Validates clientInfo (name and version)
    • ✅ Validates proper handling of server capabilities
    • Does NOT call any tools or perform additional operations
  • tools_call: Tests tool discovery and invocation

    • ✅ Initializes the client
    • ✅ Lists available tools from the server
    • ✅ Calls the add_numbers tool with test arguments (a=5, b=3)
    • ✅ Validates the tool result
  • elicitation-sep1034-client-defaults: Tests client applies default values for omitted elicitation fields (SEP-1034)

    • ✅ Initializes the client
    • ✅ Lists available tools from the server
    • ✅ Calls the test_client_elicitation_defaults tool
    • ✅ Validates that the client properly applies default values from JSON schema to elicitation responses (5/5 checks pass)
  • sse-retry: Tests client respects SSE retry field timing and reconnects properly (SEP-1699)

    • ⚠️ Initializes the client
    • ⚠️ Lists available tools from the server
    • ⚠️ Calls the test_reconnection tool which triggers SSE stream closure
    • ✅ Client reconnects after stream closure (PASSING)
    • ❌ Client does not respect retry timing (FAILING)
    • ⚠️ Client does not send Last-Event-ID header (WARNING - SHOULD requirement)

Building

Build the executable JAR:

cd conformance-tests/client-jdk-http-client
../../mvnw clean package -DskipTests

This creates an executable JAR at:

target/client-jdk-http-client-1.1.0-SNAPSHOT.jar

Running Tests

Using the Conformance Framework

Run a single scenario:

npx @modelcontextprotocol/conformance client \
  --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \
  --scenario initialize

npx @modelcontextprotocol/conformance client \
  --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \
  --scenario tools_call

npx @modelcontextprotocol/conformance client \
  --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \
  --scenario elicitation-sep1034-client-defaults

npx @modelcontextprotocol/conformance client \
  --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \
  --scenario sse-retry

Run with verbose output:

npx @modelcontextprotocol/conformance client \
  --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \
  --scenario initialize \
  --verbose

Manual Testing

You can also run the client manually if you have a test server:

export MCP_CONFORMANCE_SCENARIO=initialize
java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar http://localhost:3000/mcp

Test Results

The conformance framework generates test results showing:

Current Status (3/4 scenarios passing):

  • ✅ initialize: 1/1 checks passed
  • ✅ tools_call: 1/1 checks passed
  • ✅ elicitation-sep1034-client-defaults: 5/5 checks passed
  • ⚠️ sse-retry: 1/2 checks passed, 1 warning

Test result files are generated in results/<scenario>-<timestamp>/:

  • checks.json: Array of conformance check results with pass/fail status
  • stdout.txt: Client stdout output
  • stderr.txt: Client stderr output

Known Issue: SSE Retry Handling

The sse-retry scenario currently fails because:

  1. The client treats the SSE retry: field as invalid instead of parsing it
  2. The client does not implement retry timing (reconnects immediately)
  3. The client does not send the Last-Event-ID header on reconnection

This is a known limitation in the HttpClientStreamableHttpTransport implementation.

Next Steps

Future enhancements:

  • Fix SSE retry field handling (SEP-1699) to properly parse and respect retry timing
  • Implement Last-Event-ID header on reconnection for resumability
  • Add auth scenarios (currently excluded as per requirements)
  • Implement a comprehensive "everything-client" pattern
  • Add to CI/CD pipeline
  • Create expected-failures baseline for known issues