-
Notifications
You must be signed in to change notification settings - Fork 54
feat: support user-defined fallback protocols array #707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: anubhav-mcp-draft
Are you sure you want to change the base?
Changes from all commits
308d19f
77c181e
e7bbb17
dbc8194
f21d8a9
561478a
58cf118
ba02fe3
67ae062
da892a4
fcf49a7
36c4994
d058643
6b4845a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,12 +53,14 @@ def __init__( | |
| client_name: Optional[str], | ||
| client_version: Optional[str], | ||
| telemetry_enabled: bool, | ||
| supported_protocols: Optional[list[str]] = None, | ||
| ): | ||
| self._url = url | ||
| self._session = session | ||
| self._client_name = client_name | ||
| self._client_version = client_version | ||
| self._telemetry_enabled = telemetry_enabled | ||
| self._supported_protocols = supported_protocols | ||
| self._active_transport = self._create_transport(protocol) | ||
|
|
||
| def _create_transport(self, protocol: Protocol) -> ITransport: | ||
|
|
@@ -71,6 +73,7 @@ def _create_transport(self, protocol: Protocol) -> ITransport: | |
| self._client_name, | ||
| self._client_version, | ||
| telemetry_enabled=self._telemetry_enabled, | ||
| supported_protocols=self._supported_protocols, | ||
| ) | ||
| case Protocol.MCP_v20251125: | ||
| return McpHttpTransportV20251125( | ||
|
|
@@ -80,6 +83,7 @@ def _create_transport(self, protocol: Protocol) -> ITransport: | |
| self._client_name, | ||
| self._client_version, | ||
| telemetry_enabled=self._telemetry_enabled, | ||
| supported_protocols=self._supported_protocols, | ||
| ) | ||
| case Protocol.MCP_v20250618: | ||
| return McpHttpTransportV20250618( | ||
|
|
@@ -89,6 +93,7 @@ def _create_transport(self, protocol: Protocol) -> ITransport: | |
| self._client_name, | ||
| self._client_version, | ||
| telemetry_enabled=self._telemetry_enabled, | ||
| supported_protocols=self._supported_protocols, | ||
| ) | ||
| case Protocol.MCP_v20250326: | ||
| return McpHttpTransportV20250326( | ||
|
|
@@ -98,6 +103,7 @@ def _create_transport(self, protocol: Protocol) -> ITransport: | |
| self._client_name, | ||
| self._client_version, | ||
| telemetry_enabled=self._telemetry_enabled, | ||
| supported_protocols=self._supported_protocols, | ||
| ) | ||
| case Protocol.MCP_v20241105: | ||
| return McpHttpTransportV20241105( | ||
|
|
@@ -107,6 +113,7 @@ def _create_transport(self, protocol: Protocol) -> ITransport: | |
| self._client_name, | ||
| self._client_version, | ||
| telemetry_enabled=self._telemetry_enabled, | ||
| supported_protocols=self._supported_protocols, | ||
| ) | ||
| case _: | ||
| raise ValueError(f"Unsupported MCP protocol version: {protocol}") | ||
|
|
@@ -166,7 +173,7 @@ def __init__( | |
| client_headers: Optional[ | ||
| Mapping[str, Union[Callable[[], str], Callable[[], Awaitable[str]], str]] | ||
| ] = None, | ||
| protocol: Protocol = Protocol.MCP, | ||
| protocol: Union[Protocol, list[Protocol], list[str]] = Protocol.MCP, | ||
| client_name: Optional[str] = None, | ||
| client_version: Optional[str] = None, | ||
| telemetry_enabled: bool = False, | ||
|
|
@@ -188,13 +195,40 @@ def __init__( | |
| telemetry_enabled: Whether to enable OpenTelemetry tracing and metrics. (Default: False) | ||
| """ | ||
|
|
||
| if isinstance(protocol, list): | ||
| if not protocol: | ||
| raise ValueError("protocol list cannot be empty") | ||
| user_protocols = [ | ||
| p.value if isinstance(p, Protocol) else str(p) for p in protocol | ||
| ] | ||
|
|
||
| supported_mcp_versions = Protocol.get_supported_mcp_versions() | ||
| for p in user_protocols: | ||
| if p not in supported_mcp_versions: | ||
| raise ValueError( | ||
| f"Invalid protocol version '{p}'. Must be one of: {supported_mcp_versions}" | ||
| ) | ||
|
|
||
| user_protocols_set = set(user_protocols) | ||
| # Intersect with the globally sorted list to strictly enforce newest-to-oldest ordering | ||
| supported_protocols = [ | ||
| v for v in supported_mcp_versions if v in user_protocols_set | ||
| ] | ||
| if not supported_protocols: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every Seems like unreachable code. Should we remove this? |
||
| raise ValueError("No supported protocols found in the provided list") | ||
| initial_protocol = Protocol(supported_protocols[0]) | ||
| else: | ||
| supported_protocols = None | ||
| initial_protocol = protocol | ||
|
|
||
|
Comment on lines
+198
to
+223
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to document this new behaviour? Eg.
Also that the newest version in the list is negotiated for at first |
||
| self.__transport = _McpTransportProxy( | ||
| url, | ||
| session, | ||
| protocol, | ||
| initial_protocol, | ||
| client_name, | ||
| client_version, | ||
| telemetry_enabled, | ||
| supported_protocols, | ||
| ) | ||
|
|
||
| self.__client_headers = client_headers if client_headers is not None else {} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| TOOLBOX_SERVER_URL_STABLE = "http://localhost:5000" | ||
| TOOLBOX_SERVER_URL_DRAFT = "http://localhost:5001" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| import pytest | ||
| from aiohttp import web | ||
|
|
||
| from tests.constants import TOOLBOX_SERVER_URL_STABLE | ||
| from toolbox_core.client import ToolboxClient | ||
| from toolbox_core.itransport import ITransport | ||
| from toolbox_core.protocol import ( | ||
|
|
@@ -814,7 +815,7 @@ def test_toolbox_client_no_warning_on_mcp(): | |
| with warnings.catch_warnings(record=True) as w: | ||
| warnings.simplefilter("always") | ||
|
|
||
| client = ToolboxClient("http://localhost:5000", protocol=Protocol.MCP) | ||
| client = ToolboxClient(TOOLBOX_SERVER_URL_STABLE, protocol=Protocol.MCP) | ||
| assert len(w) == 0 | ||
|
|
||
|
|
||
|
|
@@ -825,6 +826,33 @@ def test_toolbox_client_no_warning_on_explicit_mcp_version(): | |
| warnings.simplefilter("always") | ||
|
|
||
| client = ToolboxClient( | ||
| "http://localhost:5000", protocol=Protocol.MCP_v20251125 | ||
| TOOLBOX_SERVER_URL_STABLE, protocol=Protocol.MCP_v20251125 | ||
| ) | ||
| assert len(w) == 0 | ||
|
|
||
|
|
||
| def test_toolbox_client_custom_protocols(): | ||
| """Test that custom protocols array is correctly parsed and sorted.""" | ||
| with patch("toolbox_core.client._McpTransportProxy") as mock_proxy: | ||
| client = ToolboxClient( | ||
| TOOLBOX_SERVER_URL_STABLE, | ||
| protocol=[Protocol.MCP_v20241105, Protocol.MCP_DRAFT, "2025-06-18"], | ||
| ) | ||
| mock_proxy.assert_called_once() | ||
| args, kwargs = mock_proxy.call_args | ||
|
|
||
| # Check initial_protocol | ||
| assert args[2] == Protocol.MCP_DRAFT | ||
| # Check supported_protocols (must be sorted from newest to oldest) | ||
| assert args[6] == ["DRAFT-2026-v1", "2025-06-18", "2024-11-05"] | ||
|
|
||
|
|
||
| def test_toolbox_client_custom_protocols_invalid(): | ||
| """Test that custom protocols array raises error on invalid inputs.""" | ||
| import pytest | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: pytest is also exported at the top of the file |
||
|
|
||
| with pytest.raises(ValueError, match="protocol list cannot be empty"): | ||
| ToolboxClient(TOOLBOX_SERVER_URL_STABLE, protocol=[]) | ||
|
|
||
| with pytest.raises(ValueError, match="Invalid protocol version 'invalid-version'"): | ||
| ToolboxClient(TOOLBOX_SERVER_URL_STABLE, protocol=["invalid-version"]) | ||
Uh oh!
There was an error while loading. Please reload this page.