Moves rarely used built-in tools behind ToolSearch so their schemas stay out of every turn's context

A Claude Code plugin that shrinks the tool definitions sent with every request by moving rarely used built-in tools behind ToolSearch.
Claude Code sends the full schema of each non-deferred built-in tool on every turn. Some of them are large and seldom used: the Artifact tool alone is about half of all the tool definitions sent upfront. This plugin marks these tools as deferred, so the model sees only their names and loads a schema through ToolSearch when it actually needs the tool:
ArtifactWorkflowScheduleWakeupReportFindingsReadNotificationsListAgentsAskUserQuestionIn the author's sessions this cut the tool definitions sent with each request by about 80%. Every tool stays available.
At the prompt of a terminal session:
/plugin install cc-lean-system-tools --marketplace TrueNight/claude-tools
Answer y to add the marketplace, then press Enter to install at the user scope.
From a shell:
claude plugin install cc-lean-system-tools --marketplace TrueNight/claude-tools
In the desktop app, where /plugin does not run: click + next to the prompt box, then Plugins, Add marketplace with TrueNight/claude-tools, then Add plugin.
/context may keep showing the old "System tools" number. To see what a session really sends, open its transcript (~/.claude/projects/<project>/<session-id>.jsonl) and look at the api-request-shape record: its shape.tools entries without defer_loading are the tools sent in full.
The defer option holds the tool names to defer, and defaults to the list above. Your own list replaces the default rather than adding to it, and an empty list defers nothing.
From a shell, pass the names as one comma-separated string:
echo '{"defer": "Artifact, Workflow, ScheduleWakeup, WebFetch"}' | claude plugin configure cc-lean-system-tools@claude-tools --values-stdin
Inside Claude Code, run /plugin configure cc-lean-system-tools@claude-tools. Or set it under pluginConfigs in ~/.claude/settings.json, as an array or a comma-separated string:
{
"pluginConfigs": {
"cc-lean-system-tools@claude-tools": {
"options": {
"defer": ["Artifact", "Workflow", "ScheduleWakeup", "WebFetch"]
}
}
}
}
Names match exactly, MCP tools included (mcp__<server>__<tool>). Start a new session for a change to take effect.
To remove a tool from context entirely instead of deferring it, add its bare name to permissions.deny in settings.json.
tool.describe), which is in early access and may change between releases. Written against Claude Code 2.1.300.hooks/register.ts 33 lines1import type { Register } from 'claude-code'
2
3const escapeRegExp = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
4
5// settings.json can hold the list as an array, but `claude plugin configure`
6// saves it as one comma-separated string.
7const toolNames = (value: unknown): string[] =>
8 (Array.isArray(value) ? value : typeof value === 'string' ? value.split(',') : [])
9 .map(name => String(name).trim())
10 .filter(name => name !== '')
11
12// The `defer` option's default in plugin.json lists built-in tools few
13// sessions call: carrying each full schema on every turn costs more than one
14// ToolSearch load in the session that does. A user's list replaces it.
15export const register: Register = (on, options) => {
16 const tools = toolNames(options.defer)
17 if (tools.length === 0) return
18
19 const deferred = new RegExp(`^(${tools.map(escapeRegExp).join('|')})$`)
20
21 on('tool.describe', { tool: deferred }, async ($, e, next) => ({
22 ...(await next(e)),
23 isDeferred: true,
24 }))
25
26 // A resumed session replays the tool placement it recorded; asking again
27 // applies the deferral there too.
28 on('session.start', ($, e, next) => {
29 $.ui.invalidate('tool.describe')
30 return next(e)
31 })
32}
33