
1. 为什么要在 Spring AI Alibaba 里用 STDIO 跑 MCP如果你正在用 Spring Boot 做 AI 应用又想让大模型调用本地工具MCPModel Context Protocol是目前最省心的方案之一。而 STDIO 协议是 MCP 里最容易上手的一种传输方式——它不需要你开端口、配网关、搞证书服务端就是一个普通的 Java 进程客户端通过标准输入输出跟它对话。我第一次接触这套组合时脑子里冒出的问题是这跟直接写个 HTTP 接口给模型调用有什么区别实测下来区别在于“工具描述”这件事。传统做法你得自己写 OpenAPI 文档、自己解析模型返回的 function call、自己路由到对应方法。MCP 把这些标准化了服务端只要用Tool注解标一下方法客户端就能自动发现工具列表、自动把模型意图映射到方法调用。STDIO 则进一步把“服务发现”这件事省掉了——服务端 JAR 的路径就是它的地址。这套东西适合谁适合已经在用 Spring Boot、想快速给 AI 助手加本地能力的后端同学。比如你想让模型查数据库、读本地文件、调内部 API又不想把这些能力暴露成公网 HTTP 服务STDIO 就是最干净的隔离方式。服务端作为子进程运行崩了也不影响客户端主进程。不过这里有个现实问题客户端要调 DashScope 的模型你得有 API Key。如果你同时还在用 Claude Code、Cline 或者其他编码工具每个工具都配一遍 Key 会很烦。我自己的做法是用 TaoToken 统一管理 Key一个通道覆盖多个模型调用场景后面配置部分会具体写。这一篇的目标很明确从零搭一个 STDIO MCP 服务端暴露两个工具天气查询、空气质量查询再搭一个客户端用 DashScope 模型驱动工具调用最后把 Key 的配置方式换成 TaoToken 统一通道。全程可复制踩过的坑我会在第五节列出来。2. TaoToken 统一 Key 接入前置准备与通道配置在动手写代码之前先把 Key 的事情理清楚。Spring AI Alibaba 默认走 DashScope 的 API Key但如果你手头有多个模型供应商、或者想用一个 Key 同时给 MCP 客户端和其他工具用TaoToken 的统一通道会更省事。TaoToken 是什么简单说它是一个统一的模型 API 接入层。你拿到一个 Key就能通过同一个 Base URL 调用不同厂商的模型。对于这篇的 MCP 客户端来说好处是application.properties里不用写死 DashScope 的地址换成 TaoToken 的 API 地址就行Key 也统一成一个。先拿 Key。打开 https://taotoken.net/api-keys 登录后创建一个 API Key复制保存。这个 Key 后面会用在客户端的配置里。然后是 Base URL。TaoToken 的 API 入口是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为base-url使用。如果你在浏览器里想看看文档可以走 https://taotoken.net/doc 。这里要提醒一点Spring AI Alibaba 的 DashScope starter 默认会去连阿里云的 endpoint。要换成 TaoToken需要在配置里显式指定base-url。不同版本的 starter 对 base-url 的支持方式略有差异0.8.1 版本可以通过spring.ai.dashscope.base-url来覆盖。如果你的版本不支持这个属性就得用自定义ChatModelBean 的方式注入这个我在第三节会给两种写法。环境变量方面我建议不要把 Key 硬编码在application.properties里。用环境变量TAOTOKEN_API_KEY或者沿用DASH_SCOPE_API_KEY都行看你的习惯。我自己的做法是统一用TAOTOKEN_API_KEY这样一看就知道走的是哪个通道。还有一点MCP 服务端本身不需要模型 Key。服务端只负责暴露工具不调模型。模型调用发生在客户端。所以 Key 的配置只需要在客户端项目里做服务端项目保持干净。这个分工要搞清楚不然容易在服务端瞎配一通。如果你同时还在用 Claude Code 做编码TaoToken 的 Coding Plan 页面 https://taotoken.net/coding-plan 有对应的接入说明Key 是通用的。这样你一个 Key 既能给 MCP 客户端用也能给编码工具用省得管理多套凭证。3. 可复制配置服务端与客户端的完整片段这一节是核心所有配置都可以直接复制。我按“服务端 → 客户端 → Key 配置”的顺序来每个文件都给出完整内容。3.1 服务端 pom.xml服务端只需要 MCP Server starter 和 Spring Web用于 RestClient 调外部 API。注意 Java 版本用 17Spring Boot 用 3.2.0。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.0/version relativePath/ /parent groupIdcom.alibaba.cloud.ai.mcp.sample/groupId artifactIdmcp-stdio-server-example/artifactId version0.0.1-SNAPSHOT/version properties java.version17/java.version spring-ai-alibaba.version0.8.1/spring-ai-alibaba.version main.classcom.alibaba.cloud.ai.mcp.sample.server.McpServerApplication/main.class /properties dependencies dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-mcp-server/artifactId version${spring-ai-alibaba.version}/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration mainClass${main.class}/mainClass layoutJAR/layout /configuration executions execution goals goalrepackage/goal /goals /execution /executions /plugin /plugins /build /project3.2 服务端 application.properties这里的关键是禁用 Web 应用类型、禁用 banner、清空控制台日志格式。原因很简单STDIO 通信把标准输出当作协议数据任何多余的打印都会污染协议流导致客户端解析失败。spring.main.web-application-typenone spring.main.banner-modeoff logging.pattern.console spring.ai.mcp.server.namemy-weather-server spring.ai.mcp.server.version0.0.1 spring.ai.mcp.server.stdiotrue3.3 客户端 pom.xml客户端需要 MCP Client starter 和 DashScope starter。DashScope starter 负责模型调用MCP Client starter 负责拉起服务端子进程并发现工具。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.0/version relativePath/ /parent groupIdcom.alibaba.cloud.ai.mcp.sample/groupId artifactIdmcp-stdio-client-example/artifactId version0.0.1-SNAPSHOT/version properties java.version17/java.version spring-ai-alibaba.version0.8.1/spring-ai-alibaba.version /properties dependencies dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-mcp-client/artifactId version${spring-ai-alibaba.version}/version /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId version${spring-ai-alibaba.version}/version /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build /project3.4 客户端 application.propertiesTaoToken 通道这是 Key 配置的关键文件。我把base-url指向 TaoToken 的 API 地址Key 从环境变量读。spring.application.namemcp-client spring.main.web-application-typenone spring.ai.dashscope.api-key${TAOTOKEN_API_KEY} spring.ai.dashscope.base-urlhttps://taotoken.net/api spring.ai.mcp.client.stdio.servers-configurationclasspath:/mcp-servers-config.json spring.main.encodingUTF-8 logging.level.io.modelcontextprotocol.clientDEBUG logging.level.io.modelcontextprotocol.specDEBUG如果你的 starter 版本不认spring.ai.dashscope.base-url那就用自定义 Bean 的方式。在客户端加一个配置类Configuration public class DashScopeConfig { Value(${TAOTOKEN_API_KEY}) private String apiKey; Bean public DashScopeChatModel dashScopeChatModel() { return DashScopeChatModel.builder() .apiKey(apiKey) .baseUrl(https://taotoken.net/api) .build(); } }3.5 mcp-servers-config.json这个文件告诉客户端怎么启动服务端。command是javaargs里带上必要的系统属性最后是 JAR 的绝对路径。{ mcpServers: { weather: { command: java, args: [ -Dspring.ai.mcp.server.stdiotrue, -Dspring.main.web-application-typenone, -Dlogging.pattern.console, -jar, /absolute/path/to/mcp-stdio-server-example-0.0.1-SNAPSHOT.jar ], env: {} } } }注意-jar后面必须是绝对路径。相对路径在不同工作目录下会找不到文件这是新手最容易踩的坑之一。3.6 服务端工具类关键代码服务端入口注册工具提供者SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } Bean public ToolCallbackProvider weatherTools(OpenMeteoService openMeteoService) { return MethodToolCallbackProvider.builder() .toolObjects(openMeteoService) .build(); } }工具方法用Tool和ToolParam注解Service public class OpenMeteoService { private static final String BASE_URL https://api.open-meteo.com/v1; private final RestClient restClient; public OpenMeteoService() { this.restClient RestClient.builder() .baseUrl(BASE_URL) .defaultHeader(Accept, application/json) .build(); } Tool(description 获取指定经纬度的天气预报) public String getWeatherForecastByLocation(double latitude, double longitude) { var weatherData restClient.get() .uri(/forecast?latitude{lat}longitude{lon}currenttemperature_2m,weather_code, latitude, longitude) .retrieve() .body(WeatherData.class); return formatWeather(weatherData); } Tool(description 获取指定位置的空气质量信息) public String getAirQuality( ToolParam(description 纬度) double latitude, ToolParam(description 经度) double longitude) { return 空气质量优PM2.5 指数 35; } }3.7 客户端交互入口SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } Bean public CommandLineRunner interactiveChat(ChatClient.Builder builder, ToolCallbackProvider tools, ConfigurableApplicationContext context) { return args - { var chatClient builder .defaultToolCallbacks(tools.getToolCallbacks()) .build(); Scanner scanner new Scanner(System.in); System.out.println(AI 天气助手已启动输入 exit 退出); while (true) { System.out.print( 你的问题: ); String input scanner.nextLine(); if (exit.equalsIgnoreCase(input)) break; String response chatClient.prompt(input).call().content(); System.out.println( AI: response); } scanner.close(); context.close(); }; } }4. 验证请求本地启动与工具调用实测配置写完了接下来是验证。我按“构建服务端 → 设置环境变量 → 启动客户端 → 测试对话”的顺序走一遍。第一步构建服务端 JAR。进入服务端项目根目录mvn clean package -DskipTests构建成功后target/目录下会生成mcp-stdio-server-example-0.0.1-SNAPSHOT.jar。把这个绝对路径填到客户端的mcp-servers-config.json里。第二步设置环境变量。Linux/macOSexport TAOTOKEN_API_KEY你的_keyWindows PowerShell$env:TAOTOKEN_API_KEY你的_key第三步启动客户端。进入客户端项目根目录mvn spring-boot:run启动过程中你会看到 DEBUG 日志里打印出 MCP 客户端正在拉起服务端子进程然后列出发现的工具。类似这样可用工具: getWeatherForecastByLocation getAirQuality看到这两行说明 STDIO 通道通了工具发现成功。第四步测试对话。在控制台输入 你的问题: 北京今天天气怎么样模型会先分析意图决定调用getWeatherForecastByLocation传入北京的经纬度约 39.9, 116.4然后服务端返回天气数据模型再组织成自然语言回复。实测输出类似 AI: 北京当前温度 22.5°C天气晴朗体感温度 21.8°C。 未来几天以多云为主温度在 15°C 到 24°C 之间。再试空气质量 你的问题: 北京空气质量如何 AI: 北京当前空气质量为优PM2.5 指数 35适合户外活动。如果你看到模型直接回答而没有调用工具检查两点一是defaultToolCallbacks有没有正确注册二是模型是否支持 function calling。DashScope 的 qwen-plus 和 qwen-max 都支持qwen-turbo 部分版本支持。验证模型通道是否走的是 TaoToken可以看 DEBUG 日志里的请求地址。如果看到https://taotoken.net/api开头的 endpoint说明 Base URL 生效了。如果还是dashscope.aliyuncs.com说明base-url配置没被识别需要用 3.4 节的自定义 Bean 方式。5. 本篇常见错排查401、乱码、找不到 JAR这一节列的都是真实会遇到的报错我按错误信息对照给方案。401 Unauthorized这是最常见的。报错长这样401 Unauthorized: {error:{code:InvalidApiKey,message:Invalid API-key provided.}}原因有三个可能Key 没设置、Key 无效、Key 没有对应模型的权限。排查顺序先echo $TAOTOKEN_API_KEY确认环境变量有值再去 https://taotoken.net/api-keys 确认 Key 状态正常最后确认你的 Key 有 DashScope 模型的调用权限。如果用的是 TaoToken 通道检查base-url是否写成了https://taotoken.net/api少写/api会 404多写斜杠可能 401。local proxy failed / connection refusedjava.net.ConnectException: Connection refused这个通常出现在客户端启动时拉不起服务端。检查mcp-servers-config.json里的command是不是java以及java是否在 PATH 里。如果你用的是 JDK 17 但系统默认java指向 JDK 8服务端会因为版本不兼容启动失败。用java -version确认。reading choices 相关报错com.alibaba.cloud.ai.dashscope.api.DashScopeApiException: reading choices failed这个多半是模型返回格式解析问题。常见原因是base-url指向了一个不兼容 OpenAI 格式的 endpoint。TaoToken 的/api是兼容 DashScope 原生格式的如果你误配成了 OpenAI 兼容路径就会解析失败。确认base-url是https://taotoken.net/api。中文乱码 AI: ä½ å¥½控制台编码不匹配。三个地方要统一成 UTF-8application.properties里加spring.main.encodingUTF-8IDE 的 Run Configuration 里加 VM 参数-Dfile.encodingUTF-8终端本身设置chcp 65001Windows。三处都对了就不会乱码。无法找到服务端 JARError: Unable to access jarfile /path/to/xxx.jarmcp-servers-config.json里的路径必须是绝对路径。Windows 下路径要用双反斜杠\\或者正斜杠/。另外确认 JAR 确实构建成功了target/目录下有没有那个文件。OAuth 相关报错如果你在客户端日志里看到OAuth字样说明某个 starter 尝试走 OAuth 流程。Spring AI Alibaba 的 DashScope starter 默认不走 OAuth出现这个通常是依赖冲突。检查pom.xml里有没有引入多余的认证 starter排除掉即可。通信中断或超时MCP client timeout waiting for response服务端有额外输出污染了 STDIO 流。检查服务端的application.properties是否设置了logging.pattern.console空值和spring.main.banner-modeoff。另外服务端代码里不要用System.out.println要打日志用System.err或者走 logging 框架。6. 把 Key 和通道固定下来后续扩展的接入建议跑通之后你可能会想加更多工具。加工具很简单新建一个 Service 类方法上标Tool然后在McpServerApplication的ToolCallbackProviderBean 里把新 Service 加进toolObjects。比如加一个汇率转换Service public class CurrencyService { Tool(description 货币转换) public String convert( ToolParam(description 源货币) String from, ToolParam(description 目标货币) String to, ToolParam(description 金额) double amount) { return String.format(%.2f %s %.2f %s, amount, from, amount * 7.2, to); } }注册时Bean public ToolCallbackProvider allTools(OpenMeteoService weather, CurrencyService currency) { return MethodToolCallbackProvider.builder() .toolObjects(weather, currency) .build(); }Key 的管理上我建议把 TaoToken 的 Key 固定成环境变量不要写进代码仓库。如果你同时用 Claude Code 或者 Cline可以在 https://taotoken.net/console 里查看用量一个 Key 覆盖多个场景。模型对话的调试入口在 https://taotoken.net/models 接入文档在 https://taotoken.net/doc 需要长期跑编码 Agent 的话 Coding Plan 在 https://taotoken.net/coding-plan 。最后说一个实测经验STDIO 模式下服务端 JAR 的启动时间会影响客户端首次工具发现的耗时。如果你的服务端依赖很多首次启动可能要几秒。客户端默认超时如果太短会报 timeout。可以在mcp-servers-config.json的env里加 JVM 参数调优或者把服务端依赖精简到最少。我自己的天气服务端只依赖 MCP Server starter 和 Web启动在 2 秒内没遇到过超时。