From da3a2f3783246969a4a803b966bb2a22cd035b4a Mon Sep 17 00:00:00 2001 From: Jonathan Wang Date: Mon, 21 Apr 2025 17:56:26 +0800 Subject: [PATCH 1/2] fix: sse url to claude desktop is not converted --- src/mcpm/clients/managers/claude_desktop.py | 24 +++++++++++++++------ src/mcpm/router/transport.py | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/mcpm/clients/managers/claude_desktop.py b/src/mcpm/clients/managers/claude_desktop.py index 77a2dd0d..79d695c7 100644 --- a/src/mcpm/clients/managers/claude_desktop.py +++ b/src/mcpm/clients/managers/claude_desktop.py @@ -4,10 +4,10 @@ import logging import os -from typing import Any, Dict +from typing import Any, Dict, override from mcpm.clients.base import JSONClientManager -from mcpm.schemas.server_config import ServerConfig +from mcpm.schemas.server_config import ServerConfig, SSEServerConfig, STDIOServerConfig from mcpm.utils.router_server import format_server_url_with_proxy_headers logger = logging.getLogger(__name__) @@ -116,8 +116,18 @@ def is_server_disabled(self, server_name: str) -> bool: def _format_router_server(self, profile_name, base_url) -> ServerConfig: return format_server_url_with_proxy_headers(self.client_key, profile_name, base_url) - # Uses base class implementation of remove_server - - # Uses base class implementation of get_server - - # Uses base class implementation of list_servers + @override + def to_client_format(self, server_config: ServerConfig) -> Dict[str, Any]: + if isinstance(server_config, SSEServerConfig): + # use mcp proxy to convert to stdio as sse is not supported for claude desktop yet + return self.to_client_format( + STDIOServerConfig( + name=server_config.name, + command="uvx", + args=[ + "mcp-proxy", + server_config.url, + ], + ) + ) + return super().to_client_format(server_config) diff --git a/src/mcpm/router/transport.py b/src/mcpm/router/transport.py index d771e2f0..0186657b 100644 --- a/src/mcpm/router/transport.py +++ b/src/mcpm/router/transport.py @@ -32,7 +32,7 @@ def patch_meta_data(body: bytes, **kwargs) -> bytes: data = json.loads(body.decode("utf-8")) for key, value in kwargs.items(): - data["params"]["_meta"][key] = value + data["params"].setdefault("_meta", {})[key] = value return json.dumps(data).encode("utf-8") From 4dc6cd56c261df8a6bcd2eb573a111e9661221f7 Mon Sep 17 00:00:00 2001 From: Jonathan Wang Date: Mon, 21 Apr 2025 18:11:52 +0800 Subject: [PATCH 2/2] test: add test case --- src/mcpm/clients/managers/claude_desktop.py | 16 +++------------- src/mcpm/schemas/server_config.py | 17 +++++++++++++++++ tests/conftest.py | 7 +++++++ tests/test_add.py | 20 ++++++++++++++++++++ 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/mcpm/clients/managers/claude_desktop.py b/src/mcpm/clients/managers/claude_desktop.py index 79d695c7..3cf22859 100644 --- a/src/mcpm/clients/managers/claude_desktop.py +++ b/src/mcpm/clients/managers/claude_desktop.py @@ -4,10 +4,10 @@ import logging import os -from typing import Any, Dict, override +from typing import Any, Dict from mcpm.clients.base import JSONClientManager -from mcpm.schemas.server_config import ServerConfig, SSEServerConfig, STDIOServerConfig +from mcpm.schemas.server_config import ServerConfig, SSEServerConfig from mcpm.utils.router_server import format_server_url_with_proxy_headers logger = logging.getLogger(__name__) @@ -116,18 +116,8 @@ def is_server_disabled(self, server_name: str) -> bool: def _format_router_server(self, profile_name, base_url) -> ServerConfig: return format_server_url_with_proxy_headers(self.client_key, profile_name, base_url) - @override def to_client_format(self, server_config: ServerConfig) -> Dict[str, Any]: if isinstance(server_config, SSEServerConfig): # use mcp proxy to convert to stdio as sse is not supported for claude desktop yet - return self.to_client_format( - STDIOServerConfig( - name=server_config.name, - command="uvx", - args=[ - "mcp-proxy", - server_config.url, - ], - ) - ) + return self.to_client_format(server_config.to_mcp_proxy_stdio()) return super().to_client_format(server_config) diff --git a/src/mcpm/schemas/server_config.py b/src/mcpm/schemas/server_config.py index 61e981f8..e96764a3 100644 --- a/src/mcpm/schemas/server_config.py +++ b/src/mcpm/schemas/server_config.py @@ -57,5 +57,22 @@ class SSEServerConfig(BaseServerConfig): url: str headers: Dict[str, Any] = {} + def to_mcp_proxy_stdio(self) -> STDIOServerConfig: + proxy_args = [ + "mcp-proxy", + self.url, + ] + if self.headers: + proxy_args.append("--headers") + for key, value in self.headers.items(): + proxy_args.append(f"{key}") + proxy_args.append(f"{value}") + + return STDIOServerConfig( + name=self.name, + command="uvx", + args=proxy_args, + ) + ServerConfig = Union[STDIOServerConfig, SSEServerConfig] diff --git a/tests/conftest.py b/tests/conftest.py index 829c01b4..ab0ee9f3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,7 @@ # Add the src directory to the path for all tests sys.path.insert(0, str(Path(__file__).parent.parent)) +from mcpm.clients.managers.claude_desktop import ClaudeDesktopManager from mcpm.clients.managers.windsurf import WindsurfManager from mcpm.utils.config import ConfigManager @@ -67,3 +68,9 @@ def windsurf_manager(temp_config_file): def empty_windsurf_manager(empty_config_file): """Create a WindsurfManager instance with an empty config""" return WindsurfManager(config_path=empty_config_file) + + +@pytest.fixture +def claude_desktop_manager(temp_config_file): + """Create a ClaudeDesktopManager instance with the temp config""" + return ClaudeDesktopManager(config_path=temp_config_file) diff --git a/tests/test_add.py b/tests/test_add.py index 9c30c170..2c6531e3 100644 --- a/tests/test_add.py +++ b/tests/test_add.py @@ -4,6 +4,7 @@ from mcpm.clients.client_registry import ClientRegistry from mcpm.commands.server_operations.add import add +from mcpm.schemas.server_config import SSEServerConfig from mcpm.utils.repository import RepositoryManager @@ -186,3 +187,22 @@ def test_add_server_with_empty_args(windsurf_manager, monkeypatch): "API_KEY": "test-api-key", "OPTIONAL_ENV": "", # Optional env var should be empty string } + + +def test_add_sse_server_to_claude_desktop(claude_desktop_manager, monkeypatch): + """Test add sse server to claude desktop""" + server_config = SSEServerConfig( + name="test-sse-server", url="http://localhost:8080", headers={"Authorization": "Bearer test-api-key"} + ) + claude_desktop_manager.add_server(server_config) + stored_config = claude_desktop_manager.get_server("test-sse-server") + assert stored_config is not None + assert stored_config.name == "test-sse-server" + assert stored_config.command == "uvx" + assert stored_config.args == [ + "mcp-proxy", + "http://localhost:8080", + "--headers", + "Authorization", + "Bearer test-api-key", + ]