Repository navigation
fix(remote): auth /acp dial via Authorization header, offer only acp.v1 - #45
Merged
Merged
Conversation
The remote reverse-MCP dial failed at the WS handshake with
`SubProtocol error: Server sent an invalid subprotocol`, even though the
gateway is healthy (verified live: a compliant client sending the exact
`openab.bearer.<token>, acp.v1` offer connects and negotiates `acp.v1`;
a wrong bearer gets HTTP 401).
Root cause is client-side, in tokio-tungstenite 0.23. It parses the
request's `Sec-WebSocket-Protocol` with `split(",")` and does NOT trim
(handshake/client.rs:189), so our single combined offer
`openab.bearer.<token>, acp.v1` is stored as
[`openab.bearer.<token>`, ` acp.v1`] — the second element keeps a leading
space. When the server echoes `acp.v1`, the response check
(client.rs:270-274) does `accepted_subprotocols.contains("acp.v1")`,
which is false (` acp.v1` != `acp.v1`), so it errors with
`InvalidSubProtocol`. Browser/Python clients maintain their own offer
list and trim, so they were unaffected.
Fix: Studio is a native (non-browser) client, so send the bearer in the
`Authorization` header (the gateway reads `ws_bearer_token` from it) and
offer only `acp.v1` as the sub-protocol — one clean token that matches
the server's echo. This also stops the secret bearer from riding
`Sec-WebSocket-Protocol`, which is prone to being logged by proxies
(Cloudflare sits in the path). `bearer_subprotocol()` is retained,
documented as the browser-only form, and guarded by a regression test.
Verified live end-to-end against wss://orca-acp.brettchien.cc/acp
(Authorization header + subprotocol acp.v1 connects). acp-tunnel unit
tests pass; studio-desktop compilation is left to CI (separate GUI
workspace).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brettchien
added a commit
that referenced
this pull request
Aug 14, 2026
…46) Even with the #45 handshake fix, the remote `/acp` connection still fails at `initialize`: Studio sends `"protocolVersion": "2024-11-05"` (an MCP-style date string), but ACP versions the wire protocol with a **u16 integer**. The gateway deserializes the field as `u16`, so the string is rejected: "protocolVersion": "2024-11-05" -> error -32602 "invalid type: string \"2024-11-05\", expected u16" "protocolVersion": 1 -> result { agentCapabilities … } (verified live) Fix: the outer `/acp` handshake now sends `acp::ACP_PROTOCOL_VERSION` (u16 = 1). The *tunnelled* inner MCP `initialize` (relayed to the `oab-mcp` sidecar in `handle_inner`) correctly keeps the MCP date string — that one is MCP, not ACP, and is left untouched. A comment marks the distinction so the two are not "unified" by mistake, and a regression test asserts `ACP_PROTOCOL_VERSION` serializes to a JSON number. Verified live against wss://orca-acp.brettchien.cc/acp: with pv=1 the full initialize -> session/new completes and returns a sessionId (server side confirmed by Jellyfish). acp-tunnel unit tests pass; studio-desktop compilation left to CI. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
Activating the remote connection dials
wss://…/acp, saves config, then fails:TLS, the Cloudflare upgrade, and auth are all fine — it dies purely in sub-protocol negotiation.
Root cause (client-side, tokio-tungstenite 0.23)
bearer_subprotocol()returnsopenab.bearer.<token>, acp.v1, andremote.rsset that as a singleSec-WebSocket-Protocolheader. tokio-tungstenite 0.23 parses the request's offer withsplit(",")without trimming:so when the server echoes
acp.v1, the response check fails:Browser / Python clients keep their own offer list and trim, so they were unaffected — which is why the gateway tested healthy.
Fix
Studio is a native (non-browser) client, so:
Authorization: Bearer <token>header (the gateway readsws_bearer_tokenfrom it), andacp.v1as the sub-protocol — one clean token that matches the server's echo.Bonus: the secret bearer no longer rides
Sec-WebSocket-Protocol, which proxies (Cloudflare is in the path) are prone to log.bearer_subprotocol()stays as the documented browser-only form, guarded by a new regression test.Verification
Authorizationheader +subprotocols=[acp.v1]connects and negotiatesacp.v1againstwss://orca-acp.brettchien.cc/acp; a wrong bearer gets HTTP 401. (Server side verified by Jellyfish againstopenab-gatewayacp_server.rs.)cargo test -p acp-tunnel— 19 passed (incl. newacp_subprotocol_is_a_single_clean_token).studio-desktopcompilation left to CI (desktop.yml) — separate GUI workspace, not built on the ECS host to avoid OOM.🤖 Generated with Claude Code