Spring AI’s Model Context Protocol: Architecture, Code Walkthrough & Debugging
This article provides a comprehensive analysis of Spring AI’s Model Context Protocol (MCP), covering its layered client‑server architecture, core interaction sequences, key source‑code components, and step‑by‑step debugging of the SSE‑based initialization flow, enabling developers to integrate AI capabilities into Java applications with confidence.
Introduction
In the past year the large‑model ecosystem has shifted from simple model calls to collaborative, tool‑enabled interactions. Spring AI was created to embed AI capabilities into enterprise‑grade Java applications using the Model Context Protocol (MCP), which standardises how models communicate with external resources.
Architecture Overview
Spring AI MCP follows a highly symmetrical, layered design for both client and server. The layers are:
Entry Layer : Unified entry point represented by McpClient and McpServer. Factory methods create the runtime layer instances with configuration such as time‑outs and capabilities.
Runtime Layer : Core control centre implemented by classes like McpAsyncClient, McpSyncClient, McpAsyncServer and McpSyncServer. Handles initialization, tool registration and tool invocation, using reactive programming for async flows.
Session Management Layer (server‑only): Manages multiple client sessions via McpServerTransportProvider, handling lifecycle, broadcasting, and resource cleanup.
Session Layer : Context carrier between client and server, implemented by McpClientSession and McpServerSession. Encodes messages as JSON‑RPC, decoupling business logic from transport.
Transport Layer : Actual data transmission and (de)serialization, defined by McpClientTransport and McpServerTransport. Supports Stdio, HTTP + SSE, and other protocols.
Both sides share the same four layers (entry, runtime, session, transport); the server adds the session‑management layer. The following images illustrate the client and server layer diagrams:
Core Interaction Process
3.1 Client/Server Construction and Connection
The server is built by configuring the entry layer (e.g., time‑outs, capabilities) and invoking its factory method to obtain a runtime instance.
The client follows the same steps but also creates a session layer object and then calls the transport layer to establish an SSE connection. The server’s session‑management layer receives the GET request, creates a session object, and returns a message‑endpoint URL for subsequent interactions.
3.2 Initialization Process
Initialization consists of two phases: initialisation establishment and initialisation completion notification . During establishment the client sends a POST request containing a JSON‑RPC initialisation request; the server stores the session state as STATE_INITIALIZING, replies with server version and capabilities, and the transport layer pushes the response via SSE.
After the client receives the response it sends a second POST notification signalling completion. The server updates the session state to STATE_INITIALIZED and processes the notification (no further action in the default implementation).
3.3 Tool Discovery and Invocation
Tool discovery follows the same request‑response pattern: the client sends a JSON‑RPC request, the server responds with a list of available tools, and the client receives the list through the SSE transport.
Tool invocation is analogous; the server’s session layer executes the requested tool logic and returns the result via the transport layer.
Key Source Code Snippets
POM Dependencies
<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>Server Configuration
@Configuration
@EnableWebMvc
public class McpServerConfig implements WebMvcConfigurer {
@Bean
public HttpServletSseServerTransportProvider servletSseServerTransportProvider() {
return HttpServletSseServerTransportProvider.builder()
.objectMapper(new ObjectMapper())
.messageEndpoint("/mcp/message")
.build();
}
@Bean
public ServletRegistrationBean<HttpServletSseServerTransportProvider> customServletBean(HttpServletSseServerTransportProvider transportProvider) {
return new ServletRegistrationBean<>(transportProvider);
}
}Main Application
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(McpServerApplication.class, args);
HttpServletSseServerTransportProvider transportProvider =
context.getBean(HttpServletSseServerTransportProvider.class);
var schema = """
{"type":"object","id":"urn:jsonschema:Operation","properties":{
"operation":{"type":"string"},
"a":{"type":"number"},
"b":{"type":"number"}}}
""";
var syncToolSpecification = 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);
}
}Runtime Breakpoint Tracing
The article demonstrates how to set breakpoints in the server’s HttpServletSseServerTransportProvider.doPost() method to observe the initialization establishment and completion notifications. Screenshots (omitted here) show the POST request to /mcp/message, the handling in handleIncomingRequest(), state transition to STATE_INITIALIZING, and the final SSE response sent by HttpServletMcpSessionTransport.sendMessage(). The completion notification follows a similar path, ending with the session state set to STATE_INITIALIZED.
Summary and Reflections
Spring AI’s MCP implementation showcases a highly consistent and elegantly abstracted architecture. The symmetrical client‑server design, clear separation of concerns across layers, and the use of JSON‑RPC over SSE provide a low‑coupling yet powerful framework for building AI‑augmented services. This design aligns with Spring’s “interface‑driven” philosophy and positions MCP as a potential foundational component for future modular AI platforms.
Understanding the source code and runtime behaviour of MCP equips developers with the knowledge to extend the protocol—such as adding custom transports, new tool‑registration mechanisms, or additional capabilities—making it a versatile bridge between large language models and enterprise systems.
References
Spring AI MCP浅析 – CSDN Blog
SpringAI(GA):MCP源码解读
GitHub – modelcontextprotocol/sdk/java
Deep Dive into MCP Protocol – Bilibili
modelcontextprotocol.io/sdk/java/mc…
AI Architecture Hub
Focused on sharing high-quality AI content and practical implementation, helping people learn with fewer missteps and become stronger through AI.
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.
