Skip to content

Commit 1e9454c

Browse files
committed
fix: load MCP tools only for main agent
Move MCP loading from load_agent() to KimiCLI.create() to ensure MCP servers are initialized only once, regardless of subagent count. Closes #518
1 parent 232e10b commit 1e9454c

File tree

5 files changed

+25
-39
lines changed

5 files changed

+25
-39
lines changed

examples/kimi-psql/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ async def create_psql_soul(llm: LLM, conninfo: str) -> KimiSoul:
280280

281281
# Load agent from configuration
282282
agent_file = Path(__file__).parent / "agent.yaml"
283-
agent = await load_agent(agent_file, runtime, mcp_configs=[])
283+
agent = await load_agent(agent_file, runtime)
284284

285285
# Add custom ExecuteSql tool to the loaded agent
286286
cast(KimiToolset, agent.toolset).add(ExecuteSql(conninfo))

src/kimi_cli/app.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,26 @@ async def create(
118118

119119
if agent_file is None:
120120
agent_file = DEFAULT_AGENT_FILE
121-
agent = await load_agent(agent_file, runtime, mcp_configs=mcp_configs or [])
121+
agent = await load_agent(agent_file, runtime)
122+
123+
# Load MCP tools only for main agent (not subagents)
124+
if mcp_configs:
125+
import pydantic
126+
from fastmcp.mcp_config import MCPConfig
127+
128+
from kimi_cli.exception import MCPConfigError
129+
130+
validated: list[MCPConfig] = []
131+
for mcp_config in mcp_configs:
132+
try:
133+
validated.append(
134+
mcp_config
135+
if isinstance(mcp_config, MCPConfig)
136+
else MCPConfig.model_validate(mcp_config)
137+
)
138+
except pydantic.ValidationError as e:
139+
raise MCPConfigError(f"Invalid MCP config: {e}") from e
140+
await agent.toolset.load_mcp_tools(validated, runtime)
122141

123142
context = Context(session.context_file)
124143
await context.restore()

src/kimi_cli/soul/agent.py

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,12 @@
66
from dataclasses import asdict, dataclass
77
from datetime import datetime
88
from pathlib import Path
9-
from typing import TYPE_CHECKING, Any
109

11-
import pydantic
1210
from kaos.path import KaosPath
1311
from kosong.tooling import Toolset
1412

1513
from kimi_cli.agentspec import load_agent_spec
1614
from kimi_cli.config import Config
17-
from kimi_cli.exception import MCPConfigError
1815
from kimi_cli.llm import LLM
1916
from kimi_cli.session import Session
2017
from kimi_cli.skill import discover_skills, get_claude_skills_dir, get_skills_dir
@@ -25,9 +22,6 @@
2522
from kimi_cli.utils.logging import logger
2623
from kimi_cli.utils.path import list_directory
2724

28-
if TYPE_CHECKING:
29-
from fastmcp.mcp_config import MCPConfig
30-
3125

3226
@dataclass(frozen=True, slots=True, kw_only=True)
3327
class BuiltinSystemPromptArgs:
@@ -177,21 +171,14 @@ def add_dynamic_subagent(self, name: str, agent: Agent):
177171
self.dynamic_subagents[name] = agent
178172

179173

180-
async def load_agent(
181-
agent_file: Path,
182-
runtime: Runtime,
183-
*,
184-
mcp_configs: list[MCPConfig] | list[dict[str, Any]],
185-
) -> Agent:
174+
async def load_agent(agent_file: Path, runtime: Runtime) -> Agent:
186175
"""
187176
Load agent from specification file.
188177
189178
Raises:
190179
FileNotFoundError: When the agent file is not found.
191180
AgentSpecError(KimiCLIException, ValueError): When the agent specification is invalid.
192181
InvalidToolError(KimiCLIException, ValueError): When any tool cannot be loaded.
193-
MCPConfigError(KimiCLIException, ValueError): When any MCP configuration is invalid.
194-
MCPRuntimeError(KimiCLIException, RuntimeError): When any MCP server cannot be connected.
195182
"""
196183
logger.info("Loading agent: {agent_file}", agent_file=agent_file)
197184
agent_spec = load_agent_spec(agent_file)
@@ -205,11 +192,7 @@ async def load_agent(
205192
# load subagents before loading tools because Task tool depends on LaborMarket on initialization
206193
for subagent_name, subagent_spec in agent_spec.subagents.items():
207194
logger.debug("Loading subagent: {subagent_name}", subagent_name=subagent_name)
208-
subagent = await load_agent(
209-
subagent_spec.path,
210-
runtime.copy_for_fixed_subagent(),
211-
mcp_configs=[], # Subagents don't need MCP tools, only main agent loads them
212-
)
195+
subagent = await load_agent(subagent_spec.path, runtime.copy_for_fixed_subagent())
213196
runtime.labor_market.add_fixed_subagent(subagent_name, subagent, subagent_spec.description)
214197

215198
toolset = KimiToolset()
@@ -230,22 +213,6 @@ async def load_agent(
230213
tools = [tool for tool in tools if tool not in agent_spec.exclude_tools]
231214
toolset.load_tools(tools, tool_deps)
232215

233-
if mcp_configs:
234-
validated_mcp_configs: list[MCPConfig] = []
235-
if mcp_configs:
236-
from fastmcp.mcp_config import MCPConfig
237-
238-
for mcp_config in mcp_configs:
239-
try:
240-
validated_mcp_configs.append(
241-
mcp_config
242-
if isinstance(mcp_config, MCPConfig)
243-
else MCPConfig.model_validate(mcp_config)
244-
)
245-
except pydantic.ValidationError as e:
246-
raise MCPConfigError(f"Invalid MCP config: {e}") from e
247-
await toolset.load_mcp_tools(validated_mcp_configs, runtime)
248-
249216
return Agent(
250217
name=agent_spec.name,
251218
system_prompt=system_prompt,

tests/test_default_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
@pytest.mark.skipif(platform.system() == "Windows", reason="Skipping test on Windows")
1616
async def test_default_agent(runtime: Runtime):
17-
agent = await load_agent(DEFAULT_AGENT_FILE, runtime, mcp_configs=[])
17+
agent = await load_agent(DEFAULT_AGENT_FILE, runtime)
1818
assert agent.system_prompt.replace(
1919
f"{runtime.builtin_args.KIMI_WORK_DIR}", "/path/to/work/dir"
2020
) == snapshot(

tests/test_load_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def test_load_tools_invalid(runtime: Runtime):
7373
async def test_load_agent_invalid_tools(agent_file_invalid_tools: Path, runtime: Runtime):
7474
"""Test loading agent with invalid tools raises ValueError."""
7575
with pytest.raises(ValueError, match="Invalid tools"):
76-
await load_agent(agent_file_invalid_tools, runtime, mcp_configs=[])
76+
await load_agent(agent_file_invalid_tools, runtime)
7777

7878

7979
@pytest.fixture

0 commit comments

Comments
 (0)