MCP+SpringBoot 王炸组合:拒绝CRUD,用Spring AI把Java老项目接上大模型 1. 为什么你的 SpringBoot 老项目该接 MCP 了如果你手上维护着一个跑了三五年的 SpringBoot 单体项目业务层里塞满了XxxControllerXxxServiceXxxMapper的标准三件套每天改的都是增删改查和字段校验那你大概已经感觉到光靠 CRUD 已经很难再做出让业务方眼前一亮的东西了。而大模型这波能力恰恰是给这类老而稳的系统续命的最好机会——不用重写业务层只要把已有的 Service 方法暴露成模型能调用的工具就能让工单系统自己查知识库、让订单系统自己解释异常原因。问题在于怎么接。直接在每个 Service 里写 HTTP 请求去调大模型 API代码会迅速腐化鉴权、重试、上下文拼装、工具描述全糊在业务逻辑里改一次模型就得动一次业务代码。这时候 MCPModel Context Protocol就派上用场了。你可以把它理解成 AI 世界的 USB-C 接口不管对面是 Claude Desktop、IDE 插件还是你自己的 Agent 程序只要双方都说 MCP就能即插即用。MCP 采用客户端-服务器架构主机应用Host通过 MCP 客户端连接一个或多个 MCP 服务器服务器再把本地数据库、内部 API、文件系统这些资源以工具Tool的形式暴露出去。Spring AI 把这套协议做成了 SpringBoot Starter意味着你可以在现有工程里加几个依赖、写一个Tool注解的方法、注册一个 Bean就能让老项目长出第一个非 CRUD 的 AI 能力接口。这篇就带你从零跑通一个工单场景下的知识库检索工具通过 MCP 暴露给模型调用。全程不碰你原有的业务层只做加法。2. 前置准备TaoToken 与工程环境在动手写代码之前先把模型从哪来这件事解决掉。MCP 服务器负责暴露工具但真正理解用户意图、决定调用哪个工具的还是背后的大模型。这里我用 TaoToken 作为模型接入层它提供 OpenAI 兼容的接口Spring AI 可以直接对接省去自己封装鉴权的麻烦。你需要先去控制台拿一个 API Key。打开 https://taotoken.net/api-keys 登录后创建一个新的 Key复制保存好——它只会完整显示一次。这个 Key 后面会写进application.yml用于 Spring AI 的 ChatClient 调用模型。工程环境方面确认几件事JDK 17 及以上Spring AI 对 17 有硬性要求别用 8 硬扛Maven 3.8SpringBoot 3.2。如果你现在的老项目还停在 SpringBoot 2.x建议先单独起一个模块做 MCP 服务跑通后再考虑怎么和主工程集成避免一次性改动太大把线上搞挂。依赖版本上有个坑要提前说Spring AI 的版本迭代很快spring-ai-mcp-server-webmvc-spring-boot-starter在不同小版本里包名和 API 都有微调。建议在pom.xml里用spring-ai-bom统一管理版本别一个个手写版本号否则很容易出现依赖冲突导致Tool注解扫描不到。下面第三节会给出完整的依赖片段。3. 可复制的依赖与配置骨架先看pom.xml。核心是两个方向一是 Spring AI 的 MCP 服务端 Starter二是模型客户端依赖。我把它拆成 BOM 管理和具体依赖两块你直接抄。dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0-M6/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies !-- MCP 服务端基于 Spring WebMVC 的 SSE 传输 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-webmvc-spring-boot-starter/artifactId /dependency !-- 模型客户端对接 OpenAI 兼容接口 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency !-- 如果你要暴露的工具方法涉及数据库保留原有依赖即可 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies注意spring-ai-mcp-server-webmvc-spring-boot-starter这个包名它对应的是基于 Servlet 的 SSE 传输实现。如果你的项目是 WebFlux 响应式的换成spring-ai-mcp-server-webflux-spring-boot-starter。两者别同时引入会打架。接着是application.yml。这里要配两块模型接入和 MCP 服务端行为。server: port: 8080 spring: ai: openai: # TaoToken 的 OpenAI 兼容端点 base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.3 mcp: server: name: pig-issue-mcp version: 1.0.0 # 使用 SSE 传输客户端通过 /sse 建立连接 protocol: SSE sse-endpoint: /sse # 工具变更时通知客户端刷新 type: SYNCapi-key我用了环境变量${TAOTOKEN_API_KEY}别把 Key 硬编码进 yml 提交到 Git这是最常见的翻车点。本地跑的时候在 IDE 的运行配置里加环境变量或者用export TAOTOKEN_API_KEY你的key再启动。然后是工具方法的编写。假设你有一个工单系统里面已经有一个知识库检索的 Service现在要把它暴露成 MCP 工具。关键就是Tool和ToolParam两个注解方法体里调用你原有的业务逻辑一行都不用改。Service public class IssueKnowledgeService { private final JdbcTemplate jdbcTemplate; public IssueKnowledgeService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Tool(description 检索工单知识库根据问题描述返回最匹配的解决方案) public String searchIssue( ToolParam(description 用户描述的技术问题越具体越好) String question) { // 这里复用你原有的查询逻辑示例用简单 LIKE 演示 String sql SELECT title, solution FROM issue_kb WHERE title LIKE ? LIMIT 3; ListMapString, Object rows jdbcTemplate.queryForList(sql, % question %); if (rows.isEmpty()) { return 知识库中暂无匹配记录建议转人工处理。; } return rows.stream() .map(r - 【 r.get(title) 】 r.get(solution)) .collect(Collectors.joining(\n\n)); } }最后是注册配置。Spring AI 需要一个ToolCallbackProviderBean 来收集所有带Tool的方法MCP 服务端启动时会扫描它。Configuration public class McpToolConfig { Bean public ToolCallbackProvider issueTools(IssueKnowledgeService issueKnowledgeService) { return MethodToolCallbackProvider.builder() .toolObjects(issueKnowledgeService) .build(); } }到这里配置骨架就齐了。整个改动没有侵入你原有的 Controller 和 Service只是新增了一个工具类和一个配置类。4. 启动验证让模型真的调用你的工具配置写完启动SpringBootApplication。控制台如果出现类似Registered tools: [searchIssue]的日志说明工具注册成功。如果没看到八成是Tool所在类没被 Spring 扫描到检查一下包路径。MCP 服务端启动后SSE 端点默认在http://localhost:8080/sse。你可以先用 curl 探一下连接是否建立curl -N http://localhost:8080/sse正常会返回一串event: endpoint和data: /mcp/message?sessionIdxxx的事件流说明 SSE 通道通了。-N参数是关闭缓冲方便实时看到事件。接下来验证模型调用。写一个简单的测试接口用ChatClient发起对话看模型会不会主动调用searchIssue工具RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder, ToolCallbackProvider issueTools) { this.chatClient builder .defaultTools(issueTools) .build(); } GetMapping(/ask) public String ask(RequestParam String q) { return chatClient.prompt() .user(q) .call() .content(); } }启动后访问http://localhost:8080/ask?q订单支付超时怎么排查。如果一切正常你会看到模型先思考要不要调用工具然后返回一段基于你知识库内容的回答而不是凭空编造。实测下来第一次调用可能会慢两三秒因为模型要先做工具选择后续同会话会快很多。想更直观地看工具调用过程可以在application.yml里把 Spring AI 的日志级别调到 DEBUGlogging: level: org.springframework.ai: DEBUG这样控制台会打印出模型请求了哪个工具、传了什么参数、工具返回了什么排障时非常有用。5. 本篇常见错误排查跑不通的时候先对照下面几个高频问题基本能覆盖八成情况。工具没被注册日志里看不到Registered tools。最常见的原因是Tool方法所在的类没有被 Spring 管理。检查类上有没有Service或Component以及包路径是否在启动类的扫描范围内。另一个可能是ToolCallbackProviderBean 没建或者建了但没把工具对象塞进去。启动报NoClassDefFoundError或ClassNotFoundException。这是版本冲突的典型症状。Spring AI 的 MCP Starter 和io.modelcontextprotocol.sdk下的传输实现版本必须匹配。用 BOM 统一管理能避免大部分问题如果还报错执行mvn dependency:tree看有没有重复的mcp相关包排除掉旧版本。SSE 连接建立后立刻断开。检查spring.ai.mcp.server.protocol是否配成了SSE以及sse-endpoint路径有没有和你的其他接口冲突。另外如果你在网关或 Nginx 后面跑记得关闭对 SSE 的缓冲否则事件会被攒着一起发客户端看起来就像卡死了。模型不调用工具直接自己编答案。这通常是工具描述写得太模糊。Tool(description ...)里的描述要具体说明什么时候该用这个工具而不是只写查询知识库。比如改成当用户询问工单处理方案、故障排查步骤时检索内部知识库获取标准答案模型的选择准确率会明显提升。另外temperature别设太高0.3 左右比较稳。调用工具时报参数解析失败。ToolParam的description要写清楚参数格式模型是靠这段描述来生成参数的。如果参数是枚举或特定格式在描述里给出示例比如问题类型可选值支付、物流、账号。6. 下一步从单工具到真正的 Agent 能力跑通第一个工具之后你会发现 MCP 的真正价值在于组合。一个工单系统里检索知识库只是第一步后面还可以把查询订单状态创建工单升级工单优先级都做成工具让模型根据用户一句话自动编排调用顺序。这时候你原来的 Service 层就成了模型的手脚而模型负责大脑的决策。如果你打算长期在这条路上投入建议关注 TaoToken 的 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite它针对编码和 Agent 场景做了额度优化比按量计费更适合频繁调试工具调用的阶段。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 里面有 Spring AI 对接的完整参数说明。想先直观感受模型对话效果可以直接用模型对话页面https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite试几个工单场景的提问看看模型会怎么拆解问题。最后留一个实操建议别一上来就把生产库的所有查询都暴露成工具。先用只读的、带 LIMIT 的查询跑通链路确认模型调用行为符合预期后再逐步放开写操作并且给每个写工具加上参数校验和权限判断。MCP 让接入变简单了但模型能调什么这件事边界还是得你自己守。