Files
spring-ai/mcp-server/README.md
T

4.2 KiB

mcp-server

An existing Spring service -- an order-lookup repository, nothing MCP-aware about it -- exposed as MCP tools, resources, and a prompt with @McpTool/@McpResource/@McpPrompt, served over Streamable HTTP: Spring AI 2.0's default MCP server transport.

Companion code for Build an MCP Server with Spring AI 2.0 on ankurm.com.

Versions

Spring Boot 4.1.1, Spring AI 2.0.1, Java 25 (LTS) -- same baseline as the rest of this repository.

Quickstart

./scripts/run.sh

Then, in another shell:

curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

or point MCP Inspector or Claude Desktop's MCP settings at http://localhost:8080/mcp.

What's exposed

Kind Name What it does
Tool lookup_order Looks up one order by ID; returns customer, status, items, total
Resource orders://catalog Every order ID currently in the system, one per line
Prompt summarize_order A ready-made prompt asking a model to summarize one order for support

All three live in OrderTools.java, which imports nothing MCP-specific except the three annotations -- the domain type (Order.java) and the repository behind it (OrderRepository.java) would exist whether or not MCP was ever wired in.

The one property that matters

spring:
  ai:
    mcp:
      server:
        protocol: streamable

The jar's own spring-configuration-metadata.json lists streamable as this property's default -- but leave it out of application.yml and the /mcp endpoint never gets registered at all (a plain 404, not a clearer error). See output/06-protocol-property-unset-404.txt for the real condition-evaluation log line that explains why, and the article for the full story.

Tests and captured output

OrderMcpServerTest drives the real, running server over its real Streamable HTTP transport using the official MCP Java SDK client (io.modelcontextprotocol.sdk:mcp, the same library MCP Inspector and Claude Desktop are themselves built on) -- not a mock -- and asserts on the parsed, typed results. OrderMcpTranscriptTest separately captures the literal JSON-RPC bytes that cross the wire, with a plain synchronous java.net.http.HttpClient driving the protocol by hand (initialize, read the Mcp-Session-Id header, notifications/initialized, then one request per method): an earlier version tried to capture the SDK client's own traffic with a shared servlet filter and that was intermittently flaky, because the Streamable HTTP transport writes responses during an async servlet dispatch and can multiplex more than one JSON-RPC message onto one long-lived HTTP exchange -- one request per HttpClient.send(), read back synchronously, sidesteps that instead of fighting it. Either way, every request and response quoted in the article is real, not reconstructed.

Output file What it captures
output/01-initialize-and-list-tools.txt The MCP handshake, then tools/list
output/02-call-tool-lookup-order.txt tools/call for a real order
output/03-read-resource-catalog.txt resources/read for the order catalog
output/04-get-prompt-summarize-order.txt prompts/get for the summarize prompt
output/05-call-tool-lookup-order-not-found.txt tools/call for an order ID that doesn't exist -- a tool error, not a protocol error
output/06-protocol-property-unset-404.txt Captured manually: the 404 you get without spring.ai.mcp.server.protocol set, and the real Boot condition-evaluation log line that explains it

Run mvn -o test (or ./scripts/run-all.sh) to regenerate 01 through 05.

Regenerating output

./scripts/run-all.sh