Skip to content

[codex] fix ACP MCP auth forwarding#3667

Open
neubig wants to merge 4 commits into
mainfrom
codex/acp-mcp-auth-forwarding
Open

[codex] fix ACP MCP auth forwarding#3667
neubig wants to merge 4 commits into
mainfrom
codex/acp-mcp-auth-forwarding

Conversation

@neubig

@neubig neubig commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

  • translate FastMCP-style remote MCP auth into ACP-compatible bearer headers
  • preserve explicit Authorization headers when both headers and auth are configured
  • add regression coverage for ACP MCP auth forwarding

Root cause

The normal SDK MCP client understands remote MCP auth, but ACP session creation can only receive MCP headers. ACP forwarding passed headers through and dropped string auth, so authenticated remote MCP servers could be visible in settings but unavailable to ACP providers like Codex.

Validation

  • uv run --project /home/gneubig/work/software-agent-sdk pytest tests/sdk/agent/test_acp_agent.py -q
  • uv run --project /home/gneubig/work/software-agent-sdk ruff check openhands-sdk/openhands/sdk/agent/acp_agent.py tests/sdk/agent/test_acp_agent.py

Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:968cc8b-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-968cc8b-python \
  ghcr.io/openhands/agent-server:968cc8b-python

All tags pushed for this build

ghcr.io/openhands/agent-server:968cc8b-golang-amd64
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-golang-amd64
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-golang-amd64
ghcr.io/openhands/agent-server:968cc8b-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:968cc8b-golang-arm64
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-golang-arm64
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-golang-arm64
ghcr.io/openhands/agent-server:968cc8b-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:968cc8b-java-amd64
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-java-amd64
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-java-amd64
ghcr.io/openhands/agent-server:968cc8b-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:968cc8b-java-arm64
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-java-arm64
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-java-arm64
ghcr.io/openhands/agent-server:968cc8b-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:968cc8b-python-amd64
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-python-amd64
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-python-amd64
ghcr.io/openhands/agent-server:968cc8b-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:968cc8b-python-arm64
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-python-arm64
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-python-arm64
ghcr.io/openhands/agent-server:968cc8b-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:968cc8b-golang
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-golang
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-golang
ghcr.io/openhands/agent-server:968cc8b-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:968cc8b-java
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-java
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-java
ghcr.io/openhands/agent-server:968cc8b-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:968cc8b-python
ghcr.io/openhands/agent-server:968cc8bccc9f5d87d36c43faf22eeaa2ffb61929-python
ghcr.io/openhands/agent-server:codex-acp-mcp-auth-forwarding-python
ghcr.io/openhands/agent-server:968cc8b-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., 968cc8b-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 968cc8b-python-amd64) are also available if needed

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/agent
   acp_agent.py115610291%414, 779–781, 1018–1019, 1062, 1064, 1068, 1072, 1098, 1161–1162, 1167, 1234, 1548, 1551–1552, 1569–1570, 1606, 1611, 1689, 1694, 2028–2029, 2313–2316, 2320–2322, 2325–2329, 2331, 2549, 2563–2564, 2567–2569, 2577, 2581, 2585–2586, 2592–2593, 2605–2606, 2609, 2643, 2647–2649, 2653–2654, 2686, 2770, 2957–2959, 2962–2963, 3003, 3145, 3149, 3157–3159, 3197–3198, 3201, 3209–3211, 3213, 3215, 3219, 3222, 3231–3233, 3235, 3271–3272, 3290–3293, 3296, 3300–3302, 3304, 3308–3309, 3517–3518
TOTAL30510842872% 

@neubig neubig marked this pull request as ready for review June 11, 2026 19:28

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ QA Report: PASS

Exercised the SDK ACPAgent session startup path with a local ACP-compatible stdio server and confirmed the PR forwards remote MCP auth as an ACP bearer header while preserving explicit Authorization headers.

Does this PR achieve its stated goal?

Yes. The PR set out to translate FastMCP-style remote MCP auth into ACP-compatible bearer headers and avoid overriding explicit Authorization headers. In the base branch, an ACP session created from an MCP server with auth: "token-y" received headers: []; on the PR branch, the same real Conversation.run() / ACPAgent path sent Authorization: Bearer token-y in session/new. When both headers.authorization and auth were configured, the PR branch forwarded only the explicit authorization: Bearer explicit header, confirming it does not overwrite user-provided Authorization.

Phase Result
Environment Setup ✅ Dependencies were available after repository setup (uv sync --dev completed earlier); SDK scripts ran through uv run successfully.
CI Status 🟡 At review time: 22 checks passing, 6 Docker build/push checks still in progress, 1 skipped cleanup check.
Functional Verification ✅ Base reproduced the missing ACP auth header; PR forwarded the bearer header and preserved explicit Authorization.
Functional Verification

Test 1: Remote MCP string auth is forwarded to ACP as a bearer header

Step 1 — Reproduce / establish baseline (without the fix):

Checked out origin/main (4c66baf7) and ran a small SDK scenario that creates an ACPAgent, starts a real Conversation.run(), and points it at a local ACP-compatible stdio server that captures the session/new payload:

git checkout --quiet origin/main
rm -f /tmp/qa_acp_trace.jsonl /tmp/qa_acp_mcp_auth.jsonl
OPENHANDS_SUPPRESS_BANNER=1 uv run python /tmp/qa_run_acp_mcp_auth.py auth

Relevant captured output:

{"method": "session/new", "params": {"cwd": "/home/runner/work/software-agent-sdk/software-agent-sdk/pr-repo", "mcpServers": [{"headers": [], "name": "remote", "type": "http", "url": "https://example.invalid/mcp"}]}}

This confirms the bug: the SDK forwarded the remote MCP server to ACP, but the auth: "token-y" setting was dropped, leaving ACP with no Authorization header.

Step 2 — Apply the PR's changes:

Checked out the PR branch at 067e271583842f2512779af4afb7811b0f8d519a:

git checkout --quiet codex/acp-mcp-auth-forwarding

Step 3 — Re-run with the fix in place:

Ran the same SDK scenario on the PR branch:

rm -f /tmp/qa_acp_trace.jsonl /tmp/qa_acp_mcp_auth.jsonl
OPENHANDS_SUPPRESS_BANNER=1 uv run python /tmp/qa_run_acp_mcp_auth.py auth

Relevant captured output:

{"method": "session/new", "params": {"cwd": "/home/runner/work/software-agent-sdk/software-agent-sdk/pr-repo", "mcpServers": [{"headers": [{"name": "Authorization", "value": "Bearer token-y"}], "name": "remote", "type": "http", "url": "https://example.invalid/mcp"}]}}

This shows the PR fixes the ACP forwarding boundary: the same user-facing SDK flow now sends the expected bearer Authorization header to the ACP provider.

Test 2: Explicit Authorization header is preserved when auth is also configured

Step 1 — Establish baseline:

On origin/main, I ran the same SDK scenario with both headers.authorization and auth configured:

git checkout --quiet origin/main
rm -f /tmp/qa_acp_mcp_auth.jsonl
OPENHANDS_SUPPRESS_BANNER=1 uv run python /tmp/qa_run_acp_mcp_auth.py explicit

Relevant captured output:

{"method": "session/new", "params": {"cwd": "/home/runner/work/software-agent-sdk/software-agent-sdk/pr-repo", "mcpServers": [{"headers": [{"name": "authorization", "value": "Bearer explicit"}], "name": "remote", "type": "http", "url": "https://example.invalid/mcp"}]}}

The baseline already preserved the explicit header because it ignored auth entirely.

Step 2 — Apply the PR's changes:

Checked out the PR branch again:

git checkout --quiet codex/acp-mcp-auth-forwarding

Step 3 — Re-run with the fix in place:

rm -f /tmp/qa_acp_trace.jsonl /tmp/qa_acp_mcp_auth.jsonl
OPENHANDS_SUPPRESS_BANNER=1 uv run python /tmp/qa_run_acp_mcp_auth.py explicit

Relevant captured output:

{"method": "session/new", "params": {"cwd": "/home/runner/work/software-agent-sdk/software-agent-sdk/pr-repo", "mcpServers": [{"headers": [{"name": "authorization", "value": "Bearer explicit"}], "name": "remote", "type": "http", "url": "https://example.invalid/mcp"}]}}

This verifies the new auth-to-bearer behavior does not override an explicitly configured Authorization header or add a duplicate bearer token.

Issues Found

None.

This QA review was created by an AI agent (OpenHands) on behalf of the user.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants