资讯详情

搭建基于 Solon AI 的 Streamable MCP 服务并部署至阿里云百炼:TaoToken 统一 Key 接入实践

📅 2026/10/5 18:05:54 | 华诺云谱 👁 阅读
搭建基于 Solon AI 的 Streamable MCP 服务并部署至阿里云百炼:TaoToken 统一 Key 接入实践
1. 为什么要在 Solon AI 里折腾 Streamable MCP如果你正在用 Java 写 AI 应用大概率会遇到一个很现实的问题模型调用、工具调用、流式输出这三件事代码里各写各的最后 Key 和 Base URL 散落在四五个配置文件里。我最近在做一个天气查询的 MCP 工具服务用 Solon AI 搭 Streamable MCP部署到阿里云百炼中间就踩了「多模型 Key 管理」这个坑。先说清楚这套东西是什么。MCP 是 Model Context Protocol你可以把它理解成「大模型和外部工具之间的 USB 接口」——模型不直接调你的数据库或 API而是通过 MCP 协议描述「我有哪些工具、参数是什么」模型按协议发起调用。Streamable MCP 则是在这个基础上支持流式返回适合天气查询、长文本生成、逐步推理这类「结果分多次吐出来」的场景。Solon AI 是国产轻量级 Java 框架 Solon 的 AI 扩展模块启动快、依赖少写 MCP 服务端不用背 Spring 那一大坨。阿里云百炼则是模型服务平台支持把外部 MCP 服务挂上去让百炼上的模型能调用你的工具。这套组合适合谁适合已经有 Java 后端基础、想给自己的 AI 应用加「工具调用能力」的开发者也适合团队里模型 Key 管理混乱、想统一收口的场景。我试过把 OpenAI、Claude、通义千问的 Key 分别写在三个 properties 文件里改一次配置要翻三个地方后来用 TaoToken 统一 Key 和 Base URL才算把这件事理顺。这篇文章会从零走一遍建 Solon AI 项目、写 Streamable MCP 工具、声明 SSE 端点、配置 TaoToken 统一通道、部署到阿里云百炼、最后用 curl 验证流式返回。每一步都给可复制的代码和配置你跟着敲就能跑起来。2. TaoToken 统一 Key 接入把分散的 Base URL 收口在讲代码之前先把「Key 管理」这件事说透。MCP 服务端本身不直接调模型但你的 MCP 工具内部如果要调模型比如天气工具里做一次自然语言总结就需要模型 API 的 Key 和 Base URL。传统做法是每个工具类里读环境变量或者写死在配置文件项目一大就乱。TaoToken 的思路是提供一个统一的 API 通道你只需要记一个 Base URL 和一个 Key模型 ID 在请求里指定。这样 MCP 服务端、本地调试脚本、百炼侧的模型调用都能用同一套凭证。先拿 Key。访问 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 进去后左侧菜单找「API Keys」点新建复制那串 sk- 开头的字符串。这个 Key 只显示一次先存到密码管理器里。Base URL 统一用 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 OpenAI 兼容接口的 base_url 使用。模型 ID 按你实际要调的填比如 claude-sonnet-4-5、gpt-4o、qwen-max 这些具体以文档为准。文档入口在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各模型的 Model ID 对照表。为什么要在 MCP 项目里用统一通道因为 MCP 服务端往往要同时支持多个模型——百炼侧可能用通义本地调试可能用 Claude如果每个模型一套 Key 和 Base URL配置会爆炸。统一之后你的 application.yml 里只有两个变量TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL换模型只改 Model ID。这里给一个 Solon 的配置片段放在src/main/resources/app.ymltaotoken: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} default-model: claude-sonnet-4-5 timeout: 60000 server: port: 8080 mcp: server: name: WeatherMCP version: 1.0.0 sse-path: /mcp/sse注意api-key用${TAOTOKEN_API_KEY}占位实际值通过环境变量注入不要硬编码进 Git。本地调试时在 IDE 的运行配置里加环境变量或者用.env文件配合 Solon 的配置加载。如果你用的是 Claude Code 这类编码工具TaoToken 也支持通过 Anthropic 兼容接口接入配置入口在 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 里面给了 Base URL 和 Key 的填法。不过本文重点是 MCP 服务端编码工具那块先不展开。统一 Key 之后MCP 工具内部调模型的代码就变得很干净——不管底层是哪个模型都是同一个 client、同一个 base_url只换 model 参数。这是后面所有步骤的基础先把这步做扎实。3. 可复制配置Solon AI 项目结构与 MCP 服务端代码这一节给完整的项目骨架和可复制代码。我用 Maven 建项目JDK 17Solon 2.8.x。先看pom.xml的核心依赖dependencies dependency groupIdorg.noear/groupId artifactIdsolon-ai/artifactId version2.8.1/version /dependency dependency groupIdorg.noear/groupId artifactIdsolon-ai-mcp/artifactId version2.8.1/version /dependency dependency groupIdorg.noear/groupId artifactIdsolon-ai-stream/artifactId version2.8.1/version /dependency dependency groupIdorg.noear/groupId artifactIdsolon-web/artifactId version2.8.1/version /dependency dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.13/version /dependency /dependenciesSolon 的依赖很轻没有 Spring 那一堆 starter。solon-ai-mcp提供 MCP 协议支持solon-ai-stream提供流式发射器。接下来是工具类。MCP 工具的本质是「一个带注解的方法」Solon AI 通过Tool和ToolParam把方法暴露成 MCP 工具。下面这个天气工具支持流式返回逐步吐出城市、温度、湿度、风速package com.example.mcp.tool; import org.noear.solon.ai.tool.ToolProvider; import org.noear.solon.ai.tool.annotation.Tool; import org.noear.solon.ai.tool.annotation.ToolParam; import org.noear.solon.ai.stream.StreamEmitter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; ToolProvider public class WeatherTool { private static final Logger log LoggerFactory.getLogger(WeatherTool.class); Tool(name get_weather_stream, description 获取指定城市的天气信息支持流式返回) public void getWeather( ToolParam(description 城市名称如杭州) String city, StreamEmitter emitter) { log.info(开始查询城市天气: {}, city); try { emitter.emit({\type\:\city\,\data\:\ city \}\n); Thread.sleep(300); double temp 25.0 Math.random() * 10; emitter.emit({\type\:\temperature\,\data\:\ String.format(%.1f, temp) °C\}\n); Thread.sleep(300); int humidity 40 (int) (Math.random() * 40); emitter.emit({\type\:\humidity\,\data\:\ humidity %\}\n); Thread.sleep(200); double windSpeed 3.0 Math.random() * 5; emitter.emit({\type\:\wind_speed\,\data\:\ String.format(%.1f, windSpeed) m/s\}\n); emitter.complete(); log.info(天气查询完成: {}, city); } catch (InterruptedException e) { Thread.currentThread().interrupt(); emitter.error(new RuntimeException(查询中断)); } } }关键点是StreamEmitter参数——Solon AI 会自动注入你只管emit和complete。每个emit就是一条 SSE 消息客户端会实时收到。然后是启动类声明 SSE 端点并注册工具package com.example.mcp; import com.example.mcp.tool.WeatherTool; import org.noear.solon.Solon; import org.noear.solon.annotation.Mapping; import org.noear.solon.ai.mcp.server.McpServer; import org.noear.solon.ai.mcp.server.McpServerConfig; import org.noear.solon.ai.mcp.server.transport.SseMcpTransport; public class MCPApplication { public static void main(String[] args) { Solon.start(MCPApplication.class, args, app - { app.context().beanMake(WeatherTool.class); McpServerConfig config new McpServerConfig(); config.setName(WeatherMCP); config.setVersion(1.0.0); config.setTransport(new SseMcpTransport(/mcp/sse)); McpServer server new McpServer(config); server.start(); System.out.println( Streamable MCP 服务已启动 ); System.out.println(SSE 端点: http://localhost:8080/mcp/sse); }); } Mapping(/health) public String health() { return {\status\:\UP\,\service\:\WeatherMCP\}; } }SseMcpTransport(/mcp/sse)这行声明了 SSE 端点路径客户端连这个地址就能建立流式通道。/health是给容器健康检查用的后面部署到百炼会用到。如果你要在工具内部调模型比如把天气数据做一次自然语言总结用 TaoToken 统一通道的 client 配置如下import org.noear.solon.ai.chat.ChatConfig; import org.noear.solon.ai.chat.ChatModel; ChatConfig chatConfig new ChatConfig(); chatConfig.setApiUrl(https://taotoken.net/api); chatConfig.setApiKey(System.getenv(TAOTOKEN_API_KEY)); chatConfig.setModel(claude-sonnet-4-5); ChatModel chatModel new ChatModel(chatConfig);这样不管底层换哪个模型只改setModel的参数Base URL 和 Key 不动。这就是统一通道的价值。4. 验证请求本地启动与 SSE 流式调用实测代码写完先本地跑起来验证。用 Maven 打包再运行mvn clean package -DskipTests java -jar target/mcp-weather-service-1.0.0.jar启动后控制台会打印 SSE 端点地址。先测健康检查curl http://localhost:8080/health预期返回{status:UP,service:WeatherMCP}。这一步确认服务起来了。然后测 MCP 的 SSE 端点。MCP 协议走 JSON-RPC 2.0调用工具用tools/call方法。注意 SSE 请求要带Accept: text/event-stream头curl -N -X POST http://localhost:8080/mcp/sse \ -H Content-Type: application/json \ -H Accept: text/event-stream \ -d { jsonrpc: 2.0, id: 1, method: tools/call, params: { name: get_weather_stream, arguments: { city: 杭州 } } }-N参数关闭 curl 的缓冲这样你能看到流式效果。预期输出是逐条到达的data: {type:city,data:杭州} data: {type:temperature,data:28.3°C} data: {type:humidity,data:65%} data: {type:wind_speed,data:4.2m/s}如果你看到这四条是「一条一条蹦出来」而不是一次性全出来说明流式通道通了。这是 Streamable MCP 的核心验证点。再测一下工具列表确认 MCP 协议层正常curl -N -X POST http://localhost:8080/mcp/sse \ -H Content-Type: application/json \ -H Accept: text/event-stream \ -d {jsonrpc:2.0,id:2,method:tools/list,params:{}}预期返回里能看到get_weather_stream这个工具及其参数描述。这一步过了说明 MCP 服务端完全就绪。本地验证通过后做容器化。Dockerfile 很简单FROM openjdk:17-jdk-slim WORKDIR /app COPY target/mcp-weather-service-1.0.0.jar app.jar EXPOSE 8080 ENV TAOTOKEN_API_KEY ENTRYPOINT [java, -jar, app.jar]构建并本地跑容器验证docker build -t mcp-weather:1.0.0 . docker run -p 8080:8080 -e TAOTOKEN_API_KEY你的Key mcp-weather:1.0.0容器起来后再跑一遍上面的 curl确认环境变量注入后服务正常。这一步过了就可以推镜像到阿里云容器镜像服务准备部署到百炼。5. 常见报错排查401、SSE 断流与百炼接入失败这一节列几个我实际踩过的坑都是真实报错对照着查。报错一401 Unauthorized返回{error:{message:invalid api key}}这个最常见原因是 TaoToken 的 Key 没注入或写错。检查三处环境变量TAOTOKEN_API_KEY是否设置、代码里读的是不是这个变量名、Key 有没有多余空格。如果你在百炼侧配置了模型调用百炼的环境变量也要单独设一遍容器里的环境变量不会自动同步到百炼平台。报错二local proxy failed或连接超时这个通常出现在容器内访问外部 API 时。检查容器的网络出口是否正常以及 Base URL 是否写成了https://taotoken.net/api注意结尾没有斜杠有些 HTTP client 对结尾斜杠敏感。如果你在百炼侧看到这个错还要确认百炼的出网策略允许访问外部域名。报错三reading choices相关错误或流式响应解析失败这个多半是模型返回格式和客户端预期不一致。如果你在 MCP 工具内部调模型确认用的是 OpenAI 兼容格式TaoToken 的/api端点返回的就是标准 OpenAI 格式。检查你的解析代码是不是按choices[0].delta.content取的。另外确认 Model ID 拼写正确写错模型名有时会返回非预期格式。报错四SSE 连接建立后立即断开或收不到 data检查 curl 有没有加-N以及请求头有没有Accept: text/event-stream。MCP 的 SSE 端点对 Accept 头有要求缺了会走普通 HTTP 响应而不是流。另外确认SseMcpTransport的路径和请求路径一致大小写敏感。报错五百炼侧 OAuth 或鉴权失败百炼接入外部 MCP 服务时如果配了鉴权要确认 token 传递方式。MCP 协议支持在请求头里带 Authorization你的服务端要能解析。如果百炼报 OAuth 相关错误先确认你的 MCP 服务是否要求鉴权——本地调试阶段可以先不鉴权跑通链路后再加。报错六Codex auth.json 或 Claude Code 配置不生效如果你同时用 Codex 或 Claude Code 调这套服务注意它们的配置文件路径不同。Codex 用~/.codex/auth.jsonClaude Code 用环境变量或 settings。三件套要写全Base URL 填https://taotoken.net/apiKey 填你的 sk- 串Model ID 填具体模型名。缺任何一个都会报鉴权或模型不存在。排查顺序建议先本地 curl 通再容器 curl 通最后百炼侧通。每层都通了再往上走不要跳步。6. 部署到阿里云百炼与后续接入本地和容器都验证通过后推镜像到阿里云容器镜像服务docker tag mcp-weather:1.0.0 registry.cn-hangzhou.aliyuncs.com/你的命名空间/mcp-weather:1.0.0 docker push registry.cn-hangzhou.aliyuncs.com/你的命名空间/mcp-weather:1.0.0然后在百炼控制台创建 MCP 服务选容器部署填镜像地址环境变量里加上TAOTOKEN_API_KEY健康检查路径填/health。百炼要求 HTTPS所以绑定自定义域名后要配证书。百炼侧的 MCP 接入配置长这样{ mcp_servers: { weather_service: { url: https://你的域名/mcp/sse, type: sse, capabilities: { streaming: true } } } }配好后在百炼的模型服务里绑定这个 MCP模型就能调用get_weather_stream工具了。验证方式是百炼控制台的调试窗口发一句「杭州天气怎么样」看模型是否触发工具调用并流式返回结果。如果你要长期跑编码类 Agent 或需要稳定的模型调用配额可以看下 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcodingplanutm_campaignrewrite 适合高频调用的场景。单纯验证模型效果的话用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodelchatutm_campaignrewrite 更快不用写代码就能试。最后说个实用技巧MCP 工具的描述文字description字段会直接影响模型是否愿意调用这个工具。描述写得太模糊模型可能忽略写清楚「什么时候用、返回什么」调用率会明显提升。我一开始把天气工具描述写成「查询天气」模型经常不调改成「获取指定城市的实时天气包括温度、湿度、风速适合回答天气相关问题」之后调用就稳定了。这个细节比代码本身更影响实际效果。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑