我用 Java 写个 MCP Server,让 Claude 直连业务系统,数据分析从未如此丝滑。
每个 AI 工具(Claude、GPT、Cursor)有自己的插件系统,每个业务系统想接入,就要针对每个 AI 单独写一套适配器。本文约 6000 字, 读完你将拥有一个可以被 Claude、IDEA、Cursor 等任意 AI 工具调用的业务接口。重启后打开 Claude Desktop,在输入框左下角应该能看到一个工具图标,点击可以看到你注册的三个 Tool。:基于 HTTP 的服务器推送,适
本文约 6000 字, 读完你将拥有一个可以被 Claude、IDEA、Cursor 等任意 AI 工具调用的业务接口。
一、先搞清楚:MCP 到底解决了什么问题
在 MCP 出现之前,AI 调用外部工具的方式是这样的:
每个 AI 工具(Claude、GPT、Cursor)有自己的插件系统,每个业务系统想接入,就要针对每个 AI 单独写一套适配器。M 个 AI × N 个业务系统 = M×N 个适配器,维护成本指数级爆炸。
MCP(Model Context Protocol)做的事情,和当年的 LSP(Language Server Protocol)一模一样——定义一套标准协议,把 M×N 变成 M+N。
MCP 协议规定了三类能力:
-
Tools:AI 可以主动调用的函数,有入参有出参,最常用
-
Resources:AI 可以读取的数据源,类似文件系统
-
Prompts:预定义的提示词模板
本文重点讲 Tools,这是 90% 的业务场景需要的。
二、MCP 的底层通信机制
很多人以为 MCP 是 HTTP,其实不是。
MCP 底层走的是 JSON-RPC 2.0,通信方式有两种:
stdio 模式:AI 客户端把 MCP Server 作为子进程启动,通过标准输入输出通信。本地开发用这个,零网络配置。
SSE 模式:基于 HTTP 的服务器推送,适合部署到远程服务器,多个客户端共享一个 MCP Server。
一次完整的工具调用流程是这样的:
-
AI 客户端启动时,向 MCP Server 发送
initialize请求 -
Server 返回自己支持的所有 Tools 列表(名称、描述、参数 Schema)
-
用户发起对话,AI 判断需要调用某个 Tool
-
AI 发送
tools/call请求,带上工具名和参数 -
Server 执行业务逻辑,返回结果
-
AI 把结果整合进回复
关键点:AI 是通过 Tool 的描述文字来决定要不要调用它的。描述写得好不好,直接决定 AI 能不能正确理解和使用你的接口。
三、Java 实现 MCP Server 的两条路
目前 Java 生态有两个主流选择:
|
Spring AI MCP |
MCP4J |
|
|---|---|---|
|
维护方 |
Spring 官方 |
社区 |
|
成熟度 |
1.0 正式版 |
活跃开发中 |
|
集成难度 |
低,原生 Spring Boot |
中 |
|
文档质量 |
好 |
一般 |
|
适合场景 |
已有 Spring 项目 |
独立 MCP 服务 |
本文用 Spring AI MCP,理由很简单:你大概率已经在用 Spring Boot,零额外学习成本。
四、从零搭一个 MCP Server
4.1 项目初始化
在 start.spring.io 创建项目,依赖选:
<dependencies>
<!-- Spring AI MCP Server -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
<!-- Web(SSE 模式需要) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- Spring AI BOM -->
<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>
4.2 application.yml 配置
spring:
ai:
mcp:
server:
name: my-business-mcp-server
version: 1.0.0
# stdio 模式用这个
transport: stdio
# SSE 模式改成:
# transport: sse
4.3 写第一个 Tool
假设我们有一个订单查询业务接口,让 Claude 能直接查:
@Service
publicclass OrderService {
// 模拟数据库
privatestaticfinal 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")
);
/**
* 这个注解让 Spring AI 自动把这个方法注册为 MCP Tool
* description 是关键——AI 靠这段文字决定什么时候调用它
*/
@Tool(description = "根据订单ID查询订单信息,包括客户名称、金额和配送状态")
public Order getOrderById(
@ToolParam(description = "订单ID,格式为 ORD 开头加三位数字,例如 ORD001")
String orderId) {
Order order = orders.get(orderId);
if (order == null) {
thrownew RuntimeException("订单不存在: " + orderId);
}
return order;
}
@Tool(description = "查询指定客户的所有订单列表")
public List<Order> getOrdersByCustomer(
@ToolParam(description = "客户姓名")
String customerName) {
return orders.values().stream()
.filter(o -> o.getCustomerName().equals(customerName))
.collect(Collectors.toList());
}
@Tool(description = "统计各状态订单数量,返回 PENDING/SHIPPED/DELIVERED 各自的数量")
public Map<String, Long> getOrderStatistics() {
return orders.values().stream()
.collect(Collectors.groupingBy(Order::getStatus, Collectors.counting()));
}
}
// Order 实体
public record Order(
String orderId,
String customerName,
Double amount,
String status
) {}
4.4 注册 Tool 到 MCP Server
@Configuration
public class McpConfig {
@Bean
public ToolCallbackProvider orderTools(OrderService orderService) {
// Spring AI 会自动扫描 @Tool 注解的方法
return MethodToolCallbackProvider.builder()
.toolObjects(orderService)
.build();
}
}
4.5 启动类
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}
就这些。运行 mvn spring-boot:run,一个 MCP Server 就跑起来了。

五、接入 Claude Desktop 验证
5.1 找到 Claude Desktop 配置文件
macOS:~/Library/Application Support/Claude/claude_desktop_config.json
Windows:%APPDATA%\Claude\claude_desktop_config.json
5.2 添加 MCP Server 配置
{
"mcpServers": {
"my-order-service": {
"command": "java",
"args": [
"-jar",
"/你的项目路径/target/mcp-server-1.0.0.jar"
]
}
}
}
5.3 重启 Claude Desktop,验证接入
重启后打开 Claude Desktop,在输入框左下角应该能看到一个工具图标,点击可以看到你注册的三个 Tool。
5.4 实际对话测试
现在直接用自然语言问 Claude:
你:帮我查一下 ORD002 这个订单的情况
Claude:我来帮你查询一下。[调用 getOrderById,参数:orderId="ORD002"]
查询结果:
订单号:ORD002
客户:李四
金额:¥599.0
状态:已发货(SHIPPED)
六、接入 IDEA Claude Code 插件
如果你上一篇配好了 IDEA + Claude Code,可以直接把这个 MCP Server 接进去。
在 ~/.claude.json 的 mcpServers 里加一条:
{
"mcpServers": {
"my-order-service": {
"command": "java",
"args": [
"-jar",
"/你的项目路径/target/mcp-server-1.0.0.jar"
]
}
}
}
然后在 IDEA 终端里重新启动 Claude Code,它会自动加载这个 MCP Server。
七、Tool 描述怎么写才能让 AI 准确调用
这是整篇文章最容易被忽略、但最重要的一节。
AI 决定调不调用某个 Tool,完全依赖 description 字段。描述写得差,AI 要么不知道该用,要么用错场景。
反面案例
// 坏的描述 ❌
@Tool(description = "查询订单")
public Order getOrderById(String orderId) { ... }
// 问题:
// 1. "查询订单"太模糊,AI 不知道需要什么参数
// 2. 没说返回什么信息
// 3. 没说适用场景
正面案例
// 好的描述 ✅
@Tool(description = """
根据订单ID查询单个订单的详细信息。
返回内容包括:客户姓名、订单金额、当前配送状态(PENDING待发货/SHIPPED已发货/DELIVERED已签收)。
当用户询问某个具体订单的状态、金额或客户信息时使用此工具。
""")
public Order getOrderById(
@ToolParam(description = "订单唯一标识符,格式为 ORD 开头加三位数字,例如 ORD001、ORD002")
String orderId) { ... }
描述写作原则
|
要素 |
说明 |
示例 |
|---|---|---|
|
功能说明 |
这个工具做什么 |
"根据订单ID查询订单详情" |
|
返回内容 |
返回哪些字段 |
"返回客户名、金额、状态" |
|
触发场景 |
什么情况下用 |
"当用户询问具体订单时" |
|
参数格式 |
入参的格式约束 |
"格式为 ORD 开头加三位数字" |
|
边界条件 |
什么情况不适用 |
"不支持模糊查询,需要精确订单号" |
八、生产环境注意事项
8.1 权限控制
MCP Server 暴露的是真实业务接口,必须做权限验证:
@Tool(description = "查询订单信息")
public Order getOrderById(String orderId) {
// 从 MCP 上下文获取调用方信息(Spring AI 1.0 支持)
// 根据业务规则做权限校验
validatePermission(orderId);
return orderRepository.findById(orderId);
}
8.2 参数校验
AI 传来的参数不一定合法,必须做校验:
@Tool(description = "查询订单")
public Order getOrderById(
@ToolParam(description = "订单ID") String orderId) {
// 格式校验
if (!orderId.matches("ORD\\d{3}")) {
throw new IllegalArgumentException("订单ID格式错误: " + orderId);
}
return orderService.getById(orderId);
}
8.3 返回值设计
返回 JSON 友好的对象,避免循环引用、懒加载等 JPA 常见坑:
// 不要直接返回 JPA Entity ❌
public OrderEntity getOrder(String id) { ... }
// 返回专门的 DTO ✅
public OrderDTO getOrder(String id) {
OrderEntity entity = repository.findById(id);
return OrderDTO.from(entity); // 只包含 AI 需要的字段
}
8.4 错误处理
AI 需要能读懂你的错误信息,才能告知用户:
@Tool(description = "查询订单")
public Order getOrderById(String orderId) {
return orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException(
// 错误信息要对人类友好,AI 会原样转述给用户
String.format("未找到订单 %s,请确认订单号是否正确", orderId)
));
}
九、进阶:接入真实数据库
把 OrderService 换成真实的数据库查询:
@Service
publicclass OrderService {
@Autowired
private OrderRepository orderRepository;
@Tool(description = """
查询订单详情。支持按订单ID精确查询。
返回订单的完整信息包括商品列表、价格明细、配送地址和当前状态。
""")
public OrderDetailDTO getOrderById(
@ToolParam(description = "订单ID") String orderId) {
return orderRepository.findById(orderId)
.map(OrderDetailDTO::from)
.orElseThrow(() -> new RuntimeException("订单不存在: " + orderId));
}
@Tool(description = """
统计订单数据。可以按时间范围、状态、客户维度聚合。
当用户询问"最近多少天有多少订单"、"各状态订单分布"等统计问题时使用。
""")
public OrderStatDTO getStatistics(
@ToolParam(description = "统计开始日期,格式 yyyy-MM-dd") String startDate,
@ToolParam(description = "统计结束日期,格式 yyyy-MM-dd") String endDate) {
LocalDate start = LocalDate.parse(startDate);
LocalDate end = LocalDate.parse(endDate);
return orderRepository.getStatistics(start, end);
}
}
这样,Claude 就能直接查你的生产数据库了。
十、完整架构回顾
你的业务系统
├── Spring Boot MCP Server
│ ├── OrderService (@Tool)
│ ├── UserService (@Tool)
│ └── ReportService (@Tool)
│
└── 可被以下 AI 工具直接调用:
├── Claude Desktop
├── IDEA + Claude Code
├── Cursor
└── 任何支持 MCP 的客户端
你写了一次,所有 AI 工具都能用。这就是 MCP 的价值。
总结
|
步骤 |
做了什么 |
|---|---|
|
1 |
理解 MCP 协议:JSON-RPC 2.0,stdio/SSE 两种模式 |
|
2 |
用 Spring AI MCP Starter 搭建 Server |
|
3 |
用 |
|
4 |
写好 description,让 AI 准确理解和调用 |
|
5 |
接入 Claude Desktop 和 IDEA Claude Code 验证 |
|
6 |
生产环境加权限校验、参数验证、错误处理 |
环境:Java 21 · Spring Boot 3.3 · Spring AI 1.0.0 · Claude Code v2.1.114
更多推荐



所有评论(0)