Skip to content

Commit 923e8f4

Browse files
committed
fix: redirect the stderr of mcp servers to local
1 parent 99bb8dd commit 923e8f4

File tree

3 files changed

+52
-7
lines changed

3 files changed

+52
-7
lines changed

src/mcpm/router/client_connection.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22
import logging
3-
from typing import Optional, cast
3+
from typing import Optional, TextIO, cast
44

55
from mcp import ClientSession, InitializeResult, StdioServerParameters, stdio_client
66
from mcp.client.sse import sse_client
@@ -10,10 +10,10 @@
1010
logger = logging.getLogger(__name__)
1111

1212

13-
def _stdio_transport_context(server_config: ServerConfig):
13+
def _stdio_transport_context(server_config: ServerConfig, errlog: TextIO):
1414
server_config = cast(STDIOServerConfig, server_config)
1515
server_params = StdioServerParameters(command=server_config.command, args=server_config.args, env=server_config.env)
16-
return stdio_client(server_params)
16+
return stdio_client(server_params, errlog=errlog)
1717

1818

1919
def _sse_transport_context(server_config: ServerConfig):
@@ -22,16 +22,19 @@ def _sse_transport_context(server_config: ServerConfig):
2222

2323

2424
class ServerConnection:
25-
def __init__(self, server_config: ServerConfig) -> None:
25+
def __init__(self, server_config: ServerConfig, errlog: TextIO) -> None:
2626
self.session: Optional[ClientSession] = None
2727
self.session_initialized_response: Optional[InitializeResult] = None
2828
self._initialized = False
2929
self.server_config = server_config
3030
self._initialized_event = asyncio.Event()
3131
self._shutdown_event = asyncio.Event()
32+
self._errlog = errlog
3233

3334
self._transport_context_factory = (
34-
_stdio_transport_context if isinstance(server_config, STDIOServerConfig) else _sse_transport_context
35+
lambda config: _stdio_transport_context(config, errlog=self._errlog)
36+
if isinstance(config, STDIOServerConfig)
37+
else _sse_transport_context(config)
3538
)
3639

3740
self._server_task = asyncio.create_task(self._server_lifespan_cycle())

src/mcpm/router/router.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import typing as t
77
from collections import defaultdict
88
from contextlib import asynccontextmanager
9-
from typing import Literal, Optional
9+
from typing import Literal, Optional, TextIO
1010

1111
import uvicorn
1212
from mcp import server, types
@@ -24,6 +24,7 @@
2424
from mcpm.profile.profile_config import ProfileConfigManager
2525
from mcpm.schemas.server_config import ServerConfig
2626
from mcpm.utils.config import PROMPT_SPLITOR, RESOURCE_SPLITOR, RESOURCE_TEMPLATE_SPLITOR, TOOL_SPLITOR
27+
from mcpm.utils.errlog_manager import ServerErrorLogManager
2728

2829
from .client_connection import ServerConnection
2930
from .transport import RouterSseTransport
@@ -60,6 +61,7 @@ def __init__(self, reload_server: bool = False, profile_path: str | None = None,
6061
if reload_server:
6162
self.watcher = ConfigWatcher(self.profile_manager.profile_path)
6263
self.strict: bool = strict
64+
self.error_log_manager = ServerErrorLogManager()
6365

6466
def get_unique_servers(self) -> list[ServerConfig]:
6567
profiles = self.profile_manager.list_profiles()
@@ -110,11 +112,13 @@ async def add_server(self, server_id: str, server_config: ServerConfig) -> None:
110112
raise ValueError(f"Server with ID {server_id} already exists")
111113

112114
# Create client based on connection type
113-
client = ServerConnection(server_config)
115+
errlog: TextIO = self.error_log_manager.open_errlog_file(server_id)
116+
client = ServerConnection(server_config, errlog=errlog)
114117

115118
# Connect to the server
116119
await client.wait_for_initialization()
117120
if not client.healthy():
121+
self.error_log_manager.close_errlog_file(server_id)
118122
raise ValueError(f"Failed to connect to server {server_id}")
119123

120124
response = client.session_initialized_response
@@ -218,6 +222,7 @@ async def remove_server(self, server_id: str) -> None:
218222
# Remove the server from all collections
219223
del self.server_sessions[server_id]
220224
del self.capabilities_mapping[server_id]
225+
self.error_log_manager.close_errlog_file(server_id)
221226

222227
# Delete registered tools, resources and prompts
223228
for key in list(self.tools_mapping.keys()):
@@ -574,4 +579,7 @@ async def shutdown(self):
574579
if client.healthy():
575580
await client.request_for_shutdown()
576581

582+
# close all errlog files
583+
self.error_log_manager.close_all()
584+
577585
logger.info("all alive client sessions have been shut down")

src/mcpm/utils/errlog_manager.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import os
2+
from pathlib import Path
3+
from typing import Optional, TextIO
4+
5+
from .platform import get_log_directory
6+
7+
DEFAULT_ROOT_STDERR_LOG_DIR = get_log_directory("mcpm") / "errlogs"
8+
9+
class ServerErrorLogManager:
10+
"""
11+
A manager for server error logs.
12+
"""
13+
14+
def __init__(self, root_dir: Optional[Path] = None) -> None:
15+
self.root_log_dir = root_dir if root_dir else DEFAULT_ROOT_STDERR_LOG_DIR
16+
os.makedirs(self.root_log_dir, exist_ok=True)
17+
self._log_files: dict[str, TextIO] = {}
18+
19+
def open_errlog_file(self, server_id: str) -> TextIO:
20+
if server_id not in self._log_files or self._log_files[server_id].closed:
21+
log_file = self.root_log_dir / f"{server_id}.log"
22+
# use line buffering, flush to disk when meeting a newline
23+
self._log_files[server_id] = log_file.open("a", encoding="utf-8", buffering=1)
24+
return self._log_files[server_id]
25+
26+
def close_errlog_file(self, server_id: str) -> None:
27+
if server_id in self._log_files and not self._log_files[server_id].closed:
28+
self._log_files[server_id].flush()
29+
self._log_files[server_id].close()
30+
del self._log_files[server_id]
31+
32+
def close_all(self) -> None:
33+
for server_id in self._log_files:
34+
self.close_errlog_file(server_id)

0 commit comments

Comments
 (0)