@clawos-dev/clawd 0.2.502 → 0.2.504

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21311,21 +21311,36 @@ var EnvAssetSchema = external_exports.object({
21311
21311
  name: external_exports.string().min(1)
21312
21312
  });
21313
21313
  var ConnectorNameSchema = external_exports.string().regex(/^[a-z0-9-]+$/, "\u8FDE\u63A5\u5668\u540D\u53EA\u80FD\u662F\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u77ED\u6A2A\u7EBF");
21314
- var EnvConnectorSchema = external_exports.object({ name: ConnectorNameSchema });
21314
+ var CONNECTOR_KINDS = ["proxy", "env"];
21315
+ var CONNECTOR_RUNNERS = ["mcp", "cli"];
21316
+ var CONNECTOR_PROTOCOLS = ["bearer", "lark-sidecar"];
21317
+ var CREDENTIAL_SOURCES = ["none", "shared", "personal"];
21318
+ var CONNECTOR_AUTHS = ["oauth", "fields"];
21319
+ var ConnectorFieldSchema = external_exports.string().regex(/^[A-Z][A-Z0-9_]*$/, "\u8981\u586B\u7684\u683C\u7684\u540D\u5B57\u53EA\u80FD\u662F\u5927\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u4E0B\u5212\u7EBF\uFF0C\u4E14\u4EE5\u5B57\u6BCD\u5F00\u5934");
21320
+ var EnvConnectorSchema = external_exports.object({
21321
+ name: ConnectorNameSchema,
21322
+ kind: external_exports.enum(CONNECTOR_KINDS),
21323
+ runner: external_exports.enum(CONNECTOR_RUNNERS),
21324
+ protocol: external_exports.enum(CONNECTOR_PROTOCOLS).optional(),
21325
+ id: external_exports.string().min(1),
21326
+ credentialSource: external_exports.enum(CREDENTIAL_SOURCES).optional(),
21327
+ auth: external_exports.enum(CONNECTOR_AUTHS).optional(),
21328
+ fields: external_exports.array(ConnectorFieldSchema).default([]),
21329
+ http: external_exports.object({ url: external_exports.string().min(1), headers: external_exports.record(external_exports.string(), external_exports.string()) }).optional()
21330
+ });
21315
21331
  var MANIFEST_BODY = {
21316
21332
  clis: external_exports.array(EnvCliSchema),
21317
21333
  secrets: external_exports.array(EnvSecretSchema),
21318
21334
  assets: external_exports.array(EnvAssetSchema),
21319
21335
  /**
21320
- * 装了哪些连接器(design 2026-09-09-cloud-connector-credentials §7.1)。**字符串数组**,
21321
- * 是四段里唯一不是 `{name}` 对象数组的一段:连接器没有任何附加属性,包一层对象只是多一层壳。
21336
+ * 装了哪些连接器(design 2026-09-11-mcp-connector §3.1)。一条一个对象,形状见 `EnvConnectorSchema`。
21322
21337
  *
21323
- * 不复用 secrets:那段说的是「需要一份值,由部署者填」,这里没有值要填;
21324
- * 不从 clis 推断:lark-cli 在底座里,每个 persona 都有它,推不出任何东西。
21338
+ * 不复用 secrets:那段说的是「需要一份值,由部署者填」;连接器的凭据谁出是单据上点出来的,
21339
+ * 三种来源里只有「共享」才落到 Secrets,而且靠 `sharedConnectorSecrets` 在消费处并入,不写进那段。
21325
21340
  *
21326
- * **缺省空而不是必填**:本机上已有的单据不用为此重扫一遍;没声明就是没装,扫描器照样会报。
21341
+ * **缺省空而不是必填**:本机上已有的单据不用为此重扫一遍;没声明就是没装。
21327
21342
  */
21328
- connectors: external_exports.array(ConnectorNameSchema).default([])
21343
+ connectors: external_exports.array(EnvConnectorSchema).default([])
21329
21344
  };
21330
21345
  var EnvManifestBodySchema = external_exports.object(MANIFEST_BODY);
21331
21346
  var EnvironmentManifestSchema = external_exports.object({
@@ -21496,7 +21511,8 @@ var DeployReportFindingArgsSchema = external_exports.object({
21496
21511
  personaId: external_exports.string().min(1),
21497
21512
  kind: external_exports.enum(["cli", "secret", "asset", "connector"]),
21498
21513
  // 该条目在 manifest 对应数组里的内容(形状随 kind 变,handler 按 kind 收窄校验)
21499
- // connector 的 item 只有 name,落进清单时只留名字(manifest.connectors 是字符串数组)
21514
+ // connector 的 item 是 butler 报的 CLI 连接器({ name, fields? }),daemon 补齐 kind / runner / id 后落进清单;
21515
+ // MCP 连接器不经这里——由 daemon 从 .mcp.json 机械列出(design 2026-09-11-mcp-connector §3.2)
21500
21516
  item: external_exports.record(external_exports.string(), external_exports.unknown()),
21501
21517
  // 这条发现的来历(一句人话 + 原文出处),形状同 EnvEvidence,target 由 handler 从 kind+name 派生
21502
21518
  evidence: external_exports.object({
@@ -21526,6 +21542,35 @@ var ContactRemoteAccessUpdatedFrameSchema = external_exports.object({
21526
21542
  remoteAccessAllowed: external_exports.boolean()
21527
21543
  }).strict();
21528
21544
 
21545
+ // ../protocol/src/cloud-connector-schemas.ts
21546
+ var CloudConnectorsListArgsSchema = external_exports.object({
21547
+ /** 本机 registry 里的键(`<gateway 名>-cloud`);daemon 从它的 `cloud.personaId` 找 gateway 登记行 */
21548
+ personaId: external_exports.string().min(1)
21549
+ }).strict();
21550
+ var CLOUD_CONNECTOR_STATUSES = ["connected", "missing", "reauth"];
21551
+ var CLOUD_CONNECTOR_ACTIONS = ["link", "fill"];
21552
+ var CloudConnectorStatusSchema = external_exports.object({
21553
+ name: ConnectorNameSchema,
21554
+ id: external_exports.string().min(1),
21555
+ kind: external_exports.enum(CONNECTOR_KINDS),
21556
+ runner: external_exports.enum(CONNECTOR_RUNNERS),
21557
+ auth: external_exports.enum(CONNECTOR_AUTHS),
21558
+ status: external_exports.enum(CLOUD_CONNECTOR_STATUSES),
21559
+ action: external_exports.enum(CLOUD_CONNECTOR_ACTIONS),
21560
+ /** 只有 `action: link` 且没连上(或要重新关联)时有;它不是凭证,几分钟过期 */
21561
+ authUrl: external_exports.string().optional(),
21562
+ fields: external_exports.array(ConnectorFieldSchema)
21563
+ }).strict();
21564
+ var CloudConnectorsListResultSchema = external_exports.object({
21565
+ connectors: external_exports.array(CloudConnectorStatusSchema)
21566
+ }).strict();
21567
+ var CloudConnectorFillArgsSchema = external_exports.object({
21568
+ personaId: external_exports.string().min(1),
21569
+ name: ConnectorNameSchema,
21570
+ /** 格名 → 值。格名必须是该连接器声明过的 `fields` 之一,中心校验 */
21571
+ values: external_exports.record(ConnectorFieldSchema, external_exports.string().min(1))
21572
+ }).strict();
21573
+
21529
21574
  // ../protocol/src/contact.ts
21530
21575
  var ContactSchema = external_exports.object({
21531
21576
  /**
@@ -24048,6 +24093,15 @@ var METHOD_DOCS = {
24048
24093
  summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08gh run rerun --job\uFF09\u3002**\u540C\u4E00\u4E2A commit\u3001\u540C\u4E00\u4EFD\u8F93\u5165**\uFF0C\u4E0D\u4EA7\u751F\u65B0\u63D0\u4EA4\u2014\u2014\u6539\u4E86 persona \u5185\u5BB9\u6216\u51ED\u636E\u8981\u8D70 deploy:release\u3002jobId \u5FC5\u987B\u5C5E\u4E8E\u6700\u65B0\u90A3\u6761 run\uFF1Bowner-only",
24049
24094
  args: DeployRerunJobArgsSchema
24050
24095
  },
24096
+ // ---- cloud:connectors:*(云 persona 卡片上的连接器;design 2026-09-11-mcp-connector §3.8)----
24097
+ "cloud:connectors:list": {
24098
+ summary: "\u4E91\u4E0A persona \u5361\u7247\u4E0A\u7684\u8FDE\u63A5\u5668\u533A\uFF1A\u8FD9\u4E2A persona \u58F0\u660E\u7684\u3001**\u8981\u4F7F\u7528\u8005\u672C\u4EBA\u6388\u6743\u7684**\u8FDE\u63A5\u5668\u5404\u81EA\u8FDE\u6CA1\u8FDE\u4E0A\uFF08connected / missing / reauth\uFF09\u3001\u6309\u94AE\u662F\u5173\u8054\uFF08\u5E26\u73B0\u7B97\u7684 authUrl\uFF09\u8FD8\u662F\u586B\u5199\uFF08\u5E26\u8981\u586B\u7684\u683C\uFF09\u3002daemon \u62FF gateway \u767B\u8BB0\u884C + \u672C\u673A\u767B\u5F55\u51ED\u8BC1\u5411\u4E2D\u5FC3\u4EE3\u67E5\uFF0C\u51ED\u8BC1\u4E0D\u51FA ws\uFF1Bowner-only",
24099
+ args: CloudConnectorsListArgsSchema
24100
+ },
24101
+ "cloud:connectors:fill": {
24102
+ summary: "\u586B\u5199\u578B\u8FDE\u63A5\u5668\uFF1A\u628A\u4F7F\u7528\u8005\u672C\u4EBA\u7684\u51ED\u636E\u503C\u4EA4\u7ED9\u4E2D\u5FC3\u52A0\u5BC6\u5165\u5E93\uFF08\u4E00\u6B21\u586B\u5199\u5168\u5C40\u5171\u7528\uFF09\u3002**\u8FD9\u662F\u503C\u552F\u4E00\u7684\u5165\u53E3**\uFF1A\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E\u3001\u672C\u673A\u4E0D\u843D\u76D8\uFF1B\u683C\u540D\u5FC5\u987B\u662F\u8BE5\u8FDE\u63A5\u5668\u58F0\u660E\u8FC7\u7684\uFF1Bowner-only",
24103
+ args: CloudConnectorFillArgsSchema
24104
+ },
24051
24105
  "shift:list": {
24052
24106
  summary: "\u5217\u51FA owner \u7684\u5168\u90E8 shift\uFF08\u6392\u73ED\uFF09\uFF0C\u7ED9 UI \u65E5\u5386\u6E32\u67D3\u7528\uFF1Bowner-only",
24053
24107
  args: NO_ARGS
@@ -43,11 +43,29 @@ persona 目录,很便宜,漏了的话资产进不了镜像,容器里那条
43
43
  这个 persona 如果被搬到一台**干净的 Linux 容器**里跑(没有任何人的 home 目录、没有预装工具、
44
44
  没有任何凭据),需要事先给它备好什么,它才能正常干活?
45
45
 
46
+ ## 云上容器已经有的
47
+
48
+ 「干净」是相对的——底座镜像和容器本身已经带了下面这些。**这些一律不报 `cli`、不写进 `notes`**,
49
+ 也别在任何上报里替它们操心(「需要 node」「得有可写目录」「登录态怎么进去」这类话都不该出现):
50
+
51
+ | 有什么 | 说明 |
52
+ |---|---|
53
+ | node 22、npm、npx | 底座镜像就是 `node:22-bookworm-slim`。npm 包型的 MCP server / CLI 只需报一条 `cli` 让它预装,node 本身不用管 |
54
+ | git | 底座自带 |
55
+ | codex | 云上唯一的引擎,底座自带 |
56
+ | lark-cli | 底座自带,带凭据隔离编译。飞书的登录态由**飞书连接器**在容器起来时带进去,不需要也不能在容器里 `auth login` / `config init`;persona 里写的交互式初始化步骤在云上不会走到 |
57
+ | 可写的工作目录 | 工作目录就是 persona 目录(`/opt/bundle/persona`),可写,临时文件随手落;另有 `/workspace` 持久卷,同一个使用者用同一个 persona 时跨容器保留 |
58
+ | 一个容器最长 2 小时 | 到点 k8s 收掉。长连接类命令(事件监听、机器人入会)在这个上限内正常跑 |
59
+
60
+ 出网策略这里不列(它不在仓库里定)。**但「如果有出网限制的话可能…」这种话也不要写**——见收尾那节:
61
+ 拿不准的事去查清,查不清的不写。
62
+
46
63
  ## 读什么
47
64
 
48
65
  主体是任务里给你的 persona 目录,把里面的**全部文本**读完:
49
66
 
50
- - `CLAUDE.md` / `AGENTS.md`——人格与操作指引,依赖大多写在这里
67
+ - `CLAUDE.md` / `AGENTS.md`——人格与操作指引,依赖大多写在这里。两份是镜像(daemon 双写),读一份即可;
68
+ 上报时叫它「人格」,别写文件名(见 evidence 一节)
51
69
  - `.claude/skills/**/SKILL.md` 及 skill 附带的脚本
52
70
  - `.mcp.json`——挂了哪些 MCP server,怎么启动、要什么参数
53
71
  - 目录里的任何脚本、模板、配置样例
@@ -112,6 +130,9 @@ call({ method: "deploy:reportFinding", args: {
112
130
  - **绝对不要把凭据的值写进来**,哪怕你在文件里看到了明文的 key。你只报告「这里需要一份
113
131
  supabase 凭据」,值由老板另外填进仓库的 GitHub Secrets。看到明文 key 也不要复述它的内容。
114
132
  - **不要判断这条凭据该谁出**。凭据全部由部署者提供,清单只说需要什么。
133
+ - **`.mcp.json` 里 `env` / `headers` 的 `${VAR}` 占位不报 `secret`**:那些由 daemon 直接从文件里
134
+ 机械列进「连接器」,谁出凭据老板在单据上点。同一个变量若还被别处用到(比如 `publish.sh` 拿
135
+ `SUPABASE_URL` 渲成应用的环境变量),那条照报——它是另一个用途,不是 MCP 的。
115
136
 
116
137
  ### kind: "asset" —— persona 目录之外的共享资产
117
138
 
@@ -128,24 +149,39 @@ call({ method: "deploy:reportFinding", args: {
128
149
  - 判据往往落在「这个动作在云上由谁提供」。persona 在本机走 daemon 提供的 MCP 干的活,
129
150
  云上容器里没有 daemon,靠的就是那份随行资产。
130
151
 
131
- ### kind: "connector" —— 以用户本人身份操作的第三方
152
+ ### kind: "connector" —— persona 要连的第三方(只报 CLI 那种)
132
153
 
133
- `item` 字段只有 `name`(小写字母、数字、短横线)。目前能声明的只有 `feishu`。
154
+ **连接器分两种来源,你只报其中一种:**
134
155
 
135
- 判据:**persona 里出现「以用户本人身份操作某个第三方」的动作**就报——查他的日程、发他的消息、
136
- 读他的文档,这类活在云上要先拿到他本人的授权,授权由云上的连接器完成,不是部署者填一份凭据就能代替的。
156
+ - **MCP 连接器**(`.mcp.json` 里的每个 server)——**不用报**。daemon 在扫描开始时已经从文件里机械列好
157
+ (http 型、stdio 型、要填哪些 `${VAR}` 都读得出来),你报了也是重复。它们的 `npx` 包照样要报一条 `cli`(见上)。
158
+ - **CLI 连接器**——**要报**。判据:persona 里出现**以用户本人身份操作某个第三方的命令行工具**:
159
+ `lark-cli … --as user` 操作飞书、`gh` 开 PR、`railway up`、`vercel deploy` 这类。这些工具没有配置文件可读,
160
+ 只有你读了文档才知道它从哪个环境变量拿凭据。
137
161
 
138
- - 跟 `secret` 的区别:`secret` 是「部署者出一份值,全体使用者共用」;`connector` 是「每个使用者各自授权一次」。
139
- 一个 persona 用 `lark-cli … --as user` 操作飞书,报的是 `connector: feishu`,**不要**再给它报一条飞书的 `secret`。
140
- - 跟 `cli` 的区别:`lark-cli` 这个工具本身在底座镜像里,不用报 `cli`;报 `connector` 说的是「它要用谁的身份」。
141
- - `evidence.quote` 引 skill 里带 `--as user` 的那类命令行就够。
162
+ `item` 字段:
163
+
164
+ - `name`:可执行名(小写字母、数字、短横线),如 `feishu`(lark-cli 的连接器名固定叫这个)、`gh`、`railway`。
165
+ - `fields`:它读凭据的**环境变量名**数组,如 `gh` → `["GH_TOKEN"]`、`railway` → `["RAILWAY_TOKEN"]`。
166
+ **查清楚是你的活**(同 `cli` 的 `from`):读官方文档,写工具真读的那个名字,别猜。飞书走 sidecar,`fields` 给 `[]`。
167
+
168
+ 谁出凭据(部署者出一份共用 / 每个使用者各自一份)**不由你判**,老板在单据上点。
169
+
170
+ - 跟 `secret` 的区别:`secret` 是脚本 / 应用直接读的一份值;`connector` 是「某个工具要用谁的身份」。
171
+ 报了 `connector: feishu` 就**不要**再给飞书报一条 `secret`。
172
+ - 跟 `cli` 的区别:`lark-cli` 在底座镜像里,不用报 `cli`;`gh` / `railway` 这类不在底座里的,
173
+ **既要报 `cli`(怎么装)也要报 `connector`(凭据从哪个变量来)**,两条各管一件事。
174
+ - `evidence.quote` 引 skill 里那条命令行就够。
142
175
 
143
176
  ### evidence —— 每条的来历
144
177
 
145
- - `reason`:**一句人话**,说清「为什么需要它 + 你从哪看出来的」,写给不熟悉这个 persona 的人看。
178
+ - `reason`:**一句人话**,说清「为什么需要它 + 你从哪看出来的」,写给不熟悉这个 persona 的人看——
179
+ 部署的人不一定是开发者,别用内部词、别解释机制。
146
180
  - 好:「发布脚本用它解析 aliyun CLI 返回的 JSON,没有它 publish 走不到底」
147
181
  - 差:「publish.sh 里用到了 python3」(复述原文,没说清为什么要)
148
182
  - 差:「用于 JSON 解析」(太短,看不出跟这个 persona 有什么关系)
183
+ - 人格文件在 `reason` / `notes` 里叫「人格」(「人格里写着发布走 publish.sh」),**不写 `CLAUDE.md` / `AGENTS.md`**:
184
+ 两份是镜像,老板的 persona 跑的是 codex,看见 `CLAUDE.md` 会糊涂。`file` 照实填真路径,那是给展开核对用的
149
185
  - `file`:persona 目录内的相对路径,或资产名开头的路径
150
186
  - `quote`:原文摘一两句,**引原文不要转述**(老板起疑时展开核对用)
151
187
 
@@ -196,16 +232,46 @@ call({ method: "deploy:finishScan", args: {
196
232
  }})
197
233
  ```
198
234
 
199
- **`notes`** —— 需要老板判断的事。例如「这个 skill 要求浏览器登录,云上大概率不可用」、
200
- 「这里引用的服务是裸 IP,可能受出网策略影响」、「某个工具三层都查了还是没找到装法,卡在 X」。
201
- 没有就空数组。
235
+ **`notes`** —— 页面上叫「上云前要你处理的」。**一条 = 一件老板必须亲手做、不做就上不了云(或上去也是坏的)的事。**
236
+ 写法:一两句话,先说要他做什么,再说不做会怎样。写给不是开发者的人看,别用内部词、别解释机制。
237
+
238
+ 门槛,三条都过了才写:
239
+
240
+ 1. **不做就不行**:不解决,persona 在云上跑不起来或核心动作是断的。「可能不太顺」「建议优化」不算。
241
+ 2. **只有老板能做**:要他改 persona、换一个服务、去别处申请点什么。**你自己能查清的先查清**——读文件、
242
+ 看 `--help`、上网查,查完要么进清单(cli / secret / asset / connector 各有位置),要么没事,都不是 note。
243
+ 3. **确定,不是猜**:你真读到了、真查到了。带「如果 / 可能 / 大概率」的句子写不出来就对了——拿不准就去查,
244
+ 查不清就不写。
245
+
246
+ 不写的:
247
+
248
+ - 「云上容器已经有的」那节里的东西(node、可写目录、飞书登录态……)
249
+ - 你自己的推理过程(「我按底座已有 node 处理」「我没报这条是因为…」)
250
+ - 空结论(「两份共享资产都没用到」「没发现需要凭据的地方」)——没有就是没有,老板不需要知道你检查过
251
+ - 已经报进清单的东西再说一遍
252
+
253
+ 反例(一次真实扫描里的五条,全部不该写):
254
+
255
+ | 写了什么 | 为什么不该 |
256
+ |---|---|
257
+ | 两份共享资产都没用到:persona 目录里没有 .mcp.json… | 空结论 |
258
+ | 人格里的初始化流程(lark-cli config init / auth login)是交互式,得靠飞书连接器把登录态带进去 | 容器已有:飞书登录态本来就是连接器带进去的,交互式步骤不会走到 |
259
+ | whiteboard-cli 是 npm 包运行需要 node,我按底座已有 node 处理、没单报一条 | 容器已有 + 自述推理 |
260
+ | lark-event 是长连接,云上容器有执行时长上限或出网限制的话可能跑不完整 | 猜测;且 2 小时上限在「已有」那节 |
261
+ | 画板上传会落临时文件,容器里需要一个可写的工作目录 | 容器已有 |
262
+
263
+ 正例:「人格里的『同步文档』这一步要先在浏览器里手动登录 Notion 再跑脚本,云上没有浏览器——把它换成用
264
+ token 的方式,或者把这一步从人格里去掉」;「`s` 这个工具官方文档和包源都没找到 Linux 装法,查了 npm /
265
+ GitHub release / apt,你得告诉我它从哪装」。
266
+
267
+ 没有就空数组,**空数组是常态**。
202
268
 
203
269
  调完 `finishScan` 页面就从「分析中」转成可编辑的清单,你的活就结束了。
204
270
 
205
271
  ## 纪律
206
272
 
207
273
  - **只读不写**:不要修改被扫 persona 目录里的任何文件,不要执行它的脚本。
208
- - **宁缺毋滥**:拿不准的放进 `notes`,不要编一条依赖进清单。多报一条假依赖会让镜像装一堆没用的
209
- 东西;漏报一条真依赖,上云后用起来会发现,补一行再发一次就行。
274
+ - **宁缺毋滥**:拿不准的去查清,不要编一条依赖进清单;查清了、且过得了 `notes` 那三道门槛的才是 note。
275
+ 多报一条假依赖会让镜像装一堆没用的东西;漏报一条真依赖,云上试跑和用起来会发现,补一行再发一次就行。
210
276
  - **没调 `finishScan` 就等于没扫完**——中途卡住也要调,把卡在哪写进 `notes`,别让页面一直停在
211
277
  「分析中」。
@@ -14667,6 +14667,7 @@ var StdioClientTransport = class {
14667
14667
  // src/tools/codex-mcp-config.ts
14668
14668
  var import_node_fs = __toESM(require("fs"), 1);
14669
14669
  var import_node_path = __toESM(require("path"), 1);
14670
+ var HTTP_TYPES = /* @__PURE__ */ new Set(["http", "sse", "streamable_http", "streamable-http"]);
14670
14671
  function loadCodexMcpServerDefs(args) {
14671
14672
  const out = [];
14672
14673
  const seen = /* @__PURE__ */ new Set();
@@ -14686,7 +14687,21 @@ function loadCodexMcpServerDefs(args) {
14686
14687
  if (!servers || typeof servers !== "object") continue;
14687
14688
  for (const [name, raw] of Object.entries(servers)) {
14688
14689
  const def = raw ?? {};
14689
- const type = typeof def.type === "string" ? def.type : "stdio";
14690
+ const type = typeof def.type === "string" ? def.type.toLowerCase() : typeof def.url === "string" ? "http" : "stdio";
14691
+ if (HTTP_TYPES.has(type)) {
14692
+ if (typeof def.url !== "string" || !def.url) {
14693
+ args.logger?.warn("codex mcp server http without url, skipped", { file, name });
14694
+ continue;
14695
+ }
14696
+ if (seen.has(name)) {
14697
+ args.logger?.warn("codex mcp server name collision, first-wins", { file, name });
14698
+ continue;
14699
+ }
14700
+ seen.add(name);
14701
+ const headers = def.headers && typeof def.headers === "object" ? Object.fromEntries(Object.entries(def.headers).filter((kv) => typeof kv[1] === "string")) : {};
14702
+ out.push({ name, url: def.url, ...Object.keys(headers).length > 0 ? { httpHeaders: headers } : {} });
14703
+ continue;
14704
+ }
14690
14705
  if (type !== "stdio") {
14691
14706
  args.logger?.warn("codex mcp server type unsupported, skipped", { file, name, type });
14692
14707
  continue;