Mastering Model Context Protocol (MCP) with Spring AI: From Theory to Hands‑On Implementation
This article explains the Model Context Protocol (MCP) introduced by Anthropic, compares it with Function Call, details its architecture and communication flow, and provides a step‑by‑step Spring AI guide—including server and client development, configuration, code examples, and testing—so developers can integrate AI models with back‑end services efficiently.
Overview
Large language models such as ChatGPT and DeepSeek require sufficient context to answer questions accurately. Manually supplying this context is cumbersome, so an intermediary program is needed to let the model retrieve context automatically.
Function Call
Before MCP, OpenAI introduced Function Call, a JSON‑based standard that describes tool specifications and enables the model to invoke external functions.
Model Context Protocol (MCP)
MCP (Model Context Protocol) is an open communication standard released by Anthropic in November 2024. It defines how applications exchange context with AI models, providing a standardized toolbox that allows models to interact with external tools, data sources, and services.
Principles
MCP Architecture
Host : receives user queries and interacts with the LLM.
MCP client : communicates with the MCP server using the MCP protocol.
MCP server : lightweight service exposing specific capabilities.
Local data source : files, databases, or services accessible to the server.
Remote service : external APIs reachable by the server.
MCP Call Process
User asks a question to the Host.
Host sends the question, the list of available MCP tools, and usage instructions to the LLM.
LLM selects a tool and returns the tool name and parameters.
Host starts the MCP client.
MCP client packages the request in MCP format and sends it to the MCP server.
MCP server accesses the required data source, packages the response in MCP format, and returns it.
Client forwards the response to the Host.
Host includes the MCP call context in the next LLM request.
LLM produces the final answer, which the Host returns to the user.
MCP Packet Analysis
Unlike Function Call, MCP embeds the full tool description directly in the prompt, which can reach tens of thousands of characters, making the request size much larger.
Transmission Mechanisms
MCP defines two standard transports, both using JSON‑RPC:
Stdio : the client launches the server as a subprocess; communication occurs via stdin/stdout.
HTTP with SSE : the server runs as an HTTP service exposing an SSE endpoint for push messages and a POST endpoint for client‑to‑server requests.
Key differences between the two mechanisms:
Location: Stdio runs locally; HTTP with SSE can be local or remote.
Client count: Stdio supports a single client; HTTP with SSE supports multiple clients.
Performance: Stdio offers low latency; HTTP with SSE adds network latency.
Complexity: Stdio is simpler; HTTP with SSE requires an HTTP server.
Security: Stdio relies on local permissions; HTTP with SSE needs explicit security measures such as TLS.
Deployment: Stdio is installed per user; HTTP with SSE is centrally deployed.
Practice
Implementation using Spring AI.
Prerequisites
JDK 17
Spring Boot 3.4.4
Maven
LLM: Qwen2.5‑72B‑Instruct
MCP Server Development
Core dependencies (choose one):
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
<version>1.0.0-M7</version>
</dependency>
<!-- Spring MVC SSE -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>1.0.0-M7</version>
</dependency>
<!-- Spring WebFlux SSE -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
<version>1.0.0-M7</version>
</dependency>Configuration files (choose one):
# Stdio configuration (application.yml)
spring:
ai:
mcp:
server:
name: mcp-server
version: 1.0.0
type: SYNC # SYNC or ASYNC # SSE configuration (application.yml)
spring:
ai:
mcp:
server:
name: mcp-server
version: 1.0.0
type: SYNC
sse-message-endpoint: /mcp/messagesTool implementation using the @Tool annotation:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
@Service
public class DateTimeTools {
private final Logger logger = LoggerFactory.getLogger(DateTimeTools.class);
@Tool(description = "Get the current date‑time")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
@Tool(description = "Set an alarm, ISO‑8601 formatted time required")
void setAlarm(String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
// System.out.println("Alarm set for " + alarmTime);
}
}Register the tools:
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ToolsConfig {
@Bean
public ToolCallbackProvider tools(DateTimeTools dateTimeTools) {
return MethodToolCallbackProvider.builder().toolObjects(dateTimeTools).build();
}
}Testing can be performed with tools such as Cherry Studio, which allows selecting the configured MCP server and invoking the defined tools.
MCP Client Development
Client dependencies (choose one):
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>1.0.0-M7</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
<version>1.0.0-M7</version>
</dependency>Additional dependencies for LLM interaction (example with OpenAI) and web support:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.0.0-M7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>Configuration for Stdio:
server:
port: 8081
spring:
ai:
mcp:
client:
toolcallback:
enabled: true
stdio:
root-change-notification: true
connections:
server1:
command: java
args:
- -jar
- /path/to/mcp-server.jar
# Disable banner and logging for the server when using Stdio
spring:
main:
banner-mode: off
logging:
level:
root: offConfiguration for SSE:
spring:
ai:
mcp:
client:
toolcallback:
enabled: true
sse:
connections:
server1:
url: http://localhost:8080
spring:
ai:
openai:
base-url: YOUR_BASE_URL
api-key: YOUR_API_KEY
chat:
options:
model: YOUR_MODELDeclare the ChatClient bean:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider mcpTools) {
return builder.defaultTools(mcpTools).build();
}
}Controller that forwards user input to the LLM:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import jakarta.servlet.http.HttpServletResponse;
@RestController
public class DateTimeController {
@Autowired
private ChatClient chatClient;
@GetMapping("/chat")
public String chat(String input) {
return chatClient.prompt().user(input).call().content();
}
@GetMapping("/chat/stream")
public Flux<String> streamChat(HttpServletResponse response, String input) {
response.setCharacterEncoding("UTF-8");
return chatClient.prompt().user(input).stream().content();
}
}When using Stdio, ensure the server does not write non‑protocol data to stdout; disable banner and logging as shown above.
Testing
Start the MCP server (Stdio or SSE), then start the MCP client service. Access the client endpoints (e.g., /chat?input=What+time+is+it?) and observe that the client invokes the server‑side tools (current time, alarm setting) and returns the model’s answer.
Outlook
MCP acts as a bridge between traditional back‑end systems and AI models, offering a promising new direction for back‑end developers. Although still in its early stages—with immature tooling and high token consumption—it is expected to mature into a foundational layer for AI‑enabled services as the technology evolves.
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.
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.
