Files
python-sdk/pkg/hanzo-mcp/pyproject.toml
T
zeekay aa6aa95656 hanzo-mcp: search, ranked code spans from a local index
One call answers a question or a failing test's output with the spans
of the tree most likely to hold the cause, as path:start-end and their
lines, so an agent stops chaining grep and read.

Python is chunked by ast, other text in overlapping 40-line windows.
BM25 over code tokens is fused by reciprocal rank with the spans a
traceback's path:line frames name; when the query names a test file
the index holds, the code under test ranks first. Every search stats
the tree and re-chunks only what moved.

A dense stage (Qwen3-Embedding form, vectors cached by text hash) is
on only when HANZO_SEARCH_MODEL names a model: on the 42 dev tasks it
lifts span MRR .385 to .508, but a CPU query embedding takes seconds.

`python -m hanzo_search.server --root DIR` serves the tool alone over
stdio with the standard library, outside hanzo_mcp, whose import is
seconds long.

The `hanzo` mode lists it, so the default surface carries it; another
mode, `tool disable search` or HANZO_MCP_ENABLED_TOOLS decide it as they
decide every other tool.
2026-10-12 08:51:36 -07:00

271 lines
8.2 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.
# Dense stage of the `search` tool (on when HANZO_SEARCH_MODEL names a local model)
search = [
"torch>=2.4",
"transformers>=4.51",
]
# 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*", "hanzo_search*"]
[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