Skip to main content
Glama
test_config.py8.78 kB
"""Tests for configuration module.""" from pathlib import Path import pytest from scout_mcp.config import Config def test_parse_ssh_config_extracts_hosts(tmp_path: Path) -> None: """Parse SSH config and extract host definitions.""" ssh_config = tmp_path / "config" ssh_config.write_text(""" Host dookie HostName 100.122.19.93 User jmagar IdentityFile ~/.ssh/id_ed25519 Host tootie HostName 100.120.242.29 User root Port 29229 """) config = Config(ssh_config_path=ssh_config) hosts = config.get_hosts() assert len(hosts) == 2 assert hosts["dookie"].hostname == "100.122.19.93" assert hosts["dookie"].user == "jmagar" assert hosts["dookie"].port == 22 # default assert hosts["tootie"].port == 29229 def test_allowlist_filters_hosts(tmp_path: Path) -> None: """Allowlist restricts which hosts are available.""" ssh_config = tmp_path / "config" ssh_config.write_text(""" Host dookie HostName 100.122.19.93 User jmagar Host tootie HostName 100.120.242.29 User root Host production HostName 10.0.0.1 User deploy """) config = Config(ssh_config_path=ssh_config, allowlist=["dookie", "tootie"]) hosts = config.get_hosts() assert "dookie" in hosts assert "tootie" in hosts assert "production" not in hosts def test_blocklist_filters_hosts(tmp_path: Path) -> None: """Blocklist excludes specific hosts.""" ssh_config = tmp_path / "config" ssh_config.write_text(""" Host dookie HostName 100.122.19.93 User jmagar Host production HostName 10.0.0.1 User deploy """) config = Config(ssh_config_path=ssh_config, blocklist=["production"]) hosts = config.get_hosts() assert "dookie" in hosts assert "production" not in hosts def test_get_host_returns_none_for_unknown() -> None: """get_host returns None for unknown host.""" config = Config() assert config.get_host("nonexistent") is None def test_empty_ssh_config(tmp_path: Path) -> None: """Empty SSH config file returns no hosts.""" ssh_config = tmp_path / "config" ssh_config.write_text("") config = Config(ssh_config_path=ssh_config) hosts = config.get_hosts() assert len(hosts) == 0 def test_ssh_config_with_comments_only(tmp_path: Path) -> None: """SSH config with only comments returns no hosts.""" ssh_config = tmp_path / "config" ssh_config.write_text(""" # This is a comment # Another comment # Yet another comment """) config = Config(ssh_config_path=ssh_config) hosts = config.get_hosts() assert len(hosts) == 0 def test_host_without_hostname_is_skipped(tmp_path: Path) -> None: """Host without HostName directive is skipped.""" ssh_config = tmp_path / "config" ssh_config.write_text(""" Host incomplete User jmagar Port 22 Host complete HostName 192.168.1.1 User admin """) config = Config(ssh_config_path=ssh_config) hosts = config.get_hosts() assert len(hosts) == 1 assert "incomplete" not in hosts assert "complete" in hosts assert hosts["complete"].hostname == "192.168.1.1" def test_invalid_port_defaults_to_22(tmp_path: Path) -> None: """Invalid port number defaults to 22.""" ssh_config = tmp_path / "config" ssh_config.write_text(""" Host badport HostName 192.168.1.1 User admin Port invalid Host stringport HostName 192.168.1.2 User admin Port abc123 """) config = Config(ssh_config_path=ssh_config) hosts = config.get_hosts() assert len(hosts) == 2 assert hosts["badport"].port == 22 assert hosts["stringport"].port == 22 def test_unreadable_config_file_treated_as_empty(tmp_path: Path) -> None: """Unreadable config file is treated as empty.""" ssh_config = tmp_path / "config" ssh_config.write_text("Host test\n HostName 192.168.1.1") # Make file unreadable ssh_config.chmod(0o000) try: config = Config(ssh_config_path=ssh_config) hosts = config.get_hosts() # Should return empty dict for unreadable file assert len(hosts) == 0 finally: # Restore permissions for cleanup ssh_config.chmod(0o644) def test_env_vars_override_defaults_with_scout_prefix( tmp_path: Path, monkeypatch ) -> None: """Environment variables with SCOUT_ prefix override default config values.""" monkeypatch.setenv("SCOUT_MAX_FILE_SIZE", "5242880") monkeypatch.setenv("SCOUT_COMMAND_TIMEOUT", "60") monkeypatch.setenv("SCOUT_IDLE_TIMEOUT", "120") config = Config(ssh_config_path=tmp_path / "nonexistent") assert config.max_file_size == 5242880 assert config.command_timeout == 60 assert config.idle_timeout == 120 def test_legacy_mcp_cat_env_vars_still_work(tmp_path: Path, monkeypatch) -> None: """Legacy MCP_CAT_* env vars still work for backward compatibility.""" monkeypatch.setenv("MCP_CAT_MAX_FILE_SIZE", "2097152") monkeypatch.setenv("MCP_CAT_COMMAND_TIMEOUT", "45") monkeypatch.setenv("MCP_CAT_IDLE_TIMEOUT", "90") config = Config(ssh_config_path=tmp_path / "nonexistent") assert config.max_file_size == 2097152 assert config.command_timeout == 45 assert config.idle_timeout == 90 def test_scout_prefix_takes_precedence_over_legacy(tmp_path: Path, monkeypatch) -> None: """SCOUT_* env vars take precedence over legacy MCP_CAT_* vars.""" # Set both legacy and new monkeypatch.setenv("MCP_CAT_MAX_FILE_SIZE", "1000000") monkeypatch.setenv("SCOUT_MAX_FILE_SIZE", "2000000") monkeypatch.setenv("MCP_CAT_COMMAND_TIMEOUT", "30") monkeypatch.setenv("SCOUT_COMMAND_TIMEOUT", "60") monkeypatch.setenv("MCP_CAT_IDLE_TIMEOUT", "60") monkeypatch.setenv("SCOUT_IDLE_TIMEOUT", "120") config = Config(ssh_config_path=tmp_path / "nonexistent") assert config.max_file_size == 2000000 # SCOUT_ wins assert config.command_timeout == 60 # SCOUT_ wins assert config.idle_timeout == 120 # SCOUT_ wins def test_invalid_env_var_uses_default(tmp_path: Path, monkeypatch) -> None: """Invalid environment variable values fall back to defaults.""" monkeypatch.setenv("MCP_CAT_MAX_FILE_SIZE", "not_a_number") config = Config(ssh_config_path=tmp_path / "nonexistent") assert config.max_file_size == 1_048_576 # default class TestTransportConfig: """Tests for transport configuration.""" def test_default_transport_is_http(self, tmp_path: Path) -> None: """Default transport should be http.""" config = Config(ssh_config_path=tmp_path / "ssh_config") assert config.transport == "http" def test_default_host_is_all_interfaces(self, tmp_path: Path) -> None: """Default host should be 0.0.0.0 (all interfaces).""" config = Config(ssh_config_path=tmp_path / "ssh_config") assert config.http_host == "0.0.0.0" def test_default_port_is_8000(self, tmp_path: Path) -> None: """Default port should be 8000.""" config = Config(ssh_config_path=tmp_path / "ssh_config") assert config.http_port == 8000 def test_transport_from_env( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Transport can be set via SCOUT_TRANSPORT env var.""" monkeypatch.setenv("SCOUT_TRANSPORT", "stdio") config = Config(ssh_config_path=tmp_path / "ssh_config") assert config.transport == "stdio" def test_host_from_env( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Host can be set via SCOUT_HTTP_HOST env var.""" monkeypatch.setenv("SCOUT_HTTP_HOST", "127.0.0.1") config = Config(ssh_config_path=tmp_path / "ssh_config") assert config.http_host == "127.0.0.1" def test_port_from_env( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Port can be set via SCOUT_HTTP_PORT env var.""" monkeypatch.setenv("SCOUT_HTTP_PORT", "9000") config = Config(ssh_config_path=tmp_path / "ssh_config") assert config.http_port == 9000 def test_invalid_port_uses_default( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Invalid port value falls back to default.""" monkeypatch.setenv("SCOUT_HTTP_PORT", "not-a-number") config = Config(ssh_config_path=tmp_path / "ssh_config") assert config.http_port == 8000 def test_invalid_transport_uses_default( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Invalid transport value falls back to http.""" monkeypatch.setenv("SCOUT_TRANSPORT", "invalid") config = Config(ssh_config_path=tmp_path / "ssh_config") assert config.transport == "http"

Latest Blog Posts

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/jmagar/scout_mcp'

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