An OpenCode plugin that implements Claude's Tool Search Tool pattern. Reduces context usage by deferring tool descriptions and letting the model discover tools on demand via BM25 keyword search or regex matching.
Inspired by famitzsy8/opencode-tool-search-tool (a fork of opencode). This project achieves similar results as a standalone plugin — no core modifications needed.
- The
tool.definitionhook intercepts every tool before the LLM sees it - Tools not in
alwaysLoadget their descriptions replaced with a short[deferred]stub (parameters are kept — see OpenAI compatibility) - Two search tools (
tool_searchandtool_search_regex) are always available with full descriptions - The system prompt tells the model to use
tool_searchwhen it encounters deferred tools - When the model calls
tool_search("file operations"), it gets back full descriptions and parameter schemas of matching tools
| Setup | Total tools | Deferred | Savings per turn |
|---|---|---|---|
| Built-in only | ~32 | ~24 | ~8,400 tokens (88%) |
| Built-in + custom plugin tools | ~50 | ~42 | ~14,000 tokens (88%) |
MCP tools are NOT deferred on stock opencode. The
tool.definitionplugin hook is only fired for built-in and custom plugin tools — MCP tools bypass it entirely (see opencode-tool-search#9). The savings rows for "3 MCP servers" / "6+ MCP servers" that previously appeared here were inaccurate and have been removed. To get MCP support today, use the M0Rf30/opencode fork below.
npm install opencode-tool-searchAdd to your opencode.jsonc:
For local testing with file://:
{
"plugin": [
["file:///path/to/opencode-tool-search/dist/index.js", {
"alwaysLoad": ["read", "write", "edit", "bash", "glob", "grep"]
}]
]
}| Option | Type | Default | Description |
|---|---|---|---|
alwaysLoad |
string[] |
[] |
Tool IDs that keep full descriptions (never deferred) |
searchLimit |
number |
5 |
Max results per search query |
bm25.k1 |
number |
0.9 |
Term frequency saturation (0.5–2.0) |
bm25.b |
number |
0.4 |
Document length normalization (0–1) |
deferDescription |
string |
[d] |
Custom stub for deferred tools |
Defaults are optimized for smaller language models that send vague queries. For capable models writing specific queries, increase k1 toward 1.5.
["opencode-tool-search", {
"alwaysLoad": ["read", "write", "edit", "bash"],
"bm25": { "k1": 1.5, "b": 0.75 },
"searchLimit": 10
}]BM25 keyword search. Best for natural language queries.
tool_search({ query: "file" }) // → read, write, edit, glob
tool_search({ query: "search code" }) // → grep, ast_grep_search
tool_search({ query: "github issues" }) // → github_list_issues, github_create_issue
Regex search (case-insensitive). Best for specific patterns.
tool_search_regex({ pattern: "github.*issue" }) // → GitHub issue tools
tool_search_regex({ pattern: "^lsp_" }) // → all LSP tools
tool_search_regex({ pattern: "jenkins|build" }) // → Jenkins/CI tools
The fork modifies opencode's core to fully hide deferred tools from the LLM's tool list. This plugin uses the official plugin API:
- Tools are still listed (with a
[d]stub description; parameters are preserved) - The
tool.definitionhook strips descriptions; the system prompt guides the model - ~90% of the fork's benefit with zero core changes
- Works with any opencode version that supports
tool.definitionhook (v1.4.10+)
Each deferred tool still occupies a slot in the tool list with its name (~5-15 tokens), minimal stub (~5 tokens), and parameter schema (~20-50 tokens). With 180 deferred tools this adds up to ~5,400-12,600 tokens per turn. The fork eliminates these entirely by filtering tools in resolveTools() before they reach the LLM.
Fully closing the gap requires upstream changes to opencode's plugin API — see Scalability.
Earlier versions replaced deferred tool parameters with an empty schema (z.object({})). This breaks OpenAI models: when a ChatGPT model calls a deferred tool directly (ignoring the [d] stub), the empty schema can produce undefined arguments, which the OpenAI Responses API rejects with Missing required parameter: 'input[N].arguments'.
Since v0.4.3, deferred tools keep their original parameter schemas — only descriptions are stripped. Parameter schemas are small relative to descriptions, so the token savings impact is minimal (~3-5%). A provider-aware system prompt also tells non-Anthropic models explicitly not to call [d] tools without searching first.
Stock opencode loads MCP tools directly into the AI SDK tools dict in
packages/opencode/src/session/prompt.ts without ever firing the
tool.definition plugin hook. This means no plugin can defer MCP tool
descriptions on stock opencode — see #9.
M0Rf30/opencode is a thin downstream fork
of anomalyco/opencode that carries a
single 7-line patch firing tool.definition for MCP tools, making this plugin
work with MCP. Everything else tracks dev upstream daily.
# pacman wrappers (yay / paru / etc.)
yay -S opencode-m0rf30-bin opencode-tool-searchThe opencode-m0rf30-bin package provides=('opencode') and conflicts with
opencode, opencode-bin, and opencode-git, so it drop-in replaces any
existing opencode install. The opencode-tool-search package depends on
opencode (so either upstream or this fork satisfies it) and installs the
plugin to /usr/lib/opencode/plugins/opencode-tool-search.
After install, point your opencode.jsonc at the system plugin path:
{
"plugin": [
["file:///usr/lib/opencode/plugins/opencode-tool-search", {
"alwaysLoad": ["read", "write", "edit", "bash", "glob", "grep"]
}]
]
}# Linux x86_64 example:
curl -L -o opencode-linux-x64.tar.gz \
https://github.com/M0Rf30/opencode/releases/latest/download/opencode-linux-x64.tar.gz
tar -xzf opencode-linux-x64.tar.gz
install -Dm755 opencode ~/.local/bin/opencodeOther targets in the same release: opencode-linux-arm64.tar.gz,
opencode-darwin-x64.tar.gz, opencode-darwin-arm64.tar.gz,
opencode-windows-x64.zip, opencode-windows-arm64.zip, and -musl /
-baseline variants for static / older-CPU builds.
Releases are tagged vX.Y.Z-m0rf30 mirroring upstream. Drop-in replacement —
configuration, plugins, MCP servers, and update flow are unchanged. See
M0RF30.md in the fork
for the patch-management workflow.
The patch is also queued for upstream submission. Once merged into
anomalyco/opencode, the fork becomes unnecessary and this section will be
removed.
This plugin works alongside RTK (Rust Token Killer) with no conflicts. RTK hooks into tool.execute.before to compress bash/shell output; this plugin hooks into tool.definition and experimental.chat.system.transform to defer tool descriptions. Different hooks, complementary token savings.
The tool.definition hook can modify tool descriptions and parameters but cannot remove tools from the list entirely. Two upstream proposals would close the remaining gap:
hiddenfield ontool.definitionoutput (opencode#23297) — let plugins suppress tools from the LLM tool list entirelydefer_loadingpassthrough to Anthropic API (opencode#23298) — pass Anthropic's nativedefer_loading: truethrough to the API, enabling server-side tool search with prompt cache preservation
npm install
npm run build # tsc + esbuild bundleMIT
{ "plugin": [ ["opencode-tool-search", { "alwaysLoad": ["read", "write", "edit", "bash", "glob", "grep"] }] ] }