 学习:从 STDIO 到 SSE 的 Spring AI 接入实践)
1. 为什么要在 Spring AI 里区分 STDIO 和 SSEMCP 全称 Model Context Protocol你可以把它理解成给大模型外接工具和数据源的一根“标准插头”。模型本身只会生成文本但通过 MCP它能调用天气查询、数据库读取、文件操作这类真实能力。Spring AI 把这套协议封装成了 Starter让 Java 开发者不用手写协议解析直接写Tool方法就能暴露给模型。真正落地时第一个卡住大多数人的问题不是“怎么写工具”而是“用哪种传输方式”。Spring AI 的 MCP Server 提供了三种模式spring-ai-starter-mcp-serverSTDIO、spring-ai-starter-mcp-server-webmvcSpring MVC SSE、spring-ai-starter-mcp-server-webfluxWebFlux SSE。名字看着像只是依赖不同实际决定了你的服务是本地进程还是远程 HTTP 服务。我试过把同一个天气工具分别用 STDIO 和 SSE 跑一遍差异非常直观。STDIO 模式下客户端启动一个子进程通过标准输入输出传 JSON没有端口、没有网络适合本地 CLI 工具、单机脚本、IDE 插件这类场景。SSE 模式下服务端是一个真正的 Web 服务客户端通过text/event-stream长连接接收流式响应适合把工具能力暴露给多个远程调用方比如 Dify、其他微服务或者跨机器的 Agent。选型判断其实就一句话工具和调用方在同一台机器、同一进程生命周期内优先 STDIO需要跨网络、多客户端、独立部署选 SSE。但这句话背后有一堆配置细节比如 STDIO 的command和args怎么写、SSE 的端点路径和超时怎么设、客户端连不上时日志里会报什么错。这篇就按“先跑通 STDIO再跑通 SSE最后排错”的顺序把可复制的配置和验证动作都列出来。热词里提到的 Spring AI、STDIO、SSE 三个词正好对应三种依赖和两套代码结构。下面从依赖开始一步步来。2. TaoToken 前置准备与 Spring AI 依赖配置在写 MCP 代码之前需要先确认模型侧能正常调用。MCP 本身只负责“工具怎么暴露和发现”真正决定模型能不能用工具的是模型服务端是否支持 function calling / tool use。我这边习惯用 TaoToken 作为模型接入层它兼容 OpenAI 风格的接口Spring AI 的 OpenAI Starter 可以直接指向它。先拿 Key。打开https://taotoken.net/api-keys创建一个 API Key复制出来。注意这个 Key 只在创建时显示一次丢了就重新建。然后确认你要用的模型 ID比如claude-sonnet-4-20250514这类支持工具调用的模型。Base URL 用https://taotoken.net/api不要加多余路径。Spring AI 的依赖版本建议统一用 1.0.0-M6 或更高MCP 相关 Starter 在这个版本之后才比较稳定。下面是一个最小可运行的pom.xml依赖片段包含 MCP Server STDIO、MCP Client、以及 OpenAI 兼容的模型 Starterproperties spring-ai.version1.0.0-M6/spring-ai.version /properties dependencies !-- MCP ServerSTDIO 模式 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId version${spring-ai.version}/version /dependency !-- MCP Client用于连接 STDIO 或 SSE 服务 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId version${spring-ai.version}/version /dependency !-- OpenAI 兼容模型接入 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version${spring-ai.version}/version /dependency /dependencies如果你要用 SSE 模式的服务端把第一个依赖换成spring-ai-starter-mcp-server-webmvc或spring-ai-starter-mcp-server-webflux。WebMVC 适合传统 Servlet 应用WebFlux 适合响应式高并发场景。两者在 MCP 协议层面行为一致只是底层 I/O 模型不同。application.yml里模型侧配置如下Base URL 和 Key 都指向 TaoTokenspring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-sonnet-4-20250514 temperature: 0.7环境变量TAOTOKEN_API_KEY在启动前 export 进去不要硬编码在文件里。到这里模型侧和 MCP 依赖都齐了接下来写工具。3. STDIO 模式可复制配置与工具代码STDIO 的核心是“服务端作为一个可执行进程被客户端启动”。在 Spring AI 里服务端和客户端可以写在同一个项目里也可以分开。先看服务端。服务端只需要一个SpringBootApplication加一个带Tool方法的 Bean。Tool是 Spring AI 提供的注解方法参数和返回值会被自动转成 MCP 工具描述。下面是一个查询天气的工具import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Service; Service public class WeatherTools { Tool(description 根据城市名称查询当前天气返回温度和天气状况) public String getWeather(String city) { // 这里用模拟数据实际可替换为真实 API 调用 if (北京.equals(city)) { return 北京晴26℃湿度 40%; } return city 多云22℃湿度 55%; } }服务端启动类里把工具注册进去import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; SpringBootApplication public class McpStdioServerApplication { public static void main(String[] args) { SpringApplication.run(McpStdioServerApplication.class, args); } Bean public ToolCallbackProvider weatherToolProvider(WeatherTools weatherTools) { return MethodToolCallbackProvider.builder() .toolObjects(weatherTools) .build(); } }application.yml里 STDIO 服务端配置spring: ai: mcp: server: name: weather-stdio-server version: 1.0.0 stdio: true注意stdio: true表示这个进程通过标准输入输出通信不要同时开 Web 端口否则会冲突。打包成 jar 后客户端通过java -jar启动它。客户端侧配置 STDIO 连接在application.yml里写spring: ai: mcp: client: stdio: connections: weather-server: command: java args: - -jar - /path/to/mcp-stdio-server.jarcommand是启动命令args是参数列表。客户端启动时会自动拉起这个子进程通过 stdin/stdout 交换 JSON-RPC 消息。这里有个坑如果 jar 路径写错客户端不会立刻报“文件不存在”而是卡在初始化阶段日志里只有超时。所以路径建议用绝对路径并且先手动java -jar确认能跑起来。工具调用时模型返回的 tool call 会被 Spring AI 转成对子进程的请求子进程执行getWeather后把结果写回 stdout。整个过程没有网络端口适合本地开发和单机部署。4. SSE 模式服务端与客户端验证请求SSE 模式把 MCP Server 变成一个 HTTP 服务客户端通过text/event-stream接收流式响应。服务端依赖换成spring-ai-starter-mcp-server-webmvcdependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version${spring-ai.version}/version /dependency工具代码和 STDIO 完全一样WeatherTools不用改。区别在application.ymlserver: port: 8080 spring: ai: mcp: server: name: weather-sse-server version: 1.0.0 stdio: false启动后MCP 的 SSE 端点默认在/sse消息端点默认在/mcp/message。你可以先用 curl 验证服务端是否活着curl -N http://localhost:8080/sse-N表示禁用缓冲你会看到类似这样的流式输出event: endpoint data: /mcp/message?sessionIdabc123这说明 SSE 通道建立成功服务端返回了一个 sessionId。接下来客户端用这个 sessionId 发 JSON-RPC 请求。Spring AI 的 MCP Client 会自动处理这些你只需要在客户端application.yml里配置 SSE 连接spring: ai: mcp: client: sse: connections: weather-sse: url: http://localhost:8080客户端启动后会先请求/sse拿到 sessionId再通过/mcp/message发送tools/list和tools/call。验证工具是否被发现可以在客户端写一个测试import org.springframework.ai.mcp.SyncMcpClient; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; Component public class McpSseTestRunner implements CommandLineRunner { private final SyncMcpClient mcpClient; public McpSseTestRunner(SyncMcpClient mcpClient) { this.mcpClient mcpClient; } Override public void run(String... args) { var tools mcpClient.listTools(); System.out.println(发现工具数量 tools.size()); tools.forEach(t - System.out.println(工具名 t.name())); var result mcpClient.callTool(getWeather, java.util.Map.of(city, 北京)); System.out.println(调用结果 result); } }运行后终端应该输出工具列表和“北京晴26℃”。如果listTools返回空说明客户端没连上 SSE 端点先检查端口和路径。SSE 模式的好处是服务端可以独立部署多个客户端同时连接每个连接有独立 sessionId。缺点是长连接会占用线程或连接资源WebFlux 版本用非阻塞 I/O 能缓解但配置复杂度更高。5. 本篇常见错排查401、local proxy failed、reading choices排错部分按真实报错来。第一个高频错误是模型侧 401401 Unauthorized: Incorrect API key provided这个和 MCP 无关是 TaoToken 的 Key 没配好。检查TAOTOKEN_API_KEY环境变量是否 export 成功base-url是否是https://taotoken.net/api不要多写/v1。如果 Key 刚创建确认没有多余空格。第二个错误是 STDIO 客户端启动子进程失败local proxy failed: Cannot run program java: No such file or directory这说明command里的java不在 PATH 里。解决办法是写绝对路径比如/usr/lib/jvm/java-17/bin/java。Windows 下写java.exe的完整路径。另外args里的 jar 路径也要绝对路径相对路径会以客户端工作目录为基准容易找不到。第三个错误出现在模型调用工具后解析响应时Error reading choices: Cannot deserialize value of type java.util.ArrayList from Object value这通常是模型返回的 tool call 格式和 Spring AI 期望的不一致。检查模型 ID 是否支持 function calling有些模型虽然能对话但不支持工具调用。换成claude-sonnet-4-20250514这类明确支持 tool use 的模型再试。如果还报错把spring.ai.openai.chat.options.temperature调低到 0.2减少模型输出格式抖动。第四个错误是 SSE 连接建立后工具调用超时MCP client request timeout after 30000msSSE 模式下服务端处理工具调用如果超过客户端超时时间就会断。检查application.yml里有没有配spring.ai.mcp.client.request-timeout默认 30 秒。如果工具本身耗时调大到 60000。另外确认服务端/mcp/message端点没有被 Spring Security 拦截拦截了会返回 403客户端日志里会看到OAuth或unauthorized相关提示。如果你用的是 Claude Code 或 Cline 这类工具连接 MCP Server配置里需要同时写全三件套Base URL、Key、Model ID。Base URL 用https://taotoken.net/apiKey 用 TaoToken 创建的 KeyModel ID 用支持工具调用的模型。缺任何一个都会在初始化阶段失败。6. 从 STDIO 到 SSE 的选型与接入路径跑通两种模式后选型其实看三个维度部署位置、客户端数量、生命周期。STDIO 适合工具和调用方在同一台机器客户端数量少进程随客户端启动和退出。SSE 适合工具独立部署多个客户端共享服务端需要长期运行。实际项目里我倾向于先用 STDIO 把工具逻辑跑通因为调试简单日志直接打在终端。确认工具描述和参数没问题后再换成 SSE 依赖把同一套Tool代码暴露成 HTTP 服务。这样切换成本很低只需要改依赖和application.ymlJava 代码不动。如果你要把 MCP 能力接到长期运行的编码 Agent 或自动化流程里建议用 SSE 模式配合 Coding Plan服务端独立部署客户端按需连接。模型侧继续用 TaoToken 的接口Base URL 保持https://taotoken.net/apiKey 从https://taotoken.net/api-keys创建。接入文档在https://taotoken.net/doc里面有不同语言的调用示例。最后留一个实用技巧STDIO 模式下子进程的 stderr 默认不会显示在客户端控制台调试时可以在服务端启动类里加一行System.setErr(System.out)把错误输出重定向到 stdout这样客户端日志里就能看到工具内部的异常堆栈。SSE 模式下直接在服务端看日志即可每个 sessionId 对应一条调用链排查起来更清晰。