1. 从一次真实踩坑说起多模型 Key 散落各处有多痛如果你正在用 Spring Boot 做后端最近又想给项目加上大模型对话能力大概率会遇到这样一个尴尬局面项目里同时接了 DeepSeek 做主力对话、接了另一个模型做代码补全、还想让模型能调用内部工具查订单。结果就是application.yml里躺着三四个不同厂商的api-key每个 Key 的额度、过期时间、计费方式都不一样测试环境和生产环境还得各维护一套。更麻烦的是当你想把 MCP Client 接进来做工具调用时MCP Server 那边要校验用户身份token 又得从请求头一路透传下去链路一长就容易断。这篇就聚焦一件事在 Spring Boot 项目里用 Spring AI 把大模型对话和 MCP Client 工具调用完整跑通并且用 TaoToken 的统一 Key 把多模型接入收敛成一个入口。适合已经写过 Spring Boot、想快速落地 AI 能力的后端开发者。读完之后你能拿到可直接复制的application.yml骨架、MCP Client 的config.toml配置示例、一个能验证连通性的对话接口以及 token 透传到 MCP Server 的完整写法。我试过把 Key 分散配置后来发现统一走一个 API 通道确实省心不少下面按步骤来。2. TaoToken 前置准备统一 Key 与 API 通道TaoToken 在这里扮演的角色是一个统一的大模型 API 接入层。你不需要为每个模型单独申请 Key、单独记 Base URL而是用同一个 Key 走同一个 API 地址通过模型名来区分调用哪个模型。对 Spring Boot 项目来说这意味着application.yml里只需要维护一份凭证配置。具体操作路径是这样的先到官网 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 Key 管理页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建好之后把 Key 复制出来后面配置里要用。API 通道的基础地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 Base URL 使用。模型名方面DeepSeek 系列可以直接用deepseek-chat这类标识具体以控制台模型列表为准。这里有个关键点要提醒TaoToken 是合规的 API 接入服务你通过它调用模型时请求走的是标准 HTTPS不需要任何额外网络配置。如果你的项目部署在内网确保服务器能正常访问taotoken.net即可。3. 可复制配置application.yml 与 MCP Client 骨架先看项目依赖。用 Spring Initializr 建项目时勾选 Spring Web然后手动补上 Spring AI 的 MCP Client 和模型 starter。如果你用的是 Mavenpom.xml里加上这三块dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency这里用 OpenAI 兼容的 starter 来对接 TaoToken 的统一通道因为 TaoToken 的 API 是 OpenAI 兼容格式。版本方面Spring AI 用 1.1.0 这一代比较稳如果导入报错就显式指定版本号。接下来是application.yml的核心配置。把 TaoToken 的 Key 和 Base URL 填进去同时配置 MCP Client 要连接的服务端spring: ai: openai: api-key: sk-你的TaoToken密钥 base-url: https://taotoken.net/api chat: options: model: deepseek-chat temperature: 0.7 mcp: client: streamable-http: connections: order-server: url: http://localhost:8080 endpoint: /api/mcp-endpoint这段配置里base-url指向 TaoToken 的 API 通道api-key用你在控制台创建的那把 Keymodel指定默认走 DeepSeek。MCP 部分声明了一个叫order-server的连接指向你本地或远程的 MCP Server。如果你更习惯用config.toml来管理 MCP 连接比如配合某些客户端工具对应的骨架长这样[[mcp.servers]] name order-server transport streamable-http url http://localhost:8080/api/mcp-endpoint headers { token ${MCP_TOKEN} }注意headers里的 token 是给 MCP Server 校验用户身份用的后面会讲怎么在 Java 代码里动态注入。4. 验证请求对话接口与 MCP 工具调用配置写好了得有个接口能验证整条链路通不通。先写一个最简的对话 Controller注入ChatClient.Builder构造ChatClient并调用import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/chat) public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder .defaultSystem(你是蜗牛公司的AI助手小蜗负责解答AI相关问题) .build(); } PostMapping(/ask) public String ask(RequestParam(question) String question) { return chatClient.prompt() .user(question) .call() .content(); } }启动项目后用 curl 发一个请求curl -X POST http://localhost:8081/chat/ask?question简单介绍一下你自己如果返回类似“我是小蜗蜗牛公司的AI助手”这样的内容说明 TaoToken 的 Key 和 API 通道已经通了。这一步是整个链路的地基先确保它能跑通再往下走。接下来是多轮对话。大模型本身没有记忆所谓多轮就是把历史消息一起传进去。Spring AI 里用messages()方法传入ListMessagePostMapping(/multiTurnChat) public ListMapString, String multiTurnChat( RequestBody ListMapString, String messages) { ListMessage messageList new ArrayList(); messages.forEach(item - { String role item.get(role); String content item.get(content); if (user.equals(role)) { messageList.add(new UserMessage(content)); } else if (assistant.equals(role)) { messageList.add(new AssistantMessage(content)); } }); String reply chatClient.prompt() .messages(messageList) .call() .content(); messages.add(Map.of(role, assistant, content, reply)); return messages; }前端传参格式是一个数组每项包含role和contentrole取user或assistant。这样每次请求都把完整历史带上模型就能“记住”上下文。现在把 MCP 工具调用加进来。注入SyncMcpToolCallbackProvider在.call()之前加上.toolCallbacks()Autowired private SyncMcpToolCallbackProvider toolCallbackProvider; PostMapping(/chatWithTools) public String chatWithTools(RequestBody ListMapString, String messages) { ListMessage messageList new ArrayList(); messages.forEach(item - { if (user.equals(item.get(role))) { messageList.add(new UserMessage(item.get(content))); } else if (assistant.equals(item.get(role))) { messageList.add(new AssistantMessage(item.get(content))); } }); return chatClient.prompt() .messages(messageList) .toolCallbacks(toolCallbackProvider.getToolCallbacks()) .call() .content(); }getToolCallbacks()会返回 MCP Server 暴露的所有工具模型会根据用户问题自动决定是否调用。比如你问“帮我查一下订单 12345”模型就会触发 MCP Server 的查询工具。但这里有个坑MCP Server 需要知道当前用户是谁才能返回正确的订单。所以得把 token 从 HTTP 请求头透传到 MCP 调用里。Spring AI 目前不直接支持动态 header需要自定义一个McpSyncHttpClientRequestCustomizerimport io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.common.McpTransportContext; import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.net.URI; import java.net.http.HttpRequest; Configuration public class McpConfig { Bean McpSyncHttpClientRequestCustomizer requestCustomizer() { return (builder, method, endpoint, body, context) - { if (RequestContextHolder.getRequestAttributes() null) { return; } ServletRequestAttributes attrs (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); HttpServletRequest request attrs.getRequest(); String token request.getHeader(token); if (token ! null) { builder.header(token, token); } }; } }加上这个 Bean 之后调用方在 HTTP 请求头里带上token它就会被自动塞进 MCP 请求里。MCP Server 那边就能根据 token 识别用户返回对应的订单数据。验证时用 curl 带上 headercurl -X POST http://localhost:8081/chat/chatWithTools \ -H Content-Type: application/json \ -H token: user-abc-123 \ -d [{role:user,content:帮我查一下我的订单}]如果返回的订单信息属于user-abc-123这个用户说明 token 透传链路完整打通了。5. 本篇常见错排查第一个高频问题启动时报401 Unauthorized或invalid api key。先检查application.yml里api-key有没有多余空格再确认base-url是不是https://taotoken.net/api注意结尾不要带斜杠。如果 Key 是从控制台复制的确认没有复制到前后空白字符。第二个问题MCP 工具调用不触发。先看SyncMcpToolCallbackProvider有没有成功注入如果注入失败通常是 MCP Server 没启动或endpoint路径写错。可以在启动日志里搜mcp关键字看连接是否建立。另外确认spring-ai-starter-mcp-client的版本和 Spring AI 主版本一致。第三个问题token 透传不生效MCP Server 收到的 header 是空的。检查McpConfig里的RequestContextHolder是否在异步线程里取不到请求上下文。如果你的对话接口用了Async或响应式编程RequestContextHolder会失效这时候需要手动把 token 作为参数传下去而不是依赖 ThreadLocal。第四个问题多轮对话时模型“失忆”。检查前端传的messages数组是否包含完整历史尤其是assistant角色的回复有没有被加回去。如果只传了用户消息模型自然看不到之前的对话。第五个问题切换模型后报模型不存在。TaoToken 的模型名以控制台列表为准deepseek-chat是常用标识但如果你要用其他模型把model字段换成对应名称即可不要自己拼写。6. 下一步把链路接到真实业务到这里Spring Boot 通过 Spring AI 接入大模型对话、配置 MCP Client、透传 token 的完整路径就跑通了。你可以在这个骨架上继续加东西比如用ChatMemory把历史对话存到 Redis避免每次前端传全量消息或者给 MCP Server 加更多工具让模型能查库存、下单、发通知。如果你在排障或接入过程中遇到问题建议先对照 API Keys 页面确认 Key 状态再翻一下接入文档里的参数说明。想快速验证模型对话效果可以直接用模型对话页面试几条 prompt确认通道没问题再回到代码里调。长期做编码类 Agent 的话Coding Plan 那条路径会更适合持续调用场景。最后留一个实用技巧把application.yml里的api-key用环境变量注入比如api-key: ${TAOTOKEN_API_KEY}这样本地和线上用同一份配置只换环境变量就行避免 Key 硬编码进 Git。