ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring AI 实现 MCP 服务(SSE 模式):TaoToken 统一 Key 接入与配置骨架

Spring AI 实现 MCP 服务(SSE 模式):TaoToken 统一 Key 接入与配置骨架 1. 为什么 Spring AI 的 MCP 服务端一上来就踩坑如果你正在用 Spring AI 搭 MCP 服务大概率会遇到一个很反直觉的问题依赖加对了代码也写了但客户端怎么都连不上。我第一次做的时候spring-ai-starter-mcp-server引进去启动日志干干净净结果 Cherry Studio 里配 SSE 地址一直转圈。后来翻文档才发现这个 starter 只支持 STDIO 传输压根不给你开 HTTP 端口。MCPModel Context Protocol本质上是给大模型装外挂工具的协议让模型能调用你写的 Java 方法比如查数据库、搜图片、调内部接口。SSE 模式的价值在于服务端跑成一个 HTTP 服务客户端通过text/event-stream长连接接收消息再通过一个 POST 端点回传调用请求。相比 STDIO 那种进程内管道SSE 更适合本地多客户端联调、容器化部署、以及和远程模型服务配合。这篇要解决的就是这条链路Spring AI 通过 SSE 模式暴露 MCP 服务同时用 TaoToken 统一 Key 和 API 通道完成模型侧接入。适合谁正在做本地开发联调、想让 Claude Code / Cline / Cherry Studio 这类客户端连上自己 Java 工具服务的后端同学。全文给的是可复制的application.yml、依赖坐标、工具类骨架以及一次真实的 SSE 连接 工具调用验证动作。你照着敲能跑通。先说清楚一个关键点MCP 服务端本身不调模型它只是把工具暴露出去。真正需要模型能力的地方比如让模型决定调哪个工具、或者你的工具内部要调大模型做语义处理才需要接模型 API。TaoToken 在这里的角色就是统一 Key 统一 Base URL你不用为每个模型厂商单独维护一套密钥和地址一个 Key 走通对话、编码、Agent 场景。我实测下来最容易卡住的三个地方依赖选错STDIO vs WebMVC、SSE 端点路径和客户端配置对不上、以及工具回调没注册成ToolCallbackProvider。下面逐个拆。2. TaoToken 前置准备统一 Key 与 API 通道在写 MCP 服务之前先把模型侧的通道准备好。这一步不做后面工具里想调模型就只能硬编码各家 Key维护起来很痛苦。TaoToken 的定位是统一的模型 API 接入层。你注册后拿到一个 API Key配一个 Base URL就能在 Spring AI、Claude Code、Cline、Codex 这些不同客户端里复用同一套凭证。对 MCP 场景特别有用因为你的工具服务可能同时被多个客户端调用统一 Key 能省掉大量配置同步的麻烦。具体操作路径打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号进控制台。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。在控制台里找到 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 创建一个新 Key复制保存。这个 Key 就是后面所有配置里api-key字段的值。Base URL 统一用https://taotoken.net/api注意这个地址不带任何查询参数直接填就行。模型 ID 按你实际要用的填比如对话场景常用的claude-sonnet-4-5、gpt-4o之类具体以控制台模型列表为准。这里有个三件套的概念要记牢后面无论配 Spring AI、Claude Code 还是 Codex都是这三样配置项值说明Base URLhttps://taotoken.net/api统一入口不带 UTMAPI Key控制台生成的sk-...一个 Key 多端复用Model ID如claude-sonnet-4-5按控制台列表填如果你只是想先验证模型通道通不通可以直接用模型对话页面测一下https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。发一句话看有没有正常返回通了再往下写代码能省掉到底是模型问题还是 MCP 问题的排查时间。长期做编码和 Agent 联调的话Coding Plan 会更划算地址在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到参数不确定就翻这个。注意Key 不要提交到 Git本地用环境变量或者application-local.yml隔离。MCP 服务经常要给别人联调Key 泄露风险比单机项目高。3. 可复制配置application.yml 与 MCP SSE 服务端骨架这一节是全文核心直接给能跑的配置和代码。3.1 依赖坐标别选错 starter这是第一个大坑。spring-ai-starter-mcp-server只支持 STDIO你要 SSE 必须换成spring-ai-starter-mcp-server-webmvc。两个都引会冲突选一个。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version1.0.3/version /dependency这个 starter 同时支持 SSE 和可选 STDIO通过spring.ai.mcp.server.stdiotrue开启。我们做 SSE所以 stdio 保持 false。3.2 application.yml 完整配置下面这份配置可以直接复制路径和字段名跟 Spring AI 1.0.3 对齐server: port: 8080 spring: ai: mcp: server: stdio: false name: image-search-mcp-server version: 1.0.0 type: SYNC instructions: search images from pexels request-timeout: 30 capabilities: tool: true sse-endpoint: /sse sse-message-endpoint: /mcp/message openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-sonnet-4-5 temperature: 0.7几个字段解释一下。sse-endpoint: /sse是客户端建立长连接的地址sse-message-endpoint: /mcp/message是客户端回传消息的 POST 地址这两个必须和客户端配置严格对应差一个斜杠都连不上。type: SYNC表示同步工具调用capabilities.tool: true开启工具能力。模型部分用spring.ai.openai是因为 TaoToken 兼容 OpenAI 协议格式base-url填https://taotoken.net/apiapi-key从环境变量读别写死。Model ID 按你控制台的实际模型填。3.3 工具类骨架定义一个带Tool注解的方法Spring AI 会自动扫描并注册Service public class ImageSearchTool { private static final String API_URL https://api.pexels.com/v1/search; private static final String PEXELS_KEY System.getenv(PEXELS_API_KEY); Tool(description search image by web) public String searchImage(ToolParam(description Search query keyword) String query) { try { return String.join(,, searchImageByPexels(query)); } catch (Exception e) { throw new RuntimeException(image search failed: e.getMessage(), e); } } private ListString searchImageByPexels(String query) throws JsonProcessingException { MapString, String headers Map.of(Authorization, PEXELS_KEY); MapString, Object params Map.of(query, query); String response HttpUtil.createGet(API_URL) .addHeaders(headers) .form(params) .execute() .body(); return JSONUtil.parseObj(response) .getJSONArray(photos) .stream() .map(photoObj - (JSONObject) photoObj) .map(photoObj - photoObj.getJSONObject(src)) .map(photo - photo.getStr(medium)) .filter(StringUtils::isNotEmpty) .collect(Collectors.toList()); } }3.4 注册 ToolCallbackProvider光有Tool还不够必须显式注册成 Bean自动配置才会把它合并进 MCP 工具列表Configuration public class McpToolConfig { Bean public ToolCallbackProvider searchImageTools(ImageSearchTool imageSearchTool) { return MethodToolCallbackProvider.builder() .toolObjects(imageSearchTool) .build(); } }多个 Bean 生成ToolCallbacks时自动配置会合并它们所以你可以按业务拆多个工具类各自注册一个 Provider。3.5 启动类SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } }启动后访问http://localhost:8080/sse如果看到连接挂起并持续输出事件流说明 SSE 端点通了。这一步是后面所有验证的前提。4. 验证请求一次 SSE 连接与工具调用配置写完得真连一次才算数。我用 Cherry Studio 做客户端验证你也可以用 Cline 或 Claude Code。4.1 客户端配置在 Cherry Studio 里新增 MCP 服务类型选 SSEURL 填http://localhost:8080/sse保存后客户端会自动建立长连接。如果服务端日志出现类似Client connected的记录说明握手成功。4.2 触发工具调用在对话里输入帮我搜一张猫的图片模型会判断需要调用searchImage工具通过sse-message-endpoint回传调用请求。服务端执行searchImageByPexels把结果返回给模型模型再组织成自然语言回复。一次成功的调用链路是这样的{ jsonrpc: 2.0, method: tools/call, params: { name: searchImage, arguments: { query: cat } } }服务端返回{ jsonrpc: 2.0, result: { content: [ { type: text, text: https://images.pexels.com/photos/xxx/medium.jpg,... } ] } }4.3 用 curl 直接验证 SSE 端点不想开客户端的话curl 也能测curl -N http://localhost:8080/sse-N关闭缓冲你会看到事件流持续输出。如果连接立刻断开或者返回 404说明sse-endpoint路径配错了。4.4 验证模型通道工具内部如果要调模型比如对搜索结果做语义过滤走的是spring.ai.openai那套配置。你可以单独写个测试接口注入ChatClient发一句话确认 TaoToken 通道正常RestController public class PingController { private final ChatClient chatClient; public PingController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/ping-model) public String ping() { return chatClient.prompt(说一句你好).call().content(); } }访问http://localhost:8080/ping-model有正常返回就说明 Base URL Key Model ID 三件套生效了。5. 本篇常见错排查401、local proxy failed、reading choices这一节按真实报错来都是我踩过的。401 Unauthorized。九成是 Key 问题。检查TAOTOKEN_API_KEY环境变量有没有生效echo $TAOTOKEN_API_KEY看输出。如果 Key 是对的还报 401检查base-url是不是写成了带路径的形式必须是https://taotoken.net/api不要加/v1之类的后缀Spring AI 会自己拼。local proxy failed。这个报错通常出现在客户端侧说明客户端连不上你的 SSE 端点。先确认服务端server.port和客户端 URL 端口一致再确认sse-endpoint路径。如果服务端在容器里localhost要换成宿主机 IP。还有一种情况是防火墙拦了长连接本地开发一般不会但公司网络要注意。reading choices 相关报错。这是模型响应解析失败常见于 Model ID 填错或者模型不支持当前请求格式。去控制台模型列表核对 ID别凭记忆填。如果用的是对话模型但请求里带了工具定义某些模型会返回不兼容结构换一个支持 function calling 的模型试试。OAuth 相关报错。如果你在 Claude Code 里配 MCP可能会遇到 OAuth 流程问题。Claude Code 的 MCP 配置在~/.claude.json或项目级配置里SSE 类型直接填 URL 即可不需要 OAuth。如果报 OAuth 错检查是不是误选了需要认证的传输类型。工具列表为空。客户端连上了但看不到工具八成是ToolCallbackProvider没注册或者capabilities.tool没开。检查McpToolConfig有没有被扫描到Configuration别漏。Codex auth.json 场景。如果你同时用 Codex它的凭证在~/.codex/auth.json格式和 Spring AI 不一样别混用。Codex 走的是它自己的配置体系Base URL 和 Key 单独填三件套逻辑一样但文件不同。排查顺序建议先 curl 测 SSE 端点通不通再测模型通道通不通最后测工具调用。分层定位比一上来就怀疑代码快得多。6. 把 Key 和通道固定下来后面就顺了MCP 服务搭起来之后真正省心的是配置稳定。我现在的做法是TaoToken 的 Key 放环境变量application.yml里只写${TAOTOKEN_API_KEY}本地开发用application-local.yml覆盖CI 里用 secrets 注入。这样无论换机器还是给别人联调改一个环境变量就行。工具类按业务拆一个工具一个Tool方法各自注册 Provider。SSE 端点路径定下来就别乱改客户端配置跟着走。模型 ID 单独抽成一个配置项换模型不用动代码。如果你后面要接 Claude Code 做编码 AgentMCP 服务端可以直接复用这套骨架客户端那边配 SSE URL 就行。Claude Code 的接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的配置示例。最后留一个实用技巧MCP 服务启动后先用curl -N http://localhost:8080/sse确认事件流正常再去客户端配。这一步能过滤掉 80% 的连不上问题比在客户端里反复改配置高效得多。
返回列表