Skip to content

Commit c531bb8

Browse files
authored
Add a modern client for MCP 2026-07-28 (#1325)
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 customizers and authorization error handler, on every attempt. 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 The client keeps its tool listing for the listing's ttlMs, as that user's view of the server, and uses it for x-mcp-header mirroring. listTools().list() serves the kept listing while it's fresh, and skipCache() always asks the server. A listen stream that reports tool changes drops the kept listing. 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. The client's own failures (missing input handler, too many input rounds, invalid result, lost response, timeout, lost listen stream, HTTP error status) extend McpClientException. Errors the server answered remain McpException, so catching the two covers every failure of a call. Transports: Streamable HTTP on the JDK HttpClient, and stdio. Sampling, roots, logging and extensions are left out for now. Synchronous HTTP hooks (fromSync) run on boundedElastic, so they may block, for example to fetch a token. McpHttpRequest and McpHttpResponseInfo can be created outside the SDK, so a custom HTTP transport can call the request customizers and the authorization error handler. 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. Part of #1011 Signed-off-by: Dariusz Jedrzejczyk <2554306+chemicL@users.noreply.github.com>
1 parent 33e1581 commit c531bb8

100 files changed

Lines changed: 15492 additions & 227 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/conformance.yml‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,45 @@ jobs:
107107
--spec-version 2025-11-25 \
108108
--expected-failures ./conformance-tests/conformance-baseline.yml
109109
110+
modern-client:
111+
name: Modern Client Conformance (2026-07-28)
112+
runs-on: ubuntu-latest
113+
strategy:
114+
matrix:
115+
scenario:
116+
- tools_call
117+
- request-metadata
118+
- sep-2322-client-request-state
119+
- http-standard-headers
120+
- http-custom-headers
121+
- http-invalid-tool-headers
122+
- json-schema-ref-no-deref
123+
- json-schema-2020-12-preservation
124+
steps:
125+
- uses: actions/checkout@v7
126+
127+
- name: Set up JDK 17
128+
uses: actions/setup-java@v6
129+
with:
130+
java-version: '17'
131+
distribution: 'temurin'
132+
cache: 'maven'
133+
134+
- name: Build client
135+
run: ./mvnw clean install -DskipTests
136+
137+
- name: Set up Node.js
138+
uses: actions/setup-node@v4
139+
with:
140+
node-version: '22'
141+
142+
- name: Run conformance test
143+
run: |
144+
npx --yes "$CONFORMANCE" client \
145+
--command 'java -jar conformance-tests/client-modern/target/client-modern-*-SNAPSHOT.jar' \
146+
--scenario ${{ matrix.scenario }} \
147+
--spec-version 2026-07-28
148+
110149
auth:
111150
name: Auth Conformance
112151
runs-on: ubuntu-latest

‎conformance-tests/VALIDATION_RESULTS.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Last validated: **2026-10-02** against conformance suite
99
## Summary
1010

1111
**Modern Server Tests (2026-07-28 requirements):** 37/37 required scenarios passed (`server-servlet-modern`)
12+
**Modern Client Tests (2026-07-28):** 8/8 non-auth client scenarios passed, 62/62 checks (`client-modern`)
1213
**Server Tests (active suite):** 73/73 checks passed (31 scenarios, 100%)
1314
**Server Tests (SEP-1613 `json-schema-2020-12`):** 5/5 checks passed (SEP-2106 checks skipped — post-2025-11-25 spec additions)
1415
**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
4445
`http-custom-header-server-validation` (10/10, SEP-2243 custom headers, against the `test_x_mcp_header` tool)
4546
- **Failing — tasks extension (`tasks-*`, SEP-2663):** not implemented
4647

48+
## Modern Client Test Results (2026-07-28)
49+
50+
The `client-modern` module drives `io.modelcontextprotocol.modern.client.McpSyncClient` over
51+
`HttpClientMcpTransport`. Last validated on **2026-11-05** with `--spec-version 2026-07-28`.
52+
53+
### Passing (8/8 scenarios, 62/62 checks)
54+
55+
- **tools_call (2/2):** tool discovery and invocation
56+
- **request-metadata (6/6, SEP-2575):** `_meta` with version, capabilities and client info on every
57+
request, `MCP-Protocol-Version` matching `_meta`, retry after `-32022`
58+
- **sep-2322-client-request-state (5/5, SEP-2322):** `requestState` echoed byte for byte and omitted
59+
when absent, a new JSON-RPC id per retry, no MRTR state on other requests, missing `resultType` as
60+
`complete`
61+
- **http-standard-headers (9/9, SEP-2243):** `Mcp-Method` and `Mcp-Name` on tools, resources and prompts
62+
- **http-custom-headers (18/18, SEP-2243):** `Mcp-Param-*` mirroring, Base64 encoding of unsafe values,
63+
omitted `null` arguments
64+
- **http-invalid-tool-headers (12/12, SEP-2243):** tools with invalid `x-mcp-header` annotations left out,
65+
valid tools kept
66+
- **json-schema-ref-no-deref (1/1, SEP-2106)** and **json-schema-2020-12-preservation (9/9, SEP-1613)**
67+
68+
### Not covered
69+
70+
- **auth/\*:** the modern client has no OAuth support of its own; authorization plugs in through
71+
`McpHttpRequestCustomizer` and `McpHttpAuthorizationErrorHandler`.
72+
4773
## Server Test Results
4874

4975
### Active Suite — Passing (31/31 scenarios, 73/73 checks)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# MCP Conformance Tests - Modern Client
2+
3+
Conformance client for the stateless **2026-07-28** MCP revision, built on the synchronous
4+
`io.modelcontextprotocol.modern.client.McpSyncClient` over `HttpClientMcpTransport`. The legacy
5+
(2025-11-25) clients live in [`client-jdk-http-client`](../client-jdk-http-client) and
6+
[`client-spring-http-client`](../client-spring-http-client).
7+
8+
Each scenario uses the client as an application would: list, call, read, get. The protocol details the
9+
suite checks are the client's job, so the scenarios contain none of them.
10+
11+
## Scenarios
12+
13+
| Scenario | What the client does |
14+
|---|---|
15+
| `tools_call` | Lists the tools and calls the first one |
16+
| `request-metadata` | `server/discover`, then `tools/list`; the server rejects the first request with `-32022` |
17+
| `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 |
18+
| `http-standard-headers` | Lists and uses every tool, resource and prompt |
19+
| `http-custom-headers` | Calls the tools named in `MCP_CONFORMANCE_CONTEXT` with their arguments |
20+
| `http-invalid-tool-headers` | Calls every listed tool; tools with invalid `x-mcp-header` annotations aren't listed |
21+
| `json-schema-ref-no-deref` | Lists the tools |
22+
| `json-schema-2020-12-preservation` | Sends the listed input schema back through `json_schema_echo` |
23+
24+
The `auth/*` scenarios of the 2026-07-28 requirement set aren't covered: the modern client has no OAuth
25+
support of its own, only the HTTP hooks an authorization integration plugs into.
26+
27+
## Running
28+
29+
```bash
30+
./mvnw clean install -DskipTests
31+
32+
npx @modelcontextprotocol/conformance@0.2.0-alpha.12 client \
33+
--command "java -jar conformance-tests/client-modern/target/client-modern-2.2.0-SNAPSHOT.jar" \
34+
--scenario http-custom-headers \
35+
--spec-version 2026-07-28
36+
```
37+
38+
The client reads the scenario from `MCP_CONFORMANCE_SCENARIO`, optional scenario data from
39+
`MCP_CONFORMANCE_CONTEXT`, and the server URL from its last argument. It logs to stderr.
40+
41+
See [VALIDATION_RESULTS.md](../VALIDATION_RESULTS.md) for the latest results.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.modelcontextprotocol.sdk</groupId>
8+
<artifactId>conformance-tests</artifactId>
9+
<version>2.2.0-SNAPSHOT</version>
10+
</parent>
11+
<artifactId>client-modern</artifactId>
12+
<packaging>jar</packaging>
13+
<name>MCP Conformance Tests - Modern Client</name>
14+
<description>Conformance client for the modern (2026-07-28) Java MCP SDK client</description>
15+
<url>https://github.com/modelcontextprotocol/java-sdk</url>
16+
17+
<scm>
18+
<url>https://github.com/modelcontextprotocol/java-sdk</url>
19+
<connection>scm:git:git://github.com/modelcontextprotocol/java-sdk.git</connection>
20+
<developerConnection>scm:git:ssh://git@github.com/modelcontextprotocol/java-sdk.git</developerConnection>
21+
</scm>
22+
23+
<properties>
24+
<module.name>io.modelcontextprotocol.sdk.conformance.client.modern</module.name>
25+
<maven.deploy.skip>true</maven.deploy.skip>
26+
</properties>
27+
28+
<dependencies>
29+
<dependency>
30+
<groupId>io.modelcontextprotocol.sdk</groupId>
31+
<artifactId>mcp-core</artifactId>
32+
<version>2.2.0-SNAPSHOT</version>
33+
</dependency>
34+
<dependency>
35+
<groupId>io.modelcontextprotocol.sdk</groupId>
36+
<artifactId>mcp-json-jackson3</artifactId>
37+
<version>2.2.0-SNAPSHOT</version>
38+
</dependency>
39+
40+
<!-- Logging -->
41+
<dependency>
42+
<groupId>ch.qos.logback</groupId>
43+
<artifactId>logback-classic</artifactId>
44+
<version>${logback.version}</version>
45+
<scope>runtime</scope>
46+
</dependency>
47+
</dependencies>
48+
49+
<build>
50+
<plugins>
51+
<!-- Maven Shade Plugin for creating executable JAR with dependencies -->
52+
<plugin>
53+
<groupId>org.apache.maven.plugins</groupId>
54+
<artifactId>maven-shade-plugin</artifactId>
55+
<version>3.6.2</version>
56+
<executions>
57+
<execution>
58+
<phase>package</phase>
59+
<goals>
60+
<goal>shade</goal>
61+
</goals>
62+
<configuration>
63+
<transformers>
64+
<transformer
65+
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
66+
<mainClass>
67+
io.modelcontextprotocol.conformance.client.modern.ModernConformanceClient</mainClass>
68+
</transformer>
69+
<transformer
70+
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
71+
</transformers>
72+
<filters>
73+
<filter>
74+
<artifact>*:*</artifact>
75+
<excludes>
76+
<exclude>META-INF/*.SF</exclude>
77+
<exclude>META-INF/*.DSA</exclude>
78+
<exclude>META-INF/*.RSA</exclude>
79+
</excludes>
80+
</filter>
81+
</filters>
82+
</configuration>
83+
</execution>
84+
</executions>
85+
</plugin>
86+
</plugins>
87+
</build>
88+
89+
</project>

0 commit comments

Comments
 (0)