第十五章 MCP Client 调用本地服务
版本标注
- Spring AI:
1.1.2- Spring AI Alibaba:
1.1.2.0章节定位
- 第十四章负责提供本地 MCP 服务。
- 这一章负责连接这个服务,并把远端工具接入当前应用。
s01 > s02 > s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > s13 > s14 > [ s15 ] s16 > s17 > s18
一、本章要做什么
上一章已经把天气能力作为 MCP Server 暴露出来,这一章的任务就是连接这个服务,并把它提供的工具注册给 ChatClient。完成后,模型在回答问题时就可以主动调用上一章中的天气工具。
这一步的重点不在服务端,而在客户端接入。
二、配置文件
本章的 application.yml 中,MCP Client 相关配置如下:
server:
port: 8015
spring:
application:
name: mcp-client-demo
ai:
dashscope:
api-key: ${
DASHSCOPE_API_KEY}
mcp:
client:
type: async
request-timeout: 60s
toolcallback:
enabled: true
streamable-http:
connections:
mcp-server1:
url: http://localhost:8014
endpoint: /mcp
这里的含义很直接:当前应用作为 MCP Client,通过 Streamable-HTTP 方式连接运行在 8014 端口上的本地服务。配置中的 url 是服务基础地址,endpoint 则是客户端真正连接的协议端点。它和上一章服务端配置中的 protocol: STREAMABLE 是一一对应的:服务端负责暴露 Streamable-HTTP 能力,客户端负责连接这个 Streamable-HTTP 端点。
三、配置类
最终跑通后的配置类如下:
package cn.edu.nnu.opengms.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SaaLLMConfig {
@Bean("mcpChatClient")
public ChatClient mcpChatClient(
ChatModel chatModel,
ToolCallbackProvider tools)
{
return ChatClient.builder(chatModel)
.defaultToolCallbacks(tools.getToolCallbacks())
.build();
}
}
这段代码只表达一件事:把 MCP Client 发现到的远端工具回调注册给 ChatClient。
这里使用的是 defaultToolCallbacks(...),因为当前注册的不是本地工具对象,而是框架已经整理好的工具回调。
四、控制器
控制器代码如下:
package cn.edu.nnu.opengms.controller;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
public class McpClientController {
@Resource
private ChatClient chatClient;
@Resource
private ChatModel chatModel;
@GetMapping("/mcpclient/chat")
public Flux<String> chat(@RequestParam(name = "msg", defaultValue = "北京") String msg)
{
System.out.println("使用了mcp");
return chatClient.prompt(msg).stream().content();
}
@RequestMapping("/mcpclient/chat2")
public Flux<String> chat2(@RequestParam(name = "msg", defaultValue = "北京") String msg)
{
System.out.println("未使用mcp");
return chatModel.stream(msg);
}
}
这里的 chatClient 实际上就是配置类中定义的 mcpChatClient Bean,因此 /mcpclient/chat 走的是带 MCP 工具能力的调用链。
/mcpclient/chat2 则直接使用 ChatModel,不会触发 MCP 工具。
如果希望代码语义更明确,也可以写成:
@Resource(name = "mcpChatClient")
private ChatClient mcpChatClient;
五、MCP Client 调用本地服务的原理
这一章真正需要理解的,不只是配置项怎么写,而是客户端和服务端到底是怎样连起来的。
Spring AI 官方对自己的定位是:把企业中的 Data 和 APIs 接到 AI Models 上。MCP 在这里扮演的就是标准桥梁。它不是简单的“远程调用工具”,而是把工具发现、能力协商、消息交换和调用结果回传都统一到了同一套协议里。
从 Spring AI 和 MCP Java SDK 的结构来看,这条链路可以分成三层:
客户端/服务端层
这一层的角色分别是McpClient和McpServer。客户端负责建立连接、初始化会话、发现服务端能力;服务端负责暴露工具、资源和提示模板,并处理客户端发来的调用请求。会话层
这一层由McpSession负责维护会话状态。客户端启动后,不是每次调用工具都重新“临时拼一个 HTTP 请求”,而是先建立 MCP 会话,再在会话中完成后续的协议交互。传输层
这一层负责真正的消息收发和 JSON-RPC 编解码。Spring AI 文档里明确说明,MCP 支持STDIO、SSE、Streamable-HTTP等多种传输方式。本章客户端连接的是服务端提供的Streamable-HTTP端点。
5.1 本章里的连接是怎么建立的
本章配置的是:
spring:
ai:
mcp:
client:
streamable-http:
connections:
mcp-server1:
url: http://localhost:8014
endpoint: /mcp
根据 Spring AI MCP Client Starter 文档,Streamable-HTTP 连接配置由两部分组成:
url
这是服务端的基础地址,例如http://localhost:8014endpoint
这是Streamable-HTTP端点路径。官方文档给出的默认值是/mcp。如果没有显式配置,客户端会按默认值去拼接。
也就是说,本章这条配置最终连接的并不是单纯的根路径,而是基于基础地址和 Streamable-HTTP 端点拼出来的服务入口。服务端上一章显式声明了 protocol: STREAMABLE,客户端这一章再通过 endpoint: /mcp 去连接它,这样两边的配置就能完整对上。
5.2 连接成功后,客户端做了什么
客户端启动后,并不是立刻让模型开始调工具,而是会先完成一次 MCP 初始化过程。这个过程通常包括:
- 建立到服务端的
Streamable-HTTP连接 - 完成协议初始化
- 进行版本和能力协商
- 拉取服务端已经暴露的工具列表
Spring AI 的 MCP Client Starter 在 toolcallback.enabled=true 时,会把这些发现到的远端工具整理成 ToolCallbackProvider。这也是为什么本章最终可以直接注入:
ToolCallbackProvider tools
这一步完成后,当前应用就已经知道:服务端提供了哪些工具、每个工具叫什么、需要什么参数、调用后会返回什么结果。
5.3 模型调用工具时,消息是怎样流动的
真正进入对话时,链路是这样的:
用户问题
-> ChatClient
-> ChatModel
-> 模型判断需要调用远端工具
-> ToolCallbackProvider 中对应的 MCP 工具回调
-> MCP Client 通过会话发送 JSON-RPC 消息
-> MCP Server 接收请求并执行本地工具方法
-> 服务端返回工具执行结果
-> ChatClient 将结果交回模型
-> 模型组织最终回答
其中最关键的两个动作是:
发送消息
客户端不会直接去调用上一章中的 Java 方法,而是把工具名称、参数等信息封装为 MCP 协议消息,通过Streamable-HTTP连接发给服务端。Spring AI 文档说明,底层传输层负责 JSON-RPC 消息的序列化和反序列化。接收结果
服务端执行完工具方法后,会把结果再按 JSON-RPC 消息返回给客户端。客户端拿到结果后,不是直接把原始数据返回给用户,而是继续交给当前对话链路中的模型,让模型基于工具结果生成最终自然语言回答。
所以从用户角度看,好像只是“模型回答了一个天气问题”;但实际内部已经发生了一次完整的 MCP 会话消息交换。
5.4 为什么这里既像远程调用,又不像普通 HTTP 接口
因为 MCP 不只是一个“请求某个 URL 然后拿 JSON”的简单接口,它本身还负责:
- 能力发现
- 协议初始化
- 工具描述统一
- 调用消息标准化
- 结果回传
普通 HTTP 接口只解决“发请求、拿响应”,而 MCP 解决的是“AI 应用如何以统一协议理解并调用外部能力”。这也是为什么这一章里,客户端只要拿到 ToolCallbackProvider,后面的工具调用就可以直接挂到 ChatClient 上,而不需要手写一套天气接口适配逻辑。
5.5 为什么这里改用 Streamable-HTTP
这一章最开始也可以用 SSE 跑通,但最终改成 Streamable-HTTP 更合适,主要有两个原因。
第一,Spring AI 官方文档已经把 Streamable-HTTP 作为更推荐的方向来描述。它比早期的 SSE 方案更贴近 MCP 后续演进的主线,因此用它来写当前版本的笔记,稳定性和可迁移性都会更好一些。
第二,Streamable-HTTP 的配置语义更清楚。客户端配置里直接写基础地址和 endpoint,服务端则显式切到 STREAMABLE 协议,阅读成本比 SSE 场景下去理解默认 /sse 端点更低。对于这一章这种本地联调案例,这种写法更容易把“服务地址”和“协议端点”区分开。
六、测试方式
启动顺序如下:
- 先启动上一章中的本地 MCP 服务
- 再启动当前客户端应用
测试接口:
http://localhost:8015/mcpclient/chat?msg=上海天气怎么样
http://localhost:8015/mcpclient/chat2?msg=上海天气怎么样
这两条接口的差异很清楚:
/mcpclient/chat:带 MCP 工具能力/mcpclient/chat2:只用模型本身回答
如果链路已经打通,/mcpclient/chat 的返回会带上上一章天气工具中的固定结果;如果没有调通,通常会返回“无法实时获取天气信息”这类模型自答内容。
七、本章小结
这一章的核心就是把远端 MCP 工具接到本地对话链路里。对应到代码层面,关键实现只有这一句:
.defaultToolCallbacks(tools.getToolCallbacks())
它把上一章暴露出来的工具能力真正注册给了 ChatClient。之后模型在回答问题时,就具备了调用本地 MCP 服务的能力。
本章重点:
- 当前应用的角色是 MCP Client,不再负责定义工具。
ToolCallbackProvider提供的是远端工具回调集合。defaultToolCallbacks(...)是当前项目最终跑通的接入方式。
下章剧透(s16):
本地 MCP 服务联通后,下一章继续扩展到第三方 MCP 服务接入。
📝 编辑者:Flittly
📅 更新时间:2026年4月
🔗 相关资源:MCP 官方文档 | Spring AI MCP