MCP Client 接入(单服务模式)
这一页只讲“单服务模式”。
也就是说,你当前的问题是:
- 先连上一个 MCP server
- 先确定它使用现代无状态 HTTP 还是 session-era 协议
- 先跑通 tool / resource / prompt 三类高层 API
如果你已经要接多个服务或做用户隔离,就应该去读 gateway,而不是继续停在这页。
1. McpClient 的真实生命周期
McpClient 不是创建完就能直接 callTool()。
它的真实生命周期是:
- 构造 transport
- new
McpClient(...) connect()- 按 transport/profile 完成就绪:默认 AUTO 先做有限探测;现代 HTTP 不握手;legacy transport 完成初始化握手
- 读取 tools / resources / prompts
- 调用能力
disconnect()
中间少任何一步,都不能算一个稳定 client。
2. connect() 到底做了什么
connect() 总会先执行 transport.start(),超时 30s,然后按 transport/profile 进入不同路径:
- 默认的
StreamableHttpTransport使用AUTO。它只先发送现代server/discover;有效的现代发现结果会选择2026-07-28,不发送initialize或notifications/initialized。 - AUTO 只在该探测收到未识别的 HTTP
400、404或405时回退到 initialization-era Streamable HTTP,并执行initialize -> notifications/initialized。认证失败、可识别的现代 JSON-RPC error、以及无效发现响应都不会触发降级。 - STDIO、显式
type: "sse"的 HTTP+SSE,以及显式选择 legacy Streamable HTTP profile 的 client,保留初始化时代流程。 - 若 transport 需要心跳,client 会在准备就绪后启动 heartbeat。
因此 isConnected() 和 isInitialized() 仍是调用前检查,但现代 HTTP 的 isInitialized() 表示 client ready,不表示发生过协议握手。
3. McpClient capability 边界
AI4J 的高层 API 覆盖 Tool、Resource 和 Prompt。现代 HTTP 每次请求声明的 capabilities 是保守的:它不会声称支持 sampling、roots、elicitation、MRTR 或 subscriptions,直到这些多轮协议能力真正可用。
不要根据旧 session-era initialize 示例推断现代 peer 也支持这些可选 capability。应以目标 server 的协议 profile 和实际能力目录为准。
4. 最小接入示例:STDIO
如果你连接的是本地子进程 MCP server,最短路径通常是 stdio。
McpTransport transport = new StdioTransport(
"npx",
Arrays.asList("-y", "@modelcontextprotocol/server-filesystem", "D:/workspace"),
null
);
McpClient client = new McpClient("demo-client", "1.0.0", transport);
client.connect().join();
List<McpToolDefinition> tools = client.getAvailableTools().join();
String result = client.callTool("read_file", Collections.singletonMap("path", "README.md")).join();
client.disconnect().join();
这一条路径验证的是:
- 子进程是否能启动
- stdio transport 是否能握手
- tools/list 和 tools/call 是否能通
5. 最小接入示例:Streamable HTTP / SSE
如果你接的是服务化 MCP,通常会用 HTTP 或 SSE。
Streamable HTTP
TransportConfig config = TransportConfig.streamableHttp("https://example.com/mcp");
config.setHeaders(Collections.singletonMap("Authorization", "Bearer your-token"));
McpTransport transport = McpTransportFactory.createTransport("streamable_http", config);
McpClient client = new McpClient("demo-client", "1.0.0", transport);
client.connect().join();
这个示例默认使用 AUTO。对未知或迁移中的 Streamable HTTP peer,它只通过 server/discover 做有限兼容探测;它不会自动选择 HTTP+SSE。对已知的 session-handshake server,显式设置:
config.withProtocolProfile(McpProtocolProfile.LEGACY_2025_03_26);
完整请求头、server profile 和升级路线见 Streamable HTTP 传输。
SSE
McpTransport transport = new SseTransport("https://example.com/sse");
McpClient client = new McpClient("demo-client", "1.0.0", transport);
client.connect().join();
这两条路径的区别,不在 McpClient,而在 transport 连接模型。HTTP+SSE 是 sse 的显式 transport;不要把它当作 Streamable HTTP AUTO 的回退目标。
6. McpClient 提供的高层 API 不止 Tool
当前高层 API 至少覆盖:
getAvailableTools()->tools/listcallTool(name, args)->tools/callgetAvailableResources()->resources/listreadResource(uri)->resources/readgetAvailablePrompts()->prompts/listgetPrompt(name, arguments)->prompts/get
6.1 Resource 示例
List<McpResource> resources = client.getAvailableResources().join();
McpResourceContent resource = client.readResource("file://docs/README.md").join();
6.2 Prompt 示例
List<McpPrompt> prompts = client.getAvailablePrompts().join();
McpPromptResult prompt = client.getPrompt(
"code_review_prompt",
Collections.<String, Object>singletonMap("language", "java")
).join();
这说明 MCP 在 AI4J 里不是单纯“远程 function call 协议”,而是覆盖 tool / resource / prompt 三类能力。
7. 缓存语义要先理解
McpClient 会缓存:
availableToolsavailableResourcesavailablePrompts
这有两个直接后果:
7.1 好处
- 不必每次都重复 list
- 会话级调用开销更低
7.2 边界
断线或重连后,缓存可能失效,因此 client 在断开时会清掉这些缓存。
这也是为什么 disconnect() 不只是“关连接”,还会清空状态。
8. 心跳和自动重连不是可有可无的细节
8.1 心跳
若 transport needsHeartbeat() 返回 true,client 会启动低频 heartbeat 检查。
当前实现是:
- 每 10 分钟做一次
getAvailableTools()检查
这更像保底存活检测,而不是高频 keepalive。
8.2 自动重连
McpClient 默认:
autoReconnect = true
断线后会:
- 清缓存
- 停 heartbeat
- 停 transport
- 取消 pending requests
- 5 秒后尝试重连
这意味着它已经具备基础会话恢复能力,但不是复杂的连接池。
8.3 关闭自动重连
默认的 3 参构造器把 autoReconnect 固定为 true:
new McpClient("demo-client", "1.0.0", transport)
// 等价于 new McpClient("demo-client", "1.0.0", transport, true)
如果你希望自己掌控连接生命周期(例如由上层 gateway、编排器统一调度重连,或短生命周期一次性调用),用 4 参构造器显式关闭:
McpClient client = new McpClient("demo-client", "1.0.0", transport, false);
关闭后的行为差异:
- 断线时仍会清缓存、停 heartbeat、停 transport、取消 pending requests
- 但不再调度重连(日志会打印
自动重连已禁用,跳过MCP重连) - 后续是否重连完全由调用方决定,通常做法是重新
new McpClient(...)再connect()
McpGateway 经由 McpGatewayClientFactory 创建 client 时使用默认的 3 参构造器,即 autoReconnect = true。当前没有配置项把 autoReconnect 从配置文件注入到 gateway 创建链路(见 配置与网关参考 - autoReconnect 字段)。需要关闭时,应在 gateway 外自行构造 client 再用 addMcpClient(...) 接入。
9. callTool() 的失败语义要分两层看
这里是一个容易写错的点。
9.1 连接态失败
如果 client 没连接或没初始化,callTool() 会直接返回 exceptional future。
9.2 协议层失败
如果服务端返回 MCP error response,当前实现通常会把错误压成字符串返回,而不是一定抛异常。
因此调用方不要只抓异常,也要检查返回内容是否是失败文本。
10. 推荐的接入姿势
单服务模式最稳的使用方式是:
- 构造 transport
connect().join()- 先
getAvailableTools()看真实暴露名 - 再
callTool(...) - finally 中
disconnect().join()
这样可以显著降低排障成本,因为工具名、权限、连接问题会在更靠前的步骤暴露出来。
11. 常见排障路径
11.1 not connected or not initialized
先检查:
- 有没有先
connect() - transport/profile 是否与目标 peer 匹配;AUTO 的
server/discover是否收到允许回退的响应 - 只有 legacy profile 才检查初始化握手是否真正完成;现代 HTTP 则检查请求 metadata 和 HTTP headers 是否被代理保留
11.2 tool not found
先检查:
getAvailableTools()是否能看到这个名字- 你调用的是 MCP 暴露名,而不是自己的别名
11.3 resource not found / prompt not found
先检查:
getAvailableResources()getAvailablePrompts()
11.4 HTTP 401 / 403
先检查:
TransportConfig.headers- token 是否真的打进请求
12. 什么时候应该离开这页
一旦你已经:
- 接了不止一个 MCP
- 需要用户级隔离
- 需要工具来源治理
就不该继续停留在单服务模式,应切到: