Client Integration
这一页讲的不是协议概念,而是 AI4J 怎么把一个 MCP 服务真正接进请求链。
关键问题只有三个:
- 服务怎么连上
- 服务能力怎么被缓存和发现
- 本次请求怎么显式开放它
对应源码主线是:
mcp/client/McpClient.javamcp/gateway/McpGateway.javaplatform/openai/chat/entity/ChatCompletion.javaplatform/openai/response/entity/ResponseRequest.javatool/ToolUtil.java
1. 接入的起点不是 provider,而是 McpClient
AI4J 里,单个 MCP 服务的实际宿主是 McpClient。
McpClient.connect() 做的事比“建立连接”多得多:
transport.start()启动具体 transport- 发送
initialize - 发送
notifications/initialized - 成功后才把
connected置为 true - 如果 transport 需要心跳,则启动低频健康检查
这意味着在 AI4J 的语义里,“transport 已连通” 还不算真正可用;必须完成初始化握手,能力目录才可信。
2. 初始化时客户端会声明哪些能力
McpClient.initialize() 当前会显式声明这些 client capability:
samplingroots.listChangedtools.listChangedresources.subscriberesources.listChangedprompts.listChanged
并且默认请求协议版本 2025-03-26。
这几点很重要,因为它说明 AI4J 接入第三方 MCP 不是“只支持 tools/call 的最小客户端”,而是按完整 capability 面去做握手。
3. 服务能力怎么被客户端缓存
McpClient 维护三份缓存:
availableToolsavailableResourcesavailablePrompts
第一次访问时分别通过:
tools/listresources/listprompts/list
拉取远端目录,之后复用本地缓存。
收到这些通知时缓存会失效:
notifications/tools/list_changednotifications/resources/list_changednotifications/prompts/list_changed
所以 AI4J 当前对远端能力目录的心智是:
- 默认按缓存读
- 收到通知再失效
不是每次请求都重新全量拉目录。
4. 请求是怎么显式绑定 MCP 服务的
真正把服务引入本次模型请求的入口,是请求对象上的:
ChatCompletion.mcpServices(...)ResponseRequest.mcpServices(...)
当前实现里,这两个字段都是 List<String>,也就是:
- 传的是
serviceId - 不是直接把完整
McpServerReference对象塞进请求
这点很关键。AI4J 当前稳定支持的请求语义是:
- 先由宿主把服务注册到
McpGateway - 再由请求用
serviceId白名单挑选本次开放哪些服务
5. provider 发送前真正发生了什么
以 OpenAiChatService 为代表,provider 适配层发送前会调用:
ToolUtil.getAllTools(chatCompletion.getFunctions(), chatCompletion.getMcpServices())
这一步会把:
- 本地 function tools
- 请求指定的 MCP services
合并成一组 provider 可见 Tool。
如果 mcpServices 为空,MCP 服务不会自动进入本次请求。
这也是 AI4J 当前最重要的安全默认值之一。
6. McpGateway 在 client integration 里扮演什么角色
McpGateway 是多服务宿主,但即便只接一个服务,也最好把它视为正式运行时组件。
它至少承担:
- 保存
serviceId -> McpClient - 初始化和关闭客户端
- 刷新可用工具目录
- 维护
tool -> client映射 - 为
ToolUtil提供统一查询和调用入口
所以真正的接入关系不是:
request -> remote service
而是:
request -> ToolUtil -> McpGateway -> McpClient -> transport -> remote service
7. 执行阶段如何回到远端服务
当模型返回 tool call 后,执行链最终会经过 ToolUtil.invoke(...)。
对全局工具,它的优先级是:
- 先尝试本地 MCP tool
- 再尝试传统本地 function tool
- 最后尝试
McpGateway.callTool(...)
这说明 “MCP tool 最终长得像普通 tool” 并不意味着执行链完全相同。AI4J 仍然保留了本地能力和远端能力的优先分流。
8. 断连、失败和重连时会发生什么
McpClient 的失败路径不是静默的:
onDisconnected(...)会清空缓存- 停止心跳
- 停止 transport
- 取消所有 pending request
- 如果
autoReconnect=true,默认 5 秒后安排一次重连
这意味着接入第三方服务时要明确一个事实:
- MCP 连接不是“配完即永久稳定”
- 它在运行时可能掉线、重连、重新初始化
如果你的业务需要严格的一致性或事务性,这些失败面必须在宿主层补治理。
9. 目前实现里几个容易误解的点
不是所有 transport 都需要同样的保活策略
StdioTransport.needsHeartbeat()是falseSseTransport.needsHeartbeat()也是falseStreamableHttpTransport.needsHeartbeat()才是true
所以不要把“心跳存在”误解成所有 MCP 接入的统一行为。
请求里没有传 mcpServices,不会自动开放
哪怕 gateway 里已经初始化了很多服务,本次请求仍然可能一个 MCP tool 都看不见。
McpServerReference 目前更多是服务定义对象,不是请求面主入口
当前实际请求面还是 serviceId 列表。文档里如果把二者混成一回事,会给使用者错误预期。
10. 推荐的接入顺序
对正式项目,建议按下面顺序接:
- 先单独验证 transport 能连接
- 确认
initialize握手能完成 - 在 gateway 中注册服务
- 只给一个最小
serviceId白名单做请求测试 - 再补资源、提示词或多服务组合
不要一开始就:
- 接多个服务
- 默认全部开放
- 再在 provider 侧回头查问题
11. 这一页的结论
AI4J 的 MCP client integration 是“先把服务接到
McpGateway,再由请求用mcpServices显式开放”。McpClient负责真正的握手、缓存和断连处理;provider 只看到最后被投影出来的 tools,而不是直接面对远端服务。