Update documentation and migration notes for v2 (#1024)

Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
This commit is contained in:
Dariusz Jędrzejczyk 2026-06-11 11:38:19 +02:00 • committed by GitHub
commit 275633775c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 273 additions and 191 deletions

View file

@ -1,81 +1,19 @@
# Migration Guide — 2.0 # Migration Guide — 2.0
This document covers breaking and behavioural changes introduced in the 2.0 release of the MCP Java SDK. This guide covers the breaking and behavioural changes introduced in the 2.0 release of the MCP Java SDK, relative to 1.x, and how to update existing code.
The changes fall into these areas:
- [Schema construction and required fields](#schema-construction-and-required-fields) — non-null enforcement and the builder API.
- [Schema type and shape changes](#schema-type-and-shape-changes) — record component and type changes in `McpSchema`.
- [JSON serialization behaviour](#json-serialization-behaviour) — wire-format changes.
- [Server-side validation](#server-side-validation) — runtime validation of tool arguments and embedded schemas.
- [Transport changes](#transport-changes) — removed methods and the SSE deprecation.
- [New features](#new-features) — additive, backward-compatible capabilities.
--- ---
## Jackson / JSON serialization changes ## Schema construction and required fields
### Sealed interfaces removed
The following interfaces were `sealed` in 1.x and are now plain interfaces in 2.0:
- `McpSchema.JSONRPCMessage`
- `McpSchema.Request`
- `McpSchema.Result`
- `McpSchema.Notification`
- `McpSchema.ResourceContents`
- `McpSchema.CompleteReference`
- `McpSchema.Content`
**Impact:** Exhaustive `switch` expressions or `switch` statements that relied on the sealed hierarchy for completeness checking must add a `default` branch. The compiler will no longer reject switches that omit one of the known subtypes.
### `CompleteReference` now carries `@JsonTypeInfo`
`CompleteReference` (and its implementations `PromptReference` and `ResourceReference`) is now annotated with `@JsonTypeInfo(use = NAME, include = EXISTING_PROPERTY, property = "type", visible = true)`. Jackson will automatically dispatch to the correct subtype based on the `"type"` field in the JSON without any hand-written map-walking code.
**Action:** Remove any custom code that manually inspected the `"type"` field of a completion reference map and instantiated `PromptReference` / `ResourceReference` by hand. A plain `mapper.readValue(json, CompleteRequest.class)` or `mapper.convertValue(paramsMap, CompleteRequest.class)` is sufficient.
### `Prompt` canonical constructor no longer coerces `null` arguments
In 1.x, `new Prompt(name, description, null)` silently stored an empty list for `arguments`. In 2.0 it stores `null`.
**Action:**
- Code that expected `prompt.arguments()` to return an empty list when not provided will now receive `null`. Add a null-check or use the new `Prompt.withDefaults(name, description, arguments)` factory, which preserves the old behaviour by coercing `null` to `[]`.
- On the wire, a prompt without an `arguments` field deserializes with `arguments == null` (it is not coerced to an empty list).
### `CompleteCompletion` optional fields omitted when null
`CompleteResult.CompleteCompletion.total` and `CompleteCompletion.hasMore` are now omitted from serialized JSON when they are `null` (previously they were always emitted). Deserializers that required these fields to be present in every response must be updated to treat their absence as `null`.
### `CompleteCompletion.values` is mandatory in the Java API
The compact constructor for `CompleteCompletion` asserts that `values` is not `null`. Code that constructed a completion result with a null `values` list will now fail at runtime.
**Action:** Always pass a non-null list (for example `List.of()` when there are no suggestions).
### `LoggingLevel` deserialization is lenient
`LoggingLevel` now uses a `@JsonCreator` factory (`fromValue`) so that JSON string values deserialize in a case-insensitive way. **Unrecognized level strings deserialize to `null`** instead of causing deserialization to fail.
**Impact:** `SetLevelRequest`, `LoggingMessageNotification`, and any other type that embeds `LoggingLevel` can observe a `null` level when the wire value is unknown or misspelled. Downstream code must null-check or validate before use.
### `Content.type()` is ignored for Jackson serialization
The `Content` interface still exposes `type()` as a convenience for Java callers, but the method is annotated with `@JsonIgnore` so Jackson does not treat it as a duplicate `"type"` property alongside `@JsonTypeInfo` on the interface.
**Impact:** Custom serializers or `ObjectMapper` configuration that relied on serializing `Content` through the default `type()` accessor alone should use the concrete content records (each of which carries a real `"type"` property) or the polymorphic setup on `Content`.
### `ServerParameters` no longer carries Jackson annotations
`ServerParameters` (in `client/transport`) has had its `@JsonProperty` and `@JsonInclude` annotations removed. It was never a wire type and is not serialized to JSON in normal SDK usage. If your code serialized or deserialized `ServerParameters` using Jackson, switch to a plain map or a dedicated DTO.
### Record annotation sweep
Wire-oriented `public record` types in `McpSchema` consistently use `@JsonInclude(JsonInclude.Include.NON_ABSENT)` (or equivalent per-type configuration) and `@JsonIgnoreProperties(ignoreUnknown = true)`. Nested capability objects under `ClientCapabilities` / `ServerCapabilities` (for example `Sampling`, `Elicitation`, `CompletionCapabilities`, `LoggingCapabilities`, prompt/resource/tool capability records) also ignore unknown JSON properties. This means:
- **Unknown fields** in incoming JSON are silently ignored, improving forward compatibility with newer server or client versions.
- **Absent optional properties** are omitted from outgoing JSON where `NON_ABSENT` applies, and optional Java components deserialize as `null` when missing on the wire.
### `Tool.inputSchema` is `Map<String, Object>`, not `JsonSchema`
The `Tool` record now models `inputSchema` (and `outputSchema`) as arbitrary JSON Schema objects as `Map<String, Object>`, so dialect-specific keywords (`$ref`, `unevaluatedProperties`, vendor extensions, and so on) round-trip without being trimmed by a narrow `JsonSchema` record.
**Impact:**
- Java code that used `Tool.inputSchema()` as a `JsonSchema` must switch to `Map<String, Object>` (or copy into your own schema wrapper).
- `Tool.Builder.inputSchema(JsonSchema)` remains as a **deprecated** helper that maps the old record into a map; prefer `inputSchema(Map)` or `inputSchema(McpJsonMapper, String)`.
### Required MCP spec fields are enforced at construction time ### Required MCP spec fields are enforced at construction time
@ -93,30 +31,22 @@ This applies to (non-exhaustive):
**Action:** Audit any code that constructs these records with potentially-null values and provide valid, non-null arguments. **Action:** Audit any code that constructs these records with potentially-null values and provide valid, non-null arguments.
**Wire deserialization is lenient.** Records expose a `@JsonCreator fromJson` factory that substitutes safe defaults (e.g. `[]`, `""`, `0`, `INFO`, `Action.CANCEL`) for any absent required field and logs a `WARN` naming the field and the substituted value. `JSONRPCResponse.JSONRPCError` is excluded — malformed JSON-RPC error envelopes still fail immediately. **Wire deserialization stays lenient.** Records expose a `@JsonCreator fromJson` factory that substitutes safe defaults (e.g. `[]`, `""`, `0`, `INFO`, `Action.CANCEL`) for any absent required field and logs a `WARN` naming the field and the substituted value. `JSONRPCResponse.JSONRPCError` is excluded — malformed JSON-RPC error envelopes still fail immediately.
**Note:** `LoggingMessageNotification`/`SetLevelRequest` default a *missing* `level` to `INFO`, but an *unrecognized* level string still deserializes to `null` (see the `LoggingLevel` section above) and will then fail the canonical constructor. Ensure clients and servers send only recognized level strings. **Note:** `LoggingMessageNotification` / `SetLevelRequest` default a *missing* `level` to `INFO`, but an *unrecognized* level string still deserializes to `null` (see [`LoggingLevel` deserialization is lenient](#logginglevel-deserialization-is-lenient)) and will then fail the canonical constructor. Ensure clients and servers send only recognized level strings.
### `PromptReference` discriminator pinning and equality ### `Prompt` no longer coerces `null` arguments
`PromptReference` keeps its `(type, name, title)` record components, so positional construction from 1.x still compiles. Two behavioural changes: In 1.x, `new Prompt(name, description, null)` silently stored an empty list for `arguments`. In 2.0 it stores `null`.
- The compact constructor pins `type` to `ref/prompt`. Any non-null value other than `ref/prompt` is replaced with `ref/prompt` and a `WARN` is logged. The legacy two-arg `PromptReference(String type, String name)` constructor remains `@Deprecated` and routes through the canonical constructor, so it triggers the same WARN. **Action:**
- `equals`/`hashCode` now consider `name` only (title and type are ignored). Two refs with the same name but different titles compare equal.
**Action:** Audit any code that used `PromptReference` as a map key or in a `Set` — equality semantics changed. If your code constructed instances with a custom `type` string for testing, switch to `PromptReference.builder(name)` (or `new PromptReference(name)`); the WARN tells you which call sites still pass the discriminator. - Code that expected `prompt.arguments()` to return an empty list when not provided will now receive `null`. Add a null-check.
- On the wire, a prompt without an `arguments` field deserializes with `arguments == null` (it is not coerced to an empty list).
`CompleteReference.identifier()` is `@Deprecated` and now returns `null` via a default method on the interface.
### `ResourceReference` record component reduced
Components changed from `(type, uri)` to `(uri)`. Positional construction with two arguments breaks. The legacy `ResourceReference(String type, String uri)` constructor stays `@Deprecated`; it ignores `type` and logs a `WARN`. Use `new ResourceReference(uri)` or `ResourceReference.builder(uri)`. The `type()` accessor still returns `ref/resource` and Jackson serializes it via `@JsonProperty("type")` on the accessor.
### Builder API: required-first factories; old setters/no-arg builders deprecated ### Builder API: required-first factories; old setters/no-arg builders deprecated
Most records that have a builder have gained a required-first factory method (`builder(req1, req2, …)`) and the corresponding setters for required fields are removed from the builder. The old no-arg `builder()` factory and public no-arg `Builder()` constructor are kept but `@Deprecated` where they would allow constructing a builder without required state. Most records that have a builder gained a required-first factory method (`builder(req1, req2, …)`). The old no-arg `builder()` factory, the public no-arg `Builder()` constructor, and the setters for the now-required fields are kept but `@Deprecated`. They still compile, so 1.x code keeps working with deprecation warnings; migrate to the required-first factories to clear them.
Examples:
| Type | Old (deprecated) | New | | Type | Old (deprecated) | New |
|------|-----------------|-----| |------|-----------------|-----|
@ -124,7 +54,7 @@ Examples:
| `ResourceTemplate` | `ResourceTemplate.builder().uriTemplate(u).name(n)…` | `ResourceTemplate.builder(uriTemplate, name)…` | | `ResourceTemplate` | `ResourceTemplate.builder().uriTemplate(u).name(n)…` | `ResourceTemplate.builder(uriTemplate, name)…` |
| `Implementation` | `new Implementation(name, version)` | `Implementation.builder(name, version)…` | | `Implementation` | `new Implementation(name, version)` | `Implementation.builder(name, version)…` |
| `InitializeRequest` / `InitializeResult` | `… .builder()…` | `… .builder(protocolVersion, capabilities, clientInfo/serverInfo)` | | `InitializeRequest` / `InitializeResult` | `… .builder()…` | `… .builder(protocolVersion, capabilities, clientInfo/serverInfo)` |
| `Tool` | `Tool.builder().name(n)…` | `Tool.builder(name)…` | | `Tool` | `Tool.builder().name(n).inputSchema(s)…` | `Tool.builder(name, inputSchemaMap)…` or `Tool.builder(name, jsonMapper, inputSchemaJson)…` |
| `Prompt` / `PromptArgument` / `GetPromptRequest` | `… .builder().name(n)…` | `… .builder(name)…` | | `Prompt` / `PromptArgument` / `GetPromptRequest` | `… .builder().name(n)…` | `… .builder(name)…` |
| `PromptMessage` / `SamplingMessage` | `… .builder().role(r).content(c)…` | `… .builder(role, content)…` | | `PromptMessage` / `SamplingMessage` | `… .builder().role(r).content(c)…` | `… .builder(role, content)…` |
| `CreateMessageRequest` | `… .builder().messages(m).maxTokens(n)…` | `… .builder(messages, maxTokens)…` | | `CreateMessageRequest` | `… .builder().messages(m).maxTokens(n)…` | `… .builder(messages, maxTokens)…` |
@ -133,15 +63,105 @@ Examples:
| `ListResourcesResult` / `ListResourceTemplatesResult` / `ListPromptsResult` / `ListToolsResult` / `ListRootsResult` | `… .builder()…` | `… .builder(items)…` | | `ListResourcesResult` / `ListResourceTemplatesResult` / `ListPromptsResult` / `ListToolsResult` / `ListRootsResult` | `… .builder()…` | `… .builder(items)…` |
| `ReadResourceRequest` / `SubscribeRequest` / `UnsubscribeRequest` / `ResourcesUpdatedNotification` / `Root` | n/a | `… .builder(uri)…` | | `ReadResourceRequest` / `SubscribeRequest` / `UnsubscribeRequest` / `ResourcesUpdatedNotification` / `Root` | n/a | `… .builder(uri)…` |
| `ReadResourceResult` | n/a | `ReadResourceResult.builder(contents)…` | | `ReadResourceResult` | n/a | `ReadResourceResult.builder(contents)…` |
| `TextResourceContents` / `BlobResourceContents` | n/a | `… .builder(uri, text|blob)…` | | `GetPromptResult` | `new GetPromptResult(description, messages)` | `GetPromptResult.builder(messages).description(d)…` |
| `TextResourceContents` / `BlobResourceContents` | n/a | `… .builder(uri, text\|blob)…` |
| `TextContent` / `ImageContent` / `AudioContent` / `EmbeddedResource` | n/a | `… .builder(text \| data, mimeType \| resource)…` | | `TextContent` / `ImageContent` / `AudioContent` / `EmbeddedResource` | n/a | `… .builder(text \| data, mimeType \| resource)…` |
| `CallToolResult` | unchanged | also: required-first content set via builder constructor remains optional |
| `ProgressNotification` | n/a | `ProgressNotification.builder(progressToken, progress)` | | `ProgressNotification` | n/a | `ProgressNotification.builder(progressToken, progress)` |
| `JSONRPCResponse.JSONRPCError` | n/a | `JSONRPCError.builder(code, message)` | | `JSONRPCResponse.JSONRPCError` | n/a | `JSONRPCError.builder(code, message)` |
| `CompleteRequest` | n/a | `CompleteRequest.builder(ref, argument)` | | `CompleteRequest` | n/a | `CompleteRequest.builder(ref, argument)` |
| `Annotations` | n/a | `Annotations.builder()` | | `Annotations` | n/a | `Annotations.builder()` |
| Capabilities (`Sampling`, `Elicitation`, `Roots`, `LoggingCapabilities`, `CompletionCapabilities`, prompt/resource/tool capabilities) | n/a | `… .builder()…` | | Capabilities (`Sampling`, `Elicitation`, `Roots`, `LoggingCapabilities`, `CompletionCapabilities`, prompt/resource/tool capabilities) | n/a | `… .builder()…` |
---
## Schema type and shape changes
### `Tool.inputSchema` is `Map<String, Object>`, not `JsonSchema`
The `Tool` record now models `inputSchema` (and `outputSchema`) as arbitrary JSON Schema objects of type `Map<String, Object>`, so dialect-specific keywords (`$ref`, `unevaluatedProperties`, vendor extensions, and so on) round-trip without being trimmed by a narrow `JsonSchema` record.
**Action:**
- Java code that used `Tool.inputSchema()` as a `JsonSchema` must switch to `Map<String, Object>` (or copy into your own schema wrapper).
- `Tool.Builder.inputSchema(JsonSchema)` remains as a **deprecated** helper that maps the old record into a map; prefer `inputSchema(Map)` or `inputSchema(McpJsonMapper, String)`.
### Sealed interfaces removed
The following interfaces were `sealed` in 1.x and are now plain interfaces in 2.0:
- `McpSchema.JSONRPCMessage`
- `McpSchema.Request`
- `McpSchema.Result`
- `McpSchema.Notification`
- `McpSchema.ResourceContents`
- `McpSchema.CompleteReference`
- `McpSchema.Content`
**Impact:** Exhaustive `switch` expressions or statements that relied on the sealed hierarchy for completeness checking must add a `default` branch. The compiler will no longer reject switches that omit one of the known subtypes.
### `CompleteReference` polymorphic deserialization
`CompleteReference` (and its implementations `PromptReference` and `ResourceReference`) is now annotated with `@JsonTypeInfo(use = NAME, include = EXISTING_PROPERTY, property = "type", visible = true)`. Jackson dispatches to the correct subtype based on the `"type"` field automatically.
**Action:** Remove any custom code that manually inspected the `"type"` field of a completion reference map and instantiated `PromptReference` / `ResourceReference` by hand. A plain `mapper.readValue(json, CompleteRequest.class)` or `mapper.convertValue(paramsMap, CompleteRequest.class)` is sufficient.
`CompleteReference.identifier()` is `@Deprecated` and now returns `null` via a default method on the interface.
### `PromptReference` discriminator pinning and equality
`PromptReference` keeps its `(type, name, title)` record components, so positional construction from 1.x still compiles, with two behavioural changes:
- The compact constructor pins `type` to `ref/prompt`. Any non-null value other than `ref/prompt` is replaced and a `WARN` is logged. The legacy two-arg `PromptReference(String type, String name)` constructor remains `@Deprecated` and routes through the canonical constructor, so it triggers the same WARN.
- `equals`/`hashCode` now consider `name` only (title and type are ignored). Two refs with the same name but different titles compare equal.
**Action:** Audit any code that used `PromptReference` as a map key or in a `Set` — equality semantics changed. If you constructed instances with a custom `type` string, switch to `PromptReference.builder(name)` (or `new PromptReference(name)`); the WARN identifies the call sites still passing a discriminator.
### `ResourceReference` record component reduced
Components changed from `(type, uri)` to `(uri)`. Positional construction with two arguments breaks. The legacy `ResourceReference(String type, String uri)` constructor stays `@Deprecated`; it ignores `type` and logs a `WARN`. Use `new ResourceReference(uri)` or `ResourceReference.builder(uri)`. The `type()` accessor still returns `ref/resource` and Jackson serializes it via `@JsonProperty("type")` on the accessor.
### `ElicitRequest` is now an interface
To support URL-mode elicitation (see [New features](#new-features)), the elicitation request type was split:
- `ElicitRequest` changed from a `record` to an `interface`.
- The original form-based request record is now `ElicitFormRequest`.
- The `McpClient` builder `elicitation(...)` methods now accept a handler over `ElicitFormRequest` instead of `ElicitRequest`.
**Action:** Replace references to the old `ElicitRequest` record (construction, `instanceof`, handler signatures) with `ElicitFormRequest`. Code that only referred to `ElicitRequest` as a type continues to compile against the new interface.
### `ServerParameters` no longer carries Jackson annotations
`ServerParameters` (in `client/transport`) has had its `@JsonProperty` and `@JsonInclude` annotations removed. It was never a wire type and is not serialized to JSON in normal SDK usage. If your code serialized or deserialized `ServerParameters` using Jackson, switch to a plain map or a dedicated DTO.
---
## JSON serialization behaviour
### Unknown JSON fields are ignored
Wire-oriented `public record` types in `McpSchema` consistently use `@JsonInclude(JsonInclude.Include.NON_ABSENT)` and `@JsonIgnoreProperties(ignoreUnknown = true)`. Nested capability objects under `ClientCapabilities` / `ServerCapabilities` (for example `Sampling`, `Elicitation`, `CompletionCapabilities`, `LoggingCapabilities`, and the prompt/resource/tool capability records) also ignore unknown JSON properties. As a result:
- **Unknown fields** in incoming JSON are silently ignored, improving forward compatibility with newer server or client versions.
- **Absent optional properties** are omitted from outgoing JSON where `NON_ABSENT` applies, and optional Java components deserialize as `null` when missing on the wire.
### `CompleteCompletion` field handling
- `CompleteResult.CompleteCompletion.total` and `CompleteCompletion.hasMore` are now omitted from serialized JSON when `null` (previously they were always emitted). Deserializers that required these fields to be present must treat their absence as `null`.
- The compact constructor asserts that `values` is not `null`. **Action:** always pass a non-null list (for example `List.of()` when there are no suggestions).
### `LoggingLevel` deserialization is lenient
`LoggingLevel` now uses a `@JsonCreator` factory (`fromValue`) so JSON string values deserialize case-insensitively. **Unrecognized level strings deserialize to `null`** instead of failing.
**Impact:** `SetLevelRequest`, `LoggingMessageNotification`, and any other type embedding `LoggingLevel` can observe a `null` level when the wire value is unknown or misspelled. Downstream code must null-check or validate before use.
### `Content.type()` is ignored for Jackson serialization
The `Content` interface still exposes `type()` as a convenience for Java callers, but the method is annotated with `@JsonIgnore` so Jackson does not treat it as a duplicate `"type"` property alongside `@JsonTypeInfo` on the interface.
**Impact:** Custom serializers or `ObjectMapper` configuration that relied on serializing `Content` through the `type()` accessor alone should use the concrete content records (each of which carries a real `"type"` property) or the polymorphic setup on `Content`.
### JSON-RPC envelope ergonomics ### JSON-RPC envelope ergonomics
In 1.x, every envelope was constructed via the canonical record constructor and the literal `"2.0"` `jsonrpc` string had to be threaded through every call site: In 1.x, every envelope was constructed via the canonical record constructor and the literal `"2.0"` `jsonrpc` string had to be threaded through every call site:
@ -164,12 +184,58 @@ JSONRPCResponse.result(id, result);
JSONRPCResponse.error(id, new JSONRPCError(code, message)); // 2-arg error JSONRPCResponse.error(id, new JSONRPCError(code, message)); // 2-arg error
``` ```
`JSONRPCResponse`'s compact constructor additionally enforces the JSON-RPC invariant that exactly one of `result` / `error` is set — previously the SDK could build envelopes that violated the protocol. `JSONRPCResponse`'s compact constructor additionally enforces the JSON-RPC invariant that exactly one of `result` / `error` is set — previously the SDK could build envelopes that violated the protocol. The 1.x canonical 4-arg constructors continue to compile.
The 1.x canonical 4-arg constructors continue to compile. ---
### Optional JSON Schema validation on `tools/call` (server) ## Server-side validation
### Optional JSON Schema validation on `tools/call`
When a `JsonSchemaValidator` is available (including the default from `McpJsonDefaults.getSchemaValidator()` when you do not configure one explicitly) and `validateToolInputs` is left at its default of `true`, the server validates incoming tool arguments against `tool.inputSchema()` before invoking the tool. Failed validation produces a `CallToolResult` with `isError` set and a textual error in the content. When a `JsonSchemaValidator` is available (including the default from `McpJsonDefaults.getSchemaValidator()` when you do not configure one explicitly) and `validateToolInputs` is left at its default of `true`, the server validates incoming tool arguments against `tool.inputSchema()` before invoking the tool. Failed validation produces a `CallToolResult` with `isError` set and a textual error in the content.
**Action:** Ensure `inputSchema` maps are valid for your validator, tighten client arguments, or disable validation with `validateToolInputs(false)` on the server builder if you must preserve pre-2.0 behaviour. **Action:** Ensure `inputSchema` maps are valid for your validator, tighten client arguments, or disable validation with `validateToolInputs(false)` on the server builder if you must preserve pre-2.0 behaviour.
### Embedded JSON Schemas are validated against 2020-12 (SEP-1613)
The JSON Schema documents that MCP embeds — `Tool.inputSchema`, `Tool.outputSchema`, and `ElicitRequest.requestedSchema` — are now validated against the JSON Schema 2020-12 meta-schema by default. Servers reject malformed schemas at **build time** (`McpServer.build()`) and at **runtime** (`addTool()`) with an `IllegalArgumentException` that names the offending field and references SEP-1613. Elicitation requests whose `requestedSchema` violates the meta-schema are rejected before being sent to the client.
Schemas that explicitly declare a different dialect via `$schema` are accepted without meta-schema validation — 2020-12 is the default, not the only permitted dialect.
**Action:** Make embedded schemas valid 2020-12 documents, or set an explicit `$schema` to opt into a different dialect.
---
## Transport changes
### `customizeRequest()` removed from the HttpClient transport builders
The deprecated `Builder.customizeRequest(Consumer<HttpRequest.Builder>)` method on `HttpClientSseClientTransport` and `HttpClientStreamableHttpTransport` has been removed.
**Action:** Use `requestBuilder(HttpRequest.Builder)` for static request setup, or `httpRequestCustomizer(McpSyncHttpClientRequestCustomizer)` for per-request customization.
### SSE transports are deprecated
The HTTP+SSE client and server transports (and their supporting validator/exception types) are deprecated in favour of Streamable HTTP — `HttpClientStreamableHttpTransport` on the client, and `HttpServletStreamableServerTransportProvider` on the server. They still work; plan a move to Streamable HTTP.
---
## New features
These are additive and backward-compatible.
### URL elicitation (SEP-1036)
Servers can request out-of-band URL input from users (for example payment or API-key flows) during tool execution. Adds `ElicitUrlRequest`, `urlElicitation()` / `elicitationCompleteConsumer(s)()` builder methods on `McpClient`, `sendElicitationComplete()` on `McpAsyncServer`/`McpSyncServer`, the `ElicitationCompleteNotification` record, and the `URL_ELICITATION_REQUIRED` error code. See the [`ElicitRequest` interface change](#elicitrequest-is-now-an-interface) for the related breaking change.
### Client-side elicitation defaults (SEP-1034)
A new opt-in `McpClient` builder option `applyElicitationDefaults(boolean)` fills missing keys of an accepted `ElicitResult.content` with the `default` values declared in the request's `requestedSchema` before returning the result to the server. It is a local client config, not a wire capability.
### Icons and metadata (SEP-973)
A new `Icon` record and an optional `icons` field were added to `Implementation`, `Resource`, `ResourceTemplate`, `Prompt`, and `Tool`. `Implementation` also gains optional `description` and `websiteUrl` fields. All fields are optional; existing constructors and builders are unchanged.
### `_meta` on paginated list queries
The client list operations accept an optional `_meta` map alongside the pagination cursor: `listResources(String cursor, Map<String, Object> meta)`, `listResourceTemplates(...)`, `listPrompts(...)`, and `listTools(...)`.

View file

@ -47,20 +47,21 @@ The client provides both synchronous and asynchronous APIs for flexibility in di
// Call a tool // Call a tool
CallToolResult result = client.callTool( CallToolResult result = client.callTool(
new CallToolRequest("calculator", CallToolRequest.builder("calculator")
Map.of("operation", "add", "a", 2, "b", 3)) .arguments(Map.of("operation", "add", "a", 2, "b", 3))
.build()
); );
// List and read resources // List and read resources
ListResourcesResult resources = client.listResources(); ListResourcesResult resources = client.listResources();
ReadResourceResult resource = client.readResource( ReadResourceResult resource = client.readResource(
new ReadResourceRequest("resource://uri") ReadResourceRequest.builder("resource://uri").build()
); );
// List and use prompts // List and use prompts
ListPromptsResult prompts = client.listPrompts(); ListPromptsResult prompts = client.listPrompts();
GetPromptResult prompt = client.getPrompt( GetPromptResult prompt = client.getPrompt(
new GetPromptRequest("greeting", Map.of("name", "Spring")) GetPromptRequest.builder("greeting").arguments(Map.of("name", "Spring")).build()
); );
// Add/remove roots // Add/remove roots
@ -102,24 +103,22 @@ The client provides both synchronous and asynchronous APIs for flexibility in di
client.initialize() client.initialize()
.flatMap(initResult -> client.listTools()) .flatMap(initResult -> client.listTools())
.flatMap(tools -> { .flatMap(tools -> {
return client.callTool(new CallToolRequest( return client.callTool(CallToolRequest.builder("calculator")
"calculator", .arguments(Map.of("operation", "add", "a", 2, "b", 3))
Map.of("operation", "add", "a", 2, "b", 3) .build());
));
}) })
.flatMap(result -> { .flatMap(result -> {
return client.listResources() return client.listResources()
.flatMap(resources -> .flatMap(resources ->
client.readResource(new ReadResourceRequest("resource://uri")) client.readResource(ReadResourceRequest.builder("resource://uri").build())
); );
}) })
.flatMap(resource -> { .flatMap(resource -> {
return client.listPrompts() return client.listPrompts()
.flatMap(prompts -> .flatMap(prompts ->
client.getPrompt(new GetPromptRequest( client.getPrompt(GetPromptRequest.builder("greeting")
"greeting", .arguments(Map.of("name", "Spring"))
Map.of("name", "Spring") .build())
))
); );
}) })
.flatMap(prompt -> { .flatMap(prompt -> {
@ -144,7 +143,7 @@ Creates transport for process-based communication using stdin/stdout:
ServerParameters params = ServerParameters.builder("npx") ServerParameters params = ServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-everything", "dir") .args("-y", "@modelcontextprotocol/server-everything", "dir")
.build(); .build();
McpTransport transport = new StdioClientTransport(params); McpTransport transport = new StdioClientTransport(params, McpJsonDefaults.getMapper());
``` ```
### Streamable HTTP ### Streamable HTTP
@ -184,7 +183,7 @@ McpTransport transport = new StdioClientTransport(params);
Creates a framework-agnostic (pure Java API) SSE client transport. Included in the core `mcp` module: Creates a framework-agnostic (pure Java API) SSE client transport. Included in the core `mcp` module:
```java ```java
McpTransport transport = new HttpClientSseClientTransport("http://your-mcp-server"); McpTransport transport = HttpClientSseClientTransport.builder("http://your-mcp-server").build();
``` ```
=== "SSE WebClient (external)" === "SSE WebClient (external)"
@ -301,6 +300,17 @@ The `ElicitResult` supports three actions:
- `DECLINE` - The user declined to provide the information - `DECLINE` - The user declined to provide the information
- `CANCEL` - The operation was cancelled - `CANCEL` - The operation was cancelled
You can optionally have the client fill in missing values from the schema's `default` declarations before returning an accepted result to the server:
```java
var client = McpClient.sync(transport)
.applyElicitationDefaults(true) // default is false
.elicitation(formElicitationHandler)
.build();
```
When enabled, any keys absent from an accepted `ElicitResult.content` are populated with the `default` values declared in the request's `requestedSchema`.
#### URL Elicitation Required Handling #### URL Elicitation Required Handling
When a server requires out-of-band URL elicitation but the client has not negotiated support for it (or the server strictly requires out-of-band handling), the server may return a `URL_ELICITATION_REQUIRED` error during tool execution or prompt retrieval. When a server requires out-of-band URL elicitation but the client has not negotiated support for it (or the server strictly requires out-of-band handling), the server may return a `URL_ELICITATION_REQUIRED` error during tool execution or prompt retrieval.
@ -339,7 +349,7 @@ mcpClient.initialize();
mcpClient.setLoggingLevel(McpSchema.LoggingLevel.INFO); mcpClient.setLoggingLevel(McpSchema.LoggingLevel.INFO);
// Call the tool that sends logging notifications // Call the tool that sends logging notifications
CallToolResult result = mcpClient.callTool(new CallToolRequest("logging-test", Map.of())); CallToolResult result = mcpClient.callTool(CallToolRequest.builder("logging-test").build());
``` ```
Clients can control the minimum logging level they receive through the `mcpClient.setLoggingLevel(level)` request. Messages below the set level will be filtered out. Clients can control the minimum logging level they receive through the `mcpClient.setLoggingLevel(level)` request. Messages below the set level will be filtered out.
@ -371,11 +381,13 @@ Tools are server-side functions that clients can discover and execute. The MCP c
// Call a tool with a CallToolRequest // Call a tool with a CallToolRequest
CallToolResult result = client.callTool( CallToolResult result = client.callTool(
new CallToolRequest("calculator", Map.of( CallToolRequest.builder("calculator")
"operation", "add", .arguments(Map.of(
"a", 1, "operation", "add",
"b", 2 "a", 1,
)) "b", 2
))
.build()
); );
``` ```
@ -389,11 +401,13 @@ Tools are server-side functions that clients can discover and execute. The MCP c
.subscribe(); .subscribe();
// Call a tool asynchronously // Call a tool asynchronously
client.callTool(new CallToolRequest("calculator", Map.of( client.callTool(CallToolRequest.builder("calculator")
"operation", "add", .arguments(Map.of(
"a", 1, "operation", "add",
"b", 2 "a", 1,
))) "b", 2
))
.build())
.subscribe(); .subscribe();
``` ```
@ -420,7 +434,7 @@ Resources represent server-side data sources that clients can access using URI t
// Read a resource // Read a resource
ReadResourceResult resource = client.readResource( ReadResourceResult resource = client.readResource(
new ReadResourceRequest("resource://uri") ReadResourceRequest.builder("resource://uri").build()
); );
``` ```
@ -434,7 +448,7 @@ Resources represent server-side data sources that clients can access using URI t
.subscribe(); .subscribe();
// Read a resource asynchronously // Read a resource asynchronously
client.readResource(new ReadResourceRequest("resource://uri")) client.readResource(ReadResourceRequest.builder("resource://uri").build())
.subscribe(); .subscribe();
``` ```
@ -457,10 +471,10 @@ Register a consumer on the client builder, then subscribe/unsubscribe at any tim
client.initialize(); client.initialize();
// Subscribe to a specific resource URI // Subscribe to a specific resource URI
client.subscribeResource(new McpSchema.SubscribeRequest("custom://resource")); client.subscribeResource(McpSchema.SubscribeRequest.builder("custom://resource").build());
// ... later, stop receiving updates // ... later, stop receiving updates
client.unsubscribeResource(new McpSchema.UnsubscribeRequest("custom://resource")); client.unsubscribeResource(McpSchema.UnsubscribeRequest.builder("custom://resource").build());
``` ```
=== "Async API" === "Async API"
@ -473,11 +487,11 @@ Register a consumer on the client builder, then subscribe/unsubscribe at any tim
.build(); .build();
client.initialize() client.initialize()
.then(client.subscribeResource(new McpSchema.SubscribeRequest("custom://resource"))) .then(client.subscribeResource(McpSchema.SubscribeRequest.builder("custom://resource").build()))
.subscribe(); .subscribe();
// ... later, stop receiving updates // ... later, stop receiving updates
client.unsubscribeResource(new McpSchema.UnsubscribeRequest("custom://resource")) client.unsubscribeResource(McpSchema.UnsubscribeRequest.builder("custom://resource").build())
.subscribe(); .subscribe();
``` ```
@ -493,7 +507,7 @@ The prompt system enables interaction with server-side prompt templates. These t
// Get a prompt with parameters // Get a prompt with parameters
GetPromptResult prompt = client.getPrompt( GetPromptResult prompt = client.getPrompt(
new GetPromptRequest("greeting", Map.of("name", "World")) GetPromptRequest.builder("greeting").arguments(Map.of("name", "World")).build()
); );
``` ```
@ -507,6 +521,6 @@ The prompt system enables interaction with server-side prompt templates. These t
.subscribe(); .subscribe();
// Get a prompt asynchronously // Get a prompt asynchronously
client.getPrompt(new GetPromptRequest("greeting", Map.of("name", "World"))) client.getPrompt(GetPromptRequest.builder("greeting").arguments(Map.of("name", "World")).build())
.subscribe(); .subscribe();
``` ```

View file

@ -123,7 +123,7 @@ Add the BOM to your project:
<dependency> <dependency>
<groupId>io.modelcontextprotocol.sdk</groupId> <groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-bom</artifactId> <artifactId>mcp-bom</artifactId>
<version>1.0.0</version> <version>2.0.0</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -135,7 +135,7 @@ Add the BOM to your project:
```groovy ```groovy
dependencies { dependencies {
implementation platform("io.modelcontextprotocol.sdk:mcp-bom:1.0.0") implementation platform("io.modelcontextprotocol.sdk:mcp-bom:2.0.0")
//... //...
} }
``` ```

View file

@ -111,7 +111,7 @@ Create process-based transport using stdin/stdout:
```java ```java
StdioServerTransportProvider transportProvider = StdioServerTransportProvider transportProvider =
new StdioServerTransportProvider(new ObjectMapper()); new StdioServerTransportProvider(McpJsonDefaults.getMapper());
``` ```
Provides bidirectional JSON-RPC message handling over standard input/output streams with non-blocking message processing, serialization/deserialization, and graceful shutdown support. Provides bidirectional JSON-RPC message handling over standard input/output streams with non-blocking message processing, serialization/deserialization, and graceful shutdown support.
@ -237,7 +237,9 @@ Key features:
@Bean @Bean
public HttpServletSseServerTransportProvider servletSseServerTransportProvider() { public HttpServletSseServerTransportProvider servletSseServerTransportProvider() {
return new HttpServletSseServerTransportProvider(new ObjectMapper(), "/mcp/message"); return HttpServletSseServerTransportProvider.builder()
.messageEndpoint("/mcp/message")
.build();
} }
@Bean @Bean
@ -340,10 +342,8 @@ The recommended approach is to use the builder pattern and `CallToolRequest` as
```java ```java
// Sync tool specification using builder // Sync tool specification using builder
var syncToolSpecification = SyncToolSpecification.builder() var syncToolSpecification = SyncToolSpecification.builder()
.tool(Tool.builder() .tool(Tool.builder("calculator", schema)
.name("calculator")
.description("Basic calculator") .description("Basic calculator")
.inputSchema(schema)
.build()) .build())
.callHandler((exchange, request) -> { .callHandler((exchange, request) -> {
// Access arguments via request.arguments() // Access arguments via request.arguments()
@ -363,10 +363,8 @@ The recommended approach is to use the builder pattern and `CallToolRequest` as
```java ```java
// Async tool specification using builder // Async tool specification using builder
var asyncToolSpecification = AsyncToolSpecification.builder() var asyncToolSpecification = AsyncToolSpecification.builder()
.tool(Tool.builder() .tool(Tool.builder("calculator", schema)
.name("calculator")
.description("Basic calculator") .description("Basic calculator")
.inputSchema(schema)
.build()) .build())
.callHandler((exchange, request) -> { .callHandler((exchange, request) -> {
// Access arguments via request.arguments() // Access arguments via request.arguments()
@ -389,7 +387,7 @@ You can also register tools directly on the server builder using the `toolCall`
```java ```java
var server = McpServer.sync(transportProvider) var server = McpServer.sync(transportProvider)
.toolCall( .toolCall(
Tool.builder().name("echo").description("Echoes input").inputSchema(schema).build(), Tool.builder("echo", schema).description("Echoes input").build(),
(exchange, request) -> CallToolResult.builder() (exchange, request) -> CallToolResult.builder()
.content(List.of(new McpSchema.TextContent(request.arguments().get("text").toString()))) .content(List.of(new McpSchema.TextContent(request.arguments().get("text").toString())))
.build() .build()
@ -397,6 +395,18 @@ var server = McpServer.sync(transportProvider)
.build(); .build();
``` ```
#### Tool Input Validation
By default the server validates incoming tool arguments against the tool's `inputSchema` before invoking the handler. When validation fails, the call returns a `CallToolResult` with `isError` set and a textual error, rather than reaching your handler. Validation uses the configured `JsonSchemaValidator` (or the default from `McpJsonDefaults.getSchemaValidator()`), and can be turned off on the server builder:
```java
var server = McpServer.sync(transportProvider)
.validateToolInputs(false) // default is true
.build();
```
The embedded JSON Schema documents themselves (`Tool.inputSchema`, `Tool.outputSchema`, and elicitation `requestedSchema`) are validated against the JSON Schema 2020-12 meta-schema (SEP-1613). Malformed schemas are rejected at build time (`McpServer.build()`) and when calling `addTool()`, throwing an `IllegalArgumentException` that names the offending field. A schema that declares a different dialect via `$schema` is accepted without meta-schema validation.
### Resource Specification ### Resource Specification
Specification of a resource with its handler function. Specification of a resource with its handler function.
@ -407,15 +417,13 @@ Resources provide context to AI models by exposing data such as: File contents,
```java ```java
// Sync resource specification // Sync resource specification
var syncResourceSpecification = new McpServerFeatures.SyncResourceSpecification( var syncResourceSpecification = new McpServerFeatures.SyncResourceSpecification(
Resource.builder() Resource.builder("custom://resource", "name")
.uri("custom://resource")
.name("name")
.description("description") .description("description")
.mimeType("text/plain") .mimeType("text/plain")
.build(), .build(),
(exchange, request) -> { (exchange, request) -> {
// Resource read implementation // Resource read implementation
return new ReadResourceResult(contents); return ReadResourceResult.builder(contents).build();
} }
); );
``` ```
@ -425,15 +433,13 @@ Resources provide context to AI models by exposing data such as: File contents,
```java ```java
// Async resource specification // Async resource specification
var asyncResourceSpecification = new McpServerFeatures.AsyncResourceSpecification( var asyncResourceSpecification = new McpServerFeatures.AsyncResourceSpecification(
Resource.builder() Resource.builder("custom://resource", "name")
.uri("custom://resource")
.name("name")
.description("description") .description("description")
.mimeType("text/plain") .mimeType("text/plain")
.build(), .build(),
(exchange, request) -> { (exchange, request) -> {
// Resource read implementation // Resource read implementation
return Mono.just(new ReadResourceResult(contents)); return Mono.just(ReadResourceResult.builder(contents).build());
} }
); );
``` ```
@ -481,15 +487,13 @@ Resource templates allow servers to expose parameterized resources using URI tem
```java ```java
// Resource template specification // Resource template specification
var resourceTemplateSpec = new McpServerFeatures.SyncResourceTemplateSpecification( var resourceTemplateSpec = new McpServerFeatures.SyncResourceTemplateSpecification(
ResourceTemplate.builder() ResourceTemplate.builder("file://{path}", "File Resource")
.uriTemplate("file://{path}")
.name("File Resource")
.description("Access files by path") .description("Access files by path")
.mimeType("application/octet-stream") .mimeType("application/octet-stream")
.build(), .build(),
(exchange, request) -> { (exchange, request) -> {
// Read the file at the requested URI // Read the file at the requested URI
return new ReadResourceResult(contents); return ReadResourceResult.builder(contents).build();
} }
); );
``` ```
@ -504,12 +508,18 @@ The Prompt Specification is a structured template for AI model interactions that
```java ```java
// Sync prompt specification // Sync prompt specification
var syncPromptSpecification = new McpServerFeatures.SyncPromptSpecification( var syncPromptSpecification = new McpServerFeatures.SyncPromptSpecification(
new Prompt("greeting", "description", List.of( Prompt.builder("greeting")
new PromptArgument("name", "description", true) .description("description")
)), .arguments(List.of(
PromptArgument.builder("name")
.description("description")
.required(true)
.build()
))
.build(),
(exchange, request) -> { (exchange, request) -> {
// Prompt implementation // Prompt implementation
return new GetPromptResult(description, messages); return GetPromptResult.builder(messages).description(description).build();
} }
); );
``` ```
@ -519,12 +529,18 @@ The Prompt Specification is a structured template for AI model interactions that
```java ```java
// Async prompt specification // Async prompt specification
var asyncPromptSpecification = new McpServerFeatures.AsyncPromptSpecification( var asyncPromptSpecification = new McpServerFeatures.AsyncPromptSpecification(
new Prompt("greeting", "description", List.of( Prompt.builder("greeting")
new PromptArgument("name", "description", true) .description("description")
)), .arguments(List.of(
PromptArgument.builder("name")
.description("description")
.required(true)
.build()
))
.build(),
(exchange, request) -> { (exchange, request) -> {
// Prompt implementation // Prompt implementation
return Mono.just(new GetPromptResult(description, messages)); return Mono.just(GetPromptResult.builder(messages).description(description).build());
} }
); );
``` ```
@ -592,10 +608,8 @@ Once connected to a compatible client, the server can request language model gen
// Define a tool that uses sampling // Define a tool that uses sampling
var calculatorTool = SyncToolSpecification.builder() var calculatorTool = SyncToolSpecification.builder()
.tool(Tool.builder() .tool(Tool.builder("ai-calculator", schema)
.name("ai-calculator")
.description("Performs calculations using AI") .description("Performs calculations using AI")
.inputSchema(schema)
.build()) .build())
.callHandler((exchange, request) -> { .callHandler((exchange, request) -> {
// Check if client supports sampling // Check if client supports sampling
@ -606,9 +620,10 @@ Once connected to a compatible client, the server can request language model gen
} }
// Create a sampling request // Create a sampling request
CreateMessageRequest samplingRequest = CreateMessageRequest.builder() CreateMessageRequest samplingRequest = CreateMessageRequest.builder(
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER, List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
new McpSchema.TextContent("Calculate: " + request.arguments().get("expression"))))) new McpSchema.TextContent("Calculate: " + request.arguments().get("expression")))),
100)
.modelPreferences(McpSchema.ModelPreferences.builder() .modelPreferences(McpSchema.ModelPreferences.builder()
.hints(List.of( .hints(List.of(
McpSchema.ModelHint.of("claude-3-sonnet"), McpSchema.ModelHint.of("claude-3-sonnet"),
@ -618,7 +633,6 @@ Once connected to a compatible client, the server can request language model gen
.speedPriority(0.5) .speedPriority(0.5)
.build()) .build())
.systemPrompt("You are a helpful calculator assistant. Provide only the numerical answer.") .systemPrompt("You are a helpful calculator assistant. Provide only the numerical answer.")
.maxTokens(100)
.build(); .build();
// Request sampling from the client // Request sampling from the client
@ -646,10 +660,8 @@ Once connected to a compatible client, the server can request language model gen
// Define a tool that uses sampling // Define a tool that uses sampling
var calculatorTool = AsyncToolSpecification.builder() var calculatorTool = AsyncToolSpecification.builder()
.tool(Tool.builder() .tool(Tool.builder("ai-calculator", schema)
.name("ai-calculator")
.description("Performs calculations using AI") .description("Performs calculations using AI")
.inputSchema(schema)
.build()) .build())
.callHandler((exchange, request) -> { .callHandler((exchange, request) -> {
// Check if client supports sampling // Check if client supports sampling
@ -660,9 +672,10 @@ Once connected to a compatible client, the server can request language model gen
} }
// Create a sampling request // Create a sampling request
CreateMessageRequest samplingRequest = CreateMessageRequest.builder() CreateMessageRequest samplingRequest = CreateMessageRequest.builder(
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER, List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
new McpSchema.TextContent("Calculate: " + request.arguments().get("expression"))))) new McpSchema.TextContent("Calculate: " + request.arguments().get("expression")))),
100)
.modelPreferences(McpSchema.ModelPreferences.builder() .modelPreferences(McpSchema.ModelPreferences.builder()
.hints(List.of( .hints(List.of(
McpSchema.ModelHint.of("claude-3-sonnet"), McpSchema.ModelHint.of("claude-3-sonnet"),
@ -672,7 +685,6 @@ Once connected to a compatible client, the server can request language model gen
.speedPriority(0.5) .speedPriority(0.5)
.build()) .build())
.systemPrompt("You are a helpful calculator assistant. Provide only the numerical answer.") .systemPrompt("You are a helpful calculator assistant. Provide only the numerical answer.")
.maxTokens(100)
.build(); .build();
// Request sampling from the client // Request sampling from the client
@ -701,10 +713,8 @@ Servers can request user input from connected clients that support elicitation:
```java ```java
var tool = SyncToolSpecification.builder() var tool = SyncToolSpecification.builder()
.tool(Tool.builder() .tool(Tool.builder("confirm-action", schema)
.name("confirm-action")
.description("Confirms an action with the user") .description("Confirms an action with the user")
.inputSchema(schema)
.build()) .build())
.callHandler((exchange, request) -> { .callHandler((exchange, request) -> {
// Check if client supports elicitation // Check if client supports elicitation
@ -741,10 +751,8 @@ To request out-of-band URL elicitation, such as a user authorizing an OAuth flow
```java ```java
var urlTool = SyncToolSpecification.builder() var urlTool = SyncToolSpecification.builder()
.tool(Tool.builder() .tool(Tool.builder("oauth-auth", schema)
.name("oauth-auth")
.description("Authenticates via OAuth") .description("Authenticates via OAuth")
.inputSchema(schema)
.build()) .build())
.callHandler((exchange, request) -> { .callHandler((exchange, request) -> {
// Request URL elicitation from client // Request URL elicitation from client
@ -779,23 +787,17 @@ Log notifications can only be sent from within an existing client session, such
The server can send log messages using the `McpAsyncServerExchange`/`McpSyncServerExchange` object in the tool/resource/prompt handler function: The server can send log messages using the `McpAsyncServerExchange`/`McpSyncServerExchange` object in the tool/resource/prompt handler function:
```java ```java
var tool = new McpServerFeatures.AsyncToolSpecification( var tool = AsyncToolSpecification.builder()
Tool.builder().name("logging-test").description("Test logging notifications").inputSchema(emptyJsonSchema).build(), .tool(Tool.builder("logging-test", emptyJsonSchema).description("Test logging notifications").build())
null, .callHandler((exchange, request) ->
(exchange, request) -> { exchange.loggingNotification( // Use the exchange to send log messages
McpSchema.LoggingMessageNotification.builder(McpSchema.LoggingLevel.DEBUG, "Debug message")
exchange.loggingNotification( // Use the exchange to send log messages .logger("test-logger")
McpSchema.LoggingMessageNotification.builder() .build())
.level(McpSchema.LoggingLevel.DEBUG) .then(Mono.just(CallToolResult.builder()
.logger("test-logger") .content(List.of(new McpSchema.TextContent("Logging test completed")))
.data("Debug message") .build())))
.build()) .build();
.block();
return Mono.just(CallToolResult.builder()
.content(List.of(new McpSchema.TextContent("Logging test completed")))
.build());
});
var mcpServer = McpServer.async(mcpServerTransportProvider) var mcpServer = McpServer.async(mcpServerTransportProvider)
.serverInfo("test-server", "1.0.0") .serverInfo("test-server", "1.0.0")