"""Tests for MCP config service."""
import json
from unittest.mock import patch
from preloop.services.mcp_config_service import MCPConfigService
class TestGenerateMCPConfig:
"""Test generate_mcp_config method."""
def test_generate_mcp_config_with_preloop_mcp(self):
"""Test generating config with preloop-mcp server."""
config = MCPConfigService.generate_mcp_config(
allowed_mcp_servers=["preloop-mcp"],
allowed_mcp_tools=[
{"server_name": "preloop-mcp", "tool_name": "get_issue"},
{"server_name": "preloop-mcp", "tool_name": "search_issues"},
],
preloop_url="https://app.test.com",
account_api_token="test_token_123",
)
assert "mcpServers" in config
assert "preloop-mcp" in config["mcpServers"]
server_config = config["mcpServers"]["preloop-mcp"]
assert server_config["url"] == "https://app.test.com/mcp/v1"
assert server_config["transport"] == "http-streaming"
assert "headers" in server_config
assert "Authorization" in server_config["headers"]
assert server_config["headers"]["Authorization"] == "Bearer test_token_123"
# Check allowed tools
assert "allowed_tools" in config
assert "preloop-mcp" in config["allowed_tools"]
assert "get_issue" in config["allowed_tools"]["preloop-mcp"]
assert "search_issues" in config["allowed_tools"]["preloop-mcp"]
def test_generate_mcp_config_without_token(self):
"""Test generating config without API token."""
config = MCPConfigService.generate_mcp_config(
allowed_mcp_servers=["preloop-mcp"],
allowed_mcp_tools=[],
preloop_url="https://app.test.com",
)
server_config = config["mcpServers"]["preloop-mcp"]
assert "headers" not in server_config
@patch("preloop.services.mcp_config_service.os.getenv")
def test_generate_mcp_config_default_url(self, mock_getenv):
"""Test generating config with default URL from environment."""
mock_getenv.return_value = "http://localhost:8000"
config = MCPConfigService.generate_mcp_config(
allowed_mcp_servers=["preloop-mcp"],
allowed_mcp_tools=[],
)
mock_getenv.assert_called_with(
"PRELOOP_URL", "http://host.docker.internal:8000"
)
def test_generate_mcp_config_unknown_server_excluded(self):
"""Test that unknown MCP server is excluded from config."""
config = MCPConfigService.generate_mcp_config(
allowed_mcp_servers=["unknown-server"],
allowed_mcp_tools=[],
preloop_url="https://app.test.com",
)
# Unknown server should not be in config
assert "unknown-server" not in config["mcpServers"]
# Config should still be valid
assert "mcpServers" in config
assert "allowed_tools" in config
def test_generate_mcp_config_empty_servers(self):
"""Test generating config with no servers."""
config = MCPConfigService.generate_mcp_config(
allowed_mcp_servers=[],
allowed_mcp_tools=[],
preloop_url="https://app.test.com",
)
assert config["mcpServers"] == {}
assert config["allowed_tools"] == {}
def test_generate_mcp_config_multiple_tools_same_server(self):
"""Test generating config with multiple tools for same server."""
config = MCPConfigService.generate_mcp_config(
allowed_mcp_servers=["preloop-mcp"],
allowed_mcp_tools=[
{"server_name": "preloop-mcp", "tool_name": "tool1"},
{"server_name": "preloop-mcp", "tool_name": "tool2"},
{"server_name": "preloop-mcp", "tool_name": "tool3"},
],
preloop_url="https://app.test.com",
)
tools = config["allowed_tools"]["preloop-mcp"]
assert len(tools) == 3
assert "tool1" in tools
assert "tool2" in tools
assert "tool3" in tools
def test_generate_mcp_config_tool_without_server_name(self):
"""Test that tools without server_name are skipped."""
config = MCPConfigService.generate_mcp_config(
allowed_mcp_servers=["preloop-mcp"],
allowed_mcp_tools=[
{"tool_name": "tool1"}, # Missing server_name
{"server_name": "preloop-mcp", "tool_name": "tool2"},
],
preloop_url="https://app.test.com",
)
# Only tool2 should be included
tools = config["allowed_tools"]["preloop-mcp"]
assert len(tools) == 1
assert "tool2" in tools
def test_generate_mcp_config_tool_without_tool_name(self):
"""Test that tools without tool_name are skipped."""
config = MCPConfigService.generate_mcp_config(
allowed_mcp_servers=["preloop-mcp"],
allowed_mcp_tools=[
{"server_name": "preloop-mcp"}, # Missing tool_name
{"server_name": "preloop-mcp", "tool_name": "tool2"},
],
preloop_url="https://app.test.com",
)
# Only tool2 should be included
tools = config["allowed_tools"]["preloop-mcp"]
assert len(tools) == 1
assert "tool2" in tools
class TestGenerateMCPEnvironmentVars:
"""Test generate_mcp_environment_vars method."""
def test_generate_mcp_environment_vars_with_servers_and_tools(self):
"""Test generating environment variables with servers and tools."""
env = MCPConfigService.generate_mcp_environment_vars(
allowed_mcp_servers=["preloop-mcp", "other-server"],
allowed_mcp_tools=[
{"server_name": "preloop-mcp", "tool_name": "get_issue"},
{"server_name": "preloop-mcp", "tool_name": "search_issues"},
{"server_name": "other-server", "tool_name": "other_tool"},
],
)
# Check MCP_ALLOWED_SERVERS
assert "MCP_ALLOWED_SERVERS" in env
assert "preloop-mcp" in env["MCP_ALLOWED_SERVERS"]
assert "other-server" in env["MCP_ALLOWED_SERVERS"]
# Check MCP_ALLOWED_TOOLS
assert "MCP_ALLOWED_TOOLS" in env
tools_map = json.loads(env["MCP_ALLOWED_TOOLS"])
assert "preloop-mcp" in tools_map
assert "get_issue" in tools_map["preloop-mcp"]
assert "search_issues" in tools_map["preloop-mcp"]
assert "other-server" in tools_map
assert "other_tool" in tools_map["other-server"]
# Check PRELOOP_MCP_URL
assert "PRELOOP_MCP_URL" in env
assert "/mcp/v1" in env["PRELOOP_MCP_URL"]
def test_generate_mcp_environment_vars_empty_servers(self):
"""Test generating environment variables with no servers."""
env = MCPConfigService.generate_mcp_environment_vars(
allowed_mcp_servers=[],
allowed_mcp_tools=[],
)
assert "MCP_ALLOWED_SERVERS" not in env
assert "MCP_ALLOWED_TOOLS" not in env
assert "PRELOOP_MCP_URL" in env # Always included
def test_generate_mcp_environment_vars_tool_without_server(self):
"""Test that tools without server_name are skipped."""
env = MCPConfigService.generate_mcp_environment_vars(
allowed_mcp_servers=["preloop-mcp"],
allowed_mcp_tools=[
{"tool_name": "tool1"}, # Missing server_name
{"server_name": "preloop-mcp", "tool_name": "tool2"},
],
)
tools_map = json.loads(env["MCP_ALLOWED_TOOLS"])
# Only tool2 should be included
assert len(tools_map["preloop-mcp"]) == 1
assert "tool2" in tools_map["preloop-mcp"]
@patch("preloop.services.mcp_config_service.os.getenv")
def test_generate_mcp_environment_vars_custom_url(self, mock_getenv):
"""Test generating environment variables with custom URL."""
mock_getenv.return_value = "https://custom.com"
env = MCPConfigService.generate_mcp_environment_vars(
allowed_mcp_servers=["preloop-mcp"],
allowed_mcp_tools=[],
)
assert env["PRELOOP_MCP_URL"] == "https://custom.com/mcp/v1"
class TestValidateToolAccess:
"""Test validate_tool_access method."""
def test_validate_tool_access_allowed(self):
"""Test validating access to an allowed tool."""
allowed_tools = [
{"server_name": "preloop-mcp", "tool_name": "get_issue"},
{"server_name": "preloop-mcp", "tool_name": "search_issues"},
]
result = MCPConfigService.validate_tool_access(
server_name="preloop-mcp",
tool_name="get_issue",
allowed_mcp_tools=allowed_tools,
)
assert result is True
def test_validate_tool_access_denied(self):
"""Test validating access to a non-allowed tool."""
allowed_tools = [
{"server_name": "preloop-mcp", "tool_name": "get_issue"},
]
result = MCPConfigService.validate_tool_access(
server_name="preloop-mcp",
tool_name="create_issue",
allowed_mcp_tools=allowed_tools,
)
assert result is False
def test_validate_tool_access_wrong_server(self):
"""Test validating access with wrong server name."""
allowed_tools = [
{"server_name": "preloop-mcp", "tool_name": "get_issue"},
]
result = MCPConfigService.validate_tool_access(
server_name="other-server",
tool_name="get_issue",
allowed_mcp_tools=allowed_tools,
)
assert result is False
def test_validate_tool_access_empty_list(self):
"""Test validating access with empty allowed tools list."""
result = MCPConfigService.validate_tool_access(
server_name="preloop-mcp",
tool_name="get_issue",
allowed_mcp_tools=[],
)
assert result is False
def test_validate_tool_access_multiple_servers(self):
"""Test validating access with multiple servers configured."""
allowed_tools = [
{"server_name": "server1", "tool_name": "tool1"},
{"server_name": "server2", "tool_name": "tool2"},
{"server_name": "server1", "tool_name": "tool3"},
]
# Should find tool1 on server1
assert (
MCPConfigService.validate_tool_access("server1", "tool1", allowed_tools)
is True
)
# Should find tool2 on server2
assert (
MCPConfigService.validate_tool_access("server2", "tool2", allowed_tools)
is True
)
# Should not find tool2 on server1
assert (
MCPConfigService.validate_tool_access("server1", "tool2", allowed_tools)
is False
)