Building an MCP Service with Spring Boot 3 and OpenSpec: A Hands‑On Demo
This article walks through the complete development of a minimal Model Context Protocol (MCP) server using Spring Boot 3 and Server‑Sent Events, illustrating how OpenSpec drives the workflow from proposal and task breakdown to design, implementation, testing, and integration with Claude Code.
1. MCP protocol overview
MCP (Model Context Protocol) is an open protocol defined by Anthropic for AI‑to‑external‑data interactions. It defines three core capabilities:
Tools – functions the AI can invoke.
Resources – data the AI can read.
Prompts – predefined prompt templates.
The protocol uses JSON‑RPC 2.0 for message exchange, keeping the transport layer independent.
2. OpenSpec development flow
OpenSpec structures development into five artifacts:
Proposal – records design decisions and trade‑offs.
Tasks – breaks the proposal into concrete work items.
Design – defines class structures, interfaces, and data flow.
Implementation – writes the actual code.
Verification – tests the implementation and archives results.
3. Functional goal – MCP demo service
The demo implements a minimal MCP server with SpringBoot 3 and Server‑Sent Events (SSE). Required features:
Support for the Tools capability (initialize, tools/list, tools/call).
Clear code structure suitable for demonstration.
Use SSE as the transport layer.
4. Prompt template (proposal generation)
I want to build an MCP server demo with SpringBoot3 + SSE, please create a proposal.
Requirements:
- Implement Tools capability (initialize, tools/list, tools/call)
- Provide a sample tool: get_calendar_events
- Keep the code structure clear for demonstration5. Transport layer selection
Three options were considered:
Stdio – simplest but unsuitable for a web application.
WebSocket – full‑duplex but more complex to implement.
SSE – HTTP long‑polling, easy to implement, matches MCP’s push model.
The demo chooses SSE.
6. Core abstractions
Only the Tools capability is implemented initially. The protocol methods are:
initialize – handshake and protocol version negotiation
tools/list – return all available tools
tools/call – invoke a specific tool7. Detailed design
7.1 Data model (JSON‑RPC)
public class JsonRpcRequest {
private String jsonrpc = "2.0";
private String method;
private Object params;
private Object id;
}
public class JsonRpcResponse {
private String jsonrpc = "2.0";
private Object result;
private JsonRpcError error;
private Object id;
}7.2 Protocol handler
public JsonRpcResponse handle(JsonRpcRequest request) {
switch (request.getMethod()) {
case "initialize":
return handleInitialize(request);
case "tools/list":
return handleListTools(request);
case "tools/call":
return handleCallTool(request);
default:
return JsonRpcResponse.error(-32601, "Method not found", request.getId());
}
}7.3 SSE controller
@PostMapping(value = "/mcp", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle(@RequestBody JsonRpcRequest request) {
SseEmitter emitter = new SseEmitter(60_000L);
CompletableFuture.runAsync(() -> {
try {
JsonRpcResponse response = mcpHandler.handle(request);
emitter.send(SseEmitter.event()
.data(objectMapper.writeValueAsString(response)));
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}7.4 Tool interface
public interface McpTool {
String getName(); // tool name
String getDescription(); // description shown to the AI
Map<String, Object> getInputSchema(); // argument schema
String execute(Map<String, Object> arguments); // execution logic
}7.5 Tool registry
@Component
public class ToolRegistry {
private final Map<String, McpTool> tools = new ConcurrentHashMap<>();
@Autowired
public void registerAllTools(List<McpTool> toolList) {
toolList.forEach(tool -> tools.put(tool.getName(), tool));
}
public Optional<McpTool> getTool(String name) {
return Optional.ofNullable(tools.get(name));
}
}7.6 Sample tool – GetCalendarEventsTool
@Component
public class GetCalendarEventsTool implements McpTool {
@Override
public String getName() { return "get_calendar_events"; }
@Override
public String getDescription() { return "Query calendar events for a specific date"; }
@Override
public Map<String, Object> getInputSchema() {
return Map.of("date", "string");
}
@Override
public String execute(Map<String, Object> arguments) {
String date = (String) arguments.get("date");
// Dummy implementation – in a real project this would query a calendar service
return date + " has 3 meetings";
}
}8. Implementation steps
Generate a SpringBoot project with start.spring.io and add the Spring Web dependency.
Create the JSON‑RPC request/response POJOs.
Implement McpHandler with the routing logic shown above.
Write the SSE controller to wrap the handler response in an SseEmitter.
Define the McpTool interface and the ToolRegistry component.
Implement the sample GetCalendarEventsTool and let Spring auto‑register it.
Run the application with ./gradlew bootRun (default port 8080).
9. Verification and archiving
Two test scenarios are executed:
Forward test – list tools and call the sample tool.
→ POST /mcp {"method":"tools/list","id":1}
← {"jsonrpc":"2.0","result":{"tools":[{"name":"get_calendar_events",...}]},"id":1}
→ POST /mcp {"method":"tools/call","params":{"name":"get_calendar_events","arguments":{"date":"2026-02-10"}},"id":2}
← {"jsonrpc":"2.0","result":{"content":[{"type":"text","text":"2026-02-10 has 3 meetings"}]},"id":2}Error test – invoke an unknown method.
→ POST /mcp {"method":"unknown","id":1}
← {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":1}After successful tests, the project is archived with the command:
openspec archive --change "<change-name>"10. Using the MCP server in Claude Code
Start the server ( ./gradlew bootRun).
Add the service to Claude:
claude mcp add --transport http --scope user calendar-mcp http://localhost:8080/mcpIn a Claude Code conversation, request the tool:
Please call the get_calendar_events tool to fetch today’s schedule.Claude discovers the tool via the MCP endpoint and returns the result.
11. Conclusion
The guide demonstrates how a SpringBoot 3 + SSE application can serve as a minimal MCP server, driven by OpenSpec’s artifact‑centric workflow. The step‑by‑step process—from proposal generation, transport selection, and architectural design to concrete implementation and verification—provides a reproducible template for AI‑assisted backend development.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
SpringMeng
Focused on software development, sharing source code and tutorials for various systems.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
