MCP Client 的工具是怎么注册到大模型的?深度拆解 Spring AI 的 MCP 机制
本文目录 · 30 节
- 01、MCP Client 到底干了什么?
- 02、怎么配置 MCP Client?
- 03、MCP 工具是怎么注册到大模型的?
- 04、MCP Server 挂了怎么办?
- 05、Agent 能在运行时添加新的 MCP Server 吗?
- 06、一个应用怎么同时当 Server 又当 Client?
- ending
- 01、MCP Client 到底干了什么?
- 02、怎么配置 MCP Client?
- 03、MCP 工具是怎么注册到大模型的?
- 04、MCP Server 挂了怎么办?
- 05、Agent 能在运行时添加新的 MCP Server 吗?
- 06、一个应用怎么同时当 Server 又当 Client?
- ending
- 01、MCP Client 到底干了什么?
- 02、怎么配置 MCP Client?
- 03、MCP 工具是怎么注册到大模型的?
- 04、MCP Server 挂了怎么办?
- 05、Agent 能在运行时添加新的 MCP Server 吗?
- 06、一个应用怎么同时当 Server 又当 Client?
- ending
星球:Java进阶之路&二哥编程星球 | 专栏:求职派 OpenClaw 架构 | 作者:沉默王二 | 发布:2026-07-30
MCP Client 的工具是怎么注册到大模型的?深度拆解 Spring AI 的 MCP 机制
MCP Client 的工具是怎么注册到大模型的?深度拆解 Spring AI 的 MCP 机制
大家好,我是二哥呀。
前面我们聊了怎么用 Spring AI 搭 MCP Server、怎么给它加鉴权。但光有 Server 端还不够——大模型要调用这些工具,中间必须有一个 Client 去发现工具、转发调用请求、把结果拿回来。
大多数 MCP Client 的教程都停留在了"配个 YAML,连上 Server,问个时间"这一步。真到了生产环境,问题远不止这些。
-
MCP Server 挂了,Agent 是直接报错还是有兜底方案?
-
一个应用同时是 Server 又是 Client,两个角色怎么共存?
-
Agent 能不能在对话过程中自己注册一个新的 MCP Server?
这篇就来拆解 MCP Client。从 YAML 配置到工具注册,从优雅降级到动态扩展,每个环节用求职派的源码来做证据。 https://paicoding.com/paiclaw-mcp-client
系好安全带,我们粗粗发~
01、MCP Client 到底干了什么?
先把 MCP 协议里的角色关系理清楚。
MCP Server 负责暴露工具。求职派的岗位推荐服务加上 @Tool 注解就能被外部发现和调用。MCP Client 负责连接这些 Server,拉取工具列表,在大模型需要的时候发起调用。

在 Spring AI 的体系里,MCP Client 做了三件事。
-
第一,和外部 MCP Server 建立连接,拿到工具清单。
-
第二,把这些工具包装成 Spring AI 统一的工具回调接口。
-
第三,注入到 ChatClient 或 Agent,让大模型在对话中随时调用。
关键在第二步。
Spring AI 提供了一个异步的工具回调适配器,接收一组异步 MCP 客户端实例,自动把远程工具转换成和本地工具完全一样的接口。
对上层业务来说,大模型调用一个 MCP 远程工具和调用一个本地方法,代码写法完全相同。Spring AI MCP Client 的核心价值就是屏蔽了协议细节,让工具的来源对业务完全透明。
这也意味着,如果你的 Agent 已经在用 Spring AI 的 @Tool 注解定义本地工具,接入 MCP 远程工具不需要改任何业务代码,只需要在配置里加上 MCP Server 的地址,再把工具回调适配器传进去就行了。
02、怎么配置 MCP Client?
求职派的 MCP Client 配置分两层。YAML 定义连接参数,JSON 文件定义具体的 Server 列表。
YAML 配置
先看 application-ai.yaml 中的 MCP 部分。
YAML
spring:
ai:
mcp:
client:
stdio:
servers-configuration: ${MCP_SERVERS_CONFIG}
enabled: ${AGENT_MCP_CLIENT_ENABLED:false}
name: 求职派MCP
request-timeout: 30s
type: async
几个关键配置项说明一下。
type: async 表示使用异步客户端。求职派全局统一用异步模式,代码里注入的是异步 MCP 客户端而不是同步版本。异步模式不会阻塞主线程,更适合多 Agent 并发调用的场景。
enabled 通过环境变量控制开关,默认关闭。MCP Server 是外部依赖,开发阶段不一定每次都能启动,关掉之后应用照样跑。

servers-configuration 指向一个 JSON 文件,还会区分操作系统。Windows 用 mcp-servers.json,macOS 用 mcp-servers-mac.json,因为进程启动命令不同。
JSON 定义 Server 列表
实际的 MCP Server 在 JSON 文件中定义。
JSON
{
"mcpServers": {
"selenium": {
"command": "npx",
"args": ["-y", "@angiejones/mcp-selenium"]
}
}
}
这里用的是 stdio 传输模式。
Spring AI 会启动一个子进程来运行 MCP Server,通过标准输入输出和它通信。求职派目前配置了一个 Selenium 浏览器自动化工具,用来做岗位数据的网页抓取。

除了 stdio,Spring AI 还支持 streamable-http 传输模式,通过 HTTP 请求和远程 MCP Server 通信。两种模式的配置结构不同,但上层使用方式完全一样,应用拿到的都是同一种异步客户端实例。
Spring AI 的自动配置会读取这些 YAML 和 JSON,为每个 MCP Server 创建一个异步客户端实例,注册到 Spring 容器里。业务代码通过 @Autowired 注入即可。
03、MCP 工具是怎么注册到大模型的?
配置搞定之后,Spring 容器里就有了一组异步客户端实例。但大模型不认识这些实例,它只认识 Spring AI 统一的工具回调接口。
中间需要一个桥梁,把 MCP 协议的工具转换成大模型可以调用的格式。
求职派的岗位数据采集 Agent 里有这段代码( GatherAiAgent.java)。
Java
@Autowired(required = false)
public void setToolCallbackProvider(
List mcpClients) {
if (!CollectionUtils.isEmpty(mcpClients)) {
this.toolCallbackProvider =
new AsyncMcpToolCallbackProvider(mcpClients);
}
}
这 6 行代码完成了整个桥接过程。

Spring AI 的自动配置把 JSON 里定义的每个 MCP Server 都创建为一个异步客户端实例。这些实例被收集成一个列表,注入到上面的方法里。
异步工具回调适配器拿到这些客户端后,会逐个连接 MCP Server,拉取各自暴露的工具清单,然后把每个远程工具包装成 Spring AI 标准的工具回调对象。
包装之后的工具,和本地用 @Tool 注解定义的工具在接口层面完全一致。大模型在推理过程中看到的工具列表里,本地工具和远程 MCP 工具混在一起,没有任何区别。
接下来看工具怎么传给大模型。
在多轮对话的场景下,求职派通过构建聊天选项来注册工具。
Java
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.model(modelPair.getSecond())
.toolCallbacks(
toolCallbackProvider.getToolCallbacks())
.build();
调用 getToolCallbacks() 时,适配器返回的就是已经包装好的全部工具,包括所有 MCP Server 暴露的远程工具。大模型拿到这些工具后,可以在推理过程中自主决定是否调用、调用哪一个。
整个流程串起来就是这样的。
YAML 配置 → 自动创建异步客户端 → 适配器桥接为统一工具接口 → 注入到聊天选项 → 大模型按需调用。
开发者不需要了解 MCP 协议的细节,只管把工具回调适配器传给 ChatClient 就行了。
这个机制和 Spring AI 的 Function Calling(工具调用)用的是同一套接口。如果你之前已经在用 @Tool 注解定义本地工具,MCP 远程工具的接入不会改变任何已有的工具调用逻辑。Spring AI 在设计上做到了本地工具和远程工具的统一抽象,上层代码完全感知不到工具的物理位置。
04、MCP Server 挂了怎么办?
生产环境里,MCP Server 是外部依赖,随时可能挂掉或者压根没启动。如果 Agent 只依赖 MCP 工具,一旦连接断开,整个功能就瘫痪了。
求职派用了一个双保险设计来解决这个问题。
第一层,注入 MCP 工具时标记为非必需。
Java
@Autowired(required = false)
public void setToolCallbackProvider(
List mcpClients) {
// MCP 可用时注册远程工具
}
required = false 意味着如果 Spring 容器里没有异步客户端实例(比如 MCP Client 被关闭或 Server 没启动),这个方法根本不会执行,也不会报错。
第二层,应用启动后检查工具是否就绪,没有就用本地工具兜底。
Java
@PostConstruct
public void initLocalToolCallback() {
if (this.toolCallbackProvider == null) {
this.toolCallbackProvider =
new StaticToolCallbackProvider(
ToolCallbacks.from(new CrawlerTools()));
}
}

逻辑非常清晰。
Spring 先尝试注入 MCP 客户端列表,有的话就用远程工具。然后 @PostConstruct 阶段检查,如果工具适配器还是空的(说明 MCP 没配置或不可用),就创建一个本地的静态工具作为兜底。
本地的爬虫工具提供了基础的网页抓取能力,虽然功能不如 Selenium 浏览器自动化强大,但保证了核心流程不会中断。
这个设计的巧妙之处在于,上层业务代码完全不需要关心当前用的是 MCP 远程工具还是本地工具。两者都实现了同一个工具回调接口,调用方式一模一样。Agent 代码写一套逻辑,工具来源的切换在启动阶段就完成了。
对于求职派这种同时对接了微信、钉钉、飞书三个渠道的系统,任何一个外部依赖的不可用都不应该导致整个服务瘫痪。MCP Client 挂了,Agent 还能用本地工具继续服务用户。
一行 required = false 加一个 @PostConstruct 检查,就把外部依赖的风险控制住了。
05、Agent 能在运行时添加新的 MCP Server 吗?
前面几节讲的都是"启动时确定好 MCP Server 列表"的静态模式。
但在实际使用中,Agent 经常需要接入新的工具能力。比如用户想用一个新的数据源,或者运维要挂载一个监控工具。
求职派在 Agent 的工具箱里内置了一个"注册 MCP Server"的工具。没错,这是一个 用来注册工具的工具。

看一下这个工具的定义( McpTool.java)。
Java
@Tool(description = "Adds a new streamable-HTTP "
+ "MCP server to the application configuration")
public String addStreamableHttpMcpServer(
String name, String url, String headers) {
configurationManager.updateProperties(props);
return "MCP server added successfully.";
}
这个工具同时支持 stdio 和 streamable-http 两种传输模式。Agent 在对话过程中如果判断需要新的外部工具,就可以通过它直接注册。
注册过程做了三件事。
校验服务名称格式,只允许字母、数字、连字符和下划线。把连接参数写入应用配置文件。返回注册成功的提示,告知需要重启才能生效。
配置的持久化通过配置管理器完成,变更会写入 application.yaml,重启后新的 MCP Server 就会被自动配置加载。
这个设计让 Agent 具备了自我扩展工具能力的可能性。虽然当前版本需要重启才能生效,但已经比手动改配置文件重新部署强了一个量级。

在求职派的 Agent 初始化代码里,MCP 注册工具和其他内置工具一起被注册到 ChatClient 中。Shell 执行工具、文件系统工具、任务管理工具、网页抓取工具,它们共同构成了 Agent 的基础能力层。
MCP 注册工具不是最常用的,但它代表了一种设计思路——Agent 不应该是一个封闭系统,它应该有能力去发现和接入新的工具。MCP 协议把工具标准化了,注册工具把接入流程也标准化了。
06、一个应用怎么同时当 Server 又当 Client?
求职派的 app 模块在 Maven 依赖里同时引入了 MCP Server 和 MCP Client 两个 starter。
同一个 Spring Boot 应用,既对外暴露工具(岗位推荐接口,让外部 Agent 调用),又连接外部 MCP Server(Selenium 浏览器工具,让自己的 Agent 使用)。

Server 端通过 @Tool 注解暴露工具,由 Spring AI 的 WebMVC MCP Server 自动配置提供 HTTP 端点。Client 端通过 YAML + JSON 配置连接外部 Server,由 MCP Client 自动配置创建客户端实例。
两条线各走各的自动配置流程,互不干扰。
Server 端暴露工具时,把带 @Tool 注解的服务注册为工具回调 Bean。
Java
@Bean
public ToolCallbackProvider dateProvider(
OcMcpService dateService) {
return MethodToolCallbackProvider.builder()
.toolObjects(dateService).build();
}
Client 端拿到的也是工具回调适配器,只不过是通过异步适配器包装的远程工具。两者在 Spring 容器里是不同的 Bean 实例,不会冲突。
这个双角色架构在多 Agent 系统中很常见。求职派自己的 Agent 需要浏览器自动化能力(Client 角色),同时又要让外部的 Claude Code 或其他 Agent 能调用岗位推荐功能(Server 角色)。
一个应用一套部署,两个角色同时运转。Spring AI 的 starter 分离设计让这件事变得很自然,不需要任何额外的隔离代码。
从实际效果来看,这种双角色架构大幅简化了部署复杂度。如果把 Server 和 Client 拆成两个独立应用,就要多维护一套服务,还得额外处理它们之间的网络通信和鉴权。放在同一个进程里,Server 端和 Client 端共享数据库连接、用户上下文和配置管理,运维成本低了很多。
ending
【从 YAML 配置到工具注册,从优雅降级到动态扩展,Spring AI 把 MCP 协议的复杂度藏在了自动配置背后。开发者要做的事情,说到底就是一个适配器加一行注入。】
但真正有意思的不是 API 怎么调。生产环境里那些不起眼的设计才值得细看。
MCP 挂了有本地工具兜底,Agent 能自己注册新的工具,同一个应用既当 Server 又当 Client。
我们下期见。
如何把求职派写到简历上?
项目名称:求职派(JobClaw)
项目简介:基于 Spring AI 的多 Agent 求职系统,对接微信/钉钉/飞书三个 IM 渠道,通过意图分类和 Agent 路由为用户提供智能求职服务。
技术栈:Java 21、Spring Boot 4.0、Spring AI 2.0、MCP 协议、LangGraph4J、JPA/H2/MySQL
核心职责:
-
基于 Spring AI MCP Client 实现外部工具集成,通过异步工具回调适配器将 MCP 远程工具统一为标准工具接口,Agent 无感调用远程和本地工具
-
设计 MCP 工具双保险降级机制,通过条件注入 + 启动后检查的两阶段策略,MCP Server 不可用时自动切换到本地工具
-
实现 MCP Server 动态注册工具,支持 Agent 在运行时通过对话注册 stdio 和 streamable-http 两种传输模式的新 MCP Server,配置自动持久化
-
构建 MCP 双角色架构,同一应用同时作为 MCP Server(暴露岗位推荐等业务工具)和 MCP Client(接入 Selenium 等外部工具)
-
基于自定义 ReAct 替代 Spring AI 默认的工具调用机制,实现细粒度的推理-执行控制和中间件生命周期拦截
大家好,我是二哥呀。
前面我们聊了怎么用 Spring AI 搭 MCP Server、怎么给它加鉴权。但光有 Server 端还不够——大模型要调用这些工具,中间必须有一个 Client 去发现工具、转发调用请求、把结果拿回来。
大多数 MCP Client 的教程都停留在了"配个 YAML,连上 Server,问个时间"这一步。真到了生产环境,问题远不止这些。
-
MCP Server 挂了,Agent 是直接报错还是有兜底方案?
-
一个应用同时是 Server 又是 Client,两个角色怎么共存?
-
Agent 能不能在对话过程中自己注册一个新的 MCP Server?
这篇就来拆解 MCP Client。从 YAML 配置到工具注册,从优雅降级到动态扩展,每个环节用求职派的源码来做证据。 https://paicoding.com/paiclaw-mcp-client
系好安全带,我们粗粗发~
01、MCP Client 到底干了什么?
先把 MCP 协议里的角色关系理清楚。
MCP Server 负责暴露工具。求职派的岗位推荐服务加上 @Tool 注解就能被外部发现和调用。MCP Client 负责连接这些 Server,拉取工具列表,在大模型需要的时候发起调用。

在 Spring AI 的体系里,MCP Client 做了三件事。
-
第一,和外部 MCP Server 建立连接,拿到工具清单。
-
第二,把这些工具包装成 Spring AI 统一的工具回调接口。
-
第三,注入到 ChatClient 或 Agent,让大模型在对话中随时调用。
关键在第二步。
Spring AI 提供了一个异步的工具回调适配器,接收一组异步 MCP 客户端实例,自动把远程工具转换成和本地工具完全一样的接口。
对上层业务来说,大模型调用一个 MCP 远程工具和调用一个本地方法,代码写法完全相同。Spring AI MCP Client 的核心价值就是屏蔽了协议细节,让工具的来源对业务完全透明。
这也意味着,如果你的 Agent 已经在用 Spring AI 的 @Tool 注解定义本地工具,接入 MCP 远程工具不需要改任何业务代码,只需要在配置里加上 MCP Server 的地址,再把工具回调适配器传进去就行了。
02、怎么配置 MCP Client?
求职派的 MCP Client 配置分两层。YAML 定义连接参数,JSON 文件定义具体的 Server 列表。
YAML 配置
先看 application-ai.yaml 中的 MCP 部分。
YAML
spring:
ai:
mcp:
client:
stdio:
servers-configuration: ${MCP_SERVERS_CONFIG}
enabled: ${AGENT_MCP_CLIENT_ENABLED:false}
name: 求职派MCP
request-timeout: 30s
type: async
几个关键配置项说明一下。
type: async 表示使用异步客户端。求职派全局统一用异步模式,代码里注入的是异步 MCP 客户端而不是同步版本。异步模式不会阻塞主线程,更适合多 Agent 并发调用的场景。
enabled 通过环境变量控制开关,默认关闭。MCP Server 是外部依赖,开发阶段不一定每次都能启动,关掉之后应用照样跑。

servers-configuration 指向一个 JSON 文件,还会区分操作系统。Windows 用 mcp-servers.json,macOS 用 mcp-servers-mac.json,因为进程启动命令不同。
JSON 定义 Server 列表
实际的 MCP Server 在 JSON 文件中定义。
JSON
{
"mcpServers": {
"selenium": {
"command": "npx",
"args": ["-y", "@angiejones/mcp-selenium"]
}
}
}
这里用的是 stdio 传输模式。
Spring AI 会启动一个子进程来运行 MCP Server,通过标准输入输出和它通信。求职派目前配置了一个 Selenium 浏览器自动化工具,用来做岗位数据的网页抓取。

除了 stdio,Spring AI 还支持 streamable-http 传输模式,通过 HTTP 请求和远程 MCP Server 通信。两种模式的配置结构不同,但上层使用方式完全一样,应用拿到的都是同一种异步客户端实例。
Spring AI 的自动配置会读取这些 YAML 和 JSON,为每个 MCP Server 创建一个异步客户端实例,注册到 Spring 容器里。业务代码通过 @Autowired 注入即可。
03、MCP 工具是怎么注册到大模型的?
配置搞定之后,Spring 容器里就有了一组异步客户端实例。但大模型不认识这些实例,它只认识 Spring AI 统一的工具回调接口。
中间需要一个桥梁,把 MCP 协议的工具转换成大模型可以调用的格式。
求职派的岗位数据采集 Agent 里有这段代码( GatherAiAgent.java)。
Java
@Autowired(required = false)
public void setToolCallbackProvider(
List mcpClients) {
if (!CollectionUtils.isEmpty(mcpClients)) {
this.toolCallbackProvider =
new AsyncMcpToolCallbackProvider(mcpClients);
}
}
这 6 行代码完成了整个桥接过程。

Spring AI 的自动配置把 JSON 里定义的每个 MCP Server 都创建为一个异步客户端实例。这些实例被收集成一个列表,注入到上面的方法里。
异步工具回调适配器拿到这些客户端后,会逐个连接 MCP Server,拉取各自暴露的工具清单,然后把每个远程工具包装成 Spring AI 标准的工具回调对象。
包装之后的工具,和本地用 @Tool 注解定义的工具在接口层面完全一致。大模型在推理过程中看到的工具列表里,本地工具和远程 MCP 工具混在一起,没有任何区别。
接下来看工具怎么传给大模型。
在多轮对话的场景下,求职派通过构建聊天选项来注册工具。
Java
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.model(modelPair.getSecond())
.toolCallbacks(
toolCallbackProvider.getToolCallbacks())
.build();
调用 getToolCallbacks() 时,适配器返回的就是已经包装好的全部工具,包括所有 MCP Server 暴露的远程工具。大模型拿到这些工具后,可以在推理过程中自主决定是否调用、调用哪一个。
整个流程串起来就是这样的。
YAML 配置 → 自动创建异步客户端 → 适配器桥接为统一工具接口 → 注入到聊天选项 → 大模型按需调用。
开发者不需要了解 MCP 协议的细节,只管把工具回调适配器传给 ChatClient 就行了。
这个机制和 Spring AI 的 Function Calling(工具调用)用的是同一套接口。如果你之前已经在用 @Tool 注解定义本地工具,MCP 远程工具的接入不会改变任何已有的工具调用逻辑。Spring AI 在设计上做到了本地工具和远程工具的统一抽象,上层代码完全感知不到工具的物理位置。
04、MCP Server 挂了怎么办?
生产环境里,MCP Server 是外部依赖,随时可能挂掉或者压根没启动。如果 Agent 只依赖 MCP 工具,一旦连接断开,整个功能就瘫痪了。
求职派用了一个双保险设计来解决这个问题。
第一层,注入 MCP 工具时标记为非必需。
Java
@Autowired(required = false)
public void setToolCallbackProvider(
List mcpClients) {
// MCP 可用时注册远程工具
}
required = false 意味着如果 Spring 容器里没有异步客户端实例(比如 MCP Client 被关闭或 Server 没启动),这个方法根本不会执行,也不会报错。
第二层,应用启动后检查工具是否就绪,没有就用本地工具兜底。
Java
@PostConstruct
public void initLocalToolCallback() {
if (this.toolCallbackProvider == null) {
this.toolCallbackProvider =
new StaticToolCallbackProvider(
ToolCallbacks.from(new CrawlerTools()));
}
}

逻辑非常清晰。
Spring 先尝试注入 MCP 客户端列表,有的话就用远程工具。然后 @PostConstruct 阶段检查,如果工具适配器还是空的(说明 MCP 没配置或不可用),就创建一个本地的静态工具作为兜底。
本地的爬虫工具提供了基础的网页抓取能力,虽然功能不如 Selenium 浏览器自动化强大,但保证了核心流程不会中断。
这个设计的巧妙之处在于,上层业务代码完全不需要关心当前用的是 MCP 远程工具还是本地工具。两者都实现了同一个工具回调接口,调用方式一模一样。Agent 代码写一套逻辑,工具来源的切换在启动阶段就完成了。
对于求职派这种同时对接了微信、钉钉、飞书三个渠道的系统,任何一个外部依赖的不可用都不应该导致整个服务瘫痪。MCP Client 挂了,Agent 还能用本地工具继续服务用户。
一行 required = false 加一个 @PostConstruct 检查,就把外部依赖的风险控制住了。
05、Agent 能在运行时添加新的 MCP Server 吗?
前面几节讲的都是"启动时确定好 MCP Server 列表"的静态模式。
但在实际使用中,Agent 经常需要接入新的工具能力。比如用户想用一个新的数据源,或者运维要挂载一个监控工具。
求职派在 Agent 的工具箱里内置了一个"注册 MCP Server"的工具。没错,这是一个 用来注册工具的工具。

看一下这个工具的定义( McpTool.java)。
Java
@Tool(description = "Adds a new streamable-HTTP "
+ "MCP server to the application configuration")
public String addStreamableHttpMcpServer(
String name, String url, String headers) {
configurationManager.updateProperties(props);
return "MCP server added successfully.";
}
这个工具同时支持 stdio 和 streamable-http 两种传输模式。Agent 在对话过程中如果判断需要新的外部工具,就可以通过它直接注册。
注册过程做了三件事。
校验服务名称格式,只允许字母、数字、连字符和下划线。把连接参数写入应用配置文件。返回注册成功的提示,告知需要重启才能生效。
配置的持久化通过配置管理器完成,变更会写入 application.yaml,重启后新的 MCP Server 就会被自动配置加载。
这个设计让 Agent 具备了自我扩展工具能力的可能性。虽然当前版本需要重启才能生效,但已经比手动改配置文件重新部署强了一个量级。

在求职派的 Agent 初始化代码里,MCP 注册工具和其他内置工具一起被注册到 ChatClient 中。Shell 执行工具、文件系统工具、任务管理工具、网页抓取工具,它们共同构成了 Agent 的基础能力层。
MCP 注册工具不是最常用的,但它代表了一种设计思路——Agent 不应该是一个封闭系统,它应该有能力去发现和接入新的工具。MCP 协议把工具标准化了,注册工具把接入流程也标准化了。
06、一个应用怎么同时当 Server 又当 Client?
求职派的 app 模块在 Maven 依赖里同时引入了 MCP Server 和 MCP Client 两个 starter。
同一个 Spring Boot 应用,既对外暴露工具(岗位推荐接口,让外部 Agent 调用),又连接外部 MCP Server(Selenium 浏览器工具,让自己的 Agent 使用)。

Server 端通过 @Tool 注解暴露工具,由 Spring AI 的 WebMVC MCP Server 自动配置提供 HTTP 端点。Client 端通过 YAML + JSON 配置连接外部 Server,由 MCP Client 自动配置创建客户端实例。
两条线各走各的自动配置流程,互不干扰。
Server 端暴露工具时,把带 @Tool 注解的服务注册为工具回调 Bean。
Java
@Bean
public ToolCallbackProvider dateProvider(
OcMcpService dateService) {
return MethodToolCallbackProvider.builder()
.toolObjects(dateService).build();
}
Client 端拿到的也是工具回调适配器,只不过是通过异步适配器包装的远程工具。两者在 Spring 容器里是不同的 Bean 实例,不会冲突。
这个双角色架构在多 Agent 系统中很常见。求职派自己的 Agent 需要浏览器自动化能力(Client 角色),同时又要让外部的 Claude Code 或其他 Agent 能调用岗位推荐功能(Server 角色)。
一个应用一套部署,两个角色同时运转。Spring AI 的 starter 分离设计让这件事变得很自然,不需要任何额外的隔离代码。
从实际效果来看,这种双角色架构大幅简化了部署复杂度。如果把 Server 和 Client 拆成两个独立应用,就要多维护一套服务,还得额外处理它们之间的网络通信和鉴权。放在同一个进程里,Server 端和 Client 端共享数据库连接、用户上下文和配置管理,运维成本低了很多。
ending
【从 YAML 配置到工具注册,从优雅降级到动态扩展,Spring AI 把 MCP 协议的复杂度藏在了自动配置背后。开发者要做的事情,说到底就是一个适配器加一行注入。】
但真正有意思的不是 API 怎么调。生产环境里那些不起眼的设计才值得细看。
MCP 挂了有本地工具兜底,Agent 能自己注册新的工具,同一个应用既当 Server 又当 Client。
我们下期见。
如何把求职派写到简历上?
项目名称:求职派(JobClaw)
项目简介:基于 Spring AI 的多 Agent 求职系统,对接微信/钉钉/飞书三个 IM 渠道,通过意图分类和 Agent 路由为用户提供智能求职服务。
技术栈:Java 21、Spring Boot 4.0、Spring AI 2.0、MCP 协议、LangGraph4J、JPA/H2/MySQL
核心职责:
-
基于 Spring AI MCP Client 实现外部工具集成,通过异步工具回调适配器将 MCP 远程工具统一为标准工具接口,Agent 无感调用远程和本地工具
-
设计 MCP 工具双保险降级机制,通过条件注入 + 启动后检查的两阶段策略,MCP Server 不可用时自动切换到本地工具
-
实现 MCP Server 动态注册工具,支持 Agent 在运行时通过对话注册 stdio 和 streamable-http 两种传输模式的新 MCP Server,配置自动持久化
-
构建 MCP 双角色架构,同一应用同时作为 MCP Server(暴露岗位推荐等业务工具)和 MCP Client(接入 Selenium 等外部工具)
-
基于自定义 ReAct 替代 Spring AI 默认的工具调用机制,实现细粒度的推理-执行控制和中间件生命周期拦截
大家好,我是二哥呀。
前面我们聊了怎么用 Spring AI 搭 MCP Server、怎么给它加鉴权。但光有 Server 端还不够——大模型要调用这些工具,中间必须有一个 Client 去发现工具、转发调用请求、把结果拿回来。
大多数 MCP Client 的教程都停留在了"配个 YAML,连上 Server,问个时间"这一步。真到了生产环境,问题远不止这些。
-
MCP Server 挂了,Agent 是直接报错还是有兜底方案?
-
一个应用同时是 Server 又是 Client,两个角色怎么共存?
-
Agent 能不能在对话过程中自己注册一个新的 MCP Server?
这篇就来拆解 MCP Client。从 YAML 配置到工具注册,从优雅降级到动态扩展,每个环节用求职派的源码来做证据。 https://paicoding.com/paiclaw-mcp-client
系好安全带,我们粗粗发~
01、MCP Client 到底干了什么?
先把 MCP 协议里的角色关系理清楚。
MCP Server 负责暴露工具。求职派的岗位推荐服务加上 @Tool 注解就能被外部发现和调用。MCP Client 负责连接这些 Server,拉取工具列表,在大模型需要的时候发起调用。

在 Spring AI 的体系里,MCP Client 做了三件事。
-
第一,和外部 MCP Server 建立连接,拿到工具清单。
-
第二,把这些工具包装成 Spring AI 统一的工具回调接口。
-
第三,注入到 ChatClient 或 Agent,让大模型在对话中随时调用。
关键在第二步。
Spring AI 提供了一个异步的工具回调适配器,接收一组异步 MCP 客户端实例,自动把远程工具转换成和本地工具完全一样的接口。
对上层业务来说,大模型调用一个 MCP 远程工具和调用一个本地方法,代码写法完全相同。Spring AI MCP Client 的核心价值就是屏蔽了协议细节,让工具的来源对业务完全透明。
这也意味着,如果你的 Agent 已经在用 Spring AI 的 @Tool 注解定义本地工具,接入 MCP 远程工具不需要改任何业务代码,只需要在配置里加上 MCP Server 的地址,再把工具回调适配器传进去就行了。
02、怎么配置 MCP Client?
求职派的 MCP Client 配置分两层。YAML 定义连接参数,JSON 文件定义具体的 Server 列表。
YAML 配置
先看 application-ai.yaml 中的 MCP 部分。
YAML
spring:
ai:
mcp:
client:
stdio:
servers-configuration: ${MCP_SERVERS_CONFIG}
enabled: ${AGENT_MCP_CLIENT_ENABLED:false}
name: 求职派MCP
request-timeout: 30s
type: async
几个关键配置项说明一下。
type: async 表示使用异步客户端。求职派全局统一用异步模式,代码里注入的是异步 MCP 客户端而不是同步版本。异步模式不会阻塞主线程,更适合多 Agent 并发调用的场景。
enabled 通过环境变量控制开关,默认关闭。MCP Server 是外部依赖,开发阶段不一定每次都能启动,关掉之后应用照样跑。

servers-configuration 指向一个 JSON 文件,还会区分操作系统。Windows 用 mcp-servers.json,macOS 用 mcp-servers-mac.json,因为进程启动命令不同。
JSON 定义 Server 列表
实际的 MCP Server 在 JSON 文件中定义。
JSON
{
"mcpServers": {
"selenium": {
"command": "npx",
"args": ["-y", "@angiejones/mcp-selenium"]
}
}
}
这里用的是 stdio 传输模式。
Spring AI 会启动一个子进程来运行 MCP Server,通过标准输入输出和它通信。求职派目前配置了一个 Selenium 浏览器自动化工具,用来做岗位数据的网页抓取。

除了 stdio,Spring AI 还支持 streamable-http 传输模式,通过 HTTP 请求和远程 MCP Server 通信。两种模式的配置结构不同,但上层使用方式完全一样,应用拿到的都是同一种异步客户端实例。
Spring AI 的自动配置会读取这些 YAML 和 JSON,为每个 MCP Server 创建一个异步客户端实例,注册到 Spring 容器里。业务代码通过 @Autowired 注入即可。
03、MCP 工具是怎么注册到大模型的?
配置搞定之后,Spring 容器里就有了一组异步客户端实例。但大模型不认识这些实例,它只认识 Spring AI 统一的工具回调接口。
中间需要一个桥梁,把 MCP 协议的工具转换成大模型可以调用的格式。
求职派的岗位数据采集 Agent 里有这段代码( GatherAiAgent.java)。
Java
@Autowired(required = false)
public void setToolCallbackProvider(
List mcpClients) {
if (!CollectionUtils.isEmpty(mcpClients)) {
this.toolCallbackProvider =
new AsyncMcpToolCallbackProvider(mcpClients);
}
}
这 6 行代码完成了整个桥接过程。

Spring AI 的自动配置把 JSON 里定义的每个 MCP Server 都创建为一个异步客户端实例。这些实例被收集成一个列表,注入到上面的方法里。
异步工具回调适配器拿到这些客户端后,会逐个连接 MCP Server,拉取各自暴露的工具清单,然后把每个远程工具包装成 Spring AI 标准的工具回调对象。
包装之后的工具,和本地用 @Tool 注解定义的工具在接口层面完全一致。大模型在推理过程中看到的工具列表里,本地工具和远程 MCP 工具混在一起,没有任何区别。
接下来看工具怎么传给大模型。
在多轮对话的场景下,求职派通过构建聊天选项来注册工具。
Java
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.model(modelPair.getSecond())
.toolCallbacks(
toolCallbackProvider.getToolCallbacks())
.build();
调用 getToolCallbacks() 时,适配器返回的就是已经包装好的全部工具,包括所有 MCP Server 暴露的远程工具。大模型拿到这些工具后,可以在推理过程中自主决定是否调用、调用哪一个。
整个流程串起来就是这样的。
YAML 配置 → 自动创建异步客户端 → 适配器桥接为统一工具接口 → 注入到聊天选项 → 大模型按需调用。
开发者不需要了解 MCP 协议的细节,只管把工具回调适配器传给 ChatClient 就行了。
这个机制和 Spring AI 的 Function Calling(工具调用)用的是同一套接口。如果你之前已经在用 @Tool 注解定义本地工具,MCP 远程工具的接入不会改变任何已有的工具调用逻辑。Spring AI 在设计上做到了本地工具和远程工具的统一抽象,上层代码完全感知不到工具的物理位置。
04、MCP Server 挂了怎么办?
生产环境里,MCP Server 是外部依赖,随时可能挂掉或者压根没启动。如果 Agent 只依赖 MCP 工具,一旦连接断开,整个功能就瘫痪了。
求职派用了一个双保险设计来解决这个问题。
第一层,注入 MCP 工具时标记为非必需。
Java
@Autowired(required = false)
public void setToolCallbackProvider(
List mcpClients) {
// MCP 可用时注册远程工具
}
required = false 意味着如果 Spring 容器里没有异步客户端实例(比如 MCP Client 被关闭或 Server 没启动),这个方法根本不会执行,也不会报错。
第二层,应用启动后检查工具是否就绪,没有就用本地工具兜底。
Java
@PostConstruct
public void initLocalToolCallback() {
if (this.toolCallbackProvider == null) {
this.toolCallbackProvider =
new StaticToolCallbackProvider(
ToolCallbacks.from(new CrawlerTools()));
}
}

逻辑非常清晰。
Spring 先尝试注入 MCP 客户端列表,有的话就用远程工具。然后 @PostConstruct 阶段检查,如果工具适配器还是空的(说明 MCP 没配置或不可用),就创建一个本地的静态工具作为兜底。
本地的爬虫工具提供了基础的网页抓取能力,虽然功能不如 Selenium 浏览器自动化强大,但保证了核心流程不会中断。
这个设计的巧妙之处在于,上层业务代码完全不需要关心当前用的是 MCP 远程工具还是本地工具。两者都实现了同一个工具回调接口,调用方式一模一样。Agent 代码写一套逻辑,工具来源的切换在启动阶段就完成了。
对于求职派这种同时对接了微信、钉钉、飞书三个渠道的系统,任何一个外部依赖的不可用都不应该导致整个服务瘫痪。MCP Client 挂了,Agent 还能用本地工具继续服务用户。
一行 required = false 加一个 @PostConstruct 检查,就把外部依赖的风险控制住了。
05、Agent 能在运行时添加新的 MCP Server 吗?
前面几节讲的都是"启动时确定好 MCP Server 列表"的静态模式。
但在实际使用中,Agent 经常需要接入新的工具能力。比如用户想用一个新的数据源,或者运维要挂载一个监控工具。
求职派在 Agent 的工具箱里内置了一个"注册 MCP Server"的工具。没错,这是一个 用来注册工具的工具。

看一下这个工具的定义( McpTool.java)。
Java
@Tool(description = "Adds a new streamable-HTTP "
+ "MCP server to the application configuration")
public String addStreamableHttpMcpServer(
String name, String url, String headers) {
configurationManager.updateProperties(props);
return "MCP server added successfully.";
}
这个工具同时支持 stdio 和 streamable-http 两种传输模式。Agent 在对话过程中如果判断需要新的外部工具,就可以通过它直接注册。
注册过程做了三件事。
校验服务名称格式,只允许字母、数字、连字符和下划线。把连接参数写入应用配置文件。返回注册成功的提示,告知需要重启才能生效。
配置的持久化通过配置管理器完成,变更会写入 application.yaml,重启后新的 MCP Server 就会被自动配置加载。
这个设计让 Agent 具备了自我扩展工具能力的可能性。虽然当前版本需要重启才能生效,但已经比手动改配置文件重新部署强了一个量级。

在求职派的 Agent 初始化代码里,MCP 注册工具和其他内置工具一起被注册到 ChatClient 中。Shell 执行工具、文件系统工具、任务管理工具、网页抓取工具,它们共同构成了 Agent 的基础能力层。
MCP 注册工具不是最常用的,但它代表了一种设计思路——Agent 不应该是一个封闭系统,它应该有能力去发现和接入新的工具。MCP 协议把工具标准化了,注册工具把接入流程也标准化了。
06、一个应用怎么同时当 Server 又当 Client?
求职派的 app 模块在 Maven 依赖里同时引入了 MCP Server 和 MCP Client 两个 starter。
同一个 Spring Boot 应用,既对外暴露工具(岗位推荐接口,让外部 Agent 调用),又连接外部 MCP Server(Selenium 浏览器工具,让自己的 Agent 使用)。

Server 端通过 @Tool 注解暴露工具,由 Spring AI 的 WebMVC MCP Server 自动配置提供 HTTP 端点。Client 端通过 YAML + JSON 配置连接外部 Server,由 MCP Client 自动配置创建客户端实例。
两条线各走各的自动配置流程,互不干扰。
Server 端暴露工具时,把带 @Tool 注解的服务注册为工具回调 Bean。
Java
@Bean
public ToolCallbackProvider dateProvider(
OcMcpService dateService) {
return MethodToolCallbackProvider.builder()
.toolObjects(dateService).build();
}
Client 端拿到的也是工具回调适配器,只不过是通过异步适配器包装的远程工具。两者在 Spring 容器里是不同的 Bean 实例,不会冲突。
这个双角色架构在多 Agent 系统中很常见。求职派自己的 Agent 需要浏览器自动化能力(Client 角色),同时又要让外部的 Claude Code 或其他 Agent 能调用岗位推荐功能(Server 角色)。
一个应用一套部署,两个角色同时运转。Spring AI 的 starter 分离设计让这件事变得很自然,不需要任何额外的隔离代码。
从实际效果来看,这种双角色架构大幅简化了部署复杂度。如果把 Server 和 Client 拆成两个独立应用,就要多维护一套服务,还得额外处理它们之间的网络通信和鉴权。放在同一个进程里,Server 端和 Client 端共享数据库连接、用户上下文和配置管理,运维成本低了很多。
ending
【从 YAML 配置到工具注册,从优雅降级到动态扩展,Spring AI 把 MCP 协议的复杂度藏在了自动配置背后。开发者要做的事情,说到底就是一个适配器加一行注入。】
但真正有意思的不是 API 怎么调。生产环境里那些不起眼的设计才值得细看。
MCP 挂了有本地工具兜底,Agent 能自己注册新的工具,同一个应用既当 Server 又当 Client。
我们下期见。
如何把求职派写到简历上?
项目名称:求职派(JobClaw)
项目简介:基于 Spring AI 的多 Agent 求职系统,对接微信/钉钉/飞书三个 IM 渠道,通过意图分类和 Agent 路由为用户提供智能求职服务。
技术栈:Java 21、Spring Boot 4.0、Spring AI 2.0、MCP 协议、LangGraph4J、JPA/H2/MySQL
核心职责:
-
基于 Spring AI MCP Client 实现外部工具集成,通过异步工具回调适配器将 MCP 远程工具统一为标准工具接口,Agent 无感调用远程和本地工具
-
设计 MCP 工具双保险降级机制,通过条件注入 + 启动后检查的两阶段策略,MCP Server 不可用时自动切换到本地工具
-
实现 MCP Server 动态注册工具,支持 Agent 在运行时通过对话注册 stdio 和 streamable-http 两种传输模式的新 MCP Server,配置自动持久化
-
构建 MCP 双角色架构,同一应用同时作为 MCP Server(暴露岗位推荐等业务工具)和 MCP Client(接入 Selenium 等外部工具)
-
基于自定义 ReAct 替代 Spring AI 默认的工具调用机制,实现细粒度的推理-执行控制和中间件生命周期拦截