Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion src/mcpm/commands/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
from rich.prompt import Confirm

from mcpm.clients.client_registry import ClientRegistry
from mcpm.core.utils.log_manager import get_log_directory
from mcpm.router.share import Tunnel
from mcpm.utils.config import MCPM_AUTH_HEADER, MCPM_PROFILE_HEADER, ConfigManager
from mcpm.utils.platform import get_log_directory, get_pid_directory
from mcpm.utils.platform import get_pid_directory

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
Expand Down
377 changes: 377 additions & 0 deletions src/mcpm/core/router/router.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
import os
import sys
from pathlib import Path
from typing import Optional, TextIO

from .platform import get_log_directory

def get_log_directory(app_name: str = "mcpm") -> Path:
"""
Return the appropriate log directory path based on the current operating system.

Args:
app_name: The name of the application, used in the path

Returns:
Path object representing the log directory
"""
# macOS
if sys.platform == "darwin":
return Path.home() / "Library" / "Logs" / app_name / "logs"

# Windows
elif sys.platform == "win32":
localappdata = os.environ.get("LOCALAPPDATA")
if localappdata:
return Path(localappdata) / app_name / "logs"
return Path.home() / "AppData" / "Local" / app_name / "logs"

# Linux and other Unix-like systems
else:
# Check if XDG_DATA_HOME is defined
xdg_data_home = os.environ.get("XDG_DATA_HOME")
if xdg_data_home:
return Path(xdg_data_home) / app_name / "logs"

# Default to ~/.local/share if XDG_DATA_HOME is not defined
return Path.home() / ".local" / "share" / app_name / "logs"


DEFAULT_ROOT_STDERR_LOG_DIR = get_log_directory("mcpm") / "errlogs"


class ServerErrorLogManager:
class ServerLogManager:
"""
A manager for server error logs.
"""
Expand Down
2 changes: 1 addition & 1 deletion src/mcpm/monitor/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
TextContent,
)

from mcpm.utils.config import PROMPT_SPLITOR, RESOURCE_SPLITOR, TOOL_SPLITOR
from mcpm.core.router.router import PROMPT_SPLITOR, RESOURCE_SPLITOR, TOOL_SPLITOR

from .base import AccessEventType
from .duckdb import DuckDBAccessMonitor
Expand Down
2 changes: 1 addition & 1 deletion src/mcpm/router/app.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import logging
import os

from mcpm.core.utils.log_manager import get_log_directory
from mcpm.monitor.event import monitor
from mcpm.router.router import MCPRouter
from mcpm.router.router_config import RouterConfig
from mcpm.utils.config import ConfigManager
from mcpm.utils.platform import get_log_directory

LOG_DIR = get_log_directory("mcpm")
LOG_DIR.mkdir(parents=True, exist_ok=True)
Expand Down
399 changes: 34 additions & 365 deletions src/mcpm/router/router.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/mcpm/router/sse_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
from starlette.routing import Mount, Route
from starlette.types import Receive, Scope, Send

from mcpm.core.utils.log_manager import get_log_directory
from mcpm.monitor.base import AccessEventType
from mcpm.monitor.event import monitor
from mcpm.router.router import MCPRouter
from mcpm.router.transport import RouterSseTransport
from mcpm.utils.config import ConfigManager
from mcpm.utils.platform import get_log_directory

LOG_DIR = get_log_directory("mcpm")
LOG_DIR.mkdir(parents=True, exist_ok=True)
Expand Down
4 changes: 0 additions & 4 deletions src/mcpm/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 6276 # 6276 represents MCPM on a T9 keypad (6=M, 2=C, 7=P, 6=M)
# default splitor pattern
TOOL_SPLITOR = "_t_"
RESOURCE_SPLITOR = ":"
RESOURCE_TEMPLATE_SPLITOR = ":"
PROMPT_SPLITOR = "_p_"
DEFAULT_SHARE_ADDRESS = f"share.mcpm.sh:{DEFAULT_PORT}"
MCPM_AUTH_HEADER = "X-MCPM-SECRET"
MCPM_PROFILE_HEADER = "X-MCPM-PROFILE"
Expand Down
32 changes: 0 additions & 32 deletions src/mcpm/utils/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,6 @@
from pathlib import Path


def get_log_directory(app_name: str = "mcpm") -> Path:
"""
Return the appropriate log directory path based on the current operating system.

Args:
app_name: The name of the application, used in the path

Returns:
Path object representing the log directory
"""
# macOS
if sys.platform == "darwin":
return Path.home() / "Library" / "Logs" / app_name / "logs"

# Windows
elif sys.platform == "win32":
localappdata = os.environ.get("LOCALAPPDATA")
if localappdata:
return Path(localappdata) / app_name / "logs"
return Path.home() / "AppData" / "Local" / app_name / "logs"

# Linux and other Unix-like systems
else:
# Check if XDG_DATA_HOME is defined
xdg_data_home = os.environ.get("XDG_DATA_HOME")
if xdg_data_home:
return Path(xdg_data_home) / app_name / "logs"

# Default to ~/.local/share if XDG_DATA_HOME is not defined
return Path.home() / ".local" / "share" / app_name / "logs"


def get_pid_directory(app_name: str = "mcpm") -> Path:
"""
Return the appropriate PID directory path based on the current operating system.
Expand Down
12 changes: 6 additions & 6 deletions tests/test_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from mcp import InitializeResult
from mcp.types import ListToolsResult, ServerCapabilities, Tool, ToolsCapability

from mcpm.router.client_connection import ServerConnection
from mcpm.core.router.client_connection import ServerConnection
from mcpm.router.router import MCPRouter
from mcpm.router.router_config import RouterConfig
from mcpm.schemas.server_config import RemoteServerConfig
Expand Down Expand Up @@ -82,11 +82,11 @@ def mock_get_active_servers(_profile):

# Patch the _patch_handler_func method to use our mock
with patch.object(router, "_patch_handler_func", wraps=router._patch_handler_func) as mock_patch_handler:
mock_patch_handler.return_value.get_active_servers = mock_get_active_servers
mock_patch_handler.return_value.get_target_servers = mock_get_active_servers

server_config = RemoteServerConfig(name="test-server", url="http://localhost:8080/sse")

with patch("mcpm.router.router.ServerConnection", return_value=mock_server_connection):
with patch("mcpm.core.router.router.ServerConnection", return_value=mock_server_connection):
await router.add_server("test-server", server_config)

# Verify server was added
Expand Down Expand Up @@ -114,7 +114,7 @@ async def test_add_server_unhealthy():
mock_conn = MagicMock(spec=ServerConnection)
mock_conn.healthy.return_value = False

with patch("mcpm.router.router.ServerConnection", return_value=mock_conn):
with patch("mcpm.core.router.router.ServerConnection", return_value=mock_conn):
with pytest.raises(ValueError, match="Failed to connect to server unhealthy-server"):
await router.add_server("unhealthy-server", server_config)

Expand Down Expand Up @@ -166,7 +166,7 @@ def mock_get_active_servers(_profile):

# Patch the _patch_handler_func method to use our mock
with patch.object(router, "_patch_handler_func", wraps=router._patch_handler_func) as mock_patch_handler:
mock_patch_handler.return_value.get_active_servers = mock_get_active_servers
mock_patch_handler.return_value.get_target_servers = mock_get_active_servers

# Setup initial servers with awaitable request_for_shutdown
mock_old_server = MagicMock(spec=ServerConnection)
Expand All @@ -180,7 +180,7 @@ def mock_get_active_servers(_profile):
# Configure new servers
server_configs = [RemoteServerConfig(name="test-server", url="http://localhost:8080/sse")]

with patch("mcpm.router.router.ServerConnection", return_value=mock_server_connection):
with patch("mcpm.core.router.router.ServerConnection", return_value=mock_server_connection):
await router.update_servers(server_configs)

# Verify old server was removed
Expand Down
Loading