<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>1.0.0-M7</version>
</dependency>

配置LLM接口

Spring-AI支持的LLM類型有很多,基本涵蓋了各個(gè)平臺(tái)的LLM接口規(guī)范,具體可以參考官方文檔中介紹。

我們平常用OpenAI的規(guī)范多一些,并且Idealab中也提供了開放接口,這里我們采用OpenAI接口作為chatModel,當(dāng)然大家也可自行封裝自己的API為OpenAI規(guī)范,如Whale上部署的模型也都是支持OpenAI協(xié)議的。

#配置chatModel的域名,這里我們使用的idealab
spring.ai.openai.base-url=https://idealab.alibaba-inc.com
#配置chat的ak
spring.ai.openai.api-key=脫敏
#配置chant的接口路徑
spring.ai.openai.chat.completions-path=api/openai/v1/chat/completions
#配置模型
spring.ai.openai.chat.options.model=gpt-4o-0513-global
#其他參數(shù)配置
spring.ai.openai.chat.options.temperature=0.1

模型配置完以后開始注冊我們的chatModel:

@Configuration
publicclassChatClientConfig {
@Autowired
private ToolCallbackProvider tools;
@Autowired
OpenAiChatModel chatModel;
@Bean
public CommandLineRunner predefinedQuestions(
ConfigurableApplicationContext context) {
return args -> {
// 構(gòu)建ChatClient,此時(shí)不注入任何工具
var chatClient = ChatClient.builder(chatModel)
.build();
String userInput = "幫我將這個(gè)網(wǎng)頁內(nèi)容進(jìn)行抓取 https://www.shuaijiao.cn/news/view/68320.html";
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt().user(userInput).call().content());

context.close();
};
}

此時(shí)我們先不配置chatModel的工具,只是作為一個(gè)最基本的LLM利用Spring-AI配置化框架進(jìn)行調(diào)用看看效果。

此時(shí)的回答就是沒有任何工具的一個(gè)最基礎(chǔ)的LLM能力,接下來我們開始為這個(gè)chatModel上添加MCP工具,利用框架去直接讓大模型調(diào)用MCP服務(wù)。

配置MCP服務(wù)

上述配置chatClient的過程中我們發(fā)現(xiàn),使用框架提供的能力去調(diào)用LLM的API比我們自己去寫客戶端對接API來的方便。

而調(diào)用MCP的過程,使用框架的提效會(huì)更明顯,直接給chatClient注入工具即可,不用我們?nèi)ナ謩?dòng)寫functionCall的組裝,以及獲取到結(jié)果后再去調(diào)用對應(yīng)服務(wù)。

1.服務(wù)端為SSE方式提供

a.設(shè)置配置文件

spring.ai.mcp.client.name=ai-demo
spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.toolcallback.enabled=true
spring.ai.mcp.client.request-timeout=30000
spring.ai.mcp.client.enabled=true
# 配置mcp的服務(wù)端sse地址,這里選用了一個(gè)開源的抓取網(wǎng)站內(nèi)容的工具
spring.ai.mcp.client.sse.connections.server1.url=https://mcp-09724909-442f-4b85.api-inference.modelscope.cn

b.給chatModel注入工具

運(yùn)行結(jié)果:

此時(shí)可以發(fā)現(xiàn),我們的chatModel不再是一個(gè)原生的LLM接口,已經(jīng)可以根據(jù)用戶意圖來自主調(diào)用我們的MCP工具。

當(dāng)然我們也可以看出,利用Spring-AI做MCP調(diào)用是非常簡單,只需配置好LLM的調(diào)用接口和MCP工具地址即可。

作為Demo此時(shí)沒有任何問題,但如果作為工程實(shí)現(xiàn),比如我們要去做一個(gè)助理平臺(tái),這時(shí)候其實(shí)每個(gè)助理所綁定的MCP工具是動(dòng)態(tài)的,而非像上述這樣在應(yīng)用啟動(dòng)時(shí)初始化好的Bean,這種場景也是可以實(shí)現(xiàn)的,Spring-AI也支持在調(diào)用過程中動(dòng)態(tài)封裝工具,這些工具可以是MCP,也可以是程序中的Bean,或者是某些HTTP、RCP接口等。

2.服務(wù)端為stdio方式提供

a.配置Properties

Stdio和SSE配置的區(qū)別是將調(diào)用方式由顯示的服務(wù)地址換成npm、java、python等腳本命令直接執(zhí)行的遠(yuǎn)程包或本地包。

這里采用本地配置文件的方式去配置Stdio,即spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json。

spring.ai.mcp.client.name=ai-demo
#spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.toolcallback.enabled=true
spring.ai.mcp.client.request-timeout=30000
spring.ai.mcp.client.enabled=true
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json

b.配置config.json

這里注入了兩個(gè)比較經(jīng)典的MCP服務(wù),百度地圖和新聞熱點(diǎn)服務(wù):

{
"mcpServers": {
"baidu-map": {
"command": "npx",
"args": [
"-y",
"@baidumap/mcp-server-baidu-map"
],
"env": {
"BAIDU_MAP_API_KEY": "Qr0GV6v4krVPlIJkupyPpi63d1zXh0Ko"
}
},
"mcp-server-hotnews": {
"command": "npx",
"args": [
"-y",
"@wopal/mcp-server-hotnews"
]
}
}
}

c.給chatModel注入工具

@Configuration
publicclassChatClientConfig {
@Autowired
private ToolCallbackProvider tools;
@Autowired
OpenAiChatModel chatModel;
@Bean
public CommandLineRunner predefinedQuestions(
ConfigurableApplicationContext context) {
return args -> {
// 構(gòu)建ChatClient,注入mcp工具
var chatClient = ChatClient.builder(chatModel).defaultTools(tools.getToolCallbacks())
.build();

// 使用ChatClient與LLM交互
String userInput = "幫我查找今天的知乎熱帖";
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt().user(userInput).call().content());

context.close();
};
}
}

d.讓LLM自主選擇工具調(diào)用

用戶輸入為熱點(diǎn)查詢的提問時(shí):

用戶輸入為地點(diǎn)檢索時(shí):

通過上述例子可以發(fā)現(xiàn),我們在初始化時(shí)配置了兩個(gè)MCP服務(wù),LLM可以根據(jù)用戶不同提問來自行選擇不同的工具去調(diào)用,方式也比較簡單。

Spring-AI-Alibaba

上述介紹了Spring-AI框架對于MCP調(diào)用的支持和使用方式,但在集團(tuán)內(nèi)部,Spring-AI-Alibaba項(xiàng)目在此基礎(chǔ)上也做了很多封裝,更適合集團(tuán)技術(shù)棧和內(nèi)部中間件的無縫銜接,以及基于Spring-AI項(xiàng)目擴(kuò)展了很多example項(xiàng)目可以參閱學(xué)習(xí)。如:Stramable HTTP 方式的MCP調(diào)用、OpenManus的實(shí)現(xiàn)等等,可以幫助開發(fā)者最更上層的封裝調(diào)用。

基于Spring-AI-Alibaba做MCP客戶端的實(shí)現(xiàn)與Spring-AI框架基本相似,一些差一點(diǎn)主要有:

1.引入依賴時(shí),在Spring-AI-MCP客戶端包的基礎(chǔ)上,需要新增com.alibaba.cloud.ai的依賴,如下所示:

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>1.0.0-M7</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M6.1</version>
</dependency>

2.配置chatModel時(shí),Spring-AI-Alibaba支持百煉平臺(tái)上的模型及API,即

spring:
ai:
dashscope:
# 配置通義千問API密鑰
api-key: ${DASH_SCOPE_API_KEY}

3.支持Stramable HTTP 模式的MCP調(diào)用,配置方式與Spring-AI的SSE客戶端配置方式一致,但Spring-AI-Alibaba底層是自主實(shí)現(xiàn)了Stramable HTTP的get和post請求并集成在了Spring-AI框架中,具體可參考。

自研篇

io.modelcontextprotocol.sdk+functionCall

通過上述介紹,我們發(fā)現(xiàn)既然框架層面已經(jīng)為我們封裝好了很規(guī)范的MCP調(diào)用方式,通過簡單配置即可實(shí)現(xiàn)MCP客戶端,本章節(jié)將介紹原生的MCP SDK調(diào)用方式,對于平臺(tái)類研發(fā)可能更有幫助。

1. 引入依賴

<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.9.0</version>
</dependency>

2. 獲取該MCP服務(wù)中的所資源(方法、入?yún)ⅰ⒚枋龅龋?/strong>

private McpSyncClient mcpClient;

privatestaticfinal String sseServerUrl = "https://mcp-09724909-442f-4b85.api-inference.modelscope.cn";

@PostConstruct
publicvoidinit(){
try {
McpClientTransport transport = new HttpClientSseClientTransport(sseServerUrl);
mcpClient = McpClient.sync(transport)
.requestTimeout(Duration.ofSeconds(20L))
.capabilities(ClientCapabilities.builder()
.roots(true)
.sampling()
.build())
.build();
mcpClient.initialize();
} catch (Exception e) {
thrownew RuntimeException("初始化MCP客戶端對象失敗", e);
}
}

@PreDestroy
publicvoiddestroy(){
if (mcpClient != null) {
mcpClient.closeGracefully();
}
}

@Bean
public String getToolList(){
ListToolsResult toolsResult = mcpClient.listTools();
for (Tool tool:toolsResult.tools()) {
System.out.println(tool.name());
System.out.println(tool.description());
System.out.println(tool.inputSchema());
}
return null;
}

即初始化構(gòu)建了MCP客戶端和實(shí)例關(guān)閉動(dòng)作,通過listTools即可得到該MCP-Server中的所有資源,如下示例:

其中inputSchema即用來作為functionCall中tools中的對象,獲取到該描述后,我們就可以在調(diào)用functionCall的時(shí)候這樣填寫tools中每個(gè)方法中的properties。

3. McpSyncClient來發(fā)起調(diào)用

@Bean
public String getToolList(){
ListToolsResult toolsResult = mcpClient.listTools();
for (Tool tool:toolsResult.tools()) {
System.out.println(tool.name());
System.out.println(tool.description());
System.out.println(tool.inputSchema());

Map<String, Object> schemaMap = new HashMap<>();
schemaMap.put("type", "object");
Map<String, Object> properties = new HashMap<>();
if (tool.inputSchema().properties() != null) {
tool.inputSchema().properties().forEach((key, value) -> {
properties.put(key, value);
});
}
schemaMap.put("properties", properties);

if (tool.inputSchema().required() != null && !tool.inputSchema().required().isEmpty()) {
schemaMap.put("required", tool.inputSchema().required());
}
Map<String,Object> parameters = new HashMap<>();
parameters.put("url","https://www.shuaijiao.cn/news/view/68320.html");
CallToolResult toolResult = mcpClient.callTool(new CallToolRequest("fetch", parameters));
System.out.println(extractTextContent(toolResult));

}
return null;
}

private String extractTextContent(CallToolResult toolResult){
StringBuilder resultText = new StringBuilder();
toolResult.content().forEach(content -> {
if (content instanceof TextContent) {
resultText.append(((TextContent) content).text());
}
});
return resultText.toString();
}

上述示例中,通過mock了一個(gè)parameters作為functionCall返回的格式,傳給CallToolRequest中,并且顯示的指定調(diào)用的方法是fetch方法,運(yùn)行結(jié)果如下:

通過上述過程發(fā)現(xiàn),其實(shí)調(diào)用MCP的SDK去做封裝和開發(fā)也是比較簡單的,只需調(diào)用資源獲取接口,拿到工具列表傳給functionCall,并且將LLM分析出的結(jié)果拿到后來調(diào)用MCP的call方法即可。

常見問題總結(jié)

1. 1.0.0-M6版本SSE方式報(bào)錯(cuò)Caused by: java.lang.IllegalStateException: Multiple tools with the same name

解決方式:在啟動(dòng)類上排除SseHttpClientTransportAutoConfiguration 即可。(換成1.0.0-M7版本后沒有出現(xiàn))。

2. SSE方式不帶/SSE的時(shí)候報(bào)錯(cuò) 服務(wù)找不到,本質(zhì)原因是地址url被改寫

解決方式:Spring-AI的話升級1.0.0-M6到1.0.0-M7版本,并且單獨(dú)引入io.modelcontextprotocol.sdk 0.9的版本。

3. 啟動(dòng)時(shí)報(bào)錯(cuò)MCP服務(wù)連接超時(shí)

試了很久發(fā)現(xiàn),本身一些MCP服務(wù)是做了IP安全證書等等,localhost本身ping不通,可嘗試換一些公開可訪問的url嘗試,如本文示例中的fetch網(wǎng)頁內(nèi)容的url。

思考

本文針對框架和原生SDK的調(diào)用都做了總結(jié),那如何選型,或者說如何取舍兩種不同技術(shù)路線來支撐平臺(tái)型研發(fā)呢?

如果我們自己開發(fā)一套這樣的流程,其中包括意圖識別、工具描述獲取、functionCall入?yún)⑵唇印LM調(diào)用結(jié)果獲取、工具調(diào)用、上下文記憶配置……豈不是需要投入很多的人力成本?甚至說是Spring-AI-Alibaba開源的openManus,如果我們自己開發(fā)工作量可想而知,從使用者的視角出發(fā),采用框架固然可以很快的構(gòu)建出自己的個(gè)人超級Agent,但從平臺(tái)開發(fā)視角出發(fā),我們?nèi)匀恍枰{(diào)用底層sdk去更好的服務(wù)上層,比如現(xiàn)在很多的AI助理平臺(tái)中,一個(gè)Agent不僅僅是支持調(diào)用工具,還支持其他Agent的嵌套調(diào)用、如RAG知識檢索、工作流調(diào)用等等,如果采用框架來開發(fā)。

一方面需要很大的成本將自己平臺(tái)的調(diào)用姿勢對接成框架底層的調(diào)用,如將平臺(tái)某個(gè)Agent關(guān)聯(lián)的其他Agent及工具都要抽象成適合框架的工具,才能發(fā)起調(diào)用;另一方面,框架調(diào)用會(huì)屏蔽很多處理細(xì)節(jié),比如一次規(guī)劃中到底使用了哪些工具、當(dāng)前執(zhí)行到哪個(gè)工具,此工具的輸出是什么等等,都是需要展示給用戶的,框架層面難以將很多個(gè)性化的細(xì)節(jié)一一暴露給用戶。

因此面向AI平臺(tái)研發(fā)工程,個(gè)人覺得還是需要用原生的方式去打磨平臺(tái)能力,一些如openMauns等復(fù)雜流程,如果在框架中后續(xù)暴露出接口,可以結(jié)合框架做調(diào)用,而不是完全依賴框架。

總結(jié)

本文針對MCP客戶端的開發(fā),詳細(xì)介紹了如何通過Spring-AI框架和原生SDK調(diào)用MCP服務(wù),并對使用過程中遇到的一些問題進(jìn)行了記錄。整體來說,框架調(diào)用簡單快捷,適合快速構(gòu)建應(yīng)用;而原生SDK則提供更靈活的控制,適合平臺(tái)級開發(fā)。

本文所實(shí)踐的示例僅基于一次調(diào)用過程,但MCP真正發(fā)揮其“鏈接模型和數(shù)據(jù)”意義可能需要體現(xiàn)在規(guī)劃反思類場景中,最近也在做此類場景的研發(fā)模式探索,大致上有幾個(gè)方向:

1. 提示詞中做規(guī)劃打標(biāo),每次結(jié)束后重新思考需要調(diào)用什么工具

2. 規(guī)劃本身作為一個(gè)工具去每次調(diào)用

3. Spring-AI-Alibaba發(fā)布的openManus開源代碼的設(shè)計(jì)。后續(xù)將在新的文章中做分享。

文章轉(zhuǎn)載自:MCP客戶端調(diào)用看這一篇就夠了(Java版)

#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場景實(shí)測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對比試用API 限時(shí)免費(fèi)