@pulsemcp/air-adapter-codex
v0.5.0
Published
AIR adapter for OpenAI Codex CLI — translates AIR config to Codex format
Readme
@pulsemcp/air-adapter-codex
AIR adapter extension for the OpenAI Codex CLI. Translates AIR artifacts into Codex's native formats and prepares working directories for agent sessions.
Installation
npm install @pulsemcp/air-adapter-codexUsage
With the AIR CLI
# Install the adapter globally alongside the CLI
npm install -g @pulsemcp/air-cli @pulsemcp/air-adapter-codex
# Start a Codex session
air start codex --root web-appProgrammatic
import { resolveArtifacts } from "@pulsemcp/air-core";
import { CodexAdapter } from "@pulsemcp/air-adapter-codex";
const artifacts = await resolveArtifacts("./air.json");
const adapter = new CodexAdapter();
// Prepare a working directory for a Codex session
const session = await adapter.prepareSession(artifacts, "./my-project", {
root: artifacts.roots["web-app"],
});
// session.configFiles — [] (Codex config is TOML, see "Secrets" below)
// session.skillPaths — skill dirs created in .agents/skills/
// session.hookPaths — hook dirs created in .codex/hooks/
// session.startCommand — { command: "codex", args: [], cwd: "..." }What prepareSession() does
- Writes
.codex/config.toml— translates AIR MCP server configs into[mcp_servers.*]tables and registers path-based hooks under[[hooks.<Event>]]. User-authored servers, hooks, and top-level keys are preserved; only AIR-owned keys are replaced. - Injects skills — copies
SKILL.mdfiles and associated content into.agents/skills/{name}/, where Codex discovers them. - Injects hooks — copies hook directories into
.codex/hooks/{name}/and registers their command inconfig.toml, anchored to the repo root. - Copies references — attaches referenced documents into
{artifact}/references/. - Respects local priority — if a skill or hook directory already exists in the target, it is not overwritten.
Translation Details
| AIR Format | Codex Format |
|------------|--------------|
| mcp.json (flat map with type, title, description) | [mcp_servers.<name>] tables in .codex/config.toml (metadata stripped) |
| stdio servers | { command, args, env, env_vars } |
| sse / streamable-http servers | { url, http_headers, env_http_headers } (Codex auto-detects transport from the URL) |
| MCP env value ${VAR} where key == VAR | env_vars = ["VAR"] (host env forwarding) |
| MCP env value ${OTHER} (renamed) or literal | left in the env table |
| Header value ${VAR} | env_http_headers = { Header = "VAR" } |
| Skills (SKILL.md + content) | .agents/skills/{name}/ |
| Hooks (HOOK.json + scripts) | .codex/hooks/{name}/ + [[hooks.<Event>]] registration |
| Hook events session_start, pre_tool_call, post_tool_call, user_prompt_submit, stop | Codex SessionStart, PreToolUse, PostToolUse, UserPromptSubmit, Stop |
| References | {artifact}/references/ |
Secrets
Codex's config is TOML, which is outside AIR's JSON-based transform/validation pipeline. Instead of writing ${VAR} placeholders, the adapter maps secret references to Codex's native host-env forwarding at translation time:
env: { GITHUB_TOKEN: "${GITHUB_TOKEN}" }→env_vars = ["GITHUB_TOKEN"]— Codex injects the host'sGITHUB_TOKENat launch.headers: { Authorization: "${API_TOKEN}" }→env_http_headers = { Authorization = "API_TOKEN" }.
As a result, prepareSession() returns an empty configFiles array — there is no JSON config for secret transforms to post-process.
Limitation — only whole-value, same-named refs forward. Codex's env_vars forwards a host var to an env key of the same name, and env_http_headers forwards a host var as a whole header value. A renamed ref (KEY = "${OTHER}") or a partial value ("Bearer ${TOKEN}") can't be expressed either way, so it falls through to the literal env / http_headers table. Because the TOML never passes through AIR's ${VAR} transform pipeline, Codex would inject the literal ${…} string at runtime — so the adapter emits a console.warn for each such value instead of silently shipping a broken secret. Rewrite these as whole-value, same-named refs (or set the value directly).
Known gaps
These AIR features have no static Codex equivalent and are handled out of band:
- OAuth MCP servers — AIR's detailed OAuth config (
clientId/scopes/redirectUri/…) has no staticconfig.tomlform. Codex performs interactive OAuth viacodex mcp login <name>. - Plugins — Codex's marketplace plugins are remote-installed (
codex plugin add). AIR treats plugins as composition sugar: a plugin's declared MCP servers / skills / hooks are expanded into the activation set and materialized as their underlying Codex-native artifacts. - Subagent context — Codex has no
--append-system-promptflag, so subagent-root context is returned to the caller viaPreparedSession.subagentContextrather than passed to the CLI.
