The default surface was 34 tools, 61 KB, ~15,300 tokens of schema in every
prompt of every agent. The mode system already names what a session should
carry, but only the tools register_all_tools enumerates obeyed it: zsh,
curl, playwright and the helpers (ps, npx, uvx, jq, wget, cdp, open) stayed
on in every mode. A mode now decides every discovered tool, the `hanzo`
mode lists what a coding agent calls every few steps (18 tools, 32 KB,
~8,000 tokens), and the rest is a name away: HANZO_MCP_ENABLED_TOOLS=a,b
or =all, `tool enable`, or `mode`.
hanzo-tools-shell 0.6.6: run_shell kept every byte a foreground command
wrote until the command ended. `yes` for thirty seconds is gigabytes. It
keeps the first 16 MiB per stream and drains the rest.
The tests' session-wide patch of asyncio.create_subprocess_exec handed
every shell command a stream that never reached EOF; the first test to run
one read it in a loop until the machine was out of memory (22 GB, killed).
The patch is now opt-in and its process has exited.
bench/latency.py measures the cold start, the tools/list bill and the common
calls through a real MCP client over stdio. Interleaved rounds, wall and CPU
seconds, p50 over 6 cold starts:
cold wall cold CPU tools bytes ~tokens
installed 0.15.18 9.34 s 10.46 s 30 52,601 13,150
main 0.15.20 1.52 s 1.13 s 34 61,287 15,321
this 0.96 s 1.06 s 18 32,222 8,055
The installed environment's 9 s is its hanzoai 8.5.156, whose package init
imports the whole generated cloud client; hanzo-mcp >= 0.15.20 requires
hanzoai >= 8.5.624, which loads it lazily. Per-call latency is spawn-bound
(zsh 17.6 ms, git status 33 ms, fs.read 2.5 ms at p50) and moves with the
machine's load; no call path changed.
hanzo-mcp
A Model Context Protocol server. It gives an MCP client — Claude Desktop, an editor, an agent — filesystem, shell, search and agent tools over one connection.
Install
pip install hanzo-mcp
Run
hanzo-mcp # stdio, the transport clients speak
hanzo-mcp --transport sse --host 127.0.0.1 --port 8888
hanzo-mcp --install # write the config into Claude Desktop
Point it at what it may touch, and nothing else:
hanzo-mcp --allow-path ~/work/project --project-dir ~/work/project
--allow-path may be repeated. Every filesystem tool refuses a path outside the
set, and .. and ~ are refused before resolution.
Flags
| flag | what it does |
|---|---|
--transport {stdio,sse} |
how the client connects; stdio by default |
--allow-path PATH |
grant access to a path; repeatable |
--project-dir DIR |
the project root, also granted |
--enable-agent |
let the server delegate to sub-agents |
--agent-model, --agent-api-key, --agent-base-url |
which model the agent tool calls |
--disable-write-tools |
read-only: no write, edit or shell mutation |
--disable-search-tools |
drop the search family |
--command-timeout, --search-timeout, --find-timeout, --ast-timeout |
per-family limits, in seconds |
--shell, --force-shell, --all-shells |
which shell the shell tools use |
--log-level LEVEL |
server logging |
hanzo-mcp --help prints the whole set; --version prints the build.
Tools
The server registers what the installed hanzo-tools-* packages provide — each
one publishes a TOOLS list under the hanzo.tools entry point, and the server
loads every package it finds. hanzo-tools-fs, hanzo-tools-shell and
hanzo-tools-core come with this package; install another and its tools appear
on the next start.
pip install hanzo-tools-git hanzo-tools-sql
In a client
Claude Desktop, claude_desktop_config.json — or hanzo-mcp --install, which
writes it for you:
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": ["--allow-path", "/Users/you/work"]
}
}
}
Embedding it
from hanzo_mcp.server import HanzoMCPServer
server = HanzoMCPServer(name="hanzo", allowed_paths=["/Users/you/work"])
server.run(transport="stdio")
run blocks, and takes the transport. The rest of what the CLI accepts —
allowed_paths, project_dir, enable_agent_tool, disable_write_tools,
disable_search_tools, the timeouts — are constructor keywords.
Development
uv sync
uv run pytest tests/ -v
License
Apache-2.0.