MCP协议实战:Spring AI里一行代码接入几百个外部工具,到底怎么玩
可能你最近也注意到了,MCP(Model Context Protocol)这个词在AI开发圈子里出现的频率越来越高。我身边的Java开发者朋友,特别是用了Spring AI的这批人,几乎都在聊同一个话题:怎么让大模型调用外部的API、数据库、文件系统,而不是只会聊天。这背后核心的技术方案就是MCP协议。说实话,我第一次看到“一行代码接入几百个外部工具”这种说法的时候也挺好奇,但真正在Spring AI里跑通之后,我发现它确实不只是噱头——配置得当的情况下,接入工具的代码确实能压缩到很短,但前提是你得理解它背后的机制,知道配置在哪、流程怎么走、哪些地方容易踩坑。这篇文章我就用实际跑过的项目,把Spring AI和MCP协议的实战过程拆开讲清楚,适合正在用Java做AI应用的开发者参考。
先说清楚这篇文章要解决什么问题。如果你已经用Spring AI接入了大模型,但每次想让模型调用一个外部接口,都要自己写工具类、写描述、注册回调,那你应该试试MCP。它能把你自定义的工具、第三方服务、数据库查询、文件操作等等,全部统一成一种标准协议暴露出来。模型侧只需要通过一个客户端,就能动态发现、调用这些工具,数量再多也不怕。文章会覆盖MCP的基础概念、Spring AI对它的支持方式、完整的配置和代码示例、核心调用流程,以及我实际踩过的坑。
1. 为什么是MCP:从“每个工具写适配器”到“一套协议走天下”
1.1 没有MCP之前,工具接入是什么体验
在MCP普及之前,我自己写过不少让大模型调用外部工具的代码。最典型的做法是:先定义一个Java方法,方法上写清楚功能描述和参数说明,然后封装成一个ToolCallback注册给模型。听起来不复杂,但问题在于——每接入一个工具,你都得重复整个流程。
想象一下,你的项目里需要接入天气查询、数据库查询、邮件发送、Excel导出这四个工具。用传统方式,你得写四份方法定义,写四份参数描述,甚至可能针对不同的大模型平台调整描述格式。如果工具数量上到几十个,光维护这些描述和注册代码就够喝一壶的了。更麻烦的是,如果工具所在的系统是别人维护的,你根本没法直接调用,只能自己再写一个HTTP客户端去请求人家的接口,来回折腾。这种模式本质上是在为每一个工具写一个“适配器”,虽然能跑,但扩展性很差。
1.2 MCP把“工具”变成了标准化服务
MCP的出发点很简单——把工具的定义和调用方式统一成一个标准协议,服务端负责暴露工具,客户端负责发现和调用,两边都按照同一个规则来通信。这样就不再需要为每个工具单独写适配逻辑了。
我用一个生活化的例子来解释。没有MCP的时候,你想让AI使用不同的外部能力,相当于你家每一台电器都要配一个专属遥控器。有了MCP之后,就变成了一个通用遥控器,所有电器都遵循同一个红外协议,按一下就能控制。具体到技术上,MCP服务端(Server)把工具列表、参数结构、调用入口全部按标准格式暴露出来,MCP客户端(Client)负责连接、发现、调用,AI模型这边只需要通过Spring AI的统一接口操作,完全不用关心工具底层到底是REST接口、gRPC服务还是本地代码。
1.3 标题里的“一行代码”是怎么实现的
说回标题里的“一行代码接入几百个外部工具”。这句话严格来说有一定宣传成分,但核心逻辑是真的。在Spring AI的封装下,你确实可以把“连接MCP服务器并注册为工具”这个动作压缩到几行配置或一行Bean方法上。
具体来说,Spring AI的@Tool注解加上MCP客户端自动装配机制,让你只需声明一个MCP客户端Bean,它就能自动连接远程MCP服务器,自动获取服务器上的所有工具列表,然后自动注册到AI模型的工具调用机制里。这一步如果缩写成代码,确实就一行Bean声明。但前提是MCP服务器那边已经按照标准把工具暴露好了,而且你的依赖和配置都已经就位。后面的正文我会把这个过程完整演示一遍。
2. Spring AI MCP支持方式与配置细节
2.1 两种接入角色:客户端还是服务器
在Spring AI里接入MCP,首先要分清你要扮演什么角色。通常业务项目扮演的是MCP客户端,也就是“消费工具”的一方。你通过MCP客户端连接远程的MCP服务器,把服务器提供的工具当成AI模型的可调用工具来使用。
但如果你负责搭建一个“能力提供方”,比如你们公司内部有一个统一的数据查询平台,想把它暴露给其他AI应用调用,那你需要开发一个MCP服务器。Spring AI同样提供了一套MCP Server的自动配置,你可以把任意Spring Bean方法快速暴露成一个MCP工具。对于大部分应用开发者来说,主要场景是前者——接别人已经搭好的MCP服务器。
我建议开发思路是这样的:先明确自己是要“接入工具”还是“提供工具”。如果只是接入,那重点看客户端的配置方式;如果是提供,那重点看服务端的暴露方式。两者虽然代码结构不同,但Spring AI都做了很多自动化的部分,实际工作量比想象中小很多。
2.2 依赖引入与版本选择
Spring AI的MCP支持经历了好几个版本的迭代,依赖坐标变化也比较大。我在实践中使用的是Spring Boot 3.4.x配合Spring AI 1.0.0 M6版本的组合,这个阶段MCP相关的starter已经相对稳定。
核心依赖主要有这几类:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-client-sse</artifactId> </dependency>第一依赖用来接入OpenAI协议的模型接口,第二依赖是MCP客户端自动装配的核心,第三依赖支持通过SSE(Server-Sent Events)方式连接MCP服务器。如果你的MCP服务器用的是流式HTTP传输,可能还需要引入spring-ai-mcp-client-stream-http。版本选择和Spring Boot的兼容性很关键,建议优先查看Spring AI官方Release Notes再决定版本组合,避免出现底层协议不兼容的问题。
2.3 配置文件里的核心参数
依赖引入之后,需要在application.yml配置MCP客户端参数。这一步如果不理解每个配置的含义,很容易在连接阶段就出问题。我实际使用的最简配置如下:
spring: ai: mcp: client: sse: connections: server1: url: http://localhost:8081/sse name: my-ai-client version: 1.0.0这里有几个关键点。server1是这个连接的名称,你可以随意取名,但它会出现在后续的日志和工具命名前缀里,最好起得有意义一些。url指向MCP服务器的SSE端点,很多服务器的SSE路径就是/sse,但具体要看服务端实现。name和version是客户端标识信息,会在握手时传给服务器,一般不用太纠结。
如果是使用流式HTTP传输的方式,配置会略有不同:
spring: ai: mcp: client: stream-http: connections: server1: url: http://localhost:8081/mcp从SSE换成流式HTTP,最大的区别在于连接管理方式。SSE本质上是单向长连接,服务器通过事件流推数据;流式HTTP则是双向流式请求,更灵活,但要求服务器和客户端都支持这个较新的传输标准。我在本地测试时,大部分官方示例用的还是SSE,所以新手先以SSE为主比较稳妥。
3. 完整实操:从零到一行代码接入上百个工具
3.1 准备一个可用的MCP服务器
要演示客户端接入,总得先有一个MCP服务器。最快的方式是直接用Spring AI官方仓库里的示例服务器项目,本地启动一个,然后再写客户端去连它。
我在实际演示中使用的是一个模拟的MCP Server项目,它暴露了几十个简单工具,有的用于获取当前时间,有的用于查询文本内容,有的用于执行本地计算。这些工具理论上你可以自己写,但为了快速看到效果,直接找一个现成的示例项目或者参照官方文档实现一个简单版本即可。
服务器端启动后,访问http://localhost:8081/sse,正常情况下会建立SSE连接并等待客户端握手。先手动用curl测一下地址能不能通,可以避免后面到客户端调试阶段才怀疑服务器问题。
3.2 客户端核心代码:一行Bean接入所有工具
配置好MCP服务器之后,客户端代码反而非常简单。Spring AI的自动配置类会在启动时根据配置创建对应的McpClient实例,我们只需要把它们转换成能被AI模型识别的ToolCallback,然后注册给模型。
实际操作中,可以在配置类里写这样一段:
@Configuration public class McpConfig { @Bean public ToolCallbackRepository mcpToolCallbacks( List<McpClient> mcpClients, ToolCallbackRepository repository) { return repository; } }等等,这样其实没有体现“一行代码”的感觉。更直接的方式是用Spring AI内置的自动装配承诺,只要你引入了MCP客户端依赖并配置了连接地址,Spring AI就会自动把MCP服务器上的工具转换为ToolCallback,并注入到模型调用链中。
所以如果你的项目结构正常,控制器里直接注入ChatClient,然后发起一个自然语言请求,模型就能自动感知到MCP服务器上的所有工具并调用。核心的“接入”动作就发生在这个自动装配过程中,代码量确实是零,配置到位即可。
想要更显式的写法,也有办法——手动构建ToolCallback列表:
@Bean public ChatClient chatClient(ChatClient.Builder builder, ToolCallbackRepository repository) { return builder .defaultTools(repository.getAllToolCallbacks().toArray(new ToolCallback[0])) .build(); }这一步算是把“自动注册”变成了“显式注册”,在日志中能看到全部工具数量。
3.3 模型调用与工具发现的完整流程
接入成功之后,调用流程是怎样的?我用一个天气查询的场景来说明。假设MCP服务器上有一个getWeather工具,接收city参数,返回该城市的天气信息。
当你向聊天模型发送“北京今天天气怎么样”时,完整的执行链路是这样的:
第一,模型分析你的输入,判断需要调用getWeather工具来获取信息。这个过程中,模型其实是在“工具列表”里做语义匹配,所以工具的描述信息是否清晰直接影响匹配准确率。
第二,模型生成一个工具调用请求,包含工具名称和参数值,Spring AI把这个请求转发给MCP客户端,MCP客户端按照标准协议发给MCP服务器。
第三,MCP服务器执行真正的工具逻辑,把结果返回给MCP客户端,Spring AI再把这个结果注入回模型,模型基于结果组织最终回答。
这段链路说明了一个关键点:MCP客户端本身不执行业务逻辑,它只是一个“传话人”。真实的业务逻辑还是跑在MCP服务器进程里。这种架构的好处是职责分明,坏处是多了一层网络调用,延迟会比本地工具调用略高。
需要特别注意的是,模型能不能正确调用工具,很大程度上取决于工具名称是否清晰、描述是否准确。有些MCP服务器暴露的工具命名随意,例如utils、fn1这种,模型就很难判断该不该用它。
3.4 如何验证工具确实被自动发现
完成了配置和代码,怎么确认MCP服务器上的几百个工具真的被注册进来了?我的方法是在启动日志里找线索。
Spring AI在自动装配MCP工具时会打印日志,内容大致如下:
Registered 47 tool callbacks from MCP server 'server1'看到这类日志,就能确认工具数量和来源。如果没有这条日志,说明MCP客户端连接失败了或者工具注册链路没走通。
另一种验证方式是在运行时通过调试接口查看工具列表。如果你在ChatClient配置中注入了ToolCallbackRepository,可以写一个简单的ApplicationRunner,在项目启动后打印所有工具名称:
@Component public class ToolPrinter implements ApplicationRunner { private final ToolCallbackRepository repository; public ToolPrinter(ToolCallbackRepository repository) { this.repository = repository; } @Override public void run(ApplicationArguments args) { repository.getAllToolCallbacks() .forEach(tc -> System.out.println("Tool: " + tc.getToolDefinition().name())); } }跑一下启动类,控制台就能输出被发现的工具列表。我第一次跑通时输出了一百多个工具名称,那个画面还是挺震撼的。
4. 核心机制拆解:工具声明、参数映射与协议交互
4.1 MCP服务端的工具是怎么定义的
很多人第一次接触MCP时有一个误区:觉得工具定义一定很复杂。实际上,在Spring AI的服务端项目中,定义一个工具极其简单——写一个普通方法,加上@Tool注解就行了。
下面是一个典型的MCP工具定义:
@Component public class WeatherTools { @Tool(description = "查询指定城市的实时天气") public String getWeather(String city) { // 真实场景中这里调用天气API return "城市:" + city + ",天气:晴,温度:26度"; } }这段代码暴露了一个getWeather工具。@Tool注解里的description非常关键,因为模型正是通过这段描述来决定何时调用该工具的。描述写得越具体、越贴近用户问题,模型判断就越准确。
你可能会好奇,这样一个普通Spring Bean方法,怎么就能被MCP识别并自动暴露出去?原理是Spring AI在服务端启动时扫描容器中所有标注了@Tool的方法,把它们转成标准格式的工具定义,然后通过MCP协议注册到MCP服务器里。当远程客户端调用这个工具时,MCP服务器根据方法签名完成参数反序列化,然后反射调用对应方法返回结果。
4.2 参数映射与复杂类型处理
基础的工具方法只需要简单的字符串参数,但现实场景中工具参数往往更复杂。比如查询订单列表需要传入时间范围、分页参数、排序规则等,这时候需要把参数定义成对象。
Spring AI对参数映射的支持依赖JSON Schema。当你的方法参数是复杂对象时,框架会用Jackson将参数结构序列化成JSON Schema格式,发送给模型侧,让模型能按结构填充参数值。
我实际用过的一个订单查询工具如下:
@Tool(description = "根据条件查询订单列表") public String queryOrders(OrderQuery query) { // query中包含时间范围、页码、每页数量等字段 return orderService.query(query); } public record OrderQuery( String startDate, String endDate, int page, int size ) {}这里用record作为参数类型,代码量少,Jackson也能很好的支持。有一点提醒各位注意:Java的record在反序列化时需要保证参数名与JSON字段匹配,否则会在MCP调用时出现参数错误。如果遇到这类问题,可以给record组件配置@JsonProperty注解显式指定JSON字段名。
另外还有一个细节:MCP协议中的工具名称默认是方法名,如果多个工具在方法名上有冲突,可能需要在@Tool注解里通过name属性指定唯一名称。工具名称在协议中需要全局唯一,否则会导致注册失败或调用冲突。
4.3 客户端如何感知“工具结果”
工具调用完成后,结果从MCP服务器回到AI模型的链路也值得关注。MCP协议中,服务器返回的结果是一个结构化的CallToolResult对象,包含content字段,其中可以携带文本、图片、结构化数据等多种内容类型。
Spring AI的MCP客户端拿到这个结果后,会把它转换成一个ToolExecutionResult,最终通过ChatClient的响应流程传递给模型。模型拿到结果后,结合用户的原始问题,组织出一段自然语言的回答。
有一个常见的困惑是:工具返回的结果是不是必须得是字符串。实践中,MCP工具的结果类型限制比较宽松,你可以返回JSON字符串、普通文本甚至二进制数据的引用。但如果返回的是复杂对象,建议在工具方法里先序列化成JSON字符串再返回,这样模型处理起来更稳定。我在某个项目中遇到过工具直接返回了Java对象,导致MCP序列化报错,后来统一改为返回JSON字符串就正常了。
4.4 工具调用的并发与超时机制
MCP的客户端和服务端通信基于JSON-RPC,在Spring AI的封装下,每次工具调用都会经过几个关键环节:模型生成工具调用请求、MCP客户端发送请求、服务器执行并返回结果、结果回流给模型。这个链路中任何一个环节超时,都会导致整个调用失败。
我实际测试中发现,默认的超时配置对某些耗时操作不太友好。比如一个工具需要查询大量数据,执行时间可能超过5秒,这时候必须显式调整超时时间。在Spring AI中,MCP客户端的超时可以通过WebClient的配置来调整:
@Bean public WebClient.Builder mcpWebClientBuilder() { return WebClient.builder() .baseUrl("http://localhost:8081") .build(); }更稳妥的做法是在创建MCP客户端时指定请求超时。可以参考官方文档中关于McpClient构建的配置项,调整响应超时时间。我自己在项目中设置的是30秒超时,基本覆盖了绝大多数工具执行场景。
还有一个经验:当多个工具同时被模型选中调用时,MCP客户端默认是串行执行还是并行执行,取决于Spring AI的配置。如果你追求响应速度,可以把工具调用改成并行模式,但要注意外部服务的并发压力。我一般在压测环境里测出工具处理能力后,再决定是否开启并行。
5. 实战中的坑与排查技巧
5.1 连接失败:服务器地址对但就是连不上
这是我在初学MCP时最容易遇到的问题。配置了正确的地址,但客户端一直报连接错误。排查下来发现,最常见的原因是MCP服务器的SSE端点路径不对。
不同版本的MCP服务器,SSE端点可能是/sse、/mcp/sse或者/。所以在配置客户端地址之前,一定要先在浏览器或curl里测试该地址是否正常返回事件流。一个典型的SSE连接建立后,响应头里会有Content-Type: text/event-stream,如果没有这个响应头,说明地址不对。
另一个原因是服务器启用了CORS限制或身份认证。如果客户端和服务器不在同一个域,需要确认服务器是否允许跨域请求。如果服务器配置了鉴权,客户端配置文件里还得带上对应的认证信息,否则握手会被拒绝。
5.2 工具数量确实注册了,但模型就是不调用
这个问题非常常见。MCP服务器上暴露了几十个工具,客户端注册也成功了,但用户提问时,模型宁可自己瞎猜也不调用工具。
排查思路集中在工具描述上。模型选择工具靠的是语义匹配,如果你的工具描述写得太模糊,比如“工具1”“处理数据”这种,模型根本不知道这个工具处理什么数据、在什么场景下用。我见过最典型的反面示例是把deleteUser这个工具描述为“用户操作”,结果模型在查询用户信息时也会选中它,场面一度很尴尬。
提升描述质量的建议:描述里要包含触发场景、关键参数含义和返回结果内容。例如“查询指定日期范围内的订单总数,传入startDate和endDate参数,格式为yyyy-MM-dd”,比“订单查询”这种描述精确得多。
还有一个隐藏很深的原因:模型上下文长度有限,工具数量达到几百个时,工具定义的Token开销会很大,有些模型可能会在内部做工具选择截断。这种情况下需要精简工具描述,或者把不常用的工具放到单独的MCP服务器上,按需连接。
5.3 参数反序列化报错:工具出来了,活没人干
工具被模型选中了,也传了参数,但MCP服务器执行时报错,报错信息通常是参数类型不匹配。最常见的是数字类型的参数被模型传成了字符串,或者日期格式不受Jackson解析。
解决方案有两个方向。一是增强参数类型的容错性,比如把接收日期参数的字段类型改成String,在工具方法内部再解析。二是使用@JsonComponent自定义反序列化器,处理特殊格式的参数。
我实际处理过的一个案例是:模型传了一个订单状态参数,服务器端字段类型是枚举,但模型传的值是“已完成”这种中文描述,枚举里只有“COMPLETED”这种英文定义。最终我写了一个自定义的枚举反序列化器,把中文映射到英文枚举值,问题就解决了。
5.4 多MCP服务器连接时的命名冲突
当项目需要同时连接多个MCP服务器时,比如一个服务器提供文件操作工具,另一个服务器提供数据库工具,可能会出现工具名称冲突。两个服务器都暴露了一个query工具,客户端注册时就会互相覆盖或直接报错。
Spring AI的MCP客户端支持通过连接名称来隔离工具命名空间。在配置文件中,每个连接都有独立的名称,Spring AI注册工具时会把连接名称作为前缀加到工具名称前。例如server1连接的query工具,最终被注册的工具名称可能是server1_query。通过这种命名隔离,不同服务器之间的同名校工具也能共存。
如果你发现工具名称没有按预期添加前缀,可以检查Spring AI版本对命名策略的支持情况。有些版本需要额外配置工具名称转换器,才能启用这个功能。
5.5 网络波动导致MCP客户端状态异常
SSE连接本质上是长连接,一旦网络抖动,连接可能会自动断开。如果MCP客户端没有自动重连机制,之后所有的工具调用都会失败。
Spring AI的MCP客户端内置了重连机制,但触发条件和使用场景需要确认清楚。我实际遇到的情况是:本地开发时wifi切换导致SSE断开,客户端状态一直处于异常,重启应用后才恢复。排查后发现,Spring AI的自动重连在客户端启动初期有效,但长时间运行后如果连接断开,有时不会自动恢复。
针对这个问题,我的建议是:如果业务场景对MCP工具的可用性要求比较高,可以在项目中增加一个定时任务,周期性检查MCP客户端的连接状态,发现断开就重新初始化。对于SSE连接这种长连接模型,心跳检测是很有必要的。
6. 进阶玩法与架构设计建议
6.1 “一行代码”接入背后的架构收益
回到标题的“一行代码接入几百个外部工具”,从架构层面看,这个能力最大的价值在于把工具接入模式从“点对点集成”变成了“平台化接入”。
设想一个公司内部有统一的数据平台,平台上有几百个数据查询工具。没有MCP之前,每个业务线要接入这些工具,都得让平台方开发对应的SDK或者API文档。有了MCP之后,平台方只需维护一个MCP服务器,所有AI应用通过标准协议连上来,自动发现、自动调用。工具新增、下线都不需要通知下游改动代码,这是非常典型的平台化架构收益。
我服务的某客户之前接一个内部数据平台大概要两周的联调时间,现在MCP服务器搭好后,新的AI应用接进来只需要一个密钥和一行配置,当天就能跑通。这个效率提升还是很直观的。
6.2 自建MCP服务器的三种模式
如果你也想把自己的能力暴露成MCP服务器,Spring AI提供了至少三种实现方式,我根据自己的实践经验讲一下差异。
第一种是基于Spring AI Starter的最简模式。你只需要创建一个Spring Boot项目,引入spring-ai-starter-mcp-server,把带有@Tool注解的Bean注入容器。启动后,Spring AI会自动创建MCP服务器端点,这个模式适合快速把已有工具暴露出去。
第二种是基于标准MCP Java SDK的自定义模式。你需要手动创建MCP服务器实例,配置传输方式和工具注册逻辑。这种模式适合需要深度定制协议细节的场景,比如自定义鉴权、自定义日志、自定义传输层。
第三种是嵌入式模式,也就是在同一个Spring Boot进程里同时启动MCP客户端和服务器。这种模式适合做工具编排:一个服务既消费其他MCP服务器的工具,又暴露自己的工具给别的服务调用。我在做企业内部AI网关时用过这种模式,效果很好。
从维护成本来看,我最推荐第一种模式。它把最复杂的协议处理都交给Spring AI的自动配置,开发者只需要关注业务逻辑本身,也就是写工具类。
6.3 懒加载机制:几百个工具不是每次都要全量加载
MCP客户端在启动时会自动获取服务器上的全部工具列表,这在大规模场景下有一个问题:如果两个AI应用分别只需要某一个工具,但MCP服务器暴露了五百个工具,那所有应用都得一次性加载五百个工具定义,Token消耗和时间成本都不小。
更深层的架构设计是:一个MCP服务器支持多个工具集合,通过不同的端点或命名空间隔离。例如一个文件管理服务器,可以拆出“读取工具集”和“写入工具集”两个MCP服务端点。客户端按需连接,避免全量加载。
但在实际项目里,这种拆分有时也带来额外维护成本。折中方案是让工具描述尽量精简,减少模型上下文压力,同时利用Spring AI的缓存机制,让工具列表获取结果可以被多个模型请求复用。
6.4 如果只是接两三个工具,还要用MCP吗
文章写到这里,你可能会问:我的项目只需要接两三个外部接口,有必要上MCP吗?
我的判断是分场景。如果你只是让模型调用一个固定的内部API,数据量小、工具变动频率低,那用Spring AI原生@Tool注解就够了,没必要引入额外的协议层,毕竟每多一层就多一份维护成本和网络延迟。
但如果你的工具数量在增长趋势中,或者工具是别人维护的、可能随时会新增或修改,或者你需要多个AI项目共享一批工具,那MCP的收益就很明显了。它最大的价值不是减少一次接入的代码量,而是减少长期维护工具接入逻辑的隐性成本。协议标准化带来的收益,往往在项目中期才会完全显现。
我个人的经验法则是:工具数量少于5个且不会频繁变动,用@Tool原生方式;工具数量超过10个,或者有多个系统共享工具需求,直接上MCP,前期多花半天时间搭基础,后续能省出无数个半天。
7. 我实际测试中的一些细节心得
7.1 Spring AI版本升级对MCP配置的影响比较大
MCP协议本身还在演进中,Spring AI对MCP的支持也跟着在变。从命名方式到配置结构,不同版本之间可能有不小的差异。
我最早接触MCP时用的是比较早期的版本,配置方式还是通过McpClient.Builder手动创建客户端,大概要写二三十行代码。到了1.0.0 M6版本,配置方式已经被精简成了YAML里的几行,自动装配也完善很多。
这种变化带来的启示是:看教程写代码前,先确认自己项目中的Spring AI版本。如果版本比我上面写的新,很可能配置语法已经不同了。官方文档中的迁移指南往往比第三方教程更可靠,建议优先查看官方Release Notes,而不是网上找老旧的示例。
另外,如果你还在用较老的Spring Boot 3.2甚至更早版本,很多MCP相关starter可能无法直接使用,需要手动引入更基础的依赖并自己完成许多配置。这种情况我会建议先升级Spring Boot版本再来玩MCP,否则容易在环境兼容性上浪费太多时间。
7.2 多个MCP服务器连接的统一管理经验
我在一个项目中同时连接了四个MCP服务器,分别提供文件操作、数据库查询、内部API调用和日志检索能力。连接数量增加后,管理复杂度也上来了。
首先是配置管理。四个服务器的配置信息如果散落在application.yml里,不容易追踪。我会把每个服务器的连接地址、认证方式、可用工具列表整理成一个独立的配置文档,并在代码中使用@ConfigurationProperties来管理相关参数。
其次是日志追踪。多个MCP服务器同时调用时,确实需要从日志确认某个工具调用来自哪个服务器。我会在自定义的ToolCallback包装器中加入服务器名称字段,在日志里打印完整链路信息。
最终我的做法是实现了一个简单的MCP连接管理器,统一负责创建、监控和销毁各个MCP客户端,核心逻辑是通过配置类扫描所有连接定义,为每个连接生成独立的工具注册上下文。这样即使连接再多,代码结构也不会太乱。
7.3 远程部署时SSE连接的网络限制
本地开发时一切正常,部署到远程服务器后MCP工具经常调用失败,这是我在实际项目中遇到的另一个高频问题。
原因出在网络架构上。SSE连接要求客户端能持续连接到服务器的长连接端点,如果中间有Nginx或网关层,必须确认它们支持流式响应的缓冲配置。Nginx默认可能会对响应做缓冲,导致事件流不能及时推送,表现就是工具调用响应很慢甚至超时。
解决方案是在Nginx配置中关闭对SSE端点的缓冲:
location /sse { proxy_buffering off; proxy_cache off; proxy_set_header Connection ''; proxy_http_version 1.1; chunked_transfer_encoding off; }这虽然不是Spring AI本身的配置问题,但却是将MCP应用部署到生产环境时必须掌握的技能。我在第一次远程部署时因为这个问题排查了整整一天,所以特意写出来提醒各位。
还有一个更稳妥的替代方案是,在远程部署场景下优先选择流式HTTP传输而不是SSE。流式HTTP在穿透代理时通常更稳定,配置也更简单。如果你的MCP服务器支持这种传输方式,建议生产环境直接采用。
7.4 结合流程回顾:MCP实战中的核心要点
整个实战过程走完之后,我整理了几个最核心的要点,作为这篇文章的浓缩版:
第一,MCP解决的是工具标准化接入的问题,它把“工具提供方”和“工具消费方”解耦。只要大家都遵循MCP协议,就能实现平台化的工具接入。
第二,Spring AI对MCP的支持成熟度比较高,自动装配能省去绝大部分重复代码。配置重点在于依赖版本、连接地址和传输方式的选择。
第三,工具描述质量直接影响模型调用工具的准确性。好的描述应当包含触发场景、参数含义和返回内容说明。
第四,生产环境下需要重点关注MCP连接的网络稳定性、超时配置和代理穿透问题。SSE长连接在远程部署时尤其容易出现网络限制。
第五,工具数量多时,通过多个MCP服务器按业务域拆分工具集,配合命名空间隔离,是比单一巨型MCP服务器更合理的架构选择。