(name, config)
| 247 | function tomlValue(value) { |
| 248 | if (value === null || value === undefined) return null; |
| 249 | if (typeof value === 'string') return `"${tomlEscape(value)}"`; |
| 250 | if (typeof value === 'number' || typeof value === 'boolean') return String(value); |
| 251 | if (Array.isArray(value)) { |
| 252 | if (value.length === 0) return '[]'; |
| 253 | return '[' + value.map(tomlValue).filter((v) => v !== null).join(', ') + ']'; |
| 254 | } |
| 255 | if (typeof value === 'object') { |
| 256 | const inner = Object.entries(value) |
| 257 | .filter(([k]) => !k.startsWith('_')) |
| 258 | .map(([k, v]) => `${k} = ${tomlValue(v)}`) |
| 259 | .filter((line) => !line.endsWith(' = null')); |
| 260 | return '{ ' + inner.join(', ') + ' }'; |
| 261 | } |
| 262 | return null; |
| 263 | } |
| 264 | |
| 265 | function mcpServerToToml(name, config) { |
| 266 | const lines = [`[mcp_servers.${name}]`]; |
| 267 | const warnings = []; |
| 268 | |
| 269 | // Map common fields (Claude shape → Codex shape) |
| 270 | for (const [claudeKey, codexKey] of Object.entries(CLAUDE_TO_CODEX_MAP)) { |
| 271 | if (!(claudeKey in config) || config[claudeKey] === null || config[claudeKey] === undefined) continue; |
| 272 | const value = config[claudeKey]; |
| 273 | // Skip empty arrays/objects to keep the TOML tidy. |
| 274 | if (Array.isArray(value) && value.length === 0) continue; |
| 275 | if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) continue; |
| 276 | const rendered = tomlValue(value); |
| 277 | if (rendered !== null) lines.push(`${codexKey} = ${rendered}`); |
| 278 | } |
| 279 | |
| 280 | // Token-style env detection: emit bearer_token_env_var as a hint to Codex |
| 281 | // for HTTP servers, but keep the env var in `env` (handled above) so stdio |
| 282 | // servers still see it. |
| 283 | if (config.env && typeof config.env === 'object') { |
| 284 | for (const k of Object.keys(config.env)) { |
| 285 | if (k.startsWith('_')) continue; |
| 286 | if (/token/i.test(k) && config.url) { |
| 287 | lines.push(`bearer_token_env_var = "${tomlEscape(k)}"`); |
| 288 | break; // only one bearer slot per server |
| 289 | } |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | // Codex-only extensions: anything under `_codex` is emitted verbatim. |
| 294 | // This is the forward-compatibility hatch — when OpenAI ships a new |
| 295 | // [mcp_servers.*] field, users can set it without touching this script. |
| 296 | if (config._codex && typeof config._codex === 'object') { |
| 297 | for (const [k, v] of Object.entries(config._codex)) { |
| 298 | if (v === null || v === undefined) continue; |
| 299 | const rendered = tomlValue(v); |
| 300 | if (rendered !== null) lines.push(`${k} = ${rendered}`); |
| 301 | } |
| 302 | } |
no test coverage detected