1. 为什么要在 Spring Boot 里接 MCP 和 Qwen如果你已经写过几个 Spring Boot 项目最近又被 MCPModel Context Protocol刷屏大概率会有一个疑问这东西到底能不能直接塞进我现有的工程里而不是另起一个 Python 脚本答案是能而且用 SpringAI 的 MCP Server Starter 之后配置量比想象中小很多。MCP 本质上是一套让大模型调用外部工具和数据的标准协议你可以把它理解成「AI 世界的 USB-C 接口」——以前每个模型要对接一个工具就得写一套 Function Calling 代码现在只要工具方按 MCP 暴露一次所有支持 MCP 的模型都能用。SpringAI 作为 Java 生态里的 AI 集成框架提供了spring-ai-starter-mcp-server-webmvc这类 starter让你用几个注解就能把 Spring Bean 变成 MCP Tool。这篇面向的是已经会写RestController、懂application.yml配置、但还没把 MCP 接进 Spring Boot 的开发者。我会给出可复制的 pom 依赖、application.yml、MCP 服务端骨架再补上启动验证和一次完整调用链的检查动作。模型侧用 Qwen因为它在工具调用上支持得比较早配合 MCP 客户端能直接跑通「模型决策 → 调用 MCP 工具 → 返回结果」这条链路。需要提前说明的是MCP 服务本身不绑定任何模型它只负责暴露工具真正把 Qwen 和 MCP 串起来的是客户端那一层。所以本文分成两段先把 Spring Boot 里的 MCP Server 跑起来再讲怎么让 Qwen 通过客户端连上它。2. 前置准备依赖、版本与 TaoToken 接入点在动手写代码之前先把版本和依赖理清楚。SpringAI 的 MCP 支持在 1.0.0 系列里已经比较稳定但不同小版本之间 API 有调整建议锁定一个 BOM 版本不要让它自己漂。pom.xml 里需要两块内容一是spring-ai-bom做依赖管理二是 MCP Server 的 starter。如果你用的是 WebMVC 栈大多数 Spring Boot 项目都是选spring-ai-starter-mcp-server-webmvc如果是 WebFlux换成对应的 reactive 版本。dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies这里有个坑SpringAI 的 milestone 版本在中央仓库和里程碑仓库之间分布不一样如果拉不到依赖检查一下repositories里有没有加 Spring 的 milestone 地址。正式版发布后这个问题会少很多但如果你跟的是 SNAPSHOT就得接受它偶尔抽风。模型侧要调 Qwen你需要一个能转发 OpenAI 兼容接口的入口。TaoToken 提供的就是这类接入能力官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 。拿到 API Key 之后Qwen 的模型名按平台文档填通常是qwen-plus或qwen-max这类。Key 的创建入口在控制台的 API Keys 页面地址是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 建议单独建一个 Key 给这个项目用方便后面排查调用量。3. 可复制配置application.yml 与 MCP Server 骨架配置分两部分Spring Boot 自身的 MCP Server 参数以及模型客户端的连接参数。先看 MCP Server 这边。server: port: 8080 spring: application: name: spring-ai-mcp-server ai: mcp: server: name: weather-mcp-server version: 1.0.0 protocol: STREAMABLE type: SYNC sse-endpoint: /sse sse-message-endpoint: /mcp/message几个参数解释一下。protocol选STREAMABLE是较新的传输方式兼容 SSE 和流式 HTTP如果你用的客户端只认老式 SSE就改成SSE。type选SYNC表示同步工具调用适合大多数 CRUD 类工具如果工具有长耗时操作可以考虑ASYNC。sse-endpoint是客户端建立连接的路径sse-message-endpoint是后续消息回传的路径这两个路径客户端配置里要对应上。然后是工具类。SpringAI 用Tool注解标记方法用ToolParam描述参数方法返回值会被序列化成 MCP 的响应内容。Service public class WeatherService { Tool(description 根据城市名称获取天气预报) public String getWeatherByCity( ToolParam(description 城市名称例如上海) String city) { if (city null || city.isBlank()) { return 抱歉城市名称不能为空; } MapString, String mockData Map.of( 西安, 晴天, 北京, 小雨, 上海, 大雨 ); return mockData.getOrDefault(city, 抱歉未查询到对应城市); } }工具写完之后必须注册成ToolCallbackProvider否则 MCP Server 不会把它暴露出去。这一步很多人会漏导致客户端listTools返回空列表。Configuration public class McpToolConfig { Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } }启动类就是普通的 Spring Boot 启动类不需要额外注解。如果你用 SSE 方式Web 容器会自动起来如果用 STDIO 方式需要加-Dspring.main.web-application-typenone把 Web 容器关掉否则会冲突。4. 验证请求从 listTools 到一次完整调用服务起来之后第一步不是急着调工具而是先确认工具列表能被正确读取。用任意 MCP 客户端连上http://localhost:8080发起listTools请求正常应该看到类似下面的返回{ tools: [ { name: getWeatherByCity, description: 根据城市名称获取天气预报, inputSchema: { type: object, properties: { city: { type: string, description: 城市名称例如上海 } }, required: [city] } } ] }如果tools是空数组先检查ToolCallbackProvider那个 Bean 有没有被扫描到再看Tool注解的方法是不是 public。这两个条件缺一个工具就不会注册。列表正常之后发起一次callToolvar client McpClient.sync( new HttpClientSseClientTransport(http://localhost:8080)) .build(); CallToolResult result client.callTool( new CallToolRequest(getWeatherByCity, Map.of(city, 上海))); System.out.println(result);预期输出里content数组会包含一个TextContenttext字段是「大雨」isError为false。如果isError是true说明工具方法内部抛了异常去看服务端日志里的堆栈。到这里 MCP Server 本身就算跑通了。接下来是让 Qwen 通过客户端连上它。在支持 MCP 的客户端里比如 Cherry Studio 这类工具添加 MCP 服务配置{ mcpServers: { weather: { isActive: true, description: 本地天气查询 MCP 服务, url: http://localhost:8080/sse } } }然后在模型设置里选 Qwen并确保「工具调用」特性是打开的。对话时勾选这个 MCP 服务器问一句「上海天气怎么样」模型会先决定调用getWeatherByCity拿到结果后再组织自然语言回复。这条链路走通说明 Spring Boot 里的 MCP Server 和 Qwen 已经接上了。5. 本篇常见错排查启动报端口冲突或 Web 容器异常如果你同时想用 STDIO 方式测试记得加-Dspring.main.web-application-typenone否则 Web 容器和 STDIO 会抢资源。SSE 方式则必须保留 Web 容器。listTools 返回空九成是ToolCallbackProvider没注册或者Tool方法不是 public。另外检查一下Service有没有被组件扫描到包路径对不对。调用工具时报 schema 校验失败ToolParam的 description 别写太长某些客户端对 schema 大小有限制。参数类型尽量用 String、Integer 这类基础类型复杂对象容易在序列化时出问题。Qwen 不调用工具直接瞎编答案先确认客户端里「工具」开关打开了再确认模型名选的是支持工具调用的版本。有些轻量模型不支持 function calling换qwen-plus以上通常就好了。SSE 连接建立后立刻断开检查sse-endpoint和客户端配置的 URL 是否一致。如果服务端配的是/sse客户端却连/mcp/sse握手会失败。另外反向代理场景下要确保没有缓冲 SSE 流。API Key 报 401确认 Key 是从 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 创建的并且请求头里的Authorization: Bearer key格式正确。如果 Key 刚创建等几秒再试有时候有缓存延迟。6. 把 MCP 接进现有工程的下一步最小示例跑通之后真正要落地到现有工程还有几件事值得做。一是把工具按业务域拆成多个Service每个服务注册成独立的ToolCallbackProvider这样工具列表清晰也方便按需开关。二是给工具方法加超时和降级MCP 调用是同步的一个慢工具会拖住整个请求用Async或者信号量控制并发数比较稳妥。模型侧如果要做长期编码或 Agent 场景可以考虑用 Coding Plan 这类按量方案入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 比每次手动配 Key 省事。调试模型行为的时候模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 可以直接验证 Qwen 对工具描述的理解是否准确省得在代码里反复改 prompt。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有针对不同客户端的配置示例。如果你用的是 Claude Code 这类工具Anthropic 兼容接入的说明在 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite 配置思路和本文的 MCP 客户端类似只是传输层换成了它自己的协议。最后提醒一句MCP Server 暴露的工具等于把内部能力开放给了模型生产环境一定要加鉴权和参数校验别让getWeatherByCity这种看起来无害的方法变成任意查询入口。工具方法的入参该校验就校验该限流就限流这部分和写普通 Controller 没有区别。