Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Only write entries that are worth mentioning to users.

## Unreleased

- MCP: Add loading indicators for MCP server connections — Shell displays a "Connecting to MCP servers..." spinner and Web shows a status message while MCP tools are being loaded
- Web: Fix scrollable file list overflow in the toolbar changes panel
- Core: Add `compaction_trigger_ratio` config option (default `0.85`) to control when auto-compaction triggers — compaction now fires when context usage reaches the configured ratio or when remaining space falls below `reserved_context_size`, whichever comes first
- Core: Support custom instructions in `/compact` command (e.g., `/compact keep database discussions`) to guide what the compaction preserves
Expand Down
1 change: 1 addition & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This page documents the changes in each Kimi Code CLI release.

## Unreleased

- MCP: Add loading indicators for MCP server connections — Shell displays a "Connecting to MCP servers..." spinner and Web shows a status message while MCP tools are being loaded
- Web: Fix scrollable file list overflow in the toolbar changes panel
- Core: Add `compaction_trigger_ratio` config option (default `0.85`) to control when auto-compaction triggers — compaction now fires when context usage reaches the configured ratio or when remaining space falls below `reserved_context_size`, whichever comes first
- Core: Support custom instructions in `/compact` command (e.g., `/compact keep database discussions`) to guide what the compaction preserves
Expand Down
1 change: 1 addition & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## 未发布

- MCP:为 MCP 服务器连接添加加载指示器——Shell 在连接 MCP 服务器时显示 "Connecting to MCP servers..." 加载动画,Web 在 MCP 工具加载期间显示状态消息
- Web:修复工具栏变更面板中文件列表滚动溢出的问题
- Core:新增 `compaction_trigger_ratio` 配置项(默认 `0.85`),用于控制自动压缩的触发时机——当上下文用量达到配置比例或剩余空间低于 `reserved_context_size` 时触发压缩,以先满足的条件为准
- Core:`/compact` 命令支持自定义指令(如 `/compact keep database discussions`),可指导压缩时重点保留的内容
Expand Down
6 changes: 6 additions & 0 deletions src/kimi_cli/acp/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
CompactionBegin,
CompactionEnd,
ContentPart,
MCPLoadingBegin,
MCPLoadingEnd,
QuestionRequest,
StatusUpdate,
StepBegin,
Expand Down Expand Up @@ -158,6 +160,10 @@ async def prompt(self, prompt: list[ACPContentBlock]) -> acp.PromptResponse:
pass
case CompactionEnd():
pass
case MCPLoadingBegin():
pass
case MCPLoadingEnd():
pass
case StatusUpdate():
pass
case ThinkPart(think=think):
Expand Down
11 changes: 10 additions & 1 deletion src/kimi_cli/soul/kimisoul.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
CompactionBegin,
CompactionEnd,
ContentPart,
MCPLoadingBegin,
MCPLoadingEnd,
StatusUpdate,
StepBegin,
StepInterrupted,
Expand Down Expand Up @@ -357,7 +359,14 @@ async def _agent_loop(self) -> TurnOutcome:
self._steer_queue.get_nowait()

if isinstance(self._agent.toolset, KimiToolset):
await self._agent.toolset.wait_for_mcp_tools()
loading = self._agent.toolset.has_pending_mcp_tools()
if loading:
wire_send(MCPLoadingBegin())
try:
await self._agent.toolset.wait_for_mcp_tools()
finally:
if loading:
wire_send(MCPLoadingEnd())

async def _pipe_approval_to_wire():
while True:
Expand Down
4 changes: 4 additions & 0 deletions src/kimi_cli/soul/toolset.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ async def _connect():
else:
await _connect()

def has_pending_mcp_tools(self) -> bool:
"""Return True if the background MCP tool-loading task is still running."""
return self._mcp_loading_task is not None and not self._mcp_loading_task.done()

async def wait_for_mcp_tools(self) -> None:
"""Wait for background MCP tool loading to finish."""
task = self._mcp_loading_task
Expand Down
14 changes: 13 additions & 1 deletion src/kimi_cli/ui/shell/visualize.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
CompactionEnd,
ContentPart,
DiffDisplayBlock,
MCPLoadingBegin,
MCPLoadingEnd,
QuestionRequest,
ShellDisplayBlock,
StatusUpdate,
Expand Down Expand Up @@ -748,6 +750,7 @@ def __init__(self, initial_status: StatusUpdate, cancel_event: asyncio.Event | N

self._mooning_spinner: Spinner | None = None
self._compacting_spinner: Spinner | None = None
self._mcp_loading_spinner: Spinner | None = None

self._current_content_block: _ContentBlock | None = None
self._tool_call_blocks: dict[str, _ToolCallBlock] = {}
Expand Down Expand Up @@ -852,7 +855,9 @@ def refresh_soon(self) -> None:
def compose(self) -> RenderableType:
"""Compose the live view display content."""
blocks: list[RenderableType] = []
if self._mooning_spinner is not None:
if self._mcp_loading_spinner is not None:
blocks.append(self._mcp_loading_spinner)
elif self._mooning_spinner is not None:
blocks.append(self._mooning_spinner)
elif self._compacting_spinner is not None:
blocks.append(self._compacting_spinner)
Expand All @@ -875,6 +880,7 @@ def dispatch_wire_message(self, msg: WireMessage) -> None:

if isinstance(msg, StepBegin):
self.cleanup(is_interrupt=False)
self._mcp_loading_spinner = None
self._mooning_spinner = Spinner("moon", "")
self.refresh_soon()
return
Expand All @@ -895,6 +901,12 @@ def dispatch_wire_message(self, msg: WireMessage) -> None:
case CompactionEnd():
self._compacting_spinner = None
self.refresh_soon()
case MCPLoadingBegin():
self._mcp_loading_spinner = Spinner("dots", "Connecting to MCP servers...")
self.refresh_soon()
case MCPLoadingEnd():
self._mcp_loading_spinner = None
self.refresh_soon()
case StatusUpdate():
self._status_block.update(msg)
case ContentPart():
Expand Down
16 changes: 16 additions & 0 deletions src/kimi_cli/wire/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ class CompactionEnd(BaseModel):
pass


class MCPLoadingBegin(BaseModel):
"""Indicates that MCP tool loading is in progress."""

pass


class MCPLoadingEnd(BaseModel):
"""Indicates that MCP tool loading has finished."""

pass


class StatusUpdate(BaseModel):
"""
An update on the current status of the soul.
Expand Down Expand Up @@ -349,6 +361,8 @@ def resolved(self) -> bool:
| StepInterrupted
| CompactionBegin
| CompactionEnd
| MCPLoadingBegin
| MCPLoadingEnd
| StatusUpdate
| ContentPart
| ToolCall
Expand Down Expand Up @@ -431,6 +445,8 @@ def to_wire_message(self) -> WireMessage:
"StepInterrupted",
"CompactionBegin",
"CompactionEnd",
"MCPLoadingBegin",
"MCPLoadingEnd",
"StatusUpdate",
"ContentPart",
"ToolCall",
Expand Down
10 changes: 10 additions & 0 deletions tests/core/test_wire_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
CompactionBegin,
CompactionEnd,
ImageURLPart,
MCPLoadingBegin,
MCPLoadingEnd,
QuestionItem,
QuestionOption,
QuestionRequest,
Expand Down Expand Up @@ -87,6 +89,14 @@ async def test_wire_message_serde():
assert serialize_wire_message(msg) == snapshot({"type": "CompactionEnd", "payload": {}})
_test_serde(msg)

msg = MCPLoadingBegin()
assert serialize_wire_message(msg) == snapshot({"type": "MCPLoadingBegin", "payload": {}})
_test_serde(msg)

msg = MCPLoadingEnd()
assert serialize_wire_message(msg) == snapshot({"type": "MCPLoadingEnd", "payload": {}})
_test_serde(msg)

msg = StatusUpdate(context_usage=0.5)
assert serialize_wire_message(msg) == snapshot(
{
Expand Down
2 changes: 2 additions & 0 deletions tests_e2e/test_wire_skills_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ def ping(text: str) -> str:
"type": "TurnBegin",
"payload": {"user_input": "call mcp"},
},
{"method": "event", "type": "MCPLoadingBegin", "payload": {}},
{"method": "event", "type": "MCPLoadingEnd", "payload": {}},
{"method": "event", "type": "StepBegin", "payload": {"n": 1}},
{
"method": "event",
Expand Down
37 changes: 36 additions & 1 deletion web/src/hooks/useSessionStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ export function useSessionStream(
// Track compaction indicator message so we can remove it on CompactionEnd
const compactionMessageIdRef = useRef<string | null>(null);

// Track MCP loading indicator message so we can remove it on MCPLoadingEnd
const mcpLoadingMessageIdRef = useRef<string | null>(null);

// Wrapped setMessages
const setMessages: typeof setMessagesInternal = useCallback((action) => {
setMessagesInternal(action);
Expand Down Expand Up @@ -1759,6 +1762,31 @@ export function useSessionStream(
break;
}

case "MCPLoadingBegin": {
const mcpMsgId = getNextMessageId("assistant");
mcpLoadingMessageIdRef.current = mcpMsgId;
setMessages((prev) => [
...prev,
{
id: mcpMsgId,
role: "assistant",
variant: "status",
content: "Connecting to MCP servers…",
isStreaming: true,
},
]);
break;
}

case "MCPLoadingEnd": {
const mcpMsgId = mcpLoadingMessageIdRef.current;
mcpLoadingMessageIdRef.current = null;
if (mcpMsgId) {
setMessages((prev) => prev.filter((m) => m.id !== mcpMsgId));
}
break;
}

default:
break;
}
Expand Down Expand Up @@ -2347,9 +2375,16 @@ export function useSessionStream(
pendingApprovalRequestsRef.current.clear();
pendingQuestionRequestsRef.current.clear();

// Remove lingering MCP loading indicator (e.g. MCPLoadingEnd was never received)
const mcpMsgId = mcpLoadingMessageIdRef.current;
if (mcpMsgId) {
mcpLoadingMessageIdRef.current = null;
setMessages((prev) => prev.filter((m) => m.id !== mcpMsgId));
}

// Mark all streaming/subagent messages as complete
completeStreamingMessages();
}, [completeStreamingMessages, setAwaitingFirstResponse]);
}, [completeStreamingMessages, setAwaitingFirstResponse, setMessages]);

// Send cancel request or disconnect if stream not ready
const cancel = useCallback(() => {
Expand Down
12 changes: 12 additions & 0 deletions web/src/hooks/wireTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ export type CompactionEndEvent = {
payload?: Record<string, never>;
};

export type MCPLoadingBeginEvent = {
type: "MCPLoadingBegin";
payload?: Record<string, never>;
};

export type MCPLoadingEndEvent = {
type: "MCPLoadingEnd";
payload?: Record<string, never>;
};

export type ApprovalRequestEvent = {
type: "ApprovalRequest";
payload: {
Expand Down Expand Up @@ -224,6 +234,8 @@ export type WireEvent =
| SessionNoticeEvent
| CompactionBeginEvent
| CompactionEndEvent
| MCPLoadingBeginEvent
| MCPLoadingEndEvent
| ApprovalRequestEvent
| ApprovalRequestResolvedEvent
| QuestionRequestEvent
Expand Down
Loading