Documentation Index
Fetch the complete documentation index at: https://mcp.gjxx.dev/llms.txt
Use this file to discover all available pages before exploring further.
SDK Contents
Java MCP Overview
[Java MCP Client]
Java MCP Server
Client Features
The MCP Client is a key component in the Model Context Protocol (MCP) architecture, responsible for establishing and managing connections with MCP servers. It implements the client-side of the protocol, handling:
- Protocol version negotiation to ensure compatibility with servers
- Capability negotiation to determine available features
- Message transport and JSON-RPC communication
- Tool discovery and execution
- Resource access and management
- Prompt system interactions
- Optional features like roots management and sampling support
The core io.modelcontextprotocol.sdk:mcp module provides STDIO, Streamable-HTTP and SSE client transport implementations without requiring external web frameworks.Spring-specific transport implementations are available as an optional dependency io.modelcontextprotocol.sdk:mcp-spring-webflux for Spring Framework users.
This quickstart demo, based on Spring AI MCP, will show
you how to build an AI client that connects to MCP servers.
The client provides both synchronous and asynchronous APIs for flexibility in different application contexts.
// Create a sync client with custom configuration
McpSyncClient client = McpClient.sync(transport)
.requestTimeout(Duration.ofSeconds(10))
.capabilities(ClientCapabilities.builder()
.roots(true) // Enable roots capability
.sampling() // Enable sampling capability
.elicitation() // Enable elicitation capability
.build())
.sampling(request -> CreateMessageResult.builder()...build())
.elicitation(elicitRequest -> ElicitResult.builder()...build())
.toolsChangeConsumer((List<McpSchema.Tool> tools) -> ...)
.resourcesChangeConsumer((List<McpSchema.Resource> resources) -> ...)
.promptsChangeConsumer((List<McpSchema.Prompt> prompts) -> ...)
.loggingConsumer((LoggingMessageNotification logging) -> ...)
.progressConsumer((ProgressNotification progress) -> ...)
.build();
// Initialize connection
client.initialize();
// List available tools
ListToolsResult tools = client.listTools();
// Call a tool
CallToolResult result = client.callTool(
new CallToolRequest("calculator",
Map.of("operation", "add", "a", 2, "b", 3))
);
// List and read resources
ListResourcesResult resources = client.listResources();
ReadResourceResult resource = client.readResource(
new ReadResourceRequest("resource://uri")
);
// List and use prompts
ListPromptsResult prompts = client.listPrompts();
GetPromptResult prompt = client.getPrompt(
new GetPromptRequest("greeting", Map.of("name", "Spring"))
);
// Add/remove roots
client.addRoot(new Root("file:///path", "description"));
client.removeRoot("file:///path");
// Close client
client.closeGracefully();
// Create an async client with custom configuration
McpAsyncClient client = McpClient.async(transport)
.requestTimeout(Duration.ofSeconds(10))
.capabilities(ClientCapabilities.builder()
.roots(true) // Enable roots capability
.sampling() // Enable sampling capability
.elicitation() // Enable elicitation capability
.build())
.sampling(request -> Mono.just(new CreateMessageResult(response)))
.elicitation(elicitRequest -> Mono.just(ElicitResult.builder()...build()))
.toolsChangeConsumer(tools -> Mono.fromRunnable(() -> logger.info("Tools updated: {}", tools)))
.resourcesChangeConsumer(resources -> Mono.fromRunnable(() -> logger.info("Resources updated: {}", resources)))
.promptsChangeConsumer(prompts -> Mono.fromRunnable(() -> logger.info("Prompts updated: {}", prompts)))
.loggingConsumer(notification -> Mono.fromRunnable(() -> logger.info("Log: {}", notification.data())))
.progressConsumer(progress -> Mono.fromRunnable(() -> logger.info("Progress update: {}", progress.data())))
.build();
// Initialize connection and use features
client.initialize()
.flatMap(initResult -> client.listTools())
.flatMap(tools -> {
return client.callTool(new CallToolRequest(
"calculator",
Map.of("operation", "add", "a", 2, "b", 3)
));
})
.flatMap(result -> {
return client.listResources()
.flatMap(resources ->
client.readResource(new ReadResourceRequest("resource://uri"))
);
})
.flatMap(resource -> {
return client.listPrompts()
.flatMap(prompts ->
client.getPrompt(new GetPromptRequest(
"greeting",
Map.of("name", "Spring")
))
);
})
.flatMap(prompt -> {
return client.addRoot(new Root("file:///path", "description"))
.then(client.removeRoot("file:///path"));
})
.doFinally(signalType -> {
client.closeGracefully().subscribe();
})
.subscribe();
Client Transport
The transport layer handles the communication between MCP clients and servers, providing different implementations for various use cases. The client transport manages message serialization, connection establishment, and protocol-specific communication patterns.
STDIO
HttpClient
WebClient
Creates transport for in-process based communicationServerParameters params = ServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-everything", "dir")
.build();
McpTransport transport = new StdioClientTransport(params);
Framework agnostic (only using JDK APIs) Streamable-HTTP client transportMcpTransport transport = HttpClientStreamableHttpTransport
.builder("http://your-mcp-server")
.build();
Framework agnostic (only using JDK APIs) SSE client transportMcpTransport transport = HttpClientSseClientTransport
.builder("http://your-mcp-server")
.build();
HttpClient: Customizing HTTP requestsTo customize the base HTTP request builder used for every request, provide a custom HttpRequestBuilder.
This is available in both Streamable HTTP and SSE transports.
When using a custom HttpRequest.Builder, every HTTP request issued by the transport
will use the hardcoded configuration from the builder.
For example, this is useful for providing a never-expiring security token in a header.
To add a X-Custom-Header header to every request, use:var requestBuilder = HttpRequest
.newBuilder()
.header("X-Custom-Header", "some header value");
HttpClientStreamableHttpTransport
.builder("https://mcp.example.com")
.requestBuilder(requestBuilder)
.build();
To dynamically modify HTTP request before they are issued, implement either McpSyncHttpClientRequestCustomizer or McpAsyncHttpClientRequestCustomizer.
Choose the request customizer matching the MCP Client type, sync or async.
Note that thread-locals may not be available in the customizers, context-related information
must be accessed through McpTransportContext (see adding context information).
Example implementations:// Sync
class MyRequestCustomizer implements McpSyncHttpClientRequestCustomizer {
@Override
public void customize(HttpRequest.Builder builder, String method,
URI endpoint, String body, McpTransportContext context) {
// ... custom logic ...
var token = obtainAccessToken(context);
builder.header("Authorization", "Bearer " + token);
}
}
// Async
class MyAsyncRequestCustomizer implements McpAsyncHttpClientRequestCustomizer {
@Override
public Publisher<HttpRequest.Builder> customize(HttpRequest.Builder builder,
String method, URI endpoint, String body, McpTransportContext context) {
// ... custom logic ...
Mono<String> token = obtainAccessToken(context);
return token.map(t ->
builder.copy()
.header("Authorization", "Bearer " + t)
);
}
}
The transports, both Streamable HTTP and SSE, can be configured to use a single customizer.HttpClientStreamableHttpTransport
.builder("https://mcp.example.com")
.httpRequestCustomizer(new MyRequestCustomizer()) // sync
.asyncHttpRequestCustomizer(new MyAsyncRequestCustomizer()) // OR async
.build();
To compose multiple customizers, use DelegatingMcpSyncHttpClientRequestCustomizer or DelegatingMcpAsyncHttpClientRequestCustomizer.WebClient-based client transport. Requires the mcp-spring-webflux dependency.
WebClient.Builder webClientBuilder = WebClient.builder()
.baseUrl("http://your-mcp-server");
McpTransport transport = WebClientStreamableHttpTransport
.builder(webClientBuilder)
.build();
WebClient.Builder webClientBuilder = WebClient.builder()
.baseUrl("http://your-mcp-server");
McpTransport transport = WebFluxSseClientTransport(webClientBuilder)
.builder(webClientBuilder)
.build();
WebClient: Customizing HTTP requestsTo customize outgoing HTTP requests, provide a custom WebClient.Builder.
When using a custom builder, every request sent by the transport will be customized.
For example, this is useful for providing a never-expiring security token in a header.
To add a X-Custom-Header header to every request, use:var webClientBuilder = WebClient.builder()
.defaultHeader("X-Custom-Header", "some header value");
McpTransport transport = WebClientStreamableHttpTransport
.builder(webClientBuilder)
.build();
To dynamically modify HTTP request, the builder has a dedicated API, ExchangeFilterFunction.
In that function, the new request can be computed at run time, and additional information can
be obtained from the Reactor context.
It is common to store context information in an McpTransportContext within the Reactor context,
typically in McpSyncClient, but any context key can be used. For example:WebClient.builder()
.filter((request, next) -> {
return Mono.deferContextual(ctx -> {
var transportContext = ctx.get(McpTransportContext.KEY);
var otherInfo = ctx.get("your-context-key");
Mono<String> token = obtainAccessToken(transportContext, otherInfo);
return token.map(t -> {
var newRequest = ClientRequest.from(request)
.header("Authorization", "Bearer " + t)
.build();
// Ensure you call next.exchange to execute the HTTP request
return next.exchange(newRequest);
});
});
});
To learn how to populate context information, see adding context information.
Client Capabilities
The client can be configured with various capabilities:
var capabilities = ClientCapabilities.builder()
.roots(true) // Enable filesystem roots support with list changes notifications
.sampling() // Enable LLM sampling support
.elicitation() // Enable elicitation capability
.build();
Roots Support
Roots define the boundaries of where servers can operate within the filesystem:
// Add a root dynamically
client.addRoot(new Root("file:///path", "description"));
// Remove a root
client.removeRoot("file:///path");
// Notify server of roots changes
client.rootsListChangedNotification();
The roots capability allows servers to:
- Request the list of accessible filesystem roots
- Receive notifications when the roots list changes
- Understand which directories and files they have access to
Sampling Support
Sampling enables servers to request LLM interactions (“completions” or “generations”) through the client:
// Configure sampling handler
Function<CreateMessageRequest, CreateMessageResult> samplingHandler = request -> {
// Sampling implementation that interfaces with LLM
return new CreateMessageResult(response);
};
// Create client with sampling support
var client = McpClient.sync(transport)
.capabilities(ClientCapabilities.builder()
.sampling()
.build())
.sampling(samplingHandler)
.build();
This capability allows:
- Servers to leverage AI capabilities without requiring API keys
- Clients to maintain control over model access and permissions
- Support for both text and image-based interactions
- Optional inclusion of MCP server context in prompts
Elicitation Support
Elicitation enables servers to request specific information or clarification from the client:
// Configure elicitation handler
Function<ElicitRequest, ElicitResult> elicitationHandler = request -> {
// Elicitation implementation that interfaces with LLM
return ElicitResult.builder()...build();
};
// Create client with elicitation support
var client = McpClient.sync(transport)
.capabilities(ClientCapabilities.builder()
.elicitation() // enable elicitation capability
.build())
.elicitation(elicitationHandler) // register elicitation handler
.build();
Logging Support
The client can register a logging consumer to receive log messages from the server and set the minimum logging level to filter messages:
var mcpClient = McpClient.sync(transport)
.loggingConsumer((LoggingMessageNotification notification) -> {
System.out.println("Received log message: " + notification.data());
})
.build();
mcpClient.initialize();
mcpClient.setLoggingLevel(McpSchema.LoggingLevel.INFO);
// Call the tool that can sends logging notifications
CallToolResult result = mcpClient.callTool(new McpSchema.CallToolRequest("logging-test", Map.of()));
Clients can control the minimum logging level they receive through the mcpClient.setLoggingLevel(level) request. Messages below the set level will be filtered out.
Supported logging levels (in order of increasing severity): DEBUG (0), INFO (1), NOTICE (2), WARNING (3), ERROR (4), CRITICAL (5), ALERT (6), EMERGENCY (7)
Progress Support
The client can register a progress consumer to receive progress updates from the server:
var mcpClient = McpClient.sync(transport)
.progressConsumer((ProgressNotification progress) -> {
System.out.println("Received progress update: " + progress.data());
})
.build();
mcpClient.initialize();
// Call the tool that can sends progress notifications
CallToolResult result = mcpClient.callTool(new McpSchema.CallToolRequest("progress-test", Map.of()));
Change Notifications
The client can register a change consumer to receive change notifications from the server about tools, resources, or prompts updates:
var spec = McpClient.sync(transport);
// Adds a consumer to be notified when the available tools change, such as tools
// being added or removed.
spec.toolsChangeConsumer((List<McpSchema.Tool> tools) -> {
// Handle tools change
});
// Adds a consumer to be notified when the available resources change, such as resources
// being added or removed.
spec.resourcesChangeConsumer((List<McpSchema.Resource> resources) -> {
// Handle resources change
});
// Adds a consumer to be notified when the available prompts change, such as prompts
// being added or removed.
spec.promptsChangeConsumer((List<McpSchema.Prompt> prompts) -> {
// Handle prompts change
});
Using MCP Clients
Tools are server-side functions that clients can discover and execute. The MCP client provides methods to list available tools and execute them with specific parameters. Each tool has a unique name and accepts a map of parameters.
// List available tools and their names
var tools = client.listTools();
tools.forEach(tool -> System.out.println(tool.getName()));
// Execute a tool with parameters
var result = client.callTool("calculator", Map.of(
"operation", "add",
"a", 1,
"b", 2
));
// List available tools asynchronously
client.listTools()
.doOnNext(tools -> tools.forEach(tool ->
System.out.println(tool.getName())))
.subscribe();
// Execute a tool asynchronously
client.callTool("calculator", Map.of(
"operation", "add",
"a", 1,
"b", 2
))
.subscribe();
Resource Access
Resources represent server-side data sources that clients can access using URI templates. The MCP client provides methods to discover available resources and retrieve their contents through a standardized interface.
// List available resources and their names
var resources = client.listResources();
resources.forEach(resource -> System.out.println(resource.getName()));
// Retrieve resource content using a URI template
var content = client.getResource("file", Map.of(
"path", "/path/to/file.txt"
));
// List available resources asynchronously
client.listResources()
.doOnNext(resources -> resources.forEach(resource ->
System.out.println(resource.getName())))
.subscribe();
// Retrieve resource content asynchronously
client.getResource("file", Map.of(
"path", "/path/to/file.txt"
))
.subscribe();
Prompt System
The prompt system enables interaction with server-side prompt templates. These templates can be discovered and executed with custom parameters, allowing for dynamic text generation based on predefined patterns.
// List available prompt templates
var prompts = client.listPrompts();
prompts.forEach(prompt -> System.out.println(prompt.getName()));
// Execute a prompt template with parameters
var response = client.executePrompt("echo", Map.of(
"text", "Hello, World!"
));
// List available prompt templates asynchronously
client.listPrompts()
.doOnNext(prompts -> prompts.forEach(prompt ->
System.out.println(prompt.getName())))
.subscribe();
// Execute a prompt template asynchronously
client.executePrompt("echo", Map.of(
"text", "Hello, World!"
))
.subscribe();
Using Completion
As part of the Completion capabilities, MCP provides a standardized way for servers to offer argument autocompletion suggestions for prompts and resource URIs.
Check the Server Completion capabilities to learn how to enable and configure completions on the server side.
On the client side, the MCP client provides methods to request auto-completions:
CompleteRequest request = new CompleteRequest(
new PromptReference("code_review"),
new CompleteRequest.CompleteArgument("language", "py"));
CompleteResult result = syncMcpClient.completeCompletion(request);
CompleteRequest request = new CompleteRequest(
new PromptReference("code_review"),
new CompleteRequest.CompleteArgument("language", "py"));
Mono<CompleteResult> result = mcpClient.completeCompletion(request);
Adding context information
HTTP request sent through SSE or Streamable HTTP transport can be customized with
dedicated APIs (see client transport). These customizers may need
additional context-specific information that must be injected at the client level.
The McpSyncClient is used in a blocking environment, and may rely on thread-locals to share information.
For example, some frameworks store the current server request or security tokens in a thread-local.
To make this type of information available to underlying transports, use SyncSpec#transportContextProvider:McpClient.sync(transport)
.transportContextProvider(() -> {
var data = obtainDataFromThreadLocals();
return McpTransportContext.create(
Map.of("some-data", data)
);
})
.build();
This McpTransportContext will be available in HttpClient-based McpSyncHttpClientRequestCustomizer
and WebClient-based ExchangeFilterFunction. The McpAsyncClient is used in a reactive environment.
Information is passed through the reactive chain using the Reactor context.
To insert data in the Reactor context, use contextWrite when calling client operations:var client = McpClient.async(transport)
// ...
.build();
Mono<McpSchema.ListToolsResult> toolList = client
.listTools()
.contextWrite(ctx -> {
var transportContext = McpTransportContext.create(
Map.of("some-key", "some value")
);
return ctx.put(McpTransportContext.KEY, transportContext)
.put("reactor-context-key", "some reactor value");
});
With this, the McpTransportContext will be available in HttpClient-based McpAsyncHttpClientRequestCustomizer.
The entire Reactor context is also available in both McpAsyncHttpClientRequestCustomizer
and WebClient’s ExchangeFilterFunction, through Mono.deferContextual:WebClient.builder()
.filter((request, next) -> {
return Mono.deferContextual(ctx -> {
var transportContext = ctx.get(McpTransportContext.KEY);
var someReactorValue = ctx.get("reactor-context-key");
// ... use context values ...
});
});