☰
用 Java + Spring AI 手写 MCP Server,让 Claude 直连业务系统做数据分析
2026/10/8 5:55:31 网站建设 项目流程

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。它定义了三类能力:Tools(AI 可主动调用的函数)、Resources(AI 可读取的数据源)、Prompts(预定义提示词模板)。日常业务里 90% 的场景用 Tools 就够了,本文也只讲 Tools。

在 MCP 出现之前,每个 AI 工具都有自己的插件体系,M 个 AI 客户端乘 N 个业务系统,适配器数量是乘积级增长。MCP 把这件事变成了 M+N:你只要写一个 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> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</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 怎么连你的 Server(stdio 是本地子进程,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 Map<String, 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 List<Order> getOrdersByCustomer( @ToolParam(description = "客户姓名,例如 张三、李四") String customerName) { return orders.values().stream() .filter(o -> o.customerName().equals(customerName)) .collect(Collectors.toList()); } @Tool(description = "统计各状态订单数量,返回 PENDING/SHIPPED/DELIVERED 各自的数量") public Map<String, Long> getOrderStatistics() { return orders.values().stream() .collect(Collectors.groupingBy(Order::status, Collectors.counting())); } }

关键在@Tool的 description。AI 完全靠这段文字判断要不要调用、怎么调用。写得太模糊,Claude 要么不用,要么用错场景。后面第五节会专门讲描述怎么写。

接着把 Tool 注册到 MCP Server。Spring AI 会自动扫描@Tool注解的方法,你只需要提供一个 ToolCallbackProvider:

@Configuration 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 方法。客户端启动时先发initialize,Server 返回自己支持的 Tool 列表(名称、描述、参数 Schema),用户对话时 AI 判断需要调用某个 Tool,就发tools/call带上工具名和参数,Server 执行完返回结果,AI 再把结果整合进回复。

如果你要把 endpoint 统一到 TaoToken 通道,确保 application.yml 里的 base-url 是https://taotoken.net/api,Key 从控制台拿。这样服务端做二次分析时,模型请求走的是同一个通道,不用为每个模型单独维护配置。

4. 验证请求:Claude Desktop 与 Claude Code 接入实测

Server 跑起来后,得让 Claude 能连上。先找 Claude Desktop 的配置文件:

macOS 路径是~/Library/Application Support/Claude/claude_desktop_config.json,Windows 是%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 再重启(不是关窗口,是退出进程)。

重启后,输入框左下角会出现一个工具图标,点开能看到你注册的三个 Tool:getOrderById、getOrdersByCustomer、getOrderStatistics。如果图标没出现,八成是配置文件路径写错或者 JSON 格式有误,用 JSON 校验工具过一遍。

现在直接用自然语言测试:

你:帮我查一下 ORD002 这个订单的情况

Claude 会先调用 getOrderById,参数 orderId="ORD002",然后返回:

订单号:ORD002 客户:李四 金额:¥599.0 状态:已发货(SHIPPED)

再试统计类问题:

你:现在各状态的订单分别有多少个?

Claude 会调用 getOrderStatistics,返回{PENDING=1, SHIPPED=1, DELIVERED=1},然后用自然语言总结给你。

如果你用 IDEA 里的 Claude Code,配置在~/.claude.json的 mcpServers 字段,格式和上面一样。加完后在 IDEA 终端重启 Claude Code,它会自动加载。这里有个三件套要配全:Base URL 指向https://taotoken.net/api,Key 用 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,多一个斜杠或者少一段都会 401;Key 有没有多余空格。用 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 目录。我习惯在配置文件里用一个固定的软链接指向最新构建产物,省得每次改路径。

整套东西的价值在于:你写一次 Server,Claude Desktop、IDEA Claude Code、Cursor 以及任何支持 MCP 的客户端都能直接用。业务代码没动,只是多了一层标准适配。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询