fix(codex): pass MCP bearer tokens through env
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
0e14c61da7
commit
495a8fc95e
|
|
@ -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" });
|
||||
|
|
|
|||
|
|
@ -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<string, string> {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ import {
|
|||
resolveSharedCodexHomeDir,
|
||||
seedManagedCodexHome,
|
||||
stageCodexHomeForSync,
|
||||
managedCodexMcpBearerTokenEnv,
|
||||
mergeManagedCodexMcpGateways,
|
||||
writeManagedCodexMcpConfig,
|
||||
type ManagedCodexMcpGateway,
|
||||
|
|
@ -891,6 +892,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||
onEvent,
|
||||
});
|
||||
const env: Record<string, string> = { ...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()) ||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in New Issue