
This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.
Spring AI brings MCP to Spring Boot through boot starters. Add the MCP client starter, list servers in application.yml, and every server's tools arrive as a ToolCallbackProvider you hand to a ChatClient. Add an MCP server starter, annotate methods with @McpTool, and your Spring app becomes an MCP server.
This tutorial builds both sides: a client that calls a local server, and a server with one annotated tool. Code was checked against the Spring AI MCP reference pages and the official spring-ai-examples repository on 2 November 2026. Maven Central's newest stable Spring AI release that day was 2.0.2, with 2.1.0-RC1 also published. The official examples target Spring Boot 4 and Spring AI 2.x.
Key Takeaways
- Client: add
spring-ai-starter-mcp-clientand configure connections underspring.ai.mcp.client; tools become aToolCallbackProviderbean. - Spring AI recommends the WebFlux-based client starter,
spring-ai-starter-mcp-client-webflux, for production HTTP connections. - Server: annotate methods on a Spring bean with
@McpTooland@McpToolParam; the starter registers them automatically. - Pick the server transport with
spring.ai.mcp.server.protocol:STREAMABLEorSTATELESS; SSE is deprecated since 2.0.0. - stdio servers must keep stdout clean: no web server, no banner, no console logging.
Prerequisites
- Java 17 or later and Maven or Gradle.
- A Spring Boot 4 project with the Spring AI BOM.
- A chat model starter, such as
spring-ai-starter-model-anthropic, plus its API key. - Node.js if you want to run npm-packaged MCP servers or the MCP Inspector.
Part 1: The MCP client
Add the starter
From the MCP Client Boot Starter docs:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
The docs say this starter connects over stdio, SSE, Streamable HTTP and Stateless Streamable HTTP, using the JDK HttpClient. "For production deployment, we recommend using the WebFlux-based" starter, spring-ai-starter-mcp-client-webflux.
Configure servers
spring:
ai:
mcp:
client:
type: SYNC
request-timeout: 30s
streamable-http:
connections:
notes:
url: http://localhost:8080
endpoint: /mcp
stdio:
connections:
filesystem:
command: npx
args:
- -y
- "@modelcontextprotocol/server-filesystem"
- /path/to/files
Notes:
streamable-http.connections.[name].urlis the base URL;endpointis the path and defaults to/mcp.typeisSYNCby default, orASYNCfor reactive apps. You "cannot mix sync and async clients."- You can point
stdio.servers-configurationat a Claude Desktop style JSON file instead of listing connections in YAML.
Use the tools in a ChatClient
The default client example injects the auto-configured ToolCallbackProvider:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner ask(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {
return args -> {
var chatClient = chatClientBuilder.defaultTools(tools).build();
System.out.println(chatClient.prompt("What tools are available?").call().content());
};
}
}
The tool callback auto-configuration is on by default. Set spring.ai.mcp.client.toolcallback.enabled=false if you want the raw McpSyncClient beans without tool callbacks.
Filter tools and avoid name clashes
The client docs describe tool filtering and tool name prefix generation, so two servers' search tools do not collide. Filter down to the tools each ChatClient needs. Every tool you pass adds a description to every model call; see too many MCP tools.
Windows: the cmd.exe wrapper
The docs explain that on Windows, npx, npm and node are batch files, and "Java's ProcessBuilder cannot execute batch files directly." Wrap them:
{
"mcpServers": {
"filesystem": {
"command": "cmd.exe",
"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "C:\\Users\\username\\Desktop"]
}
}
}
Part 2: An MCP server with @McpTool
Choose a starter and protocol
From the MCP Server Boot Starter docs:
| Transport | Starter | Property |
|---|---|---|
| stdio | spring-ai-starter-mcp-server | spring.ai.mcp.server.stdio=true |
| Streamable HTTP (WebMVC) | spring-ai-starter-mcp-server-webmvc | spring.ai.mcp.server.protocol=STREAMABLE |
| Stateless (WebMVC) | spring-ai-starter-mcp-server-webmvc | spring.ai.mcp.server.protocol=STATELESS |
| Streamable HTTP (WebFlux) | spring-ai-starter-mcp-server-webflux | spring.ai.mcp.server.protocol=STREAMABLE |
SSE variants still exist but are marked "deprecated since 2.0.0, use STREAMABLE instead." The Streamable HTTP server's endpoint defaults to /mcp. Our stdio vs Streamable HTTP guide helps you choose.
Write a tool
Keep each tool small and specific. A tool that does one job, with a clear name and description, is easier for the model to pick correctly than a general tool with a mode parameter. Spring AI builds the input schema from the method parameters and their @McpToolParam descriptions, so write those descriptions for the model.
The MCP annotations docs show @McpTool on a Spring bean. The import path below comes from the official weather server example:
import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.ai.mcp.annotation.McpToolParam;
import org.springframework.stereotype.Component;
@Component
public class CalculatorTools {
@McpTool(name = "add", description = "Add two numbers together")
public int add(
@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b) {
return a + b;
}
}
The weather examples say the starter "discovers all @McpTool-annotated methods automatically," so no manual bean wiring is needed.
Add a second tool, with hints
@McpTool also takes a title, generateOutputSchema, and MCP tool annotations:
@McpTool(name = "calculate-area",
description = "Calculate the area of a rectangle",
title = "Rectangle Area Calculator",
generateOutputSchema = true,
annotations = @McpTool.McpAnnotations(
title = "Rectangle Area Calculator",
readOnlyHint = true,
destructiveHint = false,
idempotentHint = true
))
public AreaResult calculateRectangleArea(
@McpToolParam(description = "Width", required = true) double width,
@McpToolParam(description = "Height", required = true) double height) {
return new AreaResult(width * height, "square units");
}
Mark read-only tools honestly. Clients may skip approval prompts for them. Our tool annotations guide explains how hints are used.
Configure a Streamable HTTP server
For an HTTP server on Spring MVC, add spring-ai-starter-mcp-server-webmvc and set the protocol:
spring.ai.mcp.server.name=calculator-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.protocol=STREAMABLE
The endpoint is /mcp unless you change mcp-endpoint. Use STATELESS for the simplest deployments; the docs describe stateless servers as "designed for simplified deployments where session state is not maintained between requests." Set spring.ai.mcp.server.type=ASYNC if the rest of your app is reactive.
Tools can also send progress and log messages. The annotations docs show a tool that takes an McpSyncRequestContext parameter and calls context.info(...) and context.progress(...) while it works, which helps clients show status on slow calls.
Keep stdout clean for stdio
For a stdio server, the official example sets:
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
spring.ai.mcp.server.name=my-weather-server
spring.ai.mcp.server.version=0.0.1
logging.file.name=./target/mcp-weather-stdio-server.log
stdout carries the protocol. A Spring banner or a console log line will corrupt it.
Test with the MCP Inspector
For a Streamable HTTP server on port 8080:
npx @modelcontextprotocol/inspector --cli http://localhost:8080/mcp --transport http --method tools/list
For a stdio server, pass the launch command instead:
npx @modelcontextprotocol/inspector --cli java -jar target/your-server.jar --method tools/list
Our MCP Inspector lesson shows how to call tools and script these checks.
Deploy notes
- Use the WebFlux client starter for production HTTP connections, as the docs recommend.
- Pick
STATELESSfor servers behind load balancers or on serverless platforms; the docs call stateless servers "ideal for microservices architectures and cloud-native deployments." - Protect HTTP servers with Spring Security or a gateway. Our OAuth for remote MCP servers guide covers the flow.
Pitfalls
- Old artifact names. Some older READMEs still mention
spring-ai-mcp-server-webmvc-spring-boot-starter. The current reference usesspring-ai-starter-mcp-server-webmvc. - Mixing SYNC and ASYNC. Pick one client type per app.
- Copied server packages. Example configs name specific npm servers. Check that any server you copy is still published and maintained before you depend on it.
- Logs on stdout in stdio mode. Redirect logging to a file.
Next step
Take the free MCP course for the protocol basics behind the starters. If you want a Java MCP server for your internal systems, built with auth and audit logging, see our MCP development service.
More in the MCP Guide
Free tools: server.json Validator · MCP Error Code Lookup
Latest research: State of MCP: October 2026
Frequently asked questions
Which Spring AI version should I use for MCP?
Use a current 2.x release. Maven Central listed 2.0.2 as the newest stable Spring AI release on 2 November 2026, and the official MCP examples target Spring Boot 4 and Spring AI 2.x.
Do I need the Java MCP SDK directly?
Usually not. The boot starters wrap it and auto-configure clients and servers from properties. Drop to the SDK only when you need manual control.
Can one Spring app be both an MCP client and server?
Yes. Add both starters. Keep the client's tool callbacks separate from the tools your server exposes, so you do not re-export third-party tools by accident.
Why does my stdio server fail when Claude Desktop starts it?
Usually stdout is not clean. Turn off the web server and banner, empty the console log pattern, and send logs to a file, as in the official stdio example.











