Skip to content

Commit 0088b0f

Browse files
wukathcopybara-github
authored andcommitted
fix: Add x-goog-user-project header to http calls in API Registry
This gcloud authentication header is required as a project override when using google ADC credentials. It is required for all OneMCP servers. Originally I manually put it in the big query sample, but since it is used for all OneMCP, it makes sense to just add it to the core API Registry logic. I also refactored the auth header logic a bit to deduplicate. Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 845967178
1 parent fcea86f commit 0088b0f

File tree

3 files changed

+78
-16
lines changed

3 files changed

+78
-16
lines changed

contributing/samples/api_registry_agent/agent.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@
2121
PROJECT_ID = "your-google-cloud-project-id"
2222
MCP_SERVER_NAME = "your-mcp-server-name"
2323

24-
# Header required for BigQuery MCP server
25-
header_provider = lambda context: {
26-
"x-goog-user-project": PROJECT_ID,
27-
}
28-
api_registry = ApiRegistry(PROJECT_ID, header_provider=header_provider)
24+
api_registry = ApiRegistry(PROJECT_ID)
2925
registry_tools = api_registry.get_toolset(
3026
mcp_server_name=MCP_SERVER_NAME,
3127
)

src/google/adk/tools/api_registry.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import sys
1818
from typing import Any
19+
from typing import Callable
1920
from typing import Dict
2021
from typing import List
2122
from typing import Optional
@@ -59,12 +60,8 @@ def __init__(
5960

6061
url = f"{API_REGISTRY_URL}/v1beta/projects/{self.api_registry_project_id}/locations/{self.location}/mcpServers"
6162
try:
62-
request = google.auth.transport.requests.Request()
63-
self._credentials.refresh(request)
64-
headers = {
65-
"Authorization": f"Bearer {self._credentials.token}",
66-
"Content-Type": "application/json",
67-
}
63+
headers = self._get_auth_headers()
64+
headers["Content-Type"] = "application/json"
6865
with httpx.Client() as client:
6966
response = client.get(url, headers=headers)
7067
response.raise_for_status()
@@ -107,11 +104,8 @@ def get_toolset(
107104
raise ValueError(f"MCP server {mcp_server_name} has no URLs.")
108105

109106
mcp_server_url = server["urls"][0]
110-
request = google.auth.transport.requests.Request()
111-
self._credentials.refresh(request)
112-
headers = {
113-
"Authorization": f"Bearer {self._credentials.token}",
114-
}
107+
headers = self._get_auth_headers()
108+
115109
return McpToolset(
116110
connection_params=StreamableHTTPConnectionParams(
117111
url="https://" + mcp_server_url,
@@ -121,3 +115,15 @@ def get_toolset(
121115
tool_name_prefix=tool_name_prefix,
122116
header_provider=self._header_provider,
123117
)
118+
119+
def _get_auth_headers(self) -> Dict[str, str]:
120+
"""Refreshes credentials and returns authorization headers."""
121+
request = google.auth.transport.requests.Request()
122+
self._credentials.refresh(request)
123+
headers = {
124+
"Authorization": f"Bearer {self._credentials.token}",
125+
}
126+
# Add quota project header if available in ADC
127+
if self._credentials.quota_project_id:
128+
headers["x-goog-user-project"] = self._credentials.quota_project_id
129+
return headers

tests/unittests/tools/test_api_registry.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import sys
1616
import unittest
17+
from unittest.mock import create_autospec
1718
from unittest.mock import MagicMock
1819
from unittest.mock import patch
1920

@@ -47,6 +48,7 @@ def setUp(self):
4748
self.mock_credentials = MagicMock()
4849
self.mock_credentials.token = "mock_token"
4950
self.mock_credentials.refresh = MagicMock()
51+
self.mock_credentials.quota_project_id = None
5052
mock_auth_patcher = patch(
5153
"google.auth.default",
5254
return_value=(self.mock_credentials, None),
@@ -80,6 +82,32 @@ def test_init_success(self, MockHttpClient):
8082
},
8183
)
8284

85+
@patch("httpx.Client", autospec=True)
86+
def test_init_with_quota_project_id_success(self, MockHttpClient):
87+
self.mock_credentials.quota_project_id = "quota-project"
88+
mock_response = create_autospec(httpx.Response, instance=True)
89+
mock_response.json.return_value = MOCK_MCP_SERVERS_LIST
90+
mock_client_instance = MockHttpClient.return_value
91+
mock_client_instance.__enter__.return_value = mock_client_instance
92+
mock_client_instance.get.return_value = mock_response
93+
94+
api_registry = ApiRegistry(
95+
api_registry_project_id=self.project_id, location=self.location
96+
)
97+
98+
self.assertEqual(len(api_registry._mcp_servers), 3)
99+
self.assertIn("test-mcp-server-1", api_registry._mcp_servers)
100+
self.assertIn("test-mcp-server-2", api_registry._mcp_servers)
101+
self.assertIn("test-mcp-server-no-url", api_registry._mcp_servers)
102+
mock_client_instance.get.assert_called_once_with(
103+
f"https://cloudapiregistry.googleapis.com/v1beta/projects/{self.project_id}/locations/{self.location}/mcpServers",
104+
headers={
105+
"Authorization": "Bearer mock_token",
106+
"Content-Type": "application/json",
107+
"x-goog-user-project": "quota-project",
108+
},
109+
)
110+
83111
@patch("httpx.Client", autospec=True)
84112
def test_init_http_error(self, MockHttpClient):
85113
mock_client_instance = MockHttpClient.return_value
@@ -138,6 +166,38 @@ async def test_get_toolset_success(self, MockHttpClient, MockMcpToolset):
138166
)
139167
self.assertEqual(toolset, MockMcpToolset.return_value)
140168

169+
@patch("google.adk.tools.api_registry.McpToolset", autospec=True)
170+
@patch("httpx.Client", autospec=True)
171+
async def test_get_toolset_with_quota_project_id_success(
172+
self, MockHttpClient, MockMcpToolset
173+
):
174+
self.mock_credentials.quota_project_id = "quota-project"
175+
mock_response = create_autospec(httpx.Response, instance=True)
176+
mock_response.json.return_value = MOCK_MCP_SERVERS_LIST
177+
mock_client_instance = MockHttpClient.return_value
178+
mock_client_instance.__enter__.return_value = mock_client_instance
179+
mock_client_instance.get.return_value = mock_response
180+
181+
api_registry = ApiRegistry(
182+
api_registry_project_id=self.project_id, location=self.location
183+
)
184+
185+
toolset = api_registry.get_toolset("test-mcp-server-1")
186+
187+
MockMcpToolset.assert_called_once_with(
188+
connection_params=StreamableHTTPConnectionParams(
189+
url="https://mcp.server1.com",
190+
headers={
191+
"Authorization": "Bearer mock_token",
192+
"x-goog-user-project": "quota-project",
193+
},
194+
),
195+
tool_filter=None,
196+
tool_name_prefix=None,
197+
header_provider=None,
198+
)
199+
self.assertEqual(toolset, MockMcpToolset.return_value)
200+
141201
@patch("google.adk.tools.api_registry.McpToolset", autospec=True)
142202
@patch("httpx.Client", autospec=True)
143203
async def test_get_toolset_with_filter_and_prefix(

0 commit comments

Comments
 (0)