Skip to content

Conversation

@P-nishant
Copy link

Fix: ID Token support for Cloud Run MCP auth

Why

Cloud Run (and Cloud Functions, IAP, etc.) with "Require authentication" expects
an OIDC ID Token, not an OAuth2 access token. The
ServiceAccountCredentialExchanger always fetched an access token via
credentials.token, so calls to authenticated Cloud Run services failed with
401 Unauthorized.

There was no way for users to ask for an ID token — the only workaround was
monkey-patching the exchanger at runtime.

What changed

ServiceAccount model (src/google/adk/auth/auth_credential.py)

Two new optional fields:

  • use_id_token (bool, default False) — when True, fetch an ID token
    instead of an access token.
  • audience (str) — the target audience for the ID token, typically the
    service URL. Required when use_id_token is True.

scopes also got a default ([]) so callers using ID tokens don't have to pass
an empty list.

ServiceAccountCredentialExchanger (src/google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py)

Added a _fetch_id_token helper and a branch at the top of exchange_credential
that calls it when use_id_token is set. The existing access-token path is
untouched.

For default credentials it uses google.oauth2.id_token.fetch_id_token(). For
explicit service-account JSON keys it uses
service_account.IDTokenCredentials.from_service_account_info().

Tests

Four new tests covering: default-credential ID token, explicit-SA ID token,
missing audience validation, and fetch failure handling. All existing tests still
pass.

Usage

auth_credential = AuthCredential(
    auth_type=AuthCredentialTypes.SERVICE_ACCOUNT,
    service_account=ServiceAccount(
        use_default_credential=True,
        use_id_token=True,
        audience="https://my-service-xyz.us-central1.run.app/mcp",
    ),
)

mcp_toolset = McpToolset(
    connection_params=StreamableHTTPConnectionParams(url=MCP_URL),
    auth_scheme=HTTPBearer(bearerFormat="JWT"),
    auth_credential=auth_credential,
)

PR Description (ready to paste)

Summary

Cloud Run-protected MCP endpoints require an OIDC ID token, but service-account
exchange always returned an OAuth access token. This change adds an opt-in ID
token path for service account auth and keeps existing access-token behavior as
the default.

Testing Plan

  • Run formatter (autoformat.sh equivalent on Windows shell):
    • .\.venv\Scripts\python.exe -m isort src\google\adk\auth\auth_credential.py src\google\adk\tools\openapi_tool\auth\credential_exchangers\service_account_exchanger.py tests\unittests\tools\openapi_tool\auth\credential_exchangers\test_service_account_exchanger.py
    • .\.venv\Scripts\python.exe -m pyink --config pyproject.toml src\google\adk\auth\auth_credential.py src\google\adk\tools\openapi_tool\auth\credential_exchangers\service_account_exchanger.py tests\unittests\tools\openapi_tool\auth\credential_exchangers\test_service_account_exchanger.py
  • Run focused unit tests:
    • .\.venv\Scripts\python.exe -m pytest tests\unittests\tools\openapi_tool\auth\credential_exchangers\test_service_account_exchanger.py -q
  • Run related broader tests:
    • .\.venv\Scripts\python.exe -m pytest tests\unittests\tools\openapi_tool\auth\ tests\unittests\auth\test_credential_manager.py tests\unittests\tools\mcp_tool\test_mcp_tool.py -q

Unit Test Evidence

  • Focused exchanger tests: 11 passed in 1.59s
  • Broader auth + MCP tests: 126 passed, 336 warnings in 2.64s

Manual E2E Evidence (MCP + Cloud Run auth)

Note: This local workspace currently has no Cloud Run endpoint configured
(MCP_URL and GOOGLE_CLOUD_PROJECT are empty), so this section is prepared
for final evidence capture in your Cloud environment.

Please attach in the PR:

  1. A screenshot of adk web prompt/response where the MCP tool call succeeds.
  2. Console logs proving successful authenticated call (no 401).
  3. The exact agent config used (use_id_token=True, audience=<cloud-run-url>).

Suggested log snippet to include:

HTTP Request: POST https://<service>.run.app/mcp "HTTP/1.1 200 OK"
... tool call result ...

Docs Impact

  • This introduces user-facing auth fields (use_id_token, audience) for
    service-account auth flow.
  • Recommended follow-up: open/update a docs PR in google/adk-docs to document
    Cloud Run authenticated MCP setup with ID token usage.

Review Request

  • Request review from ADK auth/tooling maintainers.
  • Suggested focus areas:
    • Backward compatibility of service-account access-token flow.
    • Correctness of ID-token exchange for ADC and explicit service-account keys.
    • Error messaging when audience is missing.

@google-cla
Copy link

google-cla bot commented Feb 12, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@adk-bot adk-bot added the mcp [Component] Issues about MCP support label Feb 12, 2026
@adk-bot
Copy link
Collaborator

adk-bot commented Feb 12, 2026

Response from ADK Triaging Agent

Hello @P-nishant, thank you for creating this PR!

Before we can merge this PR, could you please sign the Contributor License Agreement (CLA)? You can find more information at https://cla.developers.google.com/.

In addition, could you please provide the manual end-to-end (E2E) evidence as described in your "Manual E2E Evidence (MCP + Cloud Run auth)" section? This information will help reviewers to verify your changes more efficiently. Thanks!

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @P-nishant, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an authentication issue where Cloud Run and other identity-aware services, which require OIDC ID tokens, were incorrectly receiving OAuth2 access tokens from the service account credential exchanger. The changes introduce new configuration options to explicitly request an ID token, ensuring proper authentication for these services while maintaining backward compatibility for existing access token flows.

Highlights

  • ID Token Support: Introduced an opt-in mechanism to fetch OIDC ID tokens instead of OAuth2 access tokens for service account authentication, specifically addressing Cloud Run and similar identity-aware services.
  • ServiceAccount Model Updates: Added new optional fields use_id_token (boolean) and audience (string) to the ServiceAccount configuration model. The scopes field now defaults to an empty list.
  • Credential Exchanger Logic: Modified the ServiceAccountCredentialExchanger to include a _fetch_id_token helper, which is called when use_id_token is enabled. This helper uses google.oauth2.id_token.fetch_id_token() for default credentials and service_account.IDTokenCredentials.from_service_account_info() for explicit service account keys.
  • Comprehensive Testing: Added four new unit tests covering ID token fetching with default credentials, explicit service accounts, validation for missing audience, and error handling during token fetching.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • fix-id-token-cloud-run-auth.md
    • Added a new markdown file detailing the problem, solution, usage, testing plan, and review requests for the ID token support.
  • src/google/adk/auth/auth_credential.py
    • Added use_id_token (boolean) and audience (string) as optional fields to the ServiceAccount model.
    • Set the default value of the scopes field to an empty list in the ServiceAccount model.
  • src/google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py
    • Imported google.oauth2.id_token for ID token fetching.
    • Updated the exchange_credential method to check for use_id_token and delegate to a new _fetch_id_token method if enabled.
    • Implemented the _fetch_id_token method to handle ID token retrieval using google.oauth2.id_token.fetch_id_token for default credentials and service_account.IDTokenCredentials.from_service_account_info for explicit service accounts.
    • Added validation to ensure the audience field is provided when use_id_token is true.
  • tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_service_account_exchanger.py
    • Added test_exchange_id_token_with_default_credential to verify ID token fetching with application default credentials.
    • Added test_exchange_id_token_with_explicit_service_account to verify ID token fetching with explicit service account JSON keys.
    • Added test_exchange_id_token_missing_audience to ensure an error is raised when audience is omitted with use_id_token=True.
    • Added test_exchange_id_token_failure to test error handling during ID token fetching.
Activity
  • Focused exchanger unit tests passed: 11 tests in 1.59s.
  • Broader authentication and MCP unit tests passed: 126 tests with 336 warnings in 2.64s.
  • Manual end-to-end evidence is requested, including screenshots of successful adk web prompts, console logs showing successful authenticated calls (no 401 errors), and the exact agent configuration used.
  • Review is requested from ADK auth/tooling maintainers, with specific focus on backward compatibility, correctness of ID-token exchange for ADC and explicit keys, and error messaging for missing audience.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for OIDC ID tokens for service account authentication, which is crucial for services like Cloud Run. The changes are well-structured, introducing new fields to the ServiceAccount model and a new path in the ServiceAccountCredentialExchanger to handle ID token fetching. The existing access token flow remains untouched, ensuring backward compatibility. The new functionality is also well-covered by unit tests. I have one suggestion to improve code clarity and maintainability.

f"Failed to exchange service account token: {e}"
) from e

def _fetch_id_token(self, sa_config) -> AuthCredential:
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better code clarity and to leverage static type checking, please add a type hint for the sa_config parameter. It should be of type ServiceAccount.

You will also need to import ServiceAccount from .....auth.auth_credential.

Suggested change
def _fetch_id_token(self, sa_config) -> AuthCredential:
def _fetch_id_token(self, sa_config: ServiceAccount) -> AuthCredential:

@ryanaiagent ryanaiagent self-assigned this Feb 13, 2026
@ryanaiagent ryanaiagent added the request clarification [Status] The maintainer need clarification or more information from the author label Feb 13, 2026
@ryanaiagent
Copy link
Collaborator

Hi @P-nishant , Thank you for your contribution! It appears you haven't yet signed the Contributor License Agreement (CLA). Please visit https://cla.developers.google.com/ to complete the signing process. Once the CLA is signed, we'll be able to proceed with the review of your PR. Thank you!

@P-nishant
Copy link
Author

Manual E2E Evidence (local, Cloud Run-like ID-token behavior)

Because I don’t have a Cloud Run MCP service in this repo, I validated the fix end-to-end locally using a streamable-http MCP server that enforces the Cloud Run-relevant contract:

  • request must carry a JWT-shaped Bearer token
  • token payload must contain an aud matching the service URL

This does not verify signatures (local test only), but it validates the behavior the fix targets: using an ID-token-like JWT (with correct audience) succeeds; an opaque token fails.

Setup

  • MCP URL: http://localhost:<PORT>/mcp (PORT was )
  • ADK Web: adk web e2e_local
  • Agent: sa_idtoken_agent

Command used

Terminal A

python e2e_local\jwt_aud_mcp_server.py

Terminal B

$env:E2E_MCP_URL="http://localhost:/mcp"
$env:E2E_TOKEN = python e2e_local\make_fake_jwt.py $env:E2E_MCP_URL
adk web e2e_local

logs
(.venv) PS D:\Projects\adk-python> python .\e2e_local\try_call_tool.py
{'content': [{'type': 'text', 'text': '{\n "message": "hello world"\n}'}], 'isError': False}

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

Labels

mcp [Component] Issues about MCP support request clarification [Status] The maintainer need clarification or more information from the author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants