From 67bb1f5b5a9e4103ae97e7504a4d67b37b319239 Mon Sep 17 00:00:00 2001 From: famez Date: Thu, 12 Feb 2026 00:08:04 +0100 Subject: [PATCH] Added extra logs from stderr on commands on the /mcp list command. Allows more visibility for MCP connections. --- pentestagent/interface/tui.py | 5 +++++ pentestagent/mcp/manager.py | 5 +++++ pentestagent/mcp/transport.py | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/pentestagent/interface/tui.py b/pentestagent/interface/tui.py index 5038a83..f79d9f3 100644 --- a/pentestagent/interface/tui.py +++ b/pentestagent/interface/tui.py @@ -552,6 +552,11 @@ class MCPScreen(ModalScreen): if mcp.last_error: text.append(f"\nLast error: {mcp.last_error}\n", style="#e91b1b") + logs = mcp.get_logs() + + if logs: + text.append(f"\n{logs}\n", style="#86e41a") + desc_widget.update(text) toggle_btn.display = True diff --git a/pentestagent/mcp/manager.py b/pentestagent/mcp/manager.py index 7a81f40..4a0496d 100644 --- a/pentestagent/mcp/manager.py +++ b/pentestagent/mcp/manager.py @@ -66,6 +66,11 @@ class MCPServer: def disable(self): self.config.enabled = False + def get_logs(self) -> str: + if not self.transport: + return "" + return self.transport.get_logs() + class MCPManager: """Manages MCP server connections and exposes tools to agents.""" diff --git a/pentestagent/mcp/transport.py b/pentestagent/mcp/transport.py index 52e0883..ac0354b 100644 --- a/pentestagent/mcp/transport.py +++ b/pentestagent/mcp/transport.py @@ -31,6 +31,10 @@ class MCPTransport(ABC): """Check if the transport is connected.""" pass + @abstractmethod + async def get_logs(self) -> str: + pass + class StdioTransport(MCPTransport): """MCP transport over stdio (for npx/uvx commands).""" @@ -49,11 +53,28 @@ class StdioTransport(MCPTransport): self.env = env self.process: Optional[asyncio.subprocess.Process] = None self._lock = asyncio.Lock() + self.logstask = None + self.logs = "" + + def get_logs(self) -> str: + return self.logs @property def is_connected(self) -> bool: """Check if the process is running.""" return self.process is not None and self.process.returncode is None + + async def _read_stderr_loop(self): + try: + while True: + line = await self.process.stderr.readline() + if not line: + break + self.logs += line.decode().rstrip() + "\n" + except asyncio.CancelledError: + # Optional: do any cleanup here + pass + async def connect(self): """Start the MCP server process.""" @@ -84,6 +105,9 @@ class StdioTransport(MCPTransport): limit=1024 * 1024, # 1MB buffer limit for large MCP responses ) + self.logstask = asyncio.create_task(self._read_stderr_loop()) + + async def send(self, message: dict, timeout: float = 15.0) -> dict: """ Send a JSON-RPC message and wait for response. @@ -129,6 +153,15 @@ class StdioTransport(MCPTransport): if not self.process: return + if self.logstask: + + self.logstask.cancel() + + try: + await self.logstask + except asyncio.CancelledError: + pass # Task was successfully cancelled + proc = self.process self.process = None @@ -180,6 +213,9 @@ class SSETransport(MCPTransport): """Check if the session is active.""" return self._connected and self.session is not None + def get_logs(self) -> str: + return "" + async def connect(self): """Connect to the SSE endpoint.""" try: