fix: route MCP calls to the .mtls.googleapis.com endpoint for Agent Identity#6459
fix: route MCP calls to the .mtls.googleapis.com endpoint for Agent Identity#6459ShresthSamyak wants to merge 4 commits into
Conversation
…dentity MCPSessionManager negotiates an mTLS transport for Google MCP endpoints, but get_mcp_toolset builds the connection with the endpoint as registered (a standard *.googleapis.com host). A channel-bound (Agent Identity) access token is only accepted on the *.mtls.googleapis.com endpoint, so the call still 401s even though mTLS is configured. Resolve the MCP endpoint through _mtls_utils.effective_googleapis_endpoint when a client certificate is configured, mirroring the A2A fix (google#6370) and the Application Integration endpoint resolution in 37ca6fb. No-op for non-Google hosts, already-mTLS hosts, and when GOOGLE_API_USE_MTLS_ENDPOINT=never. Related to google#6365.
gnanirahulnutakki
left a comment
There was a problem hiding this comment.
I found one endpoint-selection edge case in the new routing. The focused Agent Registry and mTLS utility suites pass locally (80 tests); the current red unit-test matrix is failing on unrelated google-antigravity/protobuf version skew. Details and a suggested policy matrix are inline.
| # negotiated. effective_googleapis_endpoint is a no-op for non-Google hosts, | ||
| # already-mTLS hosts, and when GOOGLE_API_USE_MTLS_ENDPOINT=never. | ||
| if _use_client_cert_effective(): | ||
| endpoint_uri = effective_googleapis_endpoint(endpoint_uri) |
There was a problem hiding this comment.
Endpoint selection needs the resolved certificate/transport state, not only the cert-use policy. _use_client_cert_effective() says whether certificate use is enabled; it does not prove that a certificate is available. I reproduced this with GOOGLE_API_USE_CLIENT_CERTIFICATE=true, GOOGLE_API_USE_MTLS_ENDPOINT=auto, and mtls.has_default_client_cert_source() == false: AgentRegistry._base_url correctly remains https://agentregistry.googleapis.com/v1, but the MCP connection becomes https://us-central1-aiplatform.mtls.googleapis.com/mcp. MCPSessionManager then falls back to its plain client when mTLS configuration returns no transport, leaving a non-mTLS client pointed at the mTLS host.
The opposite edge also misses the documented policy: with GOOGLE_API_USE_MTLS_ENDPOINT=always and client-certificate use disabled, this gate never calls the resolver and leaves the regular host. AIP-4114 defines auto as mTLS only when a certificate is available, while always selects the mTLS endpoint independently.
Could this preserve the full matrix—always rewrite, auto rewrite only after mTLS actually negotiates, and never retain the original URL—and add regression cases for auto/no-cert and always/cert-disabled? The A2A follow-up in #6370 already keeps its original endpoint when transport negotiation returns None, which looks like the useful precedent here.
There was a problem hiding this comment.
Verified on c6d04107: the MCP path now shares the base-URL policy, auto is gated on the resolved certificate source, and the regression matrix covers auto with/without a cert plus always, never, and non-Google endpoints. The full focused Agent Registry suite passes locally (52 tests). This addresses the original finding—thank you for the thorough update.
Address review feedback: get_mcp_toolset selected the mTLS endpoint from the client-cert *use* policy (_use_client_cert_effective) rather than cert *availability* plus GOOGLE_API_USE_MTLS_ENDPOINT, mishandling two cases: - auto + cert-use-enabled but no cert present: rewrote to the mTLS host while MCPSessionManager fell back to a plain client, pointing a plain client at the mTLS-only host; - always + cert-use-disabled: never rewrote, leaving the standard host. Extract the base-URL decision into _should_use_mtls_endpoint(client_cert_source) (reused by _get_agent_registry_base_url so they cannot drift), retain the resolved client_cert_source on the registry, and gate the MCP endpoint rewrite on it: always -> mTLS unconditionally; auto -> mTLS only when a cert is available; never -> original URL. Adds regression tests for the full matrix.
|
Thanks for the careful review, @gnanirahulnutakki — you're right on both counts, and the base-URL policy is the correct reference. I gated on I've pushed a fix that routes the MCP endpoint through the same policy the registry base URL already uses:
That gives the full matrix:
Added regression tests for all four Google cases (auto+cert, auto+no-cert, always+no-cert, never+cert) plus the non-Google no-op. On the A2A precedent — agreed it's the right model; #6370 keys off actual transport negotiation returning (The red unit-test matrix is unrelated — the |
gnanirahulnutakki
left a comment
There was a problem hiding this comment.
The updated endpoint policy now matches the full always / auto / never matrix. I checked the author delta at c6d04107, ran the complete focused Agent Registry suite (52 passed), and verified Ruff, isort, and diff checks. One non-blocking pyink formatting nit remains inline; I found no functional concerns.
| toolset = registry.get_mcp_toolset("test-mcp-server") | ||
| assert toolset.connection_params.url == self._GOOGLE_MCP_MTLS_URL | ||
|
|
||
| def test_get_mcp_toolset_never_keeps_plain_endpoint_with_cert( |
There was a problem hiding this comment.
Non-blocking formatter nit: the repository-pinned pyink==25.12 reports this signature and rewrites it onto one line (def test_get_mcp_toolset_never_keeps_plain_endpoint_with_cert(self, registry):). The rest of the focused style checks pass.
Collapse a test signature that fits on one line, per pyink==25.12.
|
Thanks @gnanirahulnutakki — and glad the endpoint policy matrix looks right now. Good catch on the formatting nit; I ran the pinned |
gnanirahulnutakki
left a comment
There was a problem hiding this comment.
Rechecked the formatter follow-up at 2f0043ee. It is exactly the pyink normalization noted in the prior review; the two changed files now pass Pyink, isort, Ruff, and diff checks, and the full focused Agent Registry suite remains green (52 passed). No remaining issues from my review—thank you for the quick update.
|
@ShresthSamyak I can confirm the fix works for both Google BigQuery and Google Drive MCP servers! Just tested it directly in Agent Runtime. Thanks a lot for all the work you put in! |
|
That's great to hear, @HonzaKopecky — thank you for testing it directly in Agent Runtime, and for confirming it across both the BigQuery and Google Drive MCP servers. Really appreciate you following through on the verification; it's good to have it validated on real deployments without the token-sharing workaround. |
Summary
MCP tools obtained via
AgentRegistry.get_mcp_toolset(...)fail with401 UNAUTHENTICATEDwhen the app uses Agent Identity, unlessGOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES=Falseis set (whichworks around it by unbinding the token). This routes Google-hosted MCP servers
to the mutual-TLS endpoint so the channel-bound token is accepted.
Related to #6365 and follow-up to #6370 (which fixes the same class of bug on
the A2A path).
Root cause
MCPSessionManageralready negotiates an mTLS transport for Google MCPendpoints (the
Successfully configured mTLS using AsyncAuthorizedSessionlogconfirms the client certificate and channel-bound token are set up). However,
get_mcp_toolsetbuildsStreamableHTTPConnectionParams(url=endpoint_uri)withthe endpoint exactly as registered — a standard
*.googleapis.comhost. Achannel-bound (Agent Identity) token is only honored on the corresponding
*.mtls.googleapis.comendpoint, so presenting it to the standard endpointstill returns 401 even though mTLS was negotiated.
This is the same issue fixed for A2A in #6370, and mirrors the endpoint
resolution added for Application Integration tools in 37ca6fb.
Change
agent_registry.py— inget_mcp_toolset, when a client certificate isconfigured (
_use_client_cert_effective()), resolve the MCP endpoint throughthe existing
_mtls_utils.effective_googleapis_endpointhelper so it uses the*.mtls.googleapis.comvariant. It is a no-op for non-Google hosts,already-mTLS hosts, and when
GOOGLE_API_USE_MTLS_ENDPOINT=never, so ServiceAccount and non-Google flows are unaffected.
Testing plan
Unit tests (
pytest), all passing:test_get_mcp_toolset_uses_mtls_endpoint_for_google— the connection targetsthe
.mtls.googleapis.comendpoint when a client cert is configured.test_get_mcp_toolset_keeps_plain_endpoint_without_client_cert— the plainendpoint is kept when no client cert is configured.
test_get_mcp_toolset_keeps_non_google_endpoint— non-Google endpoints areleft unchanged.
Live validation against a deployed Agent Runtime + Agent Identity setup with a
registered BigQuery MCP server is welcome; the reporter in #6370 observed the
401 on this exact path.