- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
McpClients是 openJiuwen agent-core-java 中负责把 SDK 内置 MCP transport client 注册到全局ClientRegistry的注册门面,它补齐了 Python 版BaseClient.__init_subclass__的自动注册语义。本文以 McpClients.md 为骨架,结合 McpClients.java 等源码实现,系统讲解其类结构、默认注册项、clientType归一化规则、调用链路与测试验证。读完本文,你将掌握:MCP client 如何在ResourceMgr、ToolManager场景下按clientType动态创建,以及如何通过ClientRegistry直接构建 SSE、Stdio、Streamable HTTP、OpenAPI、Playwright 五类客户端。
一、类概览:MCP 客户端注册门面
McpClients位于包com.openjiuwen.core.foundation.tool.mcp.client,定义如下:
public final class McpClients这是一个最终类(final class),构造器为私有(private McpClients()),对外仅暴露静态方法,属于典型的门面(Facade)模式。其内部持有常量CLIENT_TYPE = "mcp",作为所有内置 MCP client 在ClientRegistry中的客户端类型标识。
它的核心职责可以概括为一句话:把 SDK 内置的 MCP transport client 工厂以幂等方式注册到全局ClientRegistry,从而让ResourceMgr、ToolManager以及直接使用ClientRegistry的业务代码都能按照clientType动态创建 MCP client。
背景说明:在 Python 版 openJiuwen Core 中,
BaseClient.__init_subclass__会在子类定义时自动完成客户端注册;而 Java 版没有__init_subclass__这类元编程机制,因此需要McpClients.registerDefaults()这类显式注册入口来等价补齐该语义(这一点在McpClients的 Javadoc 与FoundationToolMcpClientPackageMarker的注释中均有体现)。
1.1 主要方法
| 方法 | 返回值 | 说明 |
|---|---|---|
registerDefaults() | void | 幂等注册 SDK 内置 MCP client 工厂。 |
normalizeClientType(String clientType) | String | 归一化用户传入的clientType,用于 registry 查询。 |
二、registerDefaults():幂等注册六个默认客户端
registerDefaults()在源码中的实现如下(见 McpClients.java):
public static synchronized void registerDefaults() { ClientRegistry registry = ClientRegistry.getClientRegistry(); registerIfMissing(registry, "sse", kwargs -> new SseClient(requiredConfig(kwargs))); registerIfMissing(registry, "stdio", kwargs -> new StdioClient(requiredConfig(kwargs))); registerIfMissing(registry, "streamable-http", kwargs -> new StreamableHttpClient(requiredConfig(kwargs))); registerIfMissing(registry, "streamable_http", kwargs -> new StreamableHttpClient(requiredConfig(kwargs))); registerIfMissing(registry, "openapi", kwargs -> new OpenApiClient(requiredConfig(kwargs))); registerIfMissing(registry, "playwright", kwargs -> new PlaywrightClient(requiredConfig(kwargs))); }2.1 幂等性实现:registerIfMissing
方法声明为public static synchronized,通过同步保证并发安全;真正实现幂等的是私有辅助方法registerIfMissing(McpClients.java):
private static void registerIfMissing(ClientRegistry registry, String name, ClientRegistry.ClientFactory factory) { String key = CLIENT_TYPE + "_" + name; if (!registry.listClients().contains(key)) { registry.registerClient(name, CLIENT_TYPE, factory); } }这里有一个值得注意的细节:注册 key 的拼接规则是clientType + "_" + name,即mcp_sse、mcp_stdio、mcp_streamable-http等。而ClientRegistry.registerClient(name, clientType, factory)内部正是通过fullName(name, clientType)生成同样的mcp_xxx形式存入factories映射(见 ClientRegistry.java)。registerIfMissing通过先查listClients()再注册的方式,避免了直接调用registerClient时因「重复注册抛IllegalArgumentException」而失败(ClientRegistry.registerClient对已存在的 fullName 会抛出Client type 'xxx' already registered)。
2.2 默认注册项与对应实现类
registerDefaults()共注册6 个 key、5 个实现类(streamable-http与streamable_http都指向StreamableHttpClient):
ClientRegistrykey | 实现类 |
|---|---|
mcp_sse | SseClient |
mcp_stdio | StdioClient |
mcp_streamable-http | StreamableHttpClient |
mcp_streamable_http | StreamableHttpClient |
mcp_openapi | OpenApiClient |
mcp_playwright | PlaywrightClient |
各实现类均位于 client 包:
SseClient(mcp_sse):基于 SSE(Server-Sent Events)传输,底层通过 Java SDKio.modelcontextprotocol.client.transport.HttpClientSseClientTransport实现,连接时支持统一鉴权回调AuthCallback.unifiedAuthHandler。StdioClient(mcp_stdio):基于标准输入输出的子进程传输,内置 JSON-RPC 会话JsonRpcProcessSession,并带命令白名单校验(默认允许java、python、python3等)。StreamableHttpClient(mcp_streamable-http/mcp_streamable_http):基于 Streamable HTTP 传输,底层使用HttpClientStreamableHttpTransport,支持请求超时预算与传输断线后自动重连一次。OpenApiClient(mcp_openapi):OpenAPI 文件(JSON/YAML)驱动的 MCP 客户端,会把 spec 中的paths解析为可调用的工具。PlaywrightClient(mcp_playwright):Playwright 浏览器会话类 MCP 客户端,默认通过 stdio 参数启动浏览器驱动进程。
2.3 工厂的 config 强校验:requiredConfig
每个注册的工厂都通过requiredConfig(kwargs)校验入参(McpClients.java):
private static McpServerConfig requiredConfig(Map<String, Object> kwargs) { Object value = kwargs != null ? kwargs.get("config") : null; if (value instanceof McpServerConfig config) { return config; } String actualType = value == null ? "null" : value.getClass().getName(); throw new IllegalArgumentException( "MCP client factory requires kwargs['config'] to be McpServerConfig, actual: " + actualType); }即:MCP client 工厂要求kwargs中包含config,且值必须是McpServerConfig类型;否则抛出带实际类型信息的IllegalArgumentException。这一约束与文档「使用说明」中的第三条完全一致。
三、normalizeClientType():clientType 归一化规则
normalizeClientType(String clientType)的作用是把用户五花八门的传输类型写法(大小写混用、下划线、连字符、mcp_前缀、camelCase 等)统一为 registry 查询可用的标准形式。其源码如下(McpClients.java):
public static String normalizeClientType(String clientType) { if (clientType == null || clientType.trim().isEmpty()) { return "sse"; } String trimmed = clientType.trim(); String camelSplit = trimmed.replaceAll("([a-z])([A-Z])", "$1-$2"); String normalized = camelSplit.toLowerCase(Locale.ROOT).replace('_', '-'); if (normalized.startsWith("mcp-")) { normalized = normalized.substring("mcp-".length()); } return switch (normalized) { case "sse" -> "sse"; case "stdio" -> "stdio"; case "streamable-http" -> "streamable-http"; case "openapi", "open-api" -> "openapi"; case "playwright" -> "playwright"; default -> normalized; }; }3.1 归一化处理流水线
- 空值兜底:
null或纯空白字符串统一返回sse(默认传输类型,与McpServerConfig的默认clientType一致)。 - 去除首尾空白:
trim()。 - camelCase 拆分:用正则
([a-z])([A-Z])将streamableHttp拆分为streamable-Http。 - 统一小写并替换下划线:
toLowerCase(Locale.ROOT).replace('_', '-'),将STREAMABLE_HTTP、mcp_streamable_http中的下划线全部换成连字符。 - 剥离
mcp-前缀:仅当结果以mcp-开头时去掉该前缀。 - switch 归一化:对
open-api→openapi这类别名做最终归并;未匹配的输入原样返回(此时default -> normalized)。
3.2 归一化对照表
| 输入示例 | 归一化结果 |
|---|---|
null、空字符串 | sse |
SSE、mcp_SSE | sse |
stdio、mcp_stdio | stdio |
streamable_http、streamableHttp、STREAMABLE_HTTP | streamable-http |
mcp_streamable_http、mcp-streamable-http | streamable-http |
open-api | openapi |
playwright | playwright |
值得注意的是:归一化后的标准 key 是streamable-http(连字符),而registerDefaults()中同时注册了streamable-http与streamable_http两个别名 key,因此在ClientRegistry中两种写法均可命中同一个StreamableHttpClient工厂(这一点由McpClientsTest.registryFactoriesCreateConcreteClientsFromMcpServerConfig验证)。
四、使用说明:三类调用路径
文档明确了McpClients的三类使用场景,结合源码可以还原出完整的调用链。
4.1 ResourceMgr 与 ToolManager 路径:自动确保注册
ResourceMgr与ToolManager的 MCP Server 注册路径会自动确保默认 MCP client 已注册,业务代码无需手动调用registerDefaults()。源码佐证如下:
ToolManager构造时以ToolManager::defaultCreateClient作为默认的 MCP client 工厂(ToolManager.java),而defaultCreateClient的完整逻辑为(ToolManager.java):
private static Object defaultCreateClient(McpServerConfig config) { try { return com.openjiuwen.core.foundation.tool.mcp.McpClientFactory.create(config); } catch (UnsupportedOperationException ignored) { McpClients.registerDefaults(); return ClientRegistry.getClientRegistry().getClient( McpClients.normalizeClientType(config.getClientType()), "mcp", Map.of("config", config) ); } }这条路径的语义是:优先走McpClientFactory(SPI 机制,基于ServiceLoader加载McpClientProvider,支持扩展自定义传输类型);若 SPI 层不支持该类型,则回退到McpClients.registerDefaults()+ClientRegistry的注册表路径。回退路径中清晰地示范了McpClients两个方法的组合用法:
McpClients.registerDefaults()确保内置工厂已注册;McpClients.normalizeClientType(config.getClientType())把用户配置的clientType(例如mcp_SSE)归一化为sse;ClientRegistry.getClientRegistry().getClient(name, "mcp", Map.of("config", config))按 key 动态创建客户端。
ResourceMgr.addMcpServer(McpServerConfig)是 MCP Server 注册的入口(ResourceMgr.java),内部校验McpServerConfig后通过ToolManager完成客户端创建、连接与工具列表刷新。测试 ResourceMgrTest.java 展示了通过ClientRegistry.getClientRegistry().registerClient(clientType, "mcp", ...)注册自定义 MCP 客户端、再经ResourceMgr.addMcpServer使用的完整链路。
4.2 直接使用 ClientRegistry 路径:先调用 registerDefaults()
如果业务代码绕过ResourceMgr/ToolManager,直接通过ClientRegistry.getClient(name, "mcp", kwargs)创建 MCP client,则应先调用McpClients.registerDefaults()完成工厂注册。典型写法:
McpClients.registerDefaults(); McpServerConfig config = McpServerConfig.builder() .serverId("my-srv") .serverName("my-server") .serverPath("http://127.0.0.1:3001/mcp") .clientType("streamable-http") .build(); // 按归一化后的类型名从全局注册表创建客户端 Object client = ClientRegistry.getClientRegistry().getClient( McpClients.normalizeClientType(config.getClientType()), "mcp", Map.of("config", config) ); // client 为 StreamableHttpClient 实例4.3 kwargs 的 config 约束
无论走哪条路径,最终都会落到McpClients内部注册的工厂上,因此都必须遵守同一条铁律:kwargs中必须包含config键,且其值必须是McpServerConfig类型。违反时抛出IllegalArgumentException,消息形如:
MCP client factory requires kwargs['config'] to be McpServerConfig, actual: null4.4 包级注册标记:FoundationToolMcpClientPackageMarker
另外,包内还存在一个FoundationToolMcpClientPackageMarker(FoundationToolMcpClientPackageMarker.java),镜像 Python 版openjiuwen/core/foundation/tool/mcp/client/__init__.py的语义,其registerDefaults()直接委托给McpClients.registerDefaults():
public static void registerDefaults() { McpClients.registerDefaults(); }五、McpServerConfig:工厂入参的核心配置模型
既然config必须是McpServerConfig,理解其字段结构有助于正确构造工厂入参。McpServerConfig位于 McpServerConfig.java,其关键字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
serverId | String | 服务标识;为空时默认取serverName,再为空则生成无连字符的随机 UUID |
serverName | String | 服务名称 |
serverPath | String | 服务地址:HTTP 类传 URL,stdio 类传命令路径,OpenAPI 类传 spec 文件路径 |
clientType | String | 客户端类型,默认值为"sse" |
params | Map<String, Object> | 扩展参数(如 stdio 的command/args/env、OpenAPI 的base_url等) |
authHeaders | Map<String, String> | 鉴权请求头 |
authQueryParams | Map<String, String> | 鉴权 Query 参数 |
connectTimeoutSeconds | Double | 连接超时(秒) |
callTimeoutSeconds | Double | 调用超时(秒) |
clientType字段由setClientType保证非空(null时回落为"sse")。此外,McpServerConfig.NO_TIMEOUT = -1.0F是贯穿所有客户端方法的「无超时」哨兵值,各客户端的connect/callTool等重载方法在未显式传超时时都会使用它。
六、客户端实现差异速览
六种默认注册的 MCP client 在传输层与超时语义上各有侧重,理解其差异有助于选择正确的clientType:
- SseClient(sse):
connect(int, double)返回CompletableFuture<Boolean>,连接时先触发ToolCallEvents.TOOL_AUTH鉴权事件(AuthTrigger.defaultAuth()→AuthCallback.unifiedAuthHandler),默认 SSE 超时 60 秒;操作失败时具备「断线重连一次」的自愈逻辑(executeWithReconnect+reconnect,reconnectLock保证并发调用共享一次握手)。 - StdioClient(stdio):通过
ProcessBuilder拉起子进程并以换行分隔 JSON-RPC 通信;resolveAllowedCommand提供命令白名单安全校验,默认允许java/python/python3等命令,且校验可执行文件的真实路径(toRealPath())防止绕过白名单;初始化握手协议版本为2024-11-05。 - StreamableHttpClient(streamable-http / streamable_http):底层
HttpClientStreamableHttpTransport;runWithTimeout通过有界线程池(SynchronousQueue交接)执行带超时的操作,超时以HttpTimeoutException上抛,便于上层分支处理;retryAwareTimeout会把外部超时预算放大 2 倍以覆盖「重连 + 重试」的两段执行。 - OpenApiClient(openapi):
serverPath为 OpenAPI spec 文件路径(支持多个,逗号分隔),connect时解析 JSON/YAML 生成工具集(ToolManager内部注册表);工具名优先取operationId,冲突时自动追加_2、_3后缀去重,超长名称截断至 64 字符。 - PlaywrightClient(playwright):浏览器会话型客户端,传输方式支持 stdio(
StdioClientTransport)与 SSE(HttpClientSseClientTransport),由配置参数stdio_server_parameters等决定。
七、测试验证:McpClientsTest
McpClients的行为由 McpClientsTest.java 全量覆盖,文档「相关测试」一节列的正是该测试类。其核心用例与文档的对应关系:
| 测试方法 | 验证点 |
|---|---|
registerDefaultsRegistersAllMcpClientFactoriesIdempotently | 连续调用两次registerDefaults()后,ClientRegistry中同时包含mcp_sse、mcp_stdio、mcp_streamable-http、mcp_streamable_http、mcp_openapi、mcp_playwright六个 key —— 验证幂等性与双别名注册 |
packageMarkerRegisterDefaultsDelegatesToMcpClients | FoundationToolMcpClientPackageMarker.registerDefaults()委托后mcp_sse可见 |
registryFactoriesCreateConcreteClientsFromMcpServerConfig | 通过getClient(name, "mcp", Map.of("config", config))依次创建出 6 个正确的实现类实例 |
registryFactoryRejectsMissingOrWrongConfig | 缺config或config类型错误时抛RuntimeException,消息包含MCP client factory requires kwargs['config']与McpServerConfig |
normalizeClientTypeSupportsCommonAliases | 覆盖文档归一化表中的全部输入样例 |
此外,ToolManagerTest.java 的defaultCreateClientRegistersMcpFactoriesAndNormalizesClientType从集成视角验证了ToolManager.defaultCreateClient对clientType = "mcp_SSE"的配置能归一化并创建出SseClient,且不会修改用户配置对象的clientType字段(assertEquals("mcp_SSE", config.getClientType()))。
八、总结
McpClients是 openJiuwen agent-core-java 中 MCP 客户端注册的最小而关键的入口:
registerDefaults()以幂等方式注册 6 个 key(5 个实现类),支撑ResourceMgr、ToolManager按clientType动态创建 MCP client;normalizeClientType()提供宽容的传输类型归一化(大小写、下划线、连字符、mcp_前缀、camelCase 均可),默认回落为sse;- config 强约束保证工厂入参类型安全,
McpServerConfig是唯一合法的配置载体; - 与 SPI 版
McpClientFactory互为补充,共同构成「可扩展优先、注册表兜底」的双通道客户端创建机制。
实际使用时,凡是直接走ClientRegistry的代码务必先调用McpClients.registerDefaults();而经由ResourceMgr/ToolManager注册 MCP Server 的路径则无需手动干预,框架会自动完成默认注册与clientType归一化。
- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
相关推荐
agent-core-java InferenceAffinityModelClientFactory 详解:InferenceAffinity 模型客户端的创建入口与注册机制
agent core java InferenceAffinityModelClientFactory 详解:InferenceAffinity 模型客户端的创
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习agent-core-java 中 DashScopeModelClientFactory 详解:DashScope 模型客户端工厂的注册与创建机制
agent core java 中 DashScopeModelClientFactory 详解:DashScope 模型客户端工厂的注册与创建机制 本文以 c
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习openJiuwen Agent Core Java StreamableHttpClient 详解:面向 Streamable HTTP 服务的 MCP 客户端
openJiuwen Agent Core Java StreamableHttpClient 详解:面向 Streamable HTTP 服务的 MCP 客户
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考