文章目錄
- 1_調用公用MCP
- 2_Stdio方式
- 3_Stdio實現原理
- 4_SSE方式
- 5_自定義MCP客戶端
- 6_MCP Server權限控制
SpringAI 通過 SpringBoot 集成擴展了 MCP Java SDK ,提供了客戶端和服務端 starter,讓 AI 應用程序快速支持 MCP。
接下來直接演示。
1_調用公用MCP
在使用其他開發者提供好的 MCP 服務時,僅僅引入 MCP 的客戶端依賴即可。
SpringAI 提供了如下兩種 MCP Client 依賴,根據需求選擇一種引入即可(常見第二種)。
僅支持 Stdio 的依賴
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
既支持 SSE(遠程)也支持 Stdio(進程內)
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
以使用百度地圖提供的 MCP 服務為例,演示如何使用 Stdio 的方式調用公用的 MCP Server。
首先,對 MCP 進行配置,Stdio 的配置方式有兩種。
一種是直接配置全局配置文件中:
spring:ai:mcp:client:enabled: truename: spring-ai-mcp-clientversion: 1.0.0type: sync # 或 async 調用響應式流應用request-timeout: 60000stdio:connections:baidu-map:command: cmdargs:- "/c"- "npx"- "-y"- "@baidumap/mcp-server-baidu-map" # 第一次啟動會安裝此包env:BAIDU_MAP_API_KEY: xxx # 百度地圖開放平臺查看
另外一種是使用 Claude Desktop 格式的 JSON 文件單獨存放(推薦)
{"mcpServers": {"baidu-map": {"command": "cmd","args": ["/c","npx","-y","@baidumap/mcp-server-baidu-map"],"env": {"BAIDU_MAP_API_KEY": "xxxx"}}}
}
然后在 application.yaml
中指定文件的位置
spring:ai:mcp:client:request-timeout: 60000stdio:servers-configuration: classpath:/mcp/stdio-server-config.json
通過 ToolCallbackProvider 綁定 ChatClient,ToolCallbackProvider 可以看做外部工具提供商。
@Bean
public ChatClient serviceChatClient(OpenAiChatModel model, ChatMemory chatMemory, ToolCallbackProvider toolCallbackProvider) {return ChatClient.builder(model).defaultSystem(new ClassPathResource("call.txt")).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(), // CHAT MEMORYnew SimpleLoggerAdvisor()).defaultToolCallbacks(toolCallbackProvider)//關鍵.build();
}
如果想觀察到調用 mcp 工具的日志信息進行調試,可以添加如下配置:
logging:level:io:modelcontextprotocol:client: debugspec: debug
測試結果:
2_Stdio方式
以查詢天氣為目標,定義 MCP-Server 工程,引入 Stdio 服務端依賴
<!-- mcp-server依賴-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
配置 application.yaml
屬性:
spring:application:name: mcp-servermain:web-application-type: none # stdio 并不需要啟動為web應用banner-mode: off # 關掉banner 下方有說明ai:mcp:server:name: mcp-server # 應用名稱version: 1.0.0 # 版本
# 注意: 您必須禁用 banner 和控制臺日志記錄,以允許 Stdio 傳輸工作!!!
定義查詢城市天氣信息的工具類
@Service
public class WeatherService {@Tool(description = "根據城市名稱獲得天氣信息")public String getWeather(@ToolParam(description = "城市名稱") String cityName) {return cityName + "今日天氣 晴";}
}
將工具綁定到 ToolCallbackProvider:
@SpringBootApplication
public class McpServerApplication {public static void main(String[] args) {SpringApplication.run(McpServerApplication.class, args);}@Beanpublic ToolCallbackProvider weatherTools(WeatherService weatherService){return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();}// 客戶端使用 .defaultToolCallbacks(toolCallbackProvider) 添加
}
將項目打成 jar 包后,在客戶端 mcp/stdio-server-config.json
文件的 mcpServers 屬性中新增如下配置:
"mcp-server-weather": {"command": "java","args": ["-Dspring.ai.mcp.server.stdio=true","-Dlogging.pattern.console=","-jar","D:\\WorkSpace\\IdeaProject\\jar\\mcp-server-weather.jar"]
}
說明:
- command:內部 java 程序直接調用,所以無需 cmd 命令。
- args:以 stdio 的方式啟動,清空控制臺,否則 mcp-server 啟動也會打印干擾信息。
3_Stdio實現原理
當客戶端啟動之后,會由 McpClientAutoConfiguration 初始化并啟動傳輸連接獲取 tool 列表。
傳輸連接的關鍵流程及類圖如下:
在 StdioClientTransport 的 connect 方法中,會利用 ProcessBuilder 對象將配置文件中的命令、參數、環境等信息封裝起來創建子進程并啟動。
之后建立輸入、輸出流連接循環地讀取或寫入信息,源碼中的大致實現如下:
// 準備指定的命令、參數、環境
List<String> fullCommand = new ArrayList<>();
fullCommand.add(params.getCommand());
fullCommand.addAll(params.getArgs());
ProcessBuilder processBuilder = this.getProcessBuilder();
processBuilder.command(fullCommand);
processBuilder.environment().putAll(params.getEnv());
// 啟動子進程
this.process = processBuilder.start();
// 循環地從連接中讀取數據
try (BufferedReader processReader = new BufferedReader(new InputStreamReader(procesString line;while (!isClosing && (line = processReader.readLine()) != null) {try {JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objecif (!this.inboundSink.tryEmitNext(message).isSuccess()) {if (!isClosing) {logger.error("Failed to enqueue inbound message: {}", message);}break;}}}
}
// 如果有消息向連接中寫入數據
try {String jsonMessage = objectMapper.writeValueAsString(message);// Escape any embedded newlines in the JSON message as per spec:// https://spec.modelcontextprotocol.io/specification/basic/transports/#stdio// - Messages are delimited by newlines, and MUST NOT contain// embedded newlines.jsonMessage = jsonMessage.replace("\r\n", "\\n").replace("\n", "\\n").replace("\r", "\\n");var os = this.process.getOutputStream();synchronized (os) {os.write(jsonMessage.getBytes(StandardCharsets.UTF_8));os.write("\n".getBytes(StandardCharsets.UTF_8));os.flush();}s.next(message);
}
注意:這種方式傳輸信息時,服務端運行時打印的各種信息,如:banner、日志等都會成為干擾項。
4_SSE方式
最新版 MCP 協議已經棄用了這種方式,轉為感知的 Streamable HTTP,但 SpringAI 目前還不支持 Streamable HTTP。
SSE 需要將 MCP Server 部署為 Web 服務。
以加法計算為例,定義 MCP-Server 工程,引入 SSE 服務端依賴
<!--支持webflux以及stdio的方式,還支持webflux的方式暴露服務,-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<!---webmvc是基于mvc的依賴,同樣支持stdio,還支持-webmvc的方式暴露服務-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
配置 application.yaml
屬性:
spring:application:name: mcp-servermain:# (mvc,webflux依賴都引入則只能使用webflux,不然客戶端會404)web-application-type: reactive # webflux的方式暴露服務,除此外還有mvc-servletai:mcp:server:name: mcp-server # 應用名稱version: 1.0.0 # 版本type: async # 異步響應通信方式base-url: # base-urlsse-endpoint: /sse # 切入點,client默認會向url/sse下發送請求
server:port: 8888
定義加法計算的工具類,這里讓它返回錯誤的結果方便驗證
@Service
public class CalculationService {@Tool(description = "兩數加法計算")public Long getWeather(@ToolParam(description = "加數") Long addend,@ToolParam(description = "被加數") Long summand) {return addend * summand;}
}
將工具綁定到 ToolCallbackProvider:
@SpringBootApplication
public class McpServerApplication {public static void main(String[] args) {SpringApplication.run(McpServerApplication.class, args);}@Beanpublic ToolCallbackProvider weatherTools(CalculationService calculationService) {return MethodToolCallbackProvider.builder().toolObjects(calculationService).build();}// 客戶端使用 .defaultToolCallbacks(toolCallbackProvider) 添加
}
直接啟動 MCP Server 后,客戶端新增如下配置即調用服務端提供的工具
spring:ai:mcp:client:sse: # 新增此處配置,與stdio同級,其他配置無需更改connections: # 配置多個連接addition-calculation: # 服務名稱url: http://localhost:8888 # mcp-server 地址
驗證:
5_自定義MCP客戶端
在 Spring AI 中,MCP 客戶端分為兩類:
- 同步客戶端(Sync Client):默認類型,適用于傳統的請求-響應模式,使用阻塞操作。
- 異步客戶端(Async Client):適用于響應式應用,使用非阻塞操作。可以通過配置項
spring.ai.mcp.client.type=async
啟用。
Spring 提供了自動裝配機制,開發者只需實現 McpSyncClientCustomizer 或 McpAsyncClientCustomizer 接口,就能對 MCP 客戶端的各個方面進行個性化配置。
例如,控制請求超時、事件監聽以及消息處理邏輯。
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
import org.springframework.stereotype.Component;import java.time.Duration;
import java.util.List;/*** 自定義同步 MCP 客戶端配置器* (兼容所有的傳輸方式: stdio、mvc、webflux 傳輸方式)*/
@Slf4j
@Component
public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {@Overridepublic void customize(String serverConfigurationName, McpClient.SyncSpec spec) {log.info("正在自定義 MCP 同步客戶端配置: {}", serverConfigurationName);// 1. 配置請求超時時間spec.requestTimeout(Duration.ofSeconds(30));// 2. 工具變更監聽spec.toolsChangeConsumer((List<Tool> tools) -> {log.info("工具列表發生變更,共 {} 個", tools.size());tools.forEach(tool -> log.debug("工具: {} - {}", tool.name(), tool.description()));handleToolsChange(serverConfigurationName, tools);});// 3. 資源變更監聽spec.resourcesChangeConsumer((List<Resource> resources) -> {log.info("資源列表發生變更,共 {} 個", resources.size());resources.forEach(resource -> log.debug("資源: {} - {} ({})",resource.name(), resource.description(), resource.mimeType()));handleResourcesChange(serverConfigurationName, resources);});// 4. 提示變更監聽spec.promptsChangeConsumer((List<Prompt> prompts) -> {log.info("提示列表發生變更,共 {} 個", prompts.size());prompts.forEach(prompt -> log.debug("提示: {} - {}", prompt.name(), prompt.description()));handlePromptsChange(serverConfigurationName, prompts);});// 5. 日志消息處理spec.loggingConsumer((McpSchema.LoggingMessageNotification logMsg) -> {switch (logMsg.level()) {case ERROR -> log.error("MCP 服務器錯誤: {}", logMsg.data());case WARNING -> log.warn("MCP 服務器警告: {}", logMsg.data());case INFO -> log.info("MCP 服務器信息: {}", logMsg.data());case DEBUG -> log.debug("MCP 服務器調試: {}", logMsg.data());default -> log.trace("MCP 服務器日志: {}", logMsg.data());}});log.info("MCP 同步客戶端配置完成: {}", serverConfigurationName);// 2. 設置文件系統根目錄訪問權限// 3. 自定義采樣處理器// - 處理LLM生成請求// - 客戶端保持對模型訪問、選擇和權限的控制// - 服務器無需API密鑰即可利用AI能力}/** 工具變更處理邏輯 */private void handleToolsChange(String serverName, List<Tool> tools) {log.info("為服務器 {} 更新工具緩存", serverName);// 可擴展:更新緩存、通知下游服務等}/** 資源變更處理邏輯 */private void handleResourcesChange(String serverName, List<Resource> resources) {log.info("為服務器 {} 刷新資源索引", serverName);// 可擴展:刷新索引、更新界面等}/** 提示變更處理邏輯 */private void handlePromptsChange(String serverName, List<Prompt> prompts) {log.info("為服務器 {} 重新加載提示模板", serverName);// 可擴展:重新加載提示、推薦新模板等}
}
MCP 客戶端 bean 會自動配置并且可以通過注入的方式調用:
@Autowired
private List<McpSyncClient> mcpSyncClients; // For sync client
// OR
@Autowired
private List<McpAsyncClient> mcpAsyncClients; // For async client
當啟用工具回調(默認行為)時,所有 MCP 客戶端注冊的 MCP 工具都將作為 ToolCallbackProvider 實例提供:
@Autowired
private SyncMcpToolCallbackProvider toolCallbackProvider;
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();
6_MCP Server權限控制
系統提示詞,由調用方傳遞用戶數據:可以將用戶信息傳遞到工具中,但是可以隨意去查詢其他用戶的數據,不可取。
Stdio:
- 環境變量傳遞授權信息,但是目前不支持動態修改環境變量。
- 可以自己嘗試實現(銷毀原來的 Stdio、建立新的 Stdio,不推薦),由于多個用戶存在 Stdio 可能會造成并發競爭因此不推薦。
SSE:
按照傳統的 Web 鑒權實現即可,推薦,也不會多個用戶競爭同一個進程“切換權限”造成并發。