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

Open
#2,381 1 comment 0 reactions 0 assignees View on GitHub

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 -p mode
  • Workspace with a .mcp.json declaring 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:

  1. AgentToolSelectService.enabled() (agent/toolSelect/toolSelectService.ts) — capability dynamically_loaded_tools + tool_use + tool-select flag. Open in this run (toolSelect: True), so loadableToolsAnnouncement() injects the <tools_added> reminder naming select_tools.
  2. AgentToolActivationService (agent/toolRegistry/toolActivation.ts) — registers contribution tools into the registry using raw isToolActive(policy, name, source) against profile.activeToolNames. The builtin agent profile's list does not include select_tools, so SelectToolsTool is never registered.
  3. AgentToolSelectService.activeEntries() (schema shaping) does have the disclosure override — isToolActive(...) || (disclosure && entry.name === "select_tools" && isToolActiveForDisclosure(...)) — and the executor's registerToolCallGuard also routes select_tools through isToolActiveForDisclosure. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.