# 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](https://ankurm.com/spring-ai-2-0-mcp-server-streamable-http/) on [ankurm.com](https://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](https://github.com/modelcontextprotocol/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`](src/main/java/com/ankurm/mcpserver/tools/OrderTools.java), which imports nothing MCP-specific except the three annotations -- the domain type ([`Order.java`](src/main/java/com/ankurm/mcpserver/domain/Order.java)) and the repository behind it ([`OrderRepository.java`](src/main/java/com/ankurm/mcpserver/domain/OrderRepository.java)) would exist whether or not MCP was ever wired in. ## The one property that matters ```yaml 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`](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 ```