Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions crates/acp-tunnel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ pub mod config;
/// The ACP sub-protocol token the server echoes back on the `/acp` upgrade.
pub const ACP_SUBPROTOCOL: &str = "acp.v1";

/// The ACP protocol version Studio negotiates on the `/acp` `initialize`.
///
/// ACP versions the wire protocol with a **u16 integer** — unlike MCP, which
/// uses a date string (e.g. `"2024-11-05"`). The gateway deserializes this
/// field as `u16`, so sending the MCP string fails with
/// `invalid type: string, expected u16`. The *tunnelled* MCP `initialize`
/// (what Studio relays to the `oab-mcp` sidecar) still uses the MCP date string —
/// only the outer `/acp` handshake uses this integer.
pub const ACP_PROTOCOL_VERSION: u16 = 1;

/// The WebSocket `Sec-WebSocket-Protocol` offer for a bearer-authed `/acp` dial:
/// `openab.bearer.<token>, acp.v1` (tunnel contract §1). The server echoes only
/// `acp.v1`. The token is a secret — this returns it for the transport to place
Expand Down Expand Up @@ -350,6 +360,17 @@ mod tests {
);
}

#[test]
fn acp_protocol_version_is_an_integer_not_an_mcp_date_string() {
// ACP versions the protocol with a u16; MCP uses a date string. The
// `/acp` gateway deserializes `protocolVersion` as u16, so it must
// serialize to a JSON number — a string regresses to
// `invalid type: string, expected u16`.
let v = json!({ "protocolVersion": ACP_PROTOCOL_VERSION });
assert!(v["protocolVersion"].is_number());
assert_eq!(v["protocolVersion"], json!(1));
}

#[test]
fn acp_subprotocol_is_a_single_clean_token() {
// Native transports offer this verbatim as one `Sec-WebSocket-Protocol`
Expand Down
6 changes: 5 additions & 1 deletion src-tauri/src/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,12 @@ async fn run_once<R: Runtime>(
let mut session = Session::new(vec![acp::oab_server(&conn_id)]);

// Drive the client handshake: initialize, then session/new declaring "oab".
// This is the outer **ACP** handshake — `protocolVersion` is a u16 integer
// (the gateway deserializes it as `u16`). Do not copy MCP's date string here;
// the MCP date string is correct only for the *tunnelled* inner `initialize`
// in `handle_inner` below.
let (_id, init) = session.initialize(json!({
"protocolVersion": "2024-11-05",
"protocolVersion": acp::ACP_PROTOCOL_VERSION,
"clientCapabilities": {},
"clientInfo": { "name": "oab-studio", "version": env!("CARGO_PKG_VERSION") }
}));
Expand Down
Loading