MCP客戶端呼叫看這一篇就夠了(Java版)

如果沒有MCP
MCP協議的初衷是希望能將大模型的工具呼叫來做統一,對於 MCP 的原理介紹的文章已經隨處可見,相信大家都有自己的見解,這裡簡單介紹一些沒有MCP之前的痛點問題,幫助大家理解為何需要MCP。
1.客戶端:每個工具暴露出來的對接方式都不一樣,客戶端為了去對接各類工具,需要做很多開發,比如getWeather工具是一個http服務,getLocation是一個HSF服務,並且兩種入參,出參的資料結構都不一樣,那這時候的對接開發成本就會很高;
2.服務端:A平臺Agent和B平臺Agent所需要服務的約定不一致,同一個服務需要考慮客戶端的約定,開發兩套介面,開發成本和維護成本都較高;
因此,MCP的出現約定了在AI開發領域客戶端和服務端的對接規範,當然未來也許會有更好用的協議也會替代MCP成為一種新的規範。
大模型呼叫MCP姿勢
框架篇
Spring-AI

引包

這裡使用最新的M7版本(此前M6版本中的MCP包裡面有部分問題,會將SSE型別的服務端url做改寫,導致某些SSE服務呼叫報錯 ,社群在M7版本中已經做了依賴升級)。
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId><version>1.0.0-M7</version></dependency>

配置LLM介面

Spring-AI支援的LLM型別有很多,基本涵蓋了各個平臺的LLM介面規範,具體可以參考官方文件中介紹[1]。

我們平常用OpenAI的規範多一些,並且Idealab中也提供了開放介面,這裡我們採用OpenAI介面作為chatModel,當然大家也可自行封裝自己的API為OpenAI規範,如Whale上部署的模型也都是支援OpenAI協議的。
#配置chatModel的域名,這裡我們使用的idealabspring.ai.openai.base-url=https://idealab.alibaba-inc.com#配置chat的akspring.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#其他引數配置spring.ai.openai.chat.options.temperature=0.1
模型配置完以後開始註冊我們的chatModel:
@ConfigurationpublicclassChatClientConfig {@AutowiredprivateToolCallbackProvider tools;@AutowiredOpenAiChatModel chatModel;@BeanpublicCommandLineRunnerpredefinedQuestions(            ConfigurableApplicationContext context) {return args -> {// 構建ChatClient,此時不注入任何工具var chatClient = ChatClient.builder(chatModel)                    .build();String userInput = "幫我將這個網頁內容進行抓取 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();        };    }
此時我們先不配置chatModel的工具,只是作為一個最基本的LLM利用Spring-AI配置化框架進行呼叫看看效果。

此時的回答就是沒有任何工具的一個最基礎的LLM能力,接下來我們開始為這個chatModel上新增MCP工具,利用框架去直接讓大模型呼叫MCP服務。

配置MCP服務

上述配置chatClient的過程中我們發現,使用框架提供的能力去呼叫LLM的API比我們自己去寫客戶端對接API來的方便。
而呼叫MCP的過程,使用框架的提效會更明顯,直接給chatClient注入工具即可,不用我們去手動寫functionCall的組裝,以及獲取到結果後再去呼叫對應服務。
1.服務端為SSE方式提供
a.設定配置檔案
spring.ai.mcp.client.name=ai-demospring.ai.mcp.client.type=SYNCspring.ai.mcp.client.toolcallback.enabled=truespring.ai.mcp.client.request-timeout=30000spring.ai.mcp.client.enabled=true# 配置mcp的服務端sse地址,這裡選用了一個開源的抓取網站內容的工具spring.ai.mcp.client.sse.connections.server1.url=https://mcp-09724909-442f-4b85.api-inference.modelscope.cn
b.給chatModel注入工具

執行結果:

此時可以發現,我們的chatModel不再是一個原生的LLM介面,已經可以根據使用者意圖來自主呼叫我們的MCP工具。
當然我們也可以看出,利用Spring-AI做MCP呼叫是非常簡單,只需配置好LLM的呼叫介面和MCP工具地址即可。
作為Demo此時沒有任何問題,但如果作為工程實現,比如我們要去做一個助理平臺,這時候其實每個助理所繫結的MCP工具是動態的,而非像上述這樣在應用啟動時初始化好的Bean,這種場景也是可以實現的,Spring-AI也支援在呼叫過程中動態封裝工具,這些工具可以是MCP,也可以是程式中的Bean,或者是某些HTTP、RCP介面等。

2.服務端為stdio方式提供
a.配置Properties
Stdio和SSE配置的區別是將呼叫方式由顯示的服務地址換成npm、java、python等指令碼命令直接執行的遠端包或本地包。
這裡採用本地配置檔案的方式去配置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=SYNCspring.ai.mcp.client.toolcallback.enabled=truespring.ai.mcp.client.request-timeout=30000spring.ai.mcp.client.enabled=truespring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
b.配置config.json

這裡注入了兩個比較經典的MCP服務,百度地圖和新聞熱點服務:
{"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注入工具
@ConfigurationpublicclassChatClientConfig {@AutowiredprivateToolCallbackProvider tools;@AutowiredOpenAiChatModel chatModel;@BeanpublicCommandLineRunnerpredefinedQuestions(            ConfigurableApplicationContext context) {return args -> {// 構建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自主選擇工具呼叫
使用者輸入為熱點查詢的提問時:

使用者輸入為地點檢索時:

透過上述例子可以發現,我們在初始化時配置了兩個MCP服務,LLM可以根據使用者不同提問來自行選擇不同的工具去呼叫,方式也比較簡單。

Spring-AI-Alibaba

上述介紹了Spring-AI框架對於MCP呼叫的支援和使用方式,但在集團內部,Spring-AI-Alibaba專案在此基礎上也做了很多封裝,更適合集團技術棧和內部中介軟體的無縫銜接,以及基於Spring-AI專案擴充套件了很多example專案可以參閱學習。如:Stramable HTTP 方式的MCP呼叫、OpenManus的實現等等,可以幫助開發者最更上層的封裝呼叫[2]。

基於Spring-AI-Alibaba做MCP客戶端的實現與Spring-AI框架基本相似,一些差一點主要有:
1.引入依賴時,在Spring-AI-MCP客戶端包的基礎上,需要新增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時,Spring-AI-Alibaba支援百鍊平臺上的模型及API,即
spring:ai:dashscope:# 配置通義千問API金鑰api-key: ${DASH_SCOPE_API_KEY}
3.支援Stramable HTTP 模式的MCP呼叫,配置方式與Spring-AI的SSE客戶端配置方式一致,但Spring-AI-Alibaba底層是自主實現了Stramable HTTP的get和post請求並整合在了Spring-AI框架中,具體可參考[3]。
自研篇

io.modelcontextprotocol.sdk+functionCall

透過上述介紹,我們發現既然框架層面已經為我們封裝好了很規範的MCP呼叫方式,透過簡單配置即可實現MCP客戶端,本章節將介紹原生的MCP SDK呼叫方式,對於平臺類研發可能更有幫助。
1. 引入依賴
<dependency><groupId>io.modelcontextprotocol.sdk</groupId><artifactId>mcp</artifactId><version>0.9.0</version></dependency>

2. 獲取該MCP服務中的所資源(方法、入參、描述等)

privateMcpSyncClient mcpClient;    privatestaticfinal String sseServerUrl = "https://mcp-09724909-442f-4b85.api-inference.modelscope.cn";@PostConstructpublicvoidinit(){try {McpClientTransport transport = newHttpClientSseClientTransport(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);        }    }@PreDestroypublicvoiddestroy(){if (mcpClient != null) {            mcpClient.closeGracefully();        }    }@BeanpublicStringgetToolList(){ListToolsResult toolsResult = mcpClient.listTools();for (Tooltool:toolsResult.tools()) {System.out.println(tool.name());System.out.println(tool.description());System.out.println(tool.inputSchema());        }returnnull;    }
即初始化構建了MCP客戶端和例項關閉動作,透過listTools即可得到該MCP-Server中的所有資源,如下示例:

其中inputSchema即用來作為functionCall中tools中的物件,獲取到該描述後,我們就可以在呼叫functionCall的時候這樣填寫tools中每個方法中的properties。

3. McpSyncClient來發起呼叫

@BeanpublicStringgetToolList(){ListToolsResult toolsResult = mcpClient.listTools();for (Tooltool:toolsResult.tools()) {System.out.println(tool.name());System.out.println(tool.description());System.out.println(tool.inputSchema());Map<StringObject> schemaMap = newHashMap<>();            schemaMap.put("type""object");Map<StringObject> properties = newHashMap<>();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 = newHashMap<>();            parameters.put("url","https://www.shuaijiao.cn/news/view/68320.html");CallToolResult toolResult = mcpClient.callTool(newCallToolRequest("fetch", parameters));System.out.println(extractTextContent(toolResult));        }returnnull;    }privateStringextractTextContent(CallToolResult toolResult){StringBuilder resultText = newStringBuilder();        toolResult.content().forEach(content -> {if (content instanceofTextContent) {                resultText.append(((TextContent) content).text());            }        });return resultText.toString();    }
上述示例中,透過mock了一個parameters作為functionCall返回的格式,傳給CallToolRequest中,並且顯示的指定呼叫的方法是fetch方法,執行結果如下:

透過上述過程發現,其實呼叫MCP的SDK去做封裝和開發也是比較簡單的,只需呼叫資源獲取介面,拿到工具列表傳給functionCall,並且將LLM分析出的結果拿到後來呼叫MCP的call方法即可。
踩坑記錄

1. 1.0.0-M6版本SSE方式報錯Caused by: java.lang.IllegalStateException: Multiple tools with the same name

解決方式:在啟動類上排除SseHttpClientTransportAutoConfiguration 即可。(換成1.0.0-M7版本後沒有出現)。

2. SSE方式不帶/SSE的時候報錯 服務找不到,本質原因是地址url被改寫

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

3. 啟動時報錯MCP服務連線超時

試了很久發現,本身一些MCP服務是做了IP安全證書等等,localhost本身ping不通,可嘗試換一些公開可訪問的url嘗試,如本文示例中的fetch網頁內容的url。
思考
本文針對框架和原生SDK的呼叫都做了總結,那如何選型,或者說如何取捨兩種不同技術路線來支撐平臺型研發呢?
如果我們自己開發一套這樣的流程,其中包括意圖識別、工具描述獲取、functionCall入參拼接、LLM呼叫結果獲取、工具呼叫、上下文記憶配置……豈不是需要投入很多的人力成本?甚至說是Spring-AI-Alibaba開源的openManus,如果我們自己開發工作量可想而知,從使用者的視角出發,採用框架固然可以很快的構建出自己的個人超級Agent,但從平臺開發視角出發,我們仍然需要呼叫底層sdk去更好的服務上層,比如現在很多的AI助理平臺中,一個Agent不僅僅是支援呼叫工具,還支援其他Agent的巢狀呼叫、如RAG知識檢索、工作流呼叫等等,如果採用框架來開發。
一方面需要很大的成本將自己平臺的呼叫姿勢對接成框架底層的呼叫,如將平臺某個Agent關聯的其他Agent及工具都要抽象成適合框架的工具,才能發起呼叫;另一方面,框架呼叫會遮蔽很多處理細節,比如一次規劃中到底使用了哪些工具、當前執行到哪個工具,此工具的輸出是什麼等等,都是需要展示給使用者的,框架層面難以將很多個性化的細節一一暴露給使用者。
因此面向AI平臺研發工程,個人覺得還是需要用原生的方式去打磨平臺能力,一些如openMauns等複雜流程,如果在框架中後續暴露出介面,可以結合框架做呼叫,而不是完全依賴框架。
總結
本文針對MCP客戶端的開發,詳細介紹瞭如何透過Spring-AI框架和原生SDK呼叫MCP服務,並對使用過程中遇到的一些問題進行了記錄。整體來說,框架呼叫簡單快捷,適合快速構建應用;而原生SDK則提供更靈活的控制,適合平臺級開發。
本文所實踐的示例僅基於一次呼叫過程,但MCP真正發揮其“連結模型和資料”意義可能需要體現在規劃反思類場景中,最近也在做此類場景的研發模式探索,大致上有幾個方向:1. 提示詞中做規劃打標,每次結束後重新思考需要呼叫什麼工具  2. 規劃本身作為一個工具去每次呼叫 3. Spring-AI-Alibaba釋出的openManus開原始碼的設計。後續將在新的文章中做分享。
參考資料
[1]https://docs.spring.io/spring-ai/reference/api/index.html

[2]https://java2ai.com/docs/1.0.0-M6.1/overview/?spm=0.29160081.0.0.79fa20f6jEvcRH

[3]https://java2ai.com/blog/spring-ai-alibaba-mcp-streamable-http/?spm=0.29160081.0.0.1cb05b624WFIFp#%E9%9B%86%E6%88%90%E5%88%B0-spring-ai-%E6%A1%86%E6%9E%B6
[4]https://java2ai.com/docs/1.0.0-M6.1/overview/?spm=0.29160081.0.0.1cb05b624WFIFp
[5]https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html#_standard_mcp_client
[6]https://github.com/springaialibaba/spring-ai-alibaba-examples/blob/main/spring-ai-alibaba-mcp-example/starter-example/client/starter-default-client/pom.xml
[7]https://bailian.console.aliyun.com/?spm=5176.29015046.0.0.1dd4778bb93yqh&tab=app#/app-market
[8]https://openlm.alibaba-inc.com/web/workbench/chatOs/public-tools?projectName=alsc_eleme_iic&pathname=%2Fpublic-tools
[9]https://java2ai.com/mcp/?spm=0.29160081.0.0.1cb05b624WFIFp
OpenLake大資料&AI一體化解決方案
本方案是基於開放可控資料湖倉構建的大資料/搜尋/AI一體化解決方案。透過元資料管理平臺DLF管理結構化和半/非結構化資料,提供湖倉資料表和檔案的安全訪問及IO加速。支援多引擎對接和平權協同計算,透過DataWorks統一開發,並保障大規模任務排程。    
點選閱讀原文檢視詳情。

相關文章