Verified against iam (controllers/token.go, user.go, init_apps.go), ai
(controllers/account.go), and login (middleware.ts) sources:
- client_id hanzo-app (canonical brand CLI/desktop/mobile app seeded with the
device_code grant), not the unseeded hanzo-dev/app-hanzo.
- Device flow uses RFC paths hanzo.id proxies to IAM — POST /oauth/device and
/oauth/token — form-encoded (IAM reads device-grant params via Query(), skips
JSON), keyed off the response body so a proxy can't desync polling.
- get_user_info reads /v1/get-account (IAM Claims with accessKey inlined),
not the nonexistent /v1/user.
- Replace fictional named-key CRUD (/v1/api-keys) with Hanzo's real single
per-user hk- key model: get_api_key (read accessKey), mint_api_key (self via
/v1/iam/mint-user-keys), get_or_create_api_key (read-first), revoke_api_key.
Email/SMS/Google/GitHub/web3 all work since the device flow delegates login UI
to the browser approval page (providers wired per iam wire_providers.go).
The hanzo-tools-ui docs-RAG client targets api.hanzo.ai (host already
canonicalized on main). Move the paths off the residual /api/ prefix to
top-level /v1/, matching the cloud-api route rename and the openapi
v1.0.0 lock-in (no /api/ prefix):
/api/search-docs -> /v1/search-docs
/api/chat-docs -> /v1/chat-docs
/api/index-docs -> /v1/index-docs
/api/search-docs/stats -> /v1/search-docs/stats
Hard cutover, coordinated with hanzoai/cloud (productsvc + ai serve these
at /v1), universe cloud-api-v1 AUTH_PUBLIC_PATHS, hanzo-docs RAG clients,
and the openapi cloud spec.
Co-authored-by: Hanzo Dev <[email protected]>
The cloud API now sits behind one front door (ingress -> gateway -> cloud).
api.cloud.hanzo.ai / cloud-api.hanzo.ai / functions.hanzo.ai are redundant
aliases routing to the same backend, so migrate clients to api.hanzo.ai.
- hanzo-tools-ui: HANZO_CLOUD_API default cloud-api.hanzo.ai -> api.hanzo.ai
(docs RAG paths /api/search-docs|chat-docs|index-docs are served at /api on
the cloud backend per AUTH_PUBLIC_PATHS; kept as-is)
- hanzo base status: Functions remote host functions.hanzo.ai ->
api.hanzo.ai/v1/functions (the canonical gateway path)
Non-breaking: the aliases still resolve to the same backend.
Co-authored-by: Hanzo Dev <[email protected]>
hanzo-iam is owned on PyPI by HANZO_AI_PYPI_TOKEN, not PYPI_TOKEN, so it 403'd.
Each upload now tries PYPI_TOKEN then falls back to HANZO_AI_PYPI_TOKEN, so any
hanzo-* package publishes regardless of which token owns its project.
Co-authored-by: Hanzo Dev <[email protected]>
The hardcoded MAIN/TOOLS package lists silently dropped hanzo-zap and the
newer packages, so tag-triggered 'publish all' never shipped them. Derive
the list from pkg/ so it can never go stale again. Also retry uploads with
backoff: PyPI 429-rate-limits rapid multi-package bursts, which dropped the
tail of a large publish (hanzo-hooks, hanzo-tools-gimp, hanzo-tools-s3).
Co-authored-by: Hanzo Dev <[email protected]>
Packaging metadata (twine check was PASSED-with-warnings -> clean PASS):
- config/llm/mcp/vector: add missing 'readme = README.md' ([project] key);
README files existed but were never wired into the dist metadata.
- net/plan/vcs: fill empty (0-byte) README.md files referenced by the
readme key, so long_description is no longer missing.
Tests (hanzo-tools-core/test_all_tools.py drifted from current sources):
- browser: 1 -> 3 tools (browser, cdp, playwright after the zapd split)
- mcp_tools: 4 -> 5 tools
- required-tool total: 21 -> 23
Patch release carrying the HIP-0111 IAM endpoint migration in
hanzoai.resources.iam (/iam/api/get-account + /iam/api/userinfo ->
canonical /v1/iam/oauth/userinfo), already in-tree since e1a38363 but
unreleased. 2.2.1 is taken on PyPI; bump to 2.2.2 (x.x.x+1).
hanzo-tools-iam built URLs as {base}/api/{path} — forbidden per HIP-0111
(no /api/ prefix; one way). Now {base}/v1/iam/{path}; health probe is
/v1/iam/healthz. Asserts JSON shape, not bare 200, to dodge the IAM SPA
catch-all that serves 200 text/html for unregistered paths. Adds a test
for _iam_url + IAM_BASE_URL. (Test harness wiring for this non-member
package is a separate packaging task — code verified: 0 /api/ remain.)
hanzoai/s3 (SeaweedFS fork w/ consensus+ZAP) is the canonical object
store; the minio pip package and its branding are gone. hanzo-s3 is now
a thin native boto3 adapter exposing a stable surface (S3Client + Bucket/
Object/Stat) so the hanzo s3 CLI and s3 MCP tool are unchanged. Removed
the dead MinioAdmin re-export (no consumer) and all MinIO naming.
CLI (7) + MCP S3Tool (14) tests green; ruff clean.
Closes the last gap in the one-way Hanzo control surface. IAM/KMS/PaaS
already had both CLI subcommands and MCP tools; S3 had neither.
- hanzo s3: buckets/mb/rb/ls/stat/rm/presign over hanzo_s3 (MinIO) client,
creds from env (inject from KMS, never plaintext). Registered in the
unified hanzo CLI alongside iam/kms/paas.
- hanzo-tools-s3: S3Tool MCP tool (same action-dispatch pattern as PaaSTool),
exported via the hanzo.tools entry point; s3 added to loader prefix map.
- uv workspace: map hanzo-cli/iam/kms/s3 + tools-s3 to local sources so the
unified CLI resolves on-disk packages (fixes hanzo_iam ImportError from a
stale published 1.29.0 wheel shadowing local 1.30.0).
Tests: 7 CLI + 14 MCP, all green.
No duplication: the SDK must not carry copies of governance proposals.
HIP-0300 duplicated canonical hip-0300; the rest diverge. Canonical home is
hanzoai/HIPs (git history preserves these). Forwards-only, no backcompat.
Root cause of dead shell-exec: stale installs froze hanzo-tools-core 0.2.0 (no ToolError)
before 0.3.0 published, shadowing hanzo_tools.core and crashing the shell package import.
Floors now exclude it; a fresh/upgraded install always lands the consistent set.
hanzo-tools-browser: the zapd router envelope (frame.rs mirror) is no
longer reimplemented — zapd_consumer now uses zap.frame + zap.ZapClient
(dep zap-proto>=1.2.0). The end-to-end browser command codec stays local
and untagged to match the extension's decodeCmd (frame.encode_cmd is
tagged and would corrupt it); a new test proves the byte layout.
hanzo-zap: wire.py now delegates the zero-copy codec (Message/Builder/
ObjectBuilder/object readers) to canonical zap.wire instead of a 3rd
hand-rolled copy; the hanzo cloud-service schema + handshake + frame I/O
remain as a thin layer. dep zap-proto>=1.2.0. 114 tests pass.
When the firefox backend is targeted and Firefox exposes a WebDriver BiDi remote agent (--remote-debugging-port=9222), drive it directly via bidi_client (real navigation + trusted input) instead of the half-implemented extension path or a dead headless Playwright. Additive + gated: only fires for target firefox with live BiDi and a BiDi-mapped action; otherwise falls through to the existing extension/Playwright routing unchanged. Takes effect after the MCP server reloads.
Firefox 129+ runs WebDriver BiDi-only by default (CDP off), so /json/version 404s and the socket is served directly at /session. Four fixes make the BiDi client actually work against it:
- probe(): fall back to a TCP check + ws://host:port/session when /json/version isn't advertised (don't open a probe WebSocket — that establishes session state and conflicts with session.new).
- websockets>=13 compat: ClientConnection dropped .closed; _ws_open() reads .state/.close_code across versions.
- session lifecycle: session.end on close() so a session never orphans (Firefox keeps one at a time, and an orphan blocks every later session.new); retry session.new once after ending.
- add create_context/close_context/get_url so automation runs in a fresh tab without disturbing the user's tabs.
External evo/spark [self-hosted,linux,amd64] fleet is offline; the in-cluster ARC
scaleset is healthy and targeted by name. Decouple from the shared test.yml gate
(also pinned to the offline fleet) for dispatch publishes.
Co-authored-by: Hanzo Dev <[email protected]>
* fix(browser-mcp): migrate CDP bridge -> zapd consumer; make disk authoritative
The MCP runtime kept pulling the buggy published wheel (hanzo-tools-browser
0.5.7) from PyPI instead of the migrated on-disk source, so every restart
re-broke the `cdp` tool. Reinstate `cdp` as a zapd-native tool, finish the
bridge->zapd migration (delete dead bridge/server modules + their tests), bump
to 0.5.8 and pin hanzo-mcp >=0.5.8, add the package to the uv workspace/sources
so disk is authoritative. 9/9 browser tests pass.
Pairs with the hanzoai/extension bridge->zapd migration (separate, still
uncommitted). The ~/.claude.json MCP invocation was also pointed at the local
editable source (machine config, not in this repo).
(cherry picked from commit 11b4d4ea8a5570d6fb0e51079fa445dba3713f71)
* fix(browser): drop dead lifecycle.py (in-process zap_server/cdp_bridge are gone)
lifecycle.py only wrapped the removed in-process ZAP server + CDP bridge; nothing
imports it under the zapd-consumer model. One way: transport is zapd_consumer,
tools are browser/cdp/playwright.
---------
Co-authored-by: hanzo-dev <[email protected]>
Co-authored-by: Antje Worring <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
The `memory` service never set REDIS_URL and never wired itself to the
Redis container. Runtime config (src/hanzo_memory/config.py) defaults
redis_url=None — caching via Redis is optional and unconfigured. The
`redis:` block was a dead, profile-gated (with-cache) declaration with
no consumer.
Per "no redis, no kv" policy: remove the orphaned block and its volume.
No consumer migration needed (nothing consumed it).
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
Published 0.2.0 predates 10da71bd: its ConfigTool passed
permission_manager positionally into BaseTool.__init__(self), so the
'config' tool failed registration on every server start (and 0.2.0
lacked WorkspaceTool entirely). The fix has sat unreleased on main —
same stranded-fix pattern as hanzoai.protocols. hanzo-mcp now floors
hanzo-tools-config>=0.2.1.
Floor hanzoai at 2.2.1 — the first release that actually ships
hanzoai.protocols, which permissions.py imports.
Drop the stderr→devnull redirect in stdio mode. The MCP stdio protocol
rides stdout alone; stderr is the channel clients log for diagnostics.
Silencing it turned the missing-module crash into a zero-byte mystery —
the client log literally suggests printing to stderr while the server
was throwing tracebacks into devnull. Run-loop failures also log with
exc_info so the traceback reaches the client log.
protocols.py landed 2026-03-31 (1fff6dea) but 2.2.0 was published before
it, so the module never reached PyPI. hanzo-mcp imports
hanzoai.protocols at startup; resolving against the published 2.2.0
crashed every stdio launch with ModuleNotFoundError before the
initialize response — surfacing as 'Failed to connect' in Claude
Desktop/Code.
release-please cut its last release at 2.0.2; versions are bumped by hand
and published by tag via publish-pypi.yml. The config, manifest, marker
comment, and release-doctor workflow were vestigial Stainless scaffolding.
Adds bidi_client.py — a WebDriver BiDi client over WebSocket to
Firefox 129+ / Chrome 124+ at --remote-debugging-port=9222.
This is the "trusted input" backend that complements the existing
WebExtension scripting backend (the cdp_bridge_server WS to the
extension). The extension produces synthetic events with
isTrusted=false, which strict frameworks (Drupal AJAX, certain React
libraries, security-aware sites) reject. BiDi via input.performActions
produces real browser input with isTrusted=true that ALL frameworks
honor because they ARE the real browser events.
Methods implemented:
- probe() auto-detect at /json/version
- connect() / close() WebSocket session lifecycle
- list_contexts() browsingContext.getTree
- find_context_by_url() locate tab by URL substring
- navigate(ctx, url) browsingContext.navigate
- capture_screenshot(ctx) browsingContext.captureScreenshot
- script_evaluate(ctx, js) script.evaluate in page context
- input_mouse_click(ctx,x,y) input.performActions — TRUSTED click
- input_double_click double-click composite
- input_key_press input.performActions key
- input_insert_text trusted keyboard typing
- click_selector(ctx, sel) composite: eval rect + trusted click
Architecture: decomplected, three layers. Layer 1 = wire transport
(WebSocket JSON-RPC). Layer 2 = canonical primitives
(input.performActions / browsingContext.* / script.evaluate). Layer 3
= ergonomic aliases (click_selector composite).
Integration into cdp_bridge_server.py — auto-detect on startup, route
BiDi.* methods, optionally route Input.dispatchMouseEvent and
hanzo.click to BiDi backend when available — is the next step.
To enable BiDi in a Firefox session:
open -a "Firefox Developer Edition" --args --remote-debugging-port=9222
The bridge will probe http://localhost:9222/json/version on startup;
if BiDi is available it advertises BiDi.* in its capabilities.
0.5.6 of hanzo-tools-browser shipped with a missing register() method
on CdpTool that crashed MCP startup. 0.5.7 fixes it. Floor bumped to
0.5.7 so pip/uv refuse to resolve the broken intermediate version.
0.5.6 shipped CdpTool inheriting BaseTool but missing the required
`register()` abstract method, so any host that registered the tool
crashed with `TypeError: Can't instantiate abstract class CdpTool`.
The browser package never reached MCP startup, taking the whole hanzo
MCP server with it.
This commit adds CdpTool.register() with the cdp-specific param surface
(action / method / params / tab_id / target_browser / client_id / timeout)
matching the BrowserTool registration pattern.
Yanking 0.5.6 from PyPI alongside this release; 0.5.7 is the canonical
0.5.x for the three-tool surface.
Decomplects the browser surface. Previously: BrowserTool with three
braided transports (ZAP, CDP bridge, Playwright) plus a CdpTool alias
that was just a name change. Now: three peer tools, one transport
strategy each, env-gated registration.
Tools (each default-on, each disable-able):
- `browser` — high-level action surface, auto-routes transport.
Disable: HANZO_BROWSER_TOOL_DISABLED=1
- `cdp` — raw Chrome DevTools Protocol method dispatch
(`method=`, `params=`). No Playwright fallback.
Disable: HANZO_CDP_TOOL_DISABLED=1
- `playwright` — same action surface as `browser` but pinned to
backend=playwright (no extension / CDP-bridge).
Disable: HANZO_PLAYWRIGHT_TOOL_DISABLED=1
Transport knobs stay orthogonal to tool selection:
- HANZO_ZAP_DISABLED=1 — don't auto-start the in-process ZAP server
- HANZO_CDP_BRIDGE_ENABLED=1 — opt back into the legacy HTTP bridge
- BROWSER_TRANSPORT=zap|http|auto
- BROWSER_BACKEND=firefox|chrome|extension|playwright|auto
Lifecycle (ZAP server + CDP bridge background threads) moved out of
__init__.py into lifecycle.py. __init__.py is now a pure re-export
surface that resolves TOOLS at import time based on env flags.
CdpTool is now a genuine peer of BrowserTool — method-oriented, not
action-oriented — replacing the alias-subclass from 0.5.5.
Removes three console scripts that should never have been declared at the
top-level shell namespace from hanzo-mcp:
hanzo-dev — name-collided with the canonical Rust Codex-fork CLI
at /opt/homebrew/bin/hanzo-dev (~/work/hanzo/dev).
hanzo-plugin — unscoped; if needed, belongs in its own package.
hanzo-unified — vague + unscoped.
Modules remain importable (hanzo_mcp.dev_tools, .cli_plugin,
.unified_backend) so any user-facing entry point can ship from its own
package later. Only hanzo-mcp + hanzo-mcp-dev stay as scripts here.
Also bumps the hanzo-tools-browser floor to >=0.5.5 so the CdpTool alias
(mcp__hanzo__cdp) is guaranteed alongside mcp__hanzo__browser on every
hanzo-mcp install.
Adds CdpTool — a subclass of BrowserTool with `name = "cdp"` — so MCP
clients can reach the same browser control surface under both
`mcp__hanzo__browser` and `mcp__hanzo__cdp`. One implementation
(BrowserTool.execute is shared), two registered names. ZAP server,
CDP-bridge fallback, and Playwright transport stack are all shared.
Two fixes for the canonical ZAP server:
1. Vendor the wire format (ZAP_MAGIC, MSG_*, MAX_MESSAGE_SIZE,
encode/decode) inside hanzo_tools/browser/zap_server.py instead of
importing from the external ``zap.protocol`` module — the published
``zap-protocol`` package on PyPI is now a ``zap-schema`` stub that
doesn't expose those symbols, so the import chain breaks for any
fresh install. Inlining ~40 lines keeps the spec self-contained and
matches the TS reference at extension/packages/browser/src/shared/zap.ts.
2. Pass ``host=self.host`` to ``zap_mdns.publish`` so the advertised
address matches the bound address. Default behaviour advertised the
outbound LAN IP via ``_local_ip()`` while the WebSocket was bound to
127.0.0.1; clients dialled the LAN IP and got ECONNREFUSED.
Drops the broken ``zap-protocol>=0.2.1`` dependency from both
hanzo-mcp and hanzo-tools-browser pyprojects — no longer needed.
Co-authored-by: Hanzo Dev <[email protected]>
Replaces the legacy per-operation tool list (read/write/edit/tree/dag/
zsh/shell/open) in PersonalityRegistry.ESSENTIAL_TOOLS with the six
HIP-0300 axes:
fs bytes + paths (read, write, edit, list, stat, apply_patch, search_text)
exec execution (run, background, ps, kill, logs)
code symbols (parse, search, transform, summarize)
git diffs + history
fetch network (get, post, download)
plan orchestration
Action-routed dispatch on top, not split tools — one tool per axis.
Keeps memory/think/critic/agent/config/mode/tool unchanged.
Follow-up to 6cb1d11 (hanzo-iam 1.30.0). Consumer packages now read only
the canonical IAM_ prefix — no upstream-brand alias chain, matching the
new IAMConfig.ENV_PREFIX contract enforced by test_env.py.
Touched modules:
- hanzo-cli: hanzo_cli/auth.py, hanzo_cli/bot/commands.py
- hanzo-tools-auth: hanzo_tools/auth/session.py
- hanzo-tools-iam: hanzo_tools/iam/iam_tool.py
- hanzo: src/hanzo/commands/{auth,iam}.py
- hanzoai: auth.py, llm_client.py
- tests: test_auth_pkce.py (import IAM_CLIENT_ID)
Also adds the generated pkg/hanzo-iam/uv.lock — matches the convention
of the other 17 axis pkgs that track their lockfiles.
Bumps hanzo-iam from 1.29.0 (older Casdoor SDK port) to 1.30.0 with a
purpose-built IAM SDK aligned to the canonical IAM_ env contract.
- ENV_PREFIX="IAM_" only — no HANZO_IAM_ / {ORG}_IAM_ fallback chain.
- IAMConfig.from_env reads IAM_ENDPOINT, IAM_CLIENT_ID, IAM_CLIENT_SECRET,
IAM_ORG, IAM_APP, IAM_CERT, IAM_USERINFO_URL.
- New FastAPI middleware module wraps Casdoor sign-in flow with
request-state user injection.
- 19 unit tests covering env-read, validation, and FastAPI integration.
Consumer packages (hanzo-cli, hanzo-tools-auth, hanzo-tools-paas) bump
their hanzo-iam dep to >=1.30.0.
Tag: hanzo-iam-1.30.0
The earlier patch-bump regex passed through a shell+python double-escape
that dropped the leading 'version = ' on each match and inserted a stray
\x01 byte, leaving '\x01"0.3.3"' instead of 'version = "0.3.3"'.
TOML parse fails (line 7 invalid statement) → CI publish-pypi 1/1 jobs
exit 1 → none of the 7 axis pkgs published.
Each TOML now parses with tomllib; ready for fresh tags.
(the prior tags pointed to commits CI didn't re-evaluate after the
black format-fix landed; bumping again so the new tags have unique
SHAs and the publish-pypi workflow fires cleanly)
The MCP wire (what tools/list reports) ships exactly one tool per axis:
fs, exec, code, git, fetch, plan — action-routed. The per-action classes
(ReadTool, WriteTool, EditTool, …) stay importable for in-process
read-only sandboxing (swarm sub-agents) but are NOT on the wire.
PyPI 0.3.1 of hanzo-tools-fs shipped the legacy split (7 tools) while
the local 0.3.1 source had already migrated to TOOLS=[FsTool]. Same
version number, two different contracts — the source of the
'Python MCP surfaces 32 tools, Node MCP surfaces 13' divergence
that breaks contract parity. Bumping to 0.3.2 publishes the
HIP-0300 surface for the first time.
- pkg/hanzo-tools-fs 0.3.1 → 0.3.2: TOOLS=[FsTool]; legacy classes
importable; dropped 'Edit = EditTool' alias; cleaner __init__.
- pkg/hanzo-tools-shell 0.6.3 → 0.6.4: bump only (source already
TOOLS=_get_detected_shell_tools()).
- pkg/hanzo-tools-code 0.1.0 → 0.1.1: bump only (TOOLS=[CodeTool]).
- pkg/hanzo-tools-vcs 0.1.0 → 0.1.1: bump only (TOOLS=[GitTool]).
- pkg/hanzo-tools-net 0.1.0 → 0.1.1: bump only (TOOLS=[FetchTool]).
- pkg/hanzo-tools-plan 0.1.0 → 0.1.1: bump only (TOOLS=[PlanTool]).
- pkg/hanzo-tools-agent 0.3.1 → 0.3.2: 'Edit' alias is gone — call
EditTool by its real name in swarm_tool.py.
- pkg/hanzo-mcp 0.15.3 → 0.15.4: gets the HIP-0300-clean wire when
consumed via 'uvx --refresh-package hanzo-tools-fs hanzo-mcp serve'.