Skip to content

fix: route MCP server log notifications to loguru instead of TUI#1637

Open
he-yufeng wants to merge 1 commit intoMoonshotAI:mainfrom
he-yufeng:fix/mcp-server-log-leak
Open

fix: route MCP server log notifications to loguru instead of TUI#1637
he-yufeng wants to merge 1 commit intoMoonshotAI:mainfrom
he-yufeng:fix/mcp-server-log-leak

Conversation

@he-yufeng
Copy link
Copy Markdown

@he-yufeng he-yufeng commented Mar 30, 2026

Problem

MCP servers (e.g. SearXNG) send log notifications on each request. fastmcp.Client defaults to default_log_handler, which uses RichHandler(stderr=True) — these messages end up dumped into the TUI:

[03/28/26 08:29:23] INFO     Server log: meta=None level='info' logger=None data={'message': 'MCP SearXNG Server v0.10.0 connected via STDIO'}

Root Cause

fastmcp/client/logging.py has a default_log_handler that routes MCP LoggingMessageNotification through Python's standard logging module with a RichHandler targeting stderr. When kimi-cli creates fastmcp.Client(MCPConfig(...)) without specifying log_handler, this default kicks in and the messages bypass loguru entirely.

Fix

Pass a custom log_handler to fastmcp.Client that routes MCP server log notifications through kimi-cli's loguru logger at DEBUG level. The messages still go to kimi.log for debugging, but no longer pollute the TUI.

The handler also properly extracts the message field from the notification data (SearXNG uses data.message, not data.msg), avoiding the raw str(message) fallback that produced the ugly meta=None level='info' ... output.

Fixes #1624


Open with Devin

fastmcp's default log_handler uses RichHandler(stderr=True), which
dumps MCP server log notifications (e.g. SearXNG startup messages)
directly into the TUI. Pass a custom handler that routes through
loguru at DEBUG level so these messages go to the log file only.

Fixes MoonshotAI#1624
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment on lines +279 to +280
data = message.data
msg = data.get("message") or data.get("msg") or str(data)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 AttributeError when MCP server sends non-dict log data

The _mcp_log_handler calls data.get("message") on message.data, but per the MCP spec, data is typed as Any and can be any JSON-serializable value — including a plain string, number, or list. If an MCP server sends a log notification with a string data (e.g. "Server started"), calling .get() on it raises AttributeError: 'str' object has no attribute 'get'. This exception propagates up from mcp.client.session:_received_notification (line 431 of mcp/client/session.py) and can crash the MCP session, potentially disconnecting the server.

Suggested fix

Check if data is a dict before calling .get():

data = message.data
if isinstance(data, dict):
    msg = data.get("message") or data.get("msg") or str(data)
else:
    msg = str(data)
Suggested change
data = message.data
msg = data.get("message") or data.get("msg") or str(data)
data = message.data
msg = data.get("message") or data.get("msg") or str(data) if isinstance(data, dict) else str(data)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f438cca947

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

async def _mcp_log_handler(message: LogMessage) -> None:
"""Route MCP server log notifications to loguru instead of rich stderr."""
data = message.data
msg = data.get("message") or data.get("msg") or str(data)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle non-object MCP log payloads safely

The new _mcp_log_handler assumes message.data is a mapping and calls .get(...), but MCP log notification payloads can be arbitrary JSON values (for example a plain string or null). In those cases this line raises AttributeError, which can propagate out of the notification callback and disrupt MCP client handling for servers that emit non-object log payloads. Add a type guard (e.g., isinstance(data, dict)) before using .get, and fall back to str(data) otherwise.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SearXNG MCP Logs Dumped into TUI on each request

1 participant