From 251ce0de6e8b6665ec6faf92df550b080b0a034e Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 13 Nov 2026 00:31:07 +0100 Subject: [PATCH 1/3] Document the modern client and restructure the docs Add a guide for the 2026-07-28 client (docs/client-modern.md) and bring the rest of the documentation in line with an SDK that has a classic and a modern client and server. - Navigation groups the guides into Classic and Modern. - Overview, index and quickstart are rewritten to cover both: a "Classic or Modern?" comparison, a feature table, and a first client call for each. - The classic architecture diagrams are redrawn as SVG, with matching modern ones added. - The modern client and server are no longer described as a preview. - CHANGELOG gains an entry for the modern client. Signed-off-by: Christian Tzolov --- CHANGELOG.md | 8 +- docs/client-modern.md | 568 ++++++++++++++++++ docs/client.md | 5 + .../java-mcp-client-architecture-modern.svg | 78 +++ docs/images/java-mcp-client-architecture.svg | 121 ++++ .../java-mcp-server-architecture-modern.svg | 103 ++++ docs/images/java-mcp-server-architecture.svg | 90 +++ docs/images/mcp-stack-modern.svg | 50 ++ docs/images/mcp-stack.svg | 258 ++------ docs/index.md | 118 ++-- docs/overview.md | 157 +++-- docs/quickstart.md | 216 ++++--- docs/server-modern.md | 47 +- docs/server.md | 2 +- mkdocs.yml | 6 +- 15 files changed, 1446 insertions(+), 381 deletions(-) create mode 100644 docs/client-modern.md create mode 100644 docs/images/java-mcp-client-architecture-modern.svg create mode 100644 docs/images/java-mcp-client-architecture.svg create mode 100644 docs/images/java-mcp-server-architecture-modern.svg create mode 100644 docs/images/java-mcp-server-architecture.svg create mode 100644 docs/images/mcp-stack-modern.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 0778f6275..74eb267ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,17 @@ Versioning follows [Semantic Versioning](https://semver.org/); see ## Unreleased (2.2.0) -- Preview of a stateless server for the **2026-07-28** MCP specification revision, in +- A stateless server for the **2026-07-28** MCP specification revision, in `io.modelcontextprotocol.modern`, with Servlet (Streamable HTTP) and STDIO transports. It supports `server/discover`, multi round-trip requests with sealed `requestState`, `subscriptions/listen`, caching hints, required client capabilities and SEP-2243 `Mcp-Param` header validation. The API may change in minor releases until 3.0. See [MCP Server (2026-07-28)](docs/server-modern.md). +- A client for the **2026-07-28** MCP specification revision, in + `io.modelcontextprotocol.modern.client`, with Streamable HTTP (JDK `HttpClient`) and STDIO transports. It + needs no `initialize` handshake and supports multi round-trip requests with form and URL elicitation, + progress, `subscriptions/listen`, `server/discover` and SEP-2243 `Mcp-Param` headers. Sampling, roots and + logging are not supported. The API may change in minor releases until 3.0. See + [MCP Client (2026-07-28)](docs/client-modern.md). - Add `McpTransportContext.putAll(Map)`, returning a context that combines an existing context with additional entries, which take precedence. - `McpTransportContext` no longer compares contexts by their contents in diff --git a/docs/client-modern.md b/docs/client-modern.md new file mode 100644 index 000000000..077a424cb --- /dev/null +++ b/docs/client-modern.md @@ -0,0 +1,568 @@ +--- +title: MCP Client (2026-07-28) +description: Call stateless MCP servers of the 2026-07-28 specification revision with the modern client API +--- + +# MCP Client (2026-07-28) + +!!! note + The modern client is available since 2.2.0. Its API, in the `io.modelcontextprotocol.modern` packages, + may change in minor releases until 3.0, and [some parts of the revision](#not-yet-supported) are not + implemented. For servers of earlier revisions, use the [classic client](client.md). + +## Overview + +The modern client talks to servers of the stateless +[2026-07-28 MCP specification revision](https://modelcontextprotocol.io/specification/2026-07-28). +It lives in `io.modelcontextprotocol.modern.client`, next to the classic client, and shares no types with it: +it has its own `McpClient`, its own `McpSchema`, and its own transports. Both ship in `mcp-core`, so no extra +dependency is needed. + +The 2026-07-28 revision removes sessions and the `initialize` handshake. Every request carries the protocol +version and the client's capabilities in its `_meta`. The modern client is built around that: + +- **Nothing to initialize.** Building a client does no I/O. The first request goes out when the first call + starts. +- **One client per user.** A client stands for one identity. It is cheap to create, so an application that + serves several users creates one client per user. See [Identity and Authorization](#identity-and-authorization). +- **Shared transports.** A transport keeps no state about calls. Many clients can share one transport, and so + one connection pool. A client does not own its transport; whoever created the transport closes it. +- **Immutable request specs.** Each operation starts from a spec, for example + `client.tool("echo").arguments(args).call()`. Every setter returns a new spec, so a configured spec can be + kept and reused. +- **The protocol details are handled for you.** The client adds the `_meta` block, negotiates the protocol + version, answers the server's input requests, and sets the `Mcp-*` headers. + +### Classic or modern? + +| | [Classic client](client.md) | Modern client | +|---|---|---| +| Package | `io.modelcontextprotocol.client` | `io.modelcontextprotocol.modern.client` | +| Spec revision | 2025-11-25 and earlier | 2026-07-28 | +| Startup | `initialize()` handshake | None | +| Sessions | Yes | No | +| Elicitation | Server-to-client request | Input request answered in a retry of the call | +| Sampling, roots | Supported | Not supported | +| Logging | Supported | Not supported (deprecated by the revision) | + +The modern client only speaks the 2026-07-28 protocol version. It cannot call servers that expect an +`initialize` handshake. + +## Quick Start + +A synchronous client that lists the server's tools and calls one over HTTP: + +```java +import java.util.Map; + +import io.modelcontextprotocol.modern.McpSchema.CallToolResult; +import io.modelcontextprotocol.modern.McpSchema.Implementation; +import io.modelcontextprotocol.modern.McpSchema.Tool; +import io.modelcontextprotocol.modern.client.McpClient; +import io.modelcontextprotocol.modern.client.McpSyncClient; +import io.modelcontextprotocol.modern.client.transport.HttpClientMcpTransport; + +HttpClientMcpTransport transport = HttpClientMcpTransport.builder("http://localhost:8080/mcp").build(); + +McpSyncClient client = McpClient.sync(transport) + .clientInfo(Implementation.builder("my-client", "1.0.0").build()) + .build(); + +for (Tool tool : client.listTools().list().tools()) { + System.out.println(tool.name()); +} + +CallToolResult result = client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call(); +System.out.println(result.content()); +``` + +Clients are immutable and thread-safe. There is nothing to start and nothing to close on the client. + +Builder options: + +| Method | Purpose | +|---|---| +| `clientInfo(Implementation)` | The client's name and version, sent in `_meta` of every request. Optional, but recommended. | +| `roundTimeout(Duration)` | Timeout of one network exchange. Defaults to 20 seconds. See [Timeouts](#timeouts). | +| `callTimeout(Duration)` | Bound on a whole call. None by default. See [Timeouts](#timeouts). | +| `formElicitation(...)` / `urlElicitation(...)` | Handlers that answer the server's input requests. See [Elicitation](#elicitation). | +| `progress(...)` | Listener for progress notifications. See [Progress](#progress). | +| `transportContextProvider(Supplier)` | Sync only. The identity of each call. See [Identity and Authorization](#identity-and-authorization). | +| `maxInputRounds(int)` | How many times a call answers input requests before it fails. Defaults to 10. | +| `maxListPages(int)` | How many pages a listing of all pages reads before it fails. Defaults to 100. | +| `jsonMapper(McpJsonMapper)` | JSON mapper. Defaults to `McpJsonDefaults.getMapper()`. | + +## Transports + +### Streamable HTTP + +`HttpClientMcpTransport` uses the JDK `HttpClient`. Every request is a POST to the MCP endpoint, answered with +one JSON response or with an event stream scoped to that request. Waiting for an answer holds no thread. + +```java +HttpClientMcpTransport transport = HttpClientMcpTransport.builder("https://example.com/mcp") + .connectTimeout(Duration.ofSeconds(5)) + .customizeClient(http -> http.sslContext(sslContext)) + .build(); +``` + +| Method | Purpose | +|---|---| +| `connectTimeout(Duration)` | How long establishing a connection may take. Defaults to 10 seconds. | +| `customizeClient(Consumer)` | Customize the `HttpClient`, for example its executor or SSL context. | +| `clientBuilder(HttpClient.Builder)` | Replace the `HttpClient` builder. HTTP/1.1 is used by default. | +| `requestCustomizer(McpHttpRequestCustomizer)` | Add headers to each request. Customizers run in the order they were added. | +| `authorizationErrorHandler(McpHttpAuthorizationErrorHandler)` | Decide whether a request answered with `401` or `403` is sent again. | +| `maxResponseSize(int)` | Maximum bytes of one response body or one streamed event. Defaults to 16 MiB. | +| `jsonMapper(McpJsonMapper)` | JSON mapper. Defaults to `McpJsonDefaults.getMapper()`. | + +!!! note + Set the connect timeout with `connectTimeout(...)` on the transport builder. A connect timeout set on the + `HttpClient` builder through `customizeClient` or `clientBuilder` is replaced by the transport's value. + +### STDIO + +`StdioMcpClientTransport` runs the server as a process and exchanges newline-delimited JSON-RPC over its +standard streams. + +```java +import io.modelcontextprotocol.client.transport.ServerParameters; +import io.modelcontextprotocol.modern.client.transport.StdioMcpClientTransport; + +ServerParameters params = ServerParameters.builder("java") + .args("-jar", "my-mcp-server.jar") + .build(); + +try (StdioMcpClientTransport transport = StdioMcpClientTransport.builder(params).start()) { + McpSyncClient client = McpClient.sync(transport).build(); + // use the client +} +``` + +`start()` spawns the process. Clients never start or close the transport they are given, so one process can +serve several clients; close the transport once they are done. When the server's output ends or the transport +is closed, every open call fails with `McpResponseLostException`. + +`StdioMcpClientTransport.builder(InputStream, OutputStream)` runs over streams that are already connected, +such as a socket's. + +### Custom Transports + +Other transports implement `McpClientTransport`. Its single method, `exchange(McpTransportRequest)`, sends one +request and emits the server's notifications followed by exactly one response. + +An HTTP transport built on another HTTP client can reuse the SDK's building blocks: +`McpHttpRequestHeaders.forRequest(request)` returns the headers the revision requires, +`request.withRoundTimeout(...)` applies the round timeout to one attempt, and `McpHttpRequest.builder(...)` +and `McpHttpResponseInfo.of(...)` create the values the request customizers and the authorization error +handler receive. + +## Sync and Async + +`McpClient.sync(transport)` builds an `McpSyncClient`, and `McpClient.async(transport)` builds an +`McpAsyncClient`. Both offer the same operations. + +- **Sync** calls run on the calling thread and return the result. Elicitation handlers run on that thread too. +- **Async** calls return a `Mono` (or a `Flux` for listen streams). Nothing is sent before subscription. + Handlers return a `Mono` and must not block. + +```java +McpAsyncClient client = McpClient.async(transport).build(); + +client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call() + .subscribe(result -> System.out.println(result.content())); +``` + +The examples on this page use the sync client unless they say otherwise. + +## Calling the Server + +| Operation | Spec | Terminal | +|---|---|---| +| Call a tool | `client.tool(name)` or `client.tool(Tool)` | `call()` | +| Read a resource | `client.resource(uri)` | `read()` | +| Get a prompt | `client.prompt(name)` | `get()` | +| Complete an argument | `client.completion(ref, argument, value)` | `complete()` | +| List one page | `client.tools()`, `prompts()`, `resources()`, `resourceTemplates()` | `list()` | +| List all tools | `client.listTools()` | `list()` | +| Discover the server | | `client.discover()` | +| Listen for changes | `client.listen()` | `open(listener)` (sync), `events()` (async) | + +```java +ReadResourceResult resource = client.resource("file:///config.json").read(); + +GetPromptResult prompt = client.prompt("greeting") + .arguments(Map.of("name", "Ada")) + .get(); + +CompleteResult completion = client.completion(new PromptReference("greeting"), "name", "A").complete(); +List suggestions = completion.completion().values(); +``` + +A tool failure the model should see is not an exception. It is a `CallToolResult` whose `isError()` is `true`. + +### Per-Call Settings + +A spec starts from the client's settings. Each setter replaces one of them for that call only: + +```java +CallToolResult result = client.tool("generate_report") + .arguments(Map.of("year", 2026)) + .roundTimeout(Duration.ofMinutes(5)) + .progress(progress -> System.out.println(progress.message())) + .meta(Map.of("com.example/traceId", traceId)) + .call(); +``` + +`meta(...)` adds entries to the request's `_meta`. It rejects `progressToken` and keys starting with +`io.modelcontextprotocol/`, which the client sets itself. + +### Listing and Pagination + +The list specs return one page. Pass the previous page's `nextCursor()` to read the next one: + +```java +String cursor = null; +do { + ListResourcesResult page = client.resources().cursor(cursor).list(); + page.resources().forEach(resource -> System.out.println(resource.uri())); + cursor = page.nextCursor(); +} +while (cursor != null); +``` + +For tools, `client.listTools().list()` reads all pages and returns them as one result. + +### The Kept Tool Listing + +The client keeps the result of `listTools().list()` for as long as the server's `ttlMs` allows, and returns +it without asking the server again. + +- `client.listTools().skipCache().list()` always asks the server and keeps the new listing. +- `client.tools().list()`, which reads one page, always asks the server. +- A listing with a `ttlMs` of `0` is not kept. +- A [listen stream](#change-notifications) that tracks tool changes drops the kept listing when the tools + change. +- A listing the server marks as `private` is kept too, because a client stands for one user. + +### Server Discovery + +`client.discover()` returns the server's supported versions, capabilities and instructions. The result is kept +for its `ttlMs`. + +```java +DiscoverResult server = client.discover(); +ServerCapabilities capabilities = server.capabilities(); +Object serverInfo = server.meta() != null ? server.meta().get(McpSchema.MetaKeys.SERVER_INFO) : null; +``` + +The server's name and version are not a field of the result. Servers send them in `_meta`, under +`io.modelcontextprotocol/serverInfo`, as an untyped map. A server may leave `_meta` out, so check for `null`. + +## Timeouts + +| Timeout | Bounds | Default | +|---|---|---| +| Round timeout | One network exchange: from sending a request until its response arrives. | 20 seconds | +| Call timeout | The whole call: every exchange, every handler and every wait. | None | + +- Progress notifications do not extend the round timeout. A tool that runs longer than the round timeout + fails unless you raise it, on the builder or on the call's spec. +- The call timeout includes the time a user takes to answer an elicitation, so a slow user fails the call. + Use it where a hard deadline matters more, for example in unattended jobs. +- Listen streams have no timeout. + +A timeout fails the call with `McpTimeoutException`, whose `kind()` is `ROUND` or `CALL`. The request may +have reached the server, so its outcome is unknown. The client never retries it. + +## Elicitation + +The 2026-07-28 revision has no server-to-client requests. A server that needs input from the user answers a +`tools/call`, `resources/read` or `prompts/get` with an `input_required` result. The client asks its handlers +and sends the request again with the answers, until the server completes it. See +[Multi Round-Trip Requests](server-modern.md#multi-round-trip-requests) for the server side. + +```mermaid +sequenceDiagram + participant App as Application + participant Client as MCP client + participant Handler as Elicitation handler + participant Server as MCP server + + App->>Client: tool("deploy").arguments(args).call() + Client->>Server: tools/call + Server-->>Client: input_required (input requests, requestState) + Client->>Handler: elicit(request) + Handler-->>Client: ElicitResult + Client->>Server: tools/call with inputResponses and requestState + Server-->>Client: CallToolResult + Client-->>App: CallToolResult +``` + +The application makes one call and gets one result. The rounds in between are the client's job. + +```java +McpSyncClient client = McpClient.sync(transport) + .formElicitation(request -> { + String name = askUser(request.message(), request.requestedSchema()); + return name != null ? ElicitResult.accept(Map.of("name", name)) : ElicitResult.decline(); + }) + .urlElicitation(request -> { + boolean opened = askConsentAndOpenBrowser(request.message(), request.url()); + return opened ? ElicitResult.accept(Map.of()) : ElicitResult.decline(); + }) + .build(); +``` + +- **Form mode.** The handler receives an `ElicitFormRequest` with a message and a JSON schema, and returns + the user's answer. +- **URL mode.** The handler receives an `ElicitUrlRequest`. Ask the user for consent, open the URL, and + return right away. The server learns from the browser when the user is done. The request that follows an + accepted URL elicitation has no round timeout, because the server may hold it open until then. +- **Per call.** `formElicitation(...)` and `urlElicitation(...)` on a tool, resource or prompt spec replace + the client's handler for that call. +- **Capabilities follow the handlers.** Each request declares the `elicitation` capability for exactly the + modes that have a handler. A client without handlers declares none. +- **Threads.** A sync handler runs on the thread that made the call. A timeout cannot interrupt it; the call + timeout is checked when the handler returns. An async handler returns a `Mono`, which is cancelled when the + call ends without its answer; close any pending dialog then. + +A call answers input requests at most `maxInputRounds` times (10 by default) and then fails with +`McpInputRoundsExceededException`. + +### Running the Rounds Yourself + +`callOnce()`, `readOnce()` and `getOnce()` send the request once and return the server's answer as it is: +the final result or an `InputRequiredResult`. Use them to pause a call, for example until a user answers in a +later HTTP request, and continue it with `inputResponses(...)` and `requestState(...)`: + +```java +McpSyncClient.ToolSpec deploy = client.tool("deploy").arguments(Map.of("env", "prod")); + +CallToolOutcome outcome = deploy.callOnce(); +if (outcome instanceof InputRequiredResult inputRequired) { + Map responses = new LinkedHashMap<>(); + inputRequired.inputRequests().forEach((key, request) -> responses.put(key, answer(request))); + + CallToolResult result = deploy + .inputResponses(responses) + .requestState(inputRequired.requestState()) + .call(); +} +``` + +Key the answers as the server keyed its requests, and send `requestState` back as it was received. + +## Progress + +A progress listener receives the `notifications/progress` of a call. Set one for every call on the builder, +or for one call on its spec: + +```java +McpSyncClient client = McpClient.sync(transport) + .progress(progress -> System.out.println(progress.progress() + " / " + progress.total())) + .build(); +``` + +- The client asks the server for progress only while a listener is in effect. +- A listener receives the notifications of a call one at a time and in order. +- A sync listener runs on a thread that may block, not on the calling thread. The call returns after the + listener has seen every notification sent before the result. +- An async listener is a `Function>`. The next notification and the result wait for + the `Mono` it returns. + +## Change Notifications + +`client.listen()` opens a `subscriptions/listen` stream. Choose what to listen for on the spec: + +| Method | Event | +|---|---| +| `toolsChanged()` | `SubscriptionEvent.ToolsListChanged` | +| `promptsChanged()` | `SubscriptionEvent.PromptsListChanged` | +| `resourcesChanged()` | `SubscriptionEvent.ResourcesListChanged` | +| `resourceUpdates(uri...)` | `SubscriptionEvent.ResourceUpdated`, with the resource's `uri()`, which may be a sub-resource of a subscribed URI | + +Every stream starts with `SubscriptionEvent.Subscribed`. Its `missing()` lists what the server will not +report; poll for those instead. Changes made before `Subscribed` may have been missed, so list again what you +keep. + +=== "Sync" + + ```java + McpSyncClient.Listening listening = client.listen() + .toolsChanged() + .resourceUpdates("file:///config.json") + .open(event -> { + if (event instanceof SubscriptionEvent.ToolsListChanged) { + refreshTools(client.listTools().list()); + } + else if (event instanceof SubscriptionEvent.ResourceUpdated updated) { + reload(updated.uri()); + } + }); + + // later + listening.close(); + ``` + + `open(...)` returns without waiting for the server. The listener runs on a thread that may block, so it + may call the client. `McpSyncSubscriptionListener` also has `onError` and `onComplete`, which tell how the + stream ended. + +=== "Async" + + ```java + Disposable listening = client.listen() + .toolsChanged() + .events() + .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofSeconds(1))) + .subscribe(event -> handle(event)); + ``` + + Events are emitted on transport threads, so code that reacts to them must not block. `retryWhen` + subscribes again after every failure, including an authorization failure, so filter the errors you want + to retry. Add `repeat()` to subscribe again when the server ends the stream. + +A stream ends when the server or the network ends it: + +- it completes when the server ends it with a result, for example on shutdown; +- it fails with `McpSubscriptionLostException` when it breaks, so changes may be missed until you listen again. + +To keep listening, open the stream again. Each new stream starts with `Subscribed` again. + +A stream that tracks tool changes also keeps the client's [tool listing](#the-kept-tool-listing) current: the +client drops the kept listing on `Subscribed` and on `ToolsListChanged`. + +## Identity and Authorization + +A client stands for one user. Identity is not an argument of the client's methods. It travels as an +`McpTransportContext` from the call to the transport's hooks, which add it to the HTTP request. + +```mermaid +flowchart LR + A["Client for Alice"] -- "context: Alice" --> T + B["Client for Bob"] -- "context: Bob" --> T + T["Shared transport
one connection pool"] --> H["Request customizers
add the caller's token"] + H --> S["MCP server"] +``` + +**1. Supply the context of a call.** + +=== "Sync" + + ```java + McpSyncClient client = McpClient.sync(transport) + .transportContextProvider(() -> McpTransportContext.create(Map.of("user", user))) + .build(); + ``` + + The provider is called on the calling thread when a call starts or a listen stream opens. + +=== "Async" + + ```java + client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call() + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, McpTransportContext.create(Map.of("user", user)))); + ``` + +**2. Use it in the transport's hooks.** + +```java +HttpClientMcpTransport transport = HttpClientMcpTransport.builder("https://example.com/mcp") + .requestCustomizer(McpHttpRequestCustomizer.fromSync((request, headers, context) -> + headers.set("Authorization", "Bearer " + tokens.accessToken(context.get("user"))))) + .authorizationErrorHandler(McpHttpAuthorizationErrorHandler.fromSync((request, response, context) -> { + tokens.refresh(context.get("user")); + return true; // send the request again + })) + .build(); +``` + +- **Request customizers** run before every attempt, including the retry after an authorization error, so a + refreshed token is picked up. +- **The authorization error handler** is asked when the server answers `401` or `403`. It returns `true` to + send the request again, at most `maxRetries()` times (once by default). Without a handler, or when it + returns `false`, the call fails with `McpHttpStatusException`. +- **Hooks may run on any thread**, and the attempts of one call may run on different threads. Read the + identity from the `context` argument, not from thread-locals. +- `fromSync(...)` hooks run on a thread that may block, for example to fetch a token. An exception thrown by + a hook reaches the caller as it is. + +!!! warning "Share transports, not clients" + The client keeps the tool listing and the `server/discover` result per client, not per user. If one + client serves several users, for example with a `transportContextProvider` that reads the current user + from a thread-local, one user can receive a listing that was fetched for another. + + Create one client per user over a shared transport. Share a client between users only when the server + shows every user the same tools, or use `listTools().skipCache()`. + +## Custom Headers (SEP-2243) + +A tool can ask clients to mirror selected arguments into HTTP headers by marking them with `x-mcp-header` in +its input schema (see [Custom Headers](server-modern.md#custom-headers-sep-2243) on the server side). Over +HTTP the client sends those arguments as `Mcp-Param-{Name}` headers. To do so it needs the tool's definition: + +- `client.tool(name)` uses the definition from the client's earlier listings. If the client has not seen the + tool yet, it lists all tools first. +- `client.tool(Tool)` uses the definition you pass, so nothing is looked up. +- When the server rejects the headers with `-32020`, the client lists the tools again and sends the call once + more. +- A tool the server does not list, or whose annotations are invalid, is not called. The call fails with + `IllegalArgumentException`, and such tools are left out of `listTools()`. + +Over STDIO there are no headers, and none of this applies. + +## Error Handling + +A call that reached the server, or may have, fails with one of two types: `McpException` for an error the +server answered, and `McpClientException` for a failure in the client or on the way. + +| Exception | Meaning | +|---|---| +| `McpException` | The server answered with a JSON-RPC error. `error()` has its code, message and data. | +| `McpMissingClientCapabilityException` | An `McpException` for `-32021`: the request needs a client capability the call did not declare, for example an elicitation handler. | +| `McpTimeoutException` | A round or call timeout expired. See [Timeouts](#timeouts). | +| `McpResponseLostException` | The exchange broke after the request may have reached the server. | +| `McpHttpStatusException` | The server answered with an HTTP error status and no JSON-RPC error, or with a `401` or `403` that was not sent again. `statusCode()`, `headers()` and `body()` have the answer. | +| `McpInputHandlerMissingException` | The server asked for input no handler of the call covers. | +| `McpInputRoundsExceededException` | The server kept asking for input after `maxInputRounds`. | +| `McpInvalidResultException` | The server's result cannot be accepted, or a listing did not end within `maxListPages`. | +| `McpSubscriptionLostException` | A listen stream ended without a result from the server. | + +All but the first two extend `McpClientException`: the call failed in the client or on the way to the server. + +After `McpTimeoutException` or `McpResponseLostException`, whether the server acted on the request is unknown. +The client never retries such a request; retry it yourself only if the operation is safe to repeat. + +Failures outside those two types: + +- **Connection failures.** A connection that cannot be established, for example a `ConnectException` or an + `HttpConnectTimeoutException`, is not an `McpClientException`. The request never reached the server, so it + is safe to send again. The async client signals the exception as it is. The sync client throws a + `RuntimeException` with the original exception as its cause. +- **Exceptions from your hooks.** An exception thrown by a request customizer or an authorization error + handler reaches the caller as it is. +- `IllegalArgumentException`, over HTTP, for a tool the server does not list or whose `x-mcp-header` + annotations are invalid. +- `IllegalStateException` when an elicitation handler returns no answer. +- `CancellationException` when the thread of a sync call is interrupted. + +## Not Yet Supported + +- Sampling and roots input requests. The client declares neither capability. +- Logging, which the 2026-07-28 revision deprecates. +- Extensions, including Tasks. +- Servers of earlier revisions. Use the [classic client](client.md) for those. +- Obtaining OAuth tokens. Add tokens with a request customizer, as shown in + [Identity and Authorization](#identity-and-authorization). +- Spring WebClient transport. + +For a larger working example, see the +[modern conformance client](https://github.com/modelcontextprotocol/java-sdk/tree/main/conformance-tests/client-modern), +which passes the non-auth 2026-07-28 client conformance scenarios. diff --git a/docs/client.md b/docs/client.md index c2ec9342d..be1219e25 100644 --- a/docs/client.md +++ b/docs/client.md @@ -22,6 +22,11 @@ The MCP Client is a key component in the Model Context Protocol (MCP) architectu 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. +!!! note "2026-07-28 specification" + This page covers the classic client, which implements the 2025-11-25 specification and earlier revisions. + A client for the stateless 2026-07-28 revision is also available; see + [MCP Client (2026-07-28)](client-modern.md). + The client provides both synchronous and asynchronous APIs for flexibility in different application contexts. === "Sync API" diff --git a/docs/images/java-mcp-client-architecture-modern.svg b/docs/images/java-mcp-client-architecture-modern.svg new file mode 100644 index 000000000..d4ec0b70c --- /dev/null +++ b/docs/images/java-mcp-client-architecture-modern.svg @@ -0,0 +1,78 @@ + + + Modern Java MCP client architecture + A Java application talks to AI models and holds one McpClient per user. The clients of a + remote server share one HTTP transport and reach the server over Streamable HTTP. A client of a local + server uses a STDIO transport to reach a server process. + + + + + + + + + + + + + + + AI Models + + + + + Your Java application + + + Java Gen AI Application + + + + + + + McpClient + for Alice + + + McpClient + for Bob + + + McpClient + local tools + + + + each call carries its user's context + + + + HTTP transport + shared, one connection pool + + + STDIO transport + starts the server process + + + + MCP Protocol (Streamable HTTP) + + MCP Protocol (STDIO) + + + + MCP Server + remote, stateless + + + MCP Server + local process + diff --git a/docs/images/java-mcp-client-architecture.svg b/docs/images/java-mcp-client-architecture.svg new file mode 100644 index 000000000..b005db432 --- /dev/null +++ b/docs/images/java-mcp-client-architecture.svg @@ -0,0 +1,121 @@ + + + Classic Java MCP client architecture + A Java application on your computer talks to AI models and holds one Java MCP client per + server, each with its own session. Two clients reach remote MCP servers over Streamable HTTP or SSE. Three + clients reach local MCP servers over STDIO: one that searches the internet, one for local folders and one + for a SQLite database. + + + + + + + + + + + + + + + AI Models + + + + + Your Computer + + + Java Gen AI + Application + + + + + + + + + + + + + + Java MCP Client + one session + Java MCP Client + one session + Java MCP Client + one session + Java MCP Client + one session + Java MCP Client + one session + + + + + + MCP Protocol + (Streamable HTTP, SSE) + MCP Protocol + (Streamable HTTP, SSE) + + + + MCP Server + (remote 1) + + + MCP Server + (remote N) + + + + + MCP Protocol + (STDIO) + MCP Protocol + (STDIO) + MCP Protocol + (STDIO) + + + + + + + + + MCP Server + (Brave Search) + MCP Server + (Filesystem) + MCP Server + (SQLite) + + + + + + Search + Internet + + + + + Local Folders + + + + + SQLite database + diff --git a/docs/images/java-mcp-server-architecture-modern.svg b/docs/images/java-mcp-server-architecture-modern.svg new file mode 100644 index 000000000..5286cb865 --- /dev/null +++ b/docs/images/java-mcp-server-architecture-modern.svg @@ -0,0 +1,103 @@ + + + Modern Java MCP server architecture + Over Streamable HTTP, MCP clients in any language reach several identical server instances + through a load balancer that needs no sticky routing; each instance runs the Java MCP server with your + repositories and uses shared databases and backend applications. Over STDIO, a client process starts one + server process and talks to it through its standard streams. + + + + + MCP Server (Streamable HTTP) + + + + Java MCP Client + + Python MCP Client + + JavaScript MCP Client + + + MCP Protocol (Streamable HTTP) + + + + Load balancer + no sticky routing + + + + + + + Instance 1 + + Java MCP Server + Servlet transport + + Your repositories + + + + Instance 2 + + Java MCP Server + Servlet transport + + Your repositories + + + + Instance N + + Java MCP Server + Servlet transport + + Your repositories + + + + + + + + DBs + + + Your backend applications + + + + + + MCP Server (STDIO) + + + Client process + + LLM Application + + + Java MCP Client + STDIO transport + + + MCP Protocol (STDIO) + + + Server process + + Java MCP Server + STDIO transport + + Your repositories + + + + Your backend applications + diff --git a/docs/images/java-mcp-server-architecture.svg b/docs/images/java-mcp-server-architecture.svg new file mode 100644 index 000000000..eae806c2d --- /dev/null +++ b/docs/images/java-mcp-server-architecture.svg @@ -0,0 +1,90 @@ + + + Classic Java MCP server architecture + Over Streamable HTTP or SSE, MCP clients in any language connect to one server process. The + Java MCP server runs in a web container, keeps one session per client, and serves the tools, resources and + prompts you registered, backed by databases and backend applications. Over STDIO, a client process starts + one server process, which keeps a single session. + + + + + MCP Server (Streamable HTTP) + + + + Java MCP Client + + Python MCP Client + + JavaScript MCP Client + + + MCP Protocol (Streamable HTTP, SSE) + + + + Server process + + + Web container + + + Java MCP Server + Servlet transport, or Spring AI's WebMVC / WebFlux + + + + Session 1 + + Session 2 + + Session N + + + Your tools, resources, prompts + + + + + + + DBs + + + Your backend applications + + + + + + MCP Server (STDIO) + + + Client process + + LLM Application + + + Java MCP Client + STDIO transport + + + MCP Protocol (STDIO) + + + Server process + + Java MCP Server + STDIO transport + + Session + + Your tools, resources, prompts + + + + Your backend applications + diff --git a/docs/images/mcp-stack-modern.svg b/docs/images/mcp-stack-modern.svg new file mode 100644 index 000000000..6b48dacb1 --- /dev/null +++ b/docs/images/mcp-stack-modern.svg @@ -0,0 +1,50 @@ + + + MCP stack for the 2026-07-28 revision + On the client side, your application calls McpClient, which sends each request through a + client transport. On the server side, a server transport hands each request to McpServer, which calls your + repositories. Both sides communicate over Streamable HTTP or STDIO. There is no session layer. + + + + + + + + + + Your application + calls tools, resources, prompts + + + McpClient + sync or async, one per user + + + Client transport + JDK HttpClient or STDIO + + + + Your repositories + tools, resources, prompts + + + McpServer + features dispatch by method + + + Server transport + Servlet or STDIO + + + request + _meta + + + result or stream + + + + Streamable HTTP | STDIO + diff --git a/docs/images/mcp-stack.svg b/docs/images/mcp-stack.svg index 3847eaa8d..51c8fcf27 100644 --- a/docs/images/mcp-stack.svg +++ b/docs/images/mcp-stack.svg @@ -1,197 +1,63 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + MCP stack of the classic client and server + On the client side, your application calls McpClient, which works through an McpSession on + top of a client transport. On the server side, a server transport feeds an McpSession, on top of which + McpServer serves the tools, resources and prompts you registered. The two sessions are set up with an + initialize handshake and then carry requests in both directions, over STDIO, Streamable HTTP or SSE. + + + + + + + + + + + + + Your application + calls tools, resources, prompts + + + McpClient + sync or async, one per server + + + McpSession + communication patterns and state + + + Client transport + JDK HttpClient or STDIO + + + + Your tools, resources, prompts + registered on the server + + + McpServer + sync or async + + + McpSession + one per connected client + + + Server transport + Servlet or STDIO + + + initialize once + + + requests both ways + + + + STDIO | Streamable HTTP | SSE diff --git a/docs/index.md b/docs/index.md index 3570e2eca..13cc0355c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,51 +1,65 @@ --- -title: Index +title: Home 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. +The Java SDK for the [Model Context Protocol](https://modelcontextprotocol.io/docs/concepts/architecture) (MCP), the open protocol that connects AI applications to tools and data. +Use it to build MCP clients and MCP servers in Java 17 or later, with a synchronous or an asynchronous API. + +The SDK implements two generations of the protocol, side by side in the same module: + +- **Classic**, for the [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25) specification and earlier revisions, where client and server keep a session. +- **Modern**, for the stateless [2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28) specification, where every request stands alone. + +See [Classic or Modern?](overview.md#classic-or-modern) to choose between them. ## 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 -- Preview of a stateless server for the [2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28) specification revision, with `server/discover`, multi round-trip requests and `subscriptions/listen` (see [MCP Server (2026-07-28)](server-modern.md)) -- 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 (opt-in, see [HTTP Transport Security](server.md#http-transport-security)) - -!!! 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. +What each implementation supports: + +| Feature | Classic | Modern | +|---|---|---| +| [Tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools): discovery, calls, structured output | Yes | Yes | +| [Resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources) and URI templates | Yes | Yes | +| [Prompts](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts) | Yes | Yes | +| [Completions](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion) for argument values | Yes | Yes | +| [Elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation): asking the user for input | Yes, as a request from the server | Yes, as a multi round-trip request (form and URL) | +| [Sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) | Yes | Server only | +| [Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) | Yes | Server only | +| [Progress](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/progress) notifications | Yes | Yes | +| [Logging](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging) | Yes | No (deprecated by the revision) | +| Change notifications | Yes, over the session | Yes, with `subscriptions/listen` | +| Finding out what a server supports | `initialize` handshake | `server/discover` | +| Caching hints on results | No | Yes | +| Custom HTTP headers for tool arguments (SEP-2243) | No | Yes | + +Both implementations share the following: + +- **Sync and async APIs.** Choose blocking calls or Project Reactor types. +- **Transports without a web framework.** STDIO, and Streamable HTTP on the JDK `HttpClient` (client) and the Servlet API (server). The classic implementation also has the legacy [SSE](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) transport. +- **Pluggable JSON serialization.** Jackson 3.x by default, or Jackson 2.x. +- **Security hooks.** Pluggable authorization hooks, and opt-in DNS rebinding protection with Host/Origin header validation (see [HTTP Transport Security](server.md#http-transport-security)). + +!!! tip "Spring applications" + Spring WebFlux and WebMVC transports for the classic client and server are part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ and are not 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 the [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). +## Getting Started + +Add the `mcp` module, which bundles the core SDK with Jackson 3.x JSON serialization: + +```xml + + io.modelcontextprotocol.sdk + mcp + +``` + +The [Quickstart](quickstart.md) covers versions, the BOM, Gradle, and the other modules. + ## Next Steps
@@ -58,28 +72,44 @@ enables standardized integration between AI models and tools. [:octicons-arrow-right-24: Quickstart](quickstart.md) -- :material-monitor:{ .lg .middle } **MCP Client** +- :material-sitemap:{ .lg .middle } **Overview** --- - Learn how to create and configure MCP clients. + Understand the architecture, and choose between classic and modern. + + [:octicons-arrow-right-24: Overview](overview.md) + +- :material-monitor:{ .lg .middle } **MCP Client (Classic)** + + --- + + Create and configure clients for servers of the 2025-11-25 revision and earlier. [:octicons-arrow-right-24: Client](client.md) -- :material-server:{ .lg .middle } **MCP Server** +- :material-server:{ .lg .middle } **MCP Server (Classic)** --- - Learn how to implement and configure MCP servers. + Implement and configure servers for the 2025-11-25 revision and earlier. [:octicons-arrow-right-24: Server](server.md) -- :fontawesome-brands-github:{ .lg .middle } **GitHub** +- :material-monitor:{ .lg .middle } **MCP Client (Modern)** + + --- + + Call stateless servers of the 2026-07-28 revision. + + [:octicons-arrow-right-24: Client](client-modern.md) + +- :material-server:{ .lg .middle } **MCP Server (Modern)** --- - View the source code and contribute. + Build stateless servers for the 2026-07-28 revision. - [:octicons-arrow-right-24: Repository](https://github.com/modelcontextprotocol/java-sdk) + [:octicons-arrow-right-24: Server](server-modern.md)
diff --git a/docs/overview.md b/docs/overview.md index b8c228fc0..41eba215d 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -5,59 +5,116 @@ description: Introduction to the Model Context Protocol (MCP) Java SDK # Overview -## Architecture +The MCP Java SDK lets Java applications act as MCP clients and MCP servers. It requires Java 17 or later. -The SDK follows a layered architecture with clear separation of concerns: +The SDK contains two implementations of the client and the server, one for each lifecycle model of the protocol: + +- **Classic**, for the 2025-11-25 specification and earlier revisions. Client and server set up a session with an `initialize` handshake. +- **Modern**, for the 2026-07-28 specification. There is no handshake and no session; every request stands alone. + +Both ship in the same `mcp-core` module, and an application can use both side by side. + +## Classic or Modern? + +Choose by what the other side speaks. A classic client cannot call a modern server, and a modern client cannot call a classic server. + +| | Classic | Modern | +|---|---|---| +| Spec revision | 2025-11-25 and earlier | 2026-07-28 | +| Package | `io.modelcontextprotocol.client`, `io.modelcontextprotocol.server` | `io.modelcontextprotocol.modern` | +| Lifecycle | `initialize` handshake, then a session | None: every request carries the protocol version and capabilities | +| Requests from the server | Sampling, elicitation, roots, ping | None: the server answers "input required" and the client sends the request again | +| Client instances | One per server | One per user, sharing a transport | +| Scaling a server | Sessions live in the server process, except with the stateless server type | Any instance can answer any request | +| Transports | STDIO, Streamable HTTP, SSE (legacy) | STDIO, Streamable HTTP | +| Spring transports | Yes, in [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ | Not yet | +| Guides | [Client](client.md), [Server](server.md) | [Client](client-modern.md), [Server](server-modern.md) | + +## Classic Architecture + +The classic client and server keep a session between them, set up with an `initialize` handshake: ![MCP Stack Architecture](images/mcp-stack.svg) -- **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 +- **Client/Server Layer (McpClient/McpServer)**: The API you program against, in sync and async variants. McpClient handles the client-side protocol operations and McpServer the server-side ones. +- **Session Layer (McpSession)**: Manages communication patterns and state. It matches requests with responses, in both directions, for as long as the connection lasts. +- **Transport Layer (McpTransport)**: Handles JSON-RPC message serialization and delivery over: + - STDIO (stdin/stdout) + - Streamable HTTP (JDK `HttpClient` on the client, Servlet on the server) + - SSE, the legacy HTTP transport (JDK `HttpClient` on the client, Servlet on the server) - 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. +### Client + +An application creates one MCP client per server it uses. Each client holds its own session: to a remote server over Streamable HTTP or SSE, or to a local server process over STDIO. + +![Java MCP Client Architecture](images/java-mcp-client-architecture.svg) + +### Server + +A classic MCP server keeps one session per connected client, inside the server process. Over HTTP it runs in a web container. Over STDIO the client starts it as a process of its own. + +![Java MCP Server Architecture](images/java-mcp-server-architecture.svg) + +### Key Interactions + +- **Initialization**: Transport setup, protocol compatibility check, capability negotiation, and implementation details exchange. +- **Requests from the client**: Tool discovery and calls, resource discovery and reading, prompts, and completions. +- **Requests from the server**: Sampling, elicitation and roots listing, sent to the client over the same session. +- **Notifications**: List changes, resource updates, progress, and logging. + +See [MCP Client](client.md) and [MCP Server](server.md). + +## Modern Architecture + +The 2026-07-28 revision removes sessions and the `initialize` handshake. The modern client and server are built around that and share no types with the classic ones: + +![MCP stack for the 2026-07-28 revision](images/mcp-stack-modern.svg) + +- **Client/Server Layer (McpClient/McpServer)**: The client turns each call into a self-contained request. The server dispatches each request to a feature, which calls a repository you implement. +- **No Session Layer**: Every request carries the protocol version and the client's capabilities in its `_meta`, so any server instance can answer any request. +- **Transport Layer**: Carries one request and its answer, which is a single response or a stream of notifications followed by the response, over: + - STDIO + - Streamable HTTP (JDK `HttpClient` on the client, Servlet on the server) + +### Client -![Java MCP Client Architecture](images/java-mcp-client-architecture.jpg) +A modern MCP client stands for one user. An application creates one client per user, and the clients of a server share one transport. Each call carries its user's context to the transport, which adds it to the request. -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. +![Modern Java MCP Client Architecture](images/java-mcp-client-architecture-modern.svg) -![Java MCP Server Architecture](images/java-mcp-server-architecture.jpg) +### Server -Key Interactions: +A modern MCP server keeps nothing between requests. Over Streamable HTTP, any number of identical instances can serve the same clients without sticky routing. Over STDIO the client starts the server as a process of its own. -- **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. +![Modern Java MCP Server Architecture](images/java-mcp-server-architecture-modern.svg) -!!! note "2026-07-28 specification" - The architecture and interactions above describe the classic client and server. The preview server for the stateless - 2026-07-28 revision, in `io.modelcontextprotocol.modern`, has no sessions: a single `McpServer` dispatches - each request to features backed by repositories you implement. See [MCP Server (2026-07-28)](server-modern.md). +### Key Interactions + +- **No Initialization**: Building a client does no I/O. The first request goes out when the first call starts. +- **Requests from the client**: Tool discovery and calls, resource discovery and reading, prompts, completions, and `server/discover` for the server's capabilities. +- **Input from the user**: The server cannot send requests to the client. A server that needs input answers with an `input_required` result, and the client sends the request again with the answers. +- **Change notifications**: The client opens a `subscriptions/listen` stream, and the server reports list changes and resource updates on it. + +See [MCP Client (2026-07-28)](client-modern.md) and [MCP Server (2026-07-28)](server-modern.md). ## Module Structure -The SDK is organized into modules to separate concerns and allow adopters to bring in only what they need: +The SDK is organized into modules to separate concerns and allow adopters to bring in only what they need. All of them use the group `io.modelcontextprotocol.sdk`: -| 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+ | +| Module | Purpose | +|--------|---------| +| `mcp-bom` | Bill of Materials for dependency management | +| `mcp-core` | The classic and modern clients and servers, with the STDIO, JDK HttpClient and Servlet transports | +| `mcp-json-jackson2` | Jackson 2.x JSON serialization implementation | +| `mcp-json-jackson3` | Jackson 3.x JSON serialization implementation | +| `mcp` | Convenience bundle (`mcp-core` + `mcp-json-jackson3`) | +| `mcp-test` | Shared testing utilities and integration tests | !!! 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. + A minimal adopter may depend only on `mcp` (core + Jackson 3). + + Spring-based applications that use the classic client or server 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`), which are no longer part of this SDK. ## Next Steps @@ -71,28 +128,44 @@ The SDK is organized into modules to separate concerns and allow adopters to bri [:octicons-arrow-right-24: Quickstart](quickstart.md) -- :material-monitor:{ .lg .middle } **MCP Client** +- :fontawesome-brands-github:{ .lg .middle } **GitHub** --- - Learn how to create and configure MCP clients. + View the source code and contribute. + + [:octicons-arrow-right-24: Repository](https://github.com/modelcontextprotocol/java-sdk) + +- :material-monitor:{ .lg .middle } **MCP Client (Classic)** + + --- + + Create and configure clients for servers of the 2025-11-25 revision and earlier. [:octicons-arrow-right-24: Client](client.md) -- :material-server:{ .lg .middle } **MCP Server** +- :material-server:{ .lg .middle } **MCP Server (Classic)** --- - Learn how to implement and configure MCP servers. + Implement and configure servers for the 2025-11-25 revision and earlier. [:octicons-arrow-right-24: Server](server.md) -- :fontawesome-brands-github:{ .lg .middle } **GitHub** +- :material-monitor:{ .lg .middle } **MCP Client (Modern)** --- - View the source code and contribute. + Call stateless servers of the 2026-07-28 revision. - [:octicons-arrow-right-24: Repository](https://github.com/modelcontextprotocol/java-sdk) + [:octicons-arrow-right-24: Client](client-modern.md) + +- :material-server:{ .lg .middle } **MCP Server (Modern)** + + --- + + Build stateless servers for the 2026-07-28 revision. + + [:octicons-arrow-right-24: Server](server-modern.md) diff --git a/docs/quickstart.md b/docs/quickstart.md index 050563507..0cf33f5f1 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -5,33 +5,74 @@ description: Get started with the MCP Java SDK dependencies and configuration # Quickstart +Add the SDK to your build, then make a first call to an MCP server. The SDK requires Java 17 or later. + ## Dependencies -Add the following dependency to your project: +### Bill of Materials (BOM) -=== "Maven" +Import the BOM so that all SDK modules use the same version and the dependencies below need no version of their own: - The convenience `mcp` module bundles `mcp-core` with Jackson 3.x JSON serialization: +=== "Maven" ```xml - - io.modelcontextprotocol.sdk - mcp - + + + + io.modelcontextprotocol.sdk + mcp-bom + 2.1.0 + pom + import + + + + ``` + +=== "Gradle" + + ```groovy + dependencies { + implementation platform("io.modelcontextprotocol.sdk:mcp-bom:2.1.0") + } ``` - This includes default STDIO, SSE, and Streamable HTTP transport implementations without requiring external web frameworks. +Replace the version number with the latest version from [Maven Central](https://central.sonatype.com/artifact/io.modelcontextprotocol.sdk/mcp). + +!!! note + The modern client and server, for the 2026-07-28 specification, are available from 2.2.0. - If you need only the core module without a JSON implementation (e.g., to bring your own): +### SDK + +Add the `mcp` module, which bundles `mcp-core` with Jackson 3.x JSON serialization: + +=== "Maven" ```xml io.modelcontextprotocol.sdk - mcp-core + mcp ``` - For Jackson 2.x instead of Jackson 3.x: +=== "Gradle" + + ```groovy + dependencies { + implementation "io.modelcontextprotocol.sdk:mcp" + } + ``` + +This is all a client needs. It includes the classic and the modern client and server, with their STDIO and HTTP transports, and requires no web framework. + +!!! note "HTTP servers" + The HTTP server transports are built on the Servlet API, which the SDK does not bring in. Run them in a Servlet container, such as Tomcat or Jetty, with async support enabled. + +### Other Setups + +**Jackson 2.x instead of Jackson 3.x.** Use `mcp-core` with the Jackson 2 module in place of `mcp`: + +=== "Maven" ```xml @@ -44,120 +85,121 @@ Add the following dependency to your project: ``` - 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`): +=== "Gradle" + + ```groovy + dependencies { + implementation "io.modelcontextprotocol.sdk:mcp-core" + implementation "io.modelcontextprotocol.sdk:mcp-json-jackson2" + } + ``` + +**Your own JSON implementation.** Depend on `mcp-core` alone and provide the JSON binding yourself. + +**Spring applications.** Spring WebFlux and WebMVC transports for the classic client and server are 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`), not of this SDK: + +=== "Maven" ```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" + // Spring WebFlux-based SSE and Streamable HTTP client and server transport + implementation "org.springframework.ai:mcp-spring-webflux" + + // Spring WebMVC-based SSE and Streamable HTTP server transport + implementation "org.springframework.ai:mcp-spring-webmvc" } ``` - This includes default STDIO, SSE, and Streamable HTTP transport implementations without requiring external web frameworks. +With the `spring-ai-bom` or the Spring AI starters (`spring-ai-starter-mcp-server-webflux`, `spring-ai-starter-mcp-server-webmvc`, `spring-ai-starter-mcp-client-webflux`), no explicit version is needed. - If you need only the core module without a JSON implementation (e.g., to bring your own): +## Your First Client - ```groovy - dependencies { - implementation "io.modelcontextprotocol.sdk:mcp-core" - } - ``` +The SDK has two clients, one for each generation of the protocol; see [Classic or Modern?](overview.md#classic-or-modern) to choose. Both examples list the tools of a server at `http://localhost:8080/mcp` and call one of them. - For Jackson 2.x instead of Jackson 3.x: +=== "Classic" - ```groovy - dependencies { - implementation "io.modelcontextprotocol.sdk:mcp-core" - implementation "io.modelcontextprotocol.sdk:mcp-json-jackson2" - } - ``` + For servers of the 2025-11-25 specification and earlier. The client sets up a session with `initialize()`: - 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`): + ```java + import java.util.Map; - ```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" - } + import io.modelcontextprotocol.client.McpClient; + import io.modelcontextprotocol.client.McpSyncClient; + import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; + import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; + import io.modelcontextprotocol.spec.McpSchema.CallToolResult; - // Optional: Spring WebMVC-based SSE and Streamable HTTP server transport (Spring AI 2.0+) - dependencies { - implementation "org.springframework.ai:mcp-spring-webmvc" - } + McpSyncClient client = McpClient.sync( + HttpClientStreamableHttpTransport.builder("http://localhost:8080").build()) + .build(); + + client.initialize(); + + client.listTools().tools().forEach(tool -> System.out.println(tool.name())); + + CallToolResult result = client.callTool( + CallToolRequest.builder("echo").arguments(Map.of("text", "Hello")).build()); + System.out.println(result.content()); + + client.closeGracefully(); ``` -## Bill of Materials (BOM) + Continue with the [MCP Client](client.md) guide. -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. +=== "Modern" -Add the BOM to your project: + For servers of the 2026-07-28 specification. There is nothing to initialize and nothing to close on the client: -=== "Maven" + ```java + import java.util.Map; - ```xml - - - - io.modelcontextprotocol.sdk - mcp-bom - 2.1.0 - pom - import - - - - ``` + import io.modelcontextprotocol.modern.McpSchema.CallToolResult; + import io.modelcontextprotocol.modern.client.McpClient; + import io.modelcontextprotocol.modern.client.McpSyncClient; + import io.modelcontextprotocol.modern.client.transport.HttpClientMcpTransport; -=== "Gradle" + McpSyncClient client = McpClient.sync( + HttpClientMcpTransport.builder("http://localhost:8080/mcp").build()) + .build(); - ```groovy - dependencies { - implementation platform("io.modelcontextprotocol.sdk:mcp-bom:2.1.0") - //... - } + client.listTools().list().tools().forEach(tool -> System.out.println(tool.name())); + + CallToolResult result = client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call(); + System.out.println(result.content()); ``` - 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. + Continue with the [MCP Client (2026-07-28)](client-modern.md) guide. -Replace the version number with the latest version from [Maven Central](https://central.sonatype.com/artifact/io.modelcontextprotocol.sdk/mcp). +To build the server side, see the [MCP Server](server.md) guide for the classic server and the [Quick Start](server-modern.md#quick-start) of the modern server guide, which serves the `echo` tool the modern example calls. ## 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. +The BOM manages the following modules, all in the group `io.modelcontextprotocol.sdk`: + +| Module | Purpose | +|--------|---------| +| `mcp` | Convenience bundle that combines `mcp-core` with `mcp-json-jackson3`. | +| `mcp-core` | The classic and modern clients and servers, with the STDIO, JDK HttpClient and Servlet transports. JSON binding is pluggable. | +| `mcp-json-jackson3` | Jackson 3.x JSON serialization (included in `mcp`). | +| `mcp-json-jackson2` | Jackson 2.x JSON serialization, for projects that need Jackson 2.x. | +| `mcp-test` | Testing utilities and support for MCP-based applications. | + +The Spring transports, `mcp-spring-webflux` and `mcp-spring-webmvc`, are published by [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ in the group `org.springframework.ai`. diff --git a/docs/server-modern.md b/docs/server-modern.md index 3c9f82d4b..7ee3435b3 100644 --- a/docs/server-modern.md +++ b/docs/server-modern.md @@ -5,11 +5,11 @@ description: Build a stateless MCP server for the 2026-07-28 specification revis # MCP Server (2026-07-28) -!!! warning "Preview" - The modern server is a **preview** in 2.2.0. Its API, in the `io.modelcontextprotocol.modern` packages, - may change in minor releases until 3.0. There is no modern client yet, and - [some parts of the revision](#not-yet-supported) are not implemented. For production servers that - talk to today's clients, use the [classic server](server.md). +!!! note + The modern server is available since 2.2.0. Its API, in the `io.modelcontextprotocol.modern` packages, + may change in minor releases until 3.0, and [some parts of the revision](#not-yet-supported) are not + implemented. For production servers that talk to today's clients, use the [classic server](server.md). + To call a modern server from Java, see the [modern client](client-modern.md). ## Overview @@ -44,8 +44,6 @@ server is built around that: | Primitives | Registered specifications | Repositories you implement | | Server-to-client requests | Sampling, elicitation, roots, ping | Replaced by `InputRequiredResult` | | Logging | Supported | Not supported (deprecated by the revision) | -| Status | Stable | Preview | - By default the modern server only accepts the 2026-07-28 protocol version; change the set with `supportedVersions(...)` on the builder. A request declaring another version gets an `Unsupported protocol version` error listing the versions the server supports. Clients that start with an @@ -231,6 +229,18 @@ provides one feature per primitive, each backed by a **repository** you implemen | `ResourcesFeature` | `resources/list`, `resources/templates/list`, `resources/read` | `McpSyncResourceRepository` | `McpAsyncResourceRepository` | | `PromptsFeature` | `prompts/list`, `prompts/get` | `McpSyncPromptRepository` | `McpAsyncPromptRepository` | +Each request passes from the transport to the server, which hands it to the feature that serves its method. +The feature calls your repository: + +```mermaid +flowchart LR + C["MCP client"] --> T["Transport
Servlet or STDIO"] + T --> S["McpServer
dispatches by method"] + S --> TF["ToolsFeature"] --> TR["Your tool repository"] + S --> RF["ResourcesFeature"] --> RR["Your resource repository"] + S --> PF["PromptsFeature"] --> PR["Your prompt repository"] +``` + Create a feature with `ofSync(repository)` or `ofAsync(repository)`. Register only the features you need; the server advertises a capability only when its feature is registered. `completion/complete` has no feature of its own: the prompts and resources features answer it for their own references, see [Completions](#completions). @@ -474,6 +484,28 @@ The 2026-07-28 revision has no server-to-client requests. Instead, a `tools/call (elicitation, sampling, roots), each under a key you choose. The client fulfils them and retries the original request with the answers in `inputResponses`, keyed the same way. +```mermaid +sequenceDiagram + participant Client as MCP client + participant Server as McpServer + participant Handler as Your handler + + Client->>Server: tools/call + Server->>Handler: request without inputResponses + Handler-->>Server: InputRequiredResult (input requests, requestState) + Note over Server: seals requestState + Server-->>Client: input_required + Note over Client: asks the user + Client->>Server: tools/call with inputResponses and requestState + Note over Server: opens requestState + Server->>Handler: request with inputResponses and requestState + Handler-->>Server: CallToolResult + Server-->>Client: complete result +``` + +The handler runs once per round and keeps nothing in memory between rounds. Whatever it needs to remember +travels in `requestState` (see [Carrying State Across Rounds](#carrying-state-across-rounds)). + ```java (ctx, request) -> { ElicitResult answer = InputResponses.get( @@ -689,7 +721,6 @@ record TaskChanged(String taskId) implements ServerChange { ## Not Yet Supported -- A modern client. Use the conformance suite or another SDK's client to exercise the server. - Tasks. - Logging, which the 2026-07-28 revision deprecates. The modern server never sends log messages. - Spring WebMVC and WebFlux transports. Spring AI's transports target the classic server. diff --git a/docs/server.md b/docs/server.md index c60988e65..a77e05d69 100644 --- a/docs/server.md +++ b/docs/server.md @@ -26,7 +26,7 @@ The MCP Server is a foundational component in the Model Context Protocol (MCP) a !!! note "2026-07-28 specification" This page covers the classic server, which implements the 2025-11-25 specification and earlier revisions. - A stateless server for the 2026-07-28 revision is available as a preview; see + A stateless server for the 2026-07-28 revision is also available; see [MCP Server (2026-07-28)](server-modern.md). The server supports both synchronous and asynchronous APIs, allowing for flexible integration in different application contexts. diff --git a/mkdocs.yml b/mkdocs.yml index 05c05f311..d889d9b2a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,10 +42,12 @@ nav: - Documentation: - Overview: overview.md - Quickstart: quickstart.md - - MCP Components: + - Classic (2025-11-25 and earlier): - MCP Client: client.md - MCP Server: server.md - - MCP Server (2026-07-28, Preview): server-modern.md + - Modern (2026-07-28): + - MCP Client: client-modern.md + - MCP Server: server-modern.md - Contributing: - Contributing Guide: contribute.md - Documentation: development.md From 38519ce928eb3cb8709a63616de4e735b65afed1 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 13 Nov 2026 09:13:17 +0100 Subject: [PATCH 2/3] Rename Classic to Legacy and add sync and async tabs to the docs Follow the terms of the MCP specification, which names the handshake-based revisions "legacy" and the per-request ones "modern". - Classic is renamed to Legacy in the navigation, the guides and the diagrams. Modern now comes first everywhere. - The SSE transport is labelled "Deprecated", as in the specification. - The modern client and server guides show each example in a Sync and an Async tab. - "Calling the Server" in the modern client guide explains the two steps of a call, in place of the "Spec" and "Terminal" columns. - Spring AI links point to the 2.1.0-SNAPSHOT reference. Signed-off-by: Christian Tzolov --- docs/client-modern.md | 426 ++++++++++---- docs/client.md | 12 +- docs/images/java-mcp-client-architecture.svg | 2 +- docs/images/java-mcp-server-architecture.svg | 2 +- docs/images/mcp-stack.svg | 2 +- docs/index.md | 68 +-- docs/overview.md | 123 ++-- docs/quickstart.md | 66 +-- docs/server-modern.md | 589 +++++++++++++------ docs/server.md | 16 +- mkdocs.yml | 6 +- 11 files changed, 858 insertions(+), 454 deletions(-) diff --git a/docs/client-modern.md b/docs/client-modern.md index 077a424cb..ab16400bb 100644 --- a/docs/client-modern.md +++ b/docs/client-modern.md @@ -8,13 +8,13 @@ description: Call stateless MCP servers of the 2026-07-28 specification revision !!! note The modern client is available since 2.2.0. Its API, in the `io.modelcontextprotocol.modern` packages, may change in minor releases until 3.0, and [some parts of the revision](#not-yet-supported) are not - implemented. For servers of earlier revisions, use the [classic client](client.md). + implemented. For servers of earlier revisions, use the [legacy client](client.md). ## Overview The modern client talks to servers of the stateless [2026-07-28 MCP specification revision](https://modelcontextprotocol.io/specification/2026-07-28). -It lives in `io.modelcontextprotocol.modern.client`, next to the classic client, and shares no types with it: +It lives in `io.modelcontextprotocol.modern.client`, next to the legacy client, and shares no types with it: it has its own `McpClient`, its own `McpSchema`, and its own transports. Both ship in `mcp-core`, so no extra dependency is needed. @@ -33,50 +33,78 @@ version and the client's capabilities in its `_meta`. The modern client is built - **The protocol details are handled for you.** The client adds the `_meta` block, negotiates the protocol version, answers the server's input requests, and sets the `Mcp-*` headers. -### Classic or modern? +### Modern or legacy? -| | [Classic client](client.md) | Modern client | +| | Modern client | [Legacy client](client.md) | |---|---|---| -| Package | `io.modelcontextprotocol.client` | `io.modelcontextprotocol.modern.client` | -| Spec revision | 2025-11-25 and earlier | 2026-07-28 | -| Startup | `initialize()` handshake | None | -| Sessions | Yes | No | -| Elicitation | Server-to-client request | Input request answered in a retry of the call | -| Sampling, roots | Supported | Not supported | -| Logging | Supported | Not supported (deprecated by the revision) | +| Package | `io.modelcontextprotocol.modern.client` | `io.modelcontextprotocol.client` | +| Spec revision | 2026-07-28 | 2025-11-25 and earlier | +| Startup | None | `initialize()` handshake | +| Sessions | No | Yes | +| Elicitation | Input request answered in a retry of the call | Server-to-client request | +| Sampling, roots | Not supported | Supported | +| Logging | Not supported (deprecated by the revision) | Supported | The modern client only speaks the 2026-07-28 protocol version. It cannot call servers that expect an `initialize` handshake. ## Quick Start -A synchronous client that lists the server's tools and calls one over HTTP: +A client that lists the server's tools and calls one over HTTP: -```java -import java.util.Map; +=== "Sync" -import io.modelcontextprotocol.modern.McpSchema.CallToolResult; -import io.modelcontextprotocol.modern.McpSchema.Implementation; -import io.modelcontextprotocol.modern.McpSchema.Tool; -import io.modelcontextprotocol.modern.client.McpClient; -import io.modelcontextprotocol.modern.client.McpSyncClient; -import io.modelcontextprotocol.modern.client.transport.HttpClientMcpTransport; + ```java + import java.util.Map; -HttpClientMcpTransport transport = HttpClientMcpTransport.builder("http://localhost:8080/mcp").build(); + import io.modelcontextprotocol.modern.McpSchema.CallToolResult; + import io.modelcontextprotocol.modern.McpSchema.Implementation; + import io.modelcontextprotocol.modern.McpSchema.Tool; + import io.modelcontextprotocol.modern.client.McpClient; + import io.modelcontextprotocol.modern.client.McpSyncClient; + import io.modelcontextprotocol.modern.client.transport.HttpClientMcpTransport; -McpSyncClient client = McpClient.sync(transport) - .clientInfo(Implementation.builder("my-client", "1.0.0").build()) - .build(); + HttpClientMcpTransport transport = HttpClientMcpTransport.builder("http://localhost:8080/mcp").build(); -for (Tool tool : client.listTools().list().tools()) { - System.out.println(tool.name()); -} + McpSyncClient client = McpClient.sync(transport) + .clientInfo(Implementation.builder("my-client", "1.0.0").build()) + .build(); -CallToolResult result = client.tool("echo") - .arguments(Map.of("text", "Hello")) - .call(); -System.out.println(result.content()); -``` + for (Tool tool : client.listTools().list().tools()) { + System.out.println(tool.name()); + } + + CallToolResult result = client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call(); + System.out.println(result.content()); + ``` + +=== "Async" + + ```java + import java.util.Map; + + import io.modelcontextprotocol.modern.McpSchema.CallToolResult; + import io.modelcontextprotocol.modern.McpSchema.Implementation; + import io.modelcontextprotocol.modern.client.McpAsyncClient; + import io.modelcontextprotocol.modern.client.McpClient; + import io.modelcontextprotocol.modern.client.transport.HttpClientMcpTransport; + + HttpClientMcpTransport transport = HttpClientMcpTransport.builder("http://localhost:8080/mcp").build(); + + McpAsyncClient client = McpClient.async(transport) + .clientInfo(Implementation.builder("my-client", "1.0.0").build()) + .build(); + + CallToolResult result = client.listTools().list() + .doOnNext(tools -> tools.tools().forEach(tool -> System.out.println(tool.name()))) + .then(client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call()) + .block(); // block() only to keep the example short + System.out.println(result.content()); + ``` Clients are immutable and thread-safe. There is nothing to start and nothing to close on the client. @@ -127,19 +155,37 @@ HttpClientMcpTransport transport = HttpClientMcpTransport.builder("https://examp `StdioMcpClientTransport` runs the server as a process and exchanges newline-delimited JSON-RPC over its standard streams. -```java -import io.modelcontextprotocol.client.transport.ServerParameters; -import io.modelcontextprotocol.modern.client.transport.StdioMcpClientTransport; +=== "Sync" -ServerParameters params = ServerParameters.builder("java") - .args("-jar", "my-mcp-server.jar") - .build(); + ```java + import io.modelcontextprotocol.client.transport.ServerParameters; + import io.modelcontextprotocol.modern.client.transport.StdioMcpClientTransport; -try (StdioMcpClientTransport transport = StdioMcpClientTransport.builder(params).start()) { - McpSyncClient client = McpClient.sync(transport).build(); - // use the client -} -``` + ServerParameters params = ServerParameters.builder("java") + .args("-jar", "my-mcp-server.jar") + .build(); + + try (StdioMcpClientTransport transport = StdioMcpClientTransport.builder(params).start()) { + McpSyncClient client = McpClient.sync(transport).build(); + // use the client + } + ``` + +=== "Async" + + ```java + import io.modelcontextprotocol.client.transport.ServerParameters; + import io.modelcontextprotocol.modern.client.transport.StdioMcpClientTransport; + + ServerParameters params = ServerParameters.builder("java") + .args("-jar", "my-mcp-server.jar") + .build(); + + try (StdioMcpClientTransport transport = StdioMcpClientTransport.builder(params).start()) { + McpAsyncClient client = McpClient.async(transport).build(); + // use the client + } + ``` `start()` spawns the process. Clients never start or close the transport they are given, so one process can serve several clients; close the transport once they are done. When the server's output ends or the transport @@ -168,20 +214,41 @@ handler receive. - **Async** calls return a `Mono` (or a `Flux` for listen streams). Nothing is sent before subscription. Handlers return a `Mono` and must not block. -```java -McpAsyncClient client = McpClient.async(transport).build(); +=== "Sync" -client.tool("echo") - .arguments(Map.of("text", "Hello")) - .call() - .subscribe(result -> System.out.println(result.content())); -``` + ```java + McpSyncClient client = McpClient.sync(transport).build(); + + CallToolResult result = client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call(); + System.out.println(result.content()); + ``` + +=== "Async" + + ```java + McpAsyncClient client = McpClient.async(transport).build(); -The examples on this page use the sync client unless they say otherwise. + client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call() + .subscribe(result -> System.out.println(result.content())); + ``` + +The examples on this page have a Sync and an Async tab. Choosing one switches all of them. ## Calling the Server -| Operation | Spec | Terminal | +Every operation takes two steps: + +1. A method on the client starts the request. It sends nothing yet; it returns a request spec, on which you + set arguments and [per-call settings](#per-call-settings). +2. A final method on the spec sends the request and returns the result (in the async client, a `Mono` of it). + +In `client.tool("echo").arguments(args).call()`, `tool("echo")` starts the request and `call()` sends it. + +| Operation | Start the request | Send it | |---|---|---| | Call a tool | `client.tool(name)` or `client.tool(Tool)` | `call()` | | Read a resource | `client.resource(uri)` | `read()` | @@ -189,19 +256,35 @@ The examples on this page use the sync client unless they say otherwise. | Complete an argument | `client.completion(ref, argument, value)` | `complete()` | | List one page | `client.tools()`, `prompts()`, `resources()`, `resourceTemplates()` | `list()` | | List all tools | `client.listTools()` | `list()` | -| Discover the server | | `client.discover()` | +| Discover the server | `client.discover()` | Sent right away; there is nothing to configure | | Listen for changes | `client.listen()` | `open(listener)` (sync), `events()` (async) | -```java -ReadResourceResult resource = client.resource("file:///config.json").read(); +=== "Sync" -GetPromptResult prompt = client.prompt("greeting") - .arguments(Map.of("name", "Ada")) - .get(); + ```java + ReadResourceResult resource = client.resource("file:///config.json").read(); -CompleteResult completion = client.completion(new PromptReference("greeting"), "name", "A").complete(); -List suggestions = completion.completion().values(); -``` + GetPromptResult prompt = client.prompt("greeting") + .arguments(Map.of("name", "Ada")) + .get(); + + CompleteResult completion = client.completion(new PromptReference("greeting"), "name", "A").complete(); + List suggestions = completion.completion().values(); + ``` + +=== "Async" + + ```java + Mono resource = client.resource("file:///config.json").read(); + + Mono prompt = client.prompt("greeting") + .arguments(Map.of("name", "Ada")) + .get(); + + Mono> suggestions = client.completion(new PromptReference("greeting"), "name", "A") + .complete() + .map(completion -> completion.completion().values()); + ``` A tool failure the model should see is not an exception. It is a `CallToolResult` whose `isError()` is `true`. @@ -209,14 +292,27 @@ A tool failure the model should see is not an exception. It is a `CallToolResult A spec starts from the client's settings. Each setter replaces one of them for that call only: -```java -CallToolResult result = client.tool("generate_report") - .arguments(Map.of("year", 2026)) - .roundTimeout(Duration.ofMinutes(5)) - .progress(progress -> System.out.println(progress.message())) - .meta(Map.of("com.example/traceId", traceId)) - .call(); -``` +=== "Sync" + + ```java + CallToolResult result = client.tool("generate_report") + .arguments(Map.of("year", 2026)) + .roundTimeout(Duration.ofMinutes(5)) + .progress(progress -> System.out.println(progress.message())) + .meta(Map.of("com.example/traceId", traceId)) + .call(); + ``` + +=== "Async" + + ```java + Mono result = client.tool("generate_report") + .arguments(Map.of("year", 2026)) + .roundTimeout(Duration.ofMinutes(5)) + .progress(progress -> Mono.fromRunnable(() -> System.out.println(progress.message()))) + .meta(Map.of("com.example/traceId", traceId)) + .call(); + ``` `meta(...)` adds entries to the request's `_meta`. It rejects `progressToken` and keys starting with `io.modelcontextprotocol/`, which the client sets itself. @@ -225,15 +321,26 @@ CallToolResult result = client.tool("generate_report") The list specs return one page. Pass the previous page's `nextCursor()` to read the next one: -```java -String cursor = null; -do { - ListResourcesResult page = client.resources().cursor(cursor).list(); - page.resources().forEach(resource -> System.out.println(resource.uri())); - cursor = page.nextCursor(); -} -while (cursor != null); -``` +=== "Sync" + + ```java + String cursor = null; + do { + ListResourcesResult page = client.resources().cursor(cursor).list(); + page.resources().forEach(resource -> System.out.println(resource.uri())); + cursor = page.nextCursor(); + } + while (cursor != null); + ``` + +=== "Async" + + ```java + Flux resources = client.resources().list() + .expand(page -> page.nextCursor() == null ? Mono.empty() + : client.resources().cursor(page.nextCursor()).list()) + .flatMapIterable(ListResourcesResult::resources); + ``` For tools, `client.listTools().list()` reads all pages and returns them as one result. @@ -254,11 +361,22 @@ it without asking the server again. `client.discover()` returns the server's supported versions, capabilities and instructions. The result is kept for its `ttlMs`. -```java -DiscoverResult server = client.discover(); -ServerCapabilities capabilities = server.capabilities(); -Object serverInfo = server.meta() != null ? server.meta().get(McpSchema.MetaKeys.SERVER_INFO) : null; -``` +=== "Sync" + + ```java + DiscoverResult server = client.discover(); + ServerCapabilities capabilities = server.capabilities(); + Object serverInfo = server.meta() != null ? server.meta().get(McpSchema.MetaKeys.SERVER_INFO) : null; + ``` + +=== "Async" + + ```java + client.discover().subscribe(server -> { + ServerCapabilities capabilities = server.capabilities(); + Object serverInfo = server.meta() != null ? server.meta().get(McpSchema.MetaKeys.SERVER_INFO) : null; + }); + ``` The server's name and version are not a field of the result. Servers send them in `_meta`, under `io.modelcontextprotocol/serverInfo`, as an untyped map. A server may leave `_meta` out, so check for `null`. @@ -305,18 +423,32 @@ sequenceDiagram The application makes one call and gets one result. The rounds in between are the client's job. -```java -McpSyncClient client = McpClient.sync(transport) - .formElicitation(request -> { - String name = askUser(request.message(), request.requestedSchema()); - return name != null ? ElicitResult.accept(Map.of("name", name)) : ElicitResult.decline(); - }) - .urlElicitation(request -> { - boolean opened = askConsentAndOpenBrowser(request.message(), request.url()); - return opened ? ElicitResult.accept(Map.of()) : ElicitResult.decline(); - }) - .build(); -``` +=== "Sync" + + ```java + McpSyncClient client = McpClient.sync(transport) + .formElicitation(request -> { + String name = askUser(request.message(), request.requestedSchema()); + return name != null ? ElicitResult.accept(Map.of("name", name)) : ElicitResult.decline(); + }) + .urlElicitation(request -> { + boolean opened = askConsentAndOpenBrowser(request.message(), request.url()); + return opened ? ElicitResult.accept(Map.of()) : ElicitResult.decline(); + }) + .build(); + ``` + +=== "Async" + + ```java + McpAsyncClient client = McpClient.async(transport) + .formElicitation(request -> askUser(request.message(), request.requestedSchema()) + .map(name -> ElicitResult.accept(Map.of("name", name))) + .defaultIfEmpty(ElicitResult.decline())) + .urlElicitation(request -> askConsentAndOpenBrowser(request.message(), request.url()) + .map(opened -> opened ? ElicitResult.accept(Map.of()) : ElicitResult.decline())) + .build(); + ``` - **Form mode.** The handler receives an `ElicitFormRequest` with a message and a JSON schema, and returns the user's answer. @@ -340,20 +472,41 @@ A call answers input requests at most `maxInputRounds` times (10 by default) and the final result or an `InputRequiredResult`. Use them to pause a call, for example until a user answers in a later HTTP request, and continue it with `inputResponses(...)` and `requestState(...)`: -```java -McpSyncClient.ToolSpec deploy = client.tool("deploy").arguments(Map.of("env", "prod")); +=== "Sync" -CallToolOutcome outcome = deploy.callOnce(); -if (outcome instanceof InputRequiredResult inputRequired) { - Map responses = new LinkedHashMap<>(); - inputRequired.inputRequests().forEach((key, request) -> responses.put(key, answer(request))); + ```java + McpSyncClient.ToolSpec deploy = client.tool("deploy").arguments(Map.of("env", "prod")); + + CallToolOutcome outcome = deploy.callOnce(); + if (outcome instanceof InputRequiredResult inputRequired) { + Map responses = new LinkedHashMap<>(); + inputRequired.inputRequests().forEach((key, request) -> responses.put(key, answer(request))); + + CallToolResult result = deploy + .inputResponses(responses) + .requestState(inputRequired.requestState()) + .call(); + } + ``` - CallToolResult result = deploy - .inputResponses(responses) - .requestState(inputRequired.requestState()) - .call(); -} -``` +=== "Async" + + ```java + McpAsyncClient.ToolSpec deploy = client.tool("deploy").arguments(Map.of("env", "prod")); + + Mono result = deploy.callOnce().flatMap(outcome -> { + if (outcome instanceof InputRequiredResult inputRequired) { + Map responses = new LinkedHashMap<>(); + inputRequired.inputRequests().forEach((key, request) -> responses.put(key, answer(request))); + + return deploy + .inputResponses(responses) + .requestState(inputRequired.requestState()) + .call(); + } + return Mono.just((CallToolResult) outcome); + }); + ``` Key the answers as the server keyed its requests, and send `requestState` back as it was received. @@ -362,11 +515,22 @@ Key the answers as the server keyed its requests, and send `requestState` back a A progress listener receives the `notifications/progress` of a call. Set one for every call on the builder, or for one call on its spec: -```java -McpSyncClient client = McpClient.sync(transport) - .progress(progress -> System.out.println(progress.progress() + " / " + progress.total())) - .build(); -``` +=== "Sync" + + ```java + McpSyncClient client = McpClient.sync(transport) + .progress(progress -> System.out.println(progress.progress() + " / " + progress.total())) + .build(); + ``` + +=== "Async" + + ```java + McpAsyncClient client = McpClient.async(transport) + .progress(progress -> Mono.fromRunnable( + () -> System.out.println(progress.progress() + " / " + progress.total()))) + .build(); + ``` - The client asks the server for progress only while a listener is in effect. - A listener receives the notifications of a call one at a time and in order. @@ -471,18 +635,32 @@ flowchart LR .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, McpTransportContext.create(Map.of("user", user)))); ``` -**2. Use it in the transport's hooks.** +**2. Use it in the transport's hooks.** A hook may block (wrapped with `fromSync`) or return a `Mono`. Either kind works with both clients. -```java -HttpClientMcpTransport transport = HttpClientMcpTransport.builder("https://example.com/mcp") - .requestCustomizer(McpHttpRequestCustomizer.fromSync((request, headers, context) -> - headers.set("Authorization", "Bearer " + tokens.accessToken(context.get("user"))))) - .authorizationErrorHandler(McpHttpAuthorizationErrorHandler.fromSync((request, response, context) -> { - tokens.refresh(context.get("user")); - return true; // send the request again - })) - .build(); -``` +=== "Sync" + + ```java + HttpClientMcpTransport transport = HttpClientMcpTransport.builder("https://example.com/mcp") + .requestCustomizer(McpHttpRequestCustomizer.fromSync((request, headers, context) -> + headers.set("Authorization", "Bearer " + tokens.accessToken(context.get("user"))))) + .authorizationErrorHandler(McpHttpAuthorizationErrorHandler.fromSync((request, response, context) -> { + tokens.refresh(context.get("user")); + return true; // send the request again + })) + .build(); + ``` + +=== "Async" + + ```java + HttpClientMcpTransport transport = HttpClientMcpTransport.builder("https://example.com/mcp") + .requestCustomizer((request, headers, context) -> tokens.accessToken(context.get("user")) + .doOnNext(token -> headers.set("Authorization", "Bearer " + token)) + .then()) + .authorizationErrorHandler((request, response, context) -> tokens.refresh(context.get("user")) + .thenReturn(true)) // send the request again + .build(); + ``` - **Request customizers** run before every attempt, including the retry after an authorization error, so a refreshed token is picked up. @@ -558,7 +736,7 @@ Failures outside those two types: - Sampling and roots input requests. The client declares neither capability. - Logging, which the 2026-07-28 revision deprecates. - Extensions, including Tasks. -- Servers of earlier revisions. Use the [classic client](client.md) for those. +- Servers of earlier revisions. Use the [legacy client](client.md) for those. - Obtaining OAuth tokens. Add tokens with a request customizer, as shown in [Identity and Authorization](#identity-and-authorization). - Spring WebClient transport. diff --git a/docs/client.md b/docs/client.md index be1219e25..8a4a2f274 100644 --- a/docs/client.md +++ b/docs/client.md @@ -19,11 +19,11 @@ The MCP Client is a key component in the Model Context Protocol (MCP) architectu !!! 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 Spring-specific WebFlux transport (`mcp-spring-webflux`) is now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.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.1.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) documentation for Spring-based client setup. !!! note "2026-07-28 specification" - This page covers the classic client, which implements the 2025-11-25 specification and earlier revisions. + This page covers the legacy client, which implements the 2025-11-25 specification and earlier revisions. A client for the stateless 2026-07-28 revision is also available; see [MCP Client (2026-07-28)](client-modern.md). @@ -173,7 +173,7 @@ McpTransport transport = new StdioClientTransport(params, McpJsonDefaults.getMap === "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`): + Creates Streamable HTTP WebClient-based client transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`): ```java McpTransport transport = WebFluxSseClientTransport @@ -181,7 +181,7 @@ McpTransport transport = new StdioClientTransport(params, McpJsonDefaults.getMap .build(); ``` -### SSE HTTP (Legacy) +### SSE HTTP (Deprecated) === "SSE HttpClient" @@ -192,7 +192,7 @@ McpTransport transport = new StdioClientTransport(params, McpJsonDefaults.getMap ``` === "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`): + Creates WebFlux-based SSE client transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`): ```java WebClient.Builder webClientBuilder = WebClient.builder() diff --git a/docs/images/java-mcp-client-architecture.svg b/docs/images/java-mcp-client-architecture.svg index b005db432..fea0aa92f 100644 --- a/docs/images/java-mcp-client-architecture.svg +++ b/docs/images/java-mcp-client-architecture.svg @@ -1,7 +1,7 @@ - Classic Java MCP client architecture + Legacy Java MCP client architecture A Java application on your computer talks to AI models and holds one Java MCP client per server, each with its own session. Two clients reach remote MCP servers over Streamable HTTP or SSE. Three clients reach local MCP servers over STDIO: one that searches the internet, one for local folders and one diff --git a/docs/images/java-mcp-server-architecture.svg b/docs/images/java-mcp-server-architecture.svg index eae806c2d..cae0439cb 100644 --- a/docs/images/java-mcp-server-architecture.svg +++ b/docs/images/java-mcp-server-architecture.svg @@ -1,7 +1,7 @@ - Classic Java MCP server architecture + Legacy Java MCP server architecture Over Streamable HTTP or SSE, MCP clients in any language connect to one server process. The Java MCP server runs in a web container, keeps one session per client, and serves the tools, resources and prompts you registered, backed by databases and backend applications. Over STDIO, a client process starts diff --git a/docs/images/mcp-stack.svg b/docs/images/mcp-stack.svg index 51c8fcf27..30d17cd71 100644 --- a/docs/images/mcp-stack.svg +++ b/docs/images/mcp-stack.svg @@ -1,7 +1,7 @@ - MCP stack of the classic client and server + MCP stack of the legacy client and server On the client side, your application calls McpClient, which works through an McpSession on top of a client transport. On the server side, a server transport feeds an McpSession, on top of which McpServer serves the tools, resources and prompts you registered. The two sessions are set up with an diff --git a/docs/index.md b/docs/index.md index 13cc0355c..443efff67 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,42 +10,43 @@ Use it to build MCP clients and MCP servers in Java 17 or later, with a synchron The SDK implements two generations of the protocol, side by side in the same module: -- **Classic**, for the [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25) specification and earlier revisions, where client and server keep a session. - **Modern**, for the stateless [2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28) specification, where every request stands alone. +- **Legacy**, for the [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25) specification and earlier revisions, where client and server keep a session. -See [Classic or Modern?](overview.md#classic-or-modern) to choose between them. +The names are the specification's own, and legacy does not mean deprecated here: the SDK supports both. +See [Modern or Legacy?](overview.md#modern-or-legacy) to choose between them. ## Features What each implementation supports: -| Feature | Classic | Modern | +| Feature | Modern | Legacy | |---|---|---| | [Tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools): discovery, calls, structured output | Yes | Yes | | [Resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources) and URI templates | Yes | Yes | | [Prompts](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts) | Yes | Yes | | [Completions](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion) for argument values | Yes | Yes | -| [Elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation): asking the user for input | Yes, as a request from the server | Yes, as a multi round-trip request (form and URL) | -| [Sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) | Yes | Server only | -| [Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) | Yes | Server only | +| [Elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation): asking the user for input | Yes, as a multi round-trip request (form and URL) | Yes, as a request from the server | +| [Sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) | Server only | Yes | +| [Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) | Server only | Yes | | [Progress](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/progress) notifications | Yes | Yes | -| [Logging](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging) | Yes | No (deprecated by the revision) | -| Change notifications | Yes, over the session | Yes, with `subscriptions/listen` | -| Finding out what a server supports | `initialize` handshake | `server/discover` | -| Caching hints on results | No | Yes | -| Custom HTTP headers for tool arguments (SEP-2243) | No | Yes | +| [Logging](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging) | No (deprecated by the revision) | Yes | +| Change notifications | Yes, with `subscriptions/listen` | Yes, over the session | +| Finding out what a server supports | `server/discover` | `initialize` handshake | +| Caching hints on results | Yes | No | +| Custom HTTP headers for tool arguments (SEP-2243) | Yes | No | Both implementations share the following: - **Sync and async APIs.** Choose blocking calls or Project Reactor types. -- **Transports without a web framework.** STDIO, and Streamable HTTP on the JDK `HttpClient` (client) and the Servlet API (server). The classic implementation also has the legacy [SSE](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) transport. +- **Transports without a web framework.** STDIO, and Streamable HTTP on the JDK `HttpClient` (client) and the Servlet API (server). The legacy implementation also has the deprecated [SSE](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) transport. - **Pluggable JSON serialization.** Jackson 3.x by default, or Jackson 2.x. - **Security hooks.** Pluggable authorization hooks, and opt-in DNS rebinding protection with Host/Origin header validation (see [HTTP Transport Security](server.md#http-transport-security)). !!! tip "Spring applications" - Spring WebFlux and WebMVC transports for the classic client and server are part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ and are not 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 the [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). + Spring WebFlux and WebMVC transports for the legacy client and server are part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ and are not shipped by this SDK. + Use the [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) and the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/2.1.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.1.0-SNAPSHOT/api/mcp/mcp-annotations-overview.html) and [MCP Security](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-security.html). ## Getting Started @@ -64,52 +65,51 @@ The [Quickstart](quickstart.md) covers versions, the BOM, Gradle, and the other
-- :rocket:{ .lg .middle } **Quickstart** +- :material-sitemap:{ .lg .middle } **Overview** --- - Get started with dependencies and BOM configuration. + Understand the architecture, and choose between modern and legacy. - [:octicons-arrow-right-24: Quickstart](quickstart.md) + [:octicons-arrow-right-24: Overview](overview.md) -- :material-sitemap:{ .lg .middle } **Overview** +- :rocket:{ .lg .middle } **Quickstart** --- - Understand the architecture, and choose between classic and modern. + Get started with dependencies and BOM configuration. - [:octicons-arrow-right-24: Overview](overview.md) + [:octicons-arrow-right-24: Quickstart](quickstart.md) -- :material-monitor:{ .lg .middle } **MCP Client (Classic)** +- :material-monitor:{ .lg .middle } **MCP Client (Modern)** --- - Create and configure clients for servers of the 2025-11-25 revision and earlier. + Call stateless servers of the 2026-07-28 revision. - [:octicons-arrow-right-24: Client](client.md) + [:octicons-arrow-right-24: Client](client-modern.md) -- :material-server:{ .lg .middle } **MCP Server (Classic)** +- :material-server:{ .lg .middle } **MCP Server (Modern)** --- - Implement and configure servers for the 2025-11-25 revision and earlier. + Build stateless servers for the 2026-07-28 revision. - [:octicons-arrow-right-24: Server](server.md) + [:octicons-arrow-right-24: Server](server-modern.md) -- :material-monitor:{ .lg .middle } **MCP Client (Modern)** +- :material-monitor:{ .lg .middle } **MCP Client (Legacy)** --- - Call stateless servers of the 2026-07-28 revision. + Create and configure clients for servers of the 2025-11-25 revision and earlier. - [:octicons-arrow-right-24: Client](client-modern.md) + [:octicons-arrow-right-24: Client](client.md) -- :material-server:{ .lg .middle } **MCP Server (Modern)** +- :material-server:{ .lg .middle } **MCP Server (Legacy)** --- - Build stateless servers for the 2026-07-28 revision. - - [:octicons-arrow-right-24: Server](server-modern.md) + Implement and configure servers for the 2025-11-25 revision and earlier. + [:octicons-arrow-right-24: Server](server.md)
diff --git a/docs/overview.md b/docs/overview.md index 41eba215d..3b3af75bf 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -9,94 +9,95 @@ The MCP Java SDK lets Java applications act as MCP clients and MCP servers. It r The SDK contains two implementations of the client and the server, one for each lifecycle model of the protocol: -- **Classic**, for the 2025-11-25 specification and earlier revisions. Client and server set up a session with an `initialize` handshake. - **Modern**, for the 2026-07-28 specification. There is no handshake and no session; every request stands alone. +- **Legacy**, for the 2025-11-25 specification and earlier revisions. Client and server set up a session with an `initialize` handshake. +The names come from the [MCP specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning). Legacy does not mean deprecated here: the SDK supports both generations. Both ship in the same `mcp-core` module, and an application can use both side by side. -## Classic or Modern? +## Modern or Legacy? -Choose by what the other side speaks. A classic client cannot call a modern server, and a modern client cannot call a classic server. +Choose by what the other side speaks. A modern client cannot call a legacy server, and a legacy client cannot call a modern server. -| | Classic | Modern | +| | Modern | Legacy | |---|---|---| -| Spec revision | 2025-11-25 and earlier | 2026-07-28 | -| Package | `io.modelcontextprotocol.client`, `io.modelcontextprotocol.server` | `io.modelcontextprotocol.modern` | -| Lifecycle | `initialize` handshake, then a session | None: every request carries the protocol version and capabilities | -| Requests from the server | Sampling, elicitation, roots, ping | None: the server answers "input required" and the client sends the request again | -| Client instances | One per server | One per user, sharing a transport | -| Scaling a server | Sessions live in the server process, except with the stateless server type | Any instance can answer any request | -| Transports | STDIO, Streamable HTTP, SSE (legacy) | STDIO, Streamable HTTP | -| Spring transports | Yes, in [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ | Not yet | -| Guides | [Client](client.md), [Server](server.md) | [Client](client-modern.md), [Server](server-modern.md) | +| Spec revision | 2026-07-28 | 2025-11-25 and earlier | +| Package | `io.modelcontextprotocol.modern` | `io.modelcontextprotocol.client`, `io.modelcontextprotocol.server` | +| Lifecycle | None: every request carries the protocol version and capabilities | `initialize` handshake, then a session | +| Requests from the server | None: the server answers "input required" and the client sends the request again | Sampling, elicitation, roots, ping | +| Client instances | One per user, sharing a transport | One per server | +| Scaling a server | Any instance can answer any request | Sessions live in the server process, except with the stateless server type | +| Transports | STDIO, Streamable HTTP | STDIO, Streamable HTTP, SSE (deprecated) | +| Spring transports | Yes, in [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.1+ | Yes, in [Spring AI](https://docs.spring.io/spring-ai/reference/2.0.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ | +| Guides | [Client](client-modern.md), [Server](server-modern.md) | [Client](client.md), [Server](server.md) | -## Classic Architecture +## Modern Architecture -The classic client and server keep a session between them, set up with an `initialize` handshake: +The 2026-07-28 revision removes sessions and the `initialize` handshake. The modern client and server are built around that and share no types with the legacy ones: -![MCP Stack Architecture](images/mcp-stack.svg) +![MCP stack for the 2026-07-28 revision](images/mcp-stack-modern.svg) -- **Client/Server Layer (McpClient/McpServer)**: The API you program against, in sync and async variants. McpClient handles the client-side protocol operations and McpServer the server-side ones. -- **Session Layer (McpSession)**: Manages communication patterns and state. It matches requests with responses, in both directions, for as long as the connection lasts. -- **Transport Layer (McpTransport)**: Handles JSON-RPC message serialization and delivery over: - - STDIO (stdin/stdout) +- **Client/Server Layer (McpClient/McpServer)**: The client turns each call into a self-contained request. The server dispatches each request to a feature, which calls a repository you implement. +- **No Session Layer**: Every request carries the protocol version and the client's capabilities in its `_meta`, so any server instance can answer any request. +- **Transport Layer**: Carries one request and its answer, which is a single response or a stream of notifications followed by the response, over: + - STDIO - Streamable HTTP (JDK `HttpClient` on the client, Servlet on the server) - - SSE, the legacy HTTP transport (JDK `HttpClient` on the client, Servlet on the server) - - 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+) ### Client -An application creates one MCP client per server it uses. Each client holds its own session: to a remote server over Streamable HTTP or SSE, or to a local server process over STDIO. +A modern MCP client stands for one user. An application creates one client per user, and the clients of a server share one transport. Each call carries its user's context to the transport, which adds it to the request. -![Java MCP Client Architecture](images/java-mcp-client-architecture.svg) +![Modern Java MCP Client Architecture](images/java-mcp-client-architecture-modern.svg) ### Server -A classic MCP server keeps one session per connected client, inside the server process. Over HTTP it runs in a web container. Over STDIO the client starts it as a process of its own. +A modern MCP server keeps nothing between requests. Over Streamable HTTP, any number of identical instances can serve the same clients without sticky routing. Over STDIO the client starts the server as a process of its own. -![Java MCP Server Architecture](images/java-mcp-server-architecture.svg) +![Modern Java MCP Server Architecture](images/java-mcp-server-architecture-modern.svg) ### Key Interactions -- **Initialization**: Transport setup, protocol compatibility check, capability negotiation, and implementation details exchange. -- **Requests from the client**: Tool discovery and calls, resource discovery and reading, prompts, and completions. -- **Requests from the server**: Sampling, elicitation and roots listing, sent to the client over the same session. -- **Notifications**: List changes, resource updates, progress, and logging. +- **No Initialization**: Building a client does no I/O. The first request goes out when the first call starts. +- **Requests from the client**: Tool discovery and calls, resource discovery and reading, prompts, completions, and `server/discover` for the server's capabilities. +- **Input from the user**: The server cannot send requests to the client. A server that needs input answers with an `input_required` result, and the client sends the request again with the answers. +- **Change notifications**: The client opens a `subscriptions/listen` stream, and the server reports list changes and resource updates on it. -See [MCP Client](client.md) and [MCP Server](server.md). +See [MCP Client (2026-07-28)](client-modern.md) and [MCP Server (2026-07-28)](server-modern.md). -## Modern Architecture +## Legacy Architecture -The 2026-07-28 revision removes sessions and the `initialize` handshake. The modern client and server are built around that and share no types with the classic ones: +The legacy client and server keep a session between them, set up with an `initialize` handshake: -![MCP stack for the 2026-07-28 revision](images/mcp-stack-modern.svg) +![MCP Stack Architecture](images/mcp-stack.svg) -- **Client/Server Layer (McpClient/McpServer)**: The client turns each call into a self-contained request. The server dispatches each request to a feature, which calls a repository you implement. -- **No Session Layer**: Every request carries the protocol version and the client's capabilities in its `_meta`, so any server instance can answer any request. -- **Transport Layer**: Carries one request and its answer, which is a single response or a stream of notifications followed by the response, over: - - STDIO +- **Client/Server Layer (McpClient/McpServer)**: The API you program against, in sync and async variants. McpClient handles the client-side protocol operations and McpServer the server-side ones. +- **Session Layer (McpSession)**: Manages communication patterns and state. It matches requests with responses, in both directions, for as long as the connection lasts. +- **Transport Layer (McpTransport)**: Handles JSON-RPC message serialization and delivery over: + - STDIO (stdin/stdout) - Streamable HTTP (JDK `HttpClient` on the client, Servlet on the server) + - SSE, the deprecated HTTP transport (JDK `HttpClient` on the client, Servlet on the server) + - Spring WebFlux and Spring WebMVC transports (available in [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+) ### Client -A modern MCP client stands for one user. An application creates one client per user, and the clients of a server share one transport. Each call carries its user's context to the transport, which adds it to the request. +An application creates one MCP client per server it uses. Each client holds its own session: to a remote server over Streamable HTTP or SSE, or to a local server process over STDIO. -![Modern Java MCP Client Architecture](images/java-mcp-client-architecture-modern.svg) +![Java MCP Client Architecture](images/java-mcp-client-architecture.svg) ### Server -A modern MCP server keeps nothing between requests. Over Streamable HTTP, any number of identical instances can serve the same clients without sticky routing. Over STDIO the client starts the server as a process of its own. +A legacy MCP server keeps one session per connected client, inside the server process. Over HTTP it runs in a web container. Over STDIO the client starts it as a process of its own. -![Modern Java MCP Server Architecture](images/java-mcp-server-architecture-modern.svg) +![Java MCP Server Architecture](images/java-mcp-server-architecture.svg) ### Key Interactions -- **No Initialization**: Building a client does no I/O. The first request goes out when the first call starts. -- **Requests from the client**: Tool discovery and calls, resource discovery and reading, prompts, completions, and `server/discover` for the server's capabilities. -- **Input from the user**: The server cannot send requests to the client. A server that needs input answers with an `input_required` result, and the client sends the request again with the answers. -- **Change notifications**: The client opens a `subscriptions/listen` stream, and the server reports list changes and resource updates on it. +- **Initialization**: Transport setup, protocol compatibility check, capability negotiation, and implementation details exchange. +- **Requests from the client**: Tool discovery and calls, resource discovery and reading, prompts, and completions. +- **Requests from the server**: Sampling, elicitation and roots listing, sent to the client over the same session. +- **Notifications**: List changes, resource updates, progress, and logging. -See [MCP Client (2026-07-28)](client-modern.md) and [MCP Server (2026-07-28)](server-modern.md). +See [MCP Client](client.md) and [MCP Server](server.md). ## Module Structure @@ -105,7 +106,7 @@ The SDK is organized into modules to separate concerns and allow adopters to bri | Module | Purpose | |--------|---------| | `mcp-bom` | Bill of Materials for dependency management | -| `mcp-core` | The classic and modern clients and servers, with the STDIO, JDK HttpClient and Servlet transports | +| `mcp-core` | The modern and legacy clients and servers, with the STDIO, JDK HttpClient and Servlet transports | | `mcp-json-jackson2` | Jackson 2.x JSON serialization implementation | | `mcp-json-jackson3` | Jackson 3.x JSON serialization implementation | | `mcp` | Convenience bundle (`mcp-core` + `mcp-json-jackson3`) | @@ -114,7 +115,7 @@ The SDK is organized into modules to separate concerns and allow adopters to bri !!! tip A minimal adopter may depend only on `mcp` (core + Jackson 3). - Spring-based applications that use the classic client or server 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`), which are no longer part of this SDK. + Spring-based applications that use the legacy client or server should use the `mcp-spring-webflux` or `mcp-spring-webmvc` artifacts from [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`), which are no longer part of this SDK. ## Next Steps @@ -136,36 +137,36 @@ The SDK is organized into modules to separate concerns and allow adopters to bri [:octicons-arrow-right-24: Repository](https://github.com/modelcontextprotocol/java-sdk) -- :material-monitor:{ .lg .middle } **MCP Client (Classic)** +- :material-monitor:{ .lg .middle } **MCP Client (Modern)** --- - Create and configure clients for servers of the 2025-11-25 revision and earlier. + Call stateless servers of the 2026-07-28 revision. - [:octicons-arrow-right-24: Client](client.md) + [:octicons-arrow-right-24: Client](client-modern.md) -- :material-server:{ .lg .middle } **MCP Server (Classic)** +- :material-server:{ .lg .middle } **MCP Server (Modern)** --- - Implement and configure servers for the 2025-11-25 revision and earlier. + Build stateless servers for the 2026-07-28 revision. - [:octicons-arrow-right-24: Server](server.md) + [:octicons-arrow-right-24: Server](server-modern.md) -- :material-monitor:{ .lg .middle } **MCP Client (Modern)** +- :material-monitor:{ .lg .middle } **MCP Client (Legacy)** --- - Call stateless servers of the 2026-07-28 revision. + Create and configure clients for servers of the 2025-11-25 revision and earlier. - [:octicons-arrow-right-24: Client](client-modern.md) + [:octicons-arrow-right-24: Client](client.md) -- :material-server:{ .lg .middle } **MCP Server (Modern)** +- :material-server:{ .lg .middle } **MCP Server (Legacy)** --- - Build stateless servers for the 2026-07-28 revision. + Implement and configure servers for the 2025-11-25 revision and earlier. - [:octicons-arrow-right-24: Server](server-modern.md) + [:octicons-arrow-right-24: Server](server.md) diff --git a/docs/quickstart.md b/docs/quickstart.md index 0cf33f5f1..85ba3a6a2 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -63,7 +63,7 @@ Add the `mcp` module, which bundles `mcp-core` with Jackson 3.x JSON serializati } ``` -This is all a client needs. It includes the classic and the modern client and server, with their STDIO and HTTP transports, and requires no web framework. +This is all a client needs. It includes the modern and the legacy client and server, with their STDIO and HTTP transports, and requires no web framework. !!! note "HTTP servers" The HTTP server transports are built on the Servlet API, which the SDK does not bring in. Run them in a Servlet container, such as Tomcat or Jetty, with async support enabled. @@ -96,7 +96,7 @@ This is all a client needs. It includes the classic and the modern client and se **Your own JSON implementation.** Depend on `mcp-core` alone and provide the JSON binding yourself. -**Spring applications.** Spring WebFlux and WebMVC transports for the classic client and server are 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`), not of this SDK: +**Spring applications.** Spring WebFlux and WebMVC transports for the legacy client and server are part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`), not of this SDK: === "Maven" @@ -130,9 +130,35 @@ With the `spring-ai-bom` or the Spring AI starters (`spring-ai-starter-mcp-serve ## Your First Client -The SDK has two clients, one for each generation of the protocol; see [Classic or Modern?](overview.md#classic-or-modern) to choose. Both examples list the tools of a server at `http://localhost:8080/mcp` and call one of them. +The SDK has two clients, one for each generation of the protocol; see [Modern or Legacy?](overview.md#modern-or-legacy) to choose. Both examples list the tools of a server at `http://localhost:8080/mcp` and call one of them. -=== "Classic" +=== "Modern" + + For servers of the 2026-07-28 specification. There is nothing to initialize and nothing to close on the client: + + ```java + import java.util.Map; + + import io.modelcontextprotocol.modern.McpSchema.CallToolResult; + import io.modelcontextprotocol.modern.client.McpClient; + import io.modelcontextprotocol.modern.client.McpSyncClient; + import io.modelcontextprotocol.modern.client.transport.HttpClientMcpTransport; + + McpSyncClient client = McpClient.sync( + HttpClientMcpTransport.builder("http://localhost:8080/mcp").build()) + .build(); + + client.listTools().list().tools().forEach(tool -> System.out.println(tool.name())); + + CallToolResult result = client.tool("echo") + .arguments(Map.of("text", "Hello")) + .call(); + System.out.println(result.content()); + ``` + + Continue with the [MCP Client (2026-07-28)](client-modern.md) guide. + +=== "Legacy" For servers of the 2025-11-25 specification and earlier. The client sets up a session with `initialize()`: @@ -162,33 +188,7 @@ The SDK has two clients, one for each generation of the protocol; see [Classic o Continue with the [MCP Client](client.md) guide. -=== "Modern" - - For servers of the 2026-07-28 specification. There is nothing to initialize and nothing to close on the client: - - ```java - import java.util.Map; - - import io.modelcontextprotocol.modern.McpSchema.CallToolResult; - import io.modelcontextprotocol.modern.client.McpClient; - import io.modelcontextprotocol.modern.client.McpSyncClient; - import io.modelcontextprotocol.modern.client.transport.HttpClientMcpTransport; - - McpSyncClient client = McpClient.sync( - HttpClientMcpTransport.builder("http://localhost:8080/mcp").build()) - .build(); - - client.listTools().list().tools().forEach(tool -> System.out.println(tool.name())); - - CallToolResult result = client.tool("echo") - .arguments(Map.of("text", "Hello")) - .call(); - System.out.println(result.content()); - ``` - - Continue with the [MCP Client (2026-07-28)](client-modern.md) guide. - -To build the server side, see the [MCP Server](server.md) guide for the classic server and the [Quick Start](server-modern.md#quick-start) of the modern server guide, which serves the `echo` tool the modern example calls. +To build the server side, see the [Quick Start](server-modern.md#quick-start) of the modern server guide, which serves the `echo` tool the modern example calls, and the [MCP Server](server.md) guide for the legacy server. ## Available Dependencies @@ -197,9 +197,9 @@ The BOM manages the following modules, all in the group `io.modelcontextprotocol | Module | Purpose | |--------|---------| | `mcp` | Convenience bundle that combines `mcp-core` with `mcp-json-jackson3`. | -| `mcp-core` | The classic and modern clients and servers, with the STDIO, JDK HttpClient and Servlet transports. JSON binding is pluggable. | +| `mcp-core` | The modern and legacy clients and servers, with the STDIO, JDK HttpClient and Servlet transports. JSON binding is pluggable. | | `mcp-json-jackson3` | Jackson 3.x JSON serialization (included in `mcp`). | | `mcp-json-jackson2` | Jackson 2.x JSON serialization, for projects that need Jackson 2.x. | | `mcp-test` | Testing utilities and support for MCP-based applications. | -The Spring transports, `mcp-spring-webflux` and `mcp-spring-webmvc`, are published by [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ in the group `org.springframework.ai`. +The Spring transports, `mcp-spring-webflux` and `mcp-spring-webmvc`, are published by [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ in the group `org.springframework.ai`. diff --git a/docs/server-modern.md b/docs/server-modern.md index 7ee3435b3..11a0cf1c3 100644 --- a/docs/server-modern.md +++ b/docs/server-modern.md @@ -8,14 +8,14 @@ description: Build a stateless MCP server for the 2026-07-28 specification revis !!! note The modern server is available since 2.2.0. Its API, in the `io.modelcontextprotocol.modern` packages, may change in minor releases until 3.0, and [some parts of the revision](#not-yet-supported) are not - implemented. For production servers that talk to today's clients, use the [classic server](server.md). + implemented. For production servers that talk to today's clients, use the [legacy server](server.md). To call a modern server from Java, see the [modern client](client-modern.md). ## Overview The modern server implements the stateless [2026-07-28 MCP specification revision](https://modelcontextprotocol.io/specification/2026-07-28). -It lives in `io.modelcontextprotocol.modern.server`, next to the classic server, and shares no types with it: +It lives in `io.modelcontextprotocol.modern.server`, next to the legacy server, and shares no types with it: it has its own `McpServer`, its own `McpSchema`, and its own transports. Both ship in `mcp-core`, so no extra dependency is needed. @@ -34,79 +34,140 @@ server is built around that: - **Stream only when needed.** A handler returns a single result (sent as `application/json`) or a streaming body that can send progress before the result (sent as an SSE stream). -### Classic or modern? +### Modern or legacy? -| | [Classic server](server.md) | Modern server | +| | Modern server | [Legacy server](server.md) | |---|---|---| -| Package | `io.modelcontextprotocol.server` | `io.modelcontextprotocol.modern.server` | -| Spec revision | 2025-11-25 and earlier | 2026-07-28 | -| Sessions | Yes (except the stateless transport) | No | -| Primitives | Registered specifications | Repositories you implement | -| Server-to-client requests | Sampling, elicitation, roots, ping | Replaced by `InputRequiredResult` | -| Logging | Supported | Not supported (deprecated by the revision) | +| Package | `io.modelcontextprotocol.modern.server` | `io.modelcontextprotocol.server` | +| Spec revision | 2026-07-28 | 2025-11-25 and earlier | +| Sessions | No | Yes (except the stateless transport) | +| Primitives | Repositories you implement | Registered specifications | +| Server-to-client requests | Replaced by `InputRequiredResult` | Sampling, elicitation, roots, ping | +| Logging | Not supported (deprecated by the revision) | Supported | + By default the modern server only accepts the 2026-07-28 protocol version; change the set with `supportedVersions(...)` on the builder. A request declaring another version gets an `Unsupported protocol version` error listing the versions the server supports. Clients that start with an -`initialize` handshake are not supported; serve those with the classic server. +`initialize` handshake are not supported; serve those with the legacy server. ## Quick Start A server with one tool, served over HTTP: -```java -import java.util.List; -import java.util.Map; - -import io.modelcontextprotocol.modern.McpSchema.CallToolResult; -import io.modelcontextprotocol.modern.McpSchema.Implementation; -import io.modelcontextprotocol.modern.McpSchema.TextContent; -import io.modelcontextprotocol.modern.McpSchema.Tool; -import io.modelcontextprotocol.modern.server.McpRequestContext; -import io.modelcontextprotocol.modern.server.McpServer; -import io.modelcontextprotocol.modern.server.McpSyncResponse; -import io.modelcontextprotocol.modern.server.feature.McpSyncTool; -import io.modelcontextprotocol.modern.server.feature.McpSyncToolRepository; -import io.modelcontextprotocol.modern.server.feature.ToolsFeature; -import io.modelcontextprotocol.modern.server.feature.ToolsPage; -import io.modelcontextprotocol.modern.server.transport.HttpServletMcpTransport; - -class EchoTools implements McpSyncToolRepository { - - private final McpSyncTool echo = McpSyncTool.of( - Tool.builder("echo", Map.of( - "type", "object", - "properties", Map.of("text", Map.of("type", "string")), - "required", List.of("text"))) - .description("Echoes the given text") - .build(), - (ctx, request) -> McpSyncResponse.result(CallToolResult.builder() - .addContent(TextContent.builder((String) request.arguments().get("text")).build()) - .build())); +=== "Sync" + + ```java + import java.util.List; + import java.util.Map; + + import io.modelcontextprotocol.modern.McpSchema.CallToolResult; + import io.modelcontextprotocol.modern.McpSchema.Implementation; + import io.modelcontextprotocol.modern.McpSchema.TextContent; + import io.modelcontextprotocol.modern.McpSchema.Tool; + import io.modelcontextprotocol.modern.server.McpRequestContext; + import io.modelcontextprotocol.modern.server.McpServer; + import io.modelcontextprotocol.modern.server.McpSyncResponse; + import io.modelcontextprotocol.modern.server.feature.McpSyncTool; + import io.modelcontextprotocol.modern.server.feature.McpSyncToolRepository; + import io.modelcontextprotocol.modern.server.feature.ToolsFeature; + import io.modelcontextprotocol.modern.server.feature.ToolsPage; + import io.modelcontextprotocol.modern.server.transport.HttpServletMcpTransport; + + class EchoTools implements McpSyncToolRepository { + + private final McpSyncTool echo = McpSyncTool.of( + Tool.builder("echo", Map.of( + "type", "object", + "properties", Map.of("text", Map.of("type", "string")), + "required", List.of("text"))) + .description("Echoes the given text") + .build(), + (ctx, request) -> McpSyncResponse.result(CallToolResult.builder() + .addContent(TextContent.builder((String) request.arguments().get("text")).build()) + .build())); - @Override - public ToolsPage list(McpRequestContext ctx, String cursor) { - return ToolsPage.of(List.of(this.echo.definition())); - } + @Override + public ToolsPage list(McpRequestContext ctx, String cursor) { + return ToolsPage.of(List.of(this.echo.definition())); + } + + @Override + public McpSyncTool find(McpRequestContext ctx, String name) { + return "echo".equals(name) ? this.echo : null; + } - @Override - public McpSyncTool find(McpRequestContext ctx, String name) { - return "echo".equals(name) ? this.echo : null; } -} + McpServer server = McpServer.builder() + .serverInfo(Implementation.builder("echo-server", "1.0.0").build()) + .feature(ToolsFeature.ofSync(new EchoTools())) + .build(); -McpServer server = McpServer.builder() - .serverInfo(Implementation.builder("echo-server", "1.0.0").build()) - .feature(ToolsFeature.ofSync(new EchoTools())) - .build(); + HttpServletMcpTransport transport = HttpServletMcpTransport.builder(server) + .endpoint("/mcp") + .build(); -HttpServletMcpTransport transport = HttpServletMcpTransport.builder(server) - .endpoint("/mcp") - .build(); + // Register `transport` as a servlet in your container, mapped to /mcp, + // with async support enabled. + ``` + +=== "Async" + + ```java + import java.util.List; + import java.util.Map; + + import io.modelcontextprotocol.modern.McpSchema.CallToolResult; + import io.modelcontextprotocol.modern.McpSchema.Implementation; + import io.modelcontextprotocol.modern.McpSchema.TextContent; + import io.modelcontextprotocol.modern.McpSchema.Tool; + import io.modelcontextprotocol.modern.server.McpAsyncResponse; + import io.modelcontextprotocol.modern.server.McpRequestContext; + import io.modelcontextprotocol.modern.server.McpServer; + import io.modelcontextprotocol.modern.server.feature.McpAsyncTool; + import io.modelcontextprotocol.modern.server.feature.McpAsyncToolRepository; + import io.modelcontextprotocol.modern.server.feature.ToolsFeature; + import io.modelcontextprotocol.modern.server.feature.ToolsPage; + import io.modelcontextprotocol.modern.server.transport.HttpServletMcpTransport; + import reactor.core.publisher.Mono; + + class EchoTools implements McpAsyncToolRepository { + + private final McpAsyncTool echo = McpAsyncTool.of( + Tool.builder("echo", Map.of( + "type", "object", + "properties", Map.of("text", Map.of("type", "string")), + "required", List.of("text"))) + .description("Echoes the given text") + .build(), + (ctx, request) -> Mono.just(McpAsyncResponse.result(CallToolResult.builder() + .addContent(TextContent.builder((String) request.arguments().get("text")).build()) + .build()))); + + @Override + public Mono list(McpRequestContext ctx, String cursor) { + return Mono.just(ToolsPage.of(List.of(this.echo.definition()))); + } -// Register `transport` as a servlet in your container, mapped to /mcp, -// with async support enabled. -``` + @Override + public Mono find(McpRequestContext ctx, String name) { + return "echo".equals(name) ? Mono.just(this.echo) : Mono.empty(); + } + + } + + McpServer server = McpServer.builder() + .serverInfo(Implementation.builder("echo-server", "1.0.0").build()) + .feature(ToolsFeature.ofAsync(new EchoTools())) + .build(); + + HttpServletMcpTransport transport = HttpServletMcpTransport.builder(server) + .endpoint("/mcp") + .build(); + + // Register `transport` as a servlet in your container, mapped to /mcp, + // with async support enabled. + ``` `McpServer` is immutable and stateless. `build()` collects the capabilities of the registered features and serves `server/discover` from them. There is nothing to start; on shutdown, `server.closeGracefully()` ends @@ -255,34 +316,62 @@ To paginate, return a page with a `nextCursor`, for example `new ToolsPage(tools ### Tools -A repository lists tool definitions and finds a tool by name. `find` returns an `McpSyncTool`, which pairs a +A repository lists tool definitions and finds a tool by name. `find` returns an `McpSyncTool` (an +`McpAsyncTool` in an async repository), which pairs a `Tool` definition with its handler, or `null` if there is no such tool (the client then gets an `Unknown tool` error). -```java -McpSyncTool weather = McpSyncTool.of( - Tool.builder("get_weather", jsonMapper, """ - { - "type": "object", - "properties": { "city": { "type": "string" } }, - "required": ["city"] - } - """) - .outputSchema(jsonMapper, """ - { - "type": "object", - "properties": { "temperature": { "type": "number" } }, - "required": ["temperature"] - } - """) - .build(), - (ctx, request) -> { - double temperature = weatherService.temperature((String) request.arguments().get("city")); - return McpSyncResponse.result(CallToolResult.builder() - .structuredContent(Map.of("temperature", temperature)) - .build()); - }); -``` +=== "Sync" + + ```java + McpSyncTool weather = McpSyncTool.of( + Tool.builder("get_weather", jsonMapper, """ + { + "type": "object", + "properties": { "city": { "type": "string" } }, + "required": ["city"] + } + """) + .outputSchema(jsonMapper, """ + { + "type": "object", + "properties": { "temperature": { "type": "number" } }, + "required": ["temperature"] + } + """) + .build(), + (ctx, request) -> { + double temperature = weatherService.temperature((String) request.arguments().get("city")); + return McpSyncResponse.result(CallToolResult.builder() + .structuredContent(Map.of("temperature", temperature)) + .build()); + }); + ``` + +=== "Async" + + ```java + McpAsyncTool weather = McpAsyncTool.of( + Tool.builder("get_weather", jsonMapper, """ + { + "type": "object", + "properties": { "city": { "type": "string" } }, + "required": ["city"] + } + """) + .outputSchema(jsonMapper, """ + { + "type": "object", + "properties": { "temperature": { "type": "number" } }, + "required": ["temperature"] + } + """) + .build(), + (ctx, request) -> weatherService.temperature((String) request.arguments().get("city")) + .map(temperature -> McpAsyncResponse.result(CallToolResult.builder() + .structuredContent(Map.of("temperature", temperature)) + .build()))); + ``` The tools feature validates both directions: @@ -301,41 +390,79 @@ A resource repository lists resources and, optionally, resource templates, and f `find` returns an `McpSyncResource` holding the read handler, or `null` if the URI is unknown. Templates are not matched for you: `find` receives every URI and decides what it serves. -```java -class FileResources implements McpSyncResourceRepository { +=== "Sync" - @Override - public ResourcesPage list(McpRequestContext ctx, String cursor) { - return ResourcesPage.of(List.of( - Resource.builder("file:///config.json", "Configuration") - .mimeType("application/json") - .build())); - } + ```java + class FileResources implements McpSyncResourceRepository { - @Override - public ResourceTemplatesPage listTemplates(McpRequestContext ctx, String cursor) { - return ResourceTemplatesPage.of(List.of( - ResourceTemplate.builder("file:///logs/{date}", "Daily log").build())); - } + @Override + public ResourcesPage list(McpRequestContext ctx, String cursor) { + return ResourcesPage.of(List.of( + Resource.builder("file:///config.json", "Configuration") + .mimeType("application/json") + .build())); + } - @Override - public McpSyncResource find(McpRequestContext ctx, String uri) { - String content = files.read(uri); // null if the URI is unknown - if (content == null) { - return null; + @Override + public ResourceTemplatesPage listTemplates(McpRequestContext ctx, String cursor) { + return ResourceTemplatesPage.of(List.of( + ResourceTemplate.builder("file:///logs/{date}", "Daily log").build())); + } + + @Override + public McpSyncResource find(McpRequestContext ctx, String uri) { + String content = files.read(uri); // null if the URI is unknown + if (content == null) { + return null; + } + return McpSyncResource.of((c, request) -> McpSyncResponse.result(ReadResourceResult + .builder(List.of(TextResourceContents.builder(uri, content).build())) + .build())); + } + + @Override + public boolean supportsSubscribe() { + return true; // see Change Notifications } - return McpSyncResource.of((c, request) -> McpSyncResponse.result(ReadResourceResult - .builder(List.of(TextResourceContents.builder(uri, content).build())) - .build())); - } - @Override - public boolean supportsSubscribe() { - return true; // see Change Notifications } + ``` -} -``` +=== "Async" + + ```java + class FileResources implements McpAsyncResourceRepository { + + @Override + public Mono list(McpRequestContext ctx, String cursor) { + return Mono.just(ResourcesPage.of(List.of( + Resource.builder("file:///config.json", "Configuration") + .mimeType("application/json") + .build()))); + } + + @Override + public Mono listTemplates(McpRequestContext ctx, String cursor) { + return Mono.just(ResourceTemplatesPage.of(List.of( + ResourceTemplate.builder("file:///logs/{date}", "Daily log").build()))); + } + + @Override + public Mono find(McpRequestContext ctx, String uri) { + // files.read(uri) is empty if the URI is unknown + return files.read(uri) + .map(content -> McpAsyncResource.of((c, request) -> Mono.just(McpAsyncResponse.result(ReadResourceResult + .builder(List.of(TextResourceContents.builder(uri, content).build())) + .build())))); + } + + @Override + public boolean supportsSubscribe() { + return true; // see Change Notifications + } + + } + ``` `listTemplates`, `findTemplate`, `supportsSubscribe` and `supportsCompletion` have defaults: no templates, no subscriptions and no completion. `findTemplate` is only needed to [complete](#completions) template variables. @@ -349,47 +476,92 @@ A prompt repository lists `Prompt` definitions and finds an `McpSyncPrompt` by n with a handler returning a `GetPromptResult`. Required prompt arguments declared in the definition are checked before the handler runs; a missing one is answered with an invalid-params error. -```java -McpSyncPrompt review = McpSyncPrompt.of( - Prompt.builder("code_review") - .description("Review a piece of code") - .arguments(List.of(PromptArgument.builder("code").required(true).build())) - .build(), - (ctx, request) -> McpSyncResponse.result(GetPromptResult - .builder(List.of(PromptMessage - .builder(Role.USER, TextContent.builder("Please review:\n" + request.arguments().get("code")).build()) - .build())) - .build())); -``` +=== "Sync" + + ```java + McpSyncPrompt review = McpSyncPrompt.of( + Prompt.builder("code_review") + .description("Review a piece of code") + .arguments(List.of(PromptArgument.builder("code").required(true).build())) + .build(), + (ctx, request) -> McpSyncResponse.result(GetPromptResult + .builder(List.of(PromptMessage + .builder(Role.USER, TextContent.builder("Please review:\n" + request.arguments().get("code")).build()) + .build())) + .build())); + ``` + +=== "Async" + + ```java + McpAsyncPrompt review = McpAsyncPrompt.of( + Prompt.builder("code_review") + .description("Review a piece of code") + .arguments(List.of(PromptArgument.builder("code").required(true).build())) + .build(), + (ctx, request) -> Mono.just(McpAsyncResponse.result(GetPromptResult + .builder(List.of(PromptMessage + .builder(Role.USER, TextContent.builder("Please review:\n" + request.arguments().get("code")).build()) + .build())) + .build()))); + ``` ### Completions Completion suggests values for a prompt argument or a resource template variable. The completion handler belongs to the prompt or template it completes: -```java -McpSyncPrompt review = McpSyncPrompt.of(definition, handler) - .withCompletionHandler((ctx, request) -> CompleteResult.of(new CompleteResult.Completion( - languages.stream().filter(l -> l.startsWith(request.argument().value())).toList()))); +=== "Sync" -McpSyncResourceTemplate logs = McpSyncResourceTemplate.of(ResourceTemplate.builder("file:///logs/{date}", "Daily log").build()) - .withCompletionHandler((ctx, request) -> CompleteResult.of(new CompleteResult.Completion(logDates()))); -``` + ```java + McpSyncPrompt review = McpSyncPrompt.of(definition, handler) + .withCompletionHandler((ctx, request) -> CompleteResult.of(new CompleteResult.Completion( + languages.stream().filter(l -> l.startsWith(request.argument().value())).toList()))); + + McpSyncResourceTemplate logs = McpSyncResourceTemplate.of(ResourceTemplate.builder("file:///logs/{date}", "Daily log").build()) + .withCompletionHandler((ctx, request) -> CompleteResult.of(new CompleteResult.Completion(logDates()))); + ``` + +=== "Async" + + ```java + McpAsyncPrompt review = McpAsyncPrompt.of(definition, handler) + .withCompletionHandler((ctx, request) -> Mono.just(CompleteResult.of(new CompleteResult.Completion( + languages.stream().filter(l -> l.startsWith(request.argument().value())).toList())))); + + McpAsyncResourceTemplate logs = McpAsyncResourceTemplate.of(ResourceTemplate.builder("file:///logs/{date}", "Daily log").build()) + .withCompletionHandler((ctx, request) -> Mono.just(CompleteResult.of(new CompleteResult.Completion(logDates())))); + ``` A prompt repository returns such a prompt from `find`. A resource repository returns such a template from `findTemplate(ctx, uriTemplate)`, which is looked up by the exact `uriTemplate` string the client sends in a `ref/resource` reference. Either repository opts in by returning `true` from `supportsCompletion()`: -```java -class Prompts implements McpSyncPromptRepository { - // list, find ... +=== "Sync" - @Override - public boolean supportsCompletion() { - return true; + ```java + class Prompts implements McpSyncPromptRepository { + // list, find ... + + @Override + public boolean supportsCompletion() { + return true; + } } -} -``` + ``` + +=== "Async" + + ```java + class Prompts implements McpAsyncPromptRepository { + // list, find ... + + @Override + public boolean supportsCompletion() { + return true; + } + } + ``` The server then serves `completion/complete` and advertises `completions`, sending `ref/prompt` requests to the prompts feature and `ref/resource` requests to the resources feature. A [custom feature](#custom-features) @@ -414,12 +586,7 @@ the feature; where its code runs depends on the transport: An async repository returns `Mono`s: `list` returns `Mono`, `find` returns `Mono` (empty if there is no such tool), and handlers return `Mono>`. -```java -McpAsyncTool echo = McpAsyncTool.of(echoDefinition, - (ctx, request) -> Mono.just(McpAsyncResponse.result(CallToolResult.builder() - .addContent(TextContent.builder((String) request.arguments().get("text")).build()) - .build()))); -``` +The examples on this page have a Sync and an Async tab. Choosing one switches all of them. ## Request Context @@ -444,17 +611,31 @@ A handler answers with `McpSyncResponse.result(...)` for a single JSON response, `McpSyncResponse.streaming(...)` to send notifications before the result. The streaming body runs once the transport has opened the response stream and receives an `McpSyncNotifier`: -```java -(ctx, request) -> McpSyncResponse.streaming(notifier -> { - for (int i = 1; i <= 10; i++) { - processChunk(i); - notifier.progress(i * 10, 100.0, "Processed chunk " + i); - } - return CallToolResult.builder() - .addContent(TextContent.builder("Done").build()) - .build(); -}) -``` +=== "Sync" + + ```java + (ctx, request) -> McpSyncResponse.streaming(notifier -> { + for (int i = 1; i <= 10; i++) { + processChunk(i); + notifier.progress(i * 10, 100.0, "Processed chunk " + i); + } + return CallToolResult.builder() + .addContent(TextContent.builder("Done").build()) + .build(); + }) + ``` + +=== "Async" + + ```java + (ctx, request) -> Mono.just(McpAsyncResponse.streaming(notifier -> + Flux.range(1, 10) + .concatMap(i -> processChunk(i) + .then(notifier.progress(i * 10, 100.0, "Processed chunk " + i))) + .then(Mono.fromSupplier(() -> CallToolResult.builder() + .addContent(TextContent.builder("Done").build()) + .build())))) + ``` `progress` is a no-op when the request carries no progress token, or when the value does not increase. `notify(method, params)` sends any other request-scoped notification. The async equivalent is @@ -469,11 +650,21 @@ A tool, resource or prompt that only works with clients declaring certain capabi rejects calls from other clients with error `-32021` (missing required client capability), listing what is missing, without running the handler: -```java -ClientCapabilities needsSampling = ClientCapabilities.builder().sampling().build(); +=== "Sync" -McpSyncTool summarize = McpSyncTool.of(summarizeDefinition, needsSampling, handler); -``` + ```java + ClientCapabilities needsSampling = ClientCapabilities.builder().sampling().build(); + + McpSyncTool summarize = McpSyncTool.of(summarizeDefinition, needsSampling, handler); + ``` + +=== "Async" + + ```java + ClientCapabilities needsSampling = ClientCapabilities.builder().sampling().build(); + + McpAsyncTool summarize = McpAsyncTool.of(summarizeDefinition, needsSampling, handler); + ``` `McpSyncResource.of(...)` and `McpSyncPrompt.of(...)` take the same optional argument. @@ -506,24 +697,47 @@ sequenceDiagram The handler runs once per round and keeps nothing in memory between rounds. Whatever it needs to remember travels in `requestState` (see [Carrying State Across Rounds](#carrying-state-across-rounds)). -```java -(ctx, request) -> { - ElicitResult answer = InputResponses.get( - request.inputResponses(), "user_name", ElicitResult.class, jsonMapper); - if (answer == null || answer.action() != ElicitResult.Action.ACCEPT) { - return McpSyncResponse.result(InputRequiredResult.builder() - .elicit("user_name", ElicitFormRequest.builder("What is your name?", Map.of( - "type", "object", - "properties", Map.of("name", Map.of("type", "string")), - "required", List.of("name"))) - .build()) +=== "Sync" + + ```java + (ctx, request) -> { + ElicitResult answer = InputResponses.get( + request.inputResponses(), "user_name", ElicitResult.class, jsonMapper); + if (answer == null || answer.action() != ElicitResult.Action.ACCEPT) { + return McpSyncResponse.result(InputRequiredResult.builder() + .elicit("user_name", ElicitFormRequest.builder("What is your name?", Map.of( + "type", "object", + "properties", Map.of("name", Map.of("type", "string")), + "required", List.of("name"))) + .build()) + .build()); + } + return McpSyncResponse.result(CallToolResult.builder() + .addContent(TextContent.builder("Hello, " + answer.content().get("name") + "!").build()) .build()); } - return McpSyncResponse.result(CallToolResult.builder() - .addContent(TextContent.builder("Hello, " + answer.content().get("name") + "!").build()) - .build()); -} -``` + ``` + +=== "Async" + + ```java + (ctx, request) -> { + ElicitResult answer = InputResponses.get( + request.inputResponses(), "user_name", ElicitResult.class, jsonMapper); + if (answer == null || answer.action() != ElicitResult.Action.ACCEPT) { + return Mono.just(McpAsyncResponse.result(InputRequiredResult.builder() + .elicit("user_name", ElicitFormRequest.builder("What is your name?", Map.of( + "type", "object", + "properties", Map.of("name", Map.of("type", "string")), + "required", List.of("name"))) + .build()) + .build())); + } + return Mono.just(McpAsyncResponse.result(CallToolResult.builder() + .addContent(TextContent.builder("Hello, " + answer.content().get("name") + "!").build()) + .build())); + } + ``` `InputRequiredResult.builder()` offers `elicit(key, ElicitFormRequest)`, `elicitUrl(key, message, url)`, `createMessage(key, CreateMessageRequest)` and `listRoots(key)`. `InputResponses.get(...)` converts a response @@ -539,12 +753,23 @@ capability as [required](#required-client-capabilities) so that other clients ar A handler that needs to remember something between rounds puts it in `requestState(String)`. The client returns it unchanged with the retry, and the handler reads it from `request.requestState()`: -```java -return McpSyncResponse.result(InputRequiredResult.builder() - .elicit("step2", ElicitFormRequest.builder("What is your favorite color?", colorSchema).build()) - .requestState("round-2:" + name) - .build()); -``` +=== "Sync" + + ```java + return McpSyncResponse.result(InputRequiredResult.builder() + .elicit("step2", ElicitFormRequest.builder("What is your favorite color?", colorSchema).build()) + .requestState("round-2:" + name) + .build()); + ``` + +=== "Async" + + ```java + return Mono.just(McpAsyncResponse.result(InputRequiredResult.builder() + .elicit("step2", ElicitFormRequest.builder("What is your favorite color?", colorSchema).build()) + .requestState("round-2:" + name) + .build())); + ``` ### Securing `requestState` @@ -723,7 +948,7 @@ record TaskChanged(String taskId) implements ServerChange { - Tasks. - Logging, which the 2026-07-28 revision deprecates. The modern server never sends log messages. -- Spring WebMVC and WebFlux transports. Spring AI's transports target the classic server. +- Spring WebMVC and WebFlux transports. Spring AI's transports target the legacy server. For a larger working example, see the [modern conformance server](https://github.com/modelcontextprotocol/java-sdk/tree/main/conformance-tests/server-servlet-modern), diff --git a/docs/server.md b/docs/server.md index a77e05d69..1834bfe99 100644 --- a/docs/server.md +++ b/docs/server.md @@ -21,11 +21,11 @@ The MCP Server is a foundational component in the Model Context Protocol (MCP) a !!! 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. + Spring-specific transport implementations (`mcp-spring-webflux`, `mcp-spring-webmvc`) are now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.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.1.0-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html) documentation for Spring-based server setup. !!! note "2026-07-28 specification" - This page covers the classic server, which implements the 2025-11-25 specification and earlier revisions. + This page covers the legacy server, which implements the 2025-11-25 specification and earlier revisions. A stateless server for the 2026-07-28 revision is also available; see [MCP Server (2026-07-28)](server-modern.md). @@ -178,7 +178,7 @@ Key features: === "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`): + Creates WebFlux-based Streamable HTTP server transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`): ```java @Configuration @@ -208,7 +208,7 @@ Key features: === "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`): + Creates WebMvc-based Streamable HTTP server transport. Requires the `mcp-spring-webmvc` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`): ```java @Configuration @@ -230,7 +230,7 @@ Key features: } ``` -### SSE HTTP (Legacy) +### SSE HTTP (Deprecated) === "SSE Servlet" @@ -271,7 +271,7 @@ Key features: === "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`): + Creates WebFlux-based SSE server transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`): ```java @Configuration @@ -297,7 +297,7 @@ Key features: === "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`): + Creates WebMvc-based SSE server transport. Requires the `mcp-spring-webmvc` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`): ```java @Configuration diff --git a/mkdocs.yml b/mkdocs.yml index d889d9b2a..0d8c96609 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,12 +42,12 @@ nav: - Documentation: - Overview: overview.md - Quickstart: quickstart.md - - Classic (2025-11-25 and earlier): - - MCP Client: client.md - - MCP Server: server.md - Modern (2026-07-28): - MCP Client: client-modern.md - MCP Server: server-modern.md + - Legacy (2025-11-25 and earlier): + - MCP Client: client.md + - MCP Server: server.md - Contributing: - Contributing Guide: contribute.md - Documentation: development.md From b0b80105896fb9005ea701ca377e41713a8fdfde Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 13 Nov 2026 09:38:30 +0100 Subject: [PATCH 3/3] Update the docs for Spring AI 2.1 modern support - Spring AI 2.1+ integrates the modern client and server; both modern guides gain a "Spring" section and drop it from "Not Yet Supported". - Legacy server guide: spec links point to the 2025-11-25 revision. - Documentation development page: project structure matches the folder. Signed-off-by: Christian Tzolov --- docs/client-modern.md | 7 ++++++- docs/development.md | 15 +++++++++------ docs/index.md | 1 + docs/overview.md | 1 + docs/quickstart.md | 2 ++ docs/server-modern.md | 8 ++++++-- docs/server.md | 6 +++--- 7 files changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/client-modern.md b/docs/client-modern.md index ab16400bb..09409da83 100644 --- a/docs/client-modern.md +++ b/docs/client-modern.md @@ -194,6 +194,12 @@ is closed, every open call fails with `McpResponseLostException`. `StdioMcpClientTransport.builder(InputStream, OutputStream)` runs over streams that are already connected, such as a socket's. +### Spring + +Spring Boot integration for the modern client is part of Spring AI 2.1+, not of this SDK. Its MCP Client Boot +Starter configures modern connections and exposes their tools to Spring AI as tool callbacks. See +[MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) in the Spring AI reference. + ### Custom Transports Other transports implement `McpClientTransport`. Its single method, `exchange(McpTransportRequest)`, sends one @@ -739,7 +745,6 @@ Failures outside those two types: - Servers of earlier revisions. Use the [legacy client](client.md) for those. - Obtaining OAuth tokens. Add tokens with a request customizer, as shown in [Identity and Authorization](#identity-and-authorization). -- Spring WebClient transport. For a larger working example, see the [modern conformance client](https://github.com/modelcontextprotocol/java-sdk/tree/main/conformance-tests/client-modern), diff --git a/docs/development.md b/docs/development.md index e00c7268b..ff1eddd58 100644 --- a/docs/development.md +++ b/docs/development.md @@ -51,14 +51,17 @@ The built site will be output to the `site/` directory. ``` docs/ -├── index.md # Overview page +├── index.md # Home page +├── overview.md # Architecture and modules ├── quickstart.md # Quickstart guide -├── client.md # MCP Client documentation -├── server.md # MCP Server documentation -├── contributing.md # Contributing guide +├── client-modern.md # MCP Client, modern (2026-07-28) +├── server-modern.md # MCP Server, modern (2026-07-28) +├── client.md # MCP Client, legacy (2025-11-25 and earlier) +├── server.md # MCP Server, legacy (2025-11-25 and earlier) +├── contribute.md # Contributing guide ├── development.md # This page -├── images/ # Images and diagrams -└── stylesheets/ # Custom CSS +├── blog/ # Blog posts +└── images/ # Images and diagrams mkdocs.yml # MkDocs configuration ``` diff --git a/docs/index.md b/docs/index.md index 443efff67..1aa13c0aa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,6 +46,7 @@ Both implementations share the following: !!! tip "Spring applications" Spring WebFlux and WebMVC transports for the legacy client and server are part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ and are not shipped by this SDK. Use the [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) and the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html) from Spring AI. + For the modern client and server, Spring AI 2.1+ provides Boot starters and Spring transports; see [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) and [MCP 2026-07-28 Servers](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-modern-server-boot-starter-docs.html). Also consider the [MCP Annotations](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-annotations-overview.html) and [MCP Security](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-security.html). ## Getting Started diff --git a/docs/overview.md b/docs/overview.md index 3b3af75bf..0edca8dbf 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -116,6 +116,7 @@ The SDK is organized into modules to separate concerns and allow adopters to bri A minimal adopter may depend only on `mcp` (core + Jackson 3). Spring-based applications that use the legacy client or server should use the `mcp-spring-webflux` or `mcp-spring-webmvc` artifacts from [Spring AI](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`), which are no longer part of this SDK. + For the modern client and server, Spring AI 2.1+ provides Boot starters and Spring transports; see [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) and [MCP 2026-07-28 Servers](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-modern-server-boot-starter-docs.html). ## Next Steps diff --git a/docs/quickstart.md b/docs/quickstart.md index 85ba3a6a2..b5de286ab 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -128,6 +128,8 @@ This is all a client needs. It includes the modern and the legacy client and ser With the `spring-ai-bom` or the Spring AI starters (`spring-ai-starter-mcp-server-webflux`, `spring-ai-starter-mcp-server-webmvc`, `spring-ai-starter-mcp-client-webflux`), no explicit version is needed. +For the modern client and server, Spring AI 2.1+ provides Boot starters and Spring transports; see [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) and [MCP 2026-07-28 Servers](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-modern-server-boot-starter-docs.html). + ## Your First Client The SDK has two clients, one for each generation of the protocol; see [Modern or Legacy?](overview.md#modern-or-legacy) to choose. Both examples list the tools of a server at `http://localhost:8080/mcp` and call one of them. diff --git a/docs/server-modern.md b/docs/server-modern.md index 11a0cf1c3..3610833f2 100644 --- a/docs/server-modern.md +++ b/docs/server-modern.md @@ -8,7 +8,7 @@ description: Build a stateless MCP server for the 2026-07-28 specification revis !!! note The modern server is available since 2.2.0. Its API, in the `io.modelcontextprotocol.modern` packages, may change in minor releases until 3.0, and [some parts of the revision](#not-yet-supported) are not - implemented. For production servers that talk to today's clients, use the [legacy server](server.md). + implemented. For clients that speak earlier revisions, use the [legacy server](server.md). To call a modern server from Java, see the [modern client](client-modern.md). ## Overview @@ -261,6 +261,11 @@ STDIO has no headers and no `contextExtractor`: handlers always see a transport so anything that reads identity from `transportContext()`, such as a `requestState` [principal binding](#securing-requeststate), has nothing to read. +### Spring + +Spring WebMVC and WebFlux transports for the modern server, and a Boot starter that configures it, are part +of Spring AI 2.1+, not of this SDK. See [MCP 2026-07-28 Servers](https://docs.spring.io/spring-ai/reference/2.1.0-SNAPSHOT/api/mcp/mcp-modern-server-boot-starter-docs.html) in the Spring AI reference. + ### HTTP Security Authentication and authorization belong in servlet filters in front of `HttpServletMcpTransport`, including @@ -948,7 +953,6 @@ record TaskChanged(String taskId) implements ServerChange { - Tasks. - Logging, which the 2026-07-28 revision deprecates. The modern server never sends log messages. -- Spring WebMVC and WebFlux transports. Spring AI's transports target the legacy server. For a larger working example, see the [modern conformance server](https://github.com/modelcontextprotocol/java-sdk/tree/main/conformance-tests/server-servlet-modern), diff --git a/docs/server.md b/docs/server.md index 1834bfe99..c138e79fc 100644 --- a/docs/server.md +++ b/docs/server.md @@ -359,7 +359,7 @@ var capabilities = ServerCapabilities.builder() ### 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 Model Context Protocol allows servers to [expose tools](https://modelcontextprotocol.io/specification/2025-11-25/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. @@ -681,7 +681,7 @@ var resourceTemplateSpec = new McpServerFeatures.SyncResourceTemplateSpecificati ### 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. +As part of the [Prompting capabilities](https://modelcontextprotocol.io/specification/2025-11-25/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" @@ -846,7 +846,7 @@ Completions can be registered for both `PromptReference` and `ResourceReference` ### 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. +To use [Sampling capabilities](https://modelcontextprotocol.io/specification/2025-11-25/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).