1. 为什么 Java 后端要自己写一个 MCP 服务MCPModel Context Protocol模型上下文协议说白了就是给 AI 装一套标准插座。以前你想让 Claude、Cursor 这类客户端调用你写的业务方法得自己拼 HTTP 接口、写一堆胶水代码还得处理鉴权和参数校验。MCP 把这层统一了你只要按协议暴露「工具Tool」客户端就能自动发现并调用参数结构、返回格式都由协议约定好。对 Java 后端来说这件事的价值在于复用。你手上已经有一堆 Spring 的 Service、Repository、内部 RPC 客户端与其重写一遍不如用 Spring AI 的 MCP Server Starter 把现有方法直接标注成工具让 AI 客户端通过标准协议调进来。适合谁三类人最合适一是想把内部系统能力开放给 AI 助手的后端二是做智能硬件/Agent 平台、需要统一工具入口的团队三是想学 MCP 协议但不想从零啃 JSON-RPC 的开发者。这篇我会带你从零建一个 Spring Boot 项目定义两个工具取当前时间、整数求和打包成 JAR再把它接到 TaoToken 的统一 Key/API 通道上做连通性验证。全程可复制踩坑点我会在第五节列清楚。核心检索词先记住Spring Boot 创建 MCP 服务、Java MCP Server 接入、TaoToken 统一 Key 通道。2. 前置准备TaoToken 通道与本地环境在写代码之前先把「AI 侧」的通道准备好。MCP 服务本身是工具提供方但你要验证它、或者让上层 Agent 调用模型时需要一个统一的模型入口。TaoToken 在这里扮演的就是统一 Key/API 通道的角色一个 Key 走多家模型Base URL 固定省得你在每个客户端里维护一堆不同的地址和密钥。你需要准备的东西JDK 17 或以上推荐 JDK 21Spring Boot 3.3 对 21 支持很好。Maven 3.8或者用 IDEA 自带的。一个 TaoToken 账号去控制台生成 API Key。地址是 https://taotoken.net/api Key 在 console 里创建路径是 https://taotoken.net/console 。一个能发 HTTP 请求的工具curl 或 Postman 都行用来做连通性检查。关于 Key 的存放我的习惯是绝对不写进代码和 Git。本地用环境变量CI 用 Secret配置文件里只放占位符。你可以这样导出export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiTaoToken 的 Base URL 是 https://taotoken.net/api 注意这个地址不带任何查询参数是纯 API 根路径。模型 ID 按你实际要用的填比如 claude 系列或 gpt 系列具体以控制台文档为准。这里有个关键点MCP 服务端和模型调用是两件事MCP 负责「暴露工具」TaoToken 负责「提供模型」。你完全可以让 MCP 服务只做工具模型调用交给上层客户端也可以在自己的服务里同时调模型做增强。这篇两种都会覆盖到验证环节。环境变量设好后先别急着写代码用一条 curl 确认通道是通的curl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY | head -c 500能返回模型列表 JSON说明 Key 和通道没问题。如果这里就 401先解决鉴权别往下走否则后面报错你会分不清是 MCP 的问题还是 Key 的问题。3. 可复制配置pom、工具类与 MCP 注册这一节是全文的核心所有片段都能直接抄。先建项目用 start.spring.io 生成骨架或者手写 pom.xml。关键是引入 Spring AI 的 MCP Server Starter。project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdmy-mcp-server/artifactId version1.0.0/version parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.0/version relativePath/ /parent properties java.version21/java.version spring-ai.version1.0.0-M6/spring-ai.version /properties dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-spring-boot-starter/artifactId version${spring-ai.version}/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build /project版本号这块要注意Spring AI 的 MCP Starter 在里程碑阶段版本迭代快0.8.x 和 1.0.0-M6 的包名、注解位置有差异。如果你用 0.8.1注解是org.springframework.ai.tool.annotation.Tool用 1.0.0-M6 也基本一致但 BOM 管理更规范。我建议用 BOM 统一版本避免子依赖打架。接下来定义工具类。MCP 的核心就是「工具」一个带Tool注解的 public 方法就是一个可被 AI 调用的工具参数用ToolParam描述描述写得越清楚模型调用时越不容易传错参数。package com.example.mcp; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; Component public class MyTools { Tool(description 获取当前系统时间返回格式 yyyy-MM-dd HH:mm:ss) public String getCurrentTime() { return LocalDateTime.now() .format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)); } Tool(description 计算两个整数的和) public int add( ToolParam(description 第一个整数) int a, ToolParam(description 第二个整数) int b) { return a b; } }然后注册到 MCP。Spring AI 用ToolCallbackProvider把工具对象暴露出去package com.example.mcp; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class McpConfig { Bean public ToolCallbackProvider myToolCallbackProvider(MyTools myTools) { return MethodToolCallbackProvider.builder() .toolObjects(myTools) .build(); } }主启动类就是标准的 Spring Boot 入口package com.example.mcp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } }配置文件application.properties里声明 MCP 服务元信息spring.application.namemy-mcp-server spring.ai.mcp.server.namemy-tools spring.ai.mcp.server.version1.0.0 spring.ai.mcp.server.typeSYNC如果你想让 MCP 服务同时能调 TaoToken 的模型做增强可以再加一段模型配置。这里用环境变量占位别硬编码spring.ai.openai.base-url${TAOTOKEN_BASE_URL} spring.ai.openai.api-key${TAOTOKEN_API_KEY} spring.ai.openai.chat.options.modelclaude-3-5-sonnet注意spring.ai.openai.base-url填 https://taotoken.net/api TaoToken 兼容 OpenAI 风格的接口路径所以用 openai starter 就能对接。模型 ID 按控制台实际可用的填别照抄我这个示例名。4. 启动验证与接口连通性检查配置写完先本地跑起来看日志。用 Maven 直接启动mvn spring-boot:run默认情况下 MCP Server 以 stdio标准输入输出模式运行适合被 Claude Desktop、Cursor 这类客户端以子进程方式拉起。启动成功的标志是日志里出现 MCP server 初始化信息并且没有端口占用报错。如果你同时引入了 web starter它会额外起一个 HTTP 端口这不冲突但要注意 stdio 模式下别往 stdout 打无关日志否则会污染协议帧。打包成可执行 JARmvn clean package -DskipTests # 产物target/my-mcp-server-1.0.0.jar验证 JAR 能独立跑java -jar target/my-mcp-server-1.0.0.jar接下来做接口连通性检查。分两层第一层是 MCP 协议层第二层是 TaoToken 模型通道层。MCP 协议层最直接的办法是把它接到一个 MCP 客户端里。以 Claude Desktop 为例配置文件在 macOS 是~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 是%APPDATA%\Claude\claude_desktop_config.json。写入{ mcpServers: { my-java-mcp: { command: java, args: [-jar, /absolute/path/to/my-mcp-server-1.0.0.jar] } } }保存后重启客户端在对话里输入「帮我获取一下当前时间」如果工具被正确发现客户端会提示调用getCurrentTime返回类似2025-01-15 14:30:22。再试「计算 12 加 30」应该返回 42。这两个动作跑通说明 MCP 服务端没问题。TaoToken 通道层用 curl 直接打模型接口确认 Key 和 Base URL 正确curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-3-5-sonnet, messages: [{role: user, content: 只回复两个字通了}] }返回体里choices[0].message.content有内容就说明通道 OK。这一步很关键因为很多人把 MCP 报错和模型报错混在一起排查分开验证能省一半时间。如果你在 Spring 服务里也配了模型调用可以写个简单的 CommandLineRunner 在启动时打一条测试请求日志里能看到响应就放心了。5. 本篇常见错误排查这一节按真实报错来都是我或身边人踩过的。401 Unauthorized。出现在 curl 或 Spring 调模型时。原因基本是 Key 没读到或格式不对。检查TAOTOKEN_API_KEY是否真的导出到了当前 shellecho $TAOTOKEN_API_KEY看一眼。Spring 里如果用了${TAOTOKEN_API_KEY}但环境变量没设启动会直接失败或传空串。另外注意 Header 是Authorization: Bearer sk-xxxBearer 后面有空格别漏。local proxy failed / connection refused。这个报错通常出现在客户端拉起 MCP 子进程时command或args路径写错或者 java 不在 PATH 里。解决办法是用绝对路径command: /usr/bin/javaJAR 也用绝对路径。Windows 上路径反斜杠要转义成\\或者直接用正斜杠。reading choices of undefined。这是解析模型响应时拿不到choices字段。常见原因是 Base URL 写成了https://taotoken.net/api/v1又在代码里拼了/v1导致路径变成/v1/v1/chat/completions。记住 Base URL 只到 https://taotoken.net/api 版本段由 SDK 自己拼。另一个原因是模型 ID 写错接口返回了错误对象而不是正常响应。OAuth / token expired。如果你用的是需要 OAuth 的客户端比如某些 IDE 插件报这个说明授权过期重新走一遍授权流程即可。注意这跟 TaoToken 的 API Key 是两套东西别混。MCP 工具没被发现。客户端里看不到你的工具先确认ToolCallbackProviderBean 被扫描到了包路径要在SpringBootApplication同级或子级。再确认Tool方法所在类有Component。还有一个隐蔽点stdio 模式下任何System.out.println都会破坏协议把日志级别调高或改用 stderr。CC Switch / Cline MCP / Codex auth.json 三件套。如果你用这些工具接 MCP配置里必须同时给全三样Base URL、Key、Model ID。少一样就连不上。以 Cline 的 MCP 配置为例结构大致是{ mcpServers: { my-java-mcp: { command: java, args: [-jar, /abs/path/my-mcp-server-1.0.0.jar], env: { TAOTOKEN_BASE_URL: https://taotoken.net/api, TAOTOKEN_API_KEY: sk-你的key, TAOTOKEN_MODEL: claude-3-5-sonnet } } } }Codex 的auth.json同理Base URL 和 Key 要对上Model ID 不能空。这三件套缺一个表现就是工具列表空或者调用超时。6. 把 MCP 服务接到 TaoToken 统一通道工具跑通之后最后一步是让它和 TaoToken 的通道协同工作。有两种典型用法。第一种MCP 只做工具模型调用交给客户端。你在 Claude Desktop 或 Cline 里配置 TaoToken 作为模型提供方Base URL 填 https://taotoken.net/api Key 填你的Model ID 选好。这样客户端用 TaoToken 的模型来「思考」用你的 Java MCP 服务来「执行」职责清晰。这种模式下你的 Spring 服务不需要配模型最省事。第二种MCP 服务内部也调模型做增强。比如你的工具需要先让模型总结一段文本再返回。这时在 Spring 里配好spring.ai.openai.base-url和api-key注入ChatClient调用即可。好处是工具内部逻辑闭环坏处是模型调用和工具调用耦合排查问题时要多看一层日志。不管哪种Key 的管理都建议走环境变量或配置中心别写死在代码里。TaoToken 的统一 Key 通道优势就在这里一个 Key 覆盖多个模型你换模型只改 Model ID不用换地址和密钥MCP 服务端配置几乎不用动。如果你要长期跑编码类 Agent或者需要多模型切换做对比可以看看 Coding Plan路径是 https://taotoken.net/coding-plan 。接入文档在 https://taotoken.net/doc API Key 管理在 https://taotoken.net/api-keys 。模型对话调试入口是 https://taotoken.net/chat 适合快速验证某个模型 ID 是否可用。最后给个实操建议把 MCP 服务的工具描述写细。Tool(description...)和ToolParam(description...)不是给人看的是给模型看的。描述里写清楚单位、格式、边界条件模型调用准确率会明显提升。我试过把「计算两个整数的和」改成「计算两个 32 位有符号整数的和返回 int」参数传错的概率下降很多。工具描述就是你和模型之间的接口文档值得多花五分钟。