Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/mcpm/clients/managers/goose.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pydantic import TypeAdapter

from mcpm.clients.base import YAMLClientManager
from mcpm.core.schema import ServerConfig, STDIOServerConfig
from mcpm.core.schema import CustomServerConfig, ServerConfig, STDIOServerConfig

logger = logging.getLogger(__name__)

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

# Map Goose-specific fields to standard fields
if normalized.get("type") == "builtin":
return {"config": normalized}
if "cmd" in normalized:
normalized["command"] = normalized.pop("cmd")
if "envs" in normalized:
Expand Down Expand Up @@ -172,6 +174,9 @@ def to_client_format(self, server_config: ServerConfig) -> Dict[str, Any]:
non_empty_env = server_config.get_filtered_env_vars(os.environ)
if non_empty_env:
result["envs"] = non_empty_env
elif isinstance(server_config, CustomServerConfig):
result = server_config.config
result["type"] = "builtin"
else:
result = server_config.to_dict()
result["type"] = "sse"
Expand Down
4 changes: 3 additions & 1 deletion src/mcpm/commands/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rich.table import Table

from mcpm.clients.client_registry import ClientRegistry
from mcpm.core.schema import STDIOServerConfig
from mcpm.core.schema import CustomServerConfig, STDIOServerConfig
from mcpm.profile.profile_config import ProfileConfigManager
from mcpm.utils.config import ConfigManager

Expand Down Expand Up @@ -41,6 +41,8 @@ def list(verbose=False):
for config in configs:
if isinstance(config, STDIOServerConfig):
details.append(f"{config.name}: {config.command} {' '.join(config.args)}")
elif isinstance(config, CustomServerConfig):
details.append(f"{config.name}: Custom")
else:
details.append(f"{config.name}: {config.url}")
row.append("\n".join(details))
Expand Down
6 changes: 5 additions & 1 deletion src/mcpm/core/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ def to_mcp_proxy_stdio(self) -> STDIOServerConfig:
)


ServerConfig = Union[STDIOServerConfig, RemoteServerConfig]
class CustomServerConfig(BaseServerConfig):
config: Dict[str, Any]


ServerConfig = Union[STDIOServerConfig, RemoteServerConfig, CustomServerConfig]


class Profile(BaseModel):
Expand Down
2 changes: 2 additions & 0 deletions src/mcpm/router/client_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def _transport_context_factory(self, server_config: ServerConfig):
return _stdio_transport_context(server_config, self._errlog)
elif isinstance(server_config, RemoteServerConfig):
return _streamable_http_transport_context(server_config)
else:
raise ValueError(f"Custom server config {server_config.name} is not supported for router proxy")

def healthy(self) -> bool:
return self.session is not None and self._initialized
Expand Down
11 changes: 10 additions & 1 deletion src/mcpm/utils/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
Utility functions for displaying MCP server configurations
"""

import json

from rich.console import Console
from rich.markup import escape
from rich.table import Table

from mcpm.core.schema import RemoteServerConfig, ServerConfig
from mcpm.core.schema import CustomServerConfig, RemoteServerConfig, ServerConfig
from mcpm.utils.scope import CLIENT_PREFIX, PROFILE_PREFIX

console = Console()
Expand Down Expand Up @@ -34,6 +36,13 @@ def print_server_config(server_config: ServerConfig, is_stashed=False):
console.print(f' [bold blue]{key}[/] = [green]"{value}"[/]')
console.print(" " + "-" * 50)
return
if isinstance(server_config, CustomServerConfig):
console.print(" Type: [green]Custom[/]")
console.print(" " + "-" * 50)
console.print(" Config:")
console.print(json.dumps(server_config.config, indent=2))
console.print(" " + "-" * 50)
return
command = server_config.command
console.print(f" Command: [green]{command}[/]")

Expand Down
87 changes: 87 additions & 0 deletions tests/test_clients/test_goose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import os
import tempfile
from unittest.mock import patch

import pytest
from ruamel.yaml import YAML

from mcpm.clients.managers.goose import GooseClientManager


@pytest.fixture
def temp_yml_config():
with tempfile.NamedTemporaryFile(delete=False, suffix=".yaml") as f:
# Built in extention
config = {
"computercontroller": {
"bundled": True,
"display_name": "Computer Controller",
"enabled": False,
"name": "computercontroller",
"timeout": 300,
"type": "builtin",
}
}
YAML().dump({"extensions": config}, f)
temp_path = f.name

yield temp_path
# Clean up
os.unlink(temp_path)


@pytest.fixture
def goose_manager(temp_yml_config):
return GooseClientManager(config_path=temp_yml_config)


def test_list_servers(goose_manager):
servers = goose_manager.list_servers()
assert "computercontroller" in servers


def test_get_server(goose_manager):
server = goose_manager.get_server("computercontroller")
assert server is not None
assert server.name == "computercontroller"


def test_server_operation(goose_manager):
# builtin extension
success = goose_manager.add_server({"name": "test-server", "type": "builtin", "enabled": True}, "test-server")
assert success

server = goose_manager.get_server("test-server")
assert server is not None
assert server.name == "test-server"

# stdio extension
success = goose_manager.add_server(
{
"name": "stdio-server",
"type": "stdio",
"enabled": True,
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-test"],
},
"stdio-server",
)
assert success

server = goose_manager.get_server("stdio-server")
assert server is not None
assert server.name == "stdio-server"

# remove server
success = goose_manager.remove_server("test-server")
assert success

assert goose_manager.get_server("test-server") is None


def test_is_client_installed(goose_manager):
with patch("os.path.isdir", return_value=True):
assert goose_manager.is_client_installed()

with patch("os.path.isdir", return_value=False):
assert not goose_manager.is_client_installed()
File renamed without changes.
Loading