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.

SpringMeng
SpringMeng
SpringMeng
Building an MCP Service with Spring Boot 3 and OpenSpec: A Hands‑On Demo

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 demonstration

5. 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 tool

7. 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/mcp

In 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaMCPapi-designSSEJSON-RPCOpenSpecSpringBoot 3
SpringMeng
Written by

SpringMeng

Focused on software development, sharing source code and tutorials for various systems.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.