Repository navigation
Conversation
A session that registers many tools pays for all of them in every prompt, even when a turn needs one. Add `tool_search`: a BM25 ranker over tool metadata that activates the matches so the next model call declares them. Activation goes through the active tool set, so the change is recorded on the tool result like any other activation and survives resume and fork. Tools opt out of the default selection with `defaultActive: false`; they stay registered and reachable through the tool settings or another tool's activation. Explicit selection still wins: a name in the `--tools` allowlist is always activated. `tool_search` ships registered but undeclared, enabled per session through `defaultTools`, `--tools`, or the tool settings. Step's permission policy treats it as read-only: it loads tool definitions and touches no files.
lit26
requested review from
MelodyVAR,
Uking-xxx,
ZouR-Ma and
liuedcson
as code owners
November 3, 2026 12:39
A session that connects MCP servers pays for every server tool in every prompt. Add tool exposure so a tool can stay registered but undeclared, and make the Step tool profile's `find_tools` the loader for those tools. - `ToolDefinition.exposure`: `direct` (default), `deferred` (registered, undeclared, loaded on demand), `hidden` (registered, unreachable and left out of `getAllTools()`). Explicit selection still wins: a name in the tool settings or the `--tools` allowlist activates a deferred tool. - `find_tools` ranks deferred and active tools with a BM25 ranker and activates the deferred matches, which the next request declares. The activation is recorded on the tool result, so it survives resume and fork. - MCP servers publish their tools as `deferred` by default; `exposure` and `description` in `config.toml` set that per server. - The MCP extension adds an `mcp_servers` system-prompt section naming the servers whose tools are deferred, because the tool list and `find_tools` both stay silent about them. - `ExtensionContext` exposes the live tool set (`getActiveTools`, `getAllTools`, `setActiveTools`) so a profile tool can activate others. `find_tools` is already classified read-only by the permission policy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MCP server tools no longer enter the prompt by default; the Step profile's existing
find_toolsloads the ones a turn needs.No new tool name is introduced.
find_toolsalready shipped in the Step tool profile and could only see tools that were already active. This change gives it a ranked, activating implementation and an exposure model that leaves tools registered but undeclared.find_toolsbefore and afterincludescount per query tokenExtensionContext.getToolCatalog())N. name [score=…]+ description + schemaNewly available from your next call: …getToolCatalog()getActiveTools()/getAllTools()/setActiveTools(); a host with onlygetToolCatalog()still gets the ranked answer, just without activationThe ranking lives in
packages/coding-agent/src/core/tools/tool-search.ts(module name only). Thetool_searchtool and extension from the first commit were deleted in the second; squash on merge if you prefer a single commit.Changes
ToolDefinition.exposure:direct(default): declared and active at session start.deferred: registered, undeclared, loaded on demand byfind_tools.hidden: registered, unreachable, and left out ofgetAllTools().defaultToolsactivates a deferred tool, as does the--toolsallowlist.src/step/mcp.ts: server tools publish asdeferredby default. New per-serverconfig.tomlkeysexposureanddescription.renderMcpServersSection(): a# MCP serverssystem-prompt section listing deferred servers with their one-line summary. Neither the declared tool list norfind_toolsnames them, so without this section the model cannot know they exist.ExtensionContext:getActiveTools(),getAllTools(),setActiveTools(), wired through the runner's existing session actions.step/permissions.ts:find_toolsis read-only — it changes the session tool set and touches no files.Review focus
find_toolsloses access to MCP tools. The prompt section is the mitigation. If that trade is too sharp for a default, the alternative isdirectby default with per-server opt in — a one-line change inserverExposure().find_toolsonly ever adds tools, never removes them, so the additive-activation path that recordsaddedToolNamesapplies and the change survives resume and fork.Configuration
Existing configs keep working: unknown keys are ignored, and a server without
exposurebecomes deferred.Verification
pnpm run checkclean (biome, boundary checks,tsgo --noEmit, browser smoke).test/tool-search.test.ts— 16 tests: tokenizer and BM25 ordering, activation, deferred/direct/hidden session behavior, settings-based activation, read-only permission decision forfind_tools.test/tool-search-integration.test.ts— 2 end-to-end agent turns on the faux provider with the real Step profile: the deferred tool is absent from request 1, declared on request 2, andaddedToolNamesrecords the activation.test/tool-search-mcp.test.ts— 2 end-to-end turns against a real streamable-HTTP MCP peer: deferred by default, the prompt section appears,find_toolsloads the tool, the model calls it and the server receives the arguments; adirectserver is declared without the section.src/step/mcp-startup.test.ts(+3 exposure and section tests),src/step/mcp-catalog.test.ts(late catalog and removal observed throughfind_tools),test/step-tool-profile.test.ts(description).pnpm step --approval-mode auto -e ./demo.ts "Find a tool that can look up weather, load it, then call it for Paris."loadsweather_lookupthroughfind_toolsand answerssunny in Paris, with no settings or config change.vitest --runin this checkout fails 30 pre-existing tests that need a real provider credential (No API key found for step); unrelated.Unit and end-to-end tests use the faux provider; no paid model requests.
Not in this change
codemodescripts and nosearchTools(): Step has no codemode surface, sodeferredis the only indirect exposure.toolExposurepatterns (get_*,delete_*): exposure is per server.tool_reference, OpenAItool_search_call) already exist inpackages/providers; this change relies on the existing additive-activation plumbing.