
1. 从零搭 SpringAi Rag MCP 时Key 和配置到底卡在哪如果你正在搜 SpringAi、Rag、MCP 服务搭建大概率已经看过不少“三件套”教程Spring Boot 起服务、向量库做检索增强、MCP 挂工具调用。真正动手时最先卡住的往往不是代码而是两件事一是模型 Key 分散在 application.yml、环境变量、多个 starter 里换一个模型就要改一遍二是 MCP 服务的连接配置写死在代码里本地调试和部署环境对不上SSE 地址一改就报连接失败。这篇就按“一次跑通 Rag 检索增强链路”的目标来写。我会用 Spring Boot 3.4.2 JDK 17 Spring AI 1.0.0-M6.1 这套组合把模型访问统一收敛到 TaoToken 的一个 Key 上再给出一份可以直接复制的 config.toml 骨架把 MCP 服务、Rag 文档、会话记忆都挂进去。适合谁已经会写 Spring Boot、想快速验证 Rag MCP 链路、但不想在 Key 管理上反复折腾的开发者。核心检索词先对齐SpringAi 负责编排对话与工具调用Rag 负责把私有文档塞进向量库做检索增强MCP 负责把外部工具比如查医院位置以标准协议暴露给模型。三者串起来模型才能既懂你的文档又能调你的业务方法。下面从依赖版本开始一步步给可复制的配置。2. TaoToken 前置一个 Key 管住所有模型调用在写 config.toml 之前先把模型访问这一层理清楚。传统做法是每个模型厂商一个 api-key写在 application.yml 里Rag 用 embedding 模型、对话用 chat 模型、MCP 工具回调又可能走另一个端点Key 一多就容易漏配。TaoToken 的思路是提供一个统一的 API 入口你只需要一个 Key就能在同一个 base-url 下切换不同模型。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个不加 UTM。你需要先去控制台创建一个 API Key路径在 console 里https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。创建完 Key 之后在 API Keys 页面可以查看和管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。这里有个容易踩的坑Spring AI 的 OpenAI starter 默认会拼接/v1/chat/completions所以 base-url 要配到https://taotoken.net/api这一层不要自己再加/v1否则会变成/api/v1/v1/...。Embedding 模型同理走的是同一个 base-url。配好之后chat 和 embedding 共用一个 KeyRag 导入文档时用的 embedding 调用和对话调用就不会再出现“这个 Key 没配额、那个 Key 过期”的问题。如果你只是想先验证模型能不能通不用急着写 Java 代码可以直接在模型对话页面发一条消息试试https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。确认 Key 有效之后再回到项目里配 config.toml能省掉很多“到底是 Key 错还是代码错”的排查时间。3. 可复制配置config.toml 骨架 Spring AI 接入片段先给依赖版本这是整套配置能跑起来的前提。Spring Boot 用 3.4.2JDK 17Spring AI 用 1.0.0-M6.1。pom.xml 里核心依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.2/version relativePath/ /parent dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0-M6.1/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client-webflux/artifactId version1.0.0-M6.1/version /dependency /dependencies接下来是 config.toml 骨架。Spring AI 本身读的是 application.yml但很多团队会把模型和 MCP 的连接信息抽到独立的 config.toml 里做环境隔离。下面这份骨架你可以直接复制把YOUR_TAOTOKEN_KEY换成第 2 步创建的 Key[model] # TaoToken 统一入口chat 和 embedding 共用 base-url https://taotoken.net/api api-key YOUR_TAOTOKEN_KEY chat-model qwen-max embedding-model text-embedding-v3 timeout 60s [rag] # Rag 文档路径与向量库类型 document-path classpath:/rag/terms-of-service.txt vector-store simple chunk-size 800 [mcp] # MCP 服务连接可配多个 enabled true transport sse [[mcp.connections]] name hospital-mcp url http://localhost:8099 [[mcp.connections]] name billing-mcp url http://localhost:8100对应的 application.yml 里把 config.toml 的值映射进来。Spring AI 的 OpenAI starter 认的是spring.ai.openai前缀spring: application: name: spring-ai-rag-mcp-demo ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: qwen-max embedding: options: model: text-embedding-v3 mcp: client: sse: connections: hospital-mcp: url: http://localhost:8099注意api-key这里用了环境变量${TAOTOKEN_API_KEY}不要把 Key 硬编码进仓库。启动前在 IDE 的运行配置或 shell 里 export 一下即可。MCP 的connections是个 mapkey 是连接名value 是 SSE 地址配多个就多写几段。启动类里注册三个 Bean会话记忆、Rag 文档导入、向量库。这部分和常见写法一致但 embedding 模型现在走的是 TaoToken 统一入口不需要单独配 KeySpringBootApplication public class RagMcpApplication { public static void main(String[] args) { SpringApplication.run(RagMcpApplication.class, args); } Bean public ChatMemory chatMemory() { return new InMemoryChatMemory(); } Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { return SimpleVectorStore.builder(embeddingModel).build(); } Bean CommandLineRunner ingestDocs(VectorStore vectorStore, Value(classpath:/rag/terms-of-service.txt) Resource doc) { return args - vectorStore.write( new TokenTextSplitter().transform(new TextReader(doc).read()) ); } }Controller 层把 ChatClient 组装起来挂上会话记忆、Rag 检索 advisor 和 MCP 工具回调RestController RequestMapping(/ai) public class RagMcpController { private final ChatClient chatClient; public RagMcpController(ChatClient.Builder builder, ChatMemory chatMemory, VectorStore vectorStore, ToolCallbackProvider toolCallbackProvider) { this.chatClient builder .defaultSystem(你是医院助手回答要亲切。涉及缴费、挂号、结算时先查历史记录缺信息再问用户。) .defaultAdvisors( new PromptChatMemoryAdvisor(chatMemory), new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().build()) ) .defaultTools(toolCallbackProvider) .build(); } GetMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestParam String message) { return chatClient.prompt().user(message).stream().content(); } }到这里config.toml 骨架、依赖、启动类、Controller 都齐了。下一步是启动验证和 MCP 连通性检查。4. 验证请求Rag 检索与 MCP 连通性检查先启动主服务观察日志里有没有 embedding 调用成功的记录。Rag 文档导入是在CommandLineRunner里执行的如果 Key 或 base-url 配错这一步会直接抛 401 或连接超时。日志里看到向量写入条数说明 embedding 链路通了。然后验证 Rag 检索。准备一份terms-of-service.txt内容写几条业务规则比如“退号只能退 90% 的挂号费”“医保结算正常报销 50%”。启动后发一条请求curl -N http://localhost:8080/ai/stream?message我要退号能退多少钱如果 Rag 生效返回内容里应该出现“退 90%”这类来自文档的信息而不是模型自己编的。这一步能过说明 QuestionAnswerAdvisor 把向量库检索结果拼进了 prompt。接着验证 MCP 连通性。MCP 服务单独起一个 Spring Boot 项目加依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version1.0.0-M6.1/version /dependency写一个带Tool注解的方法Service public class HospitalMcpService { Tool(name hospitalLocation, description 获取医院的位置) public String getHospitalLocation() { return 皖中路132号; } }启动 MCP 服务端口 8099。回到主服务发一条会触发工具调用的请求curl -N http://localhost:8080/ai/stream?message医院在哪里预期结果是模型调用hospitalLocation工具返回“皖中路132号”。如果返回的是模型自己编的地址说明 MCP 连接没建立。检查主服务日志里有没有MCP client connected之类的记录以及 config.toml 里的url是否和 MCP 服务实际端口一致。成功结果长这样Rag 问题返回文档里的规则MCP 问题返回工具方法的真实返回值两者在同一个对话里可以交替出现。到这一步Rag 检索增强链路就算跑通了。5. 本篇常见错排查Key、base-url、MCP 连接第一个高频错误是 401 Unauthorized。原因通常是 Key 没读到或者环境变量名写错。检查${TAOTOKEN_API_KEY}是否真的 export 了IDE 运行配置里有没有单独覆盖。另一个可能是 base-url 写成了https://taotoken.net/api/v1导致路径重复。正确写法是https://taotoken.net/api。第二个是 Rag 检索不生效模型回答和文档无关。先确认CommandLineRunner有没有执行日志里有没有向量写入记录。如果向量库是空的QuestionAnswerAdvisor检索不到内容自然退化成普通对话。再检查文档路径classpath:/rag/terms-of-service.txt是否真的在 resources 下文件名大小写是否一致。第三个是 MCP 连接失败报 SSE 超时或 connection refused。先单独用 curl 测 MCP 服务的 SSE 端点是否可达curl -N http://localhost:8099/sse如果这个不通主服务肯定连不上。通了但主服务仍报错检查spring.ai.mcp.client.sse.connections的层级是否写对连接名和 url 是否成对出现。多个 MCP 服务时每个连接名要唯一。第四个是 embedding 模型报 model not found。TaoToken 统一入口下embedding 模型名要和实际支持的名称一致比如text-embedding-v3。如果 chat 能通但 embedding 报错说明 embedding 的 model 配置没生效检查spring.ai.openai.embedding.options.model这一层。第五个是流式返回乱码或截断。produces MediaType.TEXT_EVENT_STREAM_VALUE必须加前端用 EventSource 接收时注意[DONE]标记的处理。如果返回内容里混入了 advisor 的调试信息检查QuestionAnswerAdvisor的SearchRequest参数是否配置了过滤条件。6. 接入与排障Key、文档、Coding Plan 怎么选排障和接入阶段最常用的两个入口是 API Keys 和接入文档。Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。遇到 base-url 拼接、模型名、参数格式的问题先翻文档比在代码里猜快得多。如果你只是想验证某个模型在 Rag 场景下的表现可以直接用模型对话页面快速试https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。把 Rag 文档里的规则贴进去看模型能不能正确引用再决定要不要写进向量库。长期做编码和 Agent 类项目的话Coding Plan 会更省心入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它适合需要持续调用模型、频繁调试工具链的场景不用每次单独管配额。Claude Code 相关的接入配置在 https://taotoken.net/claude-code?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-codeutm_campaignrewrite 如果你用 Anthropic 协议的工具链可以参考这里的配置方式。最后提醒一句config.toml 里的 Key 一定要走环境变量MCP 的 SSE 地址在本地和部署环境要分开配。Rag 文档更新后记得重启服务重新导入向量否则检索到的还是旧内容。把这三件事固定成流程下次换模型或加 MCP 服务时改配置就行不用动代码。