Skip to content

g06.035 — Gemini CLI ACP consumer HTTP MCP wiring - #377

Merged
betterthanclay merged 4 commits into
mainfrom
ns-d1ab50a1-71ef-4fcd-8fab-d00cb356bc6d
Sep 25, 2026
Merged

betterthanclay merged 4 commits into
mainfrom
ns-d1ab50a1-71ef-4fcd-8fab-d00cb356bc6d

Conversation

@betterthanclay

Copy link
Copy Markdown
Contributor

What changed

Wire Contract 063's consumer-supplied streamable-HTTP MCP placement into
gemini-cli.acp production session/new at the route's current point
(0.59.0, Research 351).

  • GeminiAcpHttpMcpPlacement models the admitted http entry under the
    route-owned name swallowtail-gemini-acp; validation is structural only
    (route-owned non-empty name, absolute http/https URL, well-formed
    header names) and values pass verbatim.
  • Encoding emits ACP type: "http", which the provider maps onto its
    httpUrl transport with declared headers on requestInit.headers
    (acpSessionManager.ts/mcp-client.ts per Research 351).
  • Omission keeps mcpServers byte-identically empty
    ({"cwd":…,"mcpServers":[]} fixture asserted).
  • Provider gates surfaced typed: an unauthenticated open (Research 351's
    authRequired gate) fails as swallowtail.gemini.acp.auth_required and
    the recorded wire still carries the entry — it is never dropped to make the
    open succeed.
  • Redaction: URL and header values never enter failures, Debug, activity,
    or fingerprints; canary-tested on the placement, encoder, prepared session,
    and typed failure.
  • Prepared facade admits the placement through
    GeminiSessionProfileInput::with_http_mcp_placement and carries it onto
    working-state restoration; gemini-cli.headless stays untouched.

Emission is not honouring: the composite client_mcp_servers cell stays a
producer gap naming the live gate; no cell becomes Yes.

Validation

  • cargo fmt -p swallowtail-adapter-gemini -- --check — pass
  • effigy validate:focused swallowtail-adapter-gemini — pass
  • effigy package:verify-affected swallowtail-adapter-gemini — pass
  • effigy qa:routes — pass
  • effigy qa:docs — pass
  • git diff --check — clean
  • full crate test suite (all targets) — 105 passed
  • working public-API baseline for swallowtail-adapter-gemini regenerated
    (purely additive exports)

Research 351, g06.035. Task: d1ab50a1-71ef-4fcd-8fab-d00cb356bc6d.

…n/new (g06.035)

Emit one Contract 063 consumer-supplied streamable-HTTP MCP entry on
production gemini-cli.acp session/new at the route's current point 0.59.0.
Encoding emits ACP type "http" under the route-owned name
swallowtail-gemini-acp, which the provider maps to httpUrl with declared
headers on requestInit.headers (Research 351). Values pass verbatim and
stay out of failures, Debug, activity, and fingerprints; omission keeps
mcpServers byte-identically empty. An unauthenticated open fails typed as
swallowtail.gemini.acp.auth_required and never drops the entry. The
prepared facade admits the placement through
GeminiSessionProfileInput::with_http_mcp_placement and carries it onto
working-state restoration. Headless stays MCP-disabled; the composite
client_mcp_servers cell stays a producer gap naming the live gate.
@betterthanclay

Copy link
Copy Markdown
Contributor Author

Changes required

Reviewed 0140c13e2974647ff0805c108f08be3a571eb4ce for g06.035 (Gemini CLI ACP consumer HTTP MCP wiring).

Blocking

  1. Unauthenticated session/new is not the typed gate this task requires.
    crates/swallowtail-adapter-gemini/src/connection_dispatch.rs maps a JSON-RPC error to swallowtail.gemini.acp.auth_required only when the lowercased message contains authrequired.
    That substring is not what Gemini CLI 0.59.0 puts on the production path this PR wires. packages/cli/src/acp/acpSessionManager.ts newSession throws new acp.RequestError(-32000, authErrorMessage || 'Authentication required.'); the missing-key arm sets authErrorMessage = 'Gemini API key is missing or not configured.'.
    Research 351's RequestError.authRequired() citation is the loadSession helper, and even that SDK default (@agentclientprotocol/sdk@0.16.1) serializes as Authentication required, not authRequired / auth_required.
    None of those strings contain authrequired, so a real unauthenticated open stays swallowtail.gemini.acp.request_rejected. The typed-outcome test is circular: tests/support/agent.rs invents "authRequired: no authenticated profile is selected" with code -32603.
    Merge when: bind the actual session/new messages (and the SDK authRequired default), use those exact strings/codes in the fixture, and prove the camelCase identifier is not required. Keep the already-correct proof that a refused open still sends the HTTP entry.

  2. Contract 061 never publishes the placement.
    Contract 063 admits emission only when the route also publishes the placement through Contract 061. This crate already has that surface (GeminiPreparedSession::consumer_route_projection_contribution) and exports GEMINI_ACP_HTTP_MCP_PLACEMENT as the presence token, but consumer_route_projection.rs never emits it. The ACP census stays the old 7 rows.
    g06.019's matching row names consumer-supplied-http plus the route-owned name and carries no URL or headers.
    Merge when: a bound HTTP placement produces that namespaced row, omission stays silent, secrets stay out of the projection, and the census test is updated.

Non-blocking

  • Composite client_mcp_servers stays producer_gap and the notes name the live gate, which is the required emission-is-not-honouring outcome. The evidence JSON still points at g06.005.
  • Working-state restoration is wired through with_prepared_http_mcp, but the prepared test only asserts FreshSessionReplacement, not the restored session/new wire.

Otherwise

Encoder emits ACP type: "http" with {name,value} headers, matching Gemini's Object.fromEntries → httpUrl mapping. Values pass verbatim, omission stays mcpServers: [], Debug redacts URL and header values, and gemini-cli.headless is untouched.

Validation on this head, tree left clean: cargo fmt -p swallowtail-adapter-gemini -- --check, effigy validate:focused swallowtail-adapter-gemini (105 passed), effigy package:verify-affected swallowtail-adapter-gemini, effigy qa:docs, effigy qa:routes, git diff --check. Those tests do not prove the provider's actual auth messages.

…nt row (g06.035 review)

Review findings on 0140c13:
- Bind the unauthenticated gate to the error strings Gemini CLI 0.59.0
  actually sends on session/new: RequestError(-32000, 'Authentication
  required.'), the missing-key arm 'Gemini API key is missing or not
  configured.', and the bundled SDK authRequired() default 'Authentication
  required'. The fixture now uses those exact strings and code, and the
  camelCase authRequired identifier appears nowhere on the wire. The
  refused open still proves the HTTP entry stays emitted.
- Publish the consumer-supplied-http placement through Contract 061: a
  bound placement adds one namespaced mcp.placement row (route
  gemini-cli.acp, segment acp-v1) enumerating consumer-supplied-http and
  swallowtail-gemini-acp, with no URL or header values; omission keeps the
  7-row census silent. The observed projected-open path carries the row
  too, and working-state restoration now proves the restored session/new
  wire.
- Fixture agent records the messages it sends so exact wire errors are
  assertable.
@betterthanclay

Copy link
Copy Markdown
Contributor Author

Ready to merge

Reviewed ccca7ead2a625ca59ec36e8b16bca21c32d0ceb7 for g06.035. Both blockers from 0140c13e are fixed. No new blocking findings.

Prior blockers

  1. Typed auth gate now binds Gemini 0.59.0's actual session/new strings. connection_dispatch.rs matches authentication required and api key is missing or not configured after lowercasing. Fixtures use JSON-RPC -32000 with Authentication required., Gemini API key is missing or not configured., and the SDK default Authentication required. The loop asserts those exact messages on the wire, maps each to swallowtail.gemini.acp.auth_required, still sends the HTTP entry, and keeps the canary out of the failure.

  2. Contract 061 publishes the placement. A bound session emits one namespaced mcp.placement row (acp-v1) naming consumer-supplied-http and swallowtail-gemini-acp. Omission stays at the 7-row census. Canary URL/header values stay out of the row and Debug.

Acceptance

  • Encoder emits ACP type: "http" with {name,value} headers (Gemini maps to httpUrl / requestInit.headers).
  • Values pass verbatim; omission keeps mcpServers: [].
  • Debug, failures, and the projection redact URL and header values.
  • Unauthenticated open fails typed and does not drop the entry.
  • Prepared facade and working-state restoration carry the entry; restoration is now asserted on the restored session/new wire.
  • client_mcp_servers stays producer_gap naming the live gate. Headless MCP-disabled path unchanged. Scope stays on owned Gemini/docs/changelog/API-baseline paths.

Non-blocking: the CHANGELOG entry repeats the honouring/auth sentence once; the matrix evidence JSON still cites g06.005 while the notes name g06.035 and the live gate.

Validation on this head, tree left clean: cargo fmt -p swallowtail-adapter-gemini -- --check, effigy validate:focused swallowtail-adapter-gemini (107 passed), effigy package:verify-affected swallowtail-adapter-gemini, effigy qa:docs, effigy qa:routes, git diff --check.

…-8fab-d00cb356bc6d

# Conflicts:
#	CHANGELOG.md
@betterthanclay

Copy link
Copy Markdown
Contributor Author

Ready to merge

Reviewed fb47b782d562efc34dd65f1c7916a87580559c43 for g06.035. This head is ccca7ead plus a main merge. Gemini-owned sources, tests, guide, and API baseline are byte-identical to the last accepted review. No new blocking findings.

Merge

The only conflict was CHANGELOG.md. Resolution keeps the g06.035 Unreleased entry after the sibling HTTP-MCP wiring notes, with no conflict markers. PR-vs-main scope is still the 18 owned Gemini/docs/changelog/API-baseline paths.

Prior blockers (still fixed)

  1. Unauthenticated session/new maps Gemini 0.59.0's actual strings (Authentication required., missing API key, SDK default Authentication required) to swallowtail.gemini.acp.auth_required and still sends the HTTP entry.
  2. Contract 061 publishes one mcp.placement row naming consumer-supplied-http and swallowtail-gemini-acp; omission stays at the 7-row census; secrets stay out.

Encoder, verbatim values, empty omission, Debug redaction, restoration wire, client_mcp_servers producer-gap naming the live gate, and untouched headless still hold.

Validation

On this head, tree left clean: cargo fmt -p swallowtail-adapter-gemini -- --check, effigy validate:focused swallowtail-adapter-gemini (107 passed), effigy package:verify-affected swallowtail-adapter-gemini, effigy qa:docs, git diff --check.

effigy qa:routes failed on an inherited main condition this lane does not own: producer_gap task lacks a Status line: docs/roadmaps/g06/033-claude-agent-acp-consumer-http-mcp-wiring.md. That card and the claude-agent.acp matrix row are already on origin/main (f5f1d870). This PR's Gemini cell still cites g06.005, which has a Status line. Do not treat that routes failure as a g06.035 revision.

Non-blocking: Gemini client_mcp_servers evidence JSON still points at g06.005 while the notes name g06.035 and the live gate.

@betterthanclay

Copy link
Copy Markdown
Contributor Author

Ready to merge

Reviewed 9af5a30c445b20ea883cd5d4f0f452d1bc9794d9 for g06.035. This head is the last accepted review (fb47b782 / ccca7ead Gemini work) plus a main merge that retargets the claude-agent.acp producer-gap citation to open g06.005. Gemini-owned sources, tests, guide, and API baseline are byte-identical. No new blocking findings.

PR-vs-main scope is still the 18 owned Gemini/docs/changelog/API-baseline paths. CHANGELOG still carries the g06.035 Unreleased entry.

Prior blockers remain fixed: unauthenticated session/new binds Gemini 0.59.0's actual auth strings to swallowtail.gemini.acp.auth_required without dropping the HTTP entry; Contract 061 publishes mcp.placement as consumer-supplied-http plus swallowtail-gemini-acp. Encoder, omission, redaction, restoration wire, matrix-not-Yes, and untouched headless still hold.

Validation on this head, tree left clean: cargo fmt -p swallowtail-adapter-gemini -- --check, effigy validate:focused swallowtail-adapter-gemini (107 passed), effigy package:verify-affected swallowtail-adapter-gemini, effigy qa:docs, effigy qa:routes, git diff --check. The previous routes failure on g06.033 lacking Status is gone on this merge.

Non-blocking: Gemini client_mcp_servers evidence JSON still points at g06.005 while the notes name g06.035 and the live gate.

@betterthanclay
betterthanclay merged commit 3f4275b into main Sep 25, 2026
11 checks passed
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.

1 participant