Build an MCP server in Java with Spring Boot 4 and Spring AI — expose your app's operations as tools any AI agent can call.
Product MCP Server — Spring Boot 4 + Spring AI
A small but complete Model Context Protocol (MCP) server built with Spring Boot 4 and Spring AI. It exposes a fictional product & order management app as MCP tools that any MCP-capable AI agent (Claude Desktop, Claude Code, Cursor, VS Code, …) can call to read and change data.
It is the sample project for the Tucanoo tutorial: "Building an MCP Server in Java: Connecting Your Spring Boot App to AI Agents" → https://tucanoo.com/building-an-mcp-server-in-java-connecting-your-spring-boot-app-to-ai-agents/
No LLM, no API key, no database required. An MCP server doesn't run a model — the connecting agent brings the AI. Data is held in memory and seeded at startup.
What it exposes
Tools
| Tool | Kind | What it does |
|------|------|--------------|
| search_products | read | Keyword search across the catalog |
| get_product | read | Full details of one product by SKU |
| check_stock | read | Units in stock for a SKU |
| list_orders | read | All orders, optionally filtered by status |
| get_order | read | One order by ID |
| create_order | write | Create an order (checks + decrements stock) |
| update_order_status | write | Move an order to NEW / PAID / SHIPPED / CANCELLED |
Plus one resource — catalog://products/{sku} — returning a product summary, to demonstrate the
MCP resource primitive alongside tools.
Requirements
- Java 17+ (built and tested on Java 25)
- Node.js — only to run the MCP Inspector for testing
- An MCP client to connect to (e.g. Claude Desktop for stdio, Claude Code for HTTP)
Versions
- Spring Boot 4.0.6
- Spring AI 2.0.0-RC1 — the line that targets Boot 4. It is a release candidate (pre-GA); the
version is pinned in
build.gradle. Move to2.0.0GA when it ships.
Build
./gradlew build
This produces build/libs/product-mcp-server-0.0.1-SNAPSHOT.jar.
Two transports, one starter
This project uses the single spring-ai-starter-mcp-server-webmvc starter for both transports;
a Spring profile picks which one runs.
1. stdio (the AI client launches the jar)
stdio carries the protocol over stdin/stdout, so the app must not print anything else. The stdio
profile turns off the web server, the banner, and console logging (logs go to a file instead):
java -jar build/libs/product-mcp-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=stdio
You normally don't run this yourself — the AI client does (see Claude Desktop below).
2. Streamable HTTP (a long-running web server)
java -jar build/libs/product-mcp-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=http
# MCP endpoint: http://localhost:8080/mcp
(Use --server.port=8081 if 8080 is taken.)
Test it with the MCP Inspector
stdio:
# list tools
SPRING_PROFILES_ACTIVE=stdio npx @modelcontextprotocol/inspector --cli \
java -jar build/libs/product-mcp-server-0.0.1-SNAPSHOT.jar --method tools/list
# call a tool
SPRING_PROFILES_ACTIVE=stdio npx @modelcontextprotocol/inspector --cli \
java -jar build/libs/product-mcp-server-0.0.1-SNAPSHOT.jar \
--method tools/call --tool-name search_products --tool-arg query=headphones
HTTP (start the server with the http profile first):
npx @modelcontextprotocol/inspector --cli http://localhost:8080/mcp --transport http --method tools/list
Run npx @modelcontextprotocol/inspector with no --cli for the web UI (it prints a session-token URL
in the terminal — open that).
Connect Claude Desktop (stdio)
Edit claude_desktop_config.json:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"product-store": {
"command": "java",
"args": [
"-jar",
"/ABSOLUTE/PATH/TO/product-mcp-server/build/libs/product-mcp-server-0.0.1-SNAPSHOT.jar",
"--spring.profiles.active=stdio"
]
}
}
}
Use an absolute jar path, then fully quit and reopen Claude Desktop. Try: "Search the store for headphones," or "Create an order for 2 of SKU-1001 for Jane Doe, then mark it shipped."
Connect Claude Code (HTTP)
Start the server with the http profile, then:
claude mcp add --transport http product-store http://localhost:8080/mcp
# then, inside Claude Code: /mcp to confirm it's connected
Project layout
src/main/java/com/tucanoo/productmcpserver/
├── domain/ Product, Order, OrderLine, OrderStatus (Lombok)
├── service/ CatalogService, OrderService — seeded in-memory data (the "real app")
└── mcp/ ProductTools, OrderTools (@McpTool), CatalogResources (@McpResource)
src/main/resources/
├── application.yaml base config (server name/version, capabilities)
├── application-stdio.yaml stdio profile (web off, stdout kept clean)
└── application-http.yaml http profile (Streamable HTTP on :8080)