Skip to content

Commit bfed19c

Browse files
mikeedjoneswukath
authored andcommitted
feat: expose mcps streamable http custom httpx factory parameter
Merge #3634 Reopen of #2997 after the accepted changes in that PR were reverted by e15e19d --- This PR addresses #3005 and #2963 to allow control over the ssl cert used when connecting to an mcp server by exposing the `httpx_client_factory` parameter exposed when creating a `MCPSessionManager` in adk. Overlaps with #2966 but I don't believe that that PR's implementation will work. `streamablehttp_client` needs a client factory, not a client. ## testing plan Adds test checking that `streamablehttp_client` uses the custom httpx factory. Could also test that a factory which obeys the `McpHttpClientFactory` protocol produces valid behavior when the session is opened? ## related issues #2227 and #2881 both request _slightly different_ options to control the ssl certs used internally by adk. I think exposing a httpx factory is a good pattern which could be followed for those issues too. Co-authored-by: Kathy Wu <wukathy@google.com> COPYBARA_INTEGRATE_REVIEW=#3634 from mikeedjones:feat/expose-mcps-streamable-http-custom-httpx-factory-parameter 1017a98 PiperOrigin-RevId: 852443631
1 parent e3db2d0 commit bfed19c

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

src/google/adk/tools/mcp_tool/mcp_session_manager.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
from typing import Any
2626
from typing import Dict
2727
from typing import Optional
28+
from typing import Protocol
29+
from typing import runtime_checkable
2830
from typing import TextIO
2931
from typing import Union
3032

@@ -33,8 +35,11 @@
3335
from mcp import StdioServerParameters
3436
from mcp.client.sse import sse_client
3537
from mcp.client.stdio import stdio_client
38+
from mcp.client.streamable_http import create_mcp_http_client
39+
from mcp.client.streamable_http import McpHttpClientFactory
3640
from mcp.client.streamable_http import streamablehttp_client
3741
from pydantic import BaseModel
42+
from pydantic import ConfigDict
3843

3944
logger = logging.getLogger('google_adk.' + __name__)
4045

@@ -73,6 +78,11 @@ class SseConnectionParams(BaseModel):
7378
sse_read_timeout: float = 60 * 5.0
7479

7580

81+
@runtime_checkable
82+
class CheckableMcpHttpClientFactory(McpHttpClientFactory, Protocol):
83+
pass
84+
85+
7686
class StreamableHTTPConnectionParams(BaseModel):
7787
"""Parameters for the MCP Streamable HTTP connection.
7888
@@ -88,13 +98,18 @@ class StreamableHTTPConnectionParams(BaseModel):
8898
Streamable HTTP server.
8999
terminate_on_close: Whether to terminate the MCP Streamable HTTP server
90100
when the connection is closed.
101+
httpx_client_factory: Factory function to create a custom HTTPX client. If
102+
not provided, a default factory will be used.
91103
"""
92104

105+
model_config = ConfigDict(arbitrary_types_allowed=True)
106+
93107
url: str
94108
headers: dict[str, Any] | None = None
95109
timeout: float = 5.0
96110
sse_read_timeout: float = 60 * 5.0
97111
terminate_on_close: bool = True
112+
httpx_client_factory: CheckableMcpHttpClientFactory = create_mcp_http_client
98113

99114

100115
def retry_on_errors(func):
@@ -275,6 +290,7 @@ def _create_client(self, merged_headers: Optional[Dict[str, str]] = None):
275290
seconds=self._connection_params.sse_read_timeout
276291
),
277292
terminate_on_close=self._connection_params.terminate_on_close,
293+
httpx_client_factory=self._connection_params.httpx_client_factory,
278294
)
279295
else:
280296
raise ValueError(

tests/unittests/tools/mcp_tool/test_mcp_session_manager.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,54 @@ def test_init_with_streamable_http_params(self):
114114

115115
assert manager._connection_params == http_params
116116

117+
@patch("google.adk.tools.mcp_tool.mcp_session_manager.streamablehttp_client")
118+
def test_init_with_streamable_http_custom_httpx_factory(
119+
self, mock_streamablehttp_client
120+
):
121+
"""Test that streamablehttp_client is called with custom httpx_client_factory."""
122+
custom_httpx_factory = Mock()
123+
124+
http_params = StreamableHTTPConnectionParams(
125+
url="https://example.com/mcp",
126+
timeout=15.0,
127+
httpx_client_factory=custom_httpx_factory,
128+
)
129+
manager = MCPSessionManager(http_params)
130+
131+
manager._create_client()
132+
133+
mock_streamablehttp_client.assert_called_once_with(
134+
url="https://example.com/mcp",
135+
headers=None,
136+
timeout=timedelta(seconds=15.0),
137+
sse_read_timeout=timedelta(seconds=300.0),
138+
terminate_on_close=True,
139+
httpx_client_factory=custom_httpx_factory,
140+
)
141+
142+
@patch("google.adk.tools.mcp_tool.mcp_session_manager.streamablehttp_client")
143+
def test_init_with_streamable_http_default_httpx_factory(
144+
self, mock_streamablehttp_client
145+
):
146+
"""Test that streamablehttp_client is called with default httpx_client_factory."""
147+
http_params = StreamableHTTPConnectionParams(
148+
url="https://example.com/mcp", timeout=15.0
149+
)
150+
manager = MCPSessionManager(http_params)
151+
152+
manager._create_client()
153+
154+
mock_streamablehttp_client.assert_called_once_with(
155+
url="https://example.com/mcp",
156+
headers=None,
157+
timeout=timedelta(seconds=15.0),
158+
sse_read_timeout=timedelta(seconds=300.0),
159+
terminate_on_close=True,
160+
httpx_client_factory=StreamableHTTPConnectionParams.model_fields[
161+
"httpx_client_factory"
162+
].get_default(),
163+
)
164+
117165
def test_generate_session_key_stdio(self):
118166
"""Test session key generation for stdio connections."""
119167
manager = MCPSessionManager(self.mock_stdio_connection_params)

0 commit comments

Comments
 (0)