diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 8658ac785..ac967ff9f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -2,7 +2,7 @@
name: Bug report
about: Create a bug report to help us improve the project
title: ''
-labels: 'type: bug, status: waiting-for-triage'
+labels: status/waiting for triage
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index a07b6a840..c903204cd 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions and Community Support
- url: https://stackoverflow.com/questions/tagged/spring-ai-mcp
- about: Please ask and answer questions on StackOverflow with the spring-ai tag
+ url: https://stackoverflow.com/questions/tagged/mcp-java-sdk
+ about: Please ask and answer questions on StackOverflow with the mcp-java-sdk tag
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index aba7d39de..16ba64eef 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
-labels: 'status: waiting-for-triage, type: feature'
+labels: status/waiting for triage
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/miscellaneous.md b/.github/ISSUE_TEMPLATE/miscellaneous.md
index d77c625c3..1db42e3b9 100644
--- a/.github/ISSUE_TEMPLATE/miscellaneous.md
+++ b/.github/ISSUE_TEMPLATE/miscellaneous.md
@@ -2,7 +2,7 @@
name: Miscellaneous
about: Suggest an improvement for this project
title: ''
-labels: 'status: waiting-for-triage'
+labels: status/waiting for triage
assignees: ''
---
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..c25de745b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,22 @@
+version: 2
+updates:
+ - package-ecosystem: 'github-actions'
+ directory: '/'
+ schedule:
+ interval: monthly
+ - package-ecosystem: 'maven'
+ directory: '/'
+ schedule:
+ interval: monthly
+ open-pull-requests-limit: 10
+ ignore:
+ # Freeze production dependencies of mcp-core
+ - dependency-name: 'org.slf4j:slf4j-api'
+ - dependency-name: 'com.fasterxml.jackson.core:jackson-annotations'
+ - dependency-name: 'tools.jackson.core:jackson-databind'
+ - dependency-name: 'io.projectreactor:reactor-bom'
+ - dependency-name: 'io.projectreactor:reactor-core'
+ - dependency-name: 'jakarta.servlet:jakarta.servlet-api'
+ # mcp-json-jackson2 and mcp-json-jackson3 dependencies
+ - dependency-name: 'com.fasterxml.jackson.core:jackson-databind'
+ - dependency-name: 'com.networknt:json-schema-validator'
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7c73d9f38..0c79351a6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,7 +5,7 @@ on:
jobs:
build:
- name: Build branch
+ name: Build and Test
runs-on: ubuntu-latest
steps:
- name: Checkout source code
@@ -20,3 +20,20 @@ jobs:
- name: Build
run: mvn verify
+
+ jackson2-tests:
+ name: Jackson 2 Integration Tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Jackson 2 Integration Tests
+ run: mvn -pl mcp-test -am -Pjackson2 test
diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml
new file mode 100644
index 000000000..efd06938f
--- /dev/null
+++ b/.github/workflows/conformance.yml
@@ -0,0 +1,104 @@
+name: Conformance Tests
+
+on:
+ pull_request: {}
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+jobs:
+ server:
+ name: Server Conformance
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Build and start server
+ run: |
+ mvn clean install -DskipTests
+ mvn exec:java -pl conformance-tests/server-servlet -Dexec.mainClass="io.modelcontextprotocol.conformance.server.ConformanceServlet" &
+ timeout 30 bash -c 'until curl -s http://localhost:8080/mcp > /dev/null 2>&1; do sleep 0.5; done'
+
+ - name: Run conformance tests
+ uses: modelcontextprotocol/conformance@v0.1.11
+ with:
+ mode: server
+ url: http://localhost:8080/mcp
+ suite: active
+ expected-failures: ./conformance-tests/conformance-baseline.yml
+
+ client:
+ name: Client Conformance
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ scenario: [initialize, tools_call, elicitation-sep1034-client-defaults, sse-retry]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Build client
+ run: mvn clean install -DskipTests
+
+ - name: Run conformance test
+ uses: modelcontextprotocol/conformance@v0.1.11
+ with:
+ mode: client
+ command: 'java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-*-SNAPSHOT.jar'
+ scenario: ${{ matrix.scenario }}
+ expected-failures: ./conformance-tests/conformance-baseline.yml
+
+ auth:
+ name: Auth Conformance
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ scenario:
+ - auth/metadata-default
+ - auth/metadata-var1
+ - auth/metadata-var2
+ - auth/metadata-var3
+ - auth/basic-cimd
+ - auth/scope-from-www-authenticate
+ - auth/scope-from-scopes-supported
+ - auth/scope-omitted-when-undefined
+ - auth/scope-step-up
+ - auth/scope-retry-limit
+ - auth/token-endpoint-auth-basic
+ - auth/token-endpoint-auth-post
+ - auth/token-endpoint-auth-none
+ - auth/pre-registration
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Build client
+ run: mvn clean install -DskipTests
+
+ - name: Run conformance test
+ uses: modelcontextprotocol/conformance@v0.1.15
+ with:
+ node-version: '22' # see https://github.com/modelcontextprotocol/conformance/pull/162
+ mode: client
+ command: 'java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-*-SNAPSHOT.jar'
+ scenario: ${{ matrix.scenario }}
+ expected-failures: ./conformance-tests/conformance-baseline.yml
\ No newline at end of file
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000..56b5a1207
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,54 @@
+name: Deploy Documentation
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - 'docs/**'
+ - 'mkdocs.yml'
+ release:
+ types:
+ - published
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: 3.x
+
+ - run: pip install mkdocs-material mike
+
+ - name: Configure git user
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ - name: Deploy docs (push to main)
+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
+ run: |
+ PROJECT_VERSION=$(mvn help:evaluate -Dexpression=project.version --quiet -DforceStdout)
+ if [[ "${PROJECT_VERSION}" == *-SNAPSHOT ]]; then
+ ALIAS="latest-snapshot"
+ else
+ ALIAS="latest"
+ fi
+ mike deploy --push --update-aliases "${PROJECT_VERSION}" "${ALIAS}"
+ mike set-default latest --push
+
+ - name: Deploy versioned docs (release)
+ if: github.event_name == 'release'
+ run: |
+ VERSION=${GITHUB_REF_NAME}
+ mike deploy --push --update-aliases "${VERSION}" latest
+ mike set-default latest --push
diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml
index c6c9d3ab6..8df337ec8 100644
--- a/.github/workflows/maven-central-release.yml
+++ b/.github/workflows/maven-central-release.yml
@@ -25,7 +25,10 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '20'
-
+
+ - name: Jackson 2 Integration Tests
+ run: mvn -pl mcp-test -am -Pjackson2 test
+
- name: Build and Test
run: mvn clean verify
diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml
index 5d9b4aa39..1a61d336c 100644
--- a/.github/workflows/publish-snapshot.yml
+++ b/.github/workflows/publish-snapshot.yml
@@ -32,6 +32,9 @@ jobs:
- name: Generate Java docs
run: mvn -Pjavadoc -B javadoc:aggregate
+ - name: Jackson 2 Integration Tests
+ run: mvn -pl mcp-test -am -Pjackson2 test
+
- name: Build with Maven and deploy to Sonatype snapshot repository
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
diff --git a/.gitignore b/.gitignore
index b80dac20d..1fc975c0a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ build/
out
/.gradletasknamecache
**/*.flattened-pom.xml
+**/dependency-reduced-pom.xml
### IDE - Eclipse/STS ###
.apt_generated
@@ -56,6 +57,9 @@ node_modules/
package-lock.json
package.json
+### MkDocs ###
+site/
+
### Other ###
.antlr/
.profiler/
diff --git a/DEPENDENCY_POLICY.md b/DEPENDENCY_POLICY.md
new file mode 100644
index 000000000..5714a6b57
--- /dev/null
+++ b/DEPENDENCY_POLICY.md
@@ -0,0 +1,26 @@
+# Dependency Policy
+
+As a library consumed by downstream projects, the MCP Java SDK takes a conservative approach to dependency updates. Dependencies are kept stable unless there is a specific reason to update, such as a security vulnerability, a bug fix, or a need for new functionality.
+
+## Update Triggers
+
+Dependencies are updated when:
+
+- A **security vulnerability** is disclosed (via GitHub security alerts).
+- A bug in a dependency directly affects the SDK.
+- A new dependency feature is needed for SDK development.
+- A dependency drops support for a Java version the SDK still targets.
+
+Routine version bumps without a clear motivation are avoided to minimize churn for downstream consumers.
+
+## What We Don't Do
+
+The SDK does not run scheduled version bumps for production Maven dependencies. Updating a dependency can force downstream consumers to adopt that update transitively, which can be disruptive for projects with strict dependency policies.
+
+Dependencies are only updated when there is a concrete reason, not simply because a newer version is available.
+
+## Automated Tooling
+
+- **GitHub security updates** are enabled at the repository level and automatically open pull requests for Maven packages with known vulnerabilities. This is a GitHub repo setting, separate from the `dependabot.yml` configuration.
+- **GitHub Actions versions** are kept up to date via Dependabot on a monthly schedule (see `.github/dependabot.yml`).
+- **Maven dependencies** are monitored via Dependabot on a monthly schedule for non-production updates only (see `.github/dependabot.yml`).
diff --git a/MIGRATION-1.0.md b/MIGRATION-1.0.md
new file mode 100644
index 000000000..d1ef0fae8
--- /dev/null
+++ b/MIGRATION-1.0.md
@@ -0,0 +1,300 @@
+# MCP Java SDK Migration Guide: 0.18.1 → 1.0.0
+
+This document covers the breaking changes between **0.18.1** and **1.0.0** of the MCP Java SDK. All items listed here were already deprecated (with `@Deprecated` or `@Deprecated(forRemoval = true)`) in 0.18.1 and are now removed.
+
+> **If you are on a version earlier than 0.18.1**, upgrade progressively to **0.18.1** first. That release already provides the replacement APIs described below alongside the deprecated ones, so you can resolve all deprecation warnings before moving to 1.0.0. Many types and APIs that existed in older 0.x versions (e.g., `ClientMcpTransport`, `ServerMcpTransport`, `DefaultMcpSession`, `StdioServerTransport`, `HttpServletSseServerTransport`, `FlowSseClient`) were already removed well before 0.18.1 and are not covered here.
+
+---
+
+## 1. The `mcp` aggregator module now defaults to Jackson 3
+
+The module structure (`mcp-core`, `mcp-json-jackson2`, `mcp-json-jackson3`, `mcp`) is unchanged. What changes is the default JSON binding in the `mcp` convenience artifact:
+
+| Version | `mcp` artifact includes |
+|---|---|
+| 0.18.1 | `mcp-core` + `mcp-json-jackson2` |
+| 1.0.0 | `mcp-core` + `mcp-json-jackson3` |
+
+If your project uses **Jackson 2** (the `com.fasterxml.jackson` 2.x line), stop depending on the `mcp` aggregator and depend on the individual modules instead:
+
+```xml
+
+ io.modelcontextprotocol.sdk
+ mcp-core
+ 1.0.0-RC3
+
+
+ io.modelcontextprotocol.sdk
+ mcp-json-jackson2
+ 1.0.0-RC3
+
+```
+
+If you are ready to adopt **Jackson 3**, you can simply continue using the `mcp` aggregator:
+
+```xml
+
+ io.modelcontextprotocol.sdk
+ mcp
+ 1.0.0-RC3
+
+```
+
+### Deprecated `io.modelcontextprotocol.json.jackson` package removed
+
+In `mcp-json-jackson2`, the classes under the old `io.modelcontextprotocol.json.jackson` package (deprecated in 0.18.1) have been removed. Use the equivalent classes under `io.modelcontextprotocol.json.jackson2`:
+
+| Removed (old package) | Replacement (already available in 0.18.1) |
+|---|---|
+| `io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper` | `io.modelcontextprotocol.json.jackson2.JacksonMcpJsonMapper` |
+| `io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapperSupplier` | `io.modelcontextprotocol.json.jackson2.JacksonMcpJsonMapperSupplier` |
+| `io.modelcontextprotocol.json.schema.jackson.DefaultJsonSchemaValidator` | `io.modelcontextprotocol.json.schema.jackson2.DefaultJsonSchemaValidator` |
+| `io.modelcontextprotocol.json.schema.jackson.JacksonJsonSchemaValidatorSupplier` | `io.modelcontextprotocol.json.schema.jackson2.JacksonJsonSchemaValidatorSupplier` |
+
+---
+
+## 2. Spring transport modules (`mcp-spring-webflux`, `mcp-spring-webmvc`)
+
+These modules have been moved to the **Spring AI** project starting with Spring AI 2.0. The artifact names remain the same but the **Maven group has changed**:
+
+| 0.18.1 (MCP Java SDK) | 1.0.0+ (Spring AI 2.0) |
+|---|---|
+| `io.modelcontextprotocol.sdk:mcp-spring-webflux` | `org.springframework.ai:mcp-spring-webflux` |
+| `io.modelcontextprotocol.sdk:mcp-spring-webmvc` | `org.springframework.ai:mcp-spring-webmvc` |
+
+Update your dependency coordinates:
+
+```xml
+
+
+ io.modelcontextprotocol.sdk
+ mcp-spring-webflux
+ 0.18.1
+
+
+
+
+ org.springframework.ai
+ mcp-spring-webflux
+ ${spring-ai.version}
+
+```
+
+The Java package names and class names within these artifacts are unchanged — no source code modifications are needed beyond updating the dependency coordinates.
+
+---
+
+## 3. Tool handler signature — `tool()` removed, use `toolCall()`
+
+The `tool()` method on the `McpServer` builder (both sync and async variants) has been removed. It was deprecated in 0.18.1 in favor of `toolCall()`, which accepts a handler that receives the full `CallToolRequest` instead of a raw `Map`.
+
+#### Before (deprecated, removed in 1.0.0):
+
+```java
+McpServer.sync(transportProvider)
+ .tool(
+ myTool,
+ (exchange, args) -> new CallToolResult(List.of(new TextContent("Result: " + calculate(args))), false)
+ )
+ .build();
+```
+
+#### After (already available in 0.18.1):
+
+```java
+McpServer.sync(transportProvider)
+ .toolCall(
+ myTool,
+ (exchange, request) -> CallToolResult.builder()
+ .content(List.of(new TextContent("Result: " + calculate(request.arguments()))))
+ .isError(false)
+ .build()
+ )
+ .build();
+```
+
+---
+
+## 4. `AsyncToolSpecification` / `SyncToolSpecification` — `call` field removed
+
+The deprecated `call` record component (which accepted `Map`) has been removed from both `AsyncToolSpecification` and `SyncToolSpecification`. Only `callHandler` (which accepts `CallToolRequest`) remains.
+
+The deprecated constructors that accepted a `call` function have also been removed. Use the builder:
+
+```java
+McpServerFeatures.AsyncToolSpecification.builder()
+ .tool(tool)
+ .callHandler((exchange, request) -> Mono.just(
+ CallToolResult.builder()
+ .content(List.of(new TextContent("Done")))
+ .build()))
+ .build();
+```
+
+---
+
+## 5. Content types — deprecated `audience`/`priority` constructors and accessors removed
+
+`TextContent`, `ImageContent`, and `EmbeddedResource` previously had constructors and accessors that took inline `List audience` and `Double priority` parameters. These were deprecated in favor of the `Annotations` record. The deprecated forms are now removed.
+
+#### Before (deprecated, removed in 1.0.0):
+
+```java
+new TextContent(List.of(Role.USER), 0.8, "Hello world")
+textContent.audience() // deprecated accessor
+textContent.priority() // deprecated accessor
+```
+
+#### After (already available in 0.18.1):
+
+```java
+new TextContent(new Annotations(List.of(Role.USER), 0.8), "Hello world")
+textContent.annotations().audience()
+textContent.annotations().priority()
+```
+
+The simple `new TextContent("text")` constructor continues to work.
+
+---
+
+## 6. `CallToolResult` and `Resource` — deprecated constructors removed
+
+The constructors on `CallToolResult` and `Resource` that were deprecated in 0.18.1 have been removed. Use the builders instead.
+
+#### `CallToolResult`
+
+```java
+// Removed:
+new CallToolResult(List.of(new TextContent("result")), false);
+new CallToolResult("result text", false);
+new CallToolResult(content, isError, structuredContent);
+
+// Use instead:
+CallToolResult.builder()
+ .content(List.of(new TextContent("result")))
+ .isError(false)
+ .build();
+```
+
+#### `Resource`
+
+```java
+// Removed:
+new Resource(uri, name, description, mimeType, annotations);
+new Resource(uri, name, title, description, mimeType, size, annotations);
+
+// Use instead:
+Resource.builder()
+ .uri(uri)
+ .name(name)
+ .title(title)
+ .description(description)
+ .mimeType(mimeType)
+ .size(size)
+ .annotations(annotations)
+ .build();
+```
+
+---
+
+## 7. `McpError(Object)` constructor removed
+
+The deprecated `McpError(Object error)` constructor, which was commonly used as `new McpError("message string")`, has been removed. Construct `McpError` instances using the builder with a JSON-RPC error code:
+
+```java
+// Removed:
+throw new McpError("Something went wrong");
+
+// Use instead:
+throw McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
+ .message("Something went wrong")
+ .build();
+```
+
+Additionally, several places in the SDK that previously threw `McpError` for validation or state-checking purposes now throw standard Java exceptions (`IllegalStateException`, `IllegalArgumentException`). If you were catching `McpError` in those scenarios, update your catch blocks accordingly.
+
+---
+
+## 8. `McpSchema.LATEST_PROTOCOL_VERSION` constant removed
+
+The deprecated `McpSchema.LATEST_PROTOCOL_VERSION` constant has been removed. Use the `ProtocolVersions` interface directly:
+
+```java
+// Removed:
+McpSchema.LATEST_PROTOCOL_VERSION
+
+// Use instead:
+ProtocolVersions.MCP_2025_11_25
+```
+
+---
+
+## 9. Deprecated session constructors and inner interfaces removed
+
+The following deprecated constructors and inner interfaces, all of which already had replacements available in 0.18.1, have been removed:
+
+### `McpServerSession`
+
+| Removed | Replacement (available since 0.18.1) |
+|---|---|
+| Constructor with `InitNotificationHandler` parameter | Constructor without `InitNotificationHandler` — use `McpInitRequestHandler` in the map |
+| `McpServerSession.InitRequestHandler` (inner interface) | `McpInitRequestHandler` (top-level interface) |
+| `McpServerSession.RequestHandler` (inner interface) | `McpRequestHandler` (top-level interface) |
+| `McpServerSession.NotificationHandler` (inner interface) | `McpNotificationHandler` (top-level interface) |
+
+### `McpClientSession`
+
+| Removed | Replacement (available since 0.18.1) |
+|---|---|
+| Constructor without `connectHook` parameter | Constructor that accepts a `Function super Mono, ? extends Publisher> connectHook` |
+
+### `McpAsyncServerExchange`
+
+| Removed | Replacement (available since 0.18.1) |
+|---|---|
+| Constructor `McpAsyncServerExchange(McpSession, ClientCapabilities, Implementation)` | Constructor `McpAsyncServerExchange(String, McpLoggableSession, ClientCapabilities, Implementation, McpTransportContext)` |
+
+---
+
+## 10. `McpAsyncServer.loggingNotification()` / `McpSyncServer.loggingNotification()` removed
+
+The `loggingNotification(LoggingMessageNotification)` methods on `McpAsyncServer` and `McpSyncServer` were deprecated because they incorrectly broadcast to all connected clients. They have been removed. Use the per-session exchange method instead:
+
+```java
+// Removed:
+server.loggingNotification(notification);
+
+// Use instead (inside a handler with access to the exchange):
+exchange.loggingNotification(notification);
+```
+
+---
+
+## 11. `HttpClientSseClientTransport.Builder` — deprecated constructor removed
+
+The deprecated `new HttpClientSseClientTransport.Builder(String baseUri)` constructor has been removed. Use the static factory method:
+
+```java
+// Removed:
+new HttpClientSseClientTransport.Builder("http://localhost:8080")
+
+// Use instead:
+HttpClientSseClientTransport.builder("http://localhost:8080")
+```
+
+---
+
+## Summary checklist
+
+Before upgrading to 1.0.0, verify that your 0.18.1 build has **zero deprecation warnings** related to the MCP SDK. Every removal in 1.0.0 was preceded by a deprecation in 0.18.1 with a pointer to the replacement. Once you are clean on 0.18.1:
+
+1. Update your dependency versions — either bump the `mcp-bom` version, or bump the specific module dependencies you use (e.g., `mcp-core`, `mcp-json-jackson2`). If you were relying on the `mcp` aggregator, note it now pulls in Jackson 3 — switch to `mcp-core` + `mcp-json-jackson2` if you need to stay on Jackson 2.
+2. Replace `io.modelcontextprotocol.sdk:mcp-spring-webflux` / `mcp-spring-webmvc` with `org.springframework.ai:mcp-spring-webflux` / `mcp-spring-webmvc`.
+3. If you use the `mcp-json-jackson2` module, update imports from `io.modelcontextprotocol.json.jackson` to `io.modelcontextprotocol.json.jackson2` (and similarly for the schema validator package).
+4. Compile and verify — no further source changes should be needed.
+
+---
+
+## Need help?
+
+If you run into issues during migration or have questions, please open an issue or start a discussion in the [MCP Java SDK GitHub repository](https://github.com/modelcontextprotocol/java-sdk).
diff --git a/README.md b/README.md
index 7bda15006..34133a796 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
-A set of projects that provide Java SDK integration for the [Model Context Protocol](https://modelcontextprotocol.org/docs/concepts/architecture).
+A set of projects that provide Java SDK integration for the [Model Context Protocol](https://modelcontextprotocol.io/docs/concepts/architecture).
This SDK enables Java applications to interact with AI models and tools through a standardized interface, supporting both synchronous and asynchronous communication patterns.
## 📚 Reference Documentation
@@ -13,14 +13,17 @@ This SDK enables Java applications to interact with AI models and tools through
#### MCP Java SDK documentation
For comprehensive guides and SDK API documentation
-- [Features](https://modelcontextprotocol.io/sdk/java/mcp-overview#features) - Overview the features provided by the Java MCP SDK
-- [Architecture](https://modelcontextprotocol.io/sdk/java/mcp-overview#architecture) - Java MCP SDK architecture overview.
-- [Java Dependencies / BOM](https://modelcontextprotocol.io/sdk/java/mcp-overview#dependencies) - Java dependencies and BOM.
-- [Java MCP Client](https://modelcontextprotocol.io/sdk/java/mcp-client) - Learn how to use the MCP client to interact with MCP servers.
-- [Java MCP Server](https://modelcontextprotocol.io/sdk/java/mcp-server) - Learn how to implement and configure a MCP servers.
+- [Features](https://modelcontextprotocol.github.io/java-sdk/#features) - Overview the features provided by the Java MCP SDK
+- [Architecture](https://modelcontextprotocol.github.io/java-sdk/#architecture) - Java MCP SDK architecture overview.
+- [Java Dependencies / BOM](https://modelcontextprotocol.github.io/java-sdk/quickstart/#dependencies) - Java dependencies and BOM.
+- [Java MCP Client](https://modelcontextprotocol.github.io/java-sdk/client/) - Learn how to use the MCP client to interact with MCP servers.
+- [Java MCP Server](https://modelcontextprotocol.github.io/java-sdk/server/) - Learn how to implement and configure a MCP servers.
#### Spring AI MCP documentation
-[Spring AI MCP](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html) extends the MCP Java SDK with Spring Boot integration, providing both [client](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html) and [server](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) starters. Bootstrap your AI applications with MCP support using [Spring Initializer](https://start.spring.io).
+[Spring AI MCP](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) extends the MCP Java SDK with Spring Boot integration, providing both [client](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) and [server](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html) starters.
+The [MCP Annotations](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-annotations-overview.html) - provides annotation-based method handling for MCP servers and clients in Java.
+The [MCP Security](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-security.html) - provides comprehensive OAuth 2.0 and API key-based security support for Model Context Protocol implementations in Spring AI.
+Bootstrap your AI applications with MCP support using [Spring Initializer](https://start.spring.io).
## Development
@@ -83,11 +86,11 @@ The following sections explain what we chose, why it made sense, and how the cho
### 1. JSON Serialization
-* **SDK Choice**: Jackson for JSON serialization and deserialization, behind an SDK abstraction (`mcp-json`)
+* **SDK Choice**: Jackson for JSON serialization and deserialization, behind an SDK abstraction (package `io.modelcontextprotocol.json` in `mcp-core`)
* **Why**: Jackson is widely adopted across the Java ecosystem, provides strong performance and a mature annotation model, and is familiar to the SDK team and many potential contributors.
-* **How we expose it**: Public APIs use a zero-dependency abstraction (`mcp-json`). Jackson is shipped as the default implementation (`mcp-jackson2`), but alternatives can be plugged in.
+* **How we expose it**: Public APIs use a bundled abstraction. Jackson is shipped as the default implementation (`mcp-json-jackson3`), but alternatives can be plugged in.
* **How it fits the SDK**: This offers a pragmatic default while keeping flexibility for projects that prefer different JSON libraries.
@@ -136,21 +139,21 @@ MCP supports both clients (applications consuming MCP servers) and servers (appl
#### Client Transport in the SDK
-* **SDK Choice**: JDK HttpClient (Java 11+) as the default client, with optional Spring WebClient support
+* **SDK Choice**: JDK HttpClient (Java 11+) as the default client
-* **Why**: The JDK HttpClient is built-in, portable, and supports streaming responses. This keeps the default lightweight with no extra dependencies. Spring WebClient support is available for Spring-based projects.
+* **Why**: The JDK HttpClient is built-in, portable, and supports streaming responses. This keeps the default lightweight with no extra dependencies.
-* **How we expose it**: MCP Client APIs are transport-agnostic. The core module ships with JDK HttpClient transport. A Spring module provides WebClient integration.
+* **How we expose it**: MCP Client APIs are transport-agnostic. The core module ships with JDK HttpClient transport. Spring WebClient-based transport is available in [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+.
* **How it fits the SDK**: This ensures all applications can talk to MCP servers out of the box, while allowing richer integration in Spring and other environments.
#### Server Transport in the SDK
-* **SDK Choice**: Jakarta Servlet implementation in core, with optional Spring WebFlux and Spring WebMVC providers
+* **SDK Choice**: Jakarta Servlet implementation in core
-* **Why**: Servlet is the most widely deployed Java server API. WebFlux and WebMVC cover a significant part of the Spring community. Together these provide reach across blocking and non-blocking models.
+* **Why**: Servlet is the most widely deployed Java server API, providing broad reach across blocking and non-blocking models without additional dependencies.
-* **How we expose it**: Server APIs are transport-agnostic. Core includes Servlet support. Spring modules extend support for WebFlux and WebMVC.
+* **How we expose it**: Server APIs are transport-agnostic. Core includes Servlet support. Spring WebFlux and WebMVC server transports are available in [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+.
* **How it fits the SDK**: This allows developers to expose MCP servers in the most common Java environments today, while enabling other transport implementations such as Netty, Vert.x, or Helidon.
@@ -168,14 +171,26 @@ MCP supports both clients (applications consuming MCP servers) and servers (appl
The SDK is organized into modules to separate concerns and allow adopters to bring in only what they need:
* `mcp-bom` – Dependency versions
-* `mcp-core` – Reference implementation (STDIO, JDK HttpClient, Servlet)
-* `mcp-json` – JSON abstraction
-* `mcp-jackson2` – Jackson implementation of JSON binding
-* `mcp` – Convenience bundle (core + Jackson)
+* `mcp-core` – Reference implementation (STDIO, JDK HttpClient, Servlet), JSON binding interface definitions
+* `mcp-json-jackson2` – Jackson 2 implementation of JSON binding
+* `mcp-json-jackson3` – Jackson 3 implementation of JSON binding
+* `mcp` – Convenience bundle (core + Jackson 3)
* `mcp-test` – Shared testing utilities
-* `mcp-spring` – Spring integrations (WebClient, WebFlux, WebMVC)
-For example, a minimal adopter may depend only on `mcp` (core + Jackson), while a Spring-based application can use `mcp-spring` for deeper framework integration.
+Spring integrations (WebClient, WebFlux, WebMVC) are now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`).
+
+For example, a minimal adopter may depend only on `mcp` (core + Jackson), while a Spring-based application can use the Spring AI `mcp-spring-webflux` or `mcp-spring-webmvc` artifacts for deeper framework integration.
+
+Additionally, `mcp-test` contains integration tests for `mcp-core`.
+`mcp-core` needs a JSON implementation to run full integration tests.
+Implementations such as `mcp-json-jackson3`, depend on `mcp-core`, and therefore cannot be imported in `mcp-core` for tests.
+Instead, all integration tests that need a JSON implementation are now in `mcp-test`, and use `jackson3` by default.
+A `jackson2` maven profile allows to run integration tests with Jackson 2, like so:
+
+
+```bash
+./mvnw -pl mcp-test -am -Pjackson2 test
+```
### Future Directions
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 000000000..b5b7dc4d7
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,45 @@
+# Roadmap
+
+## Spec Implementation Tracking
+
+The SDK tracks implementation of MCP spec components via GitHub Projects, with a dedicated project board for each spec revision. For example, see the [2025-11-25 spec revision board](https://github.com/orgs/modelcontextprotocol/projects/26/views/1).
+
+## Current Focus Areas
+
+### 2025-11-25 Spec Implementation
+
+The Java SDK is actively implementing the [2025-11-25 MCP specification revision](https://github.com/orgs/modelcontextprotocol/projects/26/views/1).
+
+Key features in this revision include:
+
+- **Tasks**: Experimental support for tracking durable requests with polling and deferred result retrieval
+- **Tool calling in sampling**: Support for `tools` and `toolChoice` parameters
+- **URL mode elicitation**: Client-side URL elicitation requests
+- **Icons metadata**: Servers can expose icons for tools, resources, resource templates, and prompts
+- **Enhanced schemas**: JSON Schema 2020-12 as default, improved enum support, default values for elicitation
+- **Security improvements**: Updated security best practices, enhanced authorization flows, enabling OAuth integrations
+
+See the full [changelog](https://modelcontextprotocol.io/specification/2025-11-25/changelog) for details.
+
+### Tier 1 SDK Support
+
+Once we catch up on the most recent MCP specification revision we aim to fully support all the upcoming specification features on the day of its release.
+
+### v1.x Development
+
+The Java SDK is currently in active development as v1.x, following a recent stable 1.0.0 release. The SDK provides:
+
+- MCP protocol implementation
+- Synchronous and asynchronous programming models
+- Multiple transport options (STDIO, HTTP/SSE, Servlet)
+- Pluggable JSON serialization (Jackson 2 and Jackson 3)
+
+Development is tracked via [GitHub Issues](https://github.com/modelcontextprotocol/java-sdk/issues) and [GitHub Projects](https://github.com/orgs/modelcontextprotocol/projects).
+
+### Future Versions
+
+Major version updates will align with MCP specification changes and breaking API changes as needed. The SDK is designed to evolve with the Java ecosystem, including:
+
+- Virtual Threads and Structured Concurrency support
+- Additional transport implementations
+- Performance optimizations
diff --git a/SECURITY.md b/SECURITY.md
index 74e9880fd..502924200 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,21 +1,21 @@
# Security Policy
-Thank you for helping us keep the SDKs and systems they interact with secure.
+Thank you for helping keep the Model Context Protocol and its ecosystem secure.
## Reporting Security Issues
-This SDK is maintained by [Anthropic](https://www.anthropic.com/) as part of the Model
-Context Protocol project.
+If you discover a security vulnerability in this repository, please report it through
+the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
+for this repository.
-The security of our systems and user data is Anthropic’s top priority. We appreciate the
-work of security researchers acting in good faith in identifying and reporting potential
-vulnerabilities.
+Please **do not** report security vulnerabilities through public GitHub issues, discussions,
+or pull requests.
-Our security program is managed on HackerOne and we ask that any validated vulnerability
-in this functionality be reported through their
-[submission form](https://hackerone.com/anthropic-vdp/reports/new?type=team&report_type=vulnerability).
+## What to Include
-## Vulnerability Disclosure Program
+To help us triage and respond quickly, please include:
-Our Vulnerability Program Guidelines are defined on our
-[HackerOne program page](https://hackerone.com/anthropic-vdp).
\ No newline at end of file
+- A description of the vulnerability
+- Steps to reproduce the issue
+- The potential impact
+- Any suggested fixes (optional)
diff --git a/VERSIONING.md b/VERSIONING.md
new file mode 100644
index 000000000..331c6d05e
--- /dev/null
+++ b/VERSIONING.md
@@ -0,0 +1,46 @@
+# Versioning Policy
+
+The MCP Java SDK (`io.modelcontextprotocol.sdk`) follows [Semantic Versioning 2.0.0](https://semver.org/).
+
+## Version Format
+
+`MAJOR.MINOR.PATCH`
+
+- **MAJOR**: Incremented for breaking changes (see below).
+- **MINOR**: Incremented for new features that are backward-compatible.
+- **PATCH**: Incremented for backward-compatible bug fixes.
+
+## What Constitutes a Breaking Change
+
+The following changes are considered breaking and require a major version bump:
+
+- Removing or renaming a public API (class, interface, method, or constant).
+- Changing the signature of a public method in a way that breaks existing callers (removing parameters, changing required/optional status, changing types).
+- Removing or renaming a public interface method or field.
+- Changing the behavior of an existing API in a way that breaks documented contracts.
+- Dropping support for a Java LTS version.
+- Removing support for a transport type.
+- Changes to the MCP protocol version that require client/server code changes.
+- Removing a module from the SDK.
+
+The following are **not** considered breaking:
+
+- Adding new methods with default implementations to interfaces.
+- Adding new public APIs, classes, interfaces, or methods.
+- Adding new optional parameters to existing methods (through method overloading).
+- Bug fixes that correct behavior to match documented intent.
+- Internal refactoring that does not affect the public API.
+- Adding support for new MCP spec features.
+- Changes to test dependencies or build tooling.
+- Adding new modules to the SDK.
+
+## How Breaking Changes Are Communicated
+
+1. **Changelog**: All breaking changes are documented in the GitHub release notes with migration instructions.
+2. **Deprecation**: When feasible, APIs are deprecated for at least one minor release before removal using `@Deprecated` annotations, which surface warnings through Java tooling and IDEs.
+3. **Migration guide**: Major version releases include a migration guide describing what changed and how to update.
+4. **PR labels**: Pull requests containing breaking changes are labeled with `breaking change`.
+
+## Maven Coordinates
+
+All SDK modules share the same version number and are released together. The BOM (`mcp-bom`) provides dependency management for all SDK modules to ensure version consistency.
diff --git a/conformance-tests/VALIDATION_RESULTS.md b/conformance-tests/VALIDATION_RESULTS.md
new file mode 100644
index 000000000..19e74330c
--- /dev/null
+++ b/conformance-tests/VALIDATION_RESULTS.md
@@ -0,0 +1,124 @@
+# MCP Java SDK Conformance Test Validation Results
+
+## Summary
+
+**Server Tests:** 37/40 passed (92.5%)
+**Client Tests:** 3/4 scenarios passed (9/10 checks passed)
+**Auth Tests:** 12/14 scenarios fully passing (178 passed, 1 failed, 1 warning, 85.7% scenarios, 98.9% checks)
+
+## Server Test Results
+
+### Passing (37/40)
+
+- **Lifecycle & Utilities (4/4):** initialize, ping, logging-set-level, completion-complete
+- **Tools (11/11):** All scenarios including progress notifications ✨
+- **Elicitation (10/10):** SEP-1034 defaults (5 checks), SEP-1330 enums (5 checks)
+- **Resources (4/6):** list, read-text, read-binary, templates-read
+- **Prompts (4/4):** list, simple, with-args, embedded-resource, with-image
+- **SSE Transport (2/2):** Multiple streams
+- **Security (2/2):** Localhost validation passes, DNS rebinding protection
+
+### Failing (3/40)
+
+1. **resources-subscribe** - Not implemented in SDK
+2. **resources-unsubscribe** - Not implemented in SDK
+
+## Client Test Results
+
+### Passing (3/4 scenarios, 9/10 checks)
+
+- **initialize (1/1):** Protocol negotiation, clientInfo, capabilities
+- **tools_call (1/1):** Tool discovery and invocation
+- **elicitation-sep1034-client-defaults (5/5):** Default values for string, integer, number, enum, boolean
+
+### Partially Passing (1/4 scenarios, 1/2 checks)
+
+- **sse-retry (1/2 + 1 warning):**
+ - ✅ Reconnects after stream closure
+ - ❌ Does not respect retry timing
+ - ⚠️ Does not send Last-Event-ID header (SHOULD requirement)
+
+**Issue:** Client treats `retry:` SSE field as invalid instead of parsing it for reconnection timing.
+
+## Auth Test Results (Spring HTTP Client)
+
+**Status: 178 passed, 1 failed, 1 warning across 14 scenarios**
+
+Uses the `client-spring-http-client` module with Spring Security OAuth2 and the [mcp-client-security](https://github.com/springaicommunity/mcp-client-security) library.
+
+### Fully Passing (12/14 scenarios)
+
+- **auth/metadata-default (12/12):** Default metadata discovery
+- **auth/metadata-var1 (12/12):** Metadata discovery variant 1
+- **auth/metadata-var2 (12/12):** Metadata discovery variant 2
+- **auth/metadata-var3 (12/12):** Metadata discovery variant 3
+- **auth/scope-from-www-authenticate (13/13):** Scope extraction from WWW-Authenticate header
+- **auth/scope-from-scopes-supported (13/13):** Scope extraction from scopes_supported
+- **auth/scope-omitted-when-undefined (13/13):** Scope omitted when not defined
+- **auth/scope-retry-limit (11/11):** Scope retry limit handling
+- **auth/token-endpoint-auth-basic (17/17):** Token endpoint with HTTP Basic auth
+- **auth/token-endpoint-auth-post (17/17):** Token endpoint with POST body auth
+- **auth/token-endpoint-auth-none (17/17):** Token endpoint with no client auth
+- **auth/pre-registration (6/6):** Pre-registered client credentials flow
+
+### Partially Passing (2/14 scenarios)
+
+- **auth/basic-cimd (12/12 + 1 warning):** Basic Client-Initiated Metadata Discovery — all checks pass, minor warning
+- **auth/scope-step-up (11/12):** Scope step-up challenge — 1 failure, client does not fully handle scope escalation after initial authorization
+
+## Known Limitations
+
+1. **Resource Subscriptions:** SDK doesn't implement `resources/subscribe` and `resources/unsubscribe` handlers
+2. **Client SSE Retry:** Client doesn't parse or respect the `retry:` field, reconnects immediately, and doesn't send Last-Event-ID header
+3. **Auth Scope Step-Up:** Client does not fully handle scope step-up challenges where the server requests additional scopes after initial authorization
+4. **Auth Basic CIMD:** Minor conformance warning in the basic Client-Initiated Metadata Discovery flow
+
+## Running Tests
+
+### Server
+```bash
+# Start server
+cd conformance-tests/server-servlet
+../../mvnw compile exec:java -Dexec.mainClass="io.modelcontextprotocol.conformance.server.ConformanceServlet"
+
+# Run tests (in another terminal)
+npx @modelcontextprotocol/conformance server --url http://localhost:8080/mcp --suite active
+```
+
+### Client
+```bash
+# Build
+cd conformance-tests/client-jdk-http-client
+../../mvnw clean package -DskipTests
+
+# Run all scenarios
+for scenario in initialize tools_call elicitation-sep1034-client-defaults sse-retry; do
+ npx @modelcontextprotocol/conformance client \
+ --command "java -jar target/client-jdk-http-client-1.0.0-SNAPSHOT.jar" \
+ --scenario $scenario
+done
+```
+
+### Auth (Spring HTTP Client)
+
+Ensure you run with the conformance testing suite `0.1.15` or higher.
+
+```bash
+# Build
+cd conformance-tests/client-spring-http-client
+../../mvnw clean package -DskipTests
+
+# Run auth suite
+npx @modelcontextprotocol/conformance@0.1.15 client \
+ --spec-version 2025-11-25 \
+ --command "java -jar target/client-spring-http-client-0.18.0-SNAPSHOT.jar" \
+ --suite auth
+```
+
+## Recommendations
+
+### High Priority
+1. Fix client SSE retry field handling in `HttpClientStreamableHttpTransport`
+2. Implement resource subscription handlers in `McpStatelessAsyncServer`
+3. Implement CIMD
+4. Implement scope step up
diff --git a/conformance-tests/client-jdk-http-client/README.md b/conformance-tests/client-jdk-http-client/README.md
new file mode 100644
index 000000000..44eccedf0
--- /dev/null
+++ b/conformance-tests/client-jdk-http-client/README.md
@@ -0,0 +1,135 @@
+# 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](https://github.com/modelcontextprotocol/conformance). 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:
+
+```bash
+cd conformance-tests/client-jdk-http-client
+../../mvnw clean package -DskipTests
+```
+
+This creates an executable JAR at:
+```
+target/client-jdk-http-client-1.0.0-SNAPSHOT.jar
+```
+
+## Running Tests
+
+### Using the Conformance Framework
+
+Run a single scenario:
+
+```bash
+npx @modelcontextprotocol/conformance client \
+ --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.0.0-SNAPSHOT.jar" \
+ --scenario initialize
+
+npx @modelcontextprotocol/conformance client \
+ --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.0.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.0.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.0.0-SNAPSHOT.jar" \
+ --scenario sse-retry
+```
+
+Run with verbose output:
+
+```bash
+npx @modelcontextprotocol/conformance client \
+ --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.0.0-SNAPSHOT.jar" \
+ --scenario initialize \
+ --verbose
+```
+
+### Manual Testing
+
+You can also run the client manually if you have a test server:
+
+```bash
+export MCP_CONFORMANCE_SCENARIO=initialize
+java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.0.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/-/`:
+- `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
diff --git a/conformance-tests/client-jdk-http-client/pom.xml b/conformance-tests/client-jdk-http-client/pom.xml
new file mode 100644
index 000000000..f30361438
--- /dev/null
+++ b/conformance-tests/client-jdk-http-client/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+
+ io.modelcontextprotocol.sdk
+ conformance-tests
+ 1.1.0-SNAPSHOT
+
+ client-jdk-http-client
+ jar
+ MCP Conformance Tests - JDK HTTP Client
+ JDK HTTP Client conformance tests for the Java MCP SDK
+ https://github.com/modelcontextprotocol/java-sdk
+
+
+ https://github.com/modelcontextprotocol/java-sdk
+ git://github.com/modelcontextprotocol/java-sdk.git
+ git@github.com/modelcontextprotocol/java-sdk.git
+
+
+
+ true
+
+
+
+
+ io.modelcontextprotocol.sdk
+ mcp
+ 1.1.0-SNAPSHOT
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ runtime
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.1
+
+
+ package
+
+ shade
+
+
+
+
+ io.modelcontextprotocol.conformance.client.ConformanceJdkClientMcpClient
+
+
+
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conformance-tests/client-jdk-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceJdkClientMcpClient.java b/conformance-tests/client-jdk-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceJdkClientMcpClient.java
new file mode 100644
index 000000000..570c4614e
--- /dev/null
+++ b/conformance-tests/client-jdk-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceJdkClientMcpClient.java
@@ -0,0 +1,286 @@
+package io.modelcontextprotocol.conformance.client;
+
+import java.time.Duration;
+
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.client.McpSyncClient;
+import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
+import io.modelcontextprotocol.spec.McpSchema;
+
+/**
+ * MCP Conformance Test Client - JDK HTTP Client Implementation
+ *
+ *
+ * This client is designed to work with the MCP conformance test framework. It reads the
+ * test scenario from the MCP_CONFORMANCE_SCENARIO environment variable and the server URL
+ * from command-line arguments.
+ *
+ *
+ * Usage: ConformanceJdkClientMcpClient <server-url>
+ *
+ * @see MCP Conformance
+ * Test Framework
+ */
+public class ConformanceJdkClientMcpClient {
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ System.err.println("Usage: ConformanceJdkClientMcpClient ");
+ System.err.println("The server URL must be provided as the last command-line argument.");
+ System.err.println("The MCP_CONFORMANCE_SCENARIO environment variable must be set.");
+ System.exit(1);
+ }
+
+ String scenario = System.getenv("MCP_CONFORMANCE_SCENARIO");
+ if (scenario == null || scenario.isEmpty()) {
+ System.err.println("Error: MCP_CONFORMANCE_SCENARIO environment variable is not set");
+ System.exit(1);
+ }
+
+ String serverUrl = args[args.length - 1];
+
+ try {
+ switch (scenario) {
+ case "initialize":
+ runInitializeScenario(serverUrl);
+ break;
+ case "tools_call":
+ runToolsCallScenario(serverUrl);
+ break;
+ case "elicitation-sep1034-client-defaults":
+ runElicitationDefaultsScenario(serverUrl);
+ break;
+ case "sse-retry":
+ runSSERetryScenario(serverUrl);
+ break;
+ default:
+ System.err.println("Unknown scenario: " + scenario);
+ System.err.println("Available scenarios:");
+ System.err.println(" - initialize");
+ System.err.println(" - tools_call");
+ System.err.println(" - elicitation-sep1034-client-defaults");
+ System.err.println(" - sse-retry");
+ System.exit(1);
+ }
+ System.exit(0);
+ }
+ catch (Exception e) {
+ System.err.println("Error: " + e.getMessage());
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Helper method to create and configure an MCP client with transport.
+ * @param serverUrl the URL of the MCP server
+ * @return configured McpSyncClient instance
+ */
+ private static McpSyncClient createClient(String serverUrl) {
+ HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl).build();
+
+ return McpClient.sync(transport)
+ .clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
+ .requestTimeout(Duration.ofSeconds(30))
+ .build();
+ }
+
+ /**
+ * Helper method to create and configure an MCP client with elicitation support.
+ * @param serverUrl the URL of the MCP server
+ * @return configured McpSyncClient instance with elicitation handler
+ */
+ private static McpSyncClient createClientWithElicitation(String serverUrl) {
+ HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl).build();
+
+ // Build client capabilities with elicitation support
+ var capabilities = McpSchema.ClientCapabilities.builder().elicitation().build();
+
+ return McpClient.sync(transport)
+ .clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
+ .requestTimeout(Duration.ofSeconds(30))
+ .capabilities(capabilities)
+ .elicitation(request -> {
+ // Apply default values from the schema to create the content
+ var content = new java.util.HashMap();
+ var schema = request.requestedSchema();
+
+ if (schema != null && schema.containsKey("properties")) {
+ @SuppressWarnings("unchecked")
+ var properties = (java.util.Map) schema.get("properties");
+
+ // Apply defaults for each property
+ properties.forEach((key, propDef) -> {
+ @SuppressWarnings("unchecked")
+ var propMap = (java.util.Map) propDef;
+ if (propMap.containsKey("default")) {
+ content.put(key, propMap.get("default"));
+ }
+ });
+ }
+
+ // Return accept action with the defaults applied
+ return new McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, content, null);
+ })
+ .build();
+ }
+
+ /**
+ * Initialize scenario: Tests MCP client initialization handshake.
+ * @param serverUrl the URL of the MCP server
+ * @throws Exception if any error occurs during execution
+ */
+ private static void runInitializeScenario(String serverUrl) throws Exception {
+ McpSyncClient client = createClient(serverUrl);
+
+ try {
+ // Initialize client
+ client.initialize();
+
+ System.out.println("Successfully connected to MCP server");
+ }
+ finally {
+ // Close the client (which will close the transport)
+ client.close();
+ System.out.println("Connection closed successfully");
+ }
+ }
+
+ /**
+ * Tools call scenario: Tests tool listing and invocation functionality.
+ * @param serverUrl the URL of the MCP server
+ * @throws Exception if any error occurs during execution
+ */
+ private static void runToolsCallScenario(String serverUrl) throws Exception {
+ McpSyncClient client = createClient(serverUrl);
+
+ try {
+ // Initialize client
+ client.initialize();
+
+ System.out.println("Successfully connected to MCP server");
+
+ // List available tools
+ McpSchema.ListToolsResult toolsResult = client.listTools();
+ System.out.println("Successfully listed tools");
+
+ // Call the add_numbers tool if it exists
+ if (toolsResult != null && toolsResult.tools() != null) {
+ for (McpSchema.Tool tool : toolsResult.tools()) {
+ if ("add_numbers".equals(tool.name())) {
+ // Call the add_numbers tool with test arguments
+ var arguments = new java.util.HashMap();
+ arguments.put("a", 5);
+ arguments.put("b", 3);
+
+ McpSchema.CallToolResult result = client
+ .callTool(new McpSchema.CallToolRequest("add_numbers", arguments));
+
+ System.out.println("Successfully called add_numbers tool");
+ if (result != null && result.content() != null) {
+ System.out.println("Tool result: " + result.content());
+ }
+ break;
+ }
+ }
+ }
+ }
+ finally {
+ // Close the client (which will close the transport)
+ client.close();
+ System.out.println("Connection closed successfully");
+ }
+ }
+
+ /**
+ * Elicitation defaults scenario: Tests client applies default values for omitted
+ * elicitation fields (SEP-1034).
+ * @param serverUrl the URL of the MCP server
+ * @throws Exception if any error occurs during execution
+ */
+ private static void runElicitationDefaultsScenario(String serverUrl) throws Exception {
+ McpSyncClient client = createClientWithElicitation(serverUrl);
+
+ try {
+ // Initialize client
+ client.initialize();
+
+ System.out.println("Successfully connected to MCP server");
+
+ // List available tools
+ McpSchema.ListToolsResult toolsResult = client.listTools();
+ System.out.println("Successfully listed tools");
+
+ // Call the test_client_elicitation_defaults tool if it exists
+ if (toolsResult != null && toolsResult.tools() != null) {
+ for (McpSchema.Tool tool : toolsResult.tools()) {
+ if ("test_client_elicitation_defaults".equals(tool.name())) {
+ // Call the tool which will trigger an elicitation request
+ var arguments = new java.util.HashMap();
+
+ McpSchema.CallToolResult result = client
+ .callTool(new McpSchema.CallToolRequest("test_client_elicitation_defaults", arguments));
+
+ System.out.println("Successfully called test_client_elicitation_defaults tool");
+ if (result != null && result.content() != null) {
+ System.out.println("Tool result: " + result.content());
+ }
+ break;
+ }
+ }
+ }
+ }
+ finally {
+ // Close the client (which will close the transport)
+ client.close();
+ System.out.println("Connection closed successfully");
+ }
+ }
+
+ /**
+ * SSE retry scenario: Tests client respects SSE retry field timing and reconnects
+ * properly (SEP-1699).
+ * @param serverUrl the URL of the MCP server
+ * @throws Exception if any error occurs during execution
+ */
+ private static void runSSERetryScenario(String serverUrl) throws Exception {
+ McpSyncClient client = createClient(serverUrl);
+
+ try {
+ // Initialize client
+ client.initialize();
+
+ System.out.println("Successfully connected to MCP server");
+
+ // List available tools
+ McpSchema.ListToolsResult toolsResult = client.listTools();
+ System.out.println("Successfully listed tools");
+
+ // Call the test_reconnection tool if it exists
+ if (toolsResult != null && toolsResult.tools() != null) {
+ for (McpSchema.Tool tool : toolsResult.tools()) {
+ if ("test_reconnection".equals(tool.name())) {
+ // Call the tool which will trigger SSE stream closure and
+ // reconnection
+ var arguments = new java.util.HashMap();
+
+ McpSchema.CallToolResult result = client
+ .callTool(new McpSchema.CallToolRequest("test_reconnection", arguments));
+
+ System.out.println("Successfully called test_reconnection tool");
+ if (result != null && result.content() != null) {
+ System.out.println("Tool result: " + result.content());
+ }
+ break;
+ }
+ }
+ }
+ }
+ finally {
+ // Close the client (which will close the transport)
+ client.close();
+ System.out.println("Connection closed successfully");
+ }
+ }
+
+}
diff --git a/conformance-tests/client-jdk-http-client/src/main/resources/logback.xml b/conformance-tests/client-jdk-http-client/src/main/resources/logback.xml
new file mode 100644
index 000000000..bb8e3795d
--- /dev/null
+++ b/conformance-tests/client-jdk-http-client/src/main/resources/logback.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conformance-tests/client-spring-http-client/README.md b/conformance-tests/client-spring-http-client/README.md
new file mode 100644
index 000000000..876a86e1d
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/README.md
@@ -0,0 +1,124 @@
+# MCP Conformance Tests - Spring HTTP Client (Auth Suite)
+
+This module provides a conformance test client implementation for the Java MCP SDK's **auth** suite.
+
+OAuth2 support is not implemented in the SDK itself, but we provide hooks to implement the Authorization section of the specification. One such implementation is done in Spring, with Sprign AI and the [mcp-client-security](https://github.com/springaicommunity/mcp-client-security) library.
+
+This is a Spring web application, we interact with it through a normal HTTP-client that follows redirects and performs OAuth2 authorization flows.
+
+## Overview
+
+The conformance test client is designed to work with the [MCP Conformance Test Framework](https://github.com/modelcontextprotocol/conformance). It validates that the Java MCP SDK client, combined with Spring Security's OAuth2 support, properly implements the MCP authorization specification.
+
+Test with @modelcontextprotocol/conformance@0.1.15.
+
+## Conformance Test Results
+
+**Status: 178 passed, 1 failed, 1 warning across 14 scenarios**
+
+| Scenario | Result | Details |
+|---|---|---|
+| auth/metadata-default | ✅ Pass | 12/12 |
+| auth/metadata-var1 | ✅ Pass | 12/12 |
+| auth/metadata-var2 | ✅ Pass | 12/12 |
+| auth/metadata-var3 | ✅ Pass | 12/12 |
+| auth/basic-cimd | ⚠️ Warning | 12/12 passed, 1 warning |
+| auth/scope-from-www-authenticate | ✅ Pass | 13/13 |
+| auth/scope-from-scopes-supported | ✅ Pass | 13/13 |
+| auth/scope-omitted-when-undefined | ✅ Pass | 13/13 |
+| auth/scope-step-up | ❌ Fail | 11/12 (1 failed) |
+| auth/scope-retry-limit | ✅ Pass | 11/11 |
+| auth/token-endpoint-auth-basic | ✅ Pass | 17/17 |
+| auth/token-endpoint-auth-post | ✅ Pass | 17/17 |
+| auth/token-endpoint-auth-none | ✅ Pass | 17/17 |
+| auth/pre-registration | ✅ Pass | 6/6 |
+
+See [VALIDATION_RESULTS.md](../VALIDATION_RESULTS.md) for the full project validation results.
+
+## Architecture
+
+The client is a Spring Boot application that 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
+- **MCP_CONFORMANCE_CONTEXT**: Environment variable with JSON context (used by `auth/pre-registration`)
+- **Server URL**: Passed as the last command-line argument
+
+### Scenario Routing
+
+The application uses Spring's conditional configuration to select the appropriate scenario at startup:
+
+- **`DefaultConfiguration`** — Activated for all scenarios except `auth/pre-registration`. Uses the OAuth2 Authorization Code flow with dynamic client registration via `McpClientOAuth2Configurer`.
+- **`PreRegistrationConfiguration`** — Activated only for `auth/pre-registration`. Uses the Client Credentials flow with pre-registered client credentials read from `MCP_CONFORMANCE_CONTEXT`.
+
+### Key Dependencies
+
+- **Spring Boot 4.0** with Spring Security OAuth2 Client
+- **Spring AI MCP Client** (`spring-ai-starter-mcp-client`)
+- **mcp-client-security** — Community library providing MCP-specific OAuth2 integration (metadata discovery, dynamic client registration, transport context)
+
+## Building
+
+Build the executable JAR:
+
+```bash
+cd conformance-tests/client-spring-http-client
+../../mvnw clean package -DskipTests
+```
+
+This creates an executable JAR at:
+```
+target/client-spring-http-client-0.18.0-SNAPSHOT.jar
+```
+
+## Running Tests
+
+### Using the Conformance Framework
+
+Run the full auth suite:
+
+```bash
+npx @modelcontextprotocol/conformance@0.1.15 client \
+ --spec-version 2025-11-25 \
+ --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-0.18.0-SNAPSHOT.jar" \
+ --suite auth
+```
+
+Run a single scenario:
+
+```bash
+npx @modelcontextprotocol/conformance@0.1.15 client \
+ --spec-version 2025-11-25 \
+ --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-0.18.0-SNAPSHOT.jar" \
+ --scenario auth/metadata-default
+```
+
+Run with verbose output:
+
+```bash
+npx @modelcontextprotocol/conformance@0.1.15 client \
+ --spec-version 2025-11-25 \
+ --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-0.18.0-SNAPSHOT.jar" \
+ --scenario auth/metadata-default \
+ --verbose
+```
+
+### Manual Testing
+
+You can also run the client manually if you have a test server:
+
+```bash
+export MCP_CONFORMANCE_SCENARIO=auth/metadata-default
+java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-0.18.0-SNAPSHOT.jar http://localhost:3000/mcp
+```
+
+## Known Issues
+
+1. **auth/scope-step-up** (1 failure) — The client does not fully handle scope step-up challenges where the server requests additional scopes after initial authorization.
+2. **auth/basic-cimd** (1 warning) — Minor conformance warning in the basic Client-Initiated Metadata Discovery flow.
+
+## References
+
+- [MCP Specification — Authorization](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
+- [MCP Conformance Tests](https://github.com/modelcontextprotocol/conformance)
+- [mcp-client-security Library](https://github.com/springaicommunity/mcp-client-security)
+- [SDK Integration Guide](https://github.com/modelcontextprotocol/conformance/blob/main/SDK_INTEGRATION.md)
diff --git a/conformance-tests/client-spring-http-client/pom.xml b/conformance-tests/client-spring-http-client/pom.xml
new file mode 100644
index 000000000..94923fb5c
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/pom.xml
@@ -0,0 +1,91 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.2
+
+
+ io.modelcontextprotocol.sdk
+ client-spring-http-client
+ 1.0.0-SNAPSHOT
+ jar
+ MCP Conformance Tests - Spring HTTP Client
+ Spring HTTP Client conformance tests for the Java MCP SDK
+ https://github.com/modelcontextprotocol/java-sdk
+
+
+ https://github.com/modelcontextprotocol/java-sdk
+ git://github.com/modelcontextprotocol/java-sdk.git
+ git@github.com/modelcontextprotocol/java-sdk.git
+
+
+
+ 17
+ 2.0.0-M2
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webmvc
+
+
+
+ org.springframework.boot
+ spring-boot-starter-restclient
+
+
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+ ${spring-ai.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+
+ org.springaicommunity
+ mcp-client-security
+ 0.1.2
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ maven-central
+ https://repo.maven.apache.org/maven2/
+
+ false
+
+
+ true
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
+
diff --git a/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceSpringClientApplication.java b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceSpringClientApplication.java
new file mode 100644
index 000000000..00582c9f2
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceSpringClientApplication.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2026-2026 the original author or authors.
+ */
+
+package io.modelcontextprotocol.conformance.client;
+
+import java.util.Optional;
+
+import io.modelcontextprotocol.conformance.client.scenario.Scenario;
+import org.springaicommunity.mcp.security.client.sync.oauth2.metadata.McpMetadataDiscoveryService;
+import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DynamicClientRegistrationService;
+import org.springaicommunity.mcp.security.client.sync.oauth2.registration.InMemoryMcpClientRegistrationRepository;
+
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * MCP Conformance Test Client - Spring HTTP Client Implementation.
+ *
+ *
+ * This client is designed to work with the MCP conformance test framework. It reads the
+ * test scenario from the MCP_CONFORMANCE_SCENARIO environment variable and the server URL
+ * from command-line arguments.
+ *
+ *
+ * It specifically tests the {@code auth} conformance suite. It requires Spring to work.
+ *
+ *
+ * Usage: java -jar client-spring-http-client.jar <server-url>
+ *
+ * @see MCP Conformance
+ * Test Framework
+ */
+@SpringBootApplication
+public class ConformanceSpringClientApplication {
+
+ public static final String REGISTRATION_ID = "default_registration";
+
+ public static void main(String[] args) {
+ SpringApplication.run(ConformanceSpringClientApplication.class, args);
+ }
+
+ @Bean
+ McpMetadataDiscoveryService discovery() {
+ return new McpMetadataDiscoveryService();
+ }
+
+ @Bean
+ InMemoryMcpClientRegistrationRepository clientRegistrationRepository(McpMetadataDiscoveryService discovery) {
+ return new InMemoryMcpClientRegistrationRepository(new DynamicClientRegistrationService(), discovery);
+ }
+
+ @Bean
+ ApplicationRunner conformanceRunner(Optional scenario, ServerUrl serverUrl) {
+ return args -> {
+ String scenarioName = System.getenv("MCP_CONFORMANCE_SCENARIO");
+ if (scenarioName == null || scenarioName.isEmpty()) {
+ System.err.println("Error: MCP_CONFORMANCE_SCENARIO environment variable is not set");
+ System.exit(1);
+ }
+
+ if (scenario.isEmpty()) {
+ System.err.println("Unsupported scenario type");
+ System.exit(1);
+ }
+
+ try {
+ System.out.println("Executing " + scenarioName);
+ scenario.get().execute(serverUrl.value());
+ System.exit(0);
+ }
+ catch (Exception e) {
+ System.err.println("Error: " + e.getMessage());
+ e.printStackTrace();
+ System.exit(1);
+ }
+ };
+ }
+
+ public record ServerUrl(String value) {
+ }
+
+ @Bean
+ ServerUrl serverUrl(ApplicationArguments args) {
+ var nonOptionArgs = args.getNonOptionArgs();
+ if (nonOptionArgs.isEmpty()) {
+ System.err.println("Usage: ConformanceSpringClientApplication ");
+ System.err.println("The server URL must be provided as a command-line argument.");
+ System.err.println("The MCP_CONFORMANCE_SCENARIO environment variable must be set.");
+ System.exit(1);
+ }
+
+ return new ServerUrl(nonOptionArgs.get(nonOptionArgs.size() - 1));
+ }
+
+}
diff --git a/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/McpClientController.java b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/McpClientController.java
new file mode 100644
index 000000000..e02cfd416
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/McpClientController.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2026-2026 the original author or authors.
+ */
+
+package io.modelcontextprotocol.conformance.client;
+
+import io.modelcontextprotocol.conformance.client.scenario.Scenario;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Expose MCP client in a web environment.
+ */
+@RestController
+class McpClientController {
+
+ private final Scenario scenario;
+
+ McpClientController(Scenario scenario) {
+ this.scenario = scenario;
+ }
+
+ @GetMapping("/initialize-mcp-client")
+ public String execute() {
+ this.scenario.getMcpClient().initialize();
+ return "OK";
+ }
+
+}
diff --git a/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/configuration/DefaultConfiguration.java b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/configuration/DefaultConfiguration.java
new file mode 100644
index 000000000..acf26d94e
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/configuration/DefaultConfiguration.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2026-2026 the original author or authors.
+ */
+
+package io.modelcontextprotocol.conformance.client.configuration;
+
+import io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication;
+import io.modelcontextprotocol.conformance.client.scenario.DefaultScenario;
+import org.springaicommunity.mcp.security.client.sync.config.McpClientOAuth2Configurer;
+import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.web.SecurityFilterChain;
+import static io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication.REGISTRATION_ID;
+
+@Configuration
+@ConditionalOnExpression("#{environment['MCP_CONFORMANCE_SCENARIO'] != 'auth/pre-registration'}")
+public class DefaultConfiguration {
+
+ @Bean
+ DefaultScenario defaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
+ ServletWebServerApplicationContext serverCtx,
+ OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository) {
+ return new DefaultScenario(clientRegistrationRepository, serverCtx, oAuth2AuthorizedClientRepository);
+ }
+
+ @Bean
+ SecurityFilterChain securityFilterChain(HttpSecurity http, ConformanceSpringClientApplication.ServerUrl serverUrl) {
+ return http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
+ .with(new McpClientOAuth2Configurer(),
+ mcp -> mcp.registerMcpOAuth2Client(REGISTRATION_ID, serverUrl.value()))
+ .build();
+ }
+
+}
diff --git a/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/configuration/PreRegistrationConfiguration.java b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/configuration/PreRegistrationConfiguration.java
new file mode 100644
index 000000000..afe03f85a
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/configuration/PreRegistrationConfiguration.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2026-2026 the original author or authors.
+ */
+
+package io.modelcontextprotocol.conformance.client.configuration;
+
+import io.modelcontextprotocol.conformance.client.scenario.PreRegistrationScenario;
+import org.springaicommunity.mcp.security.client.sync.config.McpClientOAuth2Configurer;
+import org.springaicommunity.mcp.security.client.sync.oauth2.metadata.McpMetadataDiscoveryService;
+import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@ConditionalOnProperty(name = "mcp.conformance.scenario", havingValue = "auth/pre-registration")
+public class PreRegistrationConfiguration {
+
+ @Bean
+ PreRegistrationScenario defaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
+ McpMetadataDiscoveryService mcpMetadataDiscovery,
+ OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
+ return new PreRegistrationScenario(clientRegistrationRepository, mcpMetadataDiscovery,
+ oAuth2AuthorizedClientService);
+ }
+
+ @Bean
+ SecurityFilterChain securityFilterChain(HttpSecurity http) {
+ return http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
+ .with(new McpClientOAuth2Configurer(), Customizer.withDefaults())
+ .build();
+ }
+
+}
diff --git a/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/DefaultScenario.java b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/DefaultScenario.java
new file mode 100644
index 000000000..d82637de9
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/DefaultScenario.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2026-2026 the original author or authors.
+ */
+
+package io.modelcontextprotocol.conformance.client.scenario;
+
+import java.net.CookieManager;
+import java.net.CookiePolicy;
+import java.net.http.HttpClient;
+import java.time.Duration;
+
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.client.McpSyncClient;
+import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.jspecify.annotations.NonNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springaicommunity.mcp.security.client.sync.AuthenticationMcpTransportContextProvider;
+import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2AuthorizationCodeSyncHttpRequestCustomizer;
+import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
+
+import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
+import org.springframework.http.client.JdkClientHttpRequestFactory;
+import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.web.client.RestClient;
+import static io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication.REGISTRATION_ID;
+
+public class DefaultScenario implements Scenario {
+
+ private static final Logger log = LoggerFactory
+ .getLogger(DefaultScenario.class);
+
+ private final ServletWebServerApplicationContext serverCtx;
+
+ private final DefaultOAuth2AuthorizedClientManager authorizedClientManager;
+
+ private McpSyncClient client;
+
+ public DefaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
+ ServletWebServerApplicationContext serverCtx,
+ OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository) {
+ this.serverCtx = serverCtx;
+ this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository,
+ oAuth2AuthorizedClientRepository);
+ }
+
+ @Override
+ public void execute(String serverUrl) {
+ log.info("Executing DefaultScenario");
+ var testServerUrl = "http://localhost:" + serverCtx.getWebServer().getPort();
+ var testClient = buildTestClient(testServerUrl);
+
+ var customizer = new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(authorizedClientManager, REGISTRATION_ID);
+ HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl)
+ .httpRequestCustomizer(customizer)
+ .build();
+
+ this.client = McpClient.sync(transport)
+ .transportContextProvider(new AuthenticationMcpTransportContextProvider())
+ .clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
+ .requestTimeout(Duration.ofSeconds(30))
+ .build();
+
+ try {
+ testClient.get().uri("/initialize-mcp-client").retrieve().toBodilessEntity();
+ }
+ finally {
+ // Close the client (which will close the transport)
+ this.client.close();
+
+ System.out.println("Connection closed successfully");
+ }
+ }
+
+ private static @NonNull RestClient buildTestClient(String testServerUrl) {
+ var cookieManager = new CookieManager();
+ cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
+ var httpClient = HttpClient.newBuilder()
+ .cookieHandler(cookieManager)
+ .followRedirects(HttpClient.Redirect.ALWAYS)
+ .build();
+ var testClient = RestClient.builder()
+ .baseUrl(testServerUrl)
+ .requestFactory(new JdkClientHttpRequestFactory(httpClient))
+ .build();
+ return testClient;
+ }
+
+ @Override
+ public McpSyncClient getMcpClient() {
+ if (this.client == null) {
+ return Scenario.super.getMcpClient();
+ }
+
+ return this.client;
+ }
+
+}
diff --git a/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/PreRegistrationScenario.java b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/PreRegistrationScenario.java
new file mode 100644
index 000000000..8e6bbe228
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/PreRegistrationScenario.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2026-2026 the original author or authors.
+ */
+
+package io.modelcontextprotocol.conformance.client.scenario;
+
+import java.time.Duration;
+
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springaicommunity.mcp.security.client.sync.AuthenticationMcpTransportContextProvider;
+import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2ClientCredentialsSyncHttpRequestCustomizer;
+import org.springaicommunity.mcp.security.client.sync.oauth2.metadata.McpMetadataDiscoveryService;
+import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
+import tools.jackson.databind.PropertyNamingStrategies;
+import tools.jackson.databind.annotation.JsonNaming;
+import tools.jackson.databind.json.JsonMapper;
+
+import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.registration.ClientRegistrations;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import static io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication.REGISTRATION_ID;
+
+public class PreRegistrationScenario implements Scenario {
+
+ private static final Logger log = LoggerFactory.getLogger(PreRegistrationScenario.class);
+
+ private final JsonMapper mapper;
+
+ private final McpClientRegistrationRepository clientRegistrationRepository;
+
+ private final AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager;
+
+ private final McpMetadataDiscoveryService mcpMetadataDiscovery;
+
+ public PreRegistrationScenario(McpClientRegistrationRepository clientRegistrationRepository,
+ McpMetadataDiscoveryService mcpMetadataDiscovery, OAuth2AuthorizedClientService authorizedClientService) {
+ this.mapper = JsonMapper.shared();
+ this.clientRegistrationRepository = clientRegistrationRepository;
+ this.authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
+ clientRegistrationRepository, authorizedClientService);
+ this.mcpMetadataDiscovery = mcpMetadataDiscovery;
+ }
+
+ @Override
+ public void execute(String serverUrl) {
+ log.info("Executing PreRegistrationScenario");
+
+ var oauthCredentials = extractCredentialsFromContext();
+ setClientRegistration(serverUrl, oauthCredentials);
+
+ var customizer = new OAuth2ClientCredentialsSyncHttpRequestCustomizer(authorizedClientManager, REGISTRATION_ID);
+ HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl)
+ .httpRequestCustomizer(customizer)
+ .build();
+
+ var client = McpClient.sync(transport)
+ .transportContextProvider(new AuthenticationMcpTransportContextProvider())
+ .clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
+ .requestTimeout(Duration.ofSeconds(30))
+ .build();
+
+ try {
+ // Initialize client
+ client.initialize();
+
+ System.out.println("Successfully connected to MCP server");
+ }
+ finally {
+ // Close the client (which will close the transport)
+ client.close();
+
+ System.out.println("Connection closed successfully");
+ }
+ }
+
+ private void setClientRegistration(String mcpServerUrl, PreRegistrationContext oauthCredentials) {
+ var metadata = this.mcpMetadataDiscovery.getMcpMetadata(mcpServerUrl);
+ var registration = ClientRegistrations
+ .fromIssuerLocation(metadata.protectedResourceMetadata().authorizationServers().get(0))
+ .registrationId(REGISTRATION_ID)
+ .clientId(oauthCredentials.clientId())
+ .clientSecret(oauthCredentials.clientSecret())
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .build();
+ clientRegistrationRepository.addPreRegisteredClient(registration,
+ metadata.protectedResourceMetadata().resource());
+ }
+
+ private PreRegistrationContext extractCredentialsFromContext() {
+ String contextEnv = System.getenv("MCP_CONFORMANCE_CONTEXT");
+ if (contextEnv == null || contextEnv.isEmpty()) {
+ var errorMessage = "Error: MCP_CONFORMANCE_CONTEXT environment variable is not set";
+ System.err.println(errorMessage);
+ throw new RuntimeException(errorMessage);
+ }
+
+ return mapper.readValue(contextEnv, PreRegistrationContext.class);
+ }
+
+ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+ private record PreRegistrationContext(String clientId, String clientSecret) {
+
+ }
+
+}
diff --git a/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/Scenario.java b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/Scenario.java
new file mode 100644
index 000000000..9054db83b
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/Scenario.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2026-2026 the original author or authors.
+ */
+
+package io.modelcontextprotocol.conformance.client.scenario;
+
+import io.modelcontextprotocol.client.McpSyncClient;
+
+public interface Scenario {
+
+ default McpSyncClient getMcpClient() {
+ throw new IllegalStateException("Client not set");
+ }
+
+ void execute(String serverUrl);
+
+}
diff --git a/conformance-tests/client-spring-http-client/src/main/resources/application.properties b/conformance-tests/client-spring-http-client/src/main/resources/application.properties
new file mode 100644
index 000000000..0c4a77438
--- /dev/null
+++ b/conformance-tests/client-spring-http-client/src/main/resources/application.properties
@@ -0,0 +1,4 @@
+# Server runs on random port
+server.port=0
+# Disable Spring AI MCP client auto-configuration (we configure the client manually)
+spring.ai.mcp.client.enabled=false
diff --git a/conformance-tests/conformance-baseline.yml b/conformance-tests/conformance-baseline.yml
new file mode 100644
index 000000000..4ab144063
--- /dev/null
+++ b/conformance-tests/conformance-baseline.yml
@@ -0,0 +1,18 @@
+# MCP Java SDK Conformance Test Baseline
+# This file lists known failing scenarios that are expected to fail until fixed.
+# See: https://github.com/modelcontextprotocol/conformance/blob/main/SDK_INTEGRATION.md
+
+server:
+ # Resource subscription not implemented in SDK
+ - resources-subscribe
+ - resources-unsubscribe
+
+client:
+ # SSE retry field handling not implemented
+ # - Client does not parse or respect retry: field timing
+ # - Client does not send Last-Event-ID header
+ - sse-retry
+ # CIMD not implemented yet
+ - auth/basic-cimd
+ # Scope step up beyond initial authorization request not implemented
+ - auth/scope-step-up
diff --git a/mcp-json/pom.xml b/conformance-tests/pom.xml
similarity index 52%
rename from mcp-json/pom.xml
rename to conformance-tests/pom.xml
index 2cbcf3516..d1bef2a24 100644
--- a/mcp-json/pom.xml
+++ b/conformance-tests/pom.xml
@@ -6,34 +6,28 @@
io.modelcontextprotocol.sdkmcp-parent
- 0.18.0-SNAPSHOT
+ 1.1.0-SNAPSHOT
- mcp-json
- jar
- Java MCP SDK JSON Support
- Java MCP SDK JSON Support API
+ conformance-tests
+ pom
+ MCP Conformance Tests
+ Conformance tests for the Java MCP SDKhttps://github.com/modelcontextprotocol/java-sdk
+
https://github.com/modelcontextprotocol/java-sdkgit://github.com/modelcontextprotocol/java-sdk.gitgit@github.com/modelcontextprotocol/java-sdk.git
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
-
-
-
- true
-
-
-
-
-
-
-
-
+
+ true
+
+
+
+ client-jdk-http-client
+ client-spring-http-client
+ server-servlet
+
+
diff --git a/conformance-tests/server-servlet/README.md b/conformance-tests/server-servlet/README.md
new file mode 100644
index 000000000..bd86636b6
--- /dev/null
+++ b/conformance-tests/server-servlet/README.md
@@ -0,0 +1,205 @@
+# MCP Conformance Tests - Servlet Server
+
+This module contains a comprehensive MCP (Model Context Protocol) server implementation for conformance testing using the servlet stack with an embedded Tomcat server and streamable HTTP transport.
+
+## Conformance Test Results
+
+**Status: 37 out of 40 tests passing (92.5%)**
+
+The server has been validated against the official [MCP conformance test suite](https://github.com/modelcontextprotocol/conformance). See [VALIDATION_RESULTS.md](../VALIDATION_RESULTS.md) for detailed results.
+
+### What's Implemented
+
+✅ **Lifecycle & Utilities** (4/4)
+- Server initialization, ping, logging, completion
+
+✅ **Tools** (11/11)
+- Text, image, audio, embedded resources, mixed content
+- Logging, error handling, sampling, elicitation
+- Progress notifications
+
+✅ **Elicitation** (10/10)
+- SEP-1034: Default values for all primitive types
+- SEP-1330: All enum schema variants
+
+✅ **Resources** (4/6)
+- List, read text/binary, templates
+- ⚠️ Subscribe/unsubscribe (SDK limitation)
+
+✅ **Prompts** (4/4)
+- Simple, parameterized, embedded resources, images
+
+✅ **SSE Transport** (2/2)
+- Multiple streams support
+
+✅ **Security** (2/2)
+- ✅ DNS rebinding protection
+
+## Features
+
+- Embedded Tomcat servlet container
+- MCP server using HttpServletStreamableServerTransportProvider
+- Comprehensive test coverage with 15+ tools
+- Streamable HTTP transport with SSE on `/mcp` endpoint
+- Support for all MCP content types (text, image, audio, resources)
+- Advanced features: sampling, elicitation, progress (partial), completion
+
+## Running the Server
+
+To run the conformance server:
+
+```bash
+cd conformance-tests/server-servlet
+../../mvnw compile exec:java -Dexec.mainClass="io.modelcontextprotocol.conformance.server.ConformanceServlet"
+```
+
+Or from the root directory:
+
+```bash
+./mvnw compile exec:java -pl conformance-tests/server-servlet -Dexec.mainClass="io.modelcontextprotocol.conformance.server.ConformanceServlet"
+```
+
+The server will start on port 8080 with the MCP endpoint at `/mcp`.
+
+## Running Conformance Tests
+
+Once the server is running, you can validate it against the official MCP conformance test suite using `npx`:
+
+### Run Full Active Test Suite
+
+```bash
+npx @modelcontextprotocol/conformance server --url http://localhost:8080/mcp --suite active
+```
+
+### Run Specific Scenarios
+
+```bash
+# Test tools
+npx @modelcontextprotocol/conformance server --url http://localhost:8080/mcp --scenario tools-list --verbose
+
+# Test prompts
+npx @modelcontextprotocol/conformance server --url http://localhost:8080/mcp --scenario prompts-list --verbose
+
+# Test resources
+npx @modelcontextprotocol/conformance server --url http://localhost:8080/mcp --scenario resources-read-text --verbose
+
+# Test elicitation with defaults
+npx @modelcontextprotocol/conformance server --url http://localhost:8080/mcp --scenario elicitation-sep1034-defaults --verbose
+```
+
+### Available Test Suites
+
+- `active` (default) - All active/stable tests (30 scenarios)
+- `all` - All tests including pending/experimental
+- `pending` - Only pending/experimental tests
+
+### Common Scenarios
+
+**Lifecycle & Utilities:**
+- `server-initialize` - Server initialization
+- `ping` - Ping utility
+- `logging-set-level` - Logging configuration
+- `completion-complete` - Argument completion
+
+**Tools:**
+- `tools-list` - List available tools
+- `tools-call-simple-text` - Simple text response
+- `tools-call-image` - Image content
+- `tools-call-audio` - Audio content
+- `tools-call-with-logging` - Logging during execution
+- `tools-call-with-progress` - Progress notifications
+- `tools-call-sampling` - LLM sampling
+- `tools-call-elicitation` - User input requests
+
+**Resources:**
+- `resources-list` - List resources
+- `resources-read-text` - Read text resource
+- `resources-read-binary` - Read binary resource
+- `resources-templates-read` - Resource templates
+- `resources-subscribe` - Subscribe to resource updates
+- `resources-unsubscribe` - Unsubscribe from updates
+
+**Prompts:**
+- `prompts-list` - List prompts
+- `prompts-get-simple` - Simple prompt
+- `prompts-get-with-args` - Parameterized prompt
+- `prompts-get-embedded-resource` - Prompt with resource
+- `prompts-get-with-image` - Prompt with image
+
+**Elicitation:**
+- `elicitation-sep1034-defaults` - Default values (SEP-1034)
+- `elicitation-sep1330-enums` - Enum schemas (SEP-1330)
+
+## Testing with curl
+
+You can also test the endpoint manually:
+
+```bash
+# Check endpoint (will show SSE requirement)
+curl -X GET http://localhost:8080/mcp
+
+# Initialize session with proper headers
+curl -X POST http://localhost:8080/mcp \
+ -H "Content-Type: application/json" \
+ -H "Accept: text/event-stream" \
+ -H "mcp-session-id: test-session-123" \
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}}}'
+```
+
+## Architecture
+
+- **Transport**: HttpServletStreamableServerTransportProvider (streamable HTTP with SSE)
+- **Container**: Embedded Apache Tomcat
+- **Protocol**: Streamable HTTP with Server-Sent Events
+- **Port**: 8080 (default)
+- **Endpoint**: `/mcp`
+- **Request Timeout**: 30 seconds
+
+## Implemented Tools
+
+### Content Type Tools
+- `test_simple_text` - Returns simple text content
+- `test_image_content` - Returns a minimal PNG image (1x1 red pixel)
+- `test_audio_content` - Returns a minimal WAV audio file
+- `test_embedded_resource` - Returns embedded resource content
+- `test_multiple_content_types` - Returns mixed text, image, and resource content
+
+### Behavior Tools
+- `test_tool_with_logging` - Sends log notifications during execution
+- `test_error_handling` - Intentionally returns an error for testing
+- `test_tool_with_progress` - Reports progress notifications (⚠️ SDK issue)
+
+### Interactive Tools
+- `test_sampling` - Requests LLM sampling from client
+- `test_elicitation` - Requests user input from client
+- `test_elicitation_sep1034_defaults` - Elicitation with default values (SEP-1034)
+- `test_elicitation_sep1330_enums` - Elicitation with enum schemas (SEP-1330)
+
+## Implemented Prompts
+
+- `test_simple_prompt` - Simple prompt without arguments
+- `test_prompt_with_arguments` - Prompt with required arguments (arg1, arg2)
+- `test_prompt_with_embedded_resource` - Prompt with embedded resource content
+- `test_prompt_with_image` - Prompt with image content
+
+## Implemented Resources
+
+- `test://static-text` - Static text resource
+- `test://static-binary` - Static binary resource (PNG image)
+- `test://watched-resource` - Resource that can be subscribed to
+- `test://template/{id}/data` - Resource template with parameter substitution
+
+## Known Limitations
+
+See [VALIDATION_RESULTS.md](../VALIDATION_RESULTS.md) for details on:
+
+1. **Resource Subscriptions** - Not implemented in Java SDK
+2. **DNS Rebinding Protection** - Missing Host/Origin validation
+
+These are SDK-level limitations that require fixes in the core framework.
+
+## References
+
+- [MCP Specification](https://modelcontextprotocol.io/specification/)
+- [MCP Conformance Tests](https://github.com/modelcontextprotocol/conformance)
+- [SDK Integration Guide](https://github.com/modelcontextprotocol/conformance/blob/main/SDK_INTEGRATION.md)
diff --git a/conformance-tests/server-servlet/pom.xml b/conformance-tests/server-servlet/pom.xml
new file mode 100644
index 000000000..68da42158
--- /dev/null
+++ b/conformance-tests/server-servlet/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ io.modelcontextprotocol.sdk
+ conformance-tests
+ 1.1.0-SNAPSHOT
+
+ server-servlet
+ jar
+ MCP Conformance Tests - Servlet Server
+ Servlet Server conformance tests for the Java MCP SDK
+ https://github.com/modelcontextprotocol/java-sdk
+
+
+ https://github.com/modelcontextprotocol/java-sdk
+ git://github.com/modelcontextprotocol/java-sdk.git
+ git@github.com/modelcontextprotocol/java-sdk.git
+
+
+
+ true
+
+
+
+
+ io.modelcontextprotocol.sdk
+ mcp
+ 1.1.0-SNAPSHOT
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j-api.version}
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ ${jakarta.servlet.version}
+ provided
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ ${tomcat.version}
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+ io.modelcontextprotocol.conformance.server.ConformanceServlet
+
+
+
+
+
+
\ No newline at end of file
diff --git a/conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java b/conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java
new file mode 100644
index 000000000..3d162a5de
--- /dev/null
+++ b/conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java
@@ -0,0 +1,596 @@
+package io.modelcontextprotocol.conformance.server;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.server.McpServerFeatures;
+import io.modelcontextprotocol.server.transport.DefaultServerTransportSecurityValidator;
+import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
+import io.modelcontextprotocol.spec.McpSchema.AudioContent;
+import io.modelcontextprotocol.spec.McpSchema.BlobResourceContents;
+import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.CompleteResult;
+import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest;
+import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult;
+import io.modelcontextprotocol.spec.McpSchema.ElicitRequest;
+import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
+import io.modelcontextprotocol.spec.McpSchema.EmbeddedResource;
+import io.modelcontextprotocol.spec.McpSchema.GetPromptResult;
+import io.modelcontextprotocol.spec.McpSchema.ImageContent;
+import io.modelcontextprotocol.spec.McpSchema.JsonSchema;
+import io.modelcontextprotocol.spec.McpSchema.LoggingLevel;
+import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
+import io.modelcontextprotocol.spec.McpSchema.ProgressNotification;
+import io.modelcontextprotocol.spec.McpSchema.Prompt;
+import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
+import io.modelcontextprotocol.spec.McpSchema.PromptMessage;
+import io.modelcontextprotocol.spec.McpSchema.PromptReference;
+import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
+import io.modelcontextprotocol.spec.McpSchema.Resource;
+import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
+import io.modelcontextprotocol.spec.McpSchema.Role;
+import io.modelcontextprotocol.spec.McpSchema.SamplingMessage;
+import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
+import io.modelcontextprotocol.spec.McpSchema.TextContent;
+import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
+import io.modelcontextprotocol.spec.McpSchema.Tool;
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.startup.Tomcat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConformanceServlet {
+
+ private static final Logger logger = LoggerFactory.getLogger(ConformanceServlet.class);
+
+ private static final int PORT = 8080;
+
+ private static final String MCP_ENDPOINT = "/mcp";
+
+ private static final JsonSchema EMPTY_JSON_SCHEMA = new JsonSchema("object", Collections.emptyMap(), null, null,
+ null, null);
+
+ // Minimal 1x1 red pixel PNG (base64 encoded)
+ private static final String RED_PIXEL_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
+
+ // Minimal WAV file (base64 encoded) - 1 sample at 8kHz
+ private static final String MINIMAL_WAV = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQAAAAA=";
+
+ public static void main(String[] args) throws Exception {
+ logger.info("Starting MCP Conformance Tests - Servlet Server");
+
+ HttpServletStreamableServerTransportProvider transportProvider = HttpServletStreamableServerTransportProvider
+ .builder()
+ .mcpEndpoint(MCP_ENDPOINT)
+ .keepAliveInterval(Duration.ofSeconds(30))
+ .securityValidator(DefaultServerTransportSecurityValidator.builder()
+ .allowedOrigin("http://localhost:*")
+ .allowedHost("localhost:*")
+ .build())
+ .build();
+
+ // Build server with all conformance test features
+ var mcpServer = McpServer.sync(transportProvider)
+ .serverInfo("mcp-conformance-server", "1.0.0")
+ .capabilities(ServerCapabilities.builder()
+ .completions()
+ .resources(true, false)
+ .tools(false)
+ .prompts(false)
+ .build())
+ .tools(createToolSpecs())
+ .prompts(createPromptSpecs())
+ .resources(createResourceSpecs())
+ .resourceTemplates(createResourceTemplateSpecs())
+ .completions(createCompletionSpecs())
+ .requestTimeout(Duration.ofSeconds(30))
+ .build();
+
+ // Set up embedded Tomcat
+ Tomcat tomcat = createEmbeddedTomcat(transportProvider);
+
+ try {
+ tomcat.start();
+ logger.info("Conformance MCP Servlet Server started on port {} with endpoint {}", PORT, MCP_ENDPOINT);
+ logger.info("Server URL: http://localhost:{}{}", PORT, MCP_ENDPOINT);
+
+ // Keep the server running
+ tomcat.getServer().await();
+ }
+ catch (LifecycleException e) {
+ logger.error("Failed to start Tomcat server", e);
+ throw e;
+ }
+ finally {
+ logger.info("Shutting down MCP server...");
+ mcpServer.closeGracefully();
+ try {
+ tomcat.stop();
+ tomcat.destroy();
+ }
+ catch (LifecycleException e) {
+ logger.error("Error during Tomcat shutdown", e);
+ }
+ }
+ }
+
+ private static Tomcat createEmbeddedTomcat(HttpServletStreamableServerTransportProvider transportProvider) {
+ Tomcat tomcat = new Tomcat();
+ tomcat.setPort(PORT);
+
+ String baseDir = System.getProperty("java.io.tmpdir");
+ tomcat.setBaseDir(baseDir);
+
+ Context context = tomcat.addContext("", baseDir);
+
+ // Add the MCP servlet to Tomcat
+ org.apache.catalina.Wrapper wrapper = context.createWrapper();
+ wrapper.setName("mcpServlet");
+ wrapper.setServlet(transportProvider);
+ wrapper.setLoadOnStartup(1);
+ wrapper.setAsyncSupported(true);
+ context.addChild(wrapper);
+ context.addServletMappingDecoded("/*", "mcpServlet");
+
+ var connector = tomcat.getConnector();
+ connector.setAsyncTimeout(30000);
+ return tomcat;
+ }
+
+ private static List createToolSpecs() {
+ return List.of(
+ // test_simple_text - Returns simple text content
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_simple_text")
+ .description("Returns simple text content for testing")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_simple_text' called");
+ return CallToolResult.builder()
+ .content(List.of(new TextContent("This is a simple text response for testing.")))
+ .isError(false)
+ .build();
+ })
+ .build(),
+
+ // test_image_content - Returns image content
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_image_content")
+ .description("Returns image content for testing")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_image_content' called");
+ return CallToolResult.builder()
+ .content(List.of(new ImageContent(null, RED_PIXEL_PNG, "image/png")))
+ .isError(false)
+ .build();
+ })
+ .build(),
+
+ // test_audio_content - Returns audio content
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_audio_content")
+ .description("Returns audio content for testing")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_audio_content' called");
+ return CallToolResult.builder()
+ .content(List.of(new AudioContent(null, MINIMAL_WAV, "audio/wav")))
+ .isError(false)
+ .build();
+ })
+ .build(),
+
+ // test_embedded_resource - Returns embedded resource content
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_embedded_resource")
+ .description("Returns embedded resource content for testing")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_embedded_resource' called");
+ TextResourceContents resourceContents = new TextResourceContents("test://embedded-resource",
+ "text/plain", "This is an embedded resource content.");
+ EmbeddedResource embeddedResource = new EmbeddedResource(null, resourceContents);
+ return CallToolResult.builder().content(List.of(embeddedResource)).isError(false).build();
+ })
+ .build(),
+
+ // test_multiple_content_types - Returns multiple content types
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_multiple_content_types")
+ .description("Returns multiple content types for testing")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_multiple_content_types' called");
+ TextResourceContents resourceContents = new TextResourceContents(
+ "test://mixed-content-resource", "application/json",
+ "{\"test\":\"data\",\"value\":123}");
+ EmbeddedResource embeddedResource = new EmbeddedResource(null, resourceContents);
+ return CallToolResult.builder()
+ .content(List.of(new TextContent("Multiple content types test:"),
+ new ImageContent(null, RED_PIXEL_PNG, "image/png"), embeddedResource))
+ .isError(false)
+ .build();
+ })
+ .build(),
+
+ // test_tool_with_logging - Tool that sends log messages during execution
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_tool_with_logging")
+ .description("Tool that sends log messages during execution")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_tool_with_logging' called");
+ // Send log notifications
+ exchange.loggingNotification(LoggingMessageNotification.builder()
+ .level(LoggingLevel.INFO)
+ .data("Tool execution started")
+ .build());
+ exchange.loggingNotification(LoggingMessageNotification.builder()
+ .level(LoggingLevel.INFO)
+ .data("Tool processing data")
+ .build());
+ exchange.loggingNotification(LoggingMessageNotification.builder()
+ .level(LoggingLevel.INFO)
+ .data("Tool execution completed")
+ .build());
+ return CallToolResult.builder()
+ .content(List.of(new TextContent("Tool execution completed with logging")))
+ .isError(false)
+ .build();
+ })
+ .build(),
+
+ // test_error_handling - Tool that always returns an error
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_error_handling")
+ .description("Tool that returns an error for testing error handling")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_error_handling' called");
+ return CallToolResult.builder()
+ .content(List.of(new TextContent("This tool intentionally returns an error for testing")))
+ .isError(true)
+ .build();
+ })
+ .build(),
+
+ // test_tool_with_progress - Tool that reports progress
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_tool_with_progress")
+ .description("Tool that reports progress notifications")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_tool_with_progress' called");
+ Object progressToken = request.meta().get("progressToken");
+ if (progressToken != null) {
+ // Send progress notifications sequentially
+ exchange.progressNotification(new ProgressNotification(progressToken, 0.0, 100.0, null));
+ // try {
+ // Thread.sleep(50);
+ // }
+ // catch (InterruptedException e) {
+ // Thread.currentThread().interrupt();
+ // }
+ exchange.progressNotification(new ProgressNotification(progressToken, 50.0, 100.0, null));
+ // try {
+ // Thread.sleep(50);
+ // }
+ // catch (InterruptedException e) {
+ // Thread.currentThread().interrupt();
+ // }
+ exchange.progressNotification(new ProgressNotification(progressToken, 100.0, 100.0, null));
+ return CallToolResult.builder()
+ .content(List.of(new TextContent("Tool execution completed with progress")))
+ .isError(false)
+ .build();
+ }
+ else {
+ // No progress token, just execute with delays
+ // try {
+ // Thread.sleep(100);
+ // }
+ // catch (InterruptedException e) {
+ // Thread.currentThread().interrupt();
+ // }
+ return CallToolResult.builder()
+ .content(List.of(new TextContent("Tool execution completed without progress")))
+ .isError(false)
+ .build();
+ }
+ })
+ .build(),
+
+ // test_sampling - Tool that requests LLM sampling from client
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_sampling")
+ .description("Tool that requests LLM sampling from client")
+ .inputSchema(new JsonSchema("object",
+ Map.of("prompt",
+ Map.of("type", "string", "description", "The prompt to send to the LLM")),
+ List.of("prompt"), null, null, null))
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_sampling' called");
+ String prompt = (String) request.arguments().get("prompt");
+
+ // Request sampling from client
+ CreateMessageRequest samplingRequest = CreateMessageRequest.builder()
+ .messages(List.of(new SamplingMessage(Role.USER, new TextContent(prompt))))
+ .maxTokens(100)
+ .build();
+
+ CreateMessageResult response = exchange.createMessage(samplingRequest);
+ String responseText = "LLM response: " + ((TextContent) response.content()).text();
+ return CallToolResult.builder()
+ .content(List.of(new TextContent(responseText)))
+ .isError(false)
+ .build();
+ })
+ .build(),
+
+ // test_elicitation - Tool that requests user input from client
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_elicitation")
+ .description("Tool that requests user input from client")
+ .inputSchema(new JsonSchema("object",
+ Map.of("message",
+ Map.of("type", "string", "description", "The message to show the user")),
+ List.of("message"), null, null, null))
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_elicitation' called");
+ String message = (String) request.arguments().get("message");
+
+ // Request elicitation from client
+ Map requestedSchema = Map.of("type", "object", "properties",
+ Map.of("username", Map.of("type", "string", "description", "User's response"), "email",
+ Map.of("type", "string", "description", "User's email address")),
+ "required", List.of("username", "email"));
+
+ ElicitRequest elicitRequest = new ElicitRequest(message, requestedSchema);
+
+ ElicitResult response = exchange.createElicitation(elicitRequest);
+ String responseText = "User response: action=" + response.action() + ", content="
+ + response.content();
+ return CallToolResult.builder()
+ .content(List.of(new TextContent(responseText)))
+ .isError(false)
+ .build();
+ })
+ .build(),
+
+ // test_elicitation_sep1034_defaults - Tool with default values for all
+ // primitive types
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_elicitation_sep1034_defaults")
+ .description("Tool that requests elicitation with default values for all primitive types")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_elicitation_sep1034_defaults' called");
+
+ // Create schema with default values for all primitive types
+ Map requestedSchema = Map.of("type", "object", "properties",
+ Map.of("name", Map.of("type", "string", "default", "John Doe"), "age",
+ Map.of("type", "integer", "default", 30), "score",
+ Map.of("type", "number", "default", 95.5), "status",
+ Map.of("type", "string", "enum", List.of("active", "inactive", "pending"),
+ "default", "active"),
+ "verified", Map.of("type", "boolean", "default", true)),
+ "required", List.of("name", "age", "score", "status", "verified"));
+
+ ElicitRequest elicitRequest = new ElicitRequest("Please provide your information with defaults",
+ requestedSchema);
+
+ ElicitResult response = exchange.createElicitation(elicitRequest);
+ String responseText = "Elicitation completed: action=" + response.action() + ", content="
+ + response.content();
+ return CallToolResult.builder()
+ .content(List.of(new TextContent(responseText)))
+ .isError(false)
+ .build();
+ })
+ .build(),
+
+ // test_elicitation_sep1330_enums - Tool with enum schema improvements
+ McpServerFeatures.SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("test_elicitation_sep1330_enums")
+ .description("Tool that requests elicitation with enum schema improvements")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build())
+ .callHandler((exchange, request) -> {
+ logger.info("Tool 'test_elicitation_sep1330_enums' called");
+
+ // Create schema with all 5 enum variants
+ Map requestedSchema = Map.of("type", "object", "properties", Map.of(
+ // 1. Untitled single-select
+ "untitledSingle",
+ Map.of("type", "string", "enum", List.of("option1", "option2", "option3")),
+ // 2. Titled single-select using oneOf with const/title
+ "titledSingle",
+ Map.of("type", "string", "oneOf",
+ List.of(Map.of("const", "value1", "title", "First Option"),
+ Map.of("const", "value2", "title", "Second Option"),
+ Map.of("const", "value3", "title", "Third Option"))),
+ // 3. Legacy titled using enumNames (deprecated)
+ "legacyEnum",
+ Map.of("type", "string", "enum", List.of("opt1", "opt2", "opt3"), "enumNames",
+ List.of("Option One", "Option Two", "Option Three")),
+ // 4. Untitled multi-select
+ "untitledMulti",
+ Map.of("type", "array", "items",
+ Map.of("type", "string", "enum", List.of("option1", "option2", "option3"))),
+ // 5. Titled multi-select using items.anyOf with
+ // const/title
+ "titledMulti",
+ Map.of("type", "array", "items",
+ Map.of("anyOf",
+ List.of(Map.of("const", "value1", "title", "First Choice"),
+ Map.of("const", "value2", "title", "Second Choice"),
+ Map.of("const", "value3", "title", "Third Choice"))))),
+ "required", List.of("untitledSingle", "titledSingle", "legacyEnum", "untitledMulti",
+ "titledMulti"));
+
+ ElicitRequest elicitRequest = new ElicitRequest("Select your preferences", requestedSchema);
+
+ ElicitResult response = exchange.createElicitation(elicitRequest);
+ String responseText = "Elicitation completed: action=" + response.action() + ", content="
+ + response.content();
+ return CallToolResult.builder()
+ .content(List.of(new TextContent(responseText)))
+ .isError(false)
+ .build();
+ })
+ .build());
+ }
+
+ private static List createPromptSpecs() {
+ return List.of(
+ // test_simple_prompt - Simple prompt without arguments
+ new McpServerFeatures.SyncPromptSpecification(
+ new Prompt("test_simple_prompt", null, "A simple prompt for testing", List.of()),
+ (exchange, request) -> {
+ logger.info("Prompt 'test_simple_prompt' requested");
+ return new GetPromptResult(null, List.of(new PromptMessage(Role.USER,
+ new TextContent("This is a simple prompt for testing."))));
+ }),
+
+ // test_prompt_with_arguments - Prompt with arguments
+ new McpServerFeatures.SyncPromptSpecification(
+ new Prompt("test_prompt_with_arguments", null, "A prompt with arguments for testing",
+ List.of(new PromptArgument("arg1", "First test argument", true),
+ new PromptArgument("arg2", "Second test argument", true))),
+ (exchange, request) -> {
+ logger.info("Prompt 'test_prompt_with_arguments' requested");
+ String arg1 = (String) request.arguments().get("arg1");
+ String arg2 = (String) request.arguments().get("arg2");
+ String text = String.format("Prompt with arguments: arg1='%s', arg2='%s'", arg1, arg2);
+ return new GetPromptResult(null,
+ List.of(new PromptMessage(Role.USER, new TextContent(text))));
+ }),
+
+ // test_prompt_with_embedded_resource - Prompt with embedded resource
+ new McpServerFeatures.SyncPromptSpecification(
+ new Prompt("test_prompt_with_embedded_resource", null,
+ "A prompt with embedded resource for testing",
+ List.of(new PromptArgument("resourceUri", "URI of the resource to embed", true))),
+ (exchange, request) -> {
+ logger.info("Prompt 'test_prompt_with_embedded_resource' requested");
+ String resourceUri = (String) request.arguments().get("resourceUri");
+ TextResourceContents resourceContents = new TextResourceContents(resourceUri, "text/plain",
+ "Embedded resource content for testing.");
+ EmbeddedResource embeddedResource = new EmbeddedResource(null, resourceContents);
+ return new GetPromptResult(null,
+ List.of(new PromptMessage(Role.USER, embeddedResource), new PromptMessage(Role.USER,
+ new TextContent("Please process the embedded resource above."))));
+ }),
+
+ // test_prompt_with_image - Prompt with image content
+ new McpServerFeatures.SyncPromptSpecification(new Prompt("test_prompt_with_image", null,
+ "A prompt with image content for testing", List.of()), (exchange, request) -> {
+ logger.info("Prompt 'test_prompt_with_image' requested");
+ return new GetPromptResult(null, List.of(
+ new PromptMessage(Role.USER, new ImageContent(null, RED_PIXEL_PNG, "image/png")),
+ new PromptMessage(Role.USER, new TextContent("Please analyze the image above."))));
+ }));
+ }
+
+ private static List createResourceSpecs() {
+ return List.of(
+ // test://static-text - Static text resource
+ new McpServerFeatures.SyncResourceSpecification(Resource.builder()
+ .uri("test://static-text")
+ .name("Static Text Resource")
+ .description("A static text resource for testing")
+ .mimeType("text/plain")
+ .build(), (exchange, request) -> {
+ logger.info("Resource 'test://static-text' requested");
+ return new ReadResourceResult(List.of(new TextResourceContents("test://static-text",
+ "text/plain", "This is the content of the static text resource.")));
+ }),
+
+ // test://static-binary - Static binary resource (image)
+ new McpServerFeatures.SyncResourceSpecification(Resource.builder()
+ .uri("test://static-binary")
+ .name("Static Binary Resource")
+ .description("A static binary resource for testing")
+ .mimeType("image/png")
+ .build(), (exchange, request) -> {
+ logger.info("Resource 'test://static-binary' requested");
+ return new ReadResourceResult(
+ List.of(new BlobResourceContents("test://static-binary", "image/png", RED_PIXEL_PNG)));
+ }),
+
+ // test://watched-resource - Resource that can be subscribed to
+ new McpServerFeatures.SyncResourceSpecification(Resource.builder()
+ .uri("test://watched-resource")
+ .name("Watched Resource")
+ .description("A resource that can be subscribed to for updates")
+ .mimeType("text/plain")
+ .build(), (exchange, request) -> {
+ logger.info("Resource 'test://watched-resource' requested");
+ return new ReadResourceResult(List.of(new TextResourceContents("test://watched-resource",
+ "text/plain", "This is a watched resource content.")));
+ }));
+ }
+
+ private static List createResourceTemplateSpecs() {
+ return List.of(
+ // test://template/{id}/data - Resource template with parameter
+ // substitution
+ new McpServerFeatures.SyncResourceTemplateSpecification(ResourceTemplate.builder()
+ .uriTemplate("test://template/{id}/data")
+ .name("Template Resource")
+ .description("A resource template for testing parameter substitution")
+ .mimeType("application/json")
+ .build(), (exchange, request) -> {
+ logger.info("Resource template 'test://template/{{id}}/data' requested for URI: {}",
+ request.uri());
+ // Extract id from URI
+ String uri = request.uri();
+ String id = uri.replaceAll("test://template/(.+)/data", "$1");
+ String jsonContent = String
+ .format("{\"id\":\"%s\",\"templateTest\":true,\"data\":\"Data for ID: %s\"}", id, id);
+ return new ReadResourceResult(
+ List.of(new TextResourceContents(uri, "application/json", jsonContent)));
+ }));
+ }
+
+ private static List createCompletionSpecs() {
+ return List.of(
+ // Completion for test_prompt_with_arguments
+ new McpServerFeatures.SyncCompletionSpecification(new PromptReference("test_prompt_with_arguments"),
+ (exchange, request) -> {
+ logger.info("Completion requested for prompt 'test_prompt_with_arguments', argument: {}",
+ request.argument().name());
+ // Return minimal completion with required fields
+ return new CompleteResult(new CompleteResult.CompleteCompletion(List.of(), 0, false));
+ }));
+ }
+
+}
diff --git a/conformance-tests/server-servlet/src/main/resources/logback.xml b/conformance-tests/server-servlet/src/main/resources/logback.xml
new file mode 100644
index 000000000..af69ac902
--- /dev/null
+++ b/conformance-tests/server-servlet/src/main/resources/logback.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
diff --git a/docs/blog/.authors.yml b/docs/blog/.authors.yml
new file mode 100644
index 000000000..7b255c403
--- /dev/null
+++ b/docs/blog/.authors.yml
@@ -0,0 +1,5 @@
+authors:
+ mcp-team:
+ name: MCP Java SDK Team
+ description: Maintainers of the MCP Java SDK
+ avatar: https://github.com/modelcontextprotocol.png
diff --git a/docs/blog/index.md b/docs/blog/index.md
new file mode 100644
index 000000000..e61459078
--- /dev/null
+++ b/docs/blog/index.md
@@ -0,0 +1 @@
+# News
diff --git a/docs/blog/posts/mcp-server-performance-benchmark.md b/docs/blog/posts/mcp-server-performance-benchmark.md
new file mode 100644
index 000000000..a08b807b6
--- /dev/null
+++ b/docs/blog/posts/mcp-server-performance-benchmark.md
@@ -0,0 +1,72 @@
+---
+date: 2026-02-15
+authors:
+ - mcp-team
+categories:
+ - Performance
+ - Benchmarks
+---
+
+# Java Leads MCP Server Performance Benchmarks with Sub-Millisecond Latency
+
+A comprehensive independent benchmark of MCP server implementations across four major languages puts Java at the top of the performance charts — delivering sub-millisecond latency, the highest throughput, and the best CPU efficiency of all tested platforms.
+
+
+
+## The Benchmark
+
+[TM Dev Lab](https://www.tmdevlab.com/mcp-server-performance-benchmark.html) published a rigorous performance comparison of MCP server implementations spanning **3.9 million total requests** across three independent test rounds. The benchmark evaluated four implementations under identical conditions:
+
+- **Java** — Spring Boot 4.0.0 + Spring AI 2.0.0-M2 on Java 21
+- **Go** — Official MCP SDK v1.2.0
+- **Node.js** — @modelcontextprotocol/sdk v1.26.0
+- **Python** — FastMCP 2.12.0+ with FastAPI 0.109.0+
+
+Each server was tested with 50 concurrent virtual users over 5-minute sustained runs in Docker containers (1-core CPU, 1GB memory) on Ubuntu 24.04.3 LTS. Four standardized benchmark tools measured CPU-intensive, I/O-intensive, data transformation, and latency-handling scenarios — all with a **0% error rate** across every implementation.
+
+## Java's Performance Highlights
+
+The results speak for themselves:
+
+| Server | Avg Latency | Throughput (RPS) | CPU Efficiency (RPS/CPU%) |
+|------------|-------------|------------------|---------------------------|
+| **Java** | **0.835 ms** | **1,624** | **57.2** |
+| Go | 0.855 ms | 1,624 | 50.4 |
+| Node.js | 10.66 ms | 559 | 5.7 |
+| Python | 26.45 ms | 292 | 3.2 |
+
+```mermaid
+---
+config:
+ xyChart:
+ width: 700
+ height: 400
+ themeVariables:
+ xyChart:
+ backgroundColor: transparent
+---
+xychart-beta
+ title "Average Latency Comparison (milliseconds)"
+ x-axis [Java, Go, "Node.js", Python]
+ y-axis "Latency (ms)" 0 --> 30
+ bar [0.84, 0.86, 10.66, 26.45]
+```
+
+Java achieved the **lowest average latency** at 0.835 ms — edging out Go's 0.855 ms — while matching its throughput at 1,624 requests per second. Where Java truly stands out is **CPU efficiency**: at 57.2 RPS per CPU%, it extracts more performance per compute cycle than any other implementation, including Go (50.4).
+
+In CPU-bound workloads like Fibonacci calculation, Java excelled with a **0.369 ms** response time, showcasing the JVM's highly optimized just-in-time compilation.
+
+## A Clear Performance Tier
+
+The benchmark reveals two distinct performance tiers:
+
+- **High-performance tier**: Java and Go deliver sub-millisecond latencies and 1,600+ RPS
+- **Standard tier**: Node.js (12x slower) and Python (31x slower) trail significantly
+
+Java's throughput is **2.9x higher than Node.js** and **5.6x higher than Python**. For latency-sensitive MCP deployments, the difference is even more pronounced — Java responds **12.8x faster than Node.js** and **31.7x faster than Python**.
+
+## What This Means for MCP Developers
+
+For teams building production MCP servers that need to handle high concurrency and low-latency tool interactions, Java with Spring Boot and Spring AI provides a battle-tested, high-performance foundation. The JVM's mature ecosystem, strong typing, and proven scalability make it an excellent choice for enterprise MCP deployments where performance and reliability are paramount.
+
+The full benchmark details, methodology, and raw data are available at [TM Dev Lab](https://www.tmdevlab.com/mcp-server-performance-benchmark.html).
diff --git a/docs/client.md b/docs/client.md
new file mode 100644
index 000000000..6a99928c5
--- /dev/null
+++ b/docs/client.md
@@ -0,0 +1,439 @@
+---
+title: MCP Client
+description: Learn how to use the Model Context Protocol (MCP) client to interact with MCP servers
+---
+
+# MCP Client
+
+The MCP Client is a key component in the Model Context Protocol (MCP) architecture, responsible for establishing and managing connections with MCP servers. It implements the client-side of the protocol, handling:
+
+- Protocol version negotiation to ensure compatibility with servers
+- Capability negotiation to determine available features
+- Message transport and JSON-RPC communication
+- Tool discovery and execution with optional schema validation
+- Resource access and management
+- Prompt system interactions
+- Optional features like roots management, sampling, and elicitation support
+- Progress tracking for long-running operations
+
+!!! tip
+ The core `io.modelcontextprotocol.sdk:mcp` module provides STDIO, SSE, and Streamable HTTP client transport implementations without requiring external web frameworks.
+
+ The Spring-specific WebFlux transport (`mcp-spring-webflux`) is now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`) and is no longer shipped by this SDK.
+ See the [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) documentation for Spring-based client setup.
+
+The client provides both synchronous and asynchronous APIs for flexibility in different application contexts.
+
+=== "Sync API"
+
+ ```java
+ // Create a sync client with custom configuration
+ McpSyncClient client = McpClient.sync(transport)
+ .requestTimeout(Duration.ofSeconds(10))
+ .capabilities(ClientCapabilities.builder()
+ .roots(true) // Enable roots capability
+ .sampling() // Enable sampling capability
+ .elicitation() // Enable elicitation capability
+ .build())
+ .sampling(request -> new CreateMessageResult(response))
+ .elicitation(request -> new ElicitResult(ElicitResult.Action.ACCEPT, content))
+ .build();
+
+ // Initialize connection
+ client.initialize();
+
+ // List available tools
+ ListToolsResult tools = client.listTools();
+
+ // Call a tool
+ CallToolResult result = client.callTool(
+ new CallToolRequest("calculator",
+ Map.of("operation", "add", "a", 2, "b", 3))
+ );
+
+ // List and read resources
+ ListResourcesResult resources = client.listResources();
+ ReadResourceResult resource = client.readResource(
+ new ReadResourceRequest("resource://uri")
+ );
+
+ // List and use prompts
+ ListPromptsResult prompts = client.listPrompts();
+ GetPromptResult prompt = client.getPrompt(
+ new GetPromptRequest("greeting", Map.of("name", "Spring"))
+ );
+
+ // Add/remove roots
+ client.addRoot(new Root("file:///path", "description"));
+ client.removeRoot("file:///path");
+
+ // Close client
+ client.closeGracefully();
+ ```
+
+=== "Async API"
+
+ ```java
+ // Create an async client with custom configuration
+ McpAsyncClient client = McpClient.async(transport)
+ .requestTimeout(Duration.ofSeconds(10))
+ .capabilities(ClientCapabilities.builder()
+ .roots(true) // Enable roots capability
+ .sampling() // Enable sampling capability
+ .elicitation() // Enable elicitation capability
+ .build())
+ .sampling(request -> Mono.just(new CreateMessageResult(response)))
+ .elicitation(request -> Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, content)))
+ .toolsChangeConsumer(tools -> Mono.fromRunnable(() -> {
+ logger.info("Tools updated: {}", tools);
+ }))
+ .resourcesChangeConsumer(resources -> Mono.fromRunnable(() -> {
+ logger.info("Resources updated: {}", resources);
+ }))
+ .promptsChangeConsumer(prompts -> Mono.fromRunnable(() -> {
+ logger.info("Prompts updated: {}", prompts);
+ }))
+ .progressConsumer(progress -> Mono.fromRunnable(() -> {
+ logger.info("Progress: {}", progress);
+ }))
+ .build();
+
+ // Initialize connection and use features
+ client.initialize()
+ .flatMap(initResult -> client.listTools())
+ .flatMap(tools -> {
+ return client.callTool(new CallToolRequest(
+ "calculator",
+ Map.of("operation", "add", "a", 2, "b", 3)
+ ));
+ })
+ .flatMap(result -> {
+ return client.listResources()
+ .flatMap(resources ->
+ client.readResource(new ReadResourceRequest("resource://uri"))
+ );
+ })
+ .flatMap(resource -> {
+ return client.listPrompts()
+ .flatMap(prompts ->
+ client.getPrompt(new GetPromptRequest(
+ "greeting",
+ Map.of("name", "Spring")
+ ))
+ );
+ })
+ .flatMap(prompt -> {
+ return client.addRoot(new Root("file:///path", "description"))
+ .then(client.removeRoot("file:///path"));
+ })
+ .doFinally(signalType -> {
+ client.closeGracefully().subscribe();
+ })
+ .subscribe();
+ ```
+
+## Client Transport
+
+The transport layer handles the communication between MCP clients and servers, providing different implementations for various use cases. The client transport manages message serialization, connection establishment, and protocol-specific communication patterns.
+
+### STDIO
+
+Creates transport for process-based communication using stdin/stdout:
+
+```java
+ServerParameters params = ServerParameters.builder("npx")
+ .args("-y", "@modelcontextprotocol/server-everything", "dir")
+ .build();
+McpTransport transport = new StdioClientTransport(params);
+```
+
+### Streamable HTTP
+
+=== "Streamable HttpClient"
+
+ Creates a Streamable HTTP client transport for efficient bidirectional communication. Included in the core `mcp` module:
+
+ ```java
+ McpTransport transport = HttpClientStreamableHttpTransport
+ .builder("http://your-mcp-server")
+ .endpoint("/mcp")
+ .build();
+ ```
+
+ The Streamable HTTP transport supports:
+
+ - Resumable streams for connection recovery
+ - Configurable connect timeout
+ - Custom HTTP request customization
+ - Multiple protocol version negotiation
+
+=== "Streamable WebClient (external)"
+
+ Creates Streamable HTTP WebClient-based client transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
+
+ ```java
+ McpTransport transport = WebFluxSseClientTransport
+ .builder(WebClient.builder().baseUrl("http://your-mcp-server"))
+ .build();
+ ```
+
+### SSE HTTP (Legacy)
+
+=== "SSE HttpClient"
+
+ Creates a framework-agnostic (pure Java API) SSE client transport. Included in the core `mcp` module:
+
+ ```java
+ McpTransport transport = new HttpClientSseClientTransport("http://your-mcp-server");
+ ```
+=== "SSE WebClient (external)"
+
+ Creates WebFlux-based SSE client transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
+
+ ```java
+ WebClient.Builder webClientBuilder = WebClient.builder()
+ .baseUrl("http://your-mcp-server");
+ McpTransport transport = new WebFluxSseClientTransport(webClientBuilder);
+ ```
+
+
+## Client Capabilities
+
+The client can be configured with various capabilities:
+
+```java
+var capabilities = ClientCapabilities.builder()
+ .roots(true) // Enable filesystem roots support with list changes notifications
+ .sampling() // Enable LLM sampling support
+ .elicitation() // Enable elicitation support (form and URL modes)
+ .build();
+```
+
+You can also configure elicitation with specific mode support:
+
+```java
+var capabilities = ClientCapabilities.builder()
+ .elicitation(true, false) // Enable form-based elicitation, disable URL-based
+ .build();
+```
+
+### Roots Support
+
+Roots define the boundaries of where servers can operate within the filesystem:
+
+```java
+// Add a root dynamically
+client.addRoot(new Root("file:///path", "description"));
+
+// Remove a root
+client.removeRoot("file:///path");
+
+// Notify server of roots changes
+client.rootsListChangedNotification();
+```
+
+The roots capability allows servers to:
+
+- Request the list of accessible filesystem roots
+- Receive notifications when the roots list changes
+- Understand which directories and files they have access to
+
+### Sampling Support
+
+Sampling enables servers to request LLM interactions ("completions" or "generations") through the client:
+
+```java
+// Configure sampling handler
+Function samplingHandler = request -> {
+ // Sampling implementation that interfaces with LLM
+ return new CreateMessageResult(response);
+};
+
+// Create client with sampling support
+var client = McpClient.sync(transport)
+ .capabilities(ClientCapabilities.builder()
+ .sampling()
+ .build())
+ .sampling(samplingHandler)
+ .build();
+```
+
+This capability allows:
+
+- Servers to leverage AI capabilities without requiring API keys
+- Clients to maintain control over model access and permissions
+- Support for both text and image-based interactions
+- Optional inclusion of MCP server context in prompts
+
+### Elicitation Support
+
+Elicitation enables servers to request additional information or user input through the client. This is useful when a server needs clarification or confirmation during an operation:
+
+```java
+// Configure elicitation handler
+Function elicitationHandler = request -> {
+ // Present the request to the user and collect their response
+ // The request contains a message and a schema describing the expected input
+ Map userResponse = collectUserInput(request.message(), request.requestedSchema());
+ return new ElicitResult(ElicitResult.Action.ACCEPT, userResponse);
+};
+
+// Create client with elicitation support
+var client = McpClient.sync(transport)
+ .capabilities(ClientCapabilities.builder()
+ .elicitation()
+ .build())
+ .elicitation(elicitationHandler)
+ .build();
+```
+
+The `ElicitResult` supports three actions:
+
+- `ACCEPT` - The user accepted and provided the requested information
+- `DECLINE` - The user declined to provide the information
+- `CANCEL` - The operation was cancelled
+
+### Logging Support
+
+The client can register a logging consumer to receive log messages from the server and set the minimum logging level to filter messages:
+
+```java
+var mcpClient = McpClient.sync(transport)
+ .loggingConsumer(notification -> {
+ System.out.println("Received log message: " + notification.data());
+ })
+ .build();
+
+mcpClient.initialize();
+
+mcpClient.setLoggingLevel(McpSchema.LoggingLevel.INFO);
+
+// Call the tool that sends logging notifications
+CallToolResult result = mcpClient.callTool(new CallToolRequest("logging-test", Map.of()));
+```
+
+Clients can control the minimum logging level they receive through the `mcpClient.setLoggingLevel(level)` request. Messages below the set level will be filtered out.
+Supported logging levels (in order of increasing severity): DEBUG (0), INFO (1), NOTICE (2), WARNING (3), ERROR (4), CRITICAL (5), ALERT (6), EMERGENCY (7)
+
+### Progress Notifications
+
+The client can register a progress consumer to track the progress of long-running operations:
+
+```java
+var mcpClient = McpClient.sync(transport)
+ .progressConsumer(progress -> {
+ System.out.println("Progress: " + progress.progress() + "/" + progress.total());
+ })
+ .build();
+```
+
+## Using MCP Clients
+
+### Tool Execution
+
+Tools are server-side functions that clients can discover and execute. The MCP client provides methods to list available tools and execute them with specific parameters. Each tool has a unique name and accepts a map of parameters.
+
+=== "Sync API"
+
+ ```java
+ // List available tools
+ ListToolsResult tools = client.listTools();
+
+ // Call a tool with a CallToolRequest
+ CallToolResult result = client.callTool(
+ new CallToolRequest("calculator", Map.of(
+ "operation", "add",
+ "a", 1,
+ "b", 2
+ ))
+ );
+ ```
+
+=== "Async API"
+
+ ```java
+ // List available tools asynchronously
+ client.listTools()
+ .doOnNext(tools -> tools.tools().forEach(tool ->
+ System.out.println(tool.name())))
+ .subscribe();
+
+ // Call a tool asynchronously
+ client.callTool(new CallToolRequest("calculator", Map.of(
+ "operation", "add",
+ "a", 1,
+ "b", 2
+ )))
+ .subscribe();
+ ```
+
+### Tool Schema Validation and Caching
+
+The client supports optional JSON schema validation for tool call results and automatic schema caching:
+
+```java
+var client = McpClient.sync(transport)
+ .jsonSchemaValidator(myValidator) // Enable schema validation
+ .enableCallToolSchemaCaching(true) // Cache tool schemas
+ .build();
+```
+
+### Resource Access
+
+Resources represent server-side data sources that clients can access using URI templates. The MCP client provides methods to discover available resources and retrieve their contents through a standardized interface.
+
+=== "Sync API"
+
+ ```java
+ // List available resources
+ ListResourcesResult resources = client.listResources();
+
+ // Read a resource
+ ReadResourceResult resource = client.readResource(
+ new ReadResourceRequest("resource://uri")
+ );
+ ```
+
+=== "Async API"
+
+ ```java
+ // List available resources asynchronously
+ client.listResources()
+ .doOnNext(resources -> resources.resources().forEach(resource ->
+ System.out.println(resource.name())))
+ .subscribe();
+
+ // Read a resource asynchronously
+ client.readResource(new ReadResourceRequest("resource://uri"))
+ .subscribe();
+ ```
+
+### Prompt System
+
+The prompt system enables interaction with server-side prompt templates. These templates can be discovered and executed with custom parameters, allowing for dynamic text generation based on predefined patterns.
+
+=== "Sync API"
+
+ ```java
+ // List available prompt templates
+ ListPromptsResult prompts = client.listPrompts();
+
+ // Get a prompt with parameters
+ GetPromptResult prompt = client.getPrompt(
+ new GetPromptRequest("greeting", Map.of("name", "World"))
+ );
+ ```
+
+=== "Async API"
+
+ ```java
+ // List available prompt templates asynchronously
+ client.listPrompts()
+ .doOnNext(prompts -> prompts.prompts().forEach(prompt ->
+ System.out.println(prompt.name())))
+ .subscribe();
+
+ // Get a prompt asynchronously
+ client.getPrompt(new GetPromptRequest("greeting", Map.of("name", "World")))
+ .subscribe();
+ ```
diff --git a/docs/contribute.md b/docs/contribute.md
new file mode 100644
index 000000000..3199dd51f
--- /dev/null
+++ b/docs/contribute.md
@@ -0,0 +1,106 @@
+---
+title: Contributing
+description: How to contribute to the MCP Java SDK
+---
+
+# Contributing
+
+Thank you for your interest in contributing to the Model Context Protocol Java SDK!
+This guide outlines how to contribute to this project.
+
+## Prerequisites
+
+!!! info "Required Software"
+ - **Java 17** or above
+ - **Docker**
+ - **npx**
+
+## Getting Started
+
+1. Fork the repository
+2. Clone your fork:
+
+ ```bash
+ git clone https://github.com/YOUR-USERNAME/java-sdk.git
+ cd java-sdk
+ ```
+
+3. Build from source:
+
+ ```bash
+ ./mvnw clean install -DskipTests # skip the tests
+ ./mvnw test # run tests
+ ```
+
+## Reporting Issues
+
+Please create an issue in the repository if you discover a bug or would like to
+propose an enhancement. Bug reports should have a reproducer in the form of a code
+sample or a repository attached that the maintainers or contributors can work with to
+address the problem.
+
+## Making Changes
+
+1. Create a new branch:
+
+ ```bash
+ git checkout -b feature/your-feature-name
+ ```
+
+2. Make your changes.
+
+3. Validate your changes:
+
+ ```bash
+ ./mvnw clean test
+ ```
+
+### Change Proposal Guidelines
+
+#### Principles of MCP
+
+1. **Simple + Minimal**: It is much easier to add things to the codebase than it is to
+ remove them. To maintain simplicity, we keep a high bar for adding new concepts and
+ primitives as each addition requires maintenance and compatibility consideration.
+2. **Concrete**: Code changes need to be based on specific usage and implementation
+ challenges and not on speculative ideas. Most importantly, the SDK is meant to
+ implement the MCP specification.
+
+## Submitting Changes
+
+1. For non-trivial changes, please clarify with the maintainers in an issue whether
+ you can contribute the change and the desired scope of the change.
+2. For trivial changes (for example a couple of lines or documentation changes) there
+ is no need to open an issue first.
+3. Push your changes to your fork.
+4. Submit a pull request to the main repository.
+5. Follow the pull request template.
+6. Wait for review.
+7. For any follow-up work, please add new commits instead of force-pushing. This will
+ allow the reviewer to focus on incremental changes instead of having to restart the
+ review process.
+
+## Code of Conduct
+
+This project follows a Code of Conduct. Please review it in
+[CODE_OF_CONDUCT.md](https://github.com/modelcontextprotocol/java-sdk/blob/main/CODE_OF_CONDUCT.md).
+
+## Questions
+
+If you have questions, please create a discussion in the repository.
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the MIT
+License.
+
+## Security
+
+This SDK is maintained by [Anthropic](https://www.anthropic.com/) as part of the Model Context Protocol project.
+
+The security of our systems and user data is Anthropic's top priority. We appreciate the work of security researchers acting in good faith in identifying and reporting potential vulnerabilities.
+
+!!! warning "Reporting Security Vulnerabilities"
+ Do **not** report security vulnerabilities through public GitHub issues. Instead, report them through our HackerOne [submission form](https://hackerone.com/anthropic-vdp/reports/new?type=team&report_type=vulnerability).
+
+Our Vulnerability Disclosure Program guidelines are defined on our [HackerOne program page](https://hackerone.com/anthropic-vdp).
diff --git a/docs/development.md b/docs/development.md
new file mode 100644
index 000000000..e00c7268b
--- /dev/null
+++ b/docs/development.md
@@ -0,0 +1,75 @@
+---
+title: Documentation
+description: How to contribute to the MCP Java SDK documentation
+---
+
+# Documentation Development
+
+This guide covers how to set up and preview the MCP Java SDK documentation locally.
+
+!!! info "Prerequisites"
+ - Python 3.x
+ - pip (Python package manager)
+
+## Setup
+
+Install mkdocs-material:
+
+```bash
+pip install mkdocs-material
+```
+
+## Preview Locally
+
+From the project root directory, run:
+
+```bash
+mkdocs serve
+```
+
+A local preview of the documentation will be available at `http://localhost:8000`.
+
+### Custom Ports
+
+By default, mkdocs uses port 8000. You can customize the port with the `-a` flag:
+
+```bash
+mkdocs serve -a localhost:3333
+```
+
+## Building
+
+To build the static site for deployment:
+
+```bash
+mkdocs build
+```
+
+The built site will be output to the `site/` directory.
+
+## Project Structure
+
+```
+docs/
+├── index.md # Overview page
+├── quickstart.md # Quickstart guide
+├── client.md # MCP Client documentation
+├── server.md # MCP Server documentation
+├── contributing.md # Contributing guide
+├── development.md # This page
+├── images/ # Images and diagrams
+└── stylesheets/ # Custom CSS
+mkdocs.yml # MkDocs configuration
+```
+
+## Writing Guidelines
+
+- Documentation pages use standard Markdown with [mkdocs-material extensions](https://squidfunk.github.io/mkdocs-material/reference/)
+- Use content tabs (`=== "Tab Label"`) for Maven/Gradle or Sync/Async code examples
+- Use admonitions (`!!! tip`, `!!! info`, `!!! warning`) for callouts
+- All code blocks should specify a language for syntax highlighting
+- Images go in the `docs/images/` directory
+
+## IDE Support
+
+We suggest using extensions on your IDE to recognize and format Markdown. If you're a VSCode user, consider the [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) extension for enhanced Markdown support, and [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) for code formatting.
diff --git a/docs/images/favicon.svg b/docs/images/favicon.svg
new file mode 100644
index 000000000..fe5edb725
--- /dev/null
+++ b/docs/images/favicon.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/docs/images/java-mcp-client-architecture.jpg b/docs/images/java-mcp-client-architecture.jpg
new file mode 100644
index 000000000..688a2b4ad
Binary files /dev/null and b/docs/images/java-mcp-client-architecture.jpg differ
diff --git a/docs/images/java-mcp-server-architecture.jpg b/docs/images/java-mcp-server-architecture.jpg
new file mode 100644
index 000000000..4b05ca139
Binary files /dev/null and b/docs/images/java-mcp-server-architecture.jpg differ
diff --git a/docs/images/java-mcp-uml-classdiagram.svg b/docs/images/java-mcp-uml-classdiagram.svg
new file mode 100644
index 000000000..f83a586e7
--- /dev/null
+++ b/docs/images/java-mcp-uml-classdiagram.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/images/logo-dark.svg b/docs/images/logo-dark.svg
new file mode 100644
index 000000000..03d9f85d3
--- /dev/null
+++ b/docs/images/logo-dark.svg
@@ -0,0 +1,12 @@
+
diff --git a/docs/images/logo-light.svg b/docs/images/logo-light.svg
new file mode 100644
index 000000000..fe5edb725
--- /dev/null
+++ b/docs/images/logo-light.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/docs/images/mcp-stack.svg b/docs/images/mcp-stack.svg
new file mode 100644
index 000000000..3847eaa8d
--- /dev/null
+++ b/docs/images/mcp-stack.svg
@@ -0,0 +1,197 @@
+
+
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 000000000..e6062b5ff
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,84 @@
+---
+title: Index
+description: Introduction to the Model Context Protocol (MCP) Java SDK
+---
+
+# MCP Java SDK
+
+Java SDK for the [Model Context Protocol](https://modelcontextprotocol.io/docs/concepts/architecture)
+enables standardized integration between AI models and tools.
+
+## Features
+
+- MCP Client and MCP Server implementations supporting:
+ - Protocol [version compatibility negotiation](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization) with multiple protocol versions
+ - [Tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) discovery, execution, list change notifications, and structured output with schema validation
+ - [Resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources) management with URI templates
+ - [Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) list management and notifications
+ - [Prompts](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts) handling and management
+ - [Sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) support for AI model interactions
+ - [Elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) support for requesting user input from servers
+ - [Completions](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion) for argument autocompletion suggestions
+ - [Progress](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/progress) - progress notifications for tracking long-running operations
+ - [Logging](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging) - structured logging with configurable severity levels
+- Multiple transport implementations:
+ - Default transports (included in core `mcp` module, no external web frameworks required):
+ - [STDIO](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio)-based transport for process-based communication
+ - Java HttpClient-based SSE client transport for HTTP SSE Client-side streaming
+ - Servlet-based SSE server transport for HTTP SSE Server streaming
+ - [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) transport for efficient bidirectional communication (client and server)
+ - Optional Spring-based transports (available in [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+, no longer part of this SDK):
+ - WebFlux SSE client and server transports for reactive HTTP streaming
+ - WebFlux Streamable HTTP server transport
+ - WebMVC SSE server transport for servlet-based HTTP streaming
+ - WebMVC Streamable HTTP server transport
+ - WebMVC Stateless server transport
+- Supports Synchronous and Asynchronous programming paradigms
+- Pluggable JSON serialization (Jackson 2.x and Jackson 3.x)
+- Pluggable authorization hooks for server security
+- DNS rebinding protection with Host/Origin header validation
+
+!!! tip
+ The core `io.modelcontextprotocol.sdk:mcp` module provides default STDIO, SSE, and Streamable HTTP client and server transport implementations without requiring external web frameworks.
+
+ Spring-specific transports (WebFlux, WebMVC) are now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ and are no longer shipped by this SDK.
+ Use the [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) and [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html) from Spring AI.
+ Also consider the [MCP Annotations](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-annotations-overview.html) and [MCP Security](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-security.html).
+
+## Next Steps
+
+
+
+- :rocket:{ .lg .middle } **Quickstart**
+
+ ---
+
+ Get started with dependencies and BOM configuration.
+
+ [:octicons-arrow-right-24: Quickstart](quickstart.md)
+
+- :material-monitor:{ .lg .middle } **MCP Client**
+
+ ---
+
+ Learn how to create and configure MCP clients.
+
+ [:octicons-arrow-right-24: Client](client.md)
+
+- :material-server:{ .lg .middle } **MCP Server**
+
+ ---
+
+ Learn how to implement and configure MCP servers.
+
+ [:octicons-arrow-right-24: Server](server.md)
+
+- :fontawesome-brands-github:{ .lg .middle } **GitHub**
+
+ ---
+
+ View the source code and contribute.
+
+ [:octicons-arrow-right-24: Repository](https://github.com/modelcontextprotocol/java-sdk)
+
+
diff --git a/docs/overview.md b/docs/overview.md
new file mode 100644
index 000000000..9084b6a6a
--- /dev/null
+++ b/docs/overview.md
@@ -0,0 +1,93 @@
+---
+title: Overview
+description: Introduction to the Model Context Protocol (MCP) Java SDK
+---
+
+# Overview
+
+## Architecture
+
+The SDK follows a layered architecture with clear separation of concerns:
+
+
+
+- **Client/Server Layer (McpClient/McpServer)**: Both use McpSession for sync/async operations,
+ with McpClient handling client-side protocol operations and McpServer managing server-side protocol operations.
+- **Session Layer (McpSession)**: Manages communication patterns and state.
+- **Transport Layer (McpTransport)**: Handles JSON-RPC message serialization/deserialization via:
+ - StdioTransport (stdin/stdout) in the core module
+ - HTTP SSE transports in dedicated transport modules (Java HttpClient, Servlet)
+ - Streamable HTTP transports for efficient bidirectional communication
+ - Spring WebFlux and Spring WebMVC transports (available in [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+)
+
+The MCP Client is a key component in the Model Context Protocol (MCP) architecture, responsible for establishing and managing connections with MCP servers.
+It implements the client-side of the protocol.
+
+
+
+The MCP Server is a foundational component in the Model Context Protocol (MCP) architecture that provides tools, resources, and capabilities to clients.
+It implements the server-side of the protocol.
+
+
+
+Key Interactions:
+
+- **Client/Server Initialization**: Transport setup, protocol compatibility check, capability negotiation, and implementation details exchange.
+- **Message Flow**: JSON-RPC message handling with validation, type-safe response processing, and error handling.
+- **Resource Management**: Resource discovery, URI template-based access, subscription system, and content retrieval.
+
+## Module Structure
+
+The SDK is organized into modules to separate concerns and allow adopters to bring in only what they need:
+
+| Module | Artifact ID | Group | Purpose |
+|--------|------------|-------|---------|
+| `mcp-bom` | `mcp-bom` | `io.modelcontextprotocol.sdk` | Bill of Materials for dependency management |
+| `mcp-core` | `mcp-core` | `io.modelcontextprotocol.sdk` | Core reference implementation (STDIO, JDK HttpClient, Servlet, Streamable HTTP) |
+| `mcp-json-jackson2` | `mcp-json-jackson2` | `io.modelcontextprotocol.sdk` | Jackson 2.x JSON serialization implementation |
+| `mcp-json-jackson3` | `mcp-json-jackson3` | `io.modelcontextprotocol.sdk` | Jackson 3.x JSON serialization implementation |
+| `mcp` | `mcp` | `io.modelcontextprotocol.sdk` | Convenience bundle (`mcp-core` + `mcp-json-jackson3`) |
+| `mcp-test` | `mcp-test` | `io.modelcontextprotocol.sdk` | Shared testing utilities and integration tests |
+| `mcp-spring-webflux` _(external)_ | `mcp-spring-webflux` | `org.springframework.ai` | Spring WebFlux integration — part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ |
+| `mcp-spring-webmvc` _(external)_ | `mcp-spring-webmvc` | `org.springframework.ai` | Spring WebMVC integration — part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ |
+
+!!! tip
+ A minimal adopter may depend only on `mcp` (core + Jackson 3). Spring-based applications should use the `mcp-spring-webflux` or `mcp-spring-webmvc` artifacts from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`), no longer part of this SDK.
+
+## Next Steps
+
+
+
+- :rocket:{ .lg .middle } **Quickstart**
+
+ ---
+
+ Get started with dependencies and BOM configuration.
+
+ [:octicons-arrow-right-24: Quickstart](quickstart.md)
+
+- :material-monitor:{ .lg .middle } **MCP Client**
+
+ ---
+
+ Learn how to create and configure MCP clients.
+
+ [:octicons-arrow-right-24: Client](client.md)
+
+- :material-server:{ .lg .middle } **MCP Server**
+
+ ---
+
+ Learn how to implement and configure MCP servers.
+
+ [:octicons-arrow-right-24: Server](server.md)
+
+- :fontawesome-brands-github:{ .lg .middle } **GitHub**
+
+ ---
+
+ View the source code and contribute.
+
+ [:octicons-arrow-right-24: Repository](https://github.com/modelcontextprotocol/java-sdk)
+
+
diff --git a/docs/quickstart.md b/docs/quickstart.md
new file mode 100644
index 000000000..e7e76bc88
--- /dev/null
+++ b/docs/quickstart.md
@@ -0,0 +1,163 @@
+---
+title: Quickstart
+description: Get started with the MCP Java SDK dependencies and configuration
+---
+
+# Quickstart
+
+## Dependencies
+
+Add the following dependency to your project:
+
+=== "Maven"
+
+ The convenience `mcp` module bundles `mcp-core` with Jackson 3.x JSON serialization:
+
+ ```xml
+
+ io.modelcontextprotocol.sdk
+ mcp
+
+ ```
+
+ This includes default STDIO, SSE, and Streamable HTTP transport implementations without requiring external web frameworks.
+
+ If you need only the core module without a JSON implementation (e.g., to bring your own):
+
+ ```xml
+
+ io.modelcontextprotocol.sdk
+ mcp-core
+
+ ```
+
+ For Jackson 2.x instead of Jackson 3.x:
+
+ ```xml
+
+ io.modelcontextprotocol.sdk
+ mcp-core
+
+
+ io.modelcontextprotocol.sdk
+ mcp-json-jackson2
+
+ ```
+
+ If you're using Spring Framework, the Spring-specific transport implementations are now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
+
+ ```xml
+
+
+ org.springframework.ai
+ mcp-spring-webflux
+
+
+
+
+ org.springframework.ai
+ mcp-spring-webmvc
+
+ ```
+
+ !!! note
+ When using the `spring-ai-bom` or Spring AI starter dependencies (`spring-ai-starter-mcp-server-webflux`, `spring-ai-starter-mcp-server-webmvc`, `spring-ai-starter-mcp-client-webflux`) no explicit version is needed — the BOM manages it automatically.
+
+=== "Gradle"
+
+ The convenience `mcp` module bundles `mcp-core` with Jackson 3.x JSON serialization:
+
+ ```groovy
+ dependencies {
+ implementation "io.modelcontextprotocol.sdk:mcp"
+ }
+ ```
+
+ This includes default STDIO, SSE, and Streamable HTTP transport implementations without requiring external web frameworks.
+
+ If you need only the core module without a JSON implementation (e.g., to bring your own):
+
+ ```groovy
+ dependencies {
+ implementation "io.modelcontextprotocol.sdk:mcp-core"
+ }
+ ```
+
+ For Jackson 2.x instead of Jackson 3.x:
+
+ ```groovy
+ dependencies {
+ implementation "io.modelcontextprotocol.sdk:mcp-core"
+ implementation "io.modelcontextprotocol.sdk:mcp-json-jackson2"
+ }
+ ```
+
+ If you're using Spring Framework, the Spring-specific transport implementations are now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
+
+ ```groovy
+ // Optional: Spring WebFlux-based SSE and Streamable HTTP client and server transport (Spring AI 2.0+)
+ dependencies {
+ implementation "org.springframework.ai:mcp-spring-webflux"
+ }
+
+ // Optional: Spring WebMVC-based SSE and Streamable HTTP server transport (Spring AI 2.0+)
+ dependencies {
+ implementation "org.springframework.ai:mcp-spring-webmvc"
+ }
+ ```
+
+## Bill of Materials (BOM)
+
+The Bill of Materials (BOM) declares the recommended versions of all the dependencies used by a given release.
+Using the BOM from your application's build script avoids the need for you to specify and maintain the dependency versions yourself.
+Instead, the version of the BOM you're using determines the utilized dependency versions.
+It also ensures that you're using supported and tested versions of the dependencies by default, unless you choose to override them.
+
+Add the BOM to your project:
+
+=== "Maven"
+
+ ```xml
+
+
+
+ io.modelcontextprotocol.sdk
+ mcp-bom
+ 1.0.0
+ pom
+ import
+
+
+
+ ```
+
+=== "Gradle"
+
+ ```groovy
+ dependencies {
+ implementation platform("io.modelcontextprotocol.sdk:mcp-bom:1.0.0")
+ //...
+ }
+ ```
+
+ Gradle users can also leverage Gradle (5.0+) native support for declaring dependency constraints using a Maven BOM.
+ This is implemented by adding a 'platform' dependency handler method to the dependencies section of your Gradle build script.
+ As shown in the snippet above this can then be followed by version-less declarations of the dependencies.
+
+Replace the version number with the latest version from [Maven Central](https://central.sonatype.com/artifact/io.modelcontextprotocol.sdk/mcp).
+
+## Available Dependencies
+
+The following dependencies are available and managed by the BOM:
+
+- **Core Dependencies**
+ - `io.modelcontextprotocol.sdk:mcp-core` - Core MCP library providing the base functionality, APIs, and default transport implementations (STDIO, SSE, Streamable HTTP). JSON binding is abstracted for pluggability.
+ - `io.modelcontextprotocol.sdk:mcp` - Convenience bundle that combines `mcp-core` with `mcp-json-jackson3` for out-of-the-box usage.
+- **JSON Serialization**
+ - `io.modelcontextprotocol.sdk:mcp-json-jackson3` - Jackson 3.x JSON serialization implementation (included in `mcp` bundle).
+ - `io.modelcontextprotocol.sdk:mcp-json-jackson2` - Jackson 2.x JSON serialization implementation for projects that require Jackson 2.x compatibility.
+- **Optional Spring Transport Dependencies** (part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+, group `org.springframework.ai`)
+ - `org.springframework.ai:mcp-spring-webflux` - WebFlux-based SSE and Streamable HTTP transport implementation for reactive applications.
+ - `org.springframework.ai:mcp-spring-webmvc` - WebMVC-based SSE and Streamable HTTP transport implementation for servlet-based applications.
+- **Testing Dependencies**
+ - `io.modelcontextprotocol.sdk:mcp-test` - Testing utilities and support for MCP-based applications.
diff --git a/docs/server.md b/docs/server.md
new file mode 100644
index 000000000..0753726e2
--- /dev/null
+++ b/docs/server.md
@@ -0,0 +1,761 @@
+---
+title: MCP Server
+description: Learn how to implement and configure a Model Context Protocol (MCP) server
+---
+
+# MCP Server
+
+## Overview
+
+The MCP Server is a foundational component in the Model Context Protocol (MCP) architecture that provides tools, resources, and capabilities to clients. It implements the server-side of the protocol, responsible for:
+
+- Exposing tools that clients can discover and execute
+- Managing resources with URI-based access patterns and resource templates
+- Providing prompt templates and handling prompt requests
+- Supporting capability negotiation with clients
+- Providing argument autocompletion suggestions (completions)
+- Implementing server-side protocol operations
+- Managing concurrent client connections
+- Providing structured logging and notifications
+
+!!! tip
+ The core `io.modelcontextprotocol.sdk:mcp` module provides STDIO, SSE, and Streamable HTTP server transport implementations without requiring external web frameworks.
+
+ Spring-specific transport implementations (`mcp-spring-webflux`, `mcp-spring-webmvc`) are now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`) and are no longer shipped by this SDK.
+ See the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html) documentation for Spring-based server setup.
+
+The server supports both synchronous and asynchronous APIs, allowing for flexible integration in different application contexts.
+
+=== "Sync API"
+
+ ```java
+ // Create a server with custom configuration
+ McpSyncServer syncServer = McpServer.sync(transportProvider)
+ .serverInfo("my-server", "1.0.0")
+ .capabilities(ServerCapabilities.builder()
+ .resources(false, true) // Enable resource support with list changes
+ .tools(true) // Enable tool support with list changes
+ .prompts(true) // Enable prompt support with list changes
+ .completions() // Enable completions support
+ .logging() // Enable logging support
+ .build())
+ .build();
+
+ // Register tools, resources, and prompts
+ syncServer.addTool(syncToolSpecification);
+ syncServer.addResource(syncResourceSpecification);
+ syncServer.addPrompt(syncPromptSpecification);
+
+ // Close the server when done
+ syncServer.close();
+ ```
+
+=== "Async API"
+
+ ```java
+ // Create an async server with custom configuration
+ McpAsyncServer asyncServer = McpServer.async(transportProvider)
+ .serverInfo("my-server", "1.0.0")
+ .capabilities(ServerCapabilities.builder()
+ .resources(false, true) // Enable resource support with list changes
+ .tools(true) // Enable tool support with list changes
+ .prompts(true) // Enable prompt support with list changes
+ .completions() // Enable completions support
+ .logging() // Enable logging support
+ .build())
+ .build();
+
+ // Register tools, resources, and prompts
+ asyncServer.addTool(asyncToolSpecification)
+ .doOnSuccess(v -> logger.info("Tool registered"))
+ .subscribe();
+
+ asyncServer.addResource(asyncResourceSpecification)
+ .doOnSuccess(v -> logger.info("Resource registered"))
+ .subscribe();
+
+ asyncServer.addPrompt(asyncPromptSpecification)
+ .doOnSuccess(v -> logger.info("Prompt registered"))
+ .subscribe();
+
+ // Close the server when done
+ asyncServer.close()
+ .doOnSuccess(v -> logger.info("Server closed"))
+ .subscribe();
+ ```
+
+### Server Types
+
+The SDK supports multiple server creation patterns depending on your transport requirements:
+
+```java
+// Single-session server with SSE transport provider
+McpSyncServer server = McpServer.sync(sseTransportProvider).build();
+
+// Streamable HTTP server
+McpSyncServer server = McpServer.sync(streamableTransportProvider).build();
+
+// Stateless server (no session management)
+McpSyncServer server = McpServer.sync(statelessTransport).build();
+```
+
+## Server Transport Providers
+
+The transport layer in the MCP SDK is responsible for handling the communication between clients and servers.
+It provides different implementations to support various communication protocols and patterns.
+The SDK includes several built-in transport provider implementations:
+
+### STDIO
+
+Create process-based transport using stdin/stdout:
+
+```java
+StdioServerTransportProvider transportProvider =
+ new StdioServerTransportProvider(new ObjectMapper());
+```
+
+Provides bidirectional JSON-RPC message handling over standard input/output streams with non-blocking message processing, serialization/deserialization, and graceful shutdown support.
+
+Key features:
+
+- Bidirectional communication through stdin/stdout
+- Process-based integration support
+- Simple setup and configuration
+- Lightweight implementation
+
+### Streamable HTTP
+
+=== "Streamable HTTP Servlet"
+
+ Creates a Servlet-based Streamable HTTP server transport. Included in the core `mcp` module:
+
+ ```java
+ HttpServletStreamableServerTransportProvider transportProvider =
+ HttpServletStreamableServerTransportProvider.builder()
+ .jsonMapper(jsonMapper)
+ .mcpEndpoint("/mcp")
+ .build();
+ ```
+
+ To use with a Spring Web application, register it as a Servlet bean:
+
+ ```java
+ @Configuration
+ @EnableWebMvc
+ public class McpServerConfig implements WebMvcConfigurer {
+
+ @Bean
+ public HttpServletStreamableServerTransportProvider transportProvider(McpJsonMapper jsonMapper) {
+ return HttpServletStreamableServerTransportProvider.builder()
+ .jsonMapper(jsonMapper)
+ .mcpEndpoint("/mcp")
+ .build();
+ }
+
+ @Bean
+ public ServletRegistrationBean> mcpServlet(
+ HttpServletStreamableServerTransportProvider transportProvider) {
+ return new ServletRegistrationBean<>(transportProvider);
+ }
+ }
+ ```
+
+ Key features:
+
+ - Efficient bidirectional HTTP communication
+ - Session management for multiple client connections
+ - Configurable keep-alive intervals
+ - Security validation support
+ - Graceful shutdown support
+
+=== "Streamable HTTP WebFlux (external)"
+
+ Creates WebFlux-based Streamable HTTP server transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
+
+ ```java
+ @Configuration
+ class McpConfig {
+ @Bean
+ WebFluxStreamableServerTransportProvider transportProvider(McpJsonMapper jsonMapper) {
+ return WebFluxStreamableServerTransportProvider.builder()
+ .jsonMapper(jsonMapper)
+ .messageEndpoint("/mcp")
+ .build();
+ }
+
+ @Bean
+ RouterFunction> mcpRouterFunction(
+ WebFluxStreamableServerTransportProvider transportProvider) {
+ return transportProvider.getRouterFunction();
+ }
+ }
+ ```
+
+ Key features:
+
+ - Reactive HTTP streaming with WebFlux
+ - Concurrent client connections
+ - Configurable keep-alive intervals
+ - Security validation support
+
+=== "Streamable HTTP WebMvc (external)"
+
+ Creates WebMvc-based Streamable HTTP server transport. Requires the `mcp-spring-webmvc` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
+
+ ```java
+ @Configuration
+ @EnableWebMvc
+ class McpConfig {
+ @Bean
+ WebMvcStreamableServerTransportProvider transportProvider(McpJsonMapper jsonMapper) {
+ return WebMvcStreamableServerTransportProvider.builder()
+ .jsonMapper(jsonMapper)
+ .mcpEndpoint("/mcp")
+ .build();
+ }
+
+ @Bean
+ RouterFunction mcpRouterFunction(
+ WebMvcStreamableServerTransportProvider transportProvider) {
+ return transportProvider.getRouterFunction();
+ }
+ }
+ ```
+
+### SSE HTTP (Legacy)
+
+=== "SSE Servlet"
+
+ Creates a Servlet-based SSE server transport. Included in the core `mcp` module.
+ The `HttpServletSseServerTransportProvider` can be used with any Servlet container.
+ To use it with a Spring Web application, you can register it as a Servlet bean:
+
+ ```java
+ @Configuration
+ @EnableWebMvc
+ public class McpServerConfig implements WebMvcConfigurer {
+
+ @Bean
+ public HttpServletSseServerTransportProvider servletSseServerTransportProvider() {
+ return new HttpServletSseServerTransportProvider(new ObjectMapper(), "/mcp/message");
+ }
+
+ @Bean
+ public ServletRegistrationBean> customServletBean(
+ HttpServletSseServerTransportProvider transportProvider) {
+ return new ServletRegistrationBean<>(transportProvider);
+ }
+ }
+ ```
+
+ Implements the MCP HTTP with SSE transport specification using the traditional Servlet API, providing:
+
+ - Asynchronous message handling using Servlet 6.0 async support
+ - Session management for multiple client connections
+ - Two types of endpoints:
+ - SSE endpoint (`/sse`) for server-to-client events
+ - Message endpoint (configurable) for client-to-server requests
+ - Error handling and response formatting
+ - Graceful shutdown support
+
+=== "SSE WebFlux (external)"
+
+ Creates WebFlux-based SSE server transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
+
+ ```java
+ @Configuration
+ class McpConfig {
+ @Bean
+ WebFluxSseServerTransportProvider webFluxSseServerTransportProvider(ObjectMapper mapper) {
+ return new WebFluxSseServerTransportProvider(mapper, "/mcp/message");
+ }
+
+ @Bean
+ RouterFunction> mcpRouterFunction(WebFluxSseServerTransportProvider transportProvider) {
+ return transportProvider.getRouterFunction();
+ }
+ }
+ ```
+
+ Implements the MCP HTTP with SSE transport specification, providing:
+
+ - Reactive HTTP streaming with WebFlux
+ - Concurrent client connections through SSE endpoints
+ - Message routing and session management
+ - Graceful shutdown capabilities
+
+=== "SSE WebMvc (external)"
+
+ Creates WebMvc-based SSE server transport. Requires the `mcp-spring-webmvc` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
+
+ ```java
+ @Configuration
+ @EnableWebMvc
+ class McpConfig {
+ @Bean
+ WebMvcSseServerTransportProvider webMvcSseServerTransportProvider(ObjectMapper mapper) {
+ return new WebMvcSseServerTransportProvider(mapper, "/mcp/message");
+ }
+
+ @Bean
+ RouterFunction mcpRouterFunction(
+ WebMvcSseServerTransportProvider transportProvider) {
+ return transportProvider.getRouterFunction();
+ }
+ }
+ ```
+
+ Implements the MCP HTTP with SSE transport specification, providing:
+
+ - Server-side event streaming
+ - Integration with Spring WebMVC
+ - Support for traditional web applications
+ - Synchronous operation handling
+
+
+## Server Capabilities
+
+The server can be configured with various capabilities:
+
+```java
+var capabilities = ServerCapabilities.builder()
+ .resources(false, true) // Resource support (subscribe, listChanged)
+ .tools(true) // Tool support with list changes notifications
+ .prompts(true) // Prompt support with list changes notifications
+ .completions() // Enable completions support
+ .logging() // Enable logging support
+ .build();
+```
+
+### Tool Specification
+
+The Model Context Protocol allows servers to [expose tools](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/) that can be invoked by language models.
+The Java SDK allows implementing Tool Specifications with their handler functions.
+Tools enable AI models to perform calculations, access external APIs, query databases, and manipulate files.
+
+The recommended approach is to use the builder pattern and `CallToolRequest` as the handler parameter:
+
+=== "Sync"
+
+ ```java
+ // Sync tool specification using builder
+ var syncToolSpecification = SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("calculator")
+ .description("Basic calculator")
+ .inputSchema(schema)
+ .build())
+ .callHandler((exchange, request) -> {
+ // Access arguments via request.arguments()
+ String operation = (String) request.arguments().get("operation");
+ int a = (int) request.arguments().get("a");
+ int b = (int) request.arguments().get("b");
+ // Tool implementation
+ return CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent("Result: " + result)))
+ .build();
+ })
+ .build();
+ ```
+
+=== "Async"
+
+ ```java
+ // Async tool specification using builder
+ var asyncToolSpecification = AsyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("calculator")
+ .description("Basic calculator")
+ .inputSchema(schema)
+ .build())
+ .callHandler((exchange, request) -> {
+ // Access arguments via request.arguments()
+ String operation = (String) request.arguments().get("operation");
+ int a = (int) request.arguments().get("a");
+ int b = (int) request.arguments().get("b");
+ // Tool implementation
+ return Mono.just(CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent("Result: " + result)))
+ .build());
+ })
+ .build();
+ ```
+
+The Tool specification includes a Tool definition with `name`, `description`, and `inputSchema` followed by a call handler that implements the tool's logic.
+The handler receives `McpSyncServerExchange`/`McpAsyncServerExchange` for client interaction and a `CallToolRequest` containing the tool arguments.
+
+You can also register tools directly on the server builder using the `toolCall` convenience method:
+
+```java
+var server = McpServer.sync(transportProvider)
+ .toolCall(
+ Tool.builder().name("echo").description("Echoes input").inputSchema(schema).build(),
+ (exchange, request) -> CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent(request.arguments().get("text").toString())))
+ .build()
+ )
+ .build();
+```
+
+### Resource Specification
+
+Specification of a resource with its handler function.
+Resources provide context to AI models by exposing data such as: File contents, Database records, API responses, System information, Application state.
+
+=== "Sync"
+
+ ```java
+ // Sync resource specification
+ var syncResourceSpecification = new McpServerFeatures.SyncResourceSpecification(
+ Resource.builder()
+ .uri("custom://resource")
+ .name("name")
+ .description("description")
+ .mimeType("text/plain")
+ .build(),
+ (exchange, request) -> {
+ // Resource read implementation
+ return new ReadResourceResult(contents);
+ }
+ );
+ ```
+
+=== "Async"
+
+ ```java
+ // Async resource specification
+ var asyncResourceSpecification = new McpServerFeatures.AsyncResourceSpecification(
+ Resource.builder()
+ .uri("custom://resource")
+ .name("name")
+ .description("description")
+ .mimeType("text/plain")
+ .build(),
+ (exchange, request) -> {
+ // Resource read implementation
+ return Mono.just(new ReadResourceResult(contents));
+ }
+ );
+ ```
+
+### Resource Template Specification
+
+Resource templates allow servers to expose parameterized resources using URI templates:
+
+```java
+// Resource template specification
+var resourceTemplateSpec = new McpServerFeatures.SyncResourceTemplateSpecification(
+ ResourceTemplate.builder()
+ .uriTemplate("file://{path}")
+ .name("File Resource")
+ .description("Access files by path")
+ .mimeType("application/octet-stream")
+ .build(),
+ (exchange, request) -> {
+ // Read the file at the requested URI
+ return new ReadResourceResult(contents);
+ }
+);
+```
+
+### Prompt Specification
+
+As part of the [Prompting capabilities](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/), MCP provides a standardized way for servers to expose prompt templates to clients.
+The Prompt Specification is a structured template for AI model interactions that enables consistent message formatting, parameter substitution, context injection, response formatting, and instruction templating.
+
+=== "Sync"
+
+ ```java
+ // Sync prompt specification
+ var syncPromptSpecification = new McpServerFeatures.SyncPromptSpecification(
+ new Prompt("greeting", "description", List.of(
+ new PromptArgument("name", "description", true)
+ )),
+ (exchange, request) -> {
+ // Prompt implementation
+ return new GetPromptResult(description, messages);
+ }
+ );
+ ```
+
+=== "Async"
+
+ ```java
+ // Async prompt specification
+ var asyncPromptSpecification = new McpServerFeatures.AsyncPromptSpecification(
+ new Prompt("greeting", "description", List.of(
+ new PromptArgument("name", "description", true)
+ )),
+ (exchange, request) -> {
+ // Prompt implementation
+ return Mono.just(new GetPromptResult(description, messages));
+ }
+ );
+ ```
+
+The prompt definition includes name (identifier for the prompt), description (purpose of the prompt), and list of arguments (parameters for templating).
+The handler function processes requests and returns formatted templates.
+The first argument is `McpSyncServerExchange`/`McpAsyncServerExchange` for client interaction, and the second argument is a `GetPromptRequest` instance.
+
+### Completion Specification
+
+Completions allow servers to provide argument autocompletion suggestions for prompts and resources:
+
+=== "Sync"
+
+ ```java
+ // Sync completion specification
+ var syncCompletionSpec = new McpServerFeatures.SyncCompletionSpecification(
+ new McpSchema.PromptReference("greeting"), // Reference to a prompt
+ (exchange, request) -> {
+ String argName = request.argument().name();
+ String partial = request.argument().value();
+ // Return matching suggestions
+ List suggestions = findMatches(partial);
+ return new McpSchema.CompleteResult(
+ new McpSchema.CompleteResult.CompleteCompletion(suggestions, suggestions.size(), false)
+ );
+ }
+ );
+ ```
+
+=== "Async"
+
+ ```java
+ // Async completion specification
+ var asyncCompletionSpec = new McpServerFeatures.AsyncCompletionSpecification(
+ new McpSchema.PromptReference("greeting"),
+ (exchange, request) -> {
+ String argName = request.argument().name();
+ String partial = request.argument().value();
+ List suggestions = findMatches(partial);
+ return Mono.just(new McpSchema.CompleteResult(
+ new McpSchema.CompleteResult.CompleteCompletion(suggestions, suggestions.size(), false)
+ ));
+ }
+ );
+ ```
+
+Completions can be registered for both `PromptReference` and `ResourceReference` types.
+
+### Using Sampling from a Server
+
+To use [Sampling capabilities](https://spec.modelcontextprotocol.io/specification/2024-11-05/client/sampling/), connect to a client that supports sampling.
+No special server configuration is needed, but verify client sampling support before making requests.
+Learn about [client sampling support](client.md#sampling-support).
+
+Once connected to a compatible client, the server can request language model generations:
+
+=== "Sync API"
+
+ ```java
+ // Create a server
+ McpSyncServer server = McpServer.sync(transportProvider)
+ .serverInfo("my-server", "1.0.0")
+ .build();
+
+ // Define a tool that uses sampling
+ var calculatorTool = SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("ai-calculator")
+ .description("Performs calculations using AI")
+ .inputSchema(schema)
+ .build())
+ .callHandler((exchange, request) -> {
+ // Check if client supports sampling
+ if (exchange.getClientCapabilities().sampling() == null) {
+ return CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent("Client does not support AI capabilities")))
+ .build();
+ }
+
+ // Create a sampling request
+ CreateMessageRequest samplingRequest = CreateMessageRequest.builder()
+ .messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
+ new McpSchema.TextContent("Calculate: " + request.arguments().get("expression")))))
+ .modelPreferences(McpSchema.ModelPreferences.builder()
+ .hints(List.of(
+ McpSchema.ModelHint.of("claude-3-sonnet"),
+ McpSchema.ModelHint.of("claude")
+ ))
+ .intelligencePriority(0.8)
+ .speedPriority(0.5)
+ .build())
+ .systemPrompt("You are a helpful calculator assistant. Provide only the numerical answer.")
+ .maxTokens(100)
+ .build();
+
+ // Request sampling from the client
+ CreateMessageResult result = exchange.createMessage(samplingRequest);
+
+ // Process the result
+ String answer = ((McpSchema.TextContent) result.content()).text();
+ return CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent(answer)))
+ .build();
+ })
+ .build();
+
+ // Add the tool to the server
+ server.addTool(calculatorTool);
+ ```
+
+=== "Async API"
+
+ ```java
+ // Create a server
+ McpAsyncServer server = McpServer.async(transportProvider)
+ .serverInfo("my-server", "1.0.0")
+ .build();
+
+ // Define a tool that uses sampling
+ var calculatorTool = AsyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("ai-calculator")
+ .description("Performs calculations using AI")
+ .inputSchema(schema)
+ .build())
+ .callHandler((exchange, request) -> {
+ // Check if client supports sampling
+ if (exchange.getClientCapabilities().sampling() == null) {
+ return Mono.just(CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent("Client does not support AI capabilities")))
+ .build());
+ }
+
+ // Create a sampling request
+ CreateMessageRequest samplingRequest = CreateMessageRequest.builder()
+ .messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
+ new McpSchema.TextContent("Calculate: " + request.arguments().get("expression")))))
+ .modelPreferences(McpSchema.ModelPreferences.builder()
+ .hints(List.of(
+ McpSchema.ModelHint.of("claude-3-sonnet"),
+ McpSchema.ModelHint.of("claude")
+ ))
+ .intelligencePriority(0.8)
+ .speedPriority(0.5)
+ .build())
+ .systemPrompt("You are a helpful calculator assistant. Provide only the numerical answer.")
+ .maxTokens(100)
+ .build();
+
+ // Request sampling from the client
+ return exchange.createMessage(samplingRequest)
+ .map(result -> {
+ String answer = ((McpSchema.TextContent) result.content()).text();
+ return CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent(answer)))
+ .build();
+ });
+ })
+ .build();
+
+ // Add the tool to the server
+ server.addTool(calculatorTool)
+ .subscribe();
+ ```
+
+The `CreateMessageRequest` object allows you to specify: `Content` - the input text or image for the model,
+`Model Preferences` - hints and priorities for model selection, `System Prompt` - instructions for the model's behavior and
+`Max Tokens` - maximum length of the generated response.
+
+### Using Elicitation from a Server
+
+Servers can request user input from connected clients that support elicitation:
+
+```java
+var tool = SyncToolSpecification.builder()
+ .tool(Tool.builder()
+ .name("confirm-action")
+ .description("Confirms an action with the user")
+ .inputSchema(schema)
+ .build())
+ .callHandler((exchange, request) -> {
+ // Check if client supports elicitation
+ if (exchange.getClientCapabilities().elicitation() == null) {
+ return CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent("Client does not support elicitation")))
+ .build();
+ }
+
+ // Request user confirmation
+ ElicitRequest elicitRequest = ElicitRequest.builder()
+ .message("Do you want to proceed with this action?")
+ .requestedSchema(Map.of(
+ "type", "object",
+ "properties", Map.of("confirmed", Map.of("type", "boolean"))
+ ))
+ .build();
+
+ ElicitResult result = exchange.elicit(elicitRequest);
+
+ if (result.action() == ElicitResult.Action.ACCEPT) {
+ // User accepted
+ return CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent("Action confirmed")))
+ .build();
+ } else {
+ return CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent("Action declined")))
+ .build();
+ }
+ })
+ .build();
+```
+
+### Logging Support
+
+The server provides structured logging capabilities that allow sending log messages to clients with different severity levels.
+Log notifications can only be sent from within an existing client session, such as tools, resources, and prompts calls.
+
+The server can send log messages using the `McpAsyncServerExchange`/`McpSyncServerExchange` object in the tool/resource/prompt handler function:
+
+```java
+var tool = new McpServerFeatures.AsyncToolSpecification(
+ Tool.builder().name("logging-test").description("Test logging notifications").inputSchema(emptyJsonSchema).build(),
+ null,
+ (exchange, request) -> {
+
+ exchange.loggingNotification( // Use the exchange to send log messages
+ McpSchema.LoggingMessageNotification.builder()
+ .level(McpSchema.LoggingLevel.DEBUG)
+ .logger("test-logger")
+ .data("Debug message")
+ .build())
+ .block();
+
+ return Mono.just(CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent("Logging test completed")))
+ .build());
+ });
+
+var mcpServer = McpServer.async(mcpServerTransportProvider)
+ .serverInfo("test-server", "1.0.0")
+ .capabilities(
+ ServerCapabilities.builder()
+ .logging() // Enable logging support
+ .tools(true)
+ .build())
+ .tools(tool)
+ .build();
+```
+
+On the client side, you can register a logging consumer to receive log messages from the server:
+
+```java
+var mcpClient = McpClient.sync(transport)
+ .loggingConsumer(notification -> {
+ System.out.println("Received log message: " + notification.data());
+ })
+ .build();
+
+mcpClient.initialize();
+mcpClient.setLoggingLevel(McpSchema.LoggingLevel.INFO);
+```
+
+Clients can control the minimum logging level they receive through the `mcpClient.setLoggingLevel(level)` request. Messages below the set level will be filtered out.
+Supported logging levels (in order of increasing severity): DEBUG (0), INFO (1), NOTICE (2), WARNING (3), ERROR (4), CRITICAL (5), ALERT (6), EMERGENCY (7)
+
+## Error Handling
+
+The SDK provides comprehensive error handling through the McpError class, covering protocol compatibility, transport communication, JSON-RPC messaging, tool execution, resource management, prompt handling, timeouts, and connection issues. This unified error handling approach ensures consistent and reliable error management across both synchronous and asynchronous operations.
diff --git a/mcp-bom/pom.xml b/mcp-bom/pom.xml
index 447c9e0bd..fb6f3a32a 100644
--- a/mcp-bom/pom.xml
+++ b/mcp-bom/pom.xml
@@ -7,7 +7,7 @@
io.modelcontextprotocol.sdkmcp-parent
- 0.18.0-SNAPSHOT
+ 1.1.0-SNAPSHOTmcp-bom
@@ -40,13 +40,6 @@
${project.version}
-
-
- io.modelcontextprotocol.sdk
- mcp-json
- ${project.version}
-
-
io.modelcontextprotocol.sdk
@@ -61,20 +54,6 @@
${project.version}
-
-
- io.modelcontextprotocol.sdk
- mcp-spring-webflux
- ${project.version}
-
-
-
-
- io.modelcontextprotocol.sdk
- mcp-spring-webmvc
- ${project.version}
-
-
diff --git a/mcp-core/pom.xml b/mcp-core/pom.xml
index 9e23ffd79..4de0fba2b 100644
--- a/mcp-core/pom.xml
+++ b/mcp-core/pom.xml
@@ -6,7 +6,7 @@
io.modelcontextprotocol.sdkmcp-parent
- 0.18.0-SNAPSHOT
+ 1.1.0-SNAPSHOTmcp-corejar
@@ -35,13 +35,13 @@
-
- io.modelcontextprotocol.sdk
- mcp-json
- 0.18.0-SNAPSHOT
- org.slf4j
@@ -80,7 +75,7 @@
com.fasterxml.jackson.corejackson-annotations
- ${jackson.version}
+ ${jackson-annotations.version}
@@ -97,45 +92,6 @@
provided
-
-
- io.modelcontextprotocol.sdk
- mcp-json-jackson2
- 0.18.0-SNAPSHOT
- test
-
-
-
- org.springframework
- spring-webmvc
- ${springframework.version}
- test
-
-
-
-
- io.projectreactor.netty
- reactor-netty-http
- test
-
-
-
-
- org.springframework
- spring-context
- ${springframework.version}
- test
-
-
-
- org.springframework
- spring-test
- ${springframework.version}
- test
-
-
org.assertjassertj-core
@@ -201,20 +157,6 @@
test
-
-
- org.apache.tomcat.embed
- tomcat-embed-core
- ${tomcat.version}
- test
-
-
- org.apache.tomcat.embed
- tomcat-embed-websocket
- ${tomcat.version}
- test
-
-
org.testcontainerstoxiproxy
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java
index e6a09cd08..93fcc332a 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java
@@ -30,6 +30,7 @@
import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest;
import io.modelcontextprotocol.spec.McpSchema.GetPromptResult;
+import io.modelcontextprotocol.util.ToolNameValidator;
import io.modelcontextprotocol.spec.McpSchema.ListPromptsResult;
import io.modelcontextprotocol.spec.McpSchema.LoggingLevel;
import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
@@ -656,6 +657,10 @@ private Mono listToolsInternal(Initialization init, S
.sendRequest(McpSchema.METHOD_TOOLS_LIST, new McpSchema.PaginatedRequest(cursor),
LIST_TOOLS_RESULT_TYPE_REF)
.doOnNext(result -> {
+ // Validate tool names (warn only)
+ if (result.tools() != null) {
+ result.tools().forEach(tool -> ToolNameValidator.validate(tool.name(), false));
+ }
if (this.enableCallToolSchemaCaching && result.tools() != null) {
// Cache tools output schema
result.tools()
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/McpClient.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/McpClient.java
index c9989f832..12f34e60a 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/client/McpClient.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/McpClient.java
@@ -5,6 +5,7 @@
package io.modelcontextprotocol.client;
import io.modelcontextprotocol.common.McpTransportContext;
+import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
@@ -492,7 +493,7 @@ public McpSyncClient build() {
McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures);
return new McpSyncClient(new McpAsyncClient(transport, this.requestTimeout, this.initializationTimeout,
- jsonSchemaValidator != null ? jsonSchemaValidator : JsonSchemaValidator.getDefault(),
+ jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator(),
asyncFeatures), this.contextProvider);
}
@@ -826,7 +827,7 @@ public AsyncSpec enableCallToolSchemaCaching(boolean enableCallToolSchemaCaching
*/
public McpAsyncClient build() {
var jsonSchemaValidator = (this.jsonSchemaValidator != null) ? this.jsonSchemaValidator
- : JsonSchemaValidator.getDefault();
+ : McpJsonDefaults.getSchemaValidator();
return new McpAsyncClient(this.transport, this.requestTimeout, this.initializationTimeout,
jsonSchemaValidator,
new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.roots,
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java
index ae093316f..be4e4cf97 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java
@@ -22,6 +22,7 @@
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.common.McpTransportContext;
+import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.HttpHeaders;
@@ -184,19 +185,6 @@ public static class Builder {
// Default constructor
}
- /**
- * Creates a new builder with the specified base URI.
- * @param baseUri the base URI of the MCP server
- * @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead.
- * This constructor is deprecated and will be removed or made {@code protected} or
- * {@code private} in a future release.
- */
- @Deprecated(forRemoval = true)
- public Builder(String baseUri) {
- Assert.hasText(baseUri, "baseUri must not be empty");
- this.baseUri = baseUri;
- }
-
/**
* Sets the base URI.
* @param baseUri the base URI
@@ -327,7 +315,7 @@ public Builder connectTimeout(Duration connectTimeout) {
public HttpClientSseClientTransport build() {
HttpClient httpClient = this.clientBuilder.connectTimeout(this.connectTimeout).build();
return new HttpClientSseClientTransport(httpClient, requestBuilder, baseUri, sseEndpoint,
- jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, httpRequestCustomizer);
+ jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, httpRequestCustomizer);
}
}
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java
index e41f45ebb..d6b01e17f 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java
@@ -25,6 +25,7 @@
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.common.McpTransportContext;
+import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.ClosedMcpTransportSession;
@@ -295,12 +296,23 @@ private Mono reconnect(McpTransportStream stream) {
if (statusCode >= 200 && statusCode < 300) {
if (MESSAGE_EVENT_TYPE.equals(responseEvent.sseEvent().event())) {
+ String data = responseEvent.sseEvent().data();
+ // Per 2025-11-25 spec (SEP-1699), servers may
+ // send SSE events
+ // with empty data to prime the client for
+ // reconnection.
+ // Skip these events as they contain no JSON-RPC
+ // message.
+ if (data == null || data.isBlank()) {
+ logger.debug("Skipping SSE event with empty data (stream primer)");
+ return Flux.empty();
+ }
try {
// We don't support batching ATM and probably
// won't since the next version considers
// removing it.
- McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(
- this.jsonMapper, responseEvent.sseEvent().data());
+ McpSchema.JSONRPCMessage message = McpSchema
+ .deserializeJsonRpcMessage(this.jsonMapper, data);
Tuple2, Iterable> idWithMessages = Tuples
.of(Optional.ofNullable(responseEvent.sseEvent().id()),
@@ -491,7 +503,9 @@ public Mono sendMessage(McpSchema.JSONRPCMessage sentMessage) {
.firstValue(HttpHeaders.CONTENT_LENGTH)
.orElse(null);
- if (contentType.isBlank() || "0".equals(contentLength)) {
+ // For empty content or HTTP code 202 (ACCEPTED), assume success
+ if (contentType.isBlank() || "0".equals(contentLength) || statusCode == 202) {
+ // if (contentType.isBlank() || "0".equals(contentLength)) {
logger.debug("No body returned for POST in session {}", sessionRepresentation);
// No content type means no response body, so we can just
// return an empty stream
@@ -501,13 +515,22 @@ public Mono sendMessage(McpSchema.JSONRPCMessage sentMessage) {
else if (contentType.contains(TEXT_EVENT_STREAM)) {
return Flux.just(((ResponseSubscribers.SseResponseEvent) responseEvent).sseEvent())
.flatMap(sseEvent -> {
+ String data = sseEvent.data();
+ // Per 2025-11-25 spec (SEP-1699), servers may send SSE
+ // events
+ // with empty data to prime the client for reconnection.
+ // Skip these events as they contain no JSON-RPC message.
+ if (data == null || data.isBlank()) {
+ logger.debug("Skipping SSE event with empty data (stream primer)");
+ return Flux.empty();
+ }
try {
// We don't support batching ATM and probably
// won't
// since the
// next version considers removing it.
McpSchema.JSONRPCMessage message = McpSchema
- .deserializeJsonRpcMessage(this.jsonMapper, sseEvent.data());
+ .deserializeJsonRpcMessage(this.jsonMapper, data);
Tuple2, Iterable> idWithMessages = Tuples
.of(Optional.ofNullable(sseEvent.id()), List.of(message));
@@ -639,7 +662,7 @@ public static class Builder {
private Duration connectTimeout = Duration.ofSeconds(10);
private List supportedProtocolVersions = List.of(ProtocolVersions.MCP_2024_11_05,
- ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18);
+ ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25);
/**
* Creates a new builder with the specified base URI.
@@ -820,7 +843,7 @@ public Builder supportedProtocolVersions(List supportedProtocolVersions)
*/
public HttpClientStreamableHttpTransport build() {
HttpClient httpClient = this.clientBuilder.connectTimeout(this.connectTimeout).build();
- return new HttpClientStreamableHttpTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
+ return new HttpClientStreamableHttpTransport(jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper,
httpClient, requestBuilder, baseUri, endpoint, resumableStreams, openConnectionOnStartup,
httpRequestCustomizer, supportedProtocolVersions);
}
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonDefaults.java b/mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonDefaults.java
new file mode 100644
index 000000000..11b370ed8
--- /dev/null
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonDefaults.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright 2026 - 2026 the original author or authors.
+ */
+package io.modelcontextprotocol.json;
+
+import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
+import io.modelcontextprotocol.json.schema.JsonSchemaValidatorSupplier;
+import io.modelcontextprotocol.util.McpServiceLoader;
+
+/**
+ * This class is to be used to provide access to the default {@link McpJsonMapper} and to
+ * the default {@link JsonSchemaValidator} instances via the static methods:
+ * {@link #getMapper()} and {@link #getSchemaValidator()}.
+ *
+ * The initialization of (singleton) instances of this class is different in non-OSGi
+ * environments and OSGi environments. Specifically, in non-OSGi environments the
+ * {@code McpJsonDefaults} class will be loaded by whatever classloader is used to call
+ * one of the existing static get methods for the first time. For servers, this will
+ * usually be in response to the creation of the first {@code McpServer} instance. At that
+ * first time, the {@code mcpMapperServiceLoader} and {@code mcpValidatorServiceLoader}
+ * will be null, and the {@code McpJsonDefaults} constructor will be called,
+ * creating/initializing the {@code mcpMapperServiceLoader} and the
+ * {@code mcpValidatorServiceLoader}...which will then be used to call the
+ * {@code ServiceLoader.load} method.
+ *
+ * In OSGi environments, upon bundle activation SCR will create a new (singleton) instance
+ * of {@code McpJsonDefaults} (via the constructor), and then inject suppliers via the
+ * {@code setMcpJsonMapperSupplier} and {@code setJsonSchemaValidatorSupplier} methods
+ * with the SCR-discovered instances of those services. This does depend upon the
+ * jars/bundles providing those suppliers to be started/activated. This SCR behavior is
+ * dictated by xml files in {@code OSGi-INF} directory of {@code mcp-core} (this
+ * project/jar/bundle), and the jsonmapper and jsonschemavalidator provider jars/bundles
+ * (e.g. {@code mcp-json-jackson2}, {@code mcp-json-jackson3}, or others).
+ */
+public class McpJsonDefaults {
+
+ protected static McpServiceLoader mcpMapperServiceLoader;
+
+ protected static McpServiceLoader mcpValidatorServiceLoader;
+
+ public McpJsonDefaults() {
+ mcpMapperServiceLoader = new McpServiceLoader<>(McpJsonMapperSupplier.class);
+ mcpValidatorServiceLoader = new McpServiceLoader<>(JsonSchemaValidatorSupplier.class);
+ }
+
+ void setMcpJsonMapperSupplier(McpJsonMapperSupplier supplier) {
+ mcpMapperServiceLoader.setSupplier(supplier);
+ }
+
+ void unsetMcpJsonMapperSupplier(McpJsonMapperSupplier supplier) {
+ mcpMapperServiceLoader.unsetSupplier(supplier);
+ }
+
+ public synchronized static McpJsonMapper getMapper() {
+ if (mcpMapperServiceLoader == null) {
+ new McpJsonDefaults();
+ }
+ return mcpMapperServiceLoader.getDefault();
+ }
+
+ void setJsonSchemaValidatorSupplier(JsonSchemaValidatorSupplier supplier) {
+ mcpValidatorServiceLoader.setSupplier(supplier);
+ }
+
+ void unsetJsonSchemaValidatorSupplier(JsonSchemaValidatorSupplier supplier) {
+ mcpValidatorServiceLoader.unsetSupplier(supplier);
+ }
+
+ public synchronized static JsonSchemaValidator getSchemaValidator() {
+ if (mcpValidatorServiceLoader == null) {
+ new McpJsonDefaults();
+ }
+ return mcpValidatorServiceLoader.getDefault();
+ }
+
+}
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java b/mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java
similarity index 81%
rename from mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java
rename to mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java
index 1e30cad16..8481d1703 100644
--- a/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java
@@ -87,24 +87,4 @@ public interface McpJsonMapper {
*/
byte[] writeValueAsBytes(Object value) throws IOException;
- /**
- * Returns the default {@link McpJsonMapper}.
- * @return The default {@link McpJsonMapper}
- * @throws IllegalStateException If no {@link McpJsonMapper} implementation exists on
- * the classpath.
- */
- static McpJsonMapper getDefault() {
- return McpJsonInternal.getDefaultMapper();
- }
-
- /**
- * Creates a new default {@link McpJsonMapper}.
- * @return The default {@link McpJsonMapper}
- * @throws IllegalStateException If no {@link McpJsonMapper} implementation exists on
- * the classpath.
- */
- static McpJsonMapper createDefault() {
- return McpJsonInternal.createDefaultMapper();
- }
-
}
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapperSupplier.java b/mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonMapperSupplier.java
similarity index 100%
rename from mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapperSupplier.java
rename to mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonMapperSupplier.java
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/TypeRef.java b/mcp-core/src/main/java/io/modelcontextprotocol/json/TypeRef.java
similarity index 94%
rename from mcp-json/src/main/java/io/modelcontextprotocol/json/TypeRef.java
rename to mcp-core/src/main/java/io/modelcontextprotocol/json/TypeRef.java
index ab37b43f3..725513c66 100644
--- a/mcp-json/src/main/java/io/modelcontextprotocol/json/TypeRef.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/json/TypeRef.java
@@ -9,7 +9,7 @@
/**
* Captures generic type information at runtime for parameterized JSON (de)serialization.
- * Usage: TypeRef<List<Foo>> ref = new TypeRef<>(){};
+ * Usage: TypeRef> ref = new TypeRef<>(){};
*/
public abstract class TypeRef {
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java b/mcp-core/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java
similarity index 69%
rename from mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java
rename to mcp-core/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java
index 8e35c0237..09fe604f4 100644
--- a/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java
@@ -41,24 +41,4 @@ public static ValidationResponse asInvalid(String message) {
*/
ValidationResponse validate(Map schema, Object structuredContent);
- /**
- * Creates the default {@link JsonSchemaValidator}.
- * @return The default {@link JsonSchemaValidator}
- * @throws IllegalStateException If no {@link JsonSchemaValidator} implementation
- * exists on the classpath.
- */
- static JsonSchemaValidator createDefault() {
- return JsonSchemaInternal.createDefaultValidator();
- }
-
- /**
- * Returns the default {@link JsonSchemaValidator}.
- * @return The default {@link JsonSchemaValidator}
- * @throws IllegalStateException If no {@link JsonSchemaValidator} implementation
- * exists on the classpath.
- */
- static JsonSchemaValidator getDefault() {
- return JsonSchemaInternal.getDefaultValidator();
- }
-
}
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidatorSupplier.java b/mcp-core/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidatorSupplier.java
similarity index 100%
rename from mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidatorSupplier.java
rename to mcp-core/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidatorSupplier.java
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java
index d1b55f594..660a15e6a 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java
@@ -32,7 +32,9 @@ public Mono handleRequest(McpTransportContext transpo
McpSchema.JSONRPCRequest request) {
McpStatelessRequestHandler> requestHandler = this.requestHandlers.get(request.method());
if (requestHandler == null) {
- return Mono.error(new McpError("Missing handler for request type: " + request.method()));
+ return Mono.error(McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND)
+ .message("Missing handler for request type: " + request.method())
+ .build());
}
return requestHandler.handle(transportContext, request.params())
.map(result -> new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), result, null))
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java
index 23285d514..32256987a 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java
@@ -326,7 +326,7 @@ public Mono addTool(McpServerFeatures.AsyncToolSpecification toolSpecifica
if (toolSpecification.tool() == null) {
return Mono.error(new IllegalArgumentException("Tool must not be null"));
}
- if (toolSpecification.call() == null && toolSpecification.callHandler() == null) {
+ if (toolSpecification.callHandler() == null) {
return Mono.error(new IllegalArgumentException("Tool call handler must not be null"));
}
if (this.serverCapabilities.tools() == null) {
@@ -869,32 +869,6 @@ private McpRequestHandler promptsGetRequestHandler()
// Logging Management
// ---------------------------------------
- /**
- * This implementation would, incorrectly, broadcast the logging message to all
- * connected clients, using a single minLoggingLevel for all of them. Similar to the
- * sampling and roots, the logging level should be set per client session and use the
- * ServerExchange to send the logging message to the right client.
- * @param loggingMessageNotification The logging message to send
- * @return A Mono that completes when the notification has been sent
- * @deprecated Use
- * {@link McpAsyncServerExchange#loggingNotification(LoggingMessageNotification)}
- * instead.
- */
- @Deprecated
- public Mono loggingNotification(LoggingMessageNotification loggingMessageNotification) {
-
- if (loggingMessageNotification == null) {
- return Mono.error(new McpError("Logging message must not be null"));
- }
-
- if (loggingMessageNotification.level().level() < minLoggingLevel.level()) {
- return Mono.empty();
- }
-
- return this.mcpTransportProvider.notifyClients(McpSchema.METHOD_NOTIFICATION_MESSAGE,
- loggingMessageNotification);
- }
-
private McpRequestHandler