BigQuery MCP sample stops working after the access token expires

Open Beginner friendly
#7,217 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
75/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
google-cloud, python
Domain
api, cloud

Research direction

Start in contributing/samples/integrations/bigquery_mcp/agent.py and inspect how credentials and the McpToolset Authorization header are initialized. Run the sample or the minimal reproduction, then verify that a long-running process can call list_dataset_ids successfully after the original token expires without restarting.

Written by the indexing model from the issue text.

Description

🔴 Required Information

Describe the Bug:

The bigquery_mcp sample (contributing/samples/integrations/bigquery_mcp/agent.py) fetches an
Application Default Credentials access token once, when the module is imported, and passes it as a
fixed Authorization header on the McpToolset. Access tokens expire after about an hour, and
nothing refreshes this one, so in a long-running adk web or adk api_server process every
BigQuery MCP call fails after the first hour until the process is restarted.

The session manager's own ADC refresh does not help here: it is only used on the mTLS path, and it
skips requests that already have an Authorization header.

Steps to Reproduce:

  1. gcloud auth application-default login
  2. Load the sample and call the list_dataset_ids tool: it works.
  3. Keep the same process running for more than an hour and call the tool again.

Expected Behavior:

The toolset keeps working as long as the process runs, refreshing the token when it expires.

Observed Behavior:

The first call works. After the token expires, about an hour later in the same process, the
tool call is rejected, while the same call with a refreshed token still works:

09:12:57 before expiry, current sample: OK, list_dataset_ids returned 52 datasets
09:13:00 before expiry, fixed sample  : OK, list_dataset_ids returned 52 datasets
10:18:17 current sample credentials valid: False
10:18:18 after expiry, current sample : FAILED (tool call rejected)
10:18:20 after expiry, fixed sample   : OK, list_dataset_ids returned 52 datasets

Note that tools/list on this server does not require a valid token, so listing tools still
succeeds with an expired one. The failure only shows on a tool call.

Environment Details:

  • ADK Library Version (pip show google-adk): main (d57c84f1)
  • Desktop OS: macOS
  • Python Version (python -V): 3.11

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: N/A (the failure is in the MCP call, before the model)

🟡 Optional Information

Regression:

No. The sample has always fetched the token once at import.

Additional Context:

A header_provider that refreshes the credentials when they are no longer valid fixes it, and
works on both the mTLS and the regular endpoint. I have a small PR ready.

Minimal Reproduction Code:

# Calls list_dataset_ids with the headers the sample's toolset holds, waits for
# the token to expire, then calls it again with the same headers.
import asyncio
import datetime

import httpx2
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

import agent  # contributing/samples/integrations/bigquery_mcp/agent.py

URL = "https://bigquery.googleapis.com/mcp"


async def call_tool(label):
  headers = dict(agent.bigquery_mcp_toolset.connection_params.headers or {})
  client = httpx2.AsyncClient(headers=headers, timeout=60)
  try:
    async with streamable_http_client(URL, http_client=client) as (r, w):
      async with ClientSession(r, w) as session:
        await session.initialize()
        await session.call_tool("list_dataset_ids", {"projectId": "PROJECT_ID"})
    print(label, "OK")
  except Exception as e:
    print(label, "FAILED", type(e).__name__)


async def main():
  await call_tool("before expiry:")
  expiry = agent.credentials.expiry
  while datetime.datetime.utcnow() < expiry + datetime.timedelta(seconds=90):
    await asyncio.sleep(60)
  await call_tool("after expiry: ")


asyncio.run(main())

How often has this issue occurred?:

  • Always (100%)
Dominant language
Python
Stars
21.6k
Forks
4k
Avg merge
13h 49m
Merged PRs (30d)
10

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from google/adk-python

All issues in google/adk-python

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.