MoonshotAI / MoonshotAI/kimi-code
v2 engine (headless -p): deferred MCP tools announced but select_tools never registered — top-level agent cannot call any MCP tool
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 7.5k
- Forks
- 1.2k
- Avg merge
- 11h 53m
- Merged PRs (30d)
- 350
Description
Summary
In headless (-p) runs under the experimental v2 engine (KIMI_CODE_EXPERIMENTAL_FLAG=1), MCP servers from the workspace's .mcp.json connect and their tools are announced to the model as deferred/loadable, with a system reminder instructing it to load them via select_tools. But select_tools is never registered in the top-level agent's function schema, and neither are the mcp__* tools themselves. The main agent therefore cannot call any MCP tool by any mechanism. The only workaround is delegating the entire task to a subagent (whose schema does include mcp__*), which breaks any flow where the top-level agent is meant to drive MCP-based coordination.
The v1 engine (no flag) handles the identical situation correctly: select_tools is exposed, the model loads the named tools, calls succeed.
Environment
- Kimi Code CLI 0.30.0 (macOS arm64)
KIMI_CODE_EXPERIMENTAL_FLAG=1, headless-pmode- Workspace with a
.mcp.jsondeclaring at least one MCP server (any server reproduces it)
Replication
cd /path/to/workspace-with-mcp-json
PROBE='Do NOT delegate or use the Agent tool. 1) List every mcp__* tool you can call directly from your own schema. 2) If a tool-loading mechanism (e.g. select_tools) is in your schema, name it and use it to load one mcp tool and call it. 3) Final line: verdict DIRECT_TOOLS=yes/no, SELECT_TOOLS=yes/no.'
# BROKEN — v2 engine
KIMI_CODE_EXPERIMENTAL_FLAG=1 kimi -p "$PROBE"
# WORKS — v1 engine, same prompt, same cwd
kimi -p "$PROBE"
v2 output (verbatim): "The system-reminder lists mcp__convops__ tools as 'added'… It also instructs me to use a select_tools tool to load full definitions — but select_tools is not in my schema either, so I cannot invoke it."* → DIRECT_TOOLS=no, SELECT_TOOLS=no
Wire-trace evidence
From the v2 session's wire.jsonl (agents/main/wire.jsonl):
profile.bind: profile "agent"
activeToolNames = [Read, Write, Edit, Grep, Glob, Bash, TaskList, TaskOutput,
TaskStop, CronCreate, CronList, CronDelete, ReadMediaFile,
TodoList, Skill, WebSearch, Agent, AgentSwarm, FetchURL,
AskUserQuestion, EnterPlanMode, ExitPlanMode, CreateGoal,
GetGoal, SetGoalBudget, UpdateGoal, mcp__*]
→ "select_tools" is NOT in the list
llm.request: toolSelect = True ← progressive-disclosure gate is OPEN
llm.tools_snapshot: 26 tools sent to the model ← select_tools absent, zero mcp__* tools
So disclosure is active (MCP tools correctly held out of the schema and announced as loadable), but the gateway tool needed to load them is missing from the schema.
Root cause (from the bundled source)
Three gates must agree for progressive disclosure to work, and two of the three consumers have a select_tools carve-out while the third does not:
AgentToolSelectService.enabled()(agent/toolSelect/toolSelectService.ts) — capabilitydynamically_loaded_tools+tool_use+tool-selectflag. Open in this run (toolSelect: True), soloadableToolsAnnouncement()injects the<tools_added>reminder namingselect_tools.AgentToolActivationService(agent/toolRegistry/toolActivation.ts) — registers contribution tools into the registry using rawisToolActive(policy, name, source)againstprofile.activeToolNames. The builtinagentprofile's list does not includeselect_tools, soSelectToolsToolis never registered.AgentToolSelectService.activeEntries()(schema shaping) does have the disclosure override —isToolActive(...) || (disclosure && entry.name === "select_tools" && isToolActiveForDisclosure(...))— and the executor'sregisterToolCallGuardalso routesselect_toolsthroughisToolActiveForDisclosure. But both can only act on tools that made it into the registry at step 2.
Net effect: the announcement layer and the activation layer disagree, and the model is told to call a tool it can never receive.
v1 does not have this bug because loopTools (agent-core/src/agent/tool/tools.ts) appends select_tools unconditionally whenever disclosure is on (const selectToolsName = disclosure ? [SELECT_TOOLS_TOOL_NAME] : []) — no profile check.
Suggested fix
Give AgentToolActivationService the same disclosure override the other two consumers already have, e.g. register the contribution when:
isToolActive(policy, options.name, source) ||
(options.name === "select_tools" && toolSelectEnabled && isToolActiveForDisclosure(...))
or add select_tools to the builtin agent profile's tool list. The class docstring for toolSelectEnabled states every consumer should read the single three-gate decision point "so degradation is lossless" — activation is the consumer that currently violates that invariant.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the headless commands in the issue with and without KIMI_CODE_EXPERIMENTAL_FLAG=1, then inspect agent/toolRegistry/toolActivation.ts alongside agent/toolSelect/toolSelectService.ts. Compare activation with the schema-shaping and executor disclosure checks, using the v1 loopTools implementation as a reference. Done means v2 exposes select_tools, loads an mcp__* tool, and can call it directly in headless mode.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 62/100