Skip to content

Commit 8485c1b

Browse files
fix: compatible with goose builtin config (#185)
* fix: compatible with goose builtin config * Update src/mcpm/clients/managers/goose.py Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com> --------- Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com>
1 parent b8fa1b5 commit 8485c1b

File tree

7 files changed

+113
-4
lines changed

7 files changed

+113
-4
lines changed

src/mcpm/clients/managers/goose.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pydantic import TypeAdapter
1010

1111
from mcpm.clients.base import YAMLClientManager
12-
from mcpm.core.schema import ServerConfig, STDIOServerConfig
12+
from mcpm.core.schema import CustomServerConfig, ServerConfig, STDIOServerConfig
1313

1414
logger = logging.getLogger(__name__)
1515

@@ -144,6 +144,8 @@ def _normalize_server_config(self, server_config: Dict[str, Any]) -> Dict[str, A
144144
normalized = dict(server_config)
145145

146146
# Map Goose-specific fields to standard fields
147+
if normalized.get("type") == "builtin":
148+
return {"config": normalized}
147149
if "cmd" in normalized:
148150
normalized["command"] = normalized.pop("cmd")
149151
if "envs" in normalized:
@@ -172,6 +174,9 @@ def to_client_format(self, server_config: ServerConfig) -> Dict[str, Any]:
172174
non_empty_env = server_config.get_filtered_env_vars(os.environ)
173175
if non_empty_env:
174176
result["envs"] = non_empty_env
177+
elif isinstance(server_config, CustomServerConfig):
178+
result = dict(server_config.config)
179+
result["type"] = "builtin"
175180
else:
176181
result = server_config.to_dict()
177182
result["type"] = "sse"

src/mcpm/commands/profile.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from rich.table import Table
44

55
from mcpm.clients.client_registry import ClientRegistry
6-
from mcpm.core.schema import STDIOServerConfig
6+
from mcpm.core.schema import CustomServerConfig, STDIOServerConfig
77
from mcpm.profile.profile_config import ProfileConfigManager
88
from mcpm.utils.config import ConfigManager
99

@@ -41,6 +41,8 @@ def list(verbose=False):
4141
for config in configs:
4242
if isinstance(config, STDIOServerConfig):
4343
details.append(f"{config.name}: {config.command} {' '.join(config.args)}")
44+
elif isinstance(config, CustomServerConfig):
45+
details.append(f"{config.name}: Custom")
4446
else:
4547
details.append(f"{config.name}: {config.url}")
4648
row.append("\n".join(details))

src/mcpm/core/schema.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ def to_mcp_proxy_stdio(self) -> STDIOServerConfig:
7575
)
7676

7777

78-
ServerConfig = Union[STDIOServerConfig, RemoteServerConfig]
78+
class CustomServerConfig(BaseServerConfig):
79+
config: Dict[str, Any]
80+
81+
82+
ServerConfig = Union[STDIOServerConfig, RemoteServerConfig, CustomServerConfig]
7983

8084

8185
class Profile(BaseModel):

src/mcpm/router/client_connection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def _transport_context_factory(self, server_config: ServerConfig):
4949
return _stdio_transport_context(server_config, self._errlog)
5050
elif isinstance(server_config, RemoteServerConfig):
5151
return _streamable_http_transport_context(server_config)
52+
else:
53+
raise ValueError(f"Custom server config {server_config.name} is not supported for router proxy")
5254

5355
def healthy(self) -> bool:
5456
return self.session is not None and self._initialized

src/mcpm/utils/display.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
Utility functions for displaying MCP server configurations
33
"""
44

5+
import json
6+
57
from rich.console import Console
68
from rich.markup import escape
79
from rich.table import Table
810

9-
from mcpm.core.schema import RemoteServerConfig, ServerConfig
11+
from mcpm.core.schema import CustomServerConfig, RemoteServerConfig, ServerConfig
1012
from mcpm.utils.scope import CLIENT_PREFIX, PROFILE_PREFIX
1113

1214
console = Console()
@@ -34,6 +36,13 @@ def print_server_config(server_config: ServerConfig, is_stashed=False):
3436
console.print(f' [bold blue]{key}[/] = [green]"{value}"[/]')
3537
console.print(" " + "-" * 50)
3638
return
39+
if isinstance(server_config, CustomServerConfig):
40+
console.print(" Type: [green]Custom[/]")
41+
console.print(" " + "-" * 50)
42+
console.print(" Config:")
43+
console.print(json.dumps(server_config.config, indent=2))
44+
console.print(" " + "-" * 50)
45+
return
3746
command = server_config.command
3847
console.print(f" Command: [green]{command}[/]")
3948

tests/test_clients/test_goose.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import os
2+
import tempfile
3+
from unittest.mock import patch
4+
5+
import pytest
6+
from ruamel.yaml import YAML
7+
8+
from mcpm.clients.managers.goose import GooseClientManager
9+
10+
11+
@pytest.fixture
12+
def temp_yml_config():
13+
with tempfile.NamedTemporaryFile(delete=False, suffix=".yaml") as f:
14+
# Built in extention
15+
config = {
16+
"computercontroller": {
17+
"bundled": True,
18+
"display_name": "Computer Controller",
19+
"enabled": False,
20+
"name": "computercontroller",
21+
"timeout": 300,
22+
"type": "builtin",
23+
}
24+
}
25+
YAML().dump({"extensions": config}, f)
26+
temp_path = f.name
27+
28+
yield temp_path
29+
# Clean up
30+
os.unlink(temp_path)
31+
32+
33+
@pytest.fixture
34+
def goose_manager(temp_yml_config):
35+
return GooseClientManager(config_path=temp_yml_config)
36+
37+
38+
def test_list_servers(goose_manager):
39+
servers = goose_manager.list_servers()
40+
assert "computercontroller" in servers
41+
42+
43+
def test_get_server(goose_manager):
44+
server = goose_manager.get_server("computercontroller")
45+
assert server is not None
46+
assert server.name == "computercontroller"
47+
48+
49+
def test_server_operation(goose_manager):
50+
# builtin extension
51+
success = goose_manager.add_server({"name": "test-server", "type": "builtin", "enabled": True}, "test-server")
52+
assert success
53+
54+
server = goose_manager.get_server("test-server")
55+
assert server is not None
56+
assert server.name == "test-server"
57+
58+
# stdio extension
59+
success = goose_manager.add_server(
60+
{
61+
"name": "stdio-server",
62+
"type": "stdio",
63+
"enabled": True,
64+
"command": "npx",
65+
"args": ["-y", "@modelcontextprotocol/server-test"],
66+
},
67+
"stdio-server",
68+
)
69+
assert success
70+
71+
server = goose_manager.get_server("stdio-server")
72+
assert server is not None
73+
assert server.name == "stdio-server"
74+
75+
# remove server
76+
success = goose_manager.remove_server("test-server")
77+
assert success
78+
79+
assert goose_manager.get_server("test-server") is None
80+
81+
82+
def test_is_client_installed(goose_manager):
83+
with patch("os.path.isdir", return_value=True):
84+
assert goose_manager.is_client_installed()
85+
86+
with patch("os.path.isdir", return_value=False):
87+
assert not goose_manager.is_client_installed()
File renamed without changes.

0 commit comments

Comments
 (0)