当 RPC 框架学会说 AI 的语言:Jaws MCP 桥接与 REST 桥接深度解析
一、引言:我的 AI 能力版图
在深入本文之前,先交代一下我目前在 Java AI 领域的四项目布局。这四个开源项目各有侧重,共同构成了一个从框架原理到工程落地的完整探索路径:
| 项目 | 聚焦方向 | 核心框架 | 语言 |
|---|---|---|---|
| whatsmars | LangChain4j 深度探索 | LangChain4j | Java |
| spacecloud | Spring AI 2.0 微服务应用 | Spring AI 2.0 | Java |
| babi | AI Coding Agent 工程化 | AgentScope / LangGraph | Java / Python |
| jaws | RPC 转 MCP 服务 | MCP Java SDK | Java |
其中 whatsmars 深耕 LangChain4j 生态原理,spacecloud 聚焦 Spring AI 2.0 在微服务场景的应用,babi 探索 Agent 工程化落地,而 jaws 作为一个基于 Netty 的高性能 RPC 框架,最近完成了一次重要进化——将 RPC 服务自动暴露为 MCP Tools 和 REST API,打通了传统后端服务与 AI Agent 之间的调用壁垒。
项目地址:
- https://github.com/javahongxi/whatsmars
- https://github.com/javahongxi/spacecloud
- https://github.com/javahongxi/babi
- https://github.com/javahongxi/jaws
今天本文要拆解的,就是 jaws 新增的 MCP 桥接和 REST 桥接两大特性。
二、jaws 框架回顾与 MCP 协议背景
2.1 jaws 框架回顾
jaws 是一个基于 Java 17 和 Netty 的高性能 RPC 框架,经过持续迭代,已具备相当完整的微服务通信能力。在协议层,jaws 定义了自定义二进制协议,支持 fastjson2 和 hessian2 两种序列化方式,底层基于 Netty 的 EventLoop 实现高性能网络传输。在服务治理层,jaws 支持 ZooKeeper 和 Nacos 两种注册中心,内置心跳续约与失败重连机制,提供 random、roundRobin、leastActive、shortestResponse、consistentHash 五种负载均衡策略,以及 failover(失败切换)、failfast(快速失败)、failback(异步重试)三种高可用容错模式。在扩展性方面,所有核心组件(Protocol、Cluster、LoadBalance、Filter、Serialization 等)均通过 SPI 机制可插拔替换。此外还提供优雅停机(四阶段停机保证零损伤发布)、泛化调用(无需接口 JAR 包即可发起调用)、配置热更新(运行时动态调整负载均衡、容错策略、超时、重试等参数)、流量调度(跨分组流量调度,支持按权重合并多 group 服务、IP 路由规则,灰度发布利器)等高级特性。
2.2 MCP 协议与 AI Agent 的调用困境
MCP(Model Context Protocol)是 Anthropic 于 2024 年底发布的开放协议,旨在为 AI 模型(LLM)提供一种标准化的外部工具调用方式。在 MCP 出现之前,每个 AI 应用需要为不同的工具(数据库查询、文件操作、API 调用等)编写定制化的集成代码,缺乏统一标准。MCP 通过 JSON-RPC 2.0 定义了一套 Client-Server 交互协议,Server 声明自己能提供的 Tools(工具)、Resources(资源)和 Prompts(提示模板),Client(通常是 AI Agent 运行时)在对话过程中根据需要调用这些能力。
MCP 协议的交互流程分为几个阶段:首先是 initialize 握手,Client 和 Server 协商协议版本和各自的能力声明;接着 Client 发送 initialized 通知表示握手完成;此后 Client 可以随时调用 tools/list 获取可用工具列表,调用 tools/call 执行某个工具。Server 也可以通过 notifications/tools/list_changed 主动通知 Client 工具列表发生了变化。MCP 支持两种传输方式:stdio(标准输入输出,适用于本地进程间通信)和 HTTP+SSE(Streamable HTTP,适用于远程网络通信)。jaws 的 MCP 桥接使用的是后者,具体基于 MCP Java SDK 2.0.0 的 HttpServletStreamableServerTransportProvider,以 Servlet 方式嵌入 Spring Boot 应用。
理解了 MCP 协议的设计意图,就能明白 jaws MCP 桥接要解决的核心问题:在 AI 时代,Agent 是新的调用方,它不会也不应该理解 Jaws 的二进制协议、服务注册中心、负载均衡等细节。Agent 只会说 MCP——一种基于 JSON-RPC 的标准工具调用协议。如何让已有的 RPC 服务以零改造的方式出现在 Agent 的工具列表中?答案就是在 Jaws 框架内部架一座桥,自动将 RPC 方法翻译成 MCP Tool。
三、MCP Java SDK 架构深度解析
jaws 的 MCP 桥接建立在 MCP Java SDK(io.modelcontextprotocol.sdk)之上。要理解桥接的设计决策,有必要先深入 SDK 的架构。SDK 版本为 2.0.0,由 Anthropic 维护,Java 17 编译,采用 MIT 协议开源。
3.1 模块结构:核心与 JSON 绑定的解耦
SDK 采用多模块设计,核心模块 mcp-core 不依赖任何具体的 JSON 库——它只引用了 jackson-annotations(注解接口),实际的数据绑定通过 SPI 机制在 mcp-json-jackson2 或 mcp-json-jackson3 中实现。这意味着如果项目已经用了 Jackson 2,就引入 jackson2 绑定;如果用 Jackson 3,就引入 jackson3 绑定;理论上甚至可以自己实现一个 Gson 或 JSON-B 的绑定模块。jaws 选择的是 mcp-json-jackson2,配合 fastjson2 作为业务序列化层,两者各司其职。
模块概览如下:
mcp-bom → BOM,统一版本管理
mcp → 便捷聚合包(mcp-core + mcp-json-jackson3)
mcp-core → 核心SDK,传输层无关,JSON通过SPI注入
mcp-json-jackson2 → Jackson 2 绑定 + JSON Schema Validator
mcp-json-jackson3 → Jackson 3 绑定 + JSON Schema Validator
mcp-test → 测试基础设施
mcp-core 对外暴露的核心包是 io.modelcontextprotocol.server(服务端 API)和 io.modelcontextprotocol.spec(协议 schema)。值得注意的是 mcp-core 的 pom.xml 中 jakarta.servlet-api 的 scope 是 provided——Servlet 传输层编译时需要它,但运行时由宿主容器提供。这保证了 mcp-core 可以嵌入 Spring Boot、纯 Servlet 容器甚至 OSGi 环境。
3.2 McpServer:六种工厂方法,三种服务器家族
McpServer 是 SDK 的入口,它是一个接口,通过六个静态工厂方法返回不同的 Builder:
| 工厂方法 | 传输层类型 | 返回的 Builder | 会话模型 |
|---|---|---|---|
sync(McpServerTransportProvider) |
经典单会话 | SingleSessionSyncSpecification |
一个 McpServerSession |
async(McpServerTransportProvider) |
经典单会话 | SingleSessionAsyncSpecification |
一个 McpServerSession |
sync(McpStreamableServerTransportProvider) |
Streamable HTTP | StreamableSyncSpecification |
多个 McpStreamableServerSession |
async(McpStreamableServerTransportProvider) |
Streamable HTTP | StreamableServerAsyncSpecification |
多个 McpStreamableServerSession |
sync(McpStatelessServerTransport) |
无状态 | StatelessSyncSpecification |
无会话 |
async(McpStatelessServerTransport) |
无状态 | StatelessAsyncSpecification |
无会话 |
这三种服务器家族对应 MCP 协议的三种传输模式:经典单会话(STDIO)、Streamable HTTP 多会话、无状态。jaws 使用的是第三种工厂方法 McpServer.sync(McpStreamableServerTransportProvider),因为 jaws 的 MCP 服务部署在 Servlet 容器中,需要支持多个 AI Agent 客户端同时连接。
3.3 McpSyncServer:异步核心的阻塞外壳
SDK 的核心实现是 McpAsyncServer——一个基于 Project Reactor 的响应式服务器。McpSyncServer 只是对 McpAsyncServer 的一层薄封装,每个方法都是 asyncServer.X(...).block():
public class McpSyncServer {
private final McpAsyncServer asyncServer;
private final boolean immediateExecution;
public void addTool(McpServerFeatures.SyncToolSpecification toolHandler) {
this.asyncServer
.addTool(McpServerFeatures.AsyncToolSpecification.fromSync(toolHandler, this.immediateExecution))
.block();
}
// ...每个方法都是 asyncServer.X(...).block()
}
McpServerFeatures.AsyncToolSpecification.fromSync() 做的转换很关键——它将同步的 BiFunction 包装成 Mono.fromCallable(...),并根据 immediateExecution 标志决定是否切换到 Schedulers.boundedElastic() 线程池执行。默认 immediateExecution=false,意味着每个同步 handler 都会在独立的弹性线程池中执行,避免阻塞 Netty 的事件循环线程。jaws 在构建时使用了默认配置,这确保了即使某个 RPC 调用耗时较长,也不会阻塞其他 MCP 会话。
3.4 HttpServletStreamableServerTransportProvider:Servlet 即传输层
这是 jaws MCP 桥接依赖的核心传输层实现。它本身就是一个 HttpServlet,标注了 @WebServlet(asyncSupported = true),可以直接注册到 Servlet 容器中。
传输层维护了一个 ConcurrentHashMap<String, McpStreamableServerSession> sessions,以 Mcp-Session-Id 为 key 管理所有活跃会话。它的 doPost 方法是整个 MCP 协议交互的入口,处理逻辑分为几个分支:
首先是 initialize 请求——这是唯一不需要 Mcp-Session-Id 头的请求。Servlet 调用 sessionFactory.startSession(initializeRequest) 创建新会话,将会话 ID 通过 Mcp-Session-Id 响应头返回给客户端。此后该会话就存入 sessions 映射表,等待后续请求。
对于非 initialize 请求,Servlet 校验 Mcp-Session-Id 头并查找会话。如果是 JSON-RPC Request(如 tools/call),Servlet 会开启一个 SSE 流——设置 response.setContentType("text/event-stream"),调用 request.startAsync() 开启异步上下文,然后通过 session.responseStream(jsonrpcRequest, sessionTransport) 执行注册的请求处理器,将结果以 SSE event 的形式写回。
这个设计的精妙之处在于:一个 HTTP POST 请求既可以是普通的 JSON 响应(initialize 阶段),也可以是 SSE 流式响应(tool 调用阶段),完全由消息类型决定。对于 AI Agent 来说,只需用标准 HTTP 客户端就能完成所有 MCP 交互。
doGet 方法用于开启长生命周期的监听流——客户端通过 GET 请求打开一条 SSE 通道,服务器后续的主动通知(如 notifications/tools/list_changed)和服务器发起的请求(如 sampling/createMessage)都通过这条通道推送。doDelete 方法用于终止会话。
3.5 McpServerFeatures:工具注册的函数式 API
SDK 使用 record 类型定义工具规格。同步版本的 SyncToolSpecification 接受一个 BiFunction<McpSyncServerExchange, CallToolRequest, CallToolResult> 作为 handler——这是一个标准的函数式接口:
public record SyncToolSpecification(
McpSchema.Tool tool,
BiFunction<McpSyncServerExchange, McpSchema.CallToolRequest, McpSchema.CallToolResult> callHandler) {
}
jaws 在 JawsMcpServer.buildSyncToolSpecifications() 中构建的正是这个 record,handler 用 lambda 表达式 (exchange, request) -> JawsMcpToolHandler.handleToolCall(spec, request) 实现。McpSyncServerExchange 是每次请求的上下文对象,提供客户端信息、会话 ID、传输层上下文,以及服务器→客户端的反向调用能力(如 createMessage 请求模型生成、listRoots 列出客户端根目录)。
3.6 McpSchema:纯 record 的 JSON-RPC 世界
McpSchema 是一个 6400 多行的 final 类,内部定义了 MCP 协议涉及的所有数据结构,全部使用 Java record 实现。核心类型包括:
JSONRPCRequest 是 JSON-RPC 请求的 record,有 jsonrpc、method、id、params 四个字段。MCP 比 JSON-RPC 2.0 更严格——请求 ID 必须存在且只能是 String 或 Integer/Long,不允许 null ID。
Tool record 定义了 MCP 工具:name(工具名)、description(描述)、inputSchema(输入参数的 JSON Schema,类型为 Map<String, Object>)、outputSchema(可选的输出 Schema,2025-06-18 协议版本引入)、annotations(工具注解,含 readOnlyHint、destructiveHint、idempotentHint 等提示布尔值)。jaws 在构建 Tool 时设置了 name、description 和 inputSchema,未设置 outputSchema 和 annotations。
CallToolResult 是工具调用的返回值,包含 content(List<Content>,内容列表)、isError(是否为错误)、structuredContent(结构化输出,2025-06-18 引入)。Content 是一个多态接口,子类型包括 TextContent、ImageContent、AudioContent、EmbeddedResource 和 ResourceLink。jaws 的 JawsMcpToolHandler 使用 CallToolResult.builder().addTextContent(text).isError(false).build() 构建成功结果,将 RPC 返回值序列化为文本内容。
3.7 协议版本协商
SDK 支持四个协议版本:2024-11-05(初始版本)、2025-03-26(引入 Streamable HTTP)、2025-06-18(引入 Tool outputSchema 和 structuredContent)、2025-11-25(最新)。协商逻辑在 McpAsyncServer 的 initialize 请求处理器中:服务器默认返回支持的最高版本,但如果客户端请求的版本在支持列表中,则回显客户端版本。这保证了向前兼容——老客户端可以继续用老协议版本通信。
3.8 会话生命周期与 Mcp-Session-Id
Streamable HTTP 模式下,会话 ID 是整个交互的核心纽带。完整生命周期如下:
客户端 POST 一个 initialize 请求(不带 session ID),服务器创建 McpStreamableServerSession,生成 UUID 作为会话 ID,通过 Mcp-Session-Id 响应头返回。客户端后续所有请求都必须携带这个头。服务器通过 sessions.get(sessionId) 查找会话,找不到则返回 404。
客户端可以通过 GET 请求打开监听流——服务器在 McpStreamableServerSession.listeningStream() 中存储 SSE 传输引用,后续所有服务器主动推送都通过这条通道。也可以通过 DELETE 请求显式终止会话。
SDK 还支持 keepAliveInterval 配置——定期向所有活跃会话发送 ping,防止连接因超时被代理或防火墙断开。这在长时间没有 tool 调用的 AI 对话场景中特别有用。
3.9 一次 tools/call 请求的完整链路
将上面所有组件串联起来,当 AI Agent 调用一个 MCP Tool 时的完整流程如下:
客户端 POST 到 /mcp,带 Mcp-Session-Id 头,body 是 {"jsonrpc":"2.0","method":"tools/call","id":42,"params":{"name":"DemoService_hello","arguments":{"arg0":"world"}}}。
Servlet 的 doPost 反序列化消息,识别为 JSONRPCRequest(非 initialize),查找会话,开启 SSE 流,调用 session.responseStream(jsonrpcRequest, sessionTransport)。
McpStreamableServerSession 查找 requestHandlers.get("tools/call")——这个 handler 是 McpAsyncServer 在 prepareRequestHandlers() 中注册的 toolsCallRequestHandler()。它将 params 反序列化为 CallToolRequest,在 this.tools 列表中查找名为 DemoService_hello 的工具,校验输入参数,然后调用 toolSpecification.callHandler().apply(exchange, callToolRequest)。
这个 handler 正是 jaws 注册的 lambda (exchange, request) -> JawsMcpToolHandler.handleToolCall(spec, request)。它将 MCP 请求转化为 Jaws RPC 调用,拿到结果后构建 CallToolResult 返回。
SDK 将结果映射为 JSONRPCResponse.result(request.id(), result),通过 SSE 流写出一帧 event: message\ndata: <jsonrpcResponse>,然后关闭 per-request transport,Servlet 的 AsyncContext 完成,HTTP 响应结束。
这条链路穿越了 SDK 的每一层:Servlet 传输层 → 流式会话 → 请求处理器映射 → McpAsyncServerExchange → 工具 handler → JSON SPI 序列化 → McpSchema record。
四、架构设计:协议无关的桥接抽象
在直接看 MCP 或 REST 实现之前,有必要先理解 jaws 桥接架构的核心设计——一个协议无关的中间抽象层。这层抽象位于 jaws-core 模块中,包含三个关键类:ServiceMethodSpec、ArgumentConverter 和 JsonSchemaGenerator。它们的职责是将"一个 Java 方法"转化为一种与传输协议无关的元数据描述,使得 MCP、REST 乃至未来的 GraphQL、gRPC 网关都能复用同一套逻辑。
4.1 ServiceMethodSpec:方法规格的统一表示
ServiceMethodSpec 是整个桥接架构的核心数据结构,它持有调用一个 RPC 方法所需的全部信息:
public class ServiceMethodSpec {
/** 对外部 tool/action 名称,如 "DemoService_getUser" */
private final String actionName;
/** Jaws 服务接口全限定名 */
private final String interfaceName;
/** 方法名 */
private final String methodName;
/** 参数类型描述(Jaws parameterDesc 格式) */
private final String[] parameterTypes;
/** Java Method 对象,用于参数转换 */
private final Method method;
/** 能执行此方法的 Jaws Provider */
private final Provider<?> provider;
/** 方法参数,用于 Schema 生成 */
private final Parameter[] parameters;
// ...
}
这个设计的关键在于 provider 字段——它直接持有 Jaws 的 Provider<?> 引用,使得无论是 MCP Tool 调用还是 REST API 调用,最终都通过同一个 Provider.call() 方法执行,走的是完全一致的 RPC 调用链路。
actionName 的生成规则也值得注意:对于非重载方法,命名为 InterfaceName_methodName;对于重载方法,追加数字后缀(如 DemoService_save_1、DemoService_save_2),确保每个方法有唯一的标识。
4.2 ArgumentConverter:参数类型转换
外部请求(MCP JSON 参数或 REST JSON body)需要转换为 Java 方法的实际参数。ArgumentConverter 处理这个过程:
public static Object[] convertArguments(Parameter[] parameters, Map<String, Object> arguments) {
if (parameters.length == 0) {
return new Object[0];
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
String paramName = param.isNamePresent() ? param.getName() : null;
Object value = null;
if (arguments != null) {
if (paramName != null && arguments.containsKey(paramName)) {
value = arguments.get(paramName);
} else if (arguments.containsKey("arg" + i)) {
value = arguments.get("arg" + i);
} else if (arguments.size() == 1) {
value = arguments.values().iterator().next();
}
}
args[i] = convertArgument(value, param.getType());
}
return args;
}
参数匹配有三重策略:优先用参数名(需编译时加 -parameters),其次用 arg0/arg1 的位置约定,最后对单参数方法直接取唯一的值。类型转换覆盖了所有基本类型和包装类,复杂 POJO 则通过 fastjson2 做 JSON→对象的转换。
4.3 JsonSchemaGenerator:类型到 Schema 的自动推导
MCP 协议要求每个 Tool 声明 inputSchema,REST API 的服务详情端点也返回 Schema 信息。JsonSchemaGenerator 负责从 Java 类型自动推导 JSON Schema(draft 2020-12),支持基本类型、日期时间类型、枚举、数组、集合、Map 和复杂 POJO,并通过递归字段展开处理嵌套对象(最大深度 5 层):
private static Map<String, Object> generateObjectSchema(Class<?> clazz, int depth) {
Map<String, Object> schema = new LinkedHashMap<>();
schema.put("type", "object");
Map<String, Object> properties = new LinkedHashMap<>();
List<String> required = new ArrayList<>();
Class<?> current = clazz;
while (current != null && current != Object.class) {
for (Field field : current.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
continue;
}
Map<String, Object> fieldSchema = generateTypeSchema(field.getGenericType(), depth + 1);
properties.put(field.getName(), fieldSchema);
if (!field.getType().isPrimitive()) {
required.add(field.getName());
}
}
current = current.getSuperclass();
}
if (!properties.isEmpty()) {
schema.put("properties", properties);
}
if (!required.isEmpty()) {
schema.put("required", required);
}
return schema;
}
JsonSchemaGenerator 内部维护了一个静态映射表 PRIMITIVE_SCHEMAS,预定义了所有 Java 基本类型和包装类到 JSON Schema 类型的对应关系:boolean 映射为 "type": "boolean",int/long/short/byte 映射为 "type": "integer",float/double/BigDecimal/BigInteger 映射为 "type": "number",String/char 以及所有日期时间类型(Date、LocalDate、LocalDateTime、Instant 等)映射为 "type": "string"。枚举类型会自动提取所有枚举常量生成 "enum" 约束。集合类型(List、Set)映射为 "type": "array" 并递归推导泛型元素类型。Map 类型映射为 "type": "object" 并用 additionalProperties 描述值的 Schema。复杂 POJO 则通过 generateObjectSchema 递归展开所有字段,包括父类字段,最大递归深度限制为 5 层以防止循环引用导致的栈溢出。
注意它还会遍历父类字段,并自动将非基本类型字段标记为 required,确保 AI Agent 调用时能获得足够的参数约束信息。这套 Schema 生成逻辑对于 MCP 和 REST 是完全相同的——MCP 用它构建 Tool 的 inputSchema,REST 用它在方法详情端点返回参数结构信息,真正做到了一处生成、多处使用。
五、MCP 桥接深度解析
有了上面的基础抽象,MCP 桥接的实现就变得清晰了。整体调用链路如下:
AI Agent ──MCP (HTTP+SSE)──▶ jaws-mcp Servlet ──Jaws RPC──▶ Provider
5.1 JawsMcpServer:构建与注册
JawsMcpServer 是 MCP 桥接的核心类,采用 Builder 模式。它基于 MCP Java SDK 2.0.0,使用 HttpServletStreamableServerTransportProvider 作为传输层。核心流程是:扫描服务接口的所有 public 方法 → 为每个方法生成 ServiceMethodSpec → 由 spec 构建 McpServerFeatures.SyncToolSpecification → 注册到 McpSyncServer。
public static List<McpServerFeatures.SyncToolSpecification> buildSyncToolSpecifications(
List<ServiceMethodSpec> methodSpecs) {
List<McpServerFeatures.SyncToolSpecification> specifications = new ArrayList<>();
for (ServiceMethodSpec spec : methodSpecs) {
Map<String, Object> inputSchema = JsonSchemaGenerator.generateMethodSchema(spec.getParameters());
McpSchema.Tool tool = McpSchema.Tool.builder(spec.getActionName(), inputSchema)
.description(buildMethodDescription(
getInterfaceClass(spec.getInterfaceName()),
spec.getMethod(),
spec.getParameterTypes()))
.build();
McpServerFeatures.SyncToolSpecification toolSpec = new McpServerFeatures.SyncToolSpecification(
tool,
(exchange, request) -> JawsMcpToolHandler.handleToolCall(spec, request)
);
specifications.add(toolSpec);
}
return specifications;
}
Tool 的描述信息格式为 DemoService.hello(String): String,这样 AI Agent 既能看到方法签名,也能看到返回类型,便于决定何时调用。
5.2 JawsMcpToolHandler:调用执行
当 AI Agent 调用一个 MCP Tool 时,JawsMcpToolHandler 负责将 MCP 请求转化为 Jaws RPC 调用,并将结果转回 MCP 响应:
public static McpSchema.CallToolResult handleToolCall(ServiceMethodSpec spec, McpSchema.CallToolRequest request) {
try {
Map<String, Object> arguments = request.arguments();
// 构建 Jaws 请求
DefaultRequest jawsRequest = new DefaultRequest();
jawsRequest.setInterfaceName(spec.getInterfaceName());
jawsRequest.setMethodName(spec.getMethodName());
jawsRequest.setParametersDesc(buildParametersDesc(spec.getParameterTypes()));
// 通过共享的 ArgumentConverter 转换参数
Object[] args = ArgumentConverter.convertArguments(spec.getParameters(), arguments);
jawsRequest.setArguments(args);
// 调用 Provider
Provider<?> provider = spec.getProvider();
Response response = provider.call(jawsRequest);
// 转换响应
if (response instanceof DefaultResponse dr) {
if (dr.getException() != null) {
return createErrorResult(dr.getException());
}
return createSuccessResult(dr.getValue());
}
// ...
} catch (Exception e) {
log.error("[JawsMcp] Tool call failed: {}", spec.getActionName(), e);
return createErrorResult(e);
}
}
成功结果通过 CallToolResult.builder().addTextContent(text).isError(false).build() 返回,对象类型的结果会被 fastjson2 序列化为 JSON 字符串;异常则标记 isError(true) 返回错误信息。这种设计使得 AI Agent 既能读取基本类型返回值,也能解析复杂对象。
5.3 JawsMcpAutoConfiguration:Spring Boot 自动装配
MCP 桥接通过 jaws-mcp-spring-boot-starter 实现开箱即用。JawsMcpAutoConfiguration 的设计很有讲究——它在 ContextRefreshedEvent 时才动态注册 Tools,因为此时 Jaws 的服务导出(JawsBootstrap)已经完成,所有 ServiceBean 都已就绪:
@AutoConfiguration(after = JawsAutoConfiguration.class)
@EnableConfigurationProperties(JawsMcpProperties.class)
@ConditionalOnProperty(prefix = JawsMcpProperties.CONFIG_PREFIX, name = "enabled",
havingValue = "true", matchIfMissing = true)
public class JawsMcpAutoConfiguration {
@Bean
public ApplicationListener<ContextRefreshedEvent> jawsMcpToolRegistrar(
ApplicationContext applicationContext, McpSyncServer mcpSyncServer) {
return event -> {
Map<String, ServiceBean> serviceBeans = applicationContext.getBeansOfType(ServiceBean.class);
// ...
for (ServiceBean serviceBean : serviceBeans.values()) {
Class<?> interfaceClass = serviceBean.getInterface();
String interfaceName = interfaceClass.getName();
// 白黑名单过滤
if (!includeSet.isEmpty() && !includeSet.contains(interfaceName)) continue;
if (excludeSet.contains(interfaceName)) continue;
Provider<?> provider = exporters.get(0).getProvider();
List<ServiceMethodSpec> methodSpecs = JawsMcpServer.createMethodSpecs(interfaceClass, provider);
List<McpServerFeatures.SyncToolSpecification> syncSpecs =
JawsMcpServer.buildSyncToolSpecifications(methodSpecs);
for (McpServerFeatures.SyncToolSpecification spec : syncSpecs) {
mcpSyncServer.addTool(spec);
toolCount++;
}
}
if (toolCount > 0) {
mcpSyncServer.notifyToolsListChanged();
}
};
}
}
注意 @AutoConfiguration(after = JawsAutoConfiguration.class) 确保在 Jaws 自动装配之后加载,而 ContextRefreshedEvent 监听器使用最低优先级,确保在服务导出完成后执行。注册完成后调用 notifyToolsListChanged() 通知 MCP 客户端 Tool 列表已更新——这是 MCP 协议的 tools/listChanged 通知机制。
5.4 配置与使用
只需引入依赖并添加配置即可启用 MCP 桥接:
<dependency>
<groupId>org.hongxi</groupId>
<artifactId>jaws-mcp-spring-boot-starter</artifactId>
<version>${jaws.version}</version>
</dependency>
jaws:
mcp:
enabled: true
server-name: jaws-mcp-demo
server-version: 1.0.0
endpoint: /mcp
include-services: # 白名单,空则暴露全部
- org.hongxi.jaws.sample.api.DemoService
exclude-services: # 黑名单,优先级高于白名单
- org.hongxi.jaws.sample.api.OrderService
5.5 示例服务与 curl 验证
示例服务 DemoService 涵盖了各种方法签名——基本类型参数、复杂对象参数、集合返回、Map 返回、方法重载、异步返回:
public interface DemoService {
String hello(String name);
User rename(User user, String name);
List<User> getUsers();
Map<String, User> map(List<User> users);
void save(Contacts contacts);
int save(List<Contacts> contactsList); // 重载
CompletableFuture<String> helloAsync(String name);
}
启动示例(需要 Nacos 在 127.0.0.1:8848 运行):
./mvnw spring-boot:run -pl jaws-samples/jaws-sample-provider-mcp
MCP 端点可用:http://localhost:8082/mcp。完整的 MCP 协议交互流程如下:
# 1. 初始化会话(响应头中返回 Mcp-Session-Id)
curl -s -D - -X POST http://localhost:8082/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}}}'
# 2. 发送 initialized 通知
curl -s -X POST http://localhost:8082/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3. 列出所有 Tools
curl -s -X POST http://localhost:8082/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# 4. 调用 Tool
curl -s -X POST http://localhost:8082/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"DemoService_hello","arguments":{"arg0":"Jaws MCP"}}}'
tools/list 的响应中会包含所有自动注册的 Tool,以 DemoService_hello 为例:
{
"name": "DemoService_hello",
"description": "DemoService.hello(String): String",
"inputSchema": {
"type": "object",
"properties": {"arg1836019240": {"type": "string"}},
"required": ["arg1836019240"],
"additionalProperties": false
}
}
六、REST 桥接深度解析
如果说 MCP 桥接面向 AI Agent,那 REST 桥接面向的是传统 HTTP 客户端——无需理解 MCP 协议,通过简单的 HTTP/JSON 即可调用后端 RPC 服务。
6.1 RestInvokeServlet:轻量实现
RestInvokeServlet 是一个纯粹的 HttpServlet 实现,无 Spring MVC 依赖。它维护两个注册表:serviceRegistry(按接口名索引的方法列表)和 actionRegistry(按 actionName 索引的快速查找表)。核心的 handleInvoke 方法如下:
private void handleInvoke(String path, HttpServletRequest req, HttpServletResponse resp) throws IOException {
String interfaceName = path.substring(0, lastSlash);
String methodName = path.substring(lastSlash + 1);
ServiceMethodSpec spec = findMethodSpec(interfaceName, methodName);
if (spec == null) {
sendError(resp, HttpServletResponse.SC_NOT_FOUND, "Method not found");
return;
}
Map<String, Object> arguments = readRequestBody(req);
Object[] args = ArgumentConverter.convertArguments(spec.getParameters(), arguments);
DefaultRequest jawsRequest = new DefaultRequest();
jawsRequest.setInterfaceName(spec.getInterfaceName());
jawsRequest.setMethodName(spec.getMethodName());
jawsRequest.setParametersDesc(buildParametersDesc(spec.getParameterTypes()));
jawsRequest.setArguments(args);
Provider<?> provider = spec.getProvider();
Response response = provider.call(jawsRequest);
Map<String, Object> result = new LinkedHashMap<>();
result.put("actionName", spec.getActionName());
if (response.getException() != null) {
result.put("success", false);
result.put("error", response.getException().getMessage());
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} else {
result.put("success", true);
if (response instanceof DefaultResponse dr) {
result.put("data", dr.getValue());
}
resp.setStatus(HttpServletResponse.SC_OK);
}
JSON.writeTo(resp.getOutputStream(), result);
}
可以看到,REST 和 MCP 的调用路径高度一致——都通过 ArgumentConverter 转换参数,都构建 DefaultRequest 调用 Provider.call(),差异只在于请求来源和响应格式。
6.2 三个 API 端点
REST 桥接提供三个端点,对应 RestInvokeServlet 的 doGet 和 doPost 方法:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /rest/services |
列出所有已注册服务及其方法 |
| GET | /rest/services/{interfaceName} |
查看指定服务的方法详情(含 JSON Schema) |
| POST | /rest/invoke/{interfaceName}/{methodName} |
调用指定方法,body 为 JSON 参数 |
GET 端点返回的方法详情中包含 inputSchema,这样调用方无需查阅文档就能知道参数结构:
# 列出所有服务
curl -s http://localhost:8083/rest/services
# 调用方法
curl -s -X POST http://localhost:8083/rest/invoke/org.hongxi.jaws.sample.api.DemoService/hello \
-H "Content-Type: application/json" \
-d '{"arg0": "Jaws REST"}'
响应:
{
"actionName": "DemoService_hello",
"interfaceName": "org.hongxi.jaws.sample.api.DemoService",
"methodName": "hello",
"success": true,
"data": "Hello, Jaws REST"
}
6.3 JawsRestAutoConfiguration
REST 桥接的自动装配与 MCP 桥接如出一辙——同样的 @AutoConfiguration(after = JawsAutoConfiguration.class)、同样的 ContextRefreshedEvent 监听、同样的白黑名单过滤:
@Bean
public ApplicationListener<ContextRefreshedEvent> jawsRestServiceRegistrar(
ApplicationContext applicationContext, RestInvokeServlet restInvokeServlet) {
return event -> {
Map<String, ServiceBean> serviceBeans = applicationContext.getBeansOfType(ServiceBean.class);
for (ServiceBean serviceBean : serviceBeans.values()) {
// 白黑名单过滤...
Provider<?> provider = exporters.get(0).getProvider();
restInvokeServlet.registerService(interfaceClass, provider);
serviceCount++;
}
};
}
七、架构思考:协议无关的桥接抽象
回顾整个实现,最值得品味的设计是 jaws-core 中提取的三个共享组件。MCP 桥接和 REST 桥接在以下环节完全复用同一套逻辑:
方法注册:两者都调用 JawsMcpServer.createMethodSpecs() 生成 ServiceMethodSpec 列表,方法命名、重载处理的逻辑只有一份实现。
参数转换:两者都通过 ArgumentConverter.convertArguments() 将外部参数转为 Java 对象数组,基本类型、包装类、复杂 POJO 的转换逻辑统一管理。
Schema 生成:两者都通过 JsonSchemaGenerator.generateMethodSchema() 生成 JSON Schema,MCP 用它作为 Tool 的 inputSchema,REST 用它作为方法详情的返回信息。
RPC 调用:两者都构建 DefaultRequest 并调用 Provider.call(),走的是完全相同的 Jaws RPC 调用链路。
这意味着如果未来需要增加 GraphQL 网关或 gRPC 网关,只需新建一个 jaws-graphql 模块,编写 GraphQL 协议到 ServiceMethodSpec 的适配逻辑即可,核心的参数转换、Schema 生成、RPC 调用全部复用。这种"协议无关中间层 + 协议特定适配层"的架构,使得扩展新协议的成本降到最低。
从另一个角度看,ServiceMethodSpec 本质上是在做一种"接口级别的反射元数据封装"。它把 Java 方法拆解为 name、参数类型、参数对象、Provider 引用的组合,相当于一个轻量的"服务目录"。而 MCP 和 REST 只是这个目录的两种不同"视图"。
八、总结
jaws 的这次更新,核心思路是把传统 RPC 框架的服务能力以协议无关的方式桥接到 AI 生态和 HTTP 生态。MCP 桥接让 AI Agent 能通过标准 MCP 协议直接调用后端 RPC 服务,REST 桥接让传统 HTTP 客户端能通过简单的 JSON API 调用同样的服务。两者共享 ServiceMethodSpec、ArgumentConverter、JsonSchemaGenerator 三个核心组件,实现了"一次注册,多协议暴露"。
从我的四项目版图来看,jaws 的 MCP 桥接补上了"后端服务 → AI Agent"这关键一环。如果说 whatsmars 聚焦 LangChain4j 原理、spacecloud 聚焦 Spring AI 应用、babi 聚焦 Agent 工程化,那 jaws 的 MCP 桥接解决的是"已有微服务资产如何被 AI Agent 复用"这个基础设施层面的问题。对于已有大量 RPC 服务的团队来说,引入 jaws-mcp-spring-boot-starter 就能让这些服务自动出现在 AI Agent 的工具列表中,零改造成本。
项目地址:https://github.com/javahongxi/jaws
更多推荐



所有评论(0)