- Add CHANGELOG.md with release lines and history - Refresh ROADMAP.md: 2.x stable line, 3.0.x for 2026-07-28 spec (first milestones Sep 2026), Tier 2 commitment, 1.x/0.x security-patches-only - Refresh VALIDATION_RESULTS.md: 2026-08-17 run on conformance suite 0.2.0-alpha.11 (server 73/73, auth 193/193, client 3/4 w/ sse-retry baselined) - Bump conformance CI action pins v0.1.11 -> v0.1.16 - Update docs with missing features Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
1062 lines
42 KiB
Markdown
1062 lines
42 KiB
Markdown
---
|
|
title: MCP Server
|
|
description: Learn how to implement and configure a Model Context Protocol (MCP) server
|
|
---
|
|
|
|
# MCP Server
|
|
|
|
## Overview
|
|
|
|
The MCP Server is a foundational component in the Model Context Protocol (MCP) architecture that provides tools, resources, and capabilities to clients. It implements the server-side of the protocol, responsible for:
|
|
|
|
- Exposing tools that clients can discover and execute
|
|
- Managing resources with URI-based access patterns and resource templates
|
|
- Providing prompt templates and handling prompt requests
|
|
- Supporting capability negotiation with clients
|
|
- Providing argument autocompletion suggestions (completions)
|
|
- Implementing server-side protocol operations
|
|
- Managing concurrent client connections
|
|
- Providing structured logging and notifications
|
|
|
|
!!! tip
|
|
The core `io.modelcontextprotocol.sdk:mcp` module provides STDIO, SSE, and Streamable HTTP server transport implementations without requiring external web frameworks.
|
|
|
|
Spring-specific transport implementations (`mcp-spring-webflux`, `mcp-spring-webmvc`) are now part of [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`) and are no longer shipped by this SDK.
|
|
See the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html) documentation for Spring-based server setup.
|
|
|
|
The server supports both synchronous and asynchronous APIs, allowing for flexible integration in different application contexts.
|
|
|
|
=== "Sync API"
|
|
|
|
```java
|
|
// Create a server with custom configuration
|
|
McpSyncServer syncServer = McpServer.sync(transportProvider)
|
|
.serverInfo("my-server", "1.0.0")
|
|
.capabilities(ServerCapabilities.builder()
|
|
.resources(false, true) // Resource support: subscribe=false, listChanged=true
|
|
.tools(true) // Enable tool support with list changes
|
|
.prompts(true) // Enable prompt support with list changes
|
|
.completions() // Enable completions support
|
|
.logging() // Enable logging support
|
|
.build())
|
|
.build();
|
|
|
|
// Register tools, resources, and prompts
|
|
syncServer.addTool(syncToolSpecification);
|
|
syncServer.addResource(syncResourceSpecification);
|
|
syncServer.addPrompt(syncPromptSpecification);
|
|
|
|
// Close the server when done
|
|
syncServer.close();
|
|
```
|
|
|
|
=== "Async API"
|
|
|
|
```java
|
|
// Create an async server with custom configuration
|
|
McpAsyncServer asyncServer = McpServer.async(transportProvider)
|
|
.serverInfo("my-server", "1.0.0")
|
|
.capabilities(ServerCapabilities.builder()
|
|
.resources(false, true) // Resource support: subscribe=false, listChanged=true
|
|
.tools(true) // Enable tool support with list changes
|
|
.prompts(true) // Enable prompt support with list changes
|
|
.completions() // Enable completions support
|
|
.logging() // Enable logging support
|
|
.build())
|
|
.build();
|
|
|
|
// Register tools, resources, and prompts
|
|
asyncServer.addTool(asyncToolSpecification)
|
|
.doOnSuccess(v -> logger.info("Tool registered"))
|
|
.subscribe();
|
|
|
|
asyncServer.addResource(asyncResourceSpecification)
|
|
.doOnSuccess(v -> logger.info("Resource registered"))
|
|
.subscribe();
|
|
|
|
asyncServer.addPrompt(asyncPromptSpecification)
|
|
.doOnSuccess(v -> logger.info("Prompt registered"))
|
|
.subscribe();
|
|
|
|
// Close the server when done
|
|
asyncServer.close()
|
|
.doOnSuccess(v -> logger.info("Server closed"))
|
|
.subscribe();
|
|
```
|
|
|
|
### Server Types
|
|
|
|
The SDK supports multiple server creation patterns depending on your transport requirements:
|
|
|
|
```java
|
|
// Single-session server with SSE transport provider
|
|
McpSyncServer server = McpServer.sync(sseTransportProvider).build();
|
|
|
|
// Streamable HTTP server
|
|
McpSyncServer server = McpServer.sync(streamableTransportProvider).build();
|
|
|
|
// Stateless server (no session management)
|
|
McpSyncServer server = McpServer.sync(statelessTransport).build();
|
|
```
|
|
|
|
## Server Transport Providers
|
|
|
|
The transport layer in the MCP SDK is responsible for handling the communication between clients and servers.
|
|
It provides different implementations to support various communication protocols and patterns.
|
|
The SDK includes several built-in transport provider implementations:
|
|
|
|
### STDIO
|
|
|
|
Create process-based transport using stdin/stdout:
|
|
|
|
```java
|
|
StdioServerTransportProvider transportProvider =
|
|
new StdioServerTransportProvider(McpJsonDefaults.getMapper());
|
|
```
|
|
|
|
Provides bidirectional JSON-RPC message handling over standard input/output streams with non-blocking message processing, serialization/deserialization, and graceful shutdown support.
|
|
|
|
Key features:
|
|
|
|
- Bidirectional communication through stdin/stdout
|
|
- Process-based integration support
|
|
- Simple setup and configuration
|
|
- Lightweight implementation
|
|
|
|
### Streamable HTTP
|
|
|
|
=== "Streamable HTTP Servlet"
|
|
|
|
Creates a Servlet-based Streamable HTTP server transport. Included in the core `mcp` module:
|
|
|
|
```java
|
|
HttpServletStreamableServerTransportProvider transportProvider =
|
|
HttpServletStreamableServerTransportProvider.builder()
|
|
.jsonMapper(jsonMapper)
|
|
.mcpEndpoint("/mcp")
|
|
.build();
|
|
```
|
|
|
|
To use with a Spring Web application, register it as a Servlet bean:
|
|
|
|
```java
|
|
@Configuration
|
|
@EnableWebMvc
|
|
public class McpServerConfig implements WebMvcConfigurer {
|
|
|
|
@Bean
|
|
public HttpServletStreamableServerTransportProvider transportProvider(McpJsonMapper jsonMapper) {
|
|
return HttpServletStreamableServerTransportProvider.builder()
|
|
.jsonMapper(jsonMapper)
|
|
.mcpEndpoint("/mcp")
|
|
.build();
|
|
}
|
|
|
|
@Bean
|
|
public ServletRegistrationBean<?> mcpServlet(
|
|
HttpServletStreamableServerTransportProvider transportProvider) {
|
|
return new ServletRegistrationBean<>(transportProvider);
|
|
}
|
|
}
|
|
```
|
|
|
|
Key features:
|
|
|
|
- Efficient bidirectional HTTP communication
|
|
- Session management for multiple client connections
|
|
- Configurable keep-alive intervals
|
|
- Security validation support
|
|
- Graceful shutdown support
|
|
|
|
=== "Streamable HTTP WebFlux (external)"
|
|
|
|
Creates WebFlux-based Streamable HTTP server transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
|
|
|
|
```java
|
|
@Configuration
|
|
class McpConfig {
|
|
@Bean
|
|
WebFluxStreamableServerTransportProvider transportProvider(McpJsonMapper jsonMapper) {
|
|
return WebFluxStreamableServerTransportProvider.builder()
|
|
.jsonMapper(jsonMapper)
|
|
.messageEndpoint("/mcp")
|
|
.build();
|
|
}
|
|
|
|
@Bean
|
|
RouterFunction<?> mcpRouterFunction(
|
|
WebFluxStreamableServerTransportProvider transportProvider) {
|
|
return transportProvider.getRouterFunction();
|
|
}
|
|
}
|
|
```
|
|
|
|
Key features:
|
|
|
|
- Reactive HTTP streaming with WebFlux
|
|
- Concurrent client connections
|
|
- Configurable keep-alive intervals
|
|
- Security validation support
|
|
|
|
=== "Streamable HTTP WebMvc (external)"
|
|
|
|
Creates WebMvc-based Streamable HTTP server transport. Requires the `mcp-spring-webmvc` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
|
|
|
|
```java
|
|
@Configuration
|
|
@EnableWebMvc
|
|
class McpConfig {
|
|
@Bean
|
|
WebMvcStreamableServerTransportProvider transportProvider(McpJsonMapper jsonMapper) {
|
|
return WebMvcStreamableServerTransportProvider.builder()
|
|
.jsonMapper(jsonMapper)
|
|
.mcpEndpoint("/mcp")
|
|
.build();
|
|
}
|
|
|
|
@Bean
|
|
RouterFunction<ServerResponse> mcpRouterFunction(
|
|
WebMvcStreamableServerTransportProvider transportProvider) {
|
|
return transportProvider.getRouterFunction();
|
|
}
|
|
}
|
|
```
|
|
|
|
### SSE HTTP (Legacy)
|
|
|
|
=== "SSE Servlet"
|
|
|
|
Creates a Servlet-based SSE server transport. Included in the core `mcp` module.
|
|
The `HttpServletSseServerTransportProvider` can be used with any Servlet container.
|
|
To use it with a Spring Web application, you can register it as a Servlet bean:
|
|
|
|
```java
|
|
@Configuration
|
|
@EnableWebMvc
|
|
public class McpServerConfig implements WebMvcConfigurer {
|
|
|
|
@Bean
|
|
public HttpServletSseServerTransportProvider servletSseServerTransportProvider() {
|
|
return HttpServletSseServerTransportProvider.builder()
|
|
.messageEndpoint("/mcp/message")
|
|
.build();
|
|
}
|
|
|
|
@Bean
|
|
public ServletRegistrationBean<?> customServletBean(
|
|
HttpServletSseServerTransportProvider transportProvider) {
|
|
return new ServletRegistrationBean<>(transportProvider);
|
|
}
|
|
}
|
|
```
|
|
|
|
Implements the MCP HTTP with SSE transport specification using the traditional Servlet API, providing:
|
|
|
|
- Asynchronous message handling using Servlet 6.0 async support
|
|
- Session management for multiple client connections
|
|
- Two types of endpoints:
|
|
- SSE endpoint (`/sse`) for server-to-client events
|
|
- Message endpoint (configurable) for client-to-server requests
|
|
- Error handling and response formatting
|
|
- Graceful shutdown support
|
|
|
|
=== "SSE WebFlux (external)"
|
|
|
|
Creates WebFlux-based SSE server transport. Requires the `mcp-spring-webflux` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
|
|
|
|
```java
|
|
@Configuration
|
|
class McpConfig {
|
|
@Bean
|
|
WebFluxSseServerTransportProvider webFluxSseServerTransportProvider(ObjectMapper mapper) {
|
|
return new WebFluxSseServerTransportProvider(mapper, "/mcp/message");
|
|
}
|
|
|
|
@Bean
|
|
RouterFunction<?> mcpRouterFunction(WebFluxSseServerTransportProvider transportProvider) {
|
|
return transportProvider.getRouterFunction();
|
|
}
|
|
}
|
|
```
|
|
|
|
Implements the MCP HTTP with SSE transport specification, providing:
|
|
|
|
- Reactive HTTP streaming with WebFlux
|
|
- Concurrent client connections through SSE endpoints
|
|
- Message routing and session management
|
|
- Graceful shutdown capabilities
|
|
|
|
=== "SSE WebMvc (external)"
|
|
|
|
Creates WebMvc-based SSE server transport. Requires the `mcp-spring-webmvc` dependency from [Spring AI](https://docs.spring.io/spring-ai/reference/2.0-SNAPSHOT/api/mcp/mcp-overview.html) 2.0+ (group `org.springframework.ai`):
|
|
|
|
```java
|
|
@Configuration
|
|
@EnableWebMvc
|
|
class McpConfig {
|
|
@Bean
|
|
WebMvcSseServerTransportProvider webMvcSseServerTransportProvider(ObjectMapper mapper) {
|
|
return new WebMvcSseServerTransportProvider(mapper, "/mcp/message");
|
|
}
|
|
|
|
@Bean
|
|
RouterFunction<ServerResponse> mcpRouterFunction(
|
|
WebMvcSseServerTransportProvider transportProvider) {
|
|
return transportProvider.getRouterFunction();
|
|
}
|
|
}
|
|
```
|
|
|
|
Implements the MCP HTTP with SSE transport specification, providing:
|
|
|
|
- Server-side event streaming
|
|
- Integration with Spring WebMVC
|
|
- Support for traditional web applications
|
|
- Synchronous operation handling
|
|
|
|
|
|
## Server Capabilities
|
|
|
|
The server can be configured with various capabilities:
|
|
|
|
```java
|
|
var capabilities = ServerCapabilities.builder()
|
|
.resources(true, true) // Resource support: subscribe=true, listChanged=true
|
|
.tools(true) // Tool support with list changes notifications
|
|
.prompts(true) // Prompt support with list changes notifications
|
|
.completions() // Enable completions support
|
|
.logging() // Enable logging support
|
|
.build();
|
|
```
|
|
|
|
### Tool Specification
|
|
|
|
The Model Context Protocol allows servers to [expose tools](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/) that can be invoked by language models.
|
|
The Java SDK allows implementing Tool Specifications with their handler functions.
|
|
Tools enable AI models to perform calculations, access external APIs, query databases, and manipulate files.
|
|
|
|
The recommended approach is to use the builder pattern and `CallToolRequest` as the handler parameter:
|
|
|
|
=== "Sync"
|
|
|
|
```java
|
|
// Sync tool specification using builder
|
|
var syncToolSpecification = SyncToolSpecification.builder()
|
|
.tool(Tool.builder("calculator", schema)
|
|
.description("Basic calculator")
|
|
.build())
|
|
.callHandler((exchange, request) -> {
|
|
// Access arguments via request.arguments()
|
|
String operation = (String) request.arguments().get("operation");
|
|
int a = (int) request.arguments().get("a");
|
|
int b = (int) request.arguments().get("b");
|
|
// Tool implementation
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Result: " + result)))
|
|
.build();
|
|
})
|
|
.build();
|
|
```
|
|
|
|
=== "Async"
|
|
|
|
```java
|
|
// Async tool specification using builder
|
|
var asyncToolSpecification = AsyncToolSpecification.builder()
|
|
.tool(Tool.builder("calculator", schema)
|
|
.description("Basic calculator")
|
|
.build())
|
|
.callHandler((exchange, request) -> {
|
|
// Access arguments via request.arguments()
|
|
String operation = (String) request.arguments().get("operation");
|
|
int a = (int) request.arguments().get("a");
|
|
int b = (int) request.arguments().get("b");
|
|
// Tool implementation
|
|
return Mono.just(CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Result: " + result)))
|
|
.build());
|
|
})
|
|
.build();
|
|
```
|
|
|
|
The Tool specification includes a Tool definition with `name`, `description`, and `inputSchema` followed by a call handler that implements the tool's logic.
|
|
The handler receives `McpSyncServerExchange`/`McpAsyncServerExchange` for client interaction and a `CallToolRequest` containing the tool arguments.
|
|
|
|
You can also register tools directly on the server builder using the `toolCall` convenience method:
|
|
|
|
```java
|
|
var server = McpServer.sync(transportProvider)
|
|
.toolCall(
|
|
Tool.builder("echo", schema).description("Echoes input").build(),
|
|
(exchange, request) -> CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent(request.arguments().get("text").toString())))
|
|
.build()
|
|
)
|
|
.build();
|
|
```
|
|
|
|
#### Tool Input Validation
|
|
|
|
By default the server validates incoming tool arguments against the tool's `inputSchema` before invoking the handler. When validation fails, the call returns a `CallToolResult` with `isError` set and a textual error, rather than reaching your handler. Validation uses the configured `JsonSchemaValidator` (or the default from `McpJsonDefaults.getSchemaValidator()`), and can be turned off on the server builder:
|
|
|
|
```java
|
|
var server = McpServer.sync(transportProvider)
|
|
.validateToolInputs(false) // default is true
|
|
.build();
|
|
```
|
|
|
|
The embedded JSON Schema documents themselves (`Tool.inputSchema`, `Tool.outputSchema`, and elicitation `requestedSchema`) are validated against the JSON Schema 2020-12 meta-schema (SEP-1613). Malformed schemas are rejected at build time (`McpServer.build()`) and when calling `addTool()`, throwing an `IllegalArgumentException` that names the offending field. A schema that declares a different dialect via `$schema` is accepted without meta-schema validation.
|
|
|
|
#### Tool Result Content Types
|
|
|
|
Besides `TextContent`, a `CallToolResult` can return images, audio, and embedded resources — any combination of these can appear in the same result's `content` list:
|
|
|
|
```java
|
|
var syncToolSpecification = SyncToolSpecification.builder()
|
|
.tool(Tool.builder("generate-report", schema)
|
|
.description("Generates a report with mixed content")
|
|
.build())
|
|
.callHandler((exchange, request) -> {
|
|
var text = TextContent.builder("Report summary:").build();
|
|
|
|
// Image content: base64-encoded data + MIME type
|
|
var image = ImageContent.builder(base64PngData, "image/png").build();
|
|
|
|
// Audio content: base64-encoded data + MIME type
|
|
var audio = AudioContent.builder(base64WavData, "audio/wav").build();
|
|
|
|
// Embedded resource: wraps a TextResourceContents or BlobResourceContents
|
|
var resourceContents = TextResourceContents.builder("report://details", "Full details...")
|
|
.mimeType("text/plain")
|
|
.build();
|
|
var embeddedResource = EmbeddedResource.builder(resourceContents).build();
|
|
|
|
return CallToolResult.builder()
|
|
.content(List.of(text, image, audio, embeddedResource))
|
|
.isError(false)
|
|
.build();
|
|
})
|
|
.build();
|
|
```
|
|
|
|
`ImageContent.builder(data, mimeType)` and `AudioContent.builder(data, mimeType)` both take base64-encoded binary data. `EmbeddedResource.builder(resourceContents)` wraps either a `TextResourceContents` (for text data) or a `BlobResourceContents` (for base64-encoded binary data) — see [Reading Binary Resources](#reading-binary-resources) for the `BlobResourceContents` shape.
|
|
|
|
### Resource Specification
|
|
|
|
Specification of a resource with its handler function.
|
|
Resources provide context to AI models by exposing data such as: File contents, Database records, API responses, System information, Application state.
|
|
|
|
=== "Sync"
|
|
|
|
```java
|
|
// Sync resource specification
|
|
var syncResourceSpecification = new McpServerFeatures.SyncResourceSpecification(
|
|
Resource.builder("custom://resource", "name")
|
|
.description("description")
|
|
.mimeType("text/plain")
|
|
.build(),
|
|
(exchange, request) -> {
|
|
// Resource read implementation
|
|
return ReadResourceResult.builder(contents).build();
|
|
}
|
|
);
|
|
```
|
|
|
|
=== "Async"
|
|
|
|
```java
|
|
// Async resource specification
|
|
var asyncResourceSpecification = new McpServerFeatures.AsyncResourceSpecification(
|
|
Resource.builder("custom://resource", "name")
|
|
.description("description")
|
|
.mimeType("text/plain")
|
|
.build(),
|
|
(exchange, request) -> {
|
|
// Resource read implementation
|
|
return Mono.just(ReadResourceResult.builder(contents).build());
|
|
}
|
|
);
|
|
```
|
|
|
|
#### Reading Binary Resources
|
|
|
|
Binary resources (images, PDFs, audio, etc.) are returned as `BlobResourceContents`, which carries base64-encoded data instead of the plain `text` field used by `TextResourceContents`:
|
|
|
|
```java
|
|
var binaryResourceSpecification = new McpServerFeatures.SyncResourceSpecification(
|
|
Resource.builder("file:///logo.png", "Logo")
|
|
.description("Application logo")
|
|
.mimeType("image/png")
|
|
.build(),
|
|
(exchange, request) -> {
|
|
String base64Data = Base64.getEncoder().encodeToString(readLogoBytes());
|
|
return ReadResourceResult.builder(List.of(
|
|
BlobResourceContents.builder(request.uri(), base64Data)
|
|
.mimeType("image/png")
|
|
.build()))
|
|
.build();
|
|
}
|
|
);
|
|
```
|
|
|
|
`ReadResourceResult` accepts a list mixing `TextResourceContents` and `BlobResourceContents`, so a single resource read can return multiple representations if needed.
|
|
|
|
### Resource Subscriptions
|
|
|
|
When the `subscribe` capability is enabled, clients can subscribe to specific resources and receive targeted `notifications/resources/updated` notifications when those resources change. Only sessions that have explicitly subscribed to a given URI receive the notification — not every connected client.
|
|
|
|
Enable subscription support in the server capabilities:
|
|
|
|
```java
|
|
McpSyncServer server = McpServer.sync(transportProvider)
|
|
.serverInfo("my-server", "1.0.0")
|
|
.capabilities(ServerCapabilities.builder()
|
|
.resources(true, false) // subscribe=true, listChanged=false
|
|
.build())
|
|
.resources(myResourceSpec)
|
|
.build();
|
|
```
|
|
|
|
When a subscribed resource changes, notify only the interested sessions:
|
|
|
|
=== "Sync"
|
|
|
|
```java
|
|
server.notifyResourcesUpdated(
|
|
new McpSchema.ResourcesUpdatedNotification("custom://resource")
|
|
);
|
|
```
|
|
|
|
=== "Async"
|
|
|
|
```java
|
|
server.notifyResourcesUpdated(
|
|
new McpSchema.ResourcesUpdatedNotification("custom://resource")
|
|
).subscribe();
|
|
```
|
|
|
|
If no sessions are subscribed to the given URI the call completes immediately without sending any messages. Subscription state is automatically cleaned up when a client session closes.
|
|
|
|
### Resource Template Specification
|
|
|
|
Resource templates allow servers to expose parameterized resources using URI templates:
|
|
|
|
```java
|
|
// Resource template specification
|
|
var resourceTemplateSpec = new McpServerFeatures.SyncResourceTemplateSpecification(
|
|
ResourceTemplate.builder("file://{path}", "File Resource")
|
|
.description("Access files by path")
|
|
.mimeType("application/octet-stream")
|
|
.build(),
|
|
(exchange, request) -> {
|
|
// Read the file at the requested URI
|
|
return ReadResourceResult.builder(contents).build();
|
|
}
|
|
);
|
|
```
|
|
|
|
### Prompt Specification
|
|
|
|
As part of the [Prompting capabilities](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/), MCP provides a standardized way for servers to expose prompt templates to clients.
|
|
The Prompt Specification is a structured template for AI model interactions that enables consistent message formatting, parameter substitution, context injection, response formatting, and instruction templating.
|
|
|
|
=== "Sync"
|
|
|
|
```java
|
|
// Sync prompt specification
|
|
var syncPromptSpecification = new McpServerFeatures.SyncPromptSpecification(
|
|
Prompt.builder("greeting")
|
|
.description("description")
|
|
.arguments(List.of(
|
|
PromptArgument.builder("name")
|
|
.description("description")
|
|
.required(true)
|
|
.build()
|
|
))
|
|
.build(),
|
|
(exchange, request) -> {
|
|
// Prompt implementation
|
|
return GetPromptResult.builder(messages).description(description).build();
|
|
}
|
|
);
|
|
```
|
|
|
|
=== "Async"
|
|
|
|
```java
|
|
// Async prompt specification
|
|
var asyncPromptSpecification = new McpServerFeatures.AsyncPromptSpecification(
|
|
Prompt.builder("greeting")
|
|
.description("description")
|
|
.arguments(List.of(
|
|
PromptArgument.builder("name")
|
|
.description("description")
|
|
.required(true)
|
|
.build()
|
|
))
|
|
.build(),
|
|
(exchange, request) -> {
|
|
// Prompt implementation
|
|
return Mono.just(GetPromptResult.builder(messages).description(description).build());
|
|
}
|
|
);
|
|
```
|
|
|
|
The prompt definition includes name (identifier for the prompt), description (purpose of the prompt), and list of arguments (parameters for templating).
|
|
The handler function processes requests and returns formatted templates.
|
|
The first argument is `McpSyncServerExchange`/`McpAsyncServerExchange` for client interaction, and the second argument is a `GetPromptRequest` instance.
|
|
|
|
#### Prompts with Embedded Resources and Images
|
|
|
|
A prompt's messages can carry `EmbeddedResource` or `ImageContent` instead of plain text by passing them as the `content` argument of `PromptMessage.builder(role, content)`:
|
|
|
|
```java
|
|
// Prompt that embeds a resource's content in one of its messages
|
|
var promptWithResource = new McpServerFeatures.SyncPromptSpecification(
|
|
Prompt.builder("review-file")
|
|
.description("Reviews a file, embedding its content in the prompt")
|
|
.arguments(List.of(PromptArgument.builder("resourceUri").required(true).build()))
|
|
.build(),
|
|
(exchange, request) -> {
|
|
String resourceUri = (String) request.arguments().get("resourceUri");
|
|
var resourceContents = TextResourceContents.builder(resourceUri, loadFileContent(resourceUri))
|
|
.mimeType("text/plain")
|
|
.build();
|
|
var embeddedResource = EmbeddedResource.builder(resourceContents).build();
|
|
|
|
return GetPromptResult.builder(List.of(
|
|
PromptMessage.builder(Role.USER, embeddedResource).build(),
|
|
PromptMessage.builder(Role.USER, TextContent.builder("Please review the file above.").build()).build()))
|
|
.build();
|
|
}
|
|
);
|
|
|
|
// Prompt that embeds an image in one of its messages
|
|
var promptWithImage = new McpServerFeatures.SyncPromptSpecification(
|
|
Prompt.builder("describe-image")
|
|
.description("Asks the model to describe an embedded image")
|
|
.arguments(List.of())
|
|
.build(),
|
|
(exchange, request) -> GetPromptResult.builder(List.of(
|
|
PromptMessage.builder(Role.USER, ImageContent.builder(base64PngData, "image/png").build()).build(),
|
|
PromptMessage.builder(Role.USER, TextContent.builder("Describe the image above.").build()).build()))
|
|
.build()
|
|
);
|
|
```
|
|
|
|
### Completion Specification
|
|
|
|
Completions allow servers to provide argument autocompletion suggestions for prompts and resources:
|
|
|
|
=== "Sync"
|
|
|
|
```java
|
|
// Sync completion specification
|
|
var syncCompletionSpec = new McpServerFeatures.SyncCompletionSpecification(
|
|
new McpSchema.PromptReference("greeting"), // Reference to a prompt
|
|
(exchange, request) -> {
|
|
String argName = request.argument().name();
|
|
String partial = request.argument().value();
|
|
// Return matching suggestions
|
|
List<String> suggestions = findMatches(partial);
|
|
return new McpSchema.CompleteResult(
|
|
new McpSchema.CompleteResult.CompleteCompletion(suggestions, suggestions.size(), false)
|
|
);
|
|
}
|
|
);
|
|
```
|
|
|
|
=== "Async"
|
|
|
|
```java
|
|
// Async completion specification
|
|
var asyncCompletionSpec = new McpServerFeatures.AsyncCompletionSpecification(
|
|
new McpSchema.PromptReference("greeting"),
|
|
(exchange, request) -> {
|
|
String argName = request.argument().name();
|
|
String partial = request.argument().value();
|
|
List<String> suggestions = findMatches(partial);
|
|
return Mono.just(new McpSchema.CompleteResult(
|
|
new McpSchema.CompleteResult.CompleteCompletion(suggestions, suggestions.size(), false)
|
|
));
|
|
}
|
|
);
|
|
```
|
|
|
|
Completions can be registered for both `PromptReference` and `ResourceReference` types. A `ResourceReference` completion suggests values for a resource template's URI parameters instead of a prompt's arguments:
|
|
|
|
=== "Sync"
|
|
|
|
```java
|
|
// Sync completion specification for a resource template argument
|
|
var syncResourceCompletionSpec = new McpServerFeatures.SyncCompletionSpecification(
|
|
new McpSchema.ResourceReference("file://{path}"), // Reference to a resource template
|
|
(exchange, request) -> {
|
|
String argName = request.argument().name();
|
|
String partial = request.argument().value();
|
|
// Return matching suggestions, e.g. matching file paths
|
|
List<String> suggestions = findMatchingPaths(partial);
|
|
return new McpSchema.CompleteResult(
|
|
new McpSchema.CompleteResult.CompleteCompletion(suggestions, suggestions.size(), false)
|
|
);
|
|
}
|
|
);
|
|
```
|
|
|
|
=== "Async"
|
|
|
|
```java
|
|
// Async completion specification for a resource template argument
|
|
var asyncResourceCompletionSpec = new McpServerFeatures.AsyncCompletionSpecification(
|
|
new McpSchema.ResourceReference("file://{path}"),
|
|
(exchange, request) -> {
|
|
String argName = request.argument().name();
|
|
String partial = request.argument().value();
|
|
List<String> suggestions = findMatchingPaths(partial);
|
|
return Mono.just(new McpSchema.CompleteResult(
|
|
new McpSchema.CompleteResult.CompleteCompletion(suggestions, suggestions.size(), false)
|
|
));
|
|
}
|
|
);
|
|
```
|
|
|
|
### Using Sampling from a Server
|
|
|
|
To use [Sampling capabilities](https://spec.modelcontextprotocol.io/specification/2024-11-05/client/sampling/), connect to a client that supports sampling.
|
|
No special server configuration is needed, but verify client sampling support before making requests.
|
|
Learn about [client sampling support](client.md#sampling-support).
|
|
|
|
Once connected to a compatible client, the server can request language model generations:
|
|
|
|
=== "Sync API"
|
|
|
|
```java
|
|
// Create a server
|
|
McpSyncServer server = McpServer.sync(transportProvider)
|
|
.serverInfo("my-server", "1.0.0")
|
|
.build();
|
|
|
|
// Define a tool that uses sampling
|
|
var calculatorTool = SyncToolSpecification.builder()
|
|
.tool(Tool.builder("ai-calculator", schema)
|
|
.description("Performs calculations using AI")
|
|
.build())
|
|
.callHandler((exchange, request) -> {
|
|
// Check if client supports sampling
|
|
if (exchange.getClientCapabilities().sampling() == null) {
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Client does not support AI capabilities")))
|
|
.build();
|
|
}
|
|
|
|
// Create a sampling request
|
|
CreateMessageRequest samplingRequest = CreateMessageRequest.builder(
|
|
List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
|
new McpSchema.TextContent("Calculate: " + request.arguments().get("expression")))),
|
|
100)
|
|
.modelPreferences(McpSchema.ModelPreferences.builder()
|
|
.hints(List.of(
|
|
McpSchema.ModelHint.of("claude-3-sonnet"),
|
|
McpSchema.ModelHint.of("claude")
|
|
))
|
|
.intelligencePriority(0.8)
|
|
.speedPriority(0.5)
|
|
.build())
|
|
.systemPrompt("You are a helpful calculator assistant. Provide only the numerical answer.")
|
|
.build();
|
|
|
|
// Request sampling from the client
|
|
CreateMessageResult result = exchange.createMessage(samplingRequest);
|
|
|
|
// Process the result
|
|
String answer = ((McpSchema.TextContent) result.content()).text();
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent(answer)))
|
|
.build();
|
|
})
|
|
.build();
|
|
|
|
// Add the tool to the server
|
|
server.addTool(calculatorTool);
|
|
```
|
|
|
|
=== "Async API"
|
|
|
|
```java
|
|
// Create a server
|
|
McpAsyncServer server = McpServer.async(transportProvider)
|
|
.serverInfo("my-server", "1.0.0")
|
|
.build();
|
|
|
|
// Define a tool that uses sampling
|
|
var calculatorTool = AsyncToolSpecification.builder()
|
|
.tool(Tool.builder("ai-calculator", schema)
|
|
.description("Performs calculations using AI")
|
|
.build())
|
|
.callHandler((exchange, request) -> {
|
|
// Check if client supports sampling
|
|
if (exchange.getClientCapabilities().sampling() == null) {
|
|
return Mono.just(CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Client does not support AI capabilities")))
|
|
.build());
|
|
}
|
|
|
|
// Create a sampling request
|
|
CreateMessageRequest samplingRequest = CreateMessageRequest.builder(
|
|
List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
|
new McpSchema.TextContent("Calculate: " + request.arguments().get("expression")))),
|
|
100)
|
|
.modelPreferences(McpSchema.ModelPreferences.builder()
|
|
.hints(List.of(
|
|
McpSchema.ModelHint.of("claude-3-sonnet"),
|
|
McpSchema.ModelHint.of("claude")
|
|
))
|
|
.intelligencePriority(0.8)
|
|
.speedPriority(0.5)
|
|
.build())
|
|
.systemPrompt("You are a helpful calculator assistant. Provide only the numerical answer.")
|
|
.build();
|
|
|
|
// Request sampling from the client
|
|
return exchange.createMessage(samplingRequest)
|
|
.map(result -> {
|
|
String answer = ((McpSchema.TextContent) result.content()).text();
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent(answer)))
|
|
.build();
|
|
});
|
|
})
|
|
.build();
|
|
|
|
// Add the tool to the server
|
|
server.addTool(calculatorTool)
|
|
.subscribe();
|
|
```
|
|
|
|
The `CreateMessageRequest` object allows you to specify: `Content` - the input text or image for the model,
|
|
`Model Preferences` - hints and priorities for model selection, `System Prompt` - instructions for the model's behavior and
|
|
`Max Tokens` - maximum length of the generated response.
|
|
|
|
### Using Elicitation from a Server
|
|
|
|
Servers can request user input from connected clients that support elicitation:
|
|
|
|
```java
|
|
var tool = SyncToolSpecification.builder()
|
|
.tool(Tool.builder("confirm-action", schema)
|
|
.description("Confirms an action with the user")
|
|
.build())
|
|
.callHandler((exchange, request) -> {
|
|
// Check if client supports elicitation
|
|
if (exchange.getClientCapabilities().elicitation() == null) {
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Client does not support elicitation")))
|
|
.build();
|
|
}
|
|
|
|
// Request user confirmation
|
|
ElicitRequest elicitRequest = ElicitFormRequest.builder("Do you want to proceed with this action?", Map.of(
|
|
"type", "object",
|
|
"properties", Map.of("confirmed", Map.of("type", "boolean"))
|
|
))
|
|
.build();
|
|
|
|
ElicitResult result = exchange.elicit(elicitRequest);
|
|
|
|
if (result.action() == ElicitResult.Action.ACCEPT) {
|
|
// User accepted
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Action confirmed")))
|
|
.build();
|
|
} else {
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Action declined")))
|
|
.build();
|
|
}
|
|
})
|
|
.build();
|
|
```
|
|
|
|
To request out-of-band URL elicitation, such as a user authorizing an OAuth flow:
|
|
|
|
```java
|
|
var urlTool = SyncToolSpecification.builder()
|
|
.tool(Tool.builder("oauth-auth", schema)
|
|
.description("Authenticates via OAuth")
|
|
.build())
|
|
.callHandler((exchange, request) -> {
|
|
// Request URL elicitation from client
|
|
if (
|
|
exchange.getClientCapabilities().elicitation() != null
|
|
&& exchange.getClientCapabilities().elicitation().url() != null
|
|
) {
|
|
ElicitRequest urlRequest = McpSchema.ElicitUrlRequest
|
|
.builder("Please authenticate", "https://example.com/oauth", "oauth-123").build();
|
|
ElicitResult result = exchange.elicit(urlRequest);
|
|
// handle result.action == CANCELLED or DENIED
|
|
if (result.action() != ElicitResult.Action.ACCEPT) {
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Authentication failed or cancelled")))
|
|
.build();
|
|
}
|
|
}
|
|
|
|
// wait for user to visit the URL
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Authentication successful")))
|
|
.build();
|
|
})
|
|
.build();
|
|
```
|
|
|
|
#### Elicitation with Enum Values (SEP-1330)
|
|
|
|
For form elicitation, the SDK provides typed helpers to build `requestedSchema` properties that render as single-select or multi-select choices, with or without human-readable titles:
|
|
|
|
```java
|
|
// Untitled single-select: plain enum values, no separate display titles
|
|
var untitledSingle = UntitledSingleSelectEnumSchema.builder()
|
|
.enumValues("small", "medium", "large")
|
|
.build();
|
|
|
|
// Titled single-select: value/title pairs via oneOf+const
|
|
var titledSingle = TitledSingleSelectEnumSchema.builder()
|
|
.oneOf(new EnumSchemaOption("sm", "Small"),
|
|
new EnumSchemaOption("md", "Medium"),
|
|
new EnumSchemaOption("lg", "Large"))
|
|
.build();
|
|
|
|
// Untitled multi-select: an array property whose items are an enum
|
|
var untitledMulti = UntitledMultiSelectEnumSchema
|
|
.builder(UntitledMultiSelectItems.builder().enumValues("red", "green", "blue").build())
|
|
.build();
|
|
|
|
// Titled multi-select: array items with value/title pairs via anyOf+const
|
|
var titledMulti = TitledMultiSelectEnumSchema
|
|
.builder(TitledMultiSelectItems.builder()
|
|
.anyOf(new EnumSchemaOption("red", "Red"), new EnumSchemaOption("green", "Green"))
|
|
.build())
|
|
.build();
|
|
|
|
// Convert the typed schema helpers into the plain Map<String, Object> shape
|
|
// expected by ElicitRequest.builder(message, requestedSchema)
|
|
var mapper = McpJsonDefaults.getMapper();
|
|
TypeRef<Map<String, Object>> mapType = new TypeRef<>() {};
|
|
Map<String, Object> requestedSchema = Map.of("type", "object", "properties",
|
|
Map.of("size", mapper.convertValue(titledSingle, mapType),
|
|
"colors", mapper.convertValue(titledMulti, mapType)),
|
|
"required", List.of("size", "colors"));
|
|
|
|
ElicitRequest elicitRequest = ElicitRequest.builder("Choose your options", requestedSchema).build();
|
|
ElicitResult result = exchange.createElicitation(elicitRequest);
|
|
```
|
|
|
|
`LegacyTitledEnumSchema` (a value list plus a parallel `enumNames` list, built with `.enumValues(...)` and `.enumNames(...)`) is also available for clients that predate SEP-1330's `oneOf`/`anyOf` convention, but new schemas should prefer `TitledSingleSelectEnumSchema` / `TitledMultiSelectEnumSchema`.
|
|
|
|
### Pinging the Client
|
|
|
|
A server can check that a connected client is still responsive by sending a `ping` request through the exchange:
|
|
|
|
```java
|
|
var tool = SyncToolSpecification.builder()
|
|
.tool(Tool.builder("health-check", emptyJsonSchema).description("Verifies the client is reachable").build())
|
|
.callHandler((exchange, request) -> {
|
|
exchange.ping(); // blocks until the client responds, or the request times out
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Client is responsive")))
|
|
.build();
|
|
})
|
|
.build();
|
|
```
|
|
|
|
The async equivalent, `McpAsyncServerExchange.ping()`, returns a `Mono<Object>` that completes when the client responds. Ping requests are subject to the same `requestTimeout` configured on the server builder.
|
|
|
|
### Logging Support
|
|
|
|
The server provides structured logging capabilities that allow sending log messages to clients with different severity levels.
|
|
Log notifications can only be sent from within an existing client session, such as tools, resources, and prompts calls.
|
|
|
|
The server can send log messages using the `McpAsyncServerExchange`/`McpSyncServerExchange` object in the tool/resource/prompt handler function:
|
|
|
|
```java
|
|
var tool = AsyncToolSpecification.builder()
|
|
.tool(Tool.builder("logging-test", emptyJsonSchema).description("Test logging notifications").build())
|
|
.callHandler((exchange, request) ->
|
|
exchange.loggingNotification( // Use the exchange to send log messages
|
|
McpSchema.LoggingMessageNotification.builder(McpSchema.LoggingLevel.DEBUG, "Debug message")
|
|
.logger("test-logger")
|
|
.build())
|
|
.then(Mono.just(CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Logging test completed")))
|
|
.build())))
|
|
.build();
|
|
|
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
|
.serverInfo("test-server", "1.0.0")
|
|
.capabilities(
|
|
ServerCapabilities.builder()
|
|
.logging() // Enable logging support
|
|
.tools(true)
|
|
.build())
|
|
.tools(tool)
|
|
.build();
|
|
```
|
|
|
|
On the client side, you can register a logging consumer to receive log messages from the server:
|
|
|
|
```java
|
|
var mcpClient = McpClient.sync(transport)
|
|
.loggingConsumer(notification -> {
|
|
System.out.println("Received log message: " + notification.data());
|
|
})
|
|
.build();
|
|
|
|
mcpClient.initialize();
|
|
mcpClient.setLoggingLevel(McpSchema.LoggingLevel.INFO);
|
|
```
|
|
|
|
Clients can control the minimum logging level they receive through the `mcpClient.setLoggingLevel(level)` request. Messages below the set level will be filtered out.
|
|
Supported logging levels (in order of increasing severity): DEBUG (0), INFO (1), NOTICE (2), WARNING (3), ERROR (4), CRITICAL (5), ALERT (6), EMERGENCY (7)
|
|
|
|
## Error Handling
|
|
|
|
The SDK provides comprehensive error handling through the McpError class, covering protocol compatibility, transport communication, JSON-RPC messaging, tool execution, resource management, prompt handling, timeouts, and connection issues. This unified error handling approach ensures consistent and reliable error management across both synchronous and asynchronous operations.
|
|
|
|
### Error Handling in Tool Implementations
|
|
|
|
#### Two Tiers of Errors
|
|
|
|
MCP distinguishes between two categories of errors in tool execution:
|
|
|
|
**1. Tool-Level Errors (Recoverable by the LLM)**
|
|
|
|
Use `CallToolResult` with `isError(true)` for validation failures, missing arguments, or domain errors the LLM can act on and retry.
|
|
|
|
```java
|
|
// Example: Domain validation failure (e.g., invalid email format)
|
|
if (!emailAddress.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
|
|
return CallToolResult.builder()
|
|
.content(List.of(new McpSchema.TextContent("Invalid argument: 'email' must be a valid email address.")))
|
|
.isError(true)
|
|
.build();
|
|
}
|
|
```
|
|
|
|
The LLM receives this as part of the normal tool response and can self-correct in a subsequent interaction.
|
|
|
|
**2. Protocol-Level Errors (Unrecoverable)**
|
|
|
|
Uncaught exceptions from a tool handler are mapped to a JSON-RPC error response. Use this only for truly unexpected failures (e.g., infrastructure errors such as DB timeout), not for input validation.
|
|
|
|
```java
|
|
// This propagates as a JSON-RPC error — use sparingly
|
|
throw new McpError(McpSchema.ErrorCodes.INTERNAL_ERROR, "Unexpected failure");
|
|
```
|
|
|
|
#### Decision Guide
|
|
|
|
| Situation | Approach |
|
|
|------------------------------------|---------------------------------------|
|
|
| Domain validation failure | `CallToolResult` with `isError=true` |
|
|
| Infrastructure / unexpected error | Throw `McpError` or let it propagate |
|
|
| Partial success with a warning | `CallToolResult` with warning in text |
|