Skip to content
Open
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
12 changes: 11 additions & 1 deletion src/kimi_cli/soul/toolset.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,17 @@ async def load_mcp_tools(
connected.
"""
import fastmcp
from fastmcp.client.logging import LogMessage
from fastmcp.mcp_config import MCPConfig, RemoteMCPServer

from kimi_cli.ui.shell.prompt import toast

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)
Comment on lines +279 to +280
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

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 👍 / 👎.

logger.debug("MCP server log: {msg}", msg=msg)

async def _check_oauth_tokens(server_url: str) -> bool:
"""Check if OAuth tokens exist for the server."""
try:
Expand Down Expand Up @@ -372,7 +379,10 @@ async def _connect():
if isinstance(server_config, RemoteMCPServer) and server_config.auth == "oauth":
oauth_servers[server_name] = server_config.url

client = fastmcp.Client(MCPConfig(mcpServers={server_name: server_config}))
client = fastmcp.Client(
MCPConfig(mcpServers={server_name: server_config}),
log_handler=_mcp_log_handler,
)
self._mcp_servers[server_name] = MCPServerInfo(
status="pending", client=client, tools=[]
)
Expand Down
Loading