
1. 为什么 Java 后端需要自己写一个 MCP Server如果你是一名 Java 开发者手上有一套订单、库存、报表之类的业务系统最近大概率会遇到一个很具体的需求让 Claude 这类 AI 工具直接读你的业务数据而不是每次手动导出 CSV 再粘贴进对话框。MCP Server 就是干这个的——它把业务接口按 Model Context Protocol 的标准暴露出去Claude、IDEA 里的 Claude Code、Cursor 这些客户端都能直接调用。MCP 全称 Model Context Protocol底层走的是 JSON-RPC 2.0不是普通的 REST。它定义了三类能力ToolsAI 可主动调用的函数、ResourcesAI 可读取的数据源、Prompts预定义提示词模板。日常业务里 90% 的场景用 Tools 就够了本文也只讲 Tools。在 MCP 出现之前每个 AI 工具都有自己的插件体系M 个 AI 客户端乘 N 个业务系统适配器数量是乘积级增长。MCP 把这件事变成了 MN你只要写一个 Server所有支持 MCP 的客户端都能复用。这和当年 LSP 统一编辑器与语言服务的思路是一样的。本文面向已经会用 Spring Boot 的 Java 开发者从零搭一个能被 Claude 调用的 MCP Server包含可复制的依赖配置、Tool 注册代码、Claude 侧连接验证以及把模型请求 endpoint 统一到 TaoToken 通道的做法。全程不需要你改现有业务代码的架构只是多一层薄薄的适配。我试过把这套东西接到一个真实的订单库上从建项目到 Claude 成功查出数据大概四十分钟。踩的坑主要集中在 Tool 描述写得太随意、以及 Claude Desktop 配置文件路径找错这两件事上后面会逐个说清楚。2. 前置准备Spring AI MCP 依赖与 TaoToken 通道配置先说技术选型。Java 生态里实现 MCP Server 目前有两条主流路线Spring AI MCP 和社区维护的 MCP4J。Spring AI MCP 由 Spring 官方维护已经到 1.0 正式版原生 Spring Boot 集成文档质量好MCP4J 更轻量但成熟度一般。如果你已经在用 Spring Boot直接选 Spring AI MCP零额外学习成本。环境基线Java 21、Spring Boot 3.3、Spring AI 1.0.0。在 start.spring.io 建项目时勾选 Spring Web 和 Spring AI MCP Server或者手动在 pom.xml 里加依赖dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-spring-boot-starter/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement接下来是模型通道。Claude 客户端本身负责发起对话但如果你还想在服务端做二次分析、或者用 Spring AI 的 ChatClient 做结果润色就需要一个统一的模型 API 入口。TaoToken 提供 OpenAI 兼容的 API 通道一个 Key 可以走多个模型省得每个模型单独配一套环境变量。在 TaoToken 控制台创建一个 API Key然后配置到 application.yml 里。注意 Base URL 用https://taotoken.net/api不要带任何多余路径spring: ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: claude-sonnet-4-5 mcp: server: name: my-business-mcp-server version: 1.0.0 transport: stdio这里有个容易混的点MCP Server 自己的 transport 和模型 API 是两回事。transport 决定 Claude 怎么连你的 Serverstdio 是本地子进程sse 是 HTTP 远程而 base-url 决定你的 Server 内部调模型时走哪个通道。两者互不影响但都要配对。如果你打算把 MCP Server 部署到远程给多个客户端共享把 transport 改成 sse并确保 Spring Web 依赖在。本地开发阶段用 stdio 最省事零网络配置。Key 的管理建议走环境变量不要硬编码进 yml。IDEA 里可以在 Run Configuration 的 Environment variables 里加TAOTOKEN_API_KEY你的key命令行则用export。这样提交代码时不会把 Key 带上去。3. 可复制配置Tool 注册与 JSON-RPC 暴露业务接口这一节是核心直接给能跑的代码。假设我们有一个订单业务先定义实体和 Service再用Tool注解把方法暴露出去。先看实体用 record 最简洁public record Order( String orderId, String customerName, Double amount, String status ) {}然后是 Service三个方法分别对应查单个订单、查客户订单列表、统计各状态数量Service public class OrderService { private static final MapString, Order orders Map.of( ORD001, new Order(ORD001, 张三, 299.0, PENDING), ORD002, new Order(ORD002, 李四, 599.0, SHIPPED), ORD003, new Order(ORD003, 王五, 199.0, DELIVERED) ); Tool(description 根据订单ID查询单个订单的详细信息。 返回内容包括客户姓名、订单金额、当前配送状态 PENDING待发货 / SHIPPED已发货 / DELIVERED已签收。 当用户询问某个具体订单的状态、金额或客户信息时使用此工具。 ) public Order getOrderById( ToolParam(description 订单唯一标识符格式为 ORD 开头加三位数字例如 ORD001、ORD002) String orderId) { Order order orders.get(orderId); if (order null) { throw new RuntimeException(未找到订单 orderId 请确认订单号是否正确); } return order; } Tool(description 查询指定客户的所有订单列表返回订单号、金额和状态) public ListOrder getOrdersByCustomer( ToolParam(description 客户姓名例如 张三、李四) String customerName) { return orders.values().stream() .filter(o - o.customerName().equals(customerName)) .collect(Collectors.toList()); } Tool(description 统计各状态订单数量返回 PENDING/SHIPPED/DELIVERED 各自的数量) public MapString, Long getOrderStatistics() { return orders.values().stream() .collect(Collectors.groupingBy(Order::status, Collectors.counting())); } }关键在Tool的 description。AI 完全靠这段文字判断要不要调用、怎么调用。写得太模糊Claude 要么不用要么用错场景。后面第五节会专门讲描述怎么写。接着把 Tool 注册到 MCP Server。Spring AI 会自动扫描Tool注解的方法你只需要提供一个 ToolCallbackProviderConfiguration public class McpConfig { Bean public ToolCallbackProvider orderTools(OrderService orderService) { return MethodToolCallbackProvider.builder() .toolObjects(orderService) .build(); } }启动类保持默认即可SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } }跑mvn spring-boot:run一个 MCP Server 就起来了。它内部会响应initialize、tools/list、tools/call这几个 JSON-RPC 方法。客户端启动时先发initializeServer 返回自己支持的 Tool 列表名称、描述、参数 Schema用户对话时 AI 判断需要调用某个 Tool就发tools/call带上工具名和参数Server 执行完返回结果AI 再把结果整合进回复。如果你要把 endpoint 统一到 TaoToken 通道确保 application.yml 里的 base-url 是https://taotoken.net/apiKey 从控制台拿。这样服务端做二次分析时模型请求走的是同一个通道不用为每个模型单独维护配置。4. 验证请求Claude Desktop 与 Claude Code 接入实测Server 跑起来后得让 Claude 能连上。先找 Claude Desktop 的配置文件macOS 路径是~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 是%APPDATA%\Claude\claude_desktop_config.json。用编辑器打开加入你的 MCP Server{ mcpServers: { my-order-service: { command: java, args: [ -jar, /你的项目路径/target/mcp-server-1.0.0.jar ] } } }注意 args 里的 jar 路径要写绝对路径相对路径 Claude Desktop 解析不了。改完保存完全退出 Claude Desktop 再重启不是关窗口是退出进程。重启后输入框左下角会出现一个工具图标点开能看到你注册的三个 ToolgetOrderById、getOrdersByCustomer、getOrderStatistics。如果图标没出现八成是配置文件路径写错或者 JSON 格式有误用 JSON 校验工具过一遍。现在直接用自然语言测试你帮我查一下 ORD002 这个订单的情况Claude 会先调用 getOrderById参数 orderIdORD002然后返回订单号ORD002 客户李四 金额¥599.0 状态已发货SHIPPED再试统计类问题你现在各状态的订单分别有多少个Claude 会调用 getOrderStatistics返回{PENDING1, SHIPPED1, DELIVERED1}然后用自然语言总结给你。如果你用 IDEA 里的 Claude Code配置在~/.claude.json的 mcpServers 字段格式和上面一样。加完后在 IDEA 终端重启 Claude Code它会自动加载。这里有个三件套要配全Base URL 指向https://taotoken.net/apiKey 用 TaoToken 控制台生成的Model ID 填你实际要用的模型名。三者缺一请求就会报错。验证成功的标志是Claude 回复里明确显示它调用了你的 Tool并且返回的数据和你业务系统里的一致。如果 Claude 只是泛泛而谈没调工具说明 Tool 描述没让它理解该用这个工具回到第三节改 description。5. 常见报错排查401、local proxy failed 与 reading choices这一节按真实报错来。你在接入过程中大概率会遇到下面几个逐个说清楚原因和解法。401 Unauthorized。这个最常见出现在模型 API 调用环节。原因通常是 Key 没配、Key 过期、或者 base-url 写错。检查三处环境变量TAOTOKEN_API_KEY是否真的注入到了进程IDEA 里 Run Configuration 配了但没重启进程也会失效base-url 是不是https://taotoken.net/api多一个斜杠或者少一段都会 401Key 有没有多余空格。用 curl 快速验证curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:claude-sonnet-4-5,messages:[{role:user,content:hi}]}返回正常 JSON 说明 Key 和通道没问题问题在客户端配置。local proxy failed。这个报错通常出现在 Claude Desktop 启动 MCP Server 子进程时。原因是command或args指向的可执行文件找不到。检查 java 是否在 PATH 里which java确认jar 路径是否是绝对路径且文件真实存在。如果你用 SDKMAN 或 jenv 管理 Java 版本Claude Desktop 启动的子进程可能拿不到你的 shell 环境建议 command 直接写 java 的绝对路径比如/Users/you/.sdkman/candidates/java/current/bin/java。reading choices 相关报错。这类错误一般出现在模型返回体解析阶段提示读取 choices 字段失败。根因是返回的 JSON 结构和你预期的 OpenAI 格式不一致或者返回的是错误对象而不是正常响应。先看完整返回体如果是{error: {...}}按里面的 message 排查如果是空响应检查请求是否超时。Spring AI 的 OpenAI 兼容层对返回格式有要求确保 base-url 指向的是兼容 OpenAI 的端点。OAuth 相关报错。如果你在 Claude Code 里看到 OAuth 字样通常是客户端在尝试走它自己的账号体系而你想走的是 API Key 通道。检查~/.claude.json里是否同时配了 OAuth 和 apiKey两者冲突时以先加载的为准。把 OAuth 相关字段清掉只保留 Base URL、Key、Model ID 三件套。Tool 被调用但返回空。不是报错但很常见。检查 Service 方法返回的对象是否可序列化JPA 实体带懒加载的话序列化时会炸。返回专门的 DTO只包含 AI 需要的字段。另外确认方法参数上的ToolParam描述和实际入参类型匹配AI 传字符串你收 int 会直接抛异常。排查顺序建议先 curl 验证 Key 和通道再确认 MCP Server 进程能独立启动最后检查客户端配置。三层分开测比一上来就盯着客户端日志快得多。6. 把 MCP Server 用起来从本地验证到长期编码走到这里你已经有了一个能被 Claude 调用的业务接口。接下来是怎么把它用顺。本地验证通过后第一件事是把 Tool 描述打磨一遍。AI 决定调不调用某个 Tool完全依赖 description。差的描述像「查询订单」AI 不知道要什么参数、返回什么、什么时候用好的描述会写清楚功能、返回字段、触发场景、参数格式、边界条件。比如「不支持模糊查询需要精确订单号」这种边界说明能避免 AI 拿一个模糊词去调然后报错。第二件事是权限和校验。MCP Server 暴露的是真实业务接口AI 传来的参数不一定合法。在 Tool 方法里加格式校验比如订单号必须匹配ORD\d{3}不匹配直接抛带人类可读信息的异常AI 会原样转述给用户。生产环境还要加调用方权限校验别让一个 Tool 变成任意数据出口。第三件事是通道统一。如果你有多个模型要切换或者团队里几个人共用一套配置把 endpoint 统一到 TaoToken 通道会省很多事。一个 Key 走多个模型Base URL 固定https://taotoken.net/api换模型只改 Model ID。控制台里可以管理 Key 和查看用量接入文档里有各客户端的详细配置示例。长期编码场景比如你每天都要让 Claude 查业务数据、跑分析、生成报表可以考虑 Coding Plan 这类按周期计费的方案比按次调用更划算。模型对话入口适合临时验证某个模型能不能正确调用你的 Tool接入文档则在你换客户端时当参考手册用。最后说一个实际经验MCP Server 的 jar 包路径会随项目重新构建而变化每次mvn package后如果 Claude Desktop 连不上先确认配置文件里的 jar 路径是不是指向了最新的 target 目录。我习惯在配置文件里用一个固定的软链接指向最新构建产物省得每次改路径。整套东西的价值在于你写一次 ServerClaude Desktop、IDEA Claude Code、Cursor 以及任何支持 MCP 的客户端都能直接用。业务代码没动只是多了一层标准适配。