
1. 为什么MCP在Spring AI里突然成了主角1.1 从一次“工具失控”开始的标准化先说个真实场景。我上个月接手一个餐饮SaaS的AI助手改造业务方提的需求很简单用户问“今晚订4人桌7点到靠窗”智能体要直接调订座接口、查实时桌态、如果没位置还要推荐附近分店。听起来是不是就是一次常规的函数调用真正动手才发现问题不在“调用”而在“怎么把几十个业务能力稳定地暴露给模型”。团队之前用Spring AI的Tool注解把订座、查桌、优惠券核销、会员积分这些方法全部塞进一个Bean里结果模型偶尔会调用错参数更麻烦的是每次新增一个分店系统都要改代码重新发布。最痛的是一个第三方系统只给了一个HTTP接口字段命名风格和我们完全不同为了让模型理解我被迫写了一大坨描述注解根本维护不动。这个痛点其实业内早就有了大家管它叫工具调用碎片化。每个框架都用自己的方式把函数变成模型能看懂的JSON Schema换个集成方案就要重写一遍。后来社区开始推MCP也就是Model Context Protocol我最初以为这又是另一个“Java新框架”直到在Spring AI里跑通第一个MCP调用才反应过来它解决的根本不是“注解怎么写更好看”而是把“模型如何发现工具、如何调用工具、工具结果如何回传”这整条链路定义成了标准协议。1.2 MCP的四个核心抽象我建议初学者不要一上来就钻进源码先把MCP想象成“给AI用的USB-C接口”。USB-C把充电、传输、视频输出统一成一个口MCP就是把数据库查询、外部API、本地文件、浏览器操作这些能力统一成一套协议。这样模型不需要知道你是Java写的还是Python写的也不需要知道你的服务部署在哪只要大家按照协议讲话就能互通。MCP体系里最核心的就是四个角色MCP Host运行模型的地方在Spring AI里就是ChatClient或ChatModel它负责发起请求、持有上下文。MCP ClientHost内部用于连接Server的客户端Spring AI的McpClient就是这个角色它把工具描述拉取回来转成模型能识别的工具定义。MCP Server真正提供能力的进程可以暴露数据库查询、外部API、自定义函数每个能力被称为一个Tool。TransportClient和Server之间的传输通道Spring AI支持Stdio标准输入输出、SSEHTTP流式、WebFlux WebSocket等。我自己刚接触时最大的误解是以为MCP一定有个独立的Server进程。其实完全可以在你的Spring Boot应用内嵌一个McpServer把几十个业务方法包装成工具直接暴露这对单体应用特别友好不用拆服务就能享受到统一协议带来的规范性和可扩展性。1.3 Spring AI对MCP的支持演进Spring AI早期版本里MCP模块还是spring-ai-mcp-client这种独立starter配置分散文档也不全社区里不少人都是照着GitHub上的示例抄。到了1.0.0 GA之后MCP被正式收编成核心能力Spring AI 2.0里更是把MCP作为智能体工具调用的一等公民。我实测下来Spring AI的MCP支持是在“不破坏原有Tool体验”的前提下做的抽象。也就是说你以前用Tool暴露的方法完全可以不改业务逻辑直接通过MCP Server的方式重新暴露你以前用ChatClient写好的对话逻辑也不需要大幅改动只要把MCP客户端装配进去工具列表就会自动出现在模型请求里。还有个对我特别有用的点是Spring AI对MCP协议版本做了平滑适配。MCP协议有2024-11-05、2025-03-26、2025-06-18这些版本不同版本的工具调用参数格式有差异Spring AI在McpSchema里做了版本兼容我后面在源码部分会详细说。你只需要知道一点如果你的MCP Server很新而Spring AI版本比较旧优先先升级Spring AI不要手动去改协议报文。1.4 为什么不是普通Function Calling就够了有朋友问我Spring Boot里我已经有函数调用了为什么还要MCP这个问题很关键我的回答取决于你是单应用还是多系统。如果是单应用、工具固定、团队小那普通Tool完全够用不要为了技术时髦引入额外复杂度。但只要你面对下面任何一种情况MCP的收益就非常明显场景普通Function CallingMCP工具数量超过30个所有Definition随请求发出去token开销大可按需加载、分组管理跨语言/跨团队需要各自实现一套描述规范统一协议Java/Python/Node都遵循第三方接入每个厂商都要写适配只要对方是MCP Server直接连动态工具变化改代码重新发布Server端动态增删工具Client侧即时感知我在SaaS改造里出现了一个特别典型的场景客户那边有一套自建的库存系统是Node.js团队维护的他们不想把内部接口直接暴露给我们Java服务。后来他们起了个MCP Server我们这边只加了两个配置类就把库存查询能力接进了AI助手。整个过程双方只对齐了MCP协议完全没讨论字段定义、鉴权方式、调用链。这就是协议的价值。2. 十分钟跑通 Spring AI MCP 的第一个 Agent2.1 环境准备与版本锁定先说版本这里是很多人的第一个坑。Spring AI的版本号和Spring Boot的版本不是完全对应的我目前推荐用Spring Boot 3.5.x搭配Spring AI 1.0.0 GA这套组合稳定MCP模块也比较成熟。JDK至少17建议直接上21因为Spring AI的虚拟线程支持在21上效果更好。在你的pom.xml里引入三个依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency注意spring-ai-starter-mcp-client这个starter很多教程写的是spring-ai-mcp-client但新版本统一用starter前缀而且如果你打算在应用里内嵌一个工具服务还要额外引入spring-ai-starter-mcp-server。我刚开始就因为少加了server依赖导致本地起了Server却连不上。另外还需要在application.yml里配置模型API。我用的是OpenAI兼容接口配置如下spring: ai: openai: base-url: https://your-api-endpoint api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7注意api-key一定不要写死在yml里用环境变量注入。我见过太多把key直接提交到Git仓库的事故一张截图传到群里整个账号就废了。2.2 写一个最简单的 MCP Server我不建议一开始就引入mcp-server官方SDK去写stdin通信那是给独立进程用的在Spring Boot单体里比较折腾。最快的方式是直接用Spring AI的Tool注解再通过McpServer自动配置暴露出去。建一个工具类import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; Component public class RestaurantTools { Tool(description 查询指定门店在指定日期、时段的可订桌位数量) public int queryAvailableTables(String storeId, String date, String timePeriod) { // 这里一般是查数据库或调用下游系统 return 8; } Tool(description 根据订座人数和位置偏好推荐最近的门店) public String recommendNearbyStore(int peopleCount, String preference) { return 滨江店距离你1.2公里有靠窗4人桌; } }关键点在于Tool的description字段这个描述是给模型看的不是给人看的。描述写得越具体模型调用准确率越高。我在源码分析部分会展示它如何转换成JSON Schema你就能理解为什么“查询桌位”这种模糊描述会导致模型频繁猜错参数。启动类上加一行配置扫描SpringBootApplication public class AiApplication { public static void main(String[] args) { SpringApplication.run(AiApplication.class, args); } }默认情况下Spring AI会把所有带Tool的Bean自动注册到内嵌的MCP Server上。如果你的应用同时需要对外提供MCP能力还需要在配置里开一下spring: ai: mcp: server: enabled: true name: restaurant-mcp version: 1.0.02.3 Client侧装配与对话调用现在我们要让ChatClient能够看到并调用上面这些工具。Spring AI的McpToolUtils会从已连接的MCP Server拉取工具定义然后合并到模型请求的工具列表中。创建一个配置类import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.mcp.client.McpClient; import org.springframework.ai.model.tool.ToolCallback; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; Configuration public class McpChatConfig { Bean public ChatClient chatClient(ChatClient.Builder builder, ListMcpClient mcpClients) { ToolCallback[] toolCallbacks mcpClients.stream() .flatMap(client - client.getToolCallbacks().stream()) .toArray(ToolCallback[]::new); return builder.defaultTools(toolCallbacks).build(); } }如果只是单Server上面的代码没问题。但是当你有多个MCP Server时直接把所有工具合并在一组里很容易出现同名工具冲突。这个我在第三章会详细讲先用最简单的方式跑通。然后写一个Controllerimport org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient chatClient; } PostMapping(value /chat, produces text/event-stream) public FluxString chat(RequestBody String prompt) { return chatClient.prompt(prompt) .stream() .content(); } }启动应用后用curl测试curl -N -X POST http://localhost:8080/chat \ -H Content-Type: text/plain \ -d 我们4个人今晚7点想在滨江店订一桌靠窗的能订吗你会在日志里看到完整的工具调用链路模型先调用了recommendNearbyStore再调用queryAvailableTables最后结合结果生成回答。2.4 验证与第一轮对话这里我强烈建议你在IDE里开启org.springframework.ai.tool包的Debug日志能清楚看到工具被模型选中时的参数JSONlogging: level: org.springframework.ai.tool: DEBUG org.springframework.ai.mcp: DEBUG我第一次跑通时日志里出现了有趣的细节模型把“我们4个人”解析成了peopleCount4把“今晚7点”转成了timePeriodEVENING然后先把门店推荐给调了出来。这比我预想的多了一次工具调用因为模型优先确定了门店ID才去查桌量。如果模型没有调用工具优先检查几点defaultTools有没有正确传入可以在Debug日志里看工具数量。模型本身是否支持工具调用有些开源模型微调版本不支持function calling。Tool描述里有没有足够的信息让模型判断什么时候该用这个工具描述太笼统模型也会犹豫。3. 实战要提前想清楚的几个设计点3.1 多MCP Server的命名冲突与namespace跑通第一个Demo只需要几分钟但真正上生产你要面对的第一个问题就是多Server。比如餐饮SaaS里订座是一个Server会员积分是一个Server营销券又是一个Server各自由不同团队维护。直接合并工具的后果我已经踩过了两个Server里都有一个叫queryBalance的工具一个是查会员余额一个是查优惠券余额模型随机选一个调用轻则答非所问重则把积分当成现金告诉用户体验直接崩掉。Spring AI新增了命名空间机制来规避这个问题。我的做法是给每个Server设置一个namespace前缀工具的实际名称会带上这个前缀McpServer serve McpServer.builder(transport) .name(member-server) .namespace(member) // 关键配置 .tools(toolCallbacks) .build();这样工具名变成了member_queryBalance和marketing_queryBalance模型能根据名称和描述区分。实测下来冲突概率大幅降低。3.2 凭证与Token的安全落位MCP Server有两种常见部署本地Stdio进程和远程HTTP/WebSocket端点。对于远程端点很多社区Server会要求带token鉴权比如我见过有人把wss://协议后面直接拼一长串JWT然后把它写在配置里提交到仓库这绝对是安全事故。正确的做法是把凭证放在环境变量或者配置中心里代码里永远只引用占位符spring: ai: mcp: client: connections: - id: external-inventory type: sse url: ${INVENTORY_MCP_URL} headers: Authorization: Bearer ${INVENTORY_MCP_TOKEN}如果你用的是WebMVC而非WebFlux要特别注意SSE类型的连接在超时处理上的差异。我在生产里遇到过一个问题远程Server偶尔会断开连接如果用的是WebFluxSseClient连接断开后不会自动重连需要自己写一个定时任务去检查client.getServerFeatures()是否能正常返回拿不到就重建连接。3.3 工具裁剪与按需加载工具不是越多越好。MCP Server上挂了50个工具每次模型请求都会初始化这50个工具定义token消耗是把所有工具的JSON Schema拼在一起发出去的。我在压测时发现工具描述从10个涨到50个单次请求的输入token多了将近3000成本增加不少而且模型在太多工具里做选择时调用准确率会下降。所以生产环境我强烈建议做工具裁剪。在Server端控制暴露范围Configuration public class McpServerConfig { Bean public ToolCallbackProvider restaurantToolCallbackProvider(RestaurantService service) { // 只暴露订座相关不暴露营销相关 return MethodToolCallbackProvider.builder() .toolObjects(service) .build(); } }如果Server不是你能控制的只能在Client端过滤。Spring AI提供了ToolCallback的包装方式可以写一个Filter在工具定义返回前过滤掉你不希望模型看到的工具。3.4 超时、重试与降级策略MCP调用如果刚引入时不给它加防护线上会很难看。原因在于模型调用工具是同步等待结果回来的工具执行慢模型那次请求就跟着慢用户那边看到的就是AI在“思考”很久。我给工具调用加了三个策略超时控制。在MCP Client的McpClientOptions里设置请求超时McpClientOptions options McpClientOptions.builder() .requestTimeout(Duration.ofSeconds(30)) .build();重试降级。建议在工具方法内部自己做降级不要指望框架帮你做。比如查库存时下游超时就返回一个兜底值“当前库存未知建议以门店为准”而不是直接抛异常否则模型会把异常当结果答得非常生硬。熔断隔离。如果某个MCP Server连着失败可以用Resilience4j把对它的调用隔离起来失败次数达到阈值后直接短路防止一个慢Server拖垮整个AI入口。3.5 各种典型场景的集成思路我在搜集资料时看到不少人在不同领域尝试MCP思路值得参考。有人给游戏引擎接MCP让AI助手能读取场景对象、控制NPC行为有人给EDA设计工具接MCP把芯片布局检查工具的能力暴露给大模型做辅助分析还有人把同花顺这一类金融数据终端接进Agent让模型直接查行情、做技术指标计算。这些场景的共同点都是原本工具的能力是静默的、仅限人工操作的通过MCP把能力协议化之后AI就能自主编排。Spring AI Alibaba最近也在做类似的事情它在Spring AI的标准MCP之外把阿里云上的服务能力也包装成了MCP工具思路是同一套。4. 源码视角Spring AI 的 MCP 抽象与扩展点4.1 关键类与装配流程很多人问我Spring AI是怎么做到把Tool方法变成MCP工具还给模型调用的我花了一个周末把源码翻了一遍理出了核心链路。整个MCP支持的根在McpAutoConfiguration它会根据classpath里的依赖自动装配客户端或服务端。关键类有这么几个类名作用McpClient协议客户端负责连接、同步工具列表、发起工具调用McpServer协议服务端管理工具注册、处理调用请求McpToolSpecification工具描述实现了Spring AI抽象的ToolSpecification接口McpToolUtils静态工具类负责从Client里拉取工具并转成ToolCallbackMcpSchema协议数据模型各种请求响应POJO装配流程大概是启动时Spring Boot完成自动配置把配置的MCP Server或Client装配成Bean然后DefaultToolCallbacks把这些Bean的getToolCallbacks()收集起来合并成模型请求的tool_choice列表。4.2 工具描述如何变成模型可用的JSON Schema这是我最想讲清楚的地方。你写的这个Tool注解Tool(description 查询指定门店在指定日期、时段的可订桌位数量) public int queryAvailableTables(String storeId, String date, String timePeriod)Spring AI会通过MethodToolCallbackProvider解析方法签名生成一个ToolSpecification内部包含名称、描述和参数Schema。参数Schema是从Method的Parameter信息的类型推导出来的String类型变成{type:string}int变成{type:integer}如果参数上有ToolParam注解还能补充更多约束。然后McpToolSpecification会把这个标准工具定义转换成MCP协议里的CallToolRequest结构。也就是说Spring AI做了一次双向翻译对外是MCP协议格式对内是Spring AI自己的工具抽象。我在看源码时特别注意到一个细节McpToolUtils在把工具从Server拉到Client时会对工具名做处理。如果Server返回的工具名里含有冒号等特殊字符它会自动转义避免模型生成非法函数名。4.3 一次工具调用的完整链路为了帮你建立直观认识我画一条文字版的调用链用户提问 - ChatClient.prompt(content) - ChatModel把系统提示 工具定义列表发给大模型 - 模型返回 tool_call包含工具名和参数JSON - Spring AI的ToolCallingManager解析 tool_call - 找到匹配的 ToolCallback对应MCP Server里的某个Tool - McpClient发出 CallToolRequest 到 McpServer - McpServer定位到具体方法反射调用 - 返回结果回传模型 - 模型根据结果组织最终回答注意第二步里的工具定义列表就是我们前面讲的多个Server合并后的结果。所以工具数量越多发给模型的token越大这也是我前面强调裁剪的原因。4.4 自定义扩展点如果你想在Spring AI的MCP上面做二次开发有三个扩展点是值得重点关注的自定义Transport。Spring AI默认支持Stdio、SSE、WebFlux WebSocket如果你要接自定义协议实现McpTransport接口就行。自定义ToolCallback过滤器。可以在工具被模型看到之前做最后的修改或过滤。实现ToolCallback接口包装原来的callback在getToolSpecification()里拦截。自定义McpServer的工具发现策略。如果你不想用Tool注解可以自己实现ToolCallbackProvider从XML配置、数据库、或者远程配置中心动态注册工具。我在项目里就是这么做的业务运营同学在后台配一个新的工具描述不用发版Server侧动态感知到模型下一次请求就能用了。4.5 协议版本兼容的细节源码里有大量McpSchema内部类每个类都对应一个协议版本。Spring AI的兼容策略是同时支持多个版本在初始化时根据features协商选择。具体到代码体现在McpClient的协议初始化握手过程中它会请求Server的initialize拿到Server支持的协议版本和功能列表然后降级或者升级到双方都能理解的版本。我实际遇到过用新版本MCP Server对接旧版Spring AI工具调用正常但Server返回的isError标志位解析不出来导致错误结果被当成正常结果。这种问题千万别去改报文直接升Spring AI版本就好。5. 稳定性与性能调优5.1 连接生命周期与预热MCP的Stdio连接启动成本不高但远程连接尤其是需要TLS握手的首次调用会明显偏慢。我在生产里做了两件事一是启动预热。应用启动时主动调一次client.initialize()把连接和协议协商提前做完避免用户第一轮请求承担握手开销。二是连接复用。远程SSE连接本身是长连接但服务端可能会空闲断开。我写了一个轻量的心跳任务每60秒调一次tools/list既保活又顺便发现新增工具。5.2 并发与限流Spring AI 1.0.0对MCP的工具调用默认是同步阻塞的如果你在高并发场景下使用建议做两层控制应用层用Semaphore限制同时进行的MCP调用数量防止模型并发请求打出多个工具调用时把下游打垮。接入层对MCP Server端也要做限流不然AI入口成为流量的放大器一个用户的对话可能产生5到8次下游调用日常流量直接翻好几倍。5.3 日志与链路追踪MCP的调用链路比普通HTTP接口长HTTP进来到ChatClient再到模型API再到MCP Server再反射调业务方法。没有链路追踪出问题根本定位不了。我的做法是在过滤器里生成一个traceId放进MDC然后在MCP工具调用的时候把这个traceId传递到下游。Spring AI 2.0里对这块做了增强支持在工具调用上下文里传递元数据。如果你还在用1.0只能自己包装ToolCallback来实现。另外一定要记录模型拿到工具结果后的原始回答我踩过一个坑工具本身返回了“门店已打烊”模型转述时却变成了“门店营业至22点”如果不留存原始工具响应只看最终对话根本发现不了是模型幻觉还是工具数据错误。5.4 实测数据与调优方向我拿餐饮场景做了一个简单压测供你参考配了一个内嵌MCP Server暴露20个工具使用gpt-4o-mini单轮对话平均触发2.3次工具调用。优化项优化前优化后平均端到端时延8.2s3.6s单请求输入token89123905准确率人工评估50轮81%93%主要收益来自三块工具裁剪减少token、模型参数降低输出长度限制、以及将不需要模型的固定判断改成服务端规则比如“门店打烊”这个判断根本不需要模型直接在工具里返回。6. Spring AI MCP 与 LangGraph4j 的选型参考6.1 两者定位差异社区里最近经常有人问现在到底用Spring AI还是LangGraph4j这类问题我以前会回答“看需求”但你真做过几个项目就会发现它们的定位差异其实非常明显。Spring AI更像是一整套AI应用开发框架它的核心是模型接入、提示词管理和工具调用编排。MCP只是它的一个模块你主要是跟着Spring Boot的节奏走配置简单写起来顺手调试也直接。LangGraph4j则是把Graph计算范式搬到了Java里核心是节点、边、状态机强调复杂工作流的编排、循环、条件分支、人工审批节点。如果你要实现多步骤Agent比如先分析需求再决定调哪些工具一步步往下走LangGraph4j表达力更强。6.2 两者对比对比项Spring AI MCPLangGraph4j定位模型接入 工具调用标准协议Agent工作流编排框架MCP支持原生支持客户端/服务端都有通过外部适配器支持开发范式声明式、注解、配置驱动代码式、Graph状态驱动适用场景多数业务工具调用、快速上线复杂多步流程、状态回滚、人工审批学习成本较低会Spring Boot就能上手高需要理解Graph抽象调试体验与Spring生态日志、监控衔接自然依赖框架自身的可视化工具6.3 我的选型判断我现在的判断标准很简单如果业务逻辑是“用户问一句模型决定调1到3个工具回答”用Spring AI MCP就够了这种需求占了实际业务的大头。只有当你需要“AI自主执行一个多步骤任务过程中需要暂停、等待用户确认、然后继续”或者需要复杂的条件路由时才值得引入LangGraph4j级别的编排。我曾经为了“炫技”在一个简单问答里硬上Graph编排最后发现维护状态机和节点比写业务代码还累。还有一个现实考量团队里其他同事全都熟悉Spring Boot引入LangGraph4j等于引入一套新的开发心智光Code Review和培训就要花掉不少时间。在业务价值还没验证的情况下先用Spring AI MCP把效果做出来等确实需要复杂流程再引入图编排是更稳的节奏。我这个月在餐饮SaaS上把核心的订座、等位、优惠券三个MCP Server都接了进去现在AI助手能做的事情比之前多了不少而让我最意外的其实不是技术本身而是接入新业务系统的成本降到了几乎为零。任何一个系统的接口只要包一层MCP ServerAI立刻就能用。这种“插件即插即用的感觉才是MCP真正打动我的地方。以后团队里谁再说什么“我们的系统不好接AI”我只能回一句那就给它配一个MCP Server吧。