大模型开发 - SpringAI之MCP Client开发:让Agent动态调用远程工具服务
文章目录
- 引言
- 一、MCP协议概念:标准化的工具连接
- 二、MCP架构设计:Client-Server通信模式
- 三、依赖引入与项目配置
- 四、MCP Server端实现详解
- 五、MCP Client端实现详解
- 六、MCP vs 直接Tool Calling:架构对比
- 七、MCP协议深度探讨
- 八、生产环保的最佳实践
- 九、实战案例:多Agent系统架构
- 十、常见问题与故障排查
- 十一、总结与展望
- 总结

引言
当我们的AI Agent功能足够丰富时,一个新的问题浮现出来:如何让多个独立的Agent系统共享工具能力?
考虑这样的场景:
- 一个天气查询服务单独部署在8082端口,提供"获取天气"工具
- 一个医疗咨询Agent部署在8081端口,需要调用天气工具
- 两个系统怎样才能优雅地协作,而不是把工具代码重复写一遍?
MCP(Model Context Protocol)协议 应运而生。它是Anthropic定义的一套标准化协议,用于AI模型与外部工具服务进行通信。Spring AI从1.1.0版本开始原生支持MCP Client,让我们可以:
- 动态发现远程MCP Server提供的所有工具
- 跨进程调用这些工具,就像本地工具一样自然
- 简化部署——工具无需重复编码,可以被多个Agent共享
本文将深入讲解MCP协议的设计理念、Spring AI的实现方式,以及如何通过仅仅几行配置,让你的Agent获得访问远程工具服务的能力。
一、MCP协议概念:标准化的工具连接
1.1 为什么需要MCP?
在MCP出现之前,工具调用是这样的:
┌─────────────────────────┐ │ Agent1(8081) │ │ ├─ ToolA │ │ ├─ ToolB │ │ └─ ToolC │ └─────────────────────────┘ ┌─────────────────────────┐ │ Agent2(8082) │ │ ├─ ToolA(重复) │ │ ├─ ToolB(重复) │ │ └─ ToolD │ └─────────────────────────┘ 问题多多:
- 工具代码重复维护
- 工具版本不一致
- 扩展新工具需要修改多处代码
- 微服务之间缺乏标准的工具调用协议
MCP的出现改变了这一切:
┌──────────────────────┐ │ AgentClient │ │ (8081) │ │ ToolCallbackProvider│────┐ └──────────────────────┘ │ │ MCP Protocol │ (HTTP/WebSocket) ▼ ┌──────────────────────┐ │ MCP Server │ │ (8082) │ │ ├─ WeatherTool │ │ ├─ ConfigResource │ │ └─ GreetingPrompt │ └──────────────────────┘ 1.2 MCP的三大核心概念
工具(Tools)
MCP Server暴露的可执行函数,Client可以通过标准协议调用。
比如WeatherService的getWeather方法:
@McpTool(description ="获取指定城市的天气")publicStringgetWeather(String cityName){// ... 实现}资源(Resources)
MCP Server暴露的数据资源,可以被Client读取。用于传输静态配置、模板等。
@McpResource(uri ="config://{key}", name ="configuration")publicStringgetConfig(String key){return environment.getProperty(key,"123");}提示词(Prompts)
MCP Server定义的预设提示模板,Client可以请求并使用这些模板。
@McpPrompt(name ="greeting", description ="欢迎语")publicMcpSchema.GetPromptResultgreeting(@McpArg(name ="name")String name){String message ="你好, "+ name +"! 有什么可以帮您?";returnnewMcpSchema.GetPromptResult(...);}1.3 MCP的设计哲学
标准化:所有MCP Server都遵循同一套接口规范,Client无需关心具体实现。
解耦:Server和Client独立部署、独立升级,通过协议通信。
动态发现:Client启动时自动探测Server的所有能力(tools、resources、prompts),不需要硬编码工具列表。
协议中立:MCP本身与传输层无关,可以用HTTP、WebSocket、stdio等多种方式实现。
二、MCP架构设计:Client-Server通信模式
2.1 整体架构图
┌─────────────────────────────────────────────────────────────┐ │ Spring AI Application │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ ChatClient │ │ │ │ (负责与大模型交互) │ │ │ └──────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ │ │ ┌──────────────────────┴──────────────────────────────┐ │ │ │ │ │ │ │ ToolCallbackProvider.getToolCallbacks() │ │ │ │ (动态加载MCP Server的工具) │ │ │ │ │ │ │ └──────────────────────┬──────────────────────────────┘ │ └─────────────────────────┼──────────────────────────────────┘ │ ┌─────────┴─────────┐ │ MCP HTTP Client │ │ ConnectionPool │ └─────────┬─────────┘ │ MCP Protocol(HTTP) streamable-http:// │ ┌─────────▼─────────┐ │ MCP Server │ │ (8082) │ │ │ │ ┌──────────────┐ │ │ │ WeatherTool │ │ │ │ ConfigResource │ │ │ GreetingPrompt │ │ └──────────────┘ │ └───────────────────┘ 2.2 通信流程
阶段1:初始化与发现(Startup)
- MCP Client启动时,连接到Server
- 发送initialize请求,告诉Server客户端信息
- Server响应,返回自己支持的协议版本
- Client发送list-tools请求,获取所有可用工具
- Server返回工具列表,包含工具名、描述、参数schema
ClientServer │ │ ├──────── initialize ─────────►│ │ │ │◄─── initialize response ─────┤ │ │ ├──────── list-tools ─────────►│ │ │ │◄─── [getWeather,...] ───────┤ 阶段2:工具调用(Tool Calling)
当大模型决定调用工具时:
┌─ ChatClient ──────────────────────────────┐ │ prompt:"上海天气怎样?" │ │ tools:[getWeather,...] │ │ (tools来自MCP Server) │ └────────────┬──────────────────────────────┘ │ ▼ ┌─ LLM Response ────────────────┐ │ { │ │ "tool_calls":[ │ │ { │ │ "id":"call_123", │ │ "function":{ │ │ "name":"getWeather", │ │ "arguments":{ │ │ "cityName":"上海" │ │ } │ │ } │ │ } │ │ ] │ │ } │ └────────────┬──────────────────┘ │ ▼ ┌─ ToolCallbackProvider ────────────────────┐ │ 1. 识别工具名: getWeather │ │ 2. 从MCP Server动态调用 │ │ 3. 返回结果:"天晴" │ └────────────┬──────────────────────────────┘ │ ▼ ┌─ ChatClient ──────────────────────────────┐ │ 添加tool message到memory │ │ 继续与LLM交互 │ └────────────────────────────────────────────┘ 2.3 ToolCallbackProvider的职责
ToolCallbackProvider是Spring AI中的关键接口,负责:
- 工具发现:启动时连接MCP Server,获取所有工具定义
- 生成Callbacks:为每个工具创建可调用的Callback对象
- 动态绑定:大模型调用工具时,动态找到对应Callback并执行
伪代码如下:
publicclassMcpToolCallbackProviderimplementsToolCallbackProvider{privatefinalMCP_CLIENT mcpClient;// MCP HTTP客户端publicList<ToolCallback>getToolCallbacks(){// 1. 从MCP Server发送list-tools请求List<Tool> tools = mcpClient.listTools();// 2. 为每个工具创建动态Callbackreturn tools.stream().map(tool ->createCallback(tool)).toList();}privateToolCallbackcreateCallback(Tool tool){returnnewToolCallback(){@OverridepublicStringgetName(){return tool.getName();}@OverridepublicStringgetDescription(){return tool.getDescription();}@OverridepublicStringcall(String arguments){// 3. 实际调用:远程调用MCP Server的工具return mcpClient.callTool(tool.getName(), arguments);}};}}三、依赖引入与项目配置
3.1 添加MCP Client依赖
在spring-ai-demo/pom.xml中,与其他Spring AI依赖并列添加:
<dependencies><!-- 现有依赖... --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId></dependency></dependencies>这个依赖包含:
MCP Client核心库HTTP连接器(streamable-http)ToolCallbackProvider实现
3.2 MCP Server端配置
在spring-ai-mcp-server-demo/src/main/resources/application.yml中:
spring:ai:mcp:server:name: weather-mcp-server # Server名称protocol: streamable # 使用HTTP协议server:port:8082# 独立端口关键点:
protocol: streamable表示使用HTTP SSE(Server-Sent Events)方式- 端口8082与Client的配置相对应
3.3 MCP Client端配置
在spring-ai-demo/src/main/resources/application.yml中:
spring:ai:mcp:client:name: spring-ai-mcp-client-demo # Client名称,标识自己streamable-http:# HTTP连接方式connections:server1:# 连接名(任意)url: http://localhost:8082# MCP Server地址重要参数说明:
| 参数 | 说明 | 示例 |
|---|---|---|
name | 当前应用在MCP中的标识 | spring-ai-mcp-client-demo |
connections | 可以连接多个MCP Server | 下例支持多Server |
url | MCP Server的HTTP入口 | http://localhost:8082 |
多Server配置示例:
spring:ai:mcp:client:streamable-http:connections:weather-server:url: http://localhost:8082config-server:url: http://config-server:9000search-server:url: http://search-server:9001此时Client会自动发现三个Server的所有工具。
四、MCP Server端实现详解
4.1 WeatherService:工具提供者
MCP Server通过@McpTool注解声明工具:
@Service@Log4j2publicclassWeatherService{@AutowiredprivateEnvironment environment;// 工具1:获取天气@McpTool(description ="获取指定城市的天气")publicStringgetWeather(String cityName){ log.info("正在获取天气信息...");if(cityName.equals("上海")){return"天晴";}elseif(cityName.equals("北京")){return"下雨";}return"不知道";}// 工具2:问候提示词@McpPrompt(name ="greeting", description ="欢迎语")publicMcpSchema.GetPromptResultgreeting(@McpArg(name ="name")String name){String message ="你好, "+ name +"! 有什么可以帮您?";returnnewMcpSchema.GetPromptResult("Greeting",List.of(newMcpSchema.PromptMessage(McpSchema.Role.ASSISTANT,newMcpSchema.TextContent(message))));}// 工具3:配置资源@McpResource(uri ="config://{key}", name ="configuration")publicStringgetConfig(String key){return environment.getProperty(key,"123");}}三种能力详解:
@McpTool - 可调用的函数工具
@McpTool(description ="获取指定城市的天气")publicStringgetWeather(String cityName){...}- 可被Client动态调用
- 参数自动序列化为JSON Schema
- 返回值作为工具执行结果
当Client调用时,MCP协议自动处理:
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"getWeather","arguments":{"cityName":"上海"}}}@McpResource - 只读数据资源
@McpResource(uri ="config://{key}", name ="configuration")publicStringgetConfig(String key){...}- URI模板:支持参数化读取
- 读取示例:
config://spring.datasource.url→ 返回数据库地址 - 用途:共享配置、常量、模板等静态资源
@McpPrompt - 预设提示词模板
@McpPrompt(name ="greeting", description ="欢迎语")publicMcpSchema.GetPromptResultgreeting(@McpArg(name ="name")String name){...}- 提供预编写的提示词
- 支持参数化(如用户名)
- 用途:系统级别的提示词模板共享
4.2 MCP Server的启动配置
@SpringBootApplicationpublicclassMcpServerApplication{// 注意:不需要手动配置ToolCallbackProvider// Spring AI会自动扫描@McpTool @McpResource @McpPrompt注解// 并通过MCP协议暴露这些能力publicstaticvoidmain(String[] args){SpringApplication.run(McpServerApplication.class, args);}}重要:Spring AI自动检测并注册所有带MCP注解的Bean,无需手动配置。
五、MCP Client端实现详解
5.1 McpController:工具调用的入口
@RestControllerpublicclassMcpController{@AutowiredprivateChatClient chatClient;@AutowiredprivateToolCallbackProvider toolCallbackProvider;@GetMapping("/mcp")publicStringmcp(String message){return chatClient .prompt().user(message).toolCallbacks(toolCallbackProvider.getToolCallbacks()).call().content();}}逐行解析:
ToolCallbackProvider toolCallbackProvider- Spring AI自动注入
- 负责从MCP Server动态加载工具
toolCallbackProvider.getToolCallbacks()- 返回所有可用工具的Callback列表
- 这些Callback会被传给大模型
- 大模型可以选择调用其中任何一个
.toolCallbacks(...)- 将MCP Server的工具注入到ChatClient
- 大模型现在可以使用这些工具
- 完整执行流:
GET /mcp?message=上海天气怎样? │ ▼ User:"上海天气怎样?"Tools:[getWeather (from MCP Server)] ◄── 动态加载 │ ▼ LLM Response: tool_calls=[getWeather(cityName="上海")] │ ▼ ToolCallbackProvider.call("getWeather",{...}) │ ▼ MCP Client → HTTP → MCP Server tools/call getWeather │ ▼ MCP Server:"天晴" │ ▼ LLM:"根据天气工具的反馈,上海天晴" │ ▼ 返回给用户 5.2 实际测试
启动两个应用后,测试MCP调用:
# MCP Server启动在8082cd spring-ai-mcp-server-demo mvn spring-boot:run # MCP Client启动在8081cd spring-ai-demo mvn spring-boot:run # 测试调用curl"http://localhost:8081/mcp?message=上海天气怎样?"预期输出:
根据我获取的天气信息,上海天晴。 背后的调用链:
- Client的ChatClient收到请求
- 向大模型提交用户问题 + MCP Server暴露的工具列表
- 大模型选择调用
getWeather("上海") - ToolCallbackProvider通过MCP协议远程调用
- 得到"天晴"的结果
- 反馈给大模型继续推理
- 大模型生成最终回复
六、MCP vs 直接Tool Calling:架构对比
6.1 直接Tool Calling方式(之前的做法)
@ConfigurationpublicclassToolConfig{@BeanpublicToolCallbackProviderlocalTools(WeatherService weatherService){// 把WeatherService的方法注册为本地工具returnMethodToolCallbackProvider.builder().toolObjects(weatherService).build();}}@RestControllerpublicclassChatController{@GetMapping("/chat")publicStringchat(String message){return chatClient.prompt().user(message).toolCallbacks(localTools.getToolCallbacks())// 本地工具.call().content();}}特点:
- 工具与Agent紧密耦合
- 工具代码必须与Agent在同一进程
- 工具更新需要重启Agent应用
6.2 MCP Client方式(新做法)
# 仅需配置,无需代码spring:ai:mcp:client:streamable-http:connections:server1:url: http://localhost:8082@RestControllerpublicclassMcpController{@GetMapping("/mcp")publicStringmcp(String message){return chatClient.prompt().user(message).toolCallbacks(toolCallbackProvider.getToolCallbacks())// 远程工具.call().content();}}特点:
- 工具独立部署
- Agent与工具解耦
- 动态发现,无需硬编码
- 工具更新无需重启Agent
6.3 对比表
| 维度 | 直接Tool Calling | MCP Client |
|---|---|---|
| 部署方式 | 单一进程 | 多进程 |
| 通信方式 | 方法调用 | HTTP/WebSocket RPC |
| 工具发现 | 硬编码注册 | 动态发现 |
| 工具复用 | 重复编码 | 一次部署,多处使用 |
| 更新影响 | 需重启Agent | 仅Server重启 |
| 网络延迟 | 无 | 有(毫秒级) |
| 扩展性 | 中等 | 高 |
| 适用场景 | 小型单体应用 | 微服务、多Agent系统 |
6.4 选择策略
选用直接Tool Calling:
- Agent数量少(1-2个)
- 工具相对稳定,不常更新
- 工具逻辑简单,计算量小
- 无网络延迟要求
选用MCP Client:
- Agent数量多(3个以上)
- 工具经常更新迭代
- 多个Agent共享相同工具
- 工具可能部署在不同机器/容器
- 需要微服务架构
七、MCP协议深度探讨
7.1 MCP的Request-Response模式
所有通信都遵循JSON-RPC 2.0标准:
Request格式
{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"getWeather","arguments":{"cityName":"上海"}}}Response格式
{"jsonrpc":"2.0","id":"1","result":{"content":[{"type":"text","text":"天晴"}]}}或错误响应:
{"jsonrpc":"2.0","id":"1","error":{"code":-32600,"message":"Invalid Request","data":{"details":"city not found"}}}7.2 参数自动映射
MCP Server会自动将Java方法签名转换为JSON Schema,供Client理解:
@McpTool(description ="获取指定城市的天气")publicStringgetWeather(String cityName){...}自动生成的Schema:
{"name":"getWeather","description":"获取指定城市的天气","inputSchema":{"type":"object","properties":{"cityName":{"type":"string","description":"城市名称"}},"required":["cityName"]}}LLM看到这个Schema,就知道如何调用这个工具。
7.3 连接池与健康检查
Spring AI MCP Client维护HTTP连接池,定期检查Server健康状态:
spring:ai:mcp:client:streamable-http:connections:server1:url: http://localhost:8082# 可选:配置连接超时timeout: 30s # 可选:配置心跳间隔heartbeat-interval: 60s 如果Server不可用,Client会:
- 记录日志
- 标记连接为"不可用"
- 继续尝试重连
- 大模型无法使用该Server的工具(但不会崩溃)
八、生产环保的最佳实践
8.1 错误处理
MCP工具执行失败时,大模型应该收到错误信息:
@McpTool(description ="获取指定城市的天气")publicStringgetWeather(String cityName){try{// 调用天气APIWeatherData data = weatherApi.fetch(cityName);return data.getDescription();}catch(CityNotFoundException e){// 返回有意义的错误信息return"Error: 城市 "+ cityName +" 不存在";}catch(ApiTimeoutException e){return"Error: 天气服务暂时不可用,请稍后重试";}}大模型会收到这个错误信息,并可能:
- 重新尝试
- 使用备选方案
- 向用户解释为什么无法获取信息
8.2 日志与监控
在Server端详细记录所有工具调用:
@McpTool(description ="获取指定城市的天气")publicStringgetWeather(String cityName){ log.info("MCP Tool called: getWeather, cityName={}", cityName);long startTime =System.currentTimeMillis();try{String result =fetchWeather(cityName);long elapsed =System.currentTimeMillis()- startTime; log.info("MCP Tool success: getWeather, cityName={}, elapsed={}ms", cityName, elapsed);return result;}catch(Exception e){ log.error("MCP Tool error: getWeather, cityName={}", cityName, e);throw e;}}关键指标:
- 工具调用次数
- 调用延迟(网络+执行)
- 错误率
- Server连接状态
8.3 版本兼容性
Server和Client的版本可以不同步,但要保持兼容:
// Server更新添加新参数时@McpTool(description ="获取天气")publicStringgetWeather(String cityName,@McpArg(required =false)String timestamp // 新参数,但可选){// 旧Client发送的请求不包含timestamp,也能正常工作}8.4 安全性考虑
认证与授权
spring:ai:mcp:client:streamable-http:connections:secure-server:url: https://secure-server:8082auth:type: bearer token: ${MCP_SERVER_TOKEN}# 环境变量速率限制
@McpTool(description ="获取天气")@RateLimit(requestsPerMinute =60)publicStringgetWeather(String cityName){...}九、实战案例:多Agent系统架构
假设你要构建一个企业级AI系统,包含三个独立Agent:
┌─────────────────────┐ │ ChatBotAgent │ 8081 │ (对话机器人) │ └──────────┬──────────┘ │ MCP ├─────────┬─────────┬──────────┐ │ │ │ │ ┌──────▼───┐ ┌───▼────┐ ┌─▼──────┐ ┌▼────────┐ │ Weather │ │ Config │ │ Search │ │Database │ │ Service │ │Service │ │Service │ │Service │ │ 8082 │ │ 8083 │ │ 8084 │ │ 8085 │ └──────────┘ └────────┘ └────────┘ └─────────┘ 在ChatBot Agent的配置中:
spring:ai:mcp:client:name: chatbot-mcp-client streamable-http:connections:weather:url: http://localhost:8082config:url: http://localhost:8083search:url: http://localhost:8084database:url: http://localhost:8085现在ChatBot Agent可以:
- 调用WeatherService获取天气
- 调用ConfigService查询配置
- 调用SearchService搜索网络
- 调用DatabaseService查询数据库
这些服务可以独立部署、独立更新、独立扩展,ChatBot完全无感知。
十、常见问题与故障排查
10.1 “Tool not found” 错误
现象:大模型要调用某个工具,但系统报错"Tool not found"
原因:
- MCP Server未启动
- Server地址配置错误
- 工具定义有问题
排查步骤:
# 1. 检查Server是否在线curl http://localhost:8082/health # 如果有health endpoint# 2. 检查Client日志# 查看是否有"Connected to MCP Server"的日志# 3. 手动测试工具列表# 在Client启动日志中应该看到工具列表被加载# 4. 验证工具名称拼写# 确保Client请求的工具名与Server定义的完全相同10.2 网络延迟
现象:工具调用很慢
原因:
- HTTP连接开销
- 网络距离远
- Server处理慢
优化方案:
# 1. 启用连接复用spring:ai:mcp:client:streamable-http:connections:server1:url: http://localhost:8082keep-alive:trueconnection-timeout: 5s # 2. 部署Server靠近Client# 使用容器编排工具(Docker, K8s)确保网络延迟<10ms# 3. 考虑使用WebSocket而非HTTP# WebSocket长连接开销更小(未来版本)10.3 Server宕机处理
现象:Server无响应,Client应用停滞
原因:Client等待Server响应超时
解决方案:
spring:ai:mcp:client:streamable-http:connections:server1:url: http://localhost:8082timeout: 10s # 设置合理超时retry:max-attempts:3backoff: exponential 此时大模型仍可用,只是无法调用该Server的工具。
十一、总结与展望
11.1 MCP Client的核心价值
| 能力 | 收益 |
|---|---|
| 动态工具发现 | 无需硬编码工具列表 |
| 工具共享 | 一个工具,多个Agent使用 |
| 独立演进 | Server和Client独立开发 |
| 微服务支持 | 适配容器化部署 |
| 协议标准化 | 与Anthropic官方工具无缝对接 |
11.2 MCP的未来
Anthropic宣布MCP成为AI Agent的通用标准:
- 语言无关:Java、Python、Node.js都有SDK
- AI模型无关:Claude、GPT、开源模型都支持
- 工具市场:未来可能出现MCP工具市场,类似npm
- IDE集成:开发工具将内置MCP支持
11.3 学习路线
基础 → 进阶 → 生产 │ │ │ ├─────┴──────┴──────────────────────┐ │ │ ▼ ▼ 单Server工具调用 多Server容器化部署 ↓ ↓ 本地测试 Kubernetes编排 ↓ ↓ 性能优化 工具市场集成 11.4 与我们之前学到的内容的联系
Spring AI学习路线: 第1步:ToolCalling基础 (直接调用本地工具) ↓ 第2步: RAG +Memory(增强知识库与记忆) ↓ 第3步: MCP Client(本文) (远程工具调用) ↓ 第4步: 多Agent系统 (多个Agent协作) ↓ 第5步: 生产化部署 (容器、监控、高可用)本文介绍的MCP Client正是从单Agent应用到多Agent系统的关键桥梁。
总结
通过MCP(Model Context Protocol),我们打破了Agent与工具的紧耦合关系:
- 工具变成了独立服务,可以单独部署和演进
- 大模型获得了远程工具调用的能力,就像使用本地工具一样自然
- 多个Agent可以共享同一套工具,避免代码重复
- 系统整体的扩展性和灵活性得到了质的提升
MCP的出现,标志着AI Agent从"单体应用"走向"分布式系统"的开始。
在下一篇文章中,我们将深入多Agent系统的设计与实现——如何让多个Agent之间互相通信、协作完成复杂任务。