MCP 配置与网关参考
这页不是再重复“配置文件怎么写”,而是要把一个经常被写错的点讲清楚:
- 哪些字段只是配置元数据
- 哪些字段真的会进入 transport / client / gateway 运行时
如果这层不分清,文档就会把“字段存在”误写成“功能已经实现”。
1. 先分清两个 McpServerInfo
仓库里有两个名字非常像的类型:
io.github.lnyocly.ai4j.mcp.config.McpServerConfig.McpServerInfo这是运行时配置对象,来自mcp-servers-config.jsonio.github.lnyocly.ai4j.mcp.entity.McpServerInfo这是协议/展示层的服务元信息对象,只包含name/version/description/...
这一页讨论的是前者,也就是配置文件真正反序列化出来的运行时配置对象。
2. 顶层文件结构
配置文件入口默认是:
mcp-servers-config.json
结构是:
{
"mcpServers": {
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"enabled": true
}
}
}
几个关键点:
- 顶层 key 必须是
mcpServers - 每个子 key 就是
serverId serverId会直接进入 gateway 注册、路由、Agent 白名单
所以 serverId 不是备注字段,而是实际运行时主键。
3. 哪些字段真的会影响运行时
当前 McpGatewayClientFactory 的创建链是:
McpTypeSupport.resolveType(serverInfo)TransportConfig.fromServerInfo(serverInfo)McpTransportFactory.validateConfig(...)McpTransportFactory.createTransport(...)new McpClient(serverId, clientVersion, transport)
顺着这条链看,真正会进入 transport/client 的核心字段只有:
typetransport旧字段,仅用于兼容commandargsenvurlheadersprotocolProfile(仅streamable_http/http)
以及在配置源层生效的:
enabled
这是最关键的事实。
4. 字段分层表
| 字段 | 当前是否真正参与运行时 | 说明 |
|---|---|---|
type | 是 | 决定 stdio / sse / streamable_http |
transport | 是,但仅兼容 | 会被 McpTypeSupport.resolveType(...) 作为旧字段读取 |
command | 是 | stdio 必填 |
args | 是 | stdio 参数 |
env | 是 | stdio 子进程环境变量 |
url | 是 | sse / streamable_http 必填 |
headers | 是 | 远程 HTTP/SSE 请求头 |
protocolProfile | 是,限 streamable_http / http | 由 McpServerConfig.McpServerInfo 反序列化,并由 TransportConfig.fromServerInfo(...) 传入 transport;省略时为 AUTO |
enabled | 是 | 配置源提取有效服务时使用 |
cwd | 否 | 配置里有,但 TransportConfig.fromServerInfo(...) 没有传下去 |
autoReconnect | 否 | gateway 创建 McpClient 时没把它传入构造器 |
reconnectInterval | 否 | 当前 McpClient 重连逻辑固定 5 秒,不读此字段 |
maxReconnectAttempts | 否 | 当前没有接入重连次数限制 |
connectTimeout | 否 | 配置对象里有,但未映射到 TransportConfig |
tags | 否 | 当前不参与路由或过滤 |
priority | 否 | 当前不参与冲突仲裁或排序 |
version | 间接 | 会参与 FileMcpConfigSource 的 JSON 比较,从而触发更新 |
createdTime / lastUpdatedTime | 间接 | 同上,偏治理元数据 |
requiresAuth | 否 | 元信息,不会自动注入认证 |
authTypes | 否 | 元信息,不会自动改变 client 行为 |
如果你只记一件事,就记这一张表。
5. type 和 transport 的真实关系
推荐写法永远是:
type: "stdio"type: "sse"type: "streamable_http"
transport 只是兼容旧配置的遗留字段。
McpTypeSupport.normalizeType(...) 还接受一些别名:
processlocalserver-sent-eventsevent-streamhttpmcpstreamable-httphttp-streamable
但要注意:
- 未知值会被归一化回
stdio
这意味着如果你把类型写错了,不一定立刻显式报错,而可能被当成 stdio 继续走下去。生产环境里不要依赖这种容错。
6. 三种常见配置的正确写法
stdio
{
"mcpServers": {
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
},
"enabled": true
}
}
}
当前真正生效的是:
commandargsenv
cwd 虽然有字段,但当前不会进入 StdioTransport。
sse
{
"mcpServers": {
"remote-sse": {
"type": "sse",
"url": "http://127.0.0.1:8080/sse",
"headers": {
"Authorization": "Bearer ${TOKEN}"
},
"enabled": true
}
}
}
当前真正生效的是:
urlheaders
这里的 type: "sse" 是 deprecated HTTP+SSE transport 的显式选择。不要把它写成 streamable_http 并期待 AUTO 改用 SSE。
streamable_http
{
"mcpServers": {
"weather-http": {
"type": "streamable_http",
"url": "http://127.0.0.1:8000/mcp",
"protocolProfile": "AUTO",
"headers": {
"Authorization": "Bearer ${TOKEN}"
},
"enabled": true
}
}
}
http 仍兼容,但语义上已经被归一化到 streamable_http。
Streamable HTTP protocol profile
protocolProfile 已被 McpServerConfig.McpServerInfo 支持。配置驱动的 Gateway 会把它传给 Streamable HTTP transport;省略时默认 AUTO,而 http 别名也会使用相同语义。
AUTO仅以现代server/discover探测 Streamable HTTP:只有未识别的400、404或405才会进入 initialization-era fallback。MODERN_2026_07_28禁止 legacy fallback,适合已确定为现代的 peer。LEGACY_2025_11_25、LEGACY_2025_06_18和LEGACY_2025_03_26固定使用对应的初始化时代 profile。
这不是万能自动识别:认证失败、可识别的现代 JSON-RPC error、或非预期成功响应不会降级。deprecated HTTP+SSE 仍然必须配置 type: "sse"。完整行为见 Streamable HTTP 传输。
7. enabled 是真正的开关字段
FileMcpConfigSource.loadConfigs() 会调用 McpConfigIO.extractEnabledConfigs(serverConfig),也就是:
- 只有启用的配置才会进入有效配置集
reloadConfigs()后,新增/删除/更新事件都是围绕“启用配置集”计算的
这意味着把一个服务改成 enabled: false,其效果不是“保留配置但标记停用”,而是:
- 从当前有效服务集合里移除
- gateway 收到删除事件后把对应 client 下线
8. autoReconnect 等字段为什么现在不能乱宣传
这几个字段最容易被文档写过头:
autoReconnectreconnectIntervalmaxReconnectAttemptsconnectTimeout
它们在配置类里确实存在,但当前 gateway 创建 client 的时候:
- 没把
autoReconnect传给McpClient - 没把
reconnectInterval/maxReconnectAttempts用到重连调度 - 没把
connectTimeout从配置映射到TransportConfig
实际运行时的行为是:
McpClient默认autoReconnect = true- 断线后固定 5 秒后尝试重连
connect()内部 transport 启动超时固定 30 秒;legacy profile 的初始化握手也使用该超时
所以如果你在配置文件里写了这些字段,当前更应该把它理解成:
- 平台治理元数据
- 未来扩展预留
而不是“已经完全接线的运行参数”。
9. headers 和认证元数据不要混为一谈
headers
这是现在真正会参与远程请求的认证注入点。
用途包括:
Authorization- API key
- 租户标识
requiresAuth / authTypes
这两个字段当前更适合用于:
- 后台界面展示
- 配置校验
- 审计标记
它们本身不会自动生成请求头,也不会自动触发登录流程。
10. 网关初始化的 3 种常见方式
直接读配置文件
McpGateway gateway = new McpGateway();
gateway.initialize("mcp-servers-config.json").join();
先绑定配置源
McpConfigSource source = new FileMcpConfigSource("mcp-servers-config.json");
McpGateway gateway = new McpGateway();
gateway.setConfigSource(source);
gateway.initialize().join();
运行时动态注入
gateway.addMcpClient("github", client).join();
gateway.removeMcpClient("github").join();
这 3 种方式不是重复功能,而是 3 种治理层级:
- 静态配置
- 抽象配置源
- 完全平台化热插拔
11. 配置源 SPI 与内置实现
抽象配置源由 McpConfigSource 这个接口定义,是 gateway 走“模式 B / 模式 C”的前提。它的契约只有四件事:
getAllConfigs()/getConfig(serverId)addConfigChangeListener(...)/removeConfigChangeListener(...)ConfigChangeListener回调:onConfigAdded/onConfigRemoved/onConfigUpdated
McpGateway 通过 setConfigSource(...) 绑定配置源后,McpGatewayConfigSourceBinding 会把上面三类事件翻译成运行时动作(见 Gateway 管理 - 配置源热更新):新增/更新 → 创建 client 并接入,移除 → 断开并下线。
内置实现一:FileMcpConfigSource
文件型配置源,从 mcp-servers-config.json 加载。它只提供手动的 reloadConfigs(),不会监控文件系统变化;要感知外部改动,需要你自己定时调用 reload 或换实现。
内置实现二:McpConfigManager(内存型)
McpConfigManager 是一个开箱即用的纯内存 McpConfigSource 实现,适合:
- 程序化动态注册 MCP 服务(后台管理台、多租户平台)
- 单元测试里构造配置源
- 作为自定义存储(数据库 / Redis / 配置中心)的基础构件
它的特点:
- 内部用
ConcurrentHashMap存配置、CopyOnWriteArrayList存监听器,线程安全 addConfig(serverId, info)新增;对已存在的 key 会改触发onConfigUpdated,否则触发onConfigAddedremoveConfig(serverId)删除并触发onConfigRemovedupdateConfig(...)等价于addConfig(...)hasConfig(serverId)/getConfig(serverId)/getAllConfigs()查询validateConfig(info)校验:name非空、transport 类型可识别、TransportConfig合法;非法配置返回false而不抛异常
最小用法:
McpConfigManager source = new McpConfigManager();
McpServerConfig.McpServerInfo github = new McpServerConfig.McpServerInfo();
github.setName("github");
github.setType("stdio");
github.setCommand("npx");
github.setArgs(Arrays.asList("-y", "@modelcontextprotocol/server-github"));
source.addConfig("github", github); // 触发 onConfigAdded
source.updateConfig("github", updatedInfo); // 触发 onConfigUpdated
source.removeConfig("github"); // 触发 onConfigRemoved
// 绑定到 gateway:事件会自动驱动 client 增删
McpGateway gateway = new McpGateway();
gateway.setConfigSource(source);
gateway.initialize().join();
McpConfigManager 进程重启后状态丢失,本身不做持久化。需要落库时,应自己实现 McpConfigSource(可参考它的监听器通知写法),或在其外层包一层持久化。
12. Agent 侧怎么引用这些配置
配置文件中注册的 serverId,最终要进入 Agent 的 mcpServices 白名单:
.toolRegistry(Collections.<String>emptyList(), Arrays.asList("github", "filesystem"))
要点只有一句:
- “服务已被 gateway 注册”不等于“本次请求默认可见”
13. 这页最该记住的结论
在 AI4J 当前实现里,配置字段分成两层:
- 一层是真正进入 transport/client/gateway 的运行字段
- 一层是为治理、审计、未来扩展准备的元数据字段
文档写法必须忠于这条边界,否则就会把“对象字段存在”错写成“运行能力已经生效”。