diff --git a/packages/adapters/codex-local/src/server/codex-home.test.ts b/packages/adapters/codex-local/src/server/codex-home.test.ts index 5761d9756d..ea7fdd5a75 100644 --- a/packages/adapters/codex-local/src/server/codex-home.test.ts +++ b/packages/adapters/codex-local/src/server/codex-home.test.ts @@ -1023,7 +1023,8 @@ describe("evaluateCodexCredentialReadiness", () => { const alpha = await fs.readFile(path.join(alphaHome, "config.toml"), "utf8"); const zero = await fs.readFile(path.join(zeroHome, "config.toml"), "utf8"); expect(alpha).toContain('[mcp_servers."alpha"]'); - expect(alpha).toContain('Authorization = "Bearer alpha-token"'); + expect(alpha).toContain('bearer_token_env_var = "PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1"'); + expect(alpha).not.toContain("alpha-token"); expect(zero).not.toContain("mcp_servers."); expect(zero).not.toContain("stale-token"); expect(alphaHome).not.toBe(zeroHome); @@ -1142,7 +1143,7 @@ describe("stageCodexHomeForSync", () => { } }); - // config.toml carries the managed MCP `Authorization: Bearer …` header and is + // config.toml carries only the managed MCP bearer-token environment variable name and is // secret-bearing; the staged copy must be 0600, not the world-readable default. it("writes the staged config.toml (managed MCP bearer header) with mode 0600", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-stage-toml-mode-")); @@ -1154,7 +1155,7 @@ describe("stageCodexHomeForSync", () => { // and is persisted 0600 on disk. await fs.writeFile( path.join(home, "config.toml"), - "[mcp_servers.paperclip]\nheaders = { Authorization = \"Bearer secret-token\" }\n", + "[mcp_servers.paperclip]\nbearer_token_env_var = \"PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1\"\n", { mode: 0o600 }, ); staged = await stageCodexHomeForSync(home, { runId: "run-toml-mode" }); diff --git a/packages/adapters/codex-local/src/server/codex-home.ts b/packages/adapters/codex-local/src/server/codex-home.ts index b19a6d1d5d..655b8eb62c 100644 --- a/packages/adapters/codex-local/src/server/codex-home.ts +++ b/packages/adapters/codex-local/src/server/codex-home.ts @@ -10,6 +10,7 @@ const COPIED_SHARED_FILES = ["config.json", "config.toml", "instructions.md"] as const SYMLINKED_SHARED_FILES = ["auth.json"] as const; const MANAGED_MCP_BLOCK_START = "# BEGIN PAPERCLIP MANAGED MCP"; const MANAGED_MCP_BLOCK_END = "# END PAPERCLIP MANAGED MCP"; +const MANAGED_MCP_BEARER_TOKEN_ENV_PREFIX = "PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_"; /** * The allowlist of managed `CODEX_HOME` entries that the codex-local adapter @@ -33,6 +34,17 @@ export type ManagedCodexMcpGateway = { bearerToken: string; }; +export function managedCodexMcpBearerTokenEnv( + gateways: ManagedCodexMcpGateway[], +): Record { + return Object.fromEntries( + gateways.map((gateway, index) => [ + `${MANAGED_MCP_BEARER_TOKEN_ENV_PREFIX}${index + 1}`, + gateway.bearerToken, + ]), + ); +} + export function mergeManagedCodexMcpGateways( primary: ManagedCodexMcpGateway[], secondary: ManagedCodexMcpGateway[], @@ -316,7 +328,7 @@ function buildManagedMcpBlock(input: { "", `[mcp_servers.${tomlString(managedName)}]`, `url = ${tomlString(url)}`, - `headers = { Authorization = ${tomlString(`Bearer ${gateway.bearerToken}`)} }`, + `bearer_token_env_var = ${tomlString(`${MANAGED_MCP_BEARER_TOKEN_ENV_PREFIX}${index + 1}`)}`, ); }); lines.push(MANAGED_MCP_BLOCK_END); diff --git a/packages/adapters/codex-local/src/server/execute.ts b/packages/adapters/codex-local/src/server/execute.ts index dc60fabad7..3642c5c0b7 100644 --- a/packages/adapters/codex-local/src/server/execute.ts +++ b/packages/adapters/codex-local/src/server/execute.ts @@ -78,6 +78,7 @@ import { resolveSharedCodexHomeDir, seedManagedCodexHome, stageCodexHomeForSync, + managedCodexMcpBearerTokenEnv, mergeManagedCodexMcpGateways, writeManagedCodexMcpConfig, type ManagedCodexMcpGateway, @@ -891,6 +892,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise = { ...paperclipBaseEnv }; + Object.assign(env, managedCodexMcpBearerTokenEnv(managedMcpGateways)); env.PAPERCLIP_RUN_ID = runId; const wakeTaskId = (typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) || diff --git a/server/src/__tests__/codex-local-execute.test.ts b/server/src/__tests__/codex-local-execute.test.ts index 22c3463266..e1d6c82a8e 100644 --- a/server/src/__tests__/codex-local-execute.test.ts +++ b/server/src/__tests__/codex-local-execute.test.ts @@ -23,6 +23,7 @@ const payload = { paperclipApiUrl: process.env.PAPERCLIP_API_URL || null, paperclipApiKey: process.env.PAPERCLIP_API_KEY || null, paperclipApiBridgeMode: process.env.PAPERCLIP_API_BRIDGE_MODE || null, + managedMcpBearerToken: process.env.PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1 || null, paperclipEnvKeys: Object.keys(process.env) .filter((key) => key.startsWith("PAPERCLIP_")) .sort(), @@ -56,6 +57,7 @@ type CapturePayload = { paperclipApiUrl?: string | null; paperclipApiKey?: string | null; paperclipApiBridgeMode?: string | null; + managedMcpBearerToken?: string | null; paperclipEnvKeys: string[]; }; @@ -313,7 +315,9 @@ describe("codex execute", () => { expect(configText).toContain("[mcp_servers.github]"); expect(configText).toContain("[mcp_servers.\"paperclip-github\"]"); expect(configText).toContain('url = "http://paperclip.local:3100/api/tool-gateway/gateways/gateway-1/mcp"'); - expect(configText).toContain('Authorization = "Bearer pcgw_secret-managed-token"'); + expect(configText).toContain('bearer_token_env_var = "PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1"'); + expect(configText).not.toContain("pcgw_secret-managed-token"); + expect(capture.managedMcpBearerToken).toBe("pcgw_secret-managed-token"); expect(logs).toEqual( expect.arrayContaining([ expect.objectContaining({