Files
zeekay 7374804405 hanzo-mcp 0.15.21: the active mode is the whole manifest, and a flood of output has a ceiling
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.
2026-10-12 08:47:57 -07:00

265 lines
8.0 KiB
TOML

[build-system]
requires = ["setuptools>=61.0.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "hanzo-mcp"
version = "0.15.21"
description = "The Zen of Hanzo MCP: One server to rule them all. The ultimate MCP that orchestrates all others."
readme = "README.md"
requires-python = ">=3.12"
license = { text = "MIT" }
authors = [{ name = "Hanzo Industries Inc", email = "[email protected]" }]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
keywords = ["mcp", "claude", "hanzo", "code", "agent"]
dependencies = [
# Hanzo AI core SDK (canonical types, config, session, MCP client)
# 2.2.1 is the first release shipping hanzoai.protocols (permissions.py imports it);
# 8.5.624 the first carrying hanzoai.usage, which the llm tools read refusals with
"hanzoai>=8.5.624",
# Core MCP dependencies
"mcp>=1.25.0,<2",
"fastmcp>=2.14.4",
"pydantic>=2.12.5",
"pydantic-settings>=2.12.0",
"typing-extensions>=4.13.0",
# Unified async I/O (aiofiles + uvloop auto-installed on Linux/macOS, graceful fallback)
"hanzo-async>=0.1.3",
"uvloop>=0.22.1; sys_platform != 'win32'",
# Hanzo tool infrastructure (all tools)
"hanzo-tools>=0.3.9", # 0.3.9: a cloud refusal carries its body and headers, send() returns headers
"hanzo-tools-fs>=0.3.4", # 0.3.4 returns image files as native MCP ImageContent
"hanzo-tools-shell>=0.6.6", # 0.6.6 bounds a foreground command's captured output; 0.6.5 pairs with hanzo-tools>=0.3.0 (ToolError)
"hanzo-tools-core>=0.3.1", # 0.3.1 is the EMPTY shim — no longer shadows canonical hanzo-tools.core
"hanzo-tools-browser[playwright]>=0.5.12", # 0.5.12 reaches the browser over the embedded ZAP router
"hanzo-tools-memory>=0.2.0",
"hanzo-tools-todo>=0.1.0",
"hanzo-tools-reasoning>=0.1.0",
"hanzo-tools-lsp>=0.1.0",
"hanzo-tools-computer>=0.5.5", # 0.5.5 screenshots downscale before they reach the wire
# 0.2.1 fixes ConfigTool passing permission_manager positionally into BaseTool.__init__
"hanzo-tools-config>=0.2.1",
"hanzo-tools-refactor>=0.1.0",
"hanzo-tools-llm>=0.2.2", # 0.2.2: llm limits, searched models, served/paid-by, typed plan refusals
"hanzo-tools-code>=0.1.0",
"hanzo-tools-vcs>=0.1.3", # 0.1.3 adds repo: repositories on Hanzo git (list, sync now)
"hanzo-tools-net>=0.1.0",
"hanzo-tools-agent>=0.3.1",
"hanzo-tools-api>=0.3.2", # 0.3.2 is the unified `hanzo` tool projected from cloud's OpenAPI registry
# The fleet is reached through the unified `hanzo` tool (hanzo-tools-api),
# which offers what cloud offers an AGENT — it withholds an operation whose
# name discloses a bearer secret, or whose mutating verb acts on identity or
# authority. The per-service packages that used to be installed here carried
# no such filter: hanzo-tools-kms handed a model `inject`, which reads every
# secret at a path, and hanzo-tools-iam handed it create_user/delete_user and
# the token and session reads. They remain installable for a human at a CLI.
"hanzo-tools-auth>=0.1.0",
"hanzo-tools-mpc>=0.1.0",
"hanzo-tools-ui>=0.1.0",
"hanzo-tools-devserver>=0.1.0", # running dev servers as MCP nodes: index, call, errors
"hanzo-persona>=1.0.0",
]
# For local development, install hanzo-tools-* packages from pkg/
# pip install -e ../hanzo-tools ../hanzo-tools-fs etc.
# Or when published, add them back as dependencies
[project.urls]
"Homepage" = "https://github.com/hanzoai/mcp"
"Bug Tracker" = "https://github.com/hanzoai/mcp/issues"
"Documentation" = "https://mcp.hanzo.ai"
[project.optional-dependencies]
# NOTE: hanzo-tools-* packages are installed separately from pkg/
# Use: pip install -e ../hanzo-tools -e ../hanzo-tools-fs etc.
# When published to PyPI, these extras will work:
# filesystem = ["hanzo-tools-fs>=0.1.0"]
# shell = ["hanzo-tools-shell>=0.1.0"]
# etc.
# Memory tools with full backend support
memory = [
"hanzo-tools-memory[full]>=0.2.2",
"sqlite-vec>=0.1.0",
"fastembed>=0.4.0",
]
# Interactive REPL with multi-language Jupyter kernels
repl = [
"hanzo-tools-repl>=0.1.0",
"jupyter-client>=8.6.0",
"ipykernel>=6.29.0",
]
# IDE integration (VS Code, Cursor, JetBrains)
ide = [
"hanzo-tools-ide>=0.1.0",
]
# Full interactive suite (REPL + IDE + Browser)
interactive = [
"hanzo-mcp[repl,ide]",
"hanzo-tools-browser>=0.2.1",
]
# Development
dev = [
"watchdog>=3.0.0",
"pytest>=7.0.0",
"pytest-cov>=4.1.0",
"ruff>=0.14.0",
"black>=23.3.0",
"mypy>=1.10.0",
"types-aiofiles>=23.2.0",
"types-psutil>=5.9.5",
"types-setuptools>=69.5.0",
]
docs = ["sphinx>=8.0.0", "sphinx-rtd-theme>=3.0.0", "myst-parser>=4.0.0", "sphinx-copybutton>=0.5.0"]
test = [
"pytest>=7.0.0",
"pytest-cov>=4.1.0",
"pytest-mock>=3.10.0",
"pytest-asyncio>=0.26.0,<1.0.0",
]
performance = [
"ujson>=5.7.0",
"orjson>=3.9.0",
]
publish = ["twine>=4.0.2", "build>=1.0.3"]
[project.scripts]
hanzo-mcp = "hanzo_mcp.cli:main"
hanzo-mcp-dev = "hanzo_mcp.dev_server:run_dev_server"
[tool.setuptools.packages.find]
where = ["."]
include = ["hanzo_mcp*"]
[tool.setuptools.package-data]
hanzo_mcp = ["py.typed"]
[tool.basedpyright]
include = ["hanzo_mcp"]
[tool.pytest.ini_options]
# Configuration for pytest
addopts = "--no-header --no-summary -p asyncio"
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
# Note: --cov-fail-under=96.3 will be added after achieving target coverage
markers = [
"asyncio: mark test as using asyncio",
"requires_hanzo_agents: mark test as requiring hanzo-agents SDK",
"requires_memory_tools: mark test as requiring hanzo-memory package",
"slow: mark test as slow running",
"integration: mark test as integration test",
]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
[tool.pytest_asyncio]
mode = "auto"
default_loop_scope = "function"
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
show_error_codes = true
show_column_numbers = true
pretty = true
# Per-module options
[[tool.mypy.overrides]]
module = [
"bashlex.*",
"grep_ast.*",
"libtmux.*",
"llm.*",
"nbformat.*",
"ffind.*",
"fastmcp.*",
"mcp.*",
"hanzo_agents.*",
"hanzo_memory.*",
"personalities.*",
]
ignore_missing_imports = true
[tool.coverage.run]
source = ["hanzo_mcp"]
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/site-packages/*",
"*/venv/*",
"*/migrations/*",
]
[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
fail_under = 96.3
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if __name__ == .__main__.:",
"raise AssertionError",
"raise NotImplementedError",
"if TYPE_CHECKING:",
"@abstractmethod",
]
[tool.coverage.html]
directory = "htmlcov"
[tool.ruff]
exclude = [
".git",
".venv",
"venv",
"__pycache__",
"build",
"dist",
"*.egg-info",
"node_modules",
"vscode-extension",
]
[tool.ruff.lint]
select = ["E", "F", "I", "B"]
ignore = [
"E501", # Line too long
"B017", # Blind exception test
"B023", # Function definition loop variable
"B904", # Raise without from in except
"E741", # Ambiguous variable name
"E402", # Module level import not at top
"F401", # Unused imports (handled by tests themselves)
"F821", # Undefined name (tests may have conditional imports)
"F841", # Local variable assigned but never used
"B007", # Loop control variable not used
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["F401", "F821", "F841", "B007"] # Relax rules for test files
"*/__init__.py" = ["F401"] # Allow unused imports in __init__.py