Spring AI MCP 实战:同时实现 Server 与 Client
Function Call 解决了一个问题:
模型怎么调用工具。
但如果每个工具、每个 Agent、每个平台都有自己的一套接入协议,很快就会变成大量适配代码。
MCP(Model Context Protocol)解决的是:
给 AI 应用提供统一的工具接入协议。
可以把它理解成 AI 世界里的 USB:
Claude
Codex
Cursor
Agent
│
│ MCP
↓
┌───────────────┐
│ MCP Server │
├───────────────┤
│ 岗位查询 │
│ 浏览器操作 │
│ 文件系统 │
│ 数据库 │
│ 第三方服务 │
└───────────────┘
只要双方遵循 MCP,客户端就不需要针对每个工具重新开发适配。
1. MCP 和 Function Call 是什么关系?
这两个概念很容易混。
Function Call 更偏向:
LLM
↓
我要调用 searchJob
↓
arguments
↓
应用执行
MCP 更偏向:
这些 searchJob、openBrowser、publishArticle
到底从哪里来?
怎么发现?
怎么调用?
怎么跨进程通信?
所以可以简单记:
Function Call
解决“模型怎么使用工具”
MCP
解决“工具怎么标准化提供给模型”
最终两者通常结合使用:
User
↓
LLM
↓
Function Call
↓
MCP Client
↓
MCP Server
↓
真实工具
2. Spring AI 实现 MCP Server
一个 Spring Boot 应用想把自己的能力开放出去,本质上需要三步。
第一步,引入 MCP Server Starter。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
第二步,把 Java 方法声明成 Tool。
@Tool(
description = "根据用户求职条件查询匹配的岗位"
)
public List<Job> searchJobs(JobSearchRequest request) {
return jobService.search(request);
}
参数也要写清楚:
public class JobSearchRequest {
@ToolParam(
description = "工作地点,如武汉、北京、上海",
required = false
)
private String location;
@ToolParam(
description = "招聘类型,如秋招、春招、实习",
required = false
)
private String recruitmentType;
}
第三步,把这些工具注册给 MCP Server。
@Bean
ToolCallbackProvider jobTools(JobMcpService service) {
return MethodToolCallbackProvider.builder()
.toolObjects(service)
.build();
}
整体过程就是:
Java Method
↓
@Tool
↓
ToolCallback
↓
MCP Server
↓
外部 AI Client
3. @Tool 描述仍然非常重要
MCP 并没有改变 Tool Calling 的核心逻辑。
模型最终看到的依然是:
tool name
description
input schema
例如用户说:
看看武汉有没有央企秋招岗位
模型需要根据工具 Schema 自动得到:
{
"location": "武汉",
"companyType": "央国企",
"recruitmentType": "秋招"
}
所以:
MCP 标准化了工具接入,但真正决定模型会不会正确使用工具的,仍然是 Tool Description 和 Schema。
4. 多模块项目怎么管理几十个 Tool?
生产系统不会只有两个 Tool。
可能是:
job-module
searchJob
recommendJob
crawler-module
crawlUrl
extractJob
browser-plugin
openPage
click
screenshot
task-module
createTask
scheduleTask
一种比较好的方式是:
每个模块
↓
自己定义 @Tool
↓
自己注册 ToolCallbackProvider
↓
Spring 容器自动收集
↓
统一暴露给 MCP Server
于是新增工具时:
新增模块 / 方法
↓
注册 Bean
↓
自动进入 MCP
而不是维护一个巨大的:
registerToolA();
registerToolB();
registerToolC();
...
这背后的思想是:
工具属于业务模块,MCP Server 只负责统一暴露,不应该知道所有工具的实现细节。
5. 一个应用可以同时是 Server 和 Client
这个地方非常值得沉淀。
MCP Server 表示:
把我的能力给别人用。
MCP Client 表示:
我要使用别人的能力。
一个应用完全可以同时拥有两个身份。
外部 AI Client
↓
MCP Server
↓
┌──────────┐
│ 求职系统 │
└──────────┘
│
MCP Client
↓
┌───────────┴──────────┐
↓ ↓
Browser MCP WeChat MCP
例如求职系统自己拥有:
岗位数据库
岗位推荐
岗位搜索
所以作为 MCP Server 对外提供。
但微信公众号发布、浏览器自动化等能力可能来自外部 MCP Server。
此时求职系统又作为 MCP Client 去调用。
于是形成:
内部能力 → 对外输出
外部能力 → 对内引入
这就是 MCP 很有价值的地方。
6. MCP Client 可以扩展 Agent 能力
假设系统原本只能:
搜索岗位
推荐岗位
后来接入 Browser MCP:
导航网页
点击
填写表单
截图
又接入公众号 MCP:
创建文章
发布文章
保存草稿
Agent 就从:
Job Agent
逐渐变成:
Job Agent
+
Browser
+
Publishing
+
Search
+
External Services
而业务 Agent 不需要自己重新实现这些能力。
因此可以把 MCP 理解成:
Agent 的能力插件协议。
7. 一个很典型的工具编排
例如自动生成求职公众号文章:
定时任务
↓
查询当天岗位
↓
生成 Markdown
↓
交给 LLM
↓
LLM 决定调用 publishArticle
↓
MCP Client
↓
公众号 MCP Server
↓
保存公众号草稿
这里同时使用了:
本地业务能力
+
LLM
+
外部 MCP Tool
这已经不只是一次 Tool Call,而是在做:
跨系统工具编排。
8. MCP 的生产难点:连接和会话
Demo 往往到:
Client 成功连上 Server
就结束了。
但真正上线之后马上会遇到:
服务重启
网络断开
Client 重连
Session 丢失
初始化状态丢失
MCP 建立连接时通常需要经历:
initialize
↓
协议协商
↓
notifications/initialized
↓
Session Ready
所以即使工具调用本身可以设计得比较无状态,连接初始化过程仍然存在生命周期状态。
服务一旦重启:
内存 Session
→ 消失
客户端可能已经认为自己初始化完成,但服务器端已经什么都不知道了。
于是工具调用可能失败。
9. 会话恢复怎么做?
一种生产思路是:
initialize
↓
保存初始化信息
initialized
↓
保存确认信息
持久化:
sessionId
userId
initializePayload
initializedPayload
服务重启以后:
Client 重连
↓
发现当前 Session 未初始化
↓
数据库查询历史握手数据
↓
恢复 / 重放初始化过程
↓
继续 Tool Call
本质上是在做:
Session Recovery。
所以 MCP 真正进入生产环境以后,关注点会从:
“工具能不能调用?”
升级成:
“工具系统能不能长期稳定运行?”
10. Session ID 也需要设计
还有一个问题:
服务重启
↓
重新建立连接
↓
生成新的 sessionId
旧 Session 就找不到了。
因此生产设计不能永远只依赖:
sessionId
还可能需要:
userId
clientId
deviceId
connectionId
作为恢复依据。
本质和前面的 ChatMemory 很像:
ID 决定了状态属于谁。
如果未来存在:
同一用户
+
多个设备
+
多个 MCP Client
就必须设计更细的连接身份体系,而不能只按 userId 恢复最近一次 Session。
11. 最终架构
整套架构可以压成这张图:
Claude / Codex / Cursor
│
│ MCP
↓
MCP Server
│
Spring AI Tools
│
┌──────────────┼──────────────┐
↓ ↓ ↓
Job Tool Browser Tool Task Tool
│
│
AI Application
│
MCP Client
│
┌──────────────┼──────────────┐
↓ ↓ ↓
Browser MCP Publish MCP Other MCP
所以这个应用既是:
能力提供者
MCP Server
也是:
能力消费者
MCP Client
12. 这篇真正值得沉淀什么?
可以记住这一条链路:
@Tool
↓
ToolCallback
↓
MCP Server
↓
标准协议
↓
MCP Client
↓
Agent
以及几个核心概念:
MCP Server 对外提供工具
MCP Client 接入外部工具
Tool 真正的业务能力
ToolCallback Spring AI 工具抽象
Module Registry 模块化工具注册
Session Recovery 生产级连接恢复
最重要的一句话是:
Function Call 让模型学会“调用工具”,MCP 则把工具变成可以跨应用、跨语言、跨进程复用的标准能力。
把前面几篇继续串起来,现在 Spring AI Agent 的主干已经很完整了:
Model Routing
→ 用哪个模型
ChatMemory
→ 模型记住什么
Structured Output
→ 模型怎么返回
Function Call
→ 模型怎么使用工具
MCP
→ 工具从哪里来,以及怎么跨系统复用