Problem
When an MCP server fails to connect (e.g., port conflict), the Web UI session worker crashes entirely instead of continuing without MCP tools. This causes messages to get stuck in "thinking" state indefinitely, and the frontend becomes unresponsive.
Reproduction Steps
- Configure an MCP server that uses a fixed port (e.g.,
chrome-local-bridge on port 10086)
- Open a first kimi-cli TUI window — the MCP server starts successfully
- In the same session, execute
/web to switch to Web UI
- Send a message in Web UI
- The session worker tries to start, attempts to connect the same MCP server, but the port is already in use by the first TUI window
MCPRuntimeError is thrown, the worker process crashes
- Message stays stuck in "thinking" forever
Expected Behavior
MCP connection failure should be a graceful degradation:
- Log a warning about the failed MCP server
- Continue running without that MCP server's tools
- The conversation should proceed normally
Actual Behavior
wait_for_background_mcp_loading() throws MCPRuntimeError
- Exception propagates uncaught through
_agent_loop()
- Entire worker process exits
- WebSocket
_read_loop dies without emitting error/idle status
- Frontend message remains stuck in "thinking"
Root Cause
In kimi_cli/soul/kimisoul.py, _agent_loop() (around line 680):
try:
await self.wait_for_background_mcp_loading()
finally:
if loading:
wire_send(StatusUpdate(mcp_status=self._mcp_status_snapshot()))
wire_send(MCPLoadingEnd())
The try/finally only ensures MCPLoadingEnd is sent, but does not catch MCPRuntimeError. The exception bubbles up and crashes the agent loop.
Additionally, in kimi_cli/web/runner/process.py, _read_loop() catches the unexpected exception but:
- Does not clear
_in_flight_prompt_ids
- Does not emit
"error" or "idle" status to WebSockets
- Frontend has no way to know the worker died
Suggested Fix
- In
_agent_loop(): Add except MCPRuntimeError to gracefully handle MCP failures:
try:
await self.wait_for_background_mcp_loading()
except MCPRuntimeError as e:
logger.warning("MCP loading failed, continuing without MCP tools: {}", e)
finally:
if loading:
wire_send(StatusUpdate(mcp_status=self._mcp_status_snapshot()))
wire_send(MCPLoadingEnd())
- In
_read_loop(): On unexpected exceptions, also clear in-flight prompts and emit error status before exiting.
Related
- The
/web switch also has a subprocess cleanup issue: when preserve_background_tasks=True, MCP child processes from the TUI are not terminated, causing port conflicts for the Web UI worker.
Environment
- kimi-cli version: latest (via
uv tool upgrade kimi-cli --no-cache)
- OS: macOS
- Python: 3.13
Problem
When an MCP server fails to connect (e.g., port conflict), the Web UI session worker crashes entirely instead of continuing without MCP tools. This causes messages to get stuck in "thinking" state indefinitely, and the frontend becomes unresponsive.
Reproduction Steps
chrome-local-bridgeon port 10086)/webto switch to Web UIMCPRuntimeErroris thrown, the worker process crashesExpected Behavior
MCP connection failure should be a graceful degradation:
Actual Behavior
wait_for_background_mcp_loading()throwsMCPRuntimeError_agent_loop()_read_loopdies without emitting error/idle statusRoot Cause
In
kimi_cli/soul/kimisoul.py,_agent_loop()(around line 680):The
try/finallyonly ensuresMCPLoadingEndis sent, but does not catchMCPRuntimeError. The exception bubbles up and crashes the agent loop.Additionally, in
kimi_cli/web/runner/process.py,_read_loop()catches the unexpected exception but:_in_flight_prompt_ids"error"or"idle"status to WebSocketsSuggested Fix
_agent_loop(): Addexcept MCPRuntimeErrorto gracefully handle MCP failures:_read_loop(): On unexpected exceptions, also clear in-flight prompts and emit error status before exiting.Related
/webswitch also has a subprocess cleanup issue: whenpreserve_background_tasks=True, MCP child processes from the TUI are not terminated, causing port conflicts for the Web UI worker.Environment
uv tool upgrade kimi-cli --no-cache)