Smart Tool
A local MCP server that gives coding agents (Claude Code, Codex) cheaper, sharper tools than their built-in search:
Agents that must click, type or log in also get Microsoft's Playwright MCP,
registered as the playwright server where this machine lets a browser be driven (see Set up, step 3).
An optional hook routes the agent's native Grep/Glob/Read/Bash/WebSearch/WebFetch calls to these tools when they are cheaper, or just tells the agent what Smart Tool would do.
Status: 0.9.43 beta. Windows only. What changed in each version: CHANGELOG.md.
How it works
A coding agent normally finds code by running grep over the whole project and reads the web by downloading whole
pages. Both fill its context with text it does not need, and every token of it is paid for. Smart Tool is a small
program that runs on your machine and offers the agent better tools for the same jobs, through
MCP, the standard way agents call outside tools.
Claude Code / Codex ──MCP──▶ Smart Tool daemon (127.0.0.1, one per machine)
├─ smart_search ──▶ local index of your project (SQLite)
├─ smart_scout ───▶ the files behind a search, read on disk (small model optional)
├─ web_search ───▶ several search engines at once, shared cache
├─ web_fetch ────▶ the page, read by a small model
└─ project_manage
your model gateway (OpenAI, OpenRouter, Ollama...) ◀── embeddings and small chat calls
Searching code. When a project is registered, a small model looks at its folder structure once and decides what is worth indexing (source, tests, docs; not build output or dependencies). Each file is cut into chunks of up to 200 lines, and each chunk gets an embedding, a list of numbers that captures its meaning. A search runs two ways at once, by meaning (embeddings) and by exact words (SQLite full-text search), merges the two lists and, if you set a rerank model, reorders the best candidates. The agent gets back a few chunks split into code, tests and docs, with the functions most likely to matter, instead of every line that contains a word. The index follows the project: only files whose content changed are processed again, and each Git branch keeps its own view.
Reading code to answer a question. smart_scout takes a question in plain language. It finds the candidates the
same way as smart_search (or by keyword on disk while there is no index yet), reads the current lines of each one
and keeps, per file, the window with most of the question's words. The agent receives path:lines and those lines
verbatim. With a scout_model (the setup screen fills it with Claude Haiku 5.5 when the gateway has it), a small model reads those windows, writes a few lines of answer
and picks which lines to show; without an index it first picks the words and folders to scan (two calls; one with an
index). What the model writes never replaces the literal lines, and when it fails the lines come back with the error.
Searching the web. web_search asks several free search sources in parallel (DuckDuckGo, Wikipedia, Yahoo,
Exa, Google and others), in the language of the question and in English, and merges what comes back. Search APIs with a
free quota (Tavily, Firecrawl) and a real browser are tried only when those results are not enough. Answers are cached and shared by every agent
session on the machine, so the same question asked twice costs nothing the second time.
Reading a page. web_fetch takes a URL and a question. It downloads the page over plain HTTP; when the page only
shows its content with JavaScript, it opens it in a headless browser (Moli
first, Chromium as the fallback), which never reaches this machine or your local network. PDFs are read as text, page
by page. A small model then reads the page and returns only the answer, with the source.
The agent receives a paragraph instead of the whole page.
Steering the agent (optional hook). Agents keep reaching for their built-in tools out of habit. The hook sees each call before it runs: a content search over a project folder is stopped and the agent is told the exact Smart Tool call to make instead; reading a known file, or anything else, runs as usual. The same hook can remind the agent to document functions it edits and warn when it writes a function that already exists in the project.
What stays local. The daemon, the indexes, the caches and the logs live on your machine. Text leaves it only to reach the model gateway you chose (for embeddings and the small model calls) and the web search sources. With a local gateway such as Ollama, nothing about your code leaves the machine.
Requirements
- Windows 10 or 11 (x64) and Node.js 18 or newer for
npx. Python is not needed: the installer brings its own. - About 2 GB of downloads and 4 GB of disk on first install: Python 3.12 and its packages, Node.js when no supported version is installed, and the browsers for pages that need JavaScript (Camoufox alone is a 1.3 GB download; the installer shows its progress).
- An OpenAI-compatible model API for embeddings and small chat calls (OpenAI, OpenRouter, a LiteLLM proxy, Ollama...). Web search works without it; smart_search falls back to lexical search.
Install
npx @allansantos-dev/smart-tool@latest install
Updates install themselves, on by default: the tray checks npm when it starts (at logon) and every 24 h, and when there
is a newer version it runs this same command in a visible console (once per version; a failed version is tried again
after 24 h, with the previous version kept). You are told each time: a Windows notification from the tray (newer version,
updating, updated, update failed), and agent sessions get the same line on their first smart_search (and at session
start when the SessionStart report is on, shown to you too) with the link to the changes. The tray menu has "Update to
X" (or "Check for updates") on demand and an "Update automatically" switch; SMART_TOOL_AUTO_UPDATE=off also turns
automatic updates off, and the newer-version notice then carries the command to run. Running the command by hand also updates; ~/.smart-tool is kept. It downloads uv (checksum
verified) and a private Python 3.12 into ~/.smart-tool/runtime, with nothing added to PATH and, in the registry, only two per-user (HKCU) keys that make the notification buttons work, then
copies Smart Tool to %LOCALAPPDATA%\Programs\SmartTool with its own virtual environment,
installs the web runtime, registers a per-user Scheduled Task that starts the tray at logon and starts the daemon on
http://127.0.0.1:8765 (another free port is used, and announced, if 8765 is taken).
State lives in ~/.smart-tool: config.json, your model adapters in adapters/ and indexes, caches and logs in
data/. Secrets (API keys) are encrypted with Windows DPAPI for your user.
The model calls that must return JSON (the deep-research steps, smart_scout, the freshness check of cached
searches, the router's tier and sufficiency decisions, the index profile and the block-pattern review) use the model's structured output instead of a forced tool call: the request carries the JSON Schema, never
temperature, and the answer is validated against the schema before use. A stop other than a normal one (cut at the
token limit, refusal), an empty answer, a missing field or a value outside an enum fails the call with its reason
instead of passing as an empty result. The structured_output object of config.json chooses the format:
mode is auto (default: output_config for the models whose route, the id prefix before the first dot, is in
output_config_routes, which is ["vertex_ai"] by default; response_format, applied on every gateway route, for all
others, Bedrock included), response_format or output_config; output_config for a route outside the list is
refused. unsupported_keywords lists the schema keywords the gateway does not apply (maxItems, minLength...); they
are left out of the request and the code that reads the answer keeps enforcing them.
Set up
Open the setup screen from the tray icon ("Open settings") or at http://127.0.0.1:8765/setup. It opens on
Overview: everything Smart Tool needs, checked on this machine, with what needs you first and a button that takes
you to the fix. The side menu groups the rest under Connect (Agents, Models, Web search), Behavior (Agent rules,
Redirects) and Maintenance (Setup audit, Startup).
-
Model gateway: pick an adapter and fill its fields. The built-in OpenAI-compatible adapter takes a base URL ending where the API starts, like the OpenAI SDK
base_url(https://api.openai.com/v1,https://openrouter.ai/api/v1,http://localhost:11434/v1), and an optional API key. Saving tests/modelsfirst. -
Models: embedding, rerank (optional; without it results use the hybrid order), the router model (small, no reasoning: web search tier order and result checks), the index classification model (also reviews block patterns), the scout model (the short answer of
smart_scout) and the research model. Any id the gateway accepts works; the catalog is shown as suggestions. Empty roles are filled with the recommended models the gateway offers.When a new version recommends other models than the ones you use, a Windows notification (Apply now, Don't show again) and the New recommended models card on this page offer them: nothing changes until you apply, the models you had are kept, and Switch back to my previous models undoes it. The Model failures (last 24 h) table groups the failed gateway calls by type (429, 503, other 5xx, timeout, connection, authentication) and role, with the count, the first and last time and one sample, and shows the calls per purpose; a model that fails half of its calls (5 or more) also raises a notification and a tray alert. A failure inside a tool call also comes back as a warning line in the tool answer; calls made by a hook or by background work (index scope and profile, block-pattern proposals) have no answer to carry it, so they show only in the table, the log and the notification. The local embedder is not in that table or in the usage meter: it is a sidecar on your machine with no price, and its state is the Local models section.
-
Agents: register the MCP server in Claude Code or Codex (runs their official
mcp addcommand), install the hook and choose its behavior under Agent rules. A hook written by an older Smart Tool shows as outdated, with what an update changes; updating previews the entry and rewrites only Smart Tool's, after a backup.Browser control comes with the registration, and with every update for clients where smart-tool is registered: the official Playwright MCP (pinned in
web_adapters/node) is registered as theplaywrightserver, driving the Chromium Smart Tool installs, headless, with an isolated in-memory profile (no logins of yours) and its files in~/.smart-tool/data/playwright-mcp. Before registering, Smart Tool starts that server and opens a blank page as an agent would; where that fails (a corporate policy that forbids remote debugging, a missing browser) nothing is registered, agents keep reading pages throughweb_fetch, and the setup screen shows the reason with a retry button. Pages the agent opens are treated as untrusted: tools a page registers (WebMCP) are never exposed, the browser cannot open Smart Tool's own pages (every request it makes carries a header the daemon refuses), andbrowser_run_code_unsafe, which runs arbitrary JavaScript in the server process, is denied in the client (permissions.denyin~/.claude/settings.json,disabled_toolsin~/.codex/config.toml); if it cannot be denied, the server is not registered. Aplaywrightserver you registered yourself is never touched. Remove it withclaude mcp remove playwright -s user/codex mcp remove playwright.The hook behaviors:
- Redirect (default): the call is not denied. Smart Tool answers it and the answer comes back as the output
of the call the agent made:
smart_searchfor a search,web_fetchfor WebFetch,web_searchfor WebSearch (withoutallowed_domains/blocked_domains). In Claude Code a PostToolUse entry swaps the output (WebFetch and WebSearch still run natively, their result is replaced); a shell search whose answer is ready within 10 s runs as a no-op instead, so a grep with no match does not end in exit 1. In Codex the shell search is rewritten to print the answer from a file. Coexistence with other PreToolUse hooks on Bash: they run in parallel on the same original input and the host documents no order between theirupdatedInput. The Smart Tool rewrite is a constant (true/$null, whatever the search), carries no permission decision (a rivaldeny/askstill wins) and only applies to a content search, so a hook that rewrites other commands never meets it; if a rival rewrite of the same search wins, its command runs and the PostToolUse still swaps the output for the answer. Two other hooks rewriting the same command must be resolved in the plugin that owns them (the setup audit lists them). When the Smart Tool call fails or is late, the native call and its output stay as they are; a deny, with the exact call to make, is left only for a search whose query cannot be read from the command. The decision is a fixed rule, under a millisecond and the same every time: a content search over a project folder (grep -r,rg,git grep, the Grep tool on a folder) or over more than 20 listed files goes tosmart_search, in Bash and PowerShell alike and also when it is wrapped (bash -c,powershell -Command,timeout, a subshell), looped (for f in src/*.py), fed by a listing (find -exec grep,xargs,Get-ChildItem | Select-String) or written as an inline python/node script that walks a folder and matches text; reading or searching known files, listings, Glob and commands that change files run. The setup screen counts the redirects and what each session did next. With Block patterns proposed by a model on (default), the scope model reviews, in the background, bulk reads the rule could not measure and proposes regex patterns, each checked against the logged calls; a pattern redirects only after you accept it. - Advise only: the native tool runs; the agent receives the same advice next to the result and decides.
- Off: no routing.
Documentation on edit checks the public functions an edit touches (Python docstrings, JSDoc, Javadoc), in Claude Code edits and Codex patches. Private (
_name,#name,private), nested and override (@Override,@override) functions are exempt, as in pydocstyle, eslint-plugin-jsdoc and Checkstyle, and a JSDoc above a set of TypeScript overloads documents the implementation:- Remind (default): the edit runs and the agent is told which touched functions have no docstring, and which documented ones changed their signature, with their callers, the tests calling them and the command that runs those tests (from the code graph, only when it is already cached: the hook never waits for an analysis).
- Require: an edit that leaves a touched function without a docstring is blocked until it adds one.
- Off: no checks.
A documented function edited only in its body is not mentioned.
project_managewithaction=docslists the public functions still without a docstring, for documenting a project that started without them.Dependency check on edit: an edit of
package.json,requirements*.txtorgo.modthat adds or changes a dependency lists the OSV advisory IDs of up to 5 of them (exact version, or the lower bound of^,~,~=and>=), answered from a 6 h disk cache or one 2.5 s batch call that sends only package names and versions; when OSV cannot answer, the reminder says the check was skipped and the edit goes on.dependency_modeinconfig.json:warn(default) oroff.PDFs inside a project are indexed as text with
[page N]markers (20 MiB, 300 pages and 20 s per file; a scanned, password-protected or corrupt PDF is skipped with the reason), sosmart_searchreturns the page that holds a passage. Presentations and workbooks (.pptx,.xlsx) are indexed the same way: text of each slide under[slide N]with the speaker notes under[notes N], each sheet under[sheet NAME]with one tab-separated line per row (cached formula values; 100,000 rows or 1 MiB of text at most). Older formats (.ppt,.xls,.xlsm) are not read.Block patterns waiting for you are shown in one approval panel: 8 per page, the ones that would have blocked the most logged calls first, a filter over pattern, reason and example, and bulk accept or reject of the selected rows; each row opens to its reason and example.
Duplicate functions on edit compares the functions an edit writes with the indexed code. Warn (default) tells the agent when a written function has the same body as an existing one (comments, spacing and docstrings ignored) or a near-identical one (only local names, strings and numbers changed), with the path and lines of the original to reuse; it never blocks. Editing a function in place, overloads, tests, generated files and small functions (under 5 lines or 50 tokens) are not checked. Off disables it.
After editing,
project_managewithaction=affected_testslists the tests to run: every test file that imports a changed file, directly or through other files (git diff againstbase, defaultHEAD, untracked files included), plus changed tests and tests using a pytest fixture whoseconftest.pyimports the changed code, ordered with the likeliest failures first, and the command to run them. Changed files are read from disk, so new files and imports count before the project is reindexed.run_allis true, with the reason, when a configuration or lockfile changed or a changed code file is outside the import graph. Go is read by package: a file that imports a package depends on every file of it, so a changed package selects the tests of every package that imports it (go test ./pkg ...); the Go and Rust documentation and duplicate hooks stay silent.project_managewithaction=setup_auditand aproject_root(orpython setup_audit.py --project <folder>) reports what Claude Code and Codex actually load for that folder (settings layers, enabled plugins of both, hooks, MCP servers, skills, CLAUDE.md and AGENTS.md) and where they collide: several hooks deciding on the same tool in parallel, hook scripts that do not exist or hide their errors, hooks calling an MCP server that is not configured, MCP servers that do not start or offer the same tools twice (a gateway re-exposing a server also registered directly), skills sharing a name or over the description budget, an AGENTS.md only Codex reads, broken@importsand a Codex instruction chain over 32 KiB. Each finding links the documentation it relies on; nothing is edited, env and header values never enter the report, and what could not be checked is listed as not verified. A second run shows what is new and what was resolved. The daemon also runs it on its own for every registered project, two minutes after it starts and then every 30 minutes (MCP servers shared by several projects are contacted once and their result reused for 6 hours; Run now always contacts them again), and the setup screen's Setup audit panel shows each project's last report: findings by severity, what is new, what was resolved, the tokens each MCP server's tools cost and what was not verified. When a scheduled run finds new errors or warnings, a Windows notification from the tray names the folder and up to three of them, once per finding, with a button that opens the panel; manual runs and info findings stay silent.The agent hears about it without asking for it.
setup_auditwithmode=lastreturns the stored report for the folder (or the nearest audited parent) without auditing again, and fails with what to do when none is stored. On its firstsmart_searchof a session, and once per folder, the agent also gets one short line (under 100 tokens) when the stored report holds errors or warnings; a clean report adds nothing. Optionally, Report at session start on the Setup audit page (Claude Code or Codex) moves that line to the start of every new session: it previews the exact SessionStart entry, adds it to~/.claude/settings.jsonor~/.codex/hooks.jsononly after you confirm (backup first), and Turn off removes only that entry. Codex runs a new hook only after you approve it in/hooks; until then the page shows "waiting for your approval" and the search keeps repeating the line. It is off by default; while it is on, the search no longer repeats the line. The hook script (hooks/session_audit.py) only reads the stored report from the running service and prints nothing when the service is down. - Redirect (default): the call is not denied. Smart Tool answers it and the answer comes back as the output
of the call the agent made:
-
Local models (optional): if the mcp-memory embedding sidecar is installed on the machine, its local embedder can serve as a fallback (or preferred) embedding model.
-
Web search providers work without keys on their free public tiers; add your own key for more volume. Set your own contact (e-mail or URL) if you want public APIs such as Wikimedia to see it in the User-Agent.
Writing a model adapter
An adapter is a Python file in ~/.smart-tool/adapters/. It appears in the setup screen next to the built-in one.
import json
import urllib.request
NAME = "my_gateway" # unique id
LABEL = "My gateway" # shown in the setup screen
FIELDS = ( # rendered as inputs; secret values are stored with DPAPI
{"name": "url", "label": "Address", "required": True, "placeholder": "http://10.0.0.5"},
{"name": "token", "label": "Token", "secret": True},
)
def request(options, route, body=None, timeout=15):
"""route is OpenAI-style: models, model/info, embeddings, chat/completions, rerank. body None means GET.
Return (parsed JSON, response headers); let urllib.error.HTTPError propagate."""
data = json.dumps(body).encode() if body is not None else None
headers = {"Content-Type": "application/json"}
if options.get("token"):
headers["Authorization"] = "Bearer " + options["token"]
req = urllib.request.Request(f"{options['url']}/v1/{route}", data=data, headers=headers)
with urllib.request.urlopen(req, timeout=timeout) as response:
return json.load(response), response.headers
def verify(options):
"""Raise RuntimeError with a reason a person can act on."""
request(options, "models", timeout=10)
Embeddings and chat use the OpenAI request and response formats; rerank uses the Cohere/Jina format served by
LiteLLM ({"model", "query", "documents", "top_n"} returning results[].index and relevance_score). A file that
fails to load is listed in the setup screen with its error and does not affect the others.
Data and privacy
- Indexed code stays on your machine. Chunks are sent to your model gateway to compute embeddings, and small excerpts go to its chat models (scope, profile, routing, web_fetch answers). Use a local gateway (Ollama, LiteLLM) to keep everything local.
- Web queries go to the search sources listed in
NOTICE. Their free tiers are rate limited per IP address; respect each service's terms of use. - Logs and metrics are written to
~/.smart-tool/dataand never leave the machine. That includes the model failure log (model, role, tool and the error text, up to 300 characters, which can carry the gateway's error body; never the request).
Uninstall
Turn off "Start automatically" in the setup screen, quit from the tray, remove the hook entries from
~/.claude/settings.json / ~/.codex/hooks.json (the setup screen shows them), run claude mcp remove smart-tool -s user / codex mcp remove smart-tool, then delete %LOCALAPPDATA%\Programs\SmartTool and ~/.smart-tool. Turning off "Start automatically" also removes the
two per-user registry keys of the notification buttons (HKCU\Software\Classes\smart-tool-action and
HKCU\Software\Classes\AppUserModelId\SmartTool.Notifications); they come back the next time a notification is shown.
License
Apache License 2.0. See LICENSE and NOTICE for third-party components.
Page rendering is powered by Moli (Apache-2.0 / MIT), with Crawl4AI (Apache-2.0) as its fallback.