给 Java 应用接上 MCP:从一个小工具开始
假设你有一个用了多年的 Java 系统,里面能查订单、查库存,也能生成报表。现在想让 AI 帮忙处理这些事情。用户说一句"帮我查一下这个订单发货了没有",AI 就能去查询,再把结果告诉用户。这里先要解决一个问题:AI 怎么调用你的 Java 程序?你当然可以自己定义接口,再告诉 AI 每个接口怎么用。如果接入的工具越来越多,或者换了一个 AI 应用,这套说明和调用方式也要跟着维护。
MCP 提供了一套共同的约定:程序可以告诉调用方"我有哪些工具、每个工具需要什么参数",调用方再按约定发起请求。
AJ MCP Next 就是用 Java 实现这件事的一个小工具库。它兼容 Java 8,目前这版代码面向 2026-07-28 协议(注意不向下兼容旧版本的 MCP)。下面的例子使用配套的客户端和服务端,先把调用过程走通。
先分清客户端和服务端
名字听起来有点正式,其实很好理解。
提供工具的程序是服务端。比如,你把"查订单"这个功能开放出去,这个 Java 程序就承担服务端的角色。
发起调用的程序是客户端。它先了解有哪些工具,再选择一个工具,带上参数去调用。
我们先做一个最简单的工具:你发什么文字,它就返回什么文字。这个工具叫 echo,很适合用来确认整条链路是否正常。
整个过程只有三步:
- 服务端注册
echo工具。 - 客户端告诉服务端,要调用
echo,参数是"你好"。 - 服务端执行工具,返回
"你好"。
这里暂时用不到大模型。先把工具调用跑通,后面接 AI 应用时就容易定位问题了。
准备一下工程
准备一个 Java 8 或更高版本的 Maven 工程,把需要的依赖加到 pom.xml 中。
如果你要提供工具,让其他程序来调用,添加服务端依赖:
xml
<dependency>
<groupId>com.ajaxjs</groupId>
<artifactId>aj-mcp-next-server</artifactId>
<version>1.0</version>
</dependency>
如果你要调用别人的 MCP 服务,添加客户端依赖:
xml
<dependency>
<groupId>com.ajaxjs</groupId>
<artifactId>aj-mcp-next-client</artifactId>
<version>1.0</version>
</dependency>
本文会分别编写服务端和客户端。如果把两个示例放在同一个工程里,上面两个依赖都加上即可;如果分成两个工程,就各自添加对应的依赖。
项目还有一个 aj-mcp-next-core 模块,里面放着双方共用的消息、参数校验和编解码代码。Maven 会随客户端或服务端一起引入它,通常不用单独添加。
源码在哪里?
源码放在 GitHub 的 aj-mcp 仓库,本篇使用的是其中的 aj-mcp-2026 目录。进入以后,可以看到三个工程:
text
aj-mcp-2026/
├── mcp-core 共用的消息与协议处理
├── mcp-client 调用 MCP 服务
├── mcp-server 提供 MCP 工具和服务
└── sample
├── server Spring Boot 服务端
└── client 独立 Java 客户端
仓库里也保留着此前版本的实现,阅读本文时,记得进入 aj-mcp-2026 目录。
如果访问 GitHub 不方便,也可以使用 Gitee 仓库 或 GitCode 仓库。
写一个提供工具的 Java 程序
下面是一个完整的本地 HTTP 服务端示例:
java
import com.ajaxjs.mcp2026.core.ToolDefinition;
import com.ajaxjs.mcp2026.core.ToolResults;
import com.ajaxjs.mcp2026.server.HttpServerTransport;
import com.ajaxjs.mcp2026.server.McpServer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
public class EchoHttpServer {
public static void main(String[] args) throws Exception {
Map<String, Object> schema = new LinkedHashMap<>();
schema.put("type", "object");
schema.put("properties", Collections.singletonMap(
"text", Collections.singletonMap("type", "string")));
schema.put("required", Collections.singletonList("text"));
try (McpServer server = new McpServer("my-first-server", "1.0");
HttpServerTransport http =
new HttpServerTransport(server, 8080)) {
ToolDefinition echo = ToolDefinition
.create("echo", schema)
.with("description", "返回收到的文字");
server.tool(echo, context -> {
Map<?, ?> arguments = (Map<?, ?>)
context.request().getParams().get("arguments");
String text = (String) arguments.get("text");
return ToolResults.text(text);
});
http.start();
System.err.println("服务已启动:http://127.0.0.1:8080/mcp");
new CountDownLatch(1).await();
}
}
}
代码稍微有点长,主要是 Java 8 构建 Map 时比较啰嗦。真正与业务有关的,是中间注册工具的那几行。
先看这段:
java
schema.put("type", "object");
schema.put("properties", Collections.singletonMap(
"text", Collections.singletonMap("type", "string")));
schema.put("required", Collections.singletonList("text"));
它相当于工具的参数说明书:调用时要传一个对象,对象里有一个叫 text 的字符串,而且这个参数必须填写。对应的参数长这样:
json
{
"text": "你好"
}
这份说明也叫 Schema。刚接触时,记住它是在描述"工具需要什么输入"就够了。SDK 会据此检查参数,检查通过后才执行你的业务代码。
接着创建工具:
java
ToolDefinition echo = ToolDefinition
.create("echo", schema)
.with("description", "返回收到的文字");
echo 是调用时使用的名字,description 说明工具的用途。以后换成查订单这样的功能,描述可以写得更具体,例如"根据订单号查询发货状态"。
最后注册处理逻辑:
java
server.tool(echo, context -> {
// 读取参数,执行操作,返回结果
});
这里就是接入现有业务的地方。你可以在里面调用已经写好的 Service,读取数据库,或者访问其他接口。示例末尾的 await() 是为了让这个独立运行的程序继续等待请求。如果接入已有的 Web 应用,就由应用自身管理启动和关闭。
再写一个客户端调用它
保持服务端运行,另开一个 Java 程序:
java
import com.ajaxjs.mcp2026.client.HttpClientTransport;
import com.ajaxjs.mcp2026.client.McpClient;
import com.ajaxjs.mcp2026.core.McpResult;
import com.ajaxjs.mcp2026.core.ToolDefinition;
import java.util.Collections;
public class EchoHttpClient {
public static void main(String[] args) {
try (McpClient client = new McpClient(
new HttpClientTransport(
"http://127.0.0.1:8080/mcp"))) {
for (ToolDefinition tool : client.listTools()) {
System.out.println("发现工具:" + tool.getName());
}
McpResult result = client.callTool(
"echo",
Collections.singletonMap("text", "你好,MCP!"));
System.out.println(result.toMap());
}
}
}
客户端先通过 listTools() 查看服务端提供了哪些工具,再调用 echo。
结果中会包含一段文字内容:
json
{
"type": "text",
"text": "你好,MCP!"
}
实际返回值还带有结果类型等信息。这些信息由 SDK 按协议组织,我们写工具时使用 ToolResults.text(...) 就可以了。
到这里,一次完整的 MCP 工具调用就跑通了。
把 echo 换成自己的业务
接下来可以把 echo 改成 query_order,参数从 text 改成 orderId,处理逻辑换成现有的订单查询方法。工具需要做的事情仍然差不多:读取参数,调用业务方法,把结果整理成调用方能理解的内容。比如查到订单以后,可以返回:
订单 A20261015001 已于今天上午发货,承运方为某某快递,运单号为......
如果订单不存在,也可以给出明确的业务结果:
java
return ToolResults.error("没有找到这个订单,请检查订单号");
ToolResults.error() 表示工具已经执行,但业务上没能完成用户想做的事。像订单不存在、库存不足,都可以用这种方式说明原因。
AI 在哪里?
前面的客户端是我们自己写的 Java 程序,所以工具名和参数也是写在代码里的。
接入支持对应协议的 AI 应用以后,通常由应用里的 MCP 客户端获取工具说明,再交给模型判断什么时候需要调用、该填写什么参数。实际执行仍然由你的 Java 服务负责。因此,工具的名字、描述和参数说明值得认真写。一个叫 query_order、描述清楚用途的工具,比一个叫 execute、只写着"执行操作"的工具容易理解得多。
还有一点需要留意:不同客户端支持的 MCP 协议版本可能不同。接入前,要确认对方支持当前项目使用的版本。
先在本地把一个工具用起来
这个示例默认只监听 127.0.0.1,适合本机练习。准备开放给其他机器访问时,还需要配置监听地址、身份认证,并通过 HTTPS 提供服务。
第一次尝试,可以先挑一个现成的查询功能:查一条订单、读一份配置,或者获取一份报表。业务逻辑已经有了,只要补上工具说明和处理入口,就能看到 MCP 在自己的系统里怎么用。
等这个工具用顺手,再考虑进度通知、订阅,以及需要用户补充信息才能继续的调用过程,会自然得多。