From 637d995eb49488624d5f26b8634a1aa4a0fa38cb Mon Sep 17 00:00:00 2001
From: Dariusz Jedrzejczyk <2554306+chemicL@users.noreply.github.com>
Date: Mon, 9 Nov 2026 23:46:53 +0100
Subject: [PATCH 1/2] Add a modern client for MCP 2026-07-28
The latest protocol revision introduced a stateless incarnation of the
protocol. It requires no initialization handshake or session
management, and it doesn't allow server-to-client requests.
An instance of the modern client is meant to stand for one user. It
does no I/O when built and can share its transport with other clients,
so an application that handles authorization creates one per user over
a single HTTP transport. Identity isn't passed to client methods; the
caller's McpTransportContext reaches the transport's request customizer
and authorization error handler. The tool catalog is therefore that
user's view of the server. The client also uses it for x-mcp-header
mirroring.
Clients are built with McpClient.async(transport) or
McpClient.sync(transport), and each operation goes through an immutable
request spec, e.g. client.tool("create_issue").arguments(args).call().
Timeouts, progress, _meta and elicitation handlers are set on the
builder and can be overridden per call on the spec.
The client takes care of what the revision requires:
- the _meta block with version and capabilities on every request
- version renegotiation
- the multi round-trip loop for input_required results (with callOnce()
for callers that run it themselves)
- the Mcp-* and Mcp-Param-* headers, with one retry on -32020
- subscriptions/listen streams that report which parts of the filter
the server doesn't honour
A round timeout bounds each exchange but not the time a user spends
answering; a call timeout is optional.
The sync client runs the loop and its handlers on the caller's thread.
Transports: Streamable HTTP on the JDK HttpClient, and stdio. Sampling,
roots, logging and extensions are left out for now.
On the server side, the HTTP and stdio wire constants are shared with
the client, input requests are typed by method and mode, and the listen
filter is open so features can honour extension fields.
InputResponses.get and RequestStateCodec.open return null instead of an
empty Optional.
A conformance client in conformance-tests/client-modern passes the
non-auth 2026-07-28 client scenarios.
Signed-off-by: Dariusz Jedrzejczyk <2554306+chemicL@users.noreply.github.com>
---
.github/workflows/conformance.yml | 39 +
conformance-tests/VALIDATION_RESULTS.md | 26 +
conformance-tests/client-modern/README.md | 41 +
conformance-tests/client-modern/pom.xml | 89 ++
.../modern/ModernConformanceClient.java | 221 +++++
.../src/main/resources/logback.xml | 13 +
conformance-tests/pom.xml | 1 +
.../modern/ModernConformanceServlet.java | 8 +-
docs/server-modern.md | 56 +-
.../transport/ResponseBodyHandlers.java | 41 +-
.../modern/McpException.java | 8 +-
.../modelcontextprotocol/modern/McpHttp.java | 81 ++
.../modern/McpSchema.java | 561 ++++++++++-
.../modelcontextprotocol/modern/McpStdio.java | 95 ++
.../modern/ToolParamHeaderAnnotations.java | 108 ++-
.../modern/client/AsyncClientRequester.java | 155 +++
.../modern/client/CallSettings.java | 80 ++
.../modern/client/ClientRequestFactory.java | 96 ++
.../modern/client/Deadlines.java | 30 +
.../modern/client/DiscoverCache.java | 42 +
.../modern/client/InputHandlers.java | 167 ++++
.../modern/client/JsonRpcRequestSender.java | 301 ++++++
.../modern/client/Listings.java | 74 ++
.../modern/client/McpAsyncClient.java | 877 +++++++++++++++++
.../client/McpAsyncElicitationHandler.java | 36 +
.../modern/client/McpClient.java | 318 +++++++
.../McpInputHandlerMissingException.java | 51 +
.../McpInputRoundsExceededException.java | 34 +
.../client/McpInvalidResultException.java | 38 +
.../McpMissingClientCapabilityException.java | 35 +
.../client/McpResponseLostException.java | 27 +
.../client/McpSubscriptionLostException.java | 19 +
.../modern/client/McpSyncClient.java | 887 ++++++++++++++++++
.../client/McpSyncElicitationHandler.java | 36 +
.../client/McpSyncSubscriptionListener.java | 48 +
.../modern/client/McpTimeoutException.java | 57 ++
.../modern/client/MultiRoundTripRequest.java | 220 +++++
.../modern/client/RequestMeta.java | 80 ++
.../modern/client/ResultDecoding.java | 72 ++
.../modern/client/SubscriptionEvent.java | 192 ++++
.../modern/client/SubscriptionStream.java | 166 ++++
.../modern/client/SyncClientRequester.java | 317 +++++++
.../modern/client/ToolCatalog.java | 258 +++++
.../modern/client/ToolParamHeaders.java | 103 ++
.../transport/HttpClientMcpTransport.java | 404 ++++++++
.../client/transport/McpClientTransport.java | 52 +
.../client/transport/McpHeaderMirror.java | 84 ++
.../McpHttpAuthorizationErrorHandler.java | 92 ++
.../client/transport/McpHttpRequest.java | 98 ++
.../transport/McpHttpRequestCustomizer.java | 59 ++
.../transport/McpHttpRequestHeaders.java | 105 +++
.../client/transport/McpHttpResponseInfo.java | 49 +
.../transport/McpHttpStatusException.java | 59 ++
.../client/transport/McpTransportRequest.java | 203 ++++
.../transport/StdioMcpClientTransport.java | 521 ++++++++++
.../modern/server/HmacRequestStateCodec.java | 22 +-
.../modern/server/InputResponses.java | 12 +-
.../modern/server/McpFeature.java | 15 +
.../modern/server/McpParamHeaders.java | 3 +-
.../modern/server/McpServer.java | 9 +-
.../modern/server/RequestStateCodec.java | 6 +-
.../modern/server/SubscriptionsFeature.java | 125 ++-
.../modern/server/feature/ServerChange.java | 82 +-
.../transport/HttpServletMcpTransport.java | 20 +-
.../server/transport/McpHeaderDecoder.java | 12 +-
.../server/transport/StdioMcpTransport.java | 52 +-
.../modern/SubscriptionFilterTests.java | 106 +++
.../client/McpClientExceptionsTests.java | 63 ++
.../modern/client/RequestMetaTests.java | 104 ++
.../modern/client/SubscriptionEventTests.java | 27 +
.../transport/McpHeaderMirrorTests.java | 91 ++
.../transport/McpHttpRequestHeadersTests.java | 81 ++
.../transport/McpTransportRequestTests.java | 93 ++
.../modern/McpSchemaSerializationTests.java | 153 ++-
.../ToolParamHeaderAnnotationsTests.java | 101 +-
.../modern/client/ClientRequesterTests.java | 116 +++
.../modern/client/CollectingListener.java | 39 +
...ttpClientMcpTransportIntegrationTests.java | 574 ++++++++++++
.../modern/client/InputHandlersTests.java | 203 ++++
.../modern/client/IntegrationTestServer.java | 369 ++++++++
.../client/McpAsyncClientInputTests.java | 463 +++++++++
.../McpAsyncClientIntegrationTests.java | 146 +++
.../client/McpAsyncClientListenTests.java | 444 +++++++++
.../client/McpAsyncClientResponseTests.java | 356 +++++++
.../modern/client/McpAsyncClientTests.java | 509 ++++++++++
.../McpAsyncClientToolCatalogTests.java | 461 +++++++++
.../client/McpSyncClientIntegrationTests.java | 195 ++++
.../modern/client/McpSyncClientTests.java | 676 +++++++++++++
.../modern/client/MockMcpClientTransport.java | 92 ++
.../client/MultiRoundTripRequestTests.java | 216 +++++
.../modern/client/RequestMetaServerTests.java | 191 ++++
.../client/StdioMcpClientTransportTests.java | 351 +++++++
.../modern/client/StdioTestServer.java | 91 ++
.../modern/client/TestChangePublisher.java | 97 ++
.../modern/client/ToolCatalogTests.java | 85 ++
.../server/HmacRequestStateCodecTests.java | 12 +-
.../server/SubscriptionsFeatureTests.java | 184 +++-
97 files changed, 14750 insertions(+), 227 deletions(-)
create mode 100644 conformance-tests/client-modern/README.md
create mode 100644 conformance-tests/client-modern/pom.xml
create mode 100644 conformance-tests/client-modern/src/main/java/io/modelcontextprotocol/conformance/client/modern/ModernConformanceClient.java
create mode 100644 conformance-tests/client-modern/src/main/resources/logback.xml
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/McpHttp.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/McpStdio.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/AsyncClientRequester.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/CallSettings.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/ClientRequestFactory.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/Deadlines.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/DiscoverCache.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/InputHandlers.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/JsonRpcRequestSender.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/Listings.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpAsyncClient.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpAsyncElicitationHandler.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpClient.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpInputHandlerMissingException.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpInputRoundsExceededException.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpInvalidResultException.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpMissingClientCapabilityException.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpResponseLostException.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpSubscriptionLostException.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpSyncClient.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpSyncElicitationHandler.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpSyncSubscriptionListener.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/McpTimeoutException.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/MultiRoundTripRequest.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/RequestMeta.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/ResultDecoding.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/SubscriptionEvent.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/SubscriptionStream.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/SyncClientRequester.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/ToolCatalog.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/ToolParamHeaders.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/HttpClientMcpTransport.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpClientTransport.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpHeaderMirror.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpHttpAuthorizationErrorHandler.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpHttpRequest.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpHttpRequestCustomizer.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpHttpRequestHeaders.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpHttpResponseInfo.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpHttpStatusException.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/McpTransportRequest.java
create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/modern/client/transport/StdioMcpClientTransport.java
create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/modern/SubscriptionFilterTests.java
create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/modern/client/McpClientExceptionsTests.java
create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/modern/client/RequestMetaTests.java
create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/modern/client/SubscriptionEventTests.java
create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/modern/client/transport/McpHeaderMirrorTests.java
create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/modern/client/transport/McpHttpRequestHeadersTests.java
create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/modern/client/transport/McpTransportRequestTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/ClientRequesterTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/CollectingListener.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/HttpClientMcpTransportIntegrationTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/InputHandlersTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/IntegrationTestServer.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/McpAsyncClientInputTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/McpAsyncClientIntegrationTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/McpAsyncClientListenTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/McpAsyncClientResponseTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/McpAsyncClientTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/McpAsyncClientToolCatalogTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/McpSyncClientIntegrationTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/McpSyncClientTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/MockMcpClientTransport.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/MultiRoundTripRequestTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/RequestMetaServerTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/StdioMcpClientTransportTests.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/StdioTestServer.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/TestChangePublisher.java
create mode 100644 mcp-test/src/test/java/io/modelcontextprotocol/modern/client/ToolCatalogTests.java
diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml
index 7014ada8a..da194df85 100644
--- a/.github/workflows/conformance.yml
+++ b/.github/workflows/conformance.yml
@@ -107,6 +107,45 @@ jobs:
--spec-version 2025-11-25 \
--expected-failures ./conformance-tests/conformance-baseline.yml
+ modern-client:
+ name: Modern Client Conformance (2026-07-28)
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ scenario:
+ - tools_call
+ - request-metadata
+ - sep-2322-client-request-state
+ - http-standard-headers
+ - http-custom-headers
+ - http-invalid-tool-headers
+ - json-schema-ref-no-deref
+ - json-schema-2020-12-preservation
+ steps:
+ - uses: actions/checkout@v7
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v6
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Build client
+ run: ./mvnw clean install -DskipTests
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+
+ - name: Run conformance test
+ run: |
+ npx --yes "$CONFORMANCE" client \
+ --command 'java -jar conformance-tests/client-modern/target/client-modern-*-SNAPSHOT.jar' \
+ --scenario ${{ matrix.scenario }} \
+ --spec-version 2026-07-28
+
auth:
name: Auth Conformance
runs-on: ubuntu-latest
diff --git a/conformance-tests/VALIDATION_RESULTS.md b/conformance-tests/VALIDATION_RESULTS.md
index 21138fe2f..3a998233c 100644
--- a/conformance-tests/VALIDATION_RESULTS.md
+++ b/conformance-tests/VALIDATION_RESULTS.md
@@ -9,6 +9,7 @@ Last validated: **2026-10-02** against conformance suite
## Summary
**Modern Server Tests (2026-07-28 requirements):** 37/37 required scenarios passed (`server-servlet-modern`)
+**Modern Client Tests (2026-07-28):** 8/8 non-auth client scenarios passed, 62/62 checks (`client-modern`)
**Server Tests (active suite):** 73/73 checks passed (31 scenarios, 100%)
**Server Tests (SEP-1613 `json-schema-2020-12`):** 5/5 checks passed (SEP-2106 checks skipped — post-2025-11-25 spec additions)
**Client Tests:** 3/4 scenarios passed; `sse-retry` fails (tracked in `conformance-baseline.yml`)
@@ -44,6 +45,31 @@ in the anchor release (`0.2.0-alpha.10`) as `not_scored`. Pending scenarios are
`http-custom-header-server-validation` (10/10, SEP-2243 custom headers, against the `test_x_mcp_header` tool)
- **Failing — tasks extension (`tasks-*`, SEP-2663):** not implemented
+## Modern Client Test Results (2026-07-28)
+
+The `client-modern` module drives `io.modelcontextprotocol.modern.client.McpSyncClient` over
+`HttpClientMcpTransport`. Last validated on **2026-11-05** with `--spec-version 2026-07-28`.
+
+### Passing (8/8 scenarios, 62/62 checks)
+
+- **tools_call (2/2):** tool discovery and invocation
+- **request-metadata (6/6, SEP-2575):** `_meta` with version, capabilities and client info on every
+ request, `MCP-Protocol-Version` matching `_meta`, retry after `-32022`
+- **sep-2322-client-request-state (5/5, SEP-2322):** `requestState` echoed byte for byte and omitted
+ when absent, a new JSON-RPC id per retry, no MRTR state on other requests, missing `resultType` as
+ `complete`
+- **http-standard-headers (9/9, SEP-2243):** `Mcp-Method` and `Mcp-Name` on tools, resources and prompts
+- **http-custom-headers (18/18, SEP-2243):** `Mcp-Param-*` mirroring, Base64 encoding of unsafe values,
+ omitted `null` arguments
+- **http-invalid-tool-headers (12/12, SEP-2243):** tools with invalid `x-mcp-header` annotations left out,
+ valid tools kept
+- **json-schema-ref-no-deref (1/1, SEP-2106)** and **json-schema-2020-12-preservation (9/9, SEP-1613)**
+
+### Not covered
+
+- **auth/\*:** the modern client has no OAuth support of its own; authorization plugs in through
+ `McpHttpRequestCustomizer` and `McpHttpAuthorizationErrorHandler`.
+
## Server Test Results
### Active Suite — Passing (31/31 scenarios, 73/73 checks)
diff --git a/conformance-tests/client-modern/README.md b/conformance-tests/client-modern/README.md
new file mode 100644
index 000000000..c2cbfcecf
--- /dev/null
+++ b/conformance-tests/client-modern/README.md
@@ -0,0 +1,41 @@
+# MCP Conformance Tests - Modern Client
+
+Conformance client for the stateless **2026-07-28** MCP revision, built on the synchronous
+`io.modelcontextprotocol.modern.client.McpSyncClient` over `HttpClientMcpTransport`. The legacy
+(2025-11-25) clients live in [`client-jdk-http-client`](../client-jdk-http-client) and
+[`client-spring-http-client`](../client-spring-http-client).
+
+Each scenario uses the client as an application would: list, call, read, get. The protocol details the
+suite checks are the client's job, so the scenarios contain none of them.
+
+## Scenarios
+
+| Scenario | What the client does |
+|---|---|
+| `tools_call` | Lists the tools and calls the first one |
+| `request-metadata` | `server/discover`, then `tools/list`; the server rejects the first request with `-32022` |
+| `sep-2322-client-request-state` | Continues one call round by round with `callOnce()`, calling another tool in between, and lets the client run the others with its elicitation handler |
+| `http-standard-headers` | Lists and uses every tool, resource and prompt |
+| `http-custom-headers` | Calls the tools named in `MCP_CONFORMANCE_CONTEXT` with their arguments |
+| `http-invalid-tool-headers` | Calls every listed tool; tools with invalid `x-mcp-header` annotations aren't listed |
+| `json-schema-ref-no-deref` | Lists the tools |
+| `json-schema-2020-12-preservation` | Sends the listed input schema back through `json_schema_echo` |
+
+The `auth/*` scenarios of the 2026-07-28 requirement set aren't covered: the modern client has no OAuth
+support of its own, only the HTTP hooks an authorization integration plugs into.
+
+## Running
+
+```bash
+./mvnw clean install -DskipTests
+
+npx @modelcontextprotocol/conformance@0.2.0-alpha.12 client \
+ --command "java -jar conformance-tests/client-modern/target/client-modern-2.2.0-SNAPSHOT.jar" \
+ --scenario http-custom-headers \
+ --spec-version 2026-07-28
+```
+
+The client reads the scenario from `MCP_CONFORMANCE_SCENARIO`, optional scenario data from
+`MCP_CONFORMANCE_CONTEXT`, and the server URL from its last argument. It logs to stderr.
+
+See [VALIDATION_RESULTS.md](../VALIDATION_RESULTS.md) for the latest results.
diff --git a/conformance-tests/client-modern/pom.xml b/conformance-tests/client-modern/pom.xml
new file mode 100644
index 000000000..a31508dd6
--- /dev/null
+++ b/conformance-tests/client-modern/pom.xml
@@ -0,0 +1,89 @@
+
+
+ * The conformance suite starts it once per scenario, with the scenario's name in + * {@code MCP_CONFORMANCE_SCENARIO}, optional scenario data as JSON in + * {@code MCP_CONFORMANCE_CONTEXT}, and the server URL as the last argument. Each scenario + * uses the client the way an application would; the protocol details the suite checks + * (the {@code _meta} block, version negotiation, headers, multi round-trip requests) are + * the client's job. + *
+ * Usage: {@code ModernConformanceClient