直接说结论:如果你所在团队正在做企业级Agent,或者准备把AI能力接进已有的Java技术栈,Spring AI + MCP这套组合目前是少有的“生产可用”路线。这不是又一个demo级的玩具项目,而是能真正扛住业务压力的基础架构。我这篇文章不聊PPT,全部是实际踩坑和落地的细节,从MCP协议的本质讲到Spring AI Alibaba的工程化配置,再到向量库覆盖写入、连接重连、工具注册失败这些高频问题,一次性说完。
1. 项目背景与整体设计思路
1.1 为什么企业级Agent必须认真对待MCP
自从大模型能力从“聊天”走到“做事”,Agent这个概念就突然变得特别热。但等你真正动手做企业级Agent的时候,会发现一个尴尬的事实:光靠提示词根本走不通。业务系统里的数据是分散的,ERP一套、CRM一套、自研中台一套,每个系统的接口格式都不一样。你让大模型直接读文档、调API,它只会一脸懵。
MCP(Model Context Protocol)解决的就是这个问题。它相当于给了大模型一套标准化的“交接班协议”,让模型不再需要理解每个系统各自不同的鉴权方式、参数格式、返回结构,而是通过统一的接口去访问工具和资源。你可以把MCP理解成USB-C接口——以前每家手机厂商都有自己的充电口,现在都统一了,插上就能用。
我在实际项目里最直观的感受是:以前对接一个新系统,至少要写两天适配代码,还要反复调试;现在只要对方提供一个MCP Server,直接注册进去,当天就能在对话里调用。这个效率提升对团队来说不是一点半点,是质变。
1.2 技术选型:为什么是Spring AI而不是LangChain
国内做AI应用,很多团队一上来就想起LangChain。但说实话,如果你是一家Java占主导的公司,强制让团队去写Python的LangChain,后续的维护成本会非常高。你不仅要维护两套技术栈,还得处理AI服务与现有微服务体系的打通问题,DevOps、监控、日志全都要重新搞。
Spring AI的出现恰好解决了这个痛点。它把大模型接入做成了Spring Boot Starter的模式,你熟悉的自动装配、配置中心、Actuator监控那一套全部可以复用。Spring AI Alibaba更是把阿里系的一些组件也纳入了进来,比如Sofa、Graph等,对国内开发者非常友好。
我选型的时候也对比过直接调OpenAI/通义千问的SDK,但那是纯API级别的接入。Spring AI的价值在于它提供了抽象层——你在代码里面向ChatClient、EmbeddingModel、ToolCallback编程,而不是绑定某一家模型厂商。将来模型供应商想换就换,底层实现一换,上层逻辑不用动。
注意:Spring AI目前还在快速迭代阶段,版本之间的API变动比较频繁。选型时要锁定版本,别追最新。我在项目中锁的是Spring Boot 3.2.x + Spring AI Alibaba 1.0.0.2,稳定性和功能都可以接受。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念拆解:MCP、Agent与Skill
2.1 MCP协议到底解决了什么问题
很多人把MCP和Agent混为一谈,其实它们是不同层次的东西。MCP是模型与外部工具之间的一项通信协议,属于基础设施层;Agent则是承载业务逻辑的智能体,它负责推理、规划、决策,然后调用各种工具去完成任务。MCP是Agent的“手脚连接器”,Agent的“大脑”在模型那里。
MCP协议的核心有三个角色:MCP Host(宿主,通常是Agent应用)、MCP Server(工具提供方)、MCP Client(宿主与Server之间的连接器)。在实际项目里,Spring AI应用就是Host,它启动时会连接多个MCP Server,每个Server暴露一批工具(Tools)、资源(Resources)和提示词(Prompts),模型在推理过程中决定调用哪些工具。
需要理解的是,MCP Server本身并不负责AI推理。它只是把自己包装成一套标准接口等待调用。比如你做一个“订单查询”的MCP Server,它接收参数,去数据库查数据,返回结构化结果。模型只是决定“什么时候该调用这个工具”,具体干活的是这个Server里的代码。
2.2 MCP Server的三种形态与选型
从协议传输方式来看,MCP Server主要分三种:基于stdio的本地进程、基于SSE(Server-Sent Events)的远程服务、以及新兴的Streamable HTTP形态。在企业级场景里,我几乎只推荐远程SSE或HTTP形态。
stdio形态适合开发调试,它要求MCP Server和Agent应用跑在同一台机器上,通过标准输入输出通信。生产环境一旦要横向扩展Agent实例,stdio这种绑定进程的玩法就废了。SSE形态是Server单独部署成HTTP服务,Agent通过网络调用,这样Server可以独立扩容,也可以复用已有的鉴权网关。
在Spring AI Alibaba的体系里,你既可以通过spring-ai-starter-mcp-server把当前Spring应用暴露成一个MCP Server,也可以通过spring-ai-starter-mcp-client把远程MCP Server接入当前Spring应用。两端都封装好了,不需要自己手写SSE或WebSocket通信细节。
踩坑提醒:如果MCP Server需要处理大量并发请求,注意SSE连接是有状态的。Agent端和Server端要处理好连接池和断线重连,否则运行一段时间后会出现“工具调用超时”这类问题。后面的排查章节我会专门讲。
2.3 Agent Skill和MCP有什么区别
这是我在技术社区里被问得最多的问题之一。很多初学者看到“Skill”和“MCP”都觉得是指同一个东西,其实它们维度不同。
MCP是工具调用的标准化协议,它解决的是“模型怎么调用外部功能”的问题。而Skill在Spring AI Alibaba的语境里,更偏向“提示词 + 工具 + 编排逻辑”的可复用封装。打个比方,MCP相当于工具箱里的一把把螺丝刀,而Skill是一整套“怎么用这些螺丝刀组装柜子”的作业指导书。Skill本身不关心底层工具是通过MCP接入的还是直接Function Calling接入的,它更关心流程编排。
在企业级项目里,建议把两者结合使用。底层统一用MCP Server暴露工具,上层针对典型的业务场景(比如“售后工单自动分诊”“合同风险审查”)封装成Skill。这样既保证了工具层面的标准化,又能让业务逻辑沉淀成可复用的资产。
2.4 企业级Agent的整体架构设计
我落地过的Agent项目,大致分了四层:
- 接入层:面向终端用户(IM、Web、OA系统)提供统一入口。
- 智能体层:每个Agent对应一个业务域,内部维护上下文、调用Skill、执行规划。
- 工具层:各业务系统通过MCP Server把能力暴露出来,统一注册到工具中心。
- 模型层:支持配置多个大模型,按场景路由(如复杂推理用最强模型,简单抽取用小模型)。
这个分层设计的关键意图是:让模型只做决策,不让它接触杂乱的数据源和系统细节。所有数据源的适配都下沉到MCP Server里,模型只按照MCP协议去调用“黑盒”工具。这样Agent的职责就非常纯粹:理解意图、拆分任务、调用工具、汇总结果。
3. 实操过程与核心实现
3.1 搭建Spring AI项目并接入MCP
以下是我在真实项目里的搭建步骤,全部基于Spring Boot 3.2.x记录。先看一眼核心的Maven依赖:
xml复制<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
在application.yml里,需要配置两块内容:模型端点和MCP Client连接信息。模型端点的配置各家模型厂商略有差异,核心是base-url和api-key要填对。MCP Client这边需要声明要连接的Server地址:
yaml复制spring:
ai:
alibaba:
tongyi:
base-url: https://dashscope.aliyuncs.com/api/v1
api-key: ${DASHSCOPE_API_KEY}
mcp:
client:
connections:
- name: order-service
url: http://mcp-order-service.internal:8080/sse
type: sse
启动应用后,Spring AI会建立连接并拉取远程MCP Server的工具列表。可以写一个启动日志确认连接状态:
bash复制# 查看应用启动日志里是否出现MCP工具注册信息
grep "Registered tool" logs/app.log
如果看到类似Registered tool: queryOrder这样的日志,说明MCP接入成功。
3.2 用@Tool暴露你自己的MCP工具
除了接入现成的MCP Server,你还经常需要把当前的Spring Boot应用本身暴露成一个MCP Server,供其他Agent复用。Spring AI提供了@Tool注解,直接在方法上加注解就能暴露工具:
java复制@Component
public class RiskControlTools {
@Tool(name = "checkUserRiskLevel", description = "根据用户ID查询用户风险等级,返回值为LOW/MEDIUM/HIGH")
public String checkUserRiskLevel(@ToolParam(description = "用户唯一ID") String userId) {
// 此处是真实的业务逻辑
return riskService.getUserLevel(userId);
}
}
这里有两个细节极其重要。第一个是工具名必须全局唯一,重复会导致工具注册互相覆盖。第二个是工具描述要写清楚“什么场景下用、输入什么、返回什么”,因为大模型是靠描述来决定要不要调用这个工具的,描述得含糊,模型就会产生误判。
我在项目里还遇到过工具参数描述不清楚,导致模型把字符串字段拼错的情况。后来把每个@ToolParam的description都写得特别详细,甚至给出示例值,才明显改善。这一点看似微小,却直接决定了Agent的准确率。
3.3 结构化输出:定义好实体类比什么都重要
Agent开发里一个很容易被忽略的技术细节是结构化输出。你让模型返回一段自由文本,它经常会带各种前缀后缀;但如果你让它按照你定义的JSON Schema返回,效果会稳定得多。
在Spring AI里,你可以直接用一个Java record来声明返回结构:
java复制public record OrderAnalysisResult(
@JsonDescription("订单ID") String orderId,
@JsonDescription("订单总金额,单位元") BigDecimal totalAmount,
@JsonDescription("风险等级:LOW/MEDIUM/HIGH") String riskLevel,
@JsonDescription("分析建议") String suggestion
) {}
然后调用模型时指定这个类型:
java复制ChatClient chatClient = ...;
OrderAnalysisResult result = chatClient.prompt()
.user("分析订单 20241001 的风险情况")
.call()
.entity(OrderAnalysisResult.class);
这里要我多说一句:实体类的@JsonDescription字段注释一定要写完整,因为Spring AI内部会把字段注释拼成JSON Schema喂给模型。字段语义不清晰,模型猜来猜去,返回结果就五花八门。
实操心得:结构化输出最怕模型返回的JSON里有非法字符。我在生产环境里加了兜底逻辑——解析失败时尝试剥离代码块标记和多余换行,再解析一次。这个兜底在实测中救了不少请求。
3.4 向量化与知识库:解决ES重复写入问题
企业级Agent基本离不开知识库。Spring AI里做个RAG(检索增强生成)流程并不难,但很多人会踩一个坑:把文档向量化写入Elasticsearch时,每次执行都会追加一份新文档,跑两次脚本,知识库里就多出两份一模一样的内容,检索结果全是重复的。
问题的根源是ES写入时没有遵循“一个文档一个ID”的原则。文档向量化之前,需要给每个文档生成一个稳定的ID,并设置写入时覆盖。Spring AI的向量存储抽象里,VectorStore的doAdd方法本质上是按文档ID写入的,如果你不显式指定ID,就相当于每次新增。
我使用GptDocument或Document时,在构造阶段就指定ID:
java复制Document doc = new Document();
doc.setId(sha256Hex(originalFileUri + "-" + chunkIndex));
doc.setText(chunkText);
doc.getMetadata().put("source", originalFileUri);
这样反复写入同一篇文档时,相同ID的chunk会被覆盖更新,就解决了“每次写入都追增”的问题。如果业务上遇到“文档被删除了,但向量数据还在”的情况,优先做“先按source删除,再写入新增”的两步操作:
java复制// 先按来源删除旧数据
vectorStore.delete(
SearchRequest.builder()
.query("DELETE FROM source WHERE source = $source")
.build()
);
// 再写入新向量
vectorStore.add(listOfChunks);
3.5 模型层的连接池与超时机制
企业级场景下,Agent应用和模型API之间也是典型的网络IO,默认的短连接、无超时配置,在高并发下很容易拖垮整个服务。我在Spring AI里会让每个模型客户端通过RestClient.Builder自定义连接池和超时时间:
yaml复制spring:
ai:
alibaba:
tongyi:
base-url: https://dashscope.aliyuncs.com/api/v1
api-key: ${DASHSCOPE_API_KEY}
connect-timeout: 5000
read-timeout: 60000
注意读超时的时间要留足。大模型生成是流式的,一次完整回答可能持续几十秒,如果你设置了30秒读超时,Agent稍微生成长一点的内容就会被中断,体验极其糟糕。我实际运营中把读超时调到60秒甚至90秒,同时配合流式接口,让用户先看到思考过程。
4. 常见问题与排查实录
4.1 Figma MCP在Codex中总是工具注册不上
最近在做设计稿转前端代码的Agent流程,我用到了Figma MCP Server。在Codex里配置后,工具列表里始终看不到Figma相关工具。排查了大半天,最后定位到三个原因,这里把排查方法完整写出来。
第一步,确认MCP Server进程是否真的起来了。如果用的是stdio方式,检查配置里的命令和参数路径,比如Figma_MCP的--figma-api-key参数是否传递正确。第二步,检查网络连通性。Figma MCP需要访问Figma的API域名,如果本地代理被Codex进程继承,可能导致CLI进程请求失败但不报错。第三步,重点检查工具名冲突。如果你同时注册了其他MCP Server,尤其是有“figma”相关前缀的重名工具,Codex会自动去重,导致后注册的工具被丢弃。
排查时在终端执行:
bash复制# 检查MCP Server进程状态
ps aux | grep figma
# 手动调用MCP Server测试连通性
npx @michaelschade/figma-mcp-server --help
一旦确认Server端正常,重点就回到你的Agent配置侧。Codex里检查MCP工具列表可以用:
bash复制codex mcp list
如果这里没有Figma工具,说明是注册时出了问题。我自己最后的问题是两个MCP Server的启动参数写在同一行,Shell解析时把第二个参数的Key吃掉了。所以提醒各位,MCP Server的配置一定要每项单独一行,别图省事。
4.2 Spring AI连接中断与重连机制
Spring AI应用长时间运行后,尤其是MCP Client通过SSE连接远程Server时,会遇到连接中断的问题。SSE本质上是一条长连接HTTP流,服务端断开后客户端不会自动恢复。我在项目里连续跑了三天,第四个早上就开始出现“MCP工具调用一直转圈”的现象,最后日志里全是连接超时。
解决办法是给MCP Client配置重连策略。Spring AI的ReactorSseClient底层用的是WebClient,你可以自定义WebClient.Builder的底层连接池和重试参数。更稳的方案是在MCP Client与Server之间加一层负载均衡和健康检查,发现连接断了就主动重建SSE连接。
配置一个简单但有效的连接池:
yaml复制spring:
mcp:
client:
sse:
connect-timeout: 5000
read-timeout: 0
read-timeout设置为0表示不限制读超时,因为SSE是长连接,服务端可能半小时才推一条心跳。若你设置限制,反而会把正常连接误杀。
重要经验:在企业级环境里,MCP Server一端要提供健康检查接口,Agent端每隔30秒探测一次,发现服务不健康就主动重连。别等请求发起时才发现连接断了,那样会导致用户的第一个请求等10秒以上。
4.3 “The agent execution provider did not respond in time”排查手记
这条错误信息我见过太多次了,尤其是在模型负载高峰期。它直译过来就是“Agent执行提供方没有及时响应”。本质是Agent运行时在大模型API调用上超时了。
排查顺序如下:
第一,看是不是模型服务端负载高。大批量请求同时进来时,API的处理时间会显著延长,容易触发超时阈值。第二,看是不是Agent自身上下文太长。你塞给模型的内容如果超过窗口限制,服务端等待时间也会变长。第三,看自己设置的超时时间是否合理。
我的处理方式是分三层:第一层是把Agent执行的超时时间尽量调到90秒以上;第二层是在提示词里要求模型精简思考过程,减少中间输出;第三层是把不必要的历史消息做压缩,截断过期的对话内容。实测,第三层效果最明显——上下文体积降下来后,“did not respond in time”的出现频率直线下降。
4.4 向量化文档重复写入与覆盖策略
前面在3.4节已经讲了ES重复写入的根治方法,这里再补充一个实战中的细节。如果你发现向量库里脏数据已经存在,而且没有按ID做覆盖,最直接的处理是写一个清理脚本,按metadata.source条件删除旧数据。Spring AI的VectorStore.delete支持按查询条件删除:
java复制vectorStore.delete(
SearchRequest.builder()
.query("source = 'doc-2024-10-01.pdf'")
.build()
);
删除完成后再重新执行向量化脚本。这样虽然多了一步手动操作,但能保证知识库干净。更规范的做法是把“向量化-写入-覆盖”封装成一个幂等的同步任务,每次执行前自动按source清理,避免业务人员重复上传文档时产生脏数据。
4.5 MCP Server工具数量膨胀后,模型选不准工具怎么办
企业里MCP Server越接越多,工具总数可能很快超过100个。这时候模型面对100多个工具,每次推理都要把工具描述塞进上下文,不仅消耗token,还会降低工具选择的准确率。我自己实测过,当工具数量超过80个时,模型错误调用工具的概率明显上升。
我的解法是给MCP Server做分组。在Spring AI Alibaba里,可以通过调节MCP Client的配置,让不同的Agent只挂载各自业务域需要的MCP Server。比如“售后Agent”只连接工单系统、物流系统两个MCP Server,总共10个工具;“风控Agent”只连接黑名单、订单查询、支付系统三个MCP Server。这样每个Agent的工具数量都保持在可控范围内,准确率和响应速度都会有明显提升。
另外,也可以用Agent层的路由逻辑做“二级工具选择”:先根据用户意图判断调用哪个MCP Server组,再让模型在该组的工具里做具体选择。相当于给模型减少了干扰项,这是目前能兼顾灵活性和性能的最优方案。
5. 另一个值得关注的方向:企业级UI/数据可视化如何协同Agent落地
严格来说,这一节跟MCP协议栈没有直接关系,但在真实企业级Agent项目中完全绕不开。很多团队把Agent后端搭得漂漂亮亮,结果前端界面丑得没法看,用户根本不愿意用。我在做企业内部AI助手时,前端基于一套“政府/企业级设计规范”的UI Skill来生成,后台用传统Java技术栈输出数据,前端通过API直接对接;用户看到的不再是聊天框口,而是带统计图表、工单卡片、风险仪表板的智能工作台。
如果团队正在做企业级Agent,我建议不要把UI这事全部丢给CSS手写。把设计规范沉淀成可复用组件和Skill,结合Agent自动生成界面,这比“大模型输出JSON,前端再渲染JSON”的割裂方案好用得多。平时做Agent开发时,也注意让Agent输出的内容尽可能结构化,前端才好做数据绑定和可视化,这块投入的ROI很高。
6. 关于Spring AI Alibaba生态的一点观察
最后聊聊生态。Spring AI Alibaba 1.x系列的设计思路很明显:把AI能力做成Spring生态里内建的一部分。跟单独接入某个大模型SDK不同,它提供了从模型接入、MCP Server注册、工具调用、知识库向量化到Agent编排的完整链路。
我实际用下来,最舒服的是组件之间的衔接成本低——用Spring Boot Actuator可以监控ChatClient的调用指标;用Spring Cloud Config可以动态切换模型配置;用Alibaba的组件可以对接团队已有的微服务体系。这些优势在技术选型时评估不到,但上线运维时全是实打实的好处。
如果团队正准备启动企业级Agent项目,我的建议很简单:不要被各种新概念绕晕,先把MCP协议吃透,把Spring AI Alibaba的Starter跑通,再做一两个业务域的MCP Server,形成“Agent调用工具-工具反哺Agent”的闭环。先把地基夯实,后面扩展能力就是水到渠成的事了。
