How Spring AI Implements the Model Context Protocol: A Deep Architecture Walkthrough

This article provides a detailed, source‑code‑level analysis of Spring AI’s Model Context Protocol (MCP) implementation, covering its layered architecture, core client‑server interaction steps, key components, runtime debugging with SSE, and practical code examples for building and testing MCP services in Java.

Architect
Architect
Architect
How Spring AI Implements the Model Context Protocol: A Deep Architecture Walkthrough

1. Introduction

In the past year the large‑model ecosystem has shifted from simple model calls to model collaboration, where models need execution, coordination, and plug‑in capabilities. Spring AI was created to embed AI functions into enterprise Java applications using the familiar Spring programming model.

Spring AI is an AI‑integration framework for the Java ecosystem. It abstracts different model providers (OpenAI, Anthropic, Ollama, etc.) behind a unified API and adds higher‑level concepts such as prompt templates, output parsing, and tool invocation.

Since version 1.0.0 (Spring AI 2024), Spring AI supports the Model Context Protocol (MCP), a standard that defines how a model can communicate with external resources (plugins, databases, APIs, file systems) in a controlled, safe, and extensible way.

2. Architecture Overview

The MCP client and server share a highly symmetric, layered design. Both consist of four layers – Entry, Runtime, Session, and Transport – while the server adds an extra Session‑Management layer to handle multiple concurrent client sessions.

The layers and their responsibilities are:

Entry Layer : Unified entry point (interfaces McpClient / McpServer). A factory creates runtime instances with configuration (timeouts, capabilities, etc.).

Runtime Layer : Core control (classes McpAsyncClient, McpSyncClient, McpAsyncServer, McpSyncServer). Handles initialization, tool registration, and tool invocation, using reactive programming for async flows.

Session‑Management Layer (server‑only): Manages active client sessions (interface McpServerTransportProvider), broadcasting, and resource cleanup.

Session Layer : Context carrier between client and server (classes McpClientSession, McpServerSession). Encodes messages as JSON‑RPC, decoupling business logic from transport.

Transport Layer : Handles actual data transmission and serialization (interfaces McpClientTransport, McpServerTransport). Provides implementations such as Stdio, HTTP + SSE, and streamable HTTP.

3. Core Interaction Process

The interaction consists of three phases, demonstrated with the SSE transport:

Client/Server Construction & Connection : The client builds a runtime instance via the entry layer, then creates a session object and calls the transport to send a GET request, establishing an SSE long‑running connection. The server’s Session‑Management layer receives the GET, creates a session, and returns a message‑endpoint URL.

Initialization : Two sub‑steps – initialization establishment (client POSTs a JSON‑RPC request, server marks state STATE_INITIALIZING) and initialization completion notification (client sends a notification, server switches to STATE_INITIALIZED). Both steps use the Transport layer to send POST requests to /mcp/message.

Tool Discovery & Invocation : The client sends a discovery request; the server responds with a JSON‑RPC payload containing available tools. For tool invocation, the server’s session layer executes the tool logic and returns the result via the same transport.

Each phase is illustrated by diagrams (omitted here for brevity) and by the following code snippets.

3.1 Client/Server Construction Example

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>io.modelcontextprotocol.sdk</groupId>
  <artifactId>mcp</artifactId>
  <version>0.10.0</version>
</dependency>
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;

@Configuration
@EnableWebMvc
public class McpServerConfig implements WebMvcConfigurer {
    @Bean
    public HttpServletSseServerTransportProvider servletSseServerTransportProvider() {
        return HttpServletSseServerTransportProvider.builder()
                .objectMapper(new ObjectMapper())
                .messageEndpoint("/mcp/message")
                .build();
    }
    @Bean
    public ServletRegistrationBean customServletBean(HttpServletSseServerTransportProvider transportProvider) {
        return new ServletRegistrationBean(transportProvider);
    }
}

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;

@SpringBootApplication
public class McpServerApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(McpServerApplication.class, args);
        HttpServletSseServerTransportProvider transportProvider =
                context.getBean(HttpServletSseServerTransportProvider.class);
        String schema = """
                {
                  \"type\": \"object\",
                  \"id\": \"urn:jsonschema:Operation\",
                  \"properties\": {
                    \"operation\": {\"type\": \"string\"},
                    \"a\": {\"type\": \"number\"},
                    \"b\": {\"type\": \"number\"}
                  }
                }
                """;
        var syncToolSpecification = new McpServerFeatures.SyncToolSpecification(
                new McpSchema.Tool("calculator", "Basic calculator", schema),
                (exchange, arguments) -> {
                    String operation = (String) arguments.get("operation");
                    double a = Double.parseDouble(String.valueOf(arguments.get("a")));
                    double b = Double.parseDouble(String.valueOf(arguments.get("b")));
                    double resultValue;
                    switch (operation) {
                        case "add": resultValue = a + b; break;
                        case "subtract": resultValue = a - b; break;
                        case "multiply": resultValue = a * b; break;
                        case "divide":
                            if (b == 0) return new McpSchema.CallToolResult("Error: Division by zero", true);
                            resultValue = a / b; break;
                        default:
                            return new McpSchema.CallToolResult("Error: Unknown operation '" + operation + "'", true);
                    }
                    return new McpSchema.CallToolResult(String.valueOf(resultValue), false);
                });
        McpSyncServer syncServer = McpServer.sync(transportProvider)
                .serverInfo("my-server", "1.0.0")
                .capabilities(McpSchema.ServerCapabilities.builder()
                        .resources(true, true)
                        .tools(true)
                        .prompts(true)
                        .logging()
                        .completions()
                        .build())
                .build();
        syncServer.addTool(syncToolSpecification);
    }
}

3.2 Initialization Process

During initialization establishment , the client sends a POST to /mcp/message with a JSON‑RPC request containing client version and capabilities. The server’s Session‑Management layer records the session ID, sets its state to STATE_INITIALIZING, and replies with server information via the Transport layer (SSE).

When the client sends the initialization completed notification, the server transitions to STATE_INITIALIZED. The notification handler is a no‑op (implemented as Mono::empty) because the state change alone is sufficient.

3.3 Tool Discovery & Invocation

Tool discovery follows the same request/response pattern: the client POSTs a JSON‑RPC request, the server responds with a list of registered tools (e.g., the calculator tool defined above). Tool invocation adds a step where the server’s Session layer executes the tool logic (the switch statement) and returns the result through the Transport layer.

4. Source‑Code Deep Dive

The article examines the actual classes involved (e.g., McpAsyncServer, McpClientTransport, HttpServletSseServerTransportProvider) and shows how they are wired together via lambda handlers. The class diagram (omitted) reflects the symmetric client/server hierarchy.

5. Runtime Debugging with Breakpoints

To observe the interaction, the article suggests using the MCP Inspector tool (a graphical client) after launching the server. By setting breakpoints in HttpServletSseServerTransportProvider.doPost() and the session‑handling methods ( handleIncomingRequest, handleIncomingNotification), developers can watch the state transitions and message payloads in real time.

Typical debugging steps:

Start the Spring Boot server with the configuration above.

Run npx @modelcontextprotocol/inspector to launch the inspector UI.

Click “Connect” to trigger the GET request and establish the SSE channel.

Observe the POST requests for initialization, tool discovery, and tool invocation in the debugger.

6. Conclusion & Outlook

Spring AI’s MCP implementation demonstrates a highly consistent, decoupled architecture that mirrors the classic Spring philosophy of interface‑driven design. The layered approach makes the protocol easy to understand, extend, and debug, while the use of JSON‑RPC and SSE provides a clean separation between business logic and transport.

Beyond the current use‑case, MCP can serve as a foundation for future AI platforms where models become controllable services rather than opaque black boxes. As the protocol matures, it is poised to evolve from an experimental framework into a core infrastructure component for AI‑enabled applications.

JavaMCPSpring AIModel Context Protocol
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.