Spring?Boot搭建Model?Context?Protocol?(MCP)?服務(wù)
?? 前言
隨著大模型(LLM)應(yīng)用的爆發(fā),如何讓模型安全、標(biāo)準(zhǔn)地連接本地數(shù)據(jù)和工具成為了核心痛點。Model Context Protocol (MCP) 是由 Anthropic 提出的一項開放標(biāo)準(zhǔn),旨在為 AI 助手與系統(tǒng)(數(shù)據(jù)庫、工具、API)之間提供通用的連接接口。
想象一下:MCP 就是 AI 時代的 USB 接口。
本文將手把手教你使用 Spring Boot 構(gòu)建一個標(biāo)準(zhǔn)的 MCP 服務(wù)(Server),通過 SSE(Server-Sent Events)和 JSON-RPC 協(xié)議,暴露本地方法供大模型調(diào)用。
??? 架構(gòu)設(shè)計
在開始寫代碼之前,我們先理清 MCP 的 HTTP 通信流程。MCP over HTTP 通常包含兩個端點:
- SSE 端點:用于建立連接,服務(wù)器通過此通道推送通知。
- POST 端點:客戶端(大模型或 MCP 宿主)通過此接口發(fā)送 JSON-RPC 請求。
通信時序圖 (Mermaid)

??? 代碼實戰(zhàn)
1. 項目初始化
創(chuàng)建一個 Spring Boot 項目 (JDK 17+),在 pom.xml 中引入必要的依賴。主要依賴是 Web 模塊和用于處理 JSON 的 Jackson。
<dependencies>
<!-- Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok (可選,簡化代碼) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 定義 JSON-RPC 模型
MCP 基于 JSON-RPC 2.0。我們需要定義請求和響應(yīng)的基礎(chǔ)類。
JsonRpcRequest.java
package com.example.mcp.model;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
@Data
public class JsonRpcRequest {
private String jsonrpc = "2.0";
private String method;
private JsonNode params;
private Object id;
}
JsonRpcResponse.java
package com.example.mcp.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonRpcResponse {
private String jsonrpc = "2.0";
private Object result;
private Object error;
private Object id;
// 成功的靜態(tài)工廠方法
public static JsonRpcResponse success(Object id, Object result) {
return new JsonRpcResponse("2.0", result, null, id);
}
}
3. 核心服務(wù)層:處理 MCP 協(xié)議邏輯
這里是核心邏輯。我們需要處理三種主要方法:
initialize:告訴客戶端我是誰,我有什能力。tools/list:列出我可以提供的工具(Function Calling 定義)。tools/call:真正執(zhí)行業(yè)務(wù)邏輯。
McpService.java
package com.example.mcp.service;
import com.example.mcp.model.JsonRpcRequest;
import com.example.mcp.model.JsonRpcResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class McpService {
private final ObjectMapper objectMapper = new ObjectMapper();
public JsonRpcResponse handleRequest(JsonRpcRequest request) {
String method = request.getMethod();
try {
switch (method) {
case "initialize":
return handleInitialize(request);
case "tools/list":
return handleListTools(request);
case "tools/call":
return handleCallTool(request);
case "notifications/initialized":
// 客戶端確認(rèn)初始化完成,通常無需回復(fù)內(nèi)容,但需保持連接
return null;
default:
throw new RuntimeException("Method not found: " + method);
}
} catch (Exception e) {
// 簡單錯誤處理
return new JsonRpcResponse("2.0", null, Map.of("code", -32603, "message", e.getMessage()), request.getId());
}
}
// 1. 握手協(xié)議
private JsonRpcResponse handleInitialize(JsonRpcRequest request) {
Map<String, Object> result = new HashMap<>();
result.put("protocolVersion", "2025-11-05");
result.put("capabilities", Map.of("tools", Map.of())); // 聲明支持工具
result.put("serverInfo", Map.of("name", "SpringBoot-MCP-Demo", "version", "1.0.0"));
return JsonRpcResponse.success(request.getId(), result);
}
// 2. 定義工具列表
private JsonRpcResponse handleListTools(JsonRpcRequest request) {
// 定義一個簡單的加法工具
Map<String, Object> addTool = Map.of(
"name", "calculate_sum",
"description", "計算兩個數(shù)字的和",
"inputSchema", Map.of(
"type", "object",
"properties", Map.of(
"a", Map.of("type", "number", "description", "第一個數(shù)字"),
"b", Map.of("type", "number", "description", "第二個數(shù)字")
),
"required", List.of("a", "b")
)
);
// 定義一個系統(tǒng)信息工具
Map<String, Object> sysInfoTool = Map.of(
"name", "get_system_info",
"description", "獲取當(dāng)前服務(wù)器運行環(huán)境信息",
"inputSchema", Map.of("type", "object", "properties", Map.of())
);
return JsonRpcResponse.success(request.getId(), Map.of("tools", List.of(addTool, sysInfoTool)));
}
// 3. 執(zhí)行工具邏輯
private JsonRpcResponse handleCallTool(JsonRpcRequest request) {
String name = request.getParams().get("name").asText();
Map<String, Object> arguments = objectMapper.convertValue(request.getParams().get("arguments"), Map.class);
String resultText = "";
if ("calculate_sum".equals(name)) {
double a = Double.parseDouble(arguments.get("a").toString());
double b = Double.parseDouble(arguments.get("b").toString());
resultText = String.valueOf(a + b);
} else if ("get_system_info".equals(name)) {
resultText = System.getProperty("os.name") + " - Java " + System.getProperty("java.version");
} else {
throw new RuntimeException("Unknown tool: " + name);
}
// MCP 要求的返回格式 content: [{type: "text", text: "..."}]
Map<String, Object> content = Map.of(
"content", List.of(Map.of("type", "text", "text", resultText))
);
return JsonRpcResponse.success(request.getId(), content);
}
}
4. Controller 層:暴露 SSE 和 HTTP 接口
這是與外部世界交互的入口。
McpController.java
package com.example.mcp.controller;
import com.example.mcp.model.JsonRpcRequest;
import com.example.mcp.model.JsonRpcResponse;
import com.example.mcp.service.McpService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/mcp")
public class McpController {
private final McpService mcpService;
// 用于保存SSE連接(生產(chǎn)環(huán)境可能需要更復(fù)雜的Session管理)
private final ConcurrentHashMap<String, SseEmitter> emitters = new ConcurrentHashMap<>();
public McpController(McpService mcpService) {
this.mcpService = mcpService;
}
/**
* 1. SSE 連接端點
* 客戶端連接到這里監(jiān)聽服務(wù)端事件
*/
@GetMapping("/sse")
public SseEmitter handleSse(HttpServletResponse response) {
// 設(shè)置 SSE 超時時間,0表示無限
SseEmitter emitter = new SseEmitter(0L);
String sessionId = UUID.randomUUID().toString();
emitters.put(sessionId, emitter);
emitter.onCompletion(() -> emitters.remove(sessionId));
emitter.onTimeout(() -> emitters.remove(sessionId));
try {
// MCP 標(biāo)準(zhǔn):連接建立后,服務(wù)端發(fā)送 endpoint 事件,告知客戶端去哪里發(fā) POST 消息
// 注意:這里硬編碼了本地地址,實際部署需改為配置的域名/IP
String endpointUrl = "/mcp/messages?sessionId=" + sessionId;
emitter.send(SseEmitter.event().name("endpoint").data(endpointUrl));
System.out.println("Client connected, session: " + sessionId);
} catch (IOException e) {
emitters.remove(sessionId);
}
return emitter;
}
/**
* 2. 消息接收端點 (POST)
* 客戶端發(fā)送 JSON-RPC 請求到這里
*/
@PostMapping("/messages")
public JsonRpcResponse handleMessage(
@RequestParam(required = false) String sessionId,
@RequestBody JsonRpcRequest request) {
System.out.println("Received: " + request.getMethod());
// 處理核心業(yè)務(wù)邏輯
JsonRpcResponse response = mcpService.handleRequest(request);
return response;
}
}
?? 測試與驗證
要驗證我們的服務(wù)是否符合 MCP 標(biāo)準(zhǔn),我們可以使用 curl 或者 Anthropic 官方提供的 mcp-inspector(如果你有 Node.js 環(huán)境)。
這里我們使用簡單的 HTTP 請求流程來模擬驗證。
1. 啟動服務(wù)
運行 Spring Boot 應(yīng)用,默認(rèn)端口 8080。
2. 模擬連接 (SSE)
在終端使用 curl 監(jiān)聽 SSE:
curl -N http://localhost:8080/mcp/sse
預(yù)期輸出:
event:endpoint data:/mcp/messages?sessionId=xxxx-xxxx-xxxx...
(保持這個窗口打開,或者記下 sessionId)
3. 發(fā)送 Initialize 請求 (POST)
打開一個新的終端窗口,發(fā)送初始化請求:
curl -X POST http://localhost:8080/mcp/messages \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "initialize",
"id": 1,
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "curl-client", "version": "1.0"}
}
}'
預(yù)期響應(yīng): 返回包含 serverInfo 和 capabilities 的 JSON。
4. 調(diào)用工具 (Tools Call)
測試我們編寫的加法工具:
curl -X POST http://localhost:8080/mcp/messages \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 2,
"params": {
"name": "calculate_sum",
"arguments": {"a": 10, "b": 25.5}
}
}'
預(yù)期響應(yīng):
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "35.5"
}
]
},
"id": 2
}
?? 總結(jié)與展望
通過上述步驟,我們成功使用 Spring Boot 構(gòu)建了一個最小化的 MCP Server。
- 我們實現(xiàn)了
tools/list,大模型通過它“看見”了我們的功能。 - 我們實現(xiàn)了
tools/call,大模型通過它“執(zhí)行”了我們的代碼。
接下來的進(jìn)階玩法:
- 連接數(shù)據(jù)庫:在
McpService中注入 MyBatis/JPA Mapper,讓 AI 可以查詢 SQL 數(shù)據(jù)。 - 集成 Spring AI:結(jié)合 Spring AI 框架,讓 Java 方法自動映射為 Tool 定義,減少手動編寫 JSON Schema 的工作量。
- 安全認(rèn)證:在 SSE 和 POST 接口增加 Token 校驗,防止未授權(quán)調(diào)用。
???? 作者提示:MCP 協(xié)議仍在快速演進(jìn)中,建議關(guān)注 Anthropic 官方文檔獲取最新規(guī)范。如果你覺得這篇文章有幫助,歡迎 點贊、收藏、關(guān)注!
到此這篇關(guān)于使用Spring Boot搭建 Model Context Protocol (MCP) 服務(wù)的實戰(zhàn)教程的文章就介紹到這了,更多相關(guān)springboot搭建mcp服務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot 使用yml配置文件給靜態(tài)變量賦值教程
這篇文章主要介紹了springboot 使用yml配置文件給靜態(tài)變量賦值教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04
SpringCloud3.x集成BigQuery的代碼實現(xiàn)
Google BigQuery 是一種高性能、可應(yīng)用于大數(shù)據(jù)分析的公主云數(shù)據(jù)庫服務(wù),Spring Cloud 提供了完善的工具和核心功能,可以進(jìn)行泛動分布應(yīng)用構(gòu)建,本文給大家介紹了SpringCloud3.x集成BigQuery的代碼實現(xiàn),需要的朋友可以參考下2025-01-01
基于Freemarker和xml實現(xiàn)Java導(dǎo)出word
這篇文章主要介紹了基于Freemarker和xml實現(xiàn)Java導(dǎo)出word,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-04-04
使用PoolingHttpClientConnectionManager實現(xiàn)http連接池過程
文章主要介紹了`PoolingHttpClientConnectionManager`實現(xiàn)HTTP連接池的原理、依賴實現(xiàn)、定時回收鏈接的方法,以及如何正確釋放連接以復(fù)用2025-11-11
若依框架升級springBoot3啟動druid-spring-boot-starter報錯問題及解決
文章主要描述了在使用Spring Boot 3時,由于缺少`com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties`類型的bean而報錯,解決方案是考慮在配置中定義該類型bean2026-01-01
SpringBoot整合MybatisPlus的基本應(yīng)用指南
MyBatis-Plus ,簡稱 MP,是一個 MyBatis的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,下面小編就來和大家介紹一下SpringBoot整合MybatisPlus的一些基本應(yīng)用吧2025-03-03
SpringBoot2.x漏洞將logback1.2.x 升級至1.3.x
安全部門在代碼漏洞掃描中發(fā)現(xiàn)logback 1.2.x版本存在CVE漏洞,建議升級至1.3.x版本,本文就來介紹了logback1.2.x 升級至1.3.x,具有一定的參考價值,感興趣的可以了解一下2024-09-09

