Skip to main content
Glama

MCP Proxy Server

"""Tests for the configuration loader module.""" import json import shutil import tempfile from collections.abc import Callable, Generator from pathlib import Path from unittest.mock import patch import pytest from mcp.client.stdio import StdioServerParameters from mcp_proxy.config_loader import load_named_server_configs_from_file @pytest.fixture def create_temp_config_file() -> Generator[Callable[[dict], str], None, None]: """Creates a temporary JSON config file and returns its path.""" temp_files: list[str] = [] def _create_temp_config_file(config_content: dict) -> str: with tempfile.NamedTemporaryFile( mode="w", delete=False, suffix=".json", ) as tmp_config: json.dump(config_content, tmp_config) temp_files.append(tmp_config.name) return tmp_config.name yield _create_temp_config_file for f_path in temp_files: path = Path(f_path) if path.exists(): path.unlink() def test_load_valid_config(create_temp_config_file: Callable[[dict], str]) -> None: """Test loading a valid configuration file.""" config_content = { "mcpServers": { "server1": { "command": "echo", "args": ["hello"], "env": {"FOO": "bar"}, "enabled": True, }, "server2": { "command": "cat", "args": ["file.txt"], }, }, } tmp_config_path = create_temp_config_file(config_content) base_env = {"PASSED": "env_value"} base_env_with_added_env = {"PASSED": "env_value", "FOO": "bar"} loaded_params = load_named_server_configs_from_file(tmp_config_path, base_env) assert "server1" in loaded_params assert loaded_params["server1"].command == "echo" assert loaded_params["server1"].args == ["hello"] assert ( loaded_params["server1"].env == base_env_with_added_env ) # Env is a copy, check if it contains base_env items assert "server2" in loaded_params assert loaded_params["server2"].command == "cat" assert loaded_params["server2"].args == ["file.txt"] assert loaded_params["server2"].env == base_env def test_load_config_with_not_enabled_server( create_temp_config_file: Callable[[dict], str], ) -> None: """Test loading a configuration with disabled servers.""" config_content = { "mcpServers": { "explicitly_enabled_server": {"command": "true_command", "enabled": True}, # No 'enabled' flag, defaults to True "implicitly_enabled_server": {"command": "another_true_command"}, "not_enabled_server": {"command": "false_command", "enabled": False}, }, } tmp_config_path = create_temp_config_file(config_content) loaded_params = load_named_server_configs_from_file(tmp_config_path, {}) assert "explicitly_enabled_server" in loaded_params assert loaded_params["explicitly_enabled_server"].command == "true_command" assert "implicitly_enabled_server" in loaded_params assert loaded_params["implicitly_enabled_server"].command == "another_true_command" assert "not_enabled_server" not in loaded_params def test_file_not_found() -> None: """Test handling of non-existent configuration files.""" with pytest.raises(FileNotFoundError): load_named_server_configs_from_file("non_existent_file.json", {}) def test_json_decode_error() -> None: """Test handling of invalid JSON in configuration files.""" # Create a file with invalid JSON content with tempfile.NamedTemporaryFile( mode="w", delete=False, suffix=".json", ) as tmp_config: tmp_config.write("this is not json {") tmp_config_path = tmp_config.name # Use try/finally to ensure cleanup try: with pytest.raises(json.JSONDecodeError): load_named_server_configs_from_file(tmp_config_path, {}) finally: path = Path(tmp_config_path) if path.exists(): path.unlink() def test_load_example_fetch_config_if_uvx_exists() -> None: """Test loading the example fetch configuration if uvx is available.""" if not shutil.which("uvx"): pytest.skip("uvx command not found in PATH, skipping test for example config.") # Assuming the test is run from the root of the repository example_config_path = Path(__file__).parent.parent / "config_example.json" if not example_config_path.exists(): pytest.fail( f"Example config file not found at expected path: {example_config_path}", ) base_env = {"EXAMPLE_ENV": "true"} loaded_params = load_named_server_configs_from_file(example_config_path, base_env) assert "fetch" in loaded_params fetch_param = loaded_params["fetch"] assert isinstance(fetch_param, StdioServerParameters) assert fetch_param.command == "uvx" assert fetch_param.args == ["mcp-server-fetch"] assert fetch_param.env == base_env # The 'timeout' and 'transportType' fields from the config are currently ignored by the loader, # so no need to assert them on StdioServerParameters. def test_invalid_config_format_missing_mcpservers( create_temp_config_file: Callable[[dict], str], ) -> None: """Test handling of configuration files missing the mcpServers key.""" config_content = {"some_other_key": "value"} tmp_config_path = create_temp_config_file(config_content) with pytest.raises(ValueError, match="Missing 'mcpServers' key"): load_named_server_configs_from_file(tmp_config_path, {}) @patch("mcp_proxy.config_loader.logger") def test_invalid_server_entry_not_dict( mock_logger: object, create_temp_config_file: Callable[[dict], str], ) -> None: """Test handling of server entries that are not dictionaries.""" config_content = {"mcpServers": {"server1": "not_a_dict"}} tmp_config_path = create_temp_config_file(config_content) loaded_params = load_named_server_configs_from_file(tmp_config_path, {}) assert len(loaded_params) == 0 # No servers should be loaded mock_logger.warning.assert_called_with( "Skipping invalid server config for '%s' in %s. Entry is not a dictionary.", "server1", tmp_config_path, ) @patch("mcp_proxy.config_loader.logger") def test_server_entry_missing_command( mock_logger: object, create_temp_config_file: Callable[[dict], str], ) -> None: """Test handling of server entries missing the command field.""" config_content = {"mcpServers": {"server_no_command": {"args": ["arg1"]}}} tmp_config_path = create_temp_config_file(config_content) loaded_params = load_named_server_configs_from_file(tmp_config_path, {}) assert "server_no_command" not in loaded_params mock_logger.warning.assert_called_with( "Named server '%s' from config is missing 'command'. Skipping.", "server_no_command", ) @patch("mcp_proxy.config_loader.logger") def test_server_entry_invalid_args_type( mock_logger: object, create_temp_config_file: Callable[[dict], str], ) -> None: """Test handling of server entries with invalid args type.""" config_content = { "mcpServers": { "server_invalid_args": {"command": "mycmd", "args": "not_a_list"}, }, } tmp_config_path = create_temp_config_file(config_content) loaded_params = load_named_server_configs_from_file(tmp_config_path, {}) assert "server_invalid_args" not in loaded_params mock_logger.warning.assert_called_with( "Named server '%s' from config has invalid 'args' (must be a list). Skipping.", "server_invalid_args", ) def test_empty_mcpservers_dict(create_temp_config_file: Callable[[dict], str]) -> None: """Test handling of configuration files with empty mcpServers dictionary.""" config_content = {"mcpServers": {}} tmp_config_path = create_temp_config_file(config_content) loaded_params = load_named_server_configs_from_file(tmp_config_path, {}) assert len(loaded_params) == 0 def test_config_file_is_empty_json_object(create_temp_config_file: Callable[[dict], str]) -> None: """Test handling of configuration files with empty JSON objects.""" config_content = {} # Empty JSON object tmp_config_path = create_temp_config_file(config_content) with pytest.raises(ValueError, match="Missing 'mcpServers' key"): load_named_server_configs_from_file(tmp_config_path, {}) def test_config_file_is_empty_string() -> None: """Test handling of configuration files with empty content.""" # Create a file with an empty string with tempfile.NamedTemporaryFile( mode="w", delete=False, suffix=".json", ) as tmp_config: tmp_config.write("") # Empty content tmp_config_path = tmp_config.name try: with pytest.raises(json.JSONDecodeError): load_named_server_configs_from_file(tmp_config_path, {}) finally: path = Path(tmp_config_path) if path.exists(): path.unlink()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sparfenyuk/mcp-proxy'

If you have feedback or need assistance with the MCP directory API, please join our Discord server