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.
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.
- 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)
- 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.
./gradlew buildThis produces build/libs/product-mcp-server-0.0.1-SNAPSHOT.jar.
This project uses the single spring-ai-starter-mcp-server-webmvc starter for both transports;
a Spring profile picks which one runs.
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=stdioYou normally don't run this yourself — the AI client does (see Claude Desktop below).
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.)
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=headphonesHTTP (start the server with the http profile first):
npx @modelcontextprotocol/inspector --cli http://localhost:8080/mcp --transport http --method tools/listRun npx @modelcontextprotocol/inspector with no --cli for the web UI (it prints a session-token URL
in the terminal — open that).
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."
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 connectedsrc/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)