ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring AI集成MCP:从Function Calling到AI工具调用的标准化实践

Spring AI集成MCP:从Function Calling到AI工具调用的标准化实践 1. MCP到底解决了什么问题先把它放到AI应用架构里看我写Spring AI学习笔记前几篇一直在折腾模型对话、Prompt模板、Function Calling这类基础能力到第四篇终于轮到MCP了。先说结论如果你已经用过Spring AI的Function Calling那MCP理解起来会非常顺——它就是把“给AI接工具”这件事从一个项目内的局部功能升级成了跨系统、跨语言的标准化协议。传统的Function Calling做法是在当前Spring Boot进程内写一个Bean方法注册给大模型当工具模型决定调用时由Spring AI的框架帮你直接执行。这套东西在单应用内确实够用但一旦拆成微服务、或者要对接团队之外的工具问题就来了每个服务都有自己的鉴权方式、参数格式、接口文档你每接一个外部能力都要写一套适配代码。MCP要治的就是这个“接口方言满天飞”的病。MCP全称Model Context Protocol模型上下文协议最早由Anthropic在2024年底提出。它把“AI应用怎么连接数据源和工具”这个场景抽象成一套统一协议AI应用作为Host通过MCP Client连接一个或多个MCP ServerServer负责把本地的工具、资源、提示词暴露出去。这样AI应用不需要关心工具背后是Java、Python、Node还是什么其他语言也不需要关心它跑在本地进程还是远程服务上只要双方都遵守MCP协议就能直接对话。我个人的体会是MCP最值钱的地方不是“协议本身有多复杂”而是它把工具接入做成了类似USB-C这种通用接口。以前你给AI应用接一个内部查询接口要写OpenAPI规范、要处理鉴权、要调SDK现在只需要把接口包成一个MCP ServerAI应用就能自动发现工具、读取参数Schema、调用并拿到结果。对于Java开发者来说Spring AI从1.0开始把MCP的Server和Client两端都做成了Starter开箱即用这也是我为什么把这篇单独拿出来写。2. MCP的核心概念Host、Client、Server与调用链路2.1 三个角色和三个原语MCP的架构里有三个角色MCP Host指的是AI应用本身比如Claude Desktop、IDE插件或者我们的Spring AI应用。Host负责跟用户交互、跟大模型交互同时管理多个MCP连接。MCP Server对外暴露工具、资源、提示词的进程。Server可以用任何语言实现只要遵循MCP协议。MCP ClientHost内部用来连接Server的组件负责协议通信、消息编解码、会话管理。在Spring AI里这个Client由spring-ai-starter-mcp-client自动装配。协议层面有三大原语Tools工具可被大模型调用的函数比如查询数据库、调用REST接口、执行一段命令。工具是MCP最常用、也是Spring AI集成最核心的一块。Resources资源可以被读取的数据源比如文件内容、数据库记录、API返回结果通常以URI形式暴露。Prompts提示词模板可复用的提示词工程模板比如“代码评审模板”“SQL生成模板”Server端定义好后Host可以直接引用。对大多数业务场景来说你最需要关心的是Tools。因为Spring AI本身在“模型对话”这个层面已经做得够好接入MCP主要就是为了让模型拥有调用外部工具的能力而Tools这层正好覆盖这个需求。2.2 传输方式stdio和Streamable HTTP是怎么选的MCP支持多种传输方式Spring AI里最常见的两种stdioServer作为子进程启动Host通过标准输入输出流跟它通信。优点是零网络配置、安全隔离好适合Server与应用同机部署的场景。Spring AI为此提供了spring-ai-mcp-server-stdio这个分包。缺点是不能跨机器而且Server进程的生命周期要跟随Host。Streamable HTTP也称WebMVC/WebFlux模式Server独立部署成一个HTTP服务Client通过HTTP请求访问。优点是支持远程调用、便于扩缩容也方便复用已有的Spring Boot基础设施。Spring AI 1.0之后的默认HTTP模式就是这个再早一点的SSE模式已经逐渐被替换了。我自己的经验是如果只是自己本机玩或者在公司内部把AI能力嵌入现有Java服务用stdio最省事如果要做成独立的工具服务供多个AI应用调用那必须走HTTP模式。这也正好回答了“mcp host和mcp server”是什么关系Host是使用方Server是提供方两者可以同机也能跨网络完全由传输方式决定。2.3 mcp怎么被调用的一次完整调用的拆解很多人第一次看MCP代码会困惑明明我只写了一个Tool方法Spring AI是怎么让大模型调用它的实际上整条链路是这样的AI应用Host启动时MCP Client连接Server完成initialize握手。握手完成后Client调用tools/list把Server上所有工具拉回来并转换成模型能理解的Function Calling格式。用户向大模型提问模型判断“这个问题需要查数据/执行操作”时在响应里带上一个工具调用请求。Spring AI把这个请求交给MCP ClientClient按协议打包成JSON-RPC消息调用tools/call把参数传给Server。Server拿到工具名和参数后反射执行本地方法把结果返回给Client。Client把结果回传给大模型模型基于工具返回内容生成最终回答。这里最关键的一点是大模型本身不直接执行任何代码它只负责“决定调用哪个工具”和“生成参数”。真正执行逻辑的还是你写的Java方法。这也解释了为什么MCP工具的参数描述要写得足够清晰——大模型是靠描述来理解每个参数含义的描述写得模糊模型就更容易传错参数。3. 环境准备Spring Boot接入MCP的依赖与配置选型3.1 版本选择与依赖引入Spring AI对MCP的支持在1.0正式版里已经非常完善我建议直接使用1.0.0-GA或后续稳定版不要再碰0.8.x那一批老版本。老版本的包名是spring-ai-mcp-server-webmvc、spring-ai-mcp-client新版本改成了spring-ai-starter-mcp-server、spring-ai-starter-mcp-client如果不小心引了旧坐标会出现类找不到或者自动装配不生效的问题。以Maven为例一个最简的MCP Server项目需要这样引入dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency然后是模型侧的依赖。如果你是在同一个项目里既当Server又当Client还需要引入对应大模型的starterdependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency注意Spring AI的Starter设计是模块化拆分的模型、MCP Server、MCP Client各自独立你按需引入就行。如果要在同一个应用内测试“Host直连Server”可以把Server和Client都引进来但它们俩同时存在时一定要把Server端口和Client连接地址区分清楚否则很容易出现Client连了自身端口导致握手失败的情况。3.2 MCP Server端配置详解Server端配置在application.yml里核心参数是工具扫描路径和传输方式。一个典型的HTTP模式配置是这样spring: ai: mcp: server: name: my-tool-server version: 1.0.0 enabled: true transport: http如果你使用stdio模式配置稍有不同但大部分时候你甚至不需要写transportSpring AI会根据classpath里有没有web依赖来决定默认传输方式。我最常遇到的问题是项目里既引了spring-boot-starter-web又想要stdio结果服务启动时自动走了HTTP模式。这种情况下要么去掉web依赖要么显式指定transport: stdio。Server端暴露工具的方式很简单在任意被Spring管理的Bean上写Tool注解即可Service public class StockQueryService { Tool(description 根据股票代码查询最近收盘价) public String queryStockPrice(String stockCode) { // 调用数据库或第三方接口 return 股票 stockCode 最新收盘价12.34; } }看到这里你可能会发现这不就是Spring AI之前Function Calling的写法吗从代码层面确实很像但区别在于Function Calling的工具直接注册给当前模型而MCP Server的工具是注册在独立协议服务里的任何符合MCP规范的Host都能来调用不再局限于当前Spring Boot进程内的大模型。3.3 MCP Client端配置详解Client端要配置的是连接信息包括Server地址、超时时间和工具名。以HTTP模式连接本机Server为例spring: ai: mcp: client: enabled: true name: my-ai-app transport: http connection: base-url: http://localhost:8080 request-timeout: 30s在Java代码里注入McpToolSpecification或直接使用McpClientManagerSpring AI会自动把远端工具变成模型可调用的工具。最简单的用法是在构建ChatClient时把MCP工具塞进去Configuration public class McpConfig { Bean ChatClient chatClient( ChatClient.Builder builder, McpToolSpecification toolSpec) { return builder .defaultTools(toolSpec) .build(); } }这里要注意McpToolSpecification这个Bean通常由Client Starter自动装配但如果你引入了多个MCP Server需要自己定义Bean来做筛选和绑定。我早期踩过一次坑项目里同时配置了两个Server地址结果Client把所有工具合并在一起注册给模型导致不同Server下同名的工具互相覆盖只有先启动的那个生效。4. 实操把REST接口发布成MCP Server的完整过程4.1 改造已有的REST接口这节专门说“java将rest接口发布为mcp”这个高频需求。假设你有一个传统的Spring Boot项目里面已经有一个查询订单的REST接口RestController RequestMapping(/api/order) public class OrderController { private final OrderService orderService; public OrderController(OrderService orderService) { this.orderService orderService; } GetMapping(/{orderId}) public OrderVO getOrder(PathVariable String orderId) { return orderService.queryOrder(orderId); } }要发布成MCP最直接的做法是抽取Service层接口加Tool注解而不是直接在Controller上加。因为REST开发规范要求Controller只负责协议转换和参数校验工具调用本身应该是业务能力的暴露两者职责不同。改造后的ServiceService public class OrderService { Tool(description 根据订单ID查询订单详情返回订单号、商品名称、金额和状态) public OrderVO queryOrder(String orderId) { // 原有查询逻辑不变 return doQuery(orderId); } }这里建议返回VO对象而不是void或者boolean。因为大模型拿到工具返回结果后还要基于这个结果生成自然语言回答返回信息越结构化、越完整模型的回答质量越高。如果只返回一个true/false模型就只能干巴巴地告诉用户“操作成功”没法给出任何上下文。4.2 参数定义与工具描述的最佳实践MCP工具能否被大模型正确调用很大程度取决于工具描述和参数Schema。Spring AI会自动把Tool方法的Javadoc风格的description以及方法参数转换成JSON Schema但细节处还是有不少讲究。我整理了几条实践经验每个工具都要写description尤其是参数数量超过两个的时候。参数类型尽量用简单类型String、int、double或结构清晰的record少用继承体系复杂的实体类。如果某个参数有可选值应该在ToolParam的description里写明白比如“状态码可选值PENDING/SHIPPED/DONE”。方法名不要用queryOrder信息量弱的命名模型判断是否调用工具时会结合方法名和描述一起看方法名本身也尽量语义明确比如queryOrderById。参数上可以用ToolParam细化Tool(description 查询订单信息) public OrderVO queryOrder( ToolParam(description 订单ID例如ORD202501001) String orderId, ToolParam(description 是否包含已删除订单默认false) boolean includeDeleted) { // ... }为什么参数描述这么重要因为大模型本身没有业务系统的数据字典它能依赖的就是这些描述信息。描述写得足够细模型就更容易在用户含糊表达“帮我查一下那个订单”时从上下文里推断出orderId的值。反过来如果参数描述写的是“订单ID”模型可能不知道该从什么地方提取调用成功率会明显下降。4.3 配置MCP Server并验证调用按我上面的方式改完后启动Spring Boot应用控制台会打印类似这样一条日志Registered tool: queryOrder(根据订单ID查询订单详情返回订单号、商品名称、金额和状态) MCP Server started. Transport: http如果你想确认协议层是否正常工作可以直接用curl或Postman调MCP HTTP接口。MCP的HTTP模式不是像普通REST那样一个接口一个操作而是统一走一个端点消息用JSON-RPC 2.0格式。手动发一个tools/list请求可以这样curl -X POST http://localhost:8080/mcp \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:1,method:tools/list,params:{}}正常情况下会返回工具列表和参数Schema。等模型接入后完整的调用就是第2.3节描述的那条链路Host初始化→拉工具列表→模型决策→tools/call→执行方法→回传结果。这一步做完你的Spring Boot应用就已经从“只能提供REST接口”升级成了“能向AI应用提供标准化工具”的服务。4.4 Spring AI Client侧消费MCP Server服务端发布成功后再开一个Spring Boot项目专门做AI应用或者同一个项目引入Client Starter写一个最简单的调用入口Service public class ChatService { private final ChatClient chatClient; public ChatService(ChatClient chatClient) { this.chatClient chatClient; } public String ask(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }注意这里ChatClient在构建时已经把MCP的工具注册进去了。当你问模型“订单ORD202501001现在是什么状态”模型的推理过程是识别到需要订单信息。从可用工具列表里匹配到queryOrder。提取参数orderIdORD202501001。发起工具调用。拿到结果后组织回答。最终用户看到的就是一句自然语言回答订单ORD202501001已发货商品是XX金额100元。整个过程你只写了一个Service方法工具发现、参数解析、协议通信、结果回传全部由MCP和Spring AI框架代劳了。5. 集成RAG时的协同设计MCP和检索不是二选一顺着热搜词里“spring-ai集成rag”这个方向我也想聊聊MCP和RAG到底怎么配合。很多人第一次接触这两个概念时会困惑RAG是让模型读文档回答问题MCP是让模型调工具拿数据它们是不是重复了其实不然它们解决的是两类完全不同的需求。RAG适合的是“非结构化知识检索”比如公司内部的Word、PDF、Wiki页面这些内容没有固定字段你没法用参数去查询只能靠向量相似度去召回。MCP工具适合的是“结构化数据获取”比如订单状态、库存数量、用户信息这些数据有明确的查询条件直接调接口拿最新值比建向量索引更准、更快。我见过一个比较合理的组合场景一个内部客服AI先通过RAG把产品的常见问题文档召回回来再利用MCP工具查询该用户的订单详情和售后记录最后把两边的信息一并交给大模型生成答复。这种模式下RAG负责知识面MCP工具负责数据面各管一段。Spring AI里同时启用两者并不复杂只要把MCP工具和向量库Retriever都注册到同一个ChatClient即可ChatClient chatClient ChatClient.builder(model) .defaultTools(mcpToolSpecification) .defaultComponents(retriever) .build();需要注意的坑是工具太多会增大模型的选择难度响应速度也会变慢。如果你的向量检索和MCP工具加起来超过一二十个建议做一次工具裁剪只注册与当前业务强相关的。我自己的做法是按会话主题动态决定注入哪些工具避免让模型在无关工具之间反复“选择困难”。6. 实践过程中的坑与排查技巧6.1 工具注册不上或互相覆盖这是接入MCP时最容易遇到的问题。表现是模型明确应该能调某个工具但响应里就是没有工具调用或者调出来的是另一个同名工具。排查方向有三个检查MCP Server日志确认工具是否真的注册成功注意看方法所在类有没有被Spring扫描到。多个MCP Server场景下检查Client配置的server列表里有没有同名工具MCP协议本身不做去重同名工具后注册的会把先注册的覆盖掉。检查工具方法所在Bean是否被AOP代理了如果方法上既有Transactional又有Tool有时候切面会干扰Spring AI对工具方法的反射处理我建议把Tool方法放在独立的Service类里避免和事务切面混在一起。另外有个比较隐蔽的问题Spring AI会按方法名对工具做去重如果你在同一个Bean里写了两个重载方法且都加了Tool后面那个会把前面那个覆盖掉。MCP协议里工具名必须全局唯一所以我在实际开发中会让方法名尽量体现业务语义避免重载。6.2 参数类型不匹配和序列化问题模型生成的参数本质上是JSONMCP Client在调用Server时要把JSON转换成Java对象。Spring AI底层用Jackson来做反序列化所以你的工具参数类必须符合Jackson的默认序列化规则。我在项目中遇到过两次典型的坑参数里有LocalDateTime类型模型不知道具体格式传了个“2025-01-01 10:00:00”进来Jackson直接报错。解决办法是给参数类加JsonFormat注解或者在ToolParam的description里写清楚格式。参数是ListMapString,Object这种复杂嵌套结构模型生成的JSON键名跟实体类字段对不上导致部分字段丢失。解决办法是尽量把参数收敛成简单的record或者明确的DTO字段名要跟业务口径一致。要快速定位这类问题建议打开Spring AI的调试日志logging: level: org.springframework.ai: DEBUG org.springframework.ai.mcp: TRACE把日志打开后你能看到MCP Client发出去的完整请求和Server返回的原始响应序列化问题基本一眼就能看出来。6.3 超时和连接问题MCP工具调用跟普通REST请求最大的区别是它要走完“模型决策→工具调用→结果回传”整条链路耗时天然比单纯调一次Java方法长。默认的请求超时时间如果设置得太短大模型还没来得及返回工具调用结果客户端就判定超时了。我一般会把MCP Client的request-timeout设置成30秒以上如果你的工具有些慢查询甚至设置到60秒。另外HTTP传输模式下Server端也要注意网络超时和线程池配置否则高并发时工具调用直接排队前端等得心慌。还有个小问题是端口占用。同一个机器上跑多个MCP Server时如果每个Server都默认用8080端口后启动的服务肯定起不来。我给每个Server显式指定端口并在Client配置里把base-url对应好这样本地联调多个工具不会互相干扰。7. 从MCP生态看后续方向蓝湖、Figma、Codex这些都在做什么如果你在网上去搜MCP相关的内容会发现热度最高的不是Spring AI而是蓝湖MCP、Figma MCP、Codex MCP这些偏设计、偏开发工具的应用。它们本质上做的事情跟我们在Spring Boot里做的一模一样把某个软件的能力包装成MCP Server让AI应用能直接操作这个软件。拿Figma MCP举例它暴露的工具通常是“获取设计稿信息”“读取图层列表”“修改节点属性”这类能力。AI应用接入后你可以在对话里说“帮我把设计稿里主色调换成蓝色”模型就会调用Figma MCP Server里对应的工具通过Figma的API完成这个操作。蓝湖MCP也是类似面向产品设计协作场景把蓝湖上的设计稿、标注、切图信息变成AI可调用的工具。对我们Java后端开发者来说这些生态案例的启发在于MCP不只能接数据库、REST接口还能接桌面软件、设计工具、IDE、甚至游戏引擎。你只要把能力封装成工具并暴露成MCP Server任何支持MCP的AI应用都能连进来。如果你所在团队有内部系统比如用Jadx做逆向分析的、用Blender做建模的、用Godot做游戏开发的理论上都可以参考同一个套路把核心能力MCP化然后交给AI应用统一调度。这也是我在Spring AI学习到第四篇时最大的感受MCP的想象空间不在于协议本身而在于它把“AI接入外部世界”的成本降到了极低。以前每接一个新系统都要从零定制现在只需要让人家暴露一个MCP ServerAI应用就能自动发现能力、完成调用。8. 几个提高MCP应用质量的经验总结写到这里Spring AI集成MCP的主干内容基本讲完了。最后分享几条我在实际项目里沉淀下来的经验供大家参考第一工具的数量要克制。MCP Server可以暴露几百个工具但大模型一次能“看到”的工具数量是有限的工具太多不仅拖慢响应速度还会降低模型选对工具的概率。我习惯按业务域拆分MCP Server比如一个订单域Server、一个用户域ServerClient按需接入。第二工具要有可观测性。MCP调用链路变长之后出了问题很难定位。建议在工具方法里打上操作日志记录入参、出参、耗时。如果想要更精细的追踪可以在MCP Client出口加一个过滤器把tools/list和tools/call的消息都记录下来。第三安全和鉴权不能省。MCP Server暴露给AI应用的工具其实等同于对外暴露了一组高权限接口。你最好在每个工具方法上做一次权限校验而不是只依赖AI应用侧的对话权限。尤其是那些会执行写操作的工具比如“删除订单”“修改配置”必须加上二次确认逻辑。第四版本升级要谨慎。Spring AI的MCP模块版本迭代很快包名和配置项变动也频繁。升级大版本时重点检查三个地方transport配置是否还生效、ToolParam和Tool注解有没有改动、McpToolSpecification的装配逻辑有没有变化。MCP这块我目前也是边用边学Spring AI的官方文档和GitHub仓库更新很频繁遇到不懂的直接去翻源码比看二手博客更快。如果你正在用Java做AI应用MCP绝对是值得投入时间研究的方向——它让Java后端沉淀下来的业务能力第一次能这么顺畅地被AI调用起来。
返回列表