拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
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 端口。

MCP(Model 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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册账号,进控制台。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。在控制台里找到 API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,创建一个新 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统一入口,不带 UTM
API Key控制台生成的sk-...一个 Key 多端复用
Model ID如claude-sonnet-4-5按控制台列表填

如果你只是想先验证模型通道通不通,可以直接用模型对话页面测一下:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。发一句话看有没有正常返回,通了再往下写代码,能省掉"到底是模型问题还是 MCP 问题"的排查时间。

长期做编码和 Agent 联调的话,Coding Plan 会更划算,地址在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到参数不确定就翻这个。

注意: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> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <version>1.0.3</version> </dependency>

这个 starter 同时支持 SSE 和可选 STDIO(通过spring.ai.mcp.server.stdio=true开启)。我们做 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/api,api-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 List<String> searchImageByPexels(String query) throws JsonProcessingException { Map<String, String> headers = Map.of("Authorization", PEXELS_KEY); Map<String, 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 服务,类型选 SSE,URL 填:

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 做编码 Agent,MCP 服务端可以直接复用这套骨架,客户端那边配 SSE URL 就行。Claude Code 的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有完整的配置示例。

最后留一个实用技巧:MCP 服务启动后,先用curl -N http://localhost:8080/sse确认事件流正常,再去客户端配。这一步能过滤掉 80% 的"连不上"问题,比在客户端里反复改配置高效得多。

返回列表