ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

如何开发 MCP 服务?保姆级教程!从 Spring AI 到 TaoToken 统一 Key 通道

如何开发 MCP 服务?保姆级教程!从 Spring AI 到 TaoToken 统一 Key 通道 1. 从零理解 MCP 服务端与客户端到底在解决什么问题MCP 全称 Model Context Protocol直译是模型上下文协议。你可以把它想成 AI 世界里的 USB-C 接口以前每接一个外部能力查数据库、读文件、调接口都要单独写一套适配代码模型和工具之间是点对点硬连的有了 MCP 之后工具方只要按协议暴露一次任何支持 MCP 的客户端都能直接调用。它要解决的核心痛点就是模型对静态训练数据的依赖——模型本身不知道你公司今天的库存、你本地项目的文件结构、你数据库里最新的订单但通过 MCP模型可以在对话过程中动态发起工具调用把实时数据取回来再回答。对 Java 开发者来说Spring AI 把 MCP 的协议细节封装成了几个 starter你不需要手写 JSON-RPC 的序列化也不用自己维护会话状态只要写一个带Tool注解的方法注册成 Bean服务端就自动把这个方法暴露成 MCP 工具。客户端那边同样简单引入 client starter配好要连的服务端地址或启动命令再把它塞给ChatClient模型就能在对话里自动决定要不要调这个工具。这套链路适合谁第一类是后端开发者手里已经有 Spring Boot 项目想把现有业务能力快速变成 AI 可调用的工具第二类是做 AI 应用但不想被某一家模型厂商绑死的团队MCP 让工具层和模型层解耦第三类是想本地跑一个能读文件、查内部系统的智能助手又不想把数据传到外部服务的场景。我试过用 Spring AI 从零搭一条最小可运行链路踩过的坑主要集中在依赖版本、传输模式选错、以及模型 endpoint 和 Key 的配置上下面按可复制的步骤走一遍。需要先明确两个概念服务端负责“提供工具”客户端负责“连接服务端并把工具交给模型”。传输方式有两种STDIO 走标准输入输出适合本地进程间通信客户端直接拉起服务端的 jar 包SSE 走 HTTP 长连接适合远程部署一个服务端可以被多个客户端连。两种模式的服务端代码几乎一样差别在依赖和配置。本文会先把 STDIO 链路跑通再补 SSE 的改法最后把模型调用统一到 TaoToken 的 Key 通道上用 curl 和客户端各验证一次工具调用。2. TaoToken 统一 Key 通道的前置准备与模型接入配置在写 MCP 工具之前先把模型这一层理顺。MCP 客户端最终要调用一个大模型来决定“要不要调工具、调哪个工具、传什么参数”所以客户端必须能访问一个兼容 OpenAI 协议的模型 endpoint。TaoToken 提供的就是这样一个统一入口你拿一个 Key就能在同一个 Base URL 下切换不同模型不用为每个模型厂商单独维护一套鉴权和地址。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 路径后面不加任何查询参数。前置准备分三步。第一步注册并登录后进入控制台在 API Keys 页面创建一个 Key复制出来保存好这个 Key 只在创建时完整显示一次。第二步确认你要用的模型 ID比如gpt-4o-mini、claude-3-5-sonnet这类具体以控制台模型列表为准。第三步把 Base URL 和 Key 写进 Spring Boot 的配置里。这里有个关键点Spring AI 的 OpenAI starter 默认读spring.ai.openai.base-url和spring.ai.openai.api-key我们把这两个值指向 TaoToken 即可模型名通过spring.ai.openai.chat.options.model指定。如果你用的是 Spring AI Alibaba 的 dashscope starter配置项名字不同但思路一样都是把 endpoint 和 Key 换成 TaoToken 提供的值。为了减少变量本文客户端统一用 OpenAI 兼容的 starter这样配置最直观。下面这段application.yml是客户端侧的模型配置路径和字段名要和你的 Spring AI 版本对齐1.0.0-M6 系列用的是spring.ai.openai前缀spring: ai: openai: base-url: https://taotoken.net/api api-key: sk-你的TaoTokenKey chat: options: model: gpt-4o-mini temperature: 0.7注意base-url写的是https://taotoken.net/api不要在后面拼/v1或加 UTM 参数Spring AI 会自己在后面补/v1/chat/completions这类路径。Key 建议用环境变量注入比如api-key: ${TAOTOKEN_API_KEY}然后在启动参数或 IDE 的运行配置里设置避免把 Key 提交到 Git。模型 ID 要和控制台里显示的一致写错了会在请求时返回模型不存在的错误。这一步做完你可以先不碰 MCP单独写一个最简单的ChatClient调用测试模型通道是否通。如果这一步就报 401说明 Key 或 Base URL 有问题如果报模型不存在说明 model ID 写错。把模型通道单独验证通过再往上叠 MCP排障会清晰很多。这也是我建议的顺序先通模型再通工具最后通工具调用。3. 可复制的 Spring AI MCP 服务端与客户端配置这一节给出完整可复制的配置和代码。先建两个 Spring Boot 模块一个mcp-server一个mcp-client都用 Maven 管理。服务端先走 STDIO 模式客户端也走 STDIO这样本地联调不需要网络端口最省事。服务端的pom.xml关键依赖如下版本统一用1.0.0-M6Spring Boot 用 3.2.x 以上dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-spring-boot-starter/artifactId version1.0.0-M6/version /dependency服务端的application.yml要禁用 Web 应用类型否则 STDIO 模式下进程不会按预期阻塞在标准输入上spring: application: name: mcp-server main: web-application-type: none banner-mode: off ai: mcp: server: stdio: true name: mcp-server version: 0.0.1工具类写一个最简单的比如根据关键词返回一条模拟数据重点是Tool注解和description要写清楚模型靠这段描述决定是否调用Service public class DemoToolService { Tool(description 根据关键词查询内部知识库条目返回匹配的标题和链接) public String searchKnowledge(String keyword) { System.out.println(工具被调用关键词 keyword); return 匹配到条目 keyword 入门指南链接 https://example.com/ keyword; } }注册工具用一个Bean把上面的 service 包成ToolCallbackProviderConfiguration public class McpToolConfig { Bean public ToolCallbackProvider serverTools(DemoToolService demoToolService) { return MethodToolCallbackProvider.builder() .toolObjects(demoToolService) .build(); } }打包命令mvn clean package -DskipTests客户端侧依赖换成 client starterdependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-client-spring-boot-starter/artifactId version1.0.0-M6/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0-M6/version /dependency客户端的application.yml要同时配 MCP 服务端连接信息和模型信息。STDIO 模式下用servers-configuration指向一个 JSON 文件或者直接内联connections。这里用内联方式路径换成你本机 jar 的绝对路径spring: ai: mcp: client: stdio: connections: demoServer: command: java args: - -Dspring.ai.mcp.server.stdiotrue - -Dspring.main.web-application-typenone - -Dlogging.pattern.console - -jar - /your/path/mcp-server/target/mcp-server-0.0.1-SNAPSHOT.jar openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini客户端初始化ChatClient时把 MCP 工具塞进去Bean public ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider mcpTools) { return builder.defaultTools(mcpTools).build(); }再写一个 REST 接口方便用 curl 验证RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient chatClient; } PostMapping(/ai/ask) public String ask(RequestBody MapString, String body) { return chatClient.prompt() .user(body.get(q)) .call() .content(); } }到这里服务端和客户端的配置就齐了。注意三件套必须一致Base URL 是https://taotoken.net/apiKey 是 TaoToken 控制台创建的 KeyModel ID 是控制台里存在的模型名。这三者任何一个写错后面的工具调用都不会成功。4. 验证请求用 curl 与客户端各跑一次工具调用先启动服务端进程确认能独立运行再启动客户端。客户端启动时会自动按配置里的command拉起服务端 jar所以服务端不需要你手动常驻但第一次调试建议先手动跑一次服务端确认 jar 能正常启动、没有缺依赖报错java -Dspring.ai.mcp.server.stdiotrue \ -Dspring.main.web-application-typenone \ -Dlogging.pattern.console \ -jar mcp-server/target/mcp-server-0.0.1-SNAPSHOT.jar如果进程卡住不退出、也不打印异常说明 STDIO 模式正常它在等标准输入。按 CtrlC 退出然后启动客户端 Spring Boot 应用。客户端起来后先用 curl 打客户端暴露的接口curl -X POST http://localhost:8080/ai/ask \ -H Content-Type: application/json \ -d {q:帮我查一下 Docker 的入门资料}预期结果是模型判断需要调用searchKnowledge工具客户端通过 STDIO 把请求发给服务端服务端执行方法并返回结果模型再把结果组织成自然语言返回。你会在客户端日志里看到工具调用的往返在服务端控制台看到工具被调用关键词Docker这行输出。如果只看到模型直接回答、没有工具调用日志说明模型没有选择调工具可以换一个更明确的提问比如“用 searchKnowledge 工具查 Docker”。再验证一次 SSE 模式。把服务端依赖换成spring-ai-mcp-server-webflux-spring-boot-starter配置改成server: port: 8090 spring: ai: mcp: server: name: mcp-server version: 0.0.1客户端依赖换成spring-ai-mcp-client-webflux-spring-boot-starter连接配置改成 URL 形式spring: ai: mcp: client: sse: connections: demoServer: url: http://localhost:8090服务端用java -jar启动后客户端再跑一次同样的 curl。SSE 模式下服务端是常驻 HTTP 服务客户端通过/sse端点建立长连接工具调用走 JSON-RPC over SSE。两种模式验证通过后说明你的 MCP 链路是通的模型通道也走的是 TaoToken 的统一 Key。验证时建议打开客户端的 debug 日志把logging.level.org.springframework.aiDEBUG加上这样能看到模型请求的完整 payload 和工具调用的中间过程。如果工具调用成功但返回内容为空多半是工具方法返回了 null 或者模型没把工具结果拼进最终回答检查Tool方法的返回值。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth排障按“模型通道 → MCP 连接 → 工具调用”三层来定位不要一上来就怀疑 MCP 代码。第一类401 Unauthorized。报错通常长这样401 Unauthorized from POST https://taotoken.net/api/v1/chat/completions。原因只有两个Key 无效或 Base URL 写错。检查spring.ai.openai.api-key是不是完整的sk-开头字符串有没有多余空格检查base-url是不是https://taotoken.net/api有没有误写成带/v1或带 UTM 参数的地址。如果 Key 是从环境变量读的确认启动时环境变量真的注入了可以在启动日志里打印一下System.getenv(TAOTOKEN_API_KEY)的前几位做确认。第二类local proxy failed或连接被拒绝。这个报错一般出现在客户端尝试拉起服务端进程时command或args里的 jar 路径不对或者本机没有 java 命令。检查connections.demoServer.args里-jar后面那个绝对路径是否真实存在Windows 下路径要用双反斜杠或正斜杠。另外确认command: java在你的 PATH 里能直接执行可以在终端先跑java -version验证。第三类Error reading choices或返回体解析失败。这通常意味着请求发出去了但返回的不是标准 OpenAI 格式。常见原因是 Base URL 指向了一个不兼容 OpenAI 协议的端点或者模型 ID 写成了 TaoToken 不支持的名称。把base-url固定为https://taotoken.net/api模型 ID 从控制台复制不要手写。如果返回体里带error字段把完整报错贴出来对照一般是模型名或参数问题。第四类OAuth 相关报错比如OAuth token request failed或invalid_client。这类错误一般出现在你误用了需要 OAuth 鉴权的客户端配置而 TaoToken 走的是 API Key 鉴权。检查有没有多余的spring.ai.openai.oauth配置项删掉即可。MCP 客户端本身不涉及 OAuth它只是通过 STDIO 或 SSE 连服务端鉴权发生在模型调用那一层。第五类工具没被调用。表现是模型直接回答日志里没有工具往返。先确认ToolCallbackProvider真的注册进了ChatClient也就是builder.defaultTools(mcpTools)这行有没有生效再确认Tool的description是否足够明确模型靠它做决策最后确认提问方式模糊的问题模型可能选择不调工具换成“请调用工具查询 XXX”再试。如果服务端日志有调用记录但客户端没拿到结果检查 STDIO 模式下服务端有没有往标准输出打无关日志-Dlogging.pattern.console就是为了清掉控制台日志避免污染 JSON-RPC 通道。6. 把 MCP 链路接到长期编码与 Agent 工作流最小链路跑通之后下一步是把它变成日常能用的东西。如果你只是偶尔验证模型能力直接用模型对话页面就够了但如果你要把 MCP 工具接进长期编码、自动化 Agent 或者团队内部工具链建议把 Key 和模型管理收敛到 Coding Plan 这一层避免每个项目各自维护一套 Key 和 endpoint。TaoToken 的 Coding Plan 页面在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。实际落地时我建议把 MCP 服务端按业务域拆分一个服务端只暴露一组相关工具比如“知识库查询”“订单操作”“文件读取”各一个服务端客户端按需连接多个。这样工具描述不会互相干扰模型选择工具的准确率更高。服务端用 SSE 模式部署在内网客户端通过 URL 连接Key 统一走 TaoToken模型切换时只改model字段工具层完全不用动。还有一个实用技巧在Tool方法的description里写清楚参数格式和返回结构模型对工具的理解几乎完全依赖这段文字。比如“参数 keyword 为字符串返回格式为‘标题 | 链接’”比只写“查询知识库”效果好很多。工具方法内部要做好异常捕获返回可读的错误信息而不是抛异常否则模型拿到一个堆栈会不知道该怎么继续。最后把客户端暴露的/ai/ask接口接到你自己的前端或 IDE 插件里就形成了一个完整的“模型 工具”闭环。验证顺序永远是先 curl 通模型再 curl 通工具最后接 UI。每一步都单独可验证出问题时定位范围就小。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进