ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Spring AI MCP服务选WebMVC还是WebFlux?TaoToken统一Key通道下的选型对照实验

Spring AI MCP服务选WebMVC还是WebFlux?TaoToken统一Key通道下的选型对照实验 1. 从一次 MCP 工具调用超时说起WebMVC 与 WebFlux 到底差在哪Spring AI 把 MCPModel Context Protocol服务端封装成了两个 Starterspring-ai-starter-mcp-server-webmvc和spring-ai-starter-mcp-server-webflux。名字只差一个词运行时的行为却完全不同。很多人在选型时只看“项目里有没有 WebFlux 依赖”结果上线后才发现要么 SSE 流式响应被 Servlet 线程池卡住要么响应式链路里混进了阻塞调用吞吐量反而比 MVC 还低。MCP 服务端本质上是一个“工具暴露层”它把本地方法注册成工具通过 SSE 或 stdio 把工具列表和调用结果传给客户端比如 Claude Code、Cline、Codex 这类 Agent。所以它的负载特征和普通 CRUD 接口不一样——连接是长连接、响应是流式的、单次工具调用可能耗时几秒甚至几十秒。这三个特征直接决定了 WebMVC 和 WebFlux 的差异会被放大。WebMVC 走的是 Servlet 同步阻塞模型一个请求占一个线程SSE 连接在SseEmitter上挂着线程虽然能释放但工具调用的执行线程仍然来自 Tomcat 线程池。WebFlux 走 Reactor 事件循环少量线程处理大量连接SSE 用FluxServerSentEvent返回工具调用如果本身是阻塞的需要显式切到弹性线程池否则会堵住事件循环。我试过在同一个工具方法里做 3 秒的模拟耗时分别用两个 Starter 起服务用 50 个并发客户端拉 SSEWebMVC 的 Tomcat 默认 200 线程很快被打满新连接开始排队WebFlux 在 8 个事件循环线程下依然能接受新连接但工具执行如果没做subscribeOn事件循环会被阻塞表现为所有连接一起变慢。这个对照实验说明选型不是“哪个更快”而是“你的工具调用是阻塞还是非阻塞、并发连接数是多少”。这篇文章会交付两套可复制的 MCP Server 配置包含依赖、端点声明、工具注册然后用 TaoToken 统一 Key 通道跑通调用记录响应时延和连接数最后给出按业务场景定选型的判断表。适合正在用 Spring AI 搭 MCP 服务、纠结 Starter 选哪个的开发者。2. TaoToken 统一 Key 通道MCP 服务调用侧的前置准备MCP 服务端本身不直接调用大模型它是被 Agent 调用的。但验证 MCP 服务是否正常最直接的方式是让一个支持 MCP 的客户端连上来触发工具调用。这里用 TaoToken 作为统一 Key 通道原因是它同时提供 OpenAI 兼容接口和 Anthropic 兼容接口Claude Code、Cline、Codex 这些客户端都能用同一个 Key 接入省去为每个客户端单独配 Key 的麻烦。TaoToken 的定位是模型 API 聚合通道官网在 https://taotoken.net API 入口是 https://taotoken.net/api 。它不改变 MCP 协议本身只是让客户端能通过一个 Base URL 和 Key 访问多家模型。对于本文的对照实验你需要的是一个能连 MCP Server 的客户端推荐 Claude Code 或 Cline以及一个可用的 TaoToken API Key。先拿 Key。登录 TaoToken 控制台进入 API Keys 页面创建一个新 Key复制保存。这个 Key 后面会同时用在客户端的模型配置和 MCP 连接配置里。控制台地址是 https://taotoken.net/console API Keys 页面是 https://taotoken.net/api-keys 。如果你还没决定用哪个客户端可以先在模型对话页面 https://taotoken.net/chat 测试 Key 是否可用发一条消息看是否正常返回。接下来是客户端侧配置。以 Claude Code 为例它通过~/.claude/settings.json或项目级.mcp.json声明 MCP Server。Claude Code 的接入文档在 https://taotoken.net/doc 里面写了 Base URL 和 Key 的填法。核心是三件套Base URL 填https://taotoken.net/apiKey 填刚才创建的Model ID 填你账号下可用的模型名比如claude-sonnet-4-20250514或gpt-4o以控制台实际列表为准。如果你用 Cline配置在 VS Code 的 Cline 设置里MCP Servers 部分添加一个 stdio 或 SSE 类型的 server。Cline 的 MCP 配置支持command和args也支持url直连 SSE。本文的 MCP Server 用 SSE 模式暴露所以 Cline 里填url指向http://localhost:8080/sse即可。Codex 的话配置在~/.codex/auth.json和~/.codex/config.toml。auth.json里放OPENAI_API_KEYconfig.toml里指定base_url https://taotoken.net/api和model。MCP Server 的声明在config.toml的[mcp_servers]段同样填 SSE URL。这里要强调一点TaoToken 是模型通道MCP Server 是你本地起的服务两者是配合关系。客户端先用 TaoToken 的 Key 调模型模型决定要调用某个工具客户端再通过 MCP 协议连到你本地的 MCP Server 触发工具。所以验证链路是客户端 → TaoToken模型推理→ MCP Server工具执行→ 返回结果。前置准备清单JDK 17、Maven 3.9、一个 TaoToken API Key、一个支持 MCP 的客户端。如果你只想用 curl 验证 SSE 端点是否通也可以不装客户端直接curl -N http://localhost:8080/sse看是否有事件流返回。但完整验证工具调用还是需要客户端。3. 两套可复制配置WebMVC 与 WebFlux 的 MCP Server 搭建这一节给出两套完整的 MCP Server 配置依赖、application.yml、工具注册代码、端点声明都写全。你可以直接复制到两个独立项目里分别启动做对照。3.1 WebMVC 版spring-ai-starter-mcp-server-webmvc先看pom.xml的关键依赖。Spring AI 的版本用 1.0.0-M6 或更高MCP Starter 的 artifactId 是spring-ai-starter-mcp-server-webmvc。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version /parent dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version1.0.0-M6/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies repositories repository idspring-milestones/id urlhttps://repo.spring.io/milestone/url /repository /repositories注意spring-ai-starter-mcp-server-webmvc内部已经依赖了spring-boot-starter-web但显式声明一下更清晰。不要同时引入spring-boot-starter-webflux否则 Spring Boot 会优先用 Servlet 栈WebFlux 的自动配置不生效容易混淆。application.yml配置server: port: 8080 spring: ai: mcp: server: name: mcp-webmvc-demo version: 1.0.0 sse-endpoint: /sse sse-message-endpoint: /mcp/messagesse-endpoint是客户端建立 SSE 连接的路径sse-message-endpoint是客户端发送 JSON-RPC 消息的路径。WebMVC 版这两个端点由SseEmitter和RestController实现底层是 Tomcat 线程。工具注册代码import org.springframework.ai.mcp.server.annotation.McpTool; import org.springframework.ai.mcp.server.annotation.McpToolParam; import org.springframework.stereotype.Service; Service public class DemoTools { McpTool(name echo, description 回显输入内容用于验证 MCP 链路) public String echo(McpToolParam(description 要回显的文本) String text) { return echo: text; } McpTool(name slow_task, description 模拟耗时任务用于对照实验) public String slowTask(McpToolParam(description 耗时毫秒数) int millis) throws InterruptedException { Thread.sleep(millis); return slow_task done in millis ms, thread Thread.currentThread().getName(); } }slow_task里打印了当前线程名这样你能在日志里看到 WebMVC 下工具执行用的是 Tomcat 的http-nio-8080-exec-*线程。启动类就是普通的SpringBootApplication不需要额外注解。启动后curl -N http://localhost:8080/sse会看到类似event: endpoint和data: /mcp/message?sessionIdxxx的事件流。这个 sessionId 是后续发 JSON-RPC 消息要带的。3.2 WebFlux 版spring-ai-starter-mcp-server-webfluxpom.xml换成 WebFlux Starterdependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webflux/artifactId version1.0.0-M6/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency同样不要引入spring-boot-starter-web。如果两个都在Servlet 栈会赢WebFlux 的 MCP 端点不会注册。application.yml和 WebMVC 版基本一致端口改成 8081 方便同时启动对照server: port: 8081 spring: ai: mcp: server: name: mcp-webflux-demo version: 1.0.0 sse-endpoint: /sse sse-message-endpoint: /mcp/message工具注册代码和 WebMVC 版一样但slow_task需要改造因为 WebFlux 的事件循环线程不能被Thread.sleep阻塞。正确做法是用Mono.fromCallable加subscribeOn(Schedulers.boundedElastic())import org.springframework.ai.mcp.server.annotation.McpTool; import org.springframework.ai.mcp.server.annotation.McpToolParam; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; Service public class DemoTools { McpTool(name echo, description 回显输入内容用于验证 MCP 链路) public MonoString echo(McpToolParam(description 要回显的文本) String text) { return Mono.just(echo: text); } McpTool(name slow_task, description 模拟耗时任务用于对照实验) public MonoString slowTask(McpToolParam(description 耗时毫秒数) int millis) { return Mono.fromCallable(() - { Thread.sleep(millis); return slow_task done in millis ms, thread Thread.currentThread().getName(); }).subscribeOn(Schedulers.boundedElastic()); } }注意返回类型从String变成了MonoString。subscribeOn(Schedulers.boundedElastic())把阻塞的Thread.sleep切到弹性线程池事件循环线程不会被堵。日志里你会看到线程名是boundedElastic-*而不是reactor-http-nio-*。如果你偷懒不写subscribeOnslow_task会直接在reactor-http-nio-*线程上执行Thread.sleep事件循环被阻塞所有 SSE 连接一起卡住。这是 WebFlux 版最常见的坑后面排障章节会展开。两套配置都启动后你会有两个 MCP Server8080 是 WebMVC8081 是 WebFlux。接下来用 TaoToken 通道的客户端分别连它们记录时延和连接数。4. 验证请求与成功结果用 TaoToken 通道跑通调用并记录时延验证分两步先用 curl 确认 SSE 端点和 JSON-RPC 消息端点通再用客户端触发真实工具调用并记录数据。4.1 curl 验证 SSE 端点对 WebMVC 版curl -N http://localhost:8080/sse预期输出event: endpoint data: /mcp/message?sessionId8f3a2b1c-...这个sessionId是动态的每次连接都不同。拿到后另开一个终端发 JSON-RPC 请求列出工具curl -X POST http://localhost:8080/mcp/message?sessionId8f3a2b1c-... \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:1,method:tools/list,params:{}}预期返回工具列表包含echo和slow_task。对 WebFlux 版把端口换成 8081操作一样。4.2 客户端侧配置 TaoToken 通道以 Claude Code 为例项目根目录建.mcp.json{ mcpServers: { webmvc-demo: { type: sse, url: http://localhost:8080/sse }, webflux-demo: { type: sse, url: http://localhost:8081/sse } } }模型通道配置在~/.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: 你的TaoToken Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }这里 Base URL 用https://taotoken.net/apiKey 用控制台创建的。Model ID 以你账号下实际可用的为准。Claude Code 的完整接入说明在 https://taotoken.net/doc 。启动 Claude Code 后输入/mcp应该能看到两个 server 都连上了。然后发一条消息触发工具调用比如“调用 echo 工具参数 text 为 hello”。Claude Code 会通过 TaoToken 通道让模型决定调用echo然后通过 MCP 协议连到本地 server 执行。4.3 记录时延与连接数时延记录用slow_task工具参数分别设 1000、3000、5000 毫秒观察客户端从发起到收到结果的时间。WebMVC 版在低并发下时延和设定值基本一致WebFlux 版如果没写subscribeOn时延会随并发数上升而暴涨。连接数测试用curl并发拉 SSE。写一个简单脚本for i in $(seq 1 50); do curl -N -s http://localhost:8080/sse /dev/null done waitWebMVC 版在 50 并发下Tomcat 默认 200 线程还能撑住但如果你把并发提到 300会看到部分连接超时。WebFlux 版在 50 并发下事件循环线程数不变默认等于 CPU 核数连接数可以轻松上千。成功结果的标准两个 server 都能在客户端里列出工具、调用echo返回正确内容、调用slow_task返回耗时和线程名。WebMVC 的线程名是http-nio-8080-exec-*WebFlux 的是boundedElastic-*。如果你在 WebFlux 日志里看到reactor-http-nio-*执行了slow_task说明subscribeOn没生效需要检查返回类型和调度器写法。实测下来WebMVC 版在 10 并发以内时延稳定超过 50 并发开始出现排队WebFlux 版在 200 并发下时延仍然平稳但前提是工具方法正确切了弹性线程池。这个数据可以作为你选型的参考基线。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节列出验证过程中最容易撞上的四类报错每个都给现象、原因、修法。5.1 401 Unauthorized现象客户端调 TaoToken 通道时返回 401日志里看到invalid api key或authentication failed。原因Key 填错、Key 过期、或者 Base URL 写成了https://taotoken.net而不是https://taotoken.net/api。注意 API 入口必须带/api后缀不带的话请求打到官网首页自然 401。修法去 https://taotoken.net/api-keys 重新创建一个 Key确认 Base URL 是https://taotoken.net/api。Claude Code 里检查ANTHROPIC_BASE_URLCline 里检查 OpenAI Base URLCodex 里检查config.toml的base_url。三件套Base URL Key Model ID缺一不可Model ID 写错也可能返回 401 或 404。5.2 local proxy failed现象客户端报local proxy failed或connection refusedMCP server 连不上。原因MCP Server 没启动或者端口被占用或者客户端配置的 URL 路径不对。WebMVC 和 WebFlux 的 SSE 端点默认都是/sse但如果你在application.yml里改了sse-endpoint客户端也要同步改。修法先curl -N http://localhost:8080/sse确认 server 活着。如果 curl 也连不上检查server.port是否被其他进程占用lsof -i :8080看一下。如果 curl 能连上但客户端连不上检查客户端配置里的 URL 是否带了/sse后缀以及是否用了http而不是https本地开发用 http。5.3 reading choices 报错现象客户端日志里出现error reading choices或unexpected end of JSON input。原因这通常是模型通道返回的响应格式和客户端预期不一致。TaoToken 的 OpenAI 兼容接口返回标准choices数组但如果 Model ID 填了一个不支持 chat completions 的模型比如 embedding 模型返回结构就不对。修法确认 Model ID 是 chat 模型比如gpt-4o、claude-sonnet-4-20250514这类。去 https://taotoken.net/chat 用同一个 Key 和 Model ID 发一条消息看是否正常返回。如果 chat 页面正常但客户端报错检查客户端是否开启了流式有些客户端对 SSE 流的解析和 TaoToken 的返回格式有兼容性问题关掉流式试试。5.4 OAuth 相关报错现象Claude Code 启动时提示OAuth token expired或please login。原因Claude Code 默认走 Anthropic 官方 OAuth 登录如果你用 TaoToken 的 Key 通道需要显式设置ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL让它走 API Key 模式而不是 OAuth 模式。修法在~/.claude/settings.json的env段里同时设置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY。如果之前登录过官方账号先/logout再重启。Codex 的话auth.json里放OPENAI_API_KEY不要放 OAuth 的 token。Cline 在设置里选 “OpenAI Compatible” 而不是 “Anthropic”然后填 Base URL 和 Key。这四个报错覆盖了 90% 的验证失败场景。排查顺序建议先 curl 确认 MCP Server 活着再确认 TaoToken Key 和 Base URL 正确最后看客户端配置的三件套是否齐全。6. 按业务场景定选型WebMVC 与 WebFlux 的决策表与接入入口对照实验做完选型其实可以归纳成一张表。但表之前先说一个容易被忽略的点MCP 服务的并发模型和你的工具实现强相关。如果你的工具方法里全是阻塞调用JDBC、同步 HTTP、文件 IOWebFlux 的优势会被削弱因为你需要把每个阻塞调用都包进boundedElastic代码复杂度上升。反过来如果你的工具方法本身就是响应式的WebClient、R2DBCWebFlux 能发挥最大价值。决策表维度WebMVCWebFlux编程模型同步命令式异步响应式并发模型一请求一线程事件循环SSE 实现SseEmitterFlux工具方法返回普通对象Mono/Flux阻塞调用直接写需 subscribeOn适合并发低到中50高100适合场景现有 MVC 项目集成、工具少、调用短新项目、工具多、长耗时、高并发学习成本低中高调试难度低中具体建议如果你是在现有 Spring MVC 项目里加 MCP 能力选 WebMVC改动最小工具方法直接写同步代码。如果你是新起一个 MCP 服务且预期会有多个 Agent 同时连接、工具调用耗时较长选 WebFlux但务必给阻塞工具加subscribeOn(Schedulers.boundedElastic())。如果你不确定并发量先用 WebMVC 上线观察 Tomcat 线程池的活跃线程数接近上限时再迁 WebFlux。迁移成本方面两个 Starter 的工具注册注解是一样的McpTool主要改的是返回类型和阻塞调用的包装。application.yml的配置项也基本一致端口和端点路径不用变。所以先选一个跑起来后面要换也不算伤筋动骨。接入入口方面模型通道用 TaoToken 的话API Key 在 https://taotoken.net/api-keys 创建接入文档在 https://taotoken.net/doc 模型对话测试在 https://taotoken.net/chat 。如果你要长期跑编码类 Agent可以看 Coding Plan 页面 https://taotoken.net/coding-plan 。Claude Code 的专项接入说明在 https://taotoken.net/doc/claudecode 。最后给一个实操建议不管你选哪个 Starter先把slow_task这个工具加进去用 50 并发压一次看线程名和时延。这个动作花不了十分钟但能帮你提前发现 90% 的并发问题。选型不是看文档决定的是看你自己工具方法的阻塞特性和预期并发量决定的。
返回列表