Commit Graph
25 Commits
Author SHA1 Message Date
zeekay af78255a39 devserver: running dev servers as MCP nodes
An agent editing a web app needs the dev server's own view of it: which
routes exist, and whether the last edit broke the build. Next 16 serves
that over MCP at /_next/mcp, and Vite-based frameworks can with
vite-plugin-mcp, but nothing here found those endpoints.

index finds Next, Vite, Nuxt, Remix, React Router, Astro and SvelteKit
dev servers from each process's argv, cwd and listening ports, and lists
the tools of the MCP each serves. call runs one. errors returns current
build and runtime errors: Next's get_errors, after opening the app in the
browser tool, since Next reports only what a connected session reported;
otherwise Vite's error overlay, console errors and page errors, read
through the same browser tool.

discover() returns the servers as plain records, the shape a devserver
node will take. hanzo-mcp depends on the package so the tool ships.

Claude-Session: https://claude.ai/code/session_01Dc8DXc3fEQCRHUa6XWK4Ku
2026-09-29 12:37:53 -07:00
zeekay 169648c43d hanzo_kai.jev lists the decision models of data in Jev's shape, and the conformance fixtures name every reach refusal and pin Kai's versioned id 2026-09-28 17:37:54 -07:00
zeekay 15c3a6ea95 Bring hanzo-kai to the frozen decision contract, with the Jev-compatible hanzo_kai.jev and a conformance fixture per rule 2026-09-28 14:57:06 -07:00
zeekay 75771bfe7f Add hanzo-kai, the Python client for Kai decisions 2026-09-28 14:32:02 -07:00
zeekay eab5541a89 Stop handing a model the secrets the fleet withholds
Nine per-service packages registered a hanzo.tools entry point and sat in
hanzo-mcp's default dependencies, so installing it put them in a model's tool
list beside the unified tool and without its filter.

hanzo-tools-kms offered inject, which lists a path and reads every secret at it.
hanzo-tools-iam offered create_user, delete_user and the token and session reads.
cloud withholds all of them from an agent; these were a second way to the same
API, so the rule held on one path and not the other.

The auto-registration is withdrawn, not the packages: they stay importable for a
human at a CLI, which is where reading a secret is a legitimate act. An agent
reaches all eight through the unified tool, verified against the live document.

mpc keeps its registration because the fleet has no equivalent, and auth keeps
its because it reads the caller's own claims rather than another identity's.
2026-08-30 20:08:02 -07:00
zeekay 42139d1849 Offer what the fleet offers, not what the contract lists
The unified tool derived its surface from /v1/openapi.json, which describes the
API rather than what a model may be handed. cloud keeps an operation off the
agent surface when its name discloses a bearer secret or when a mutating verb
acts on identity — post_iam_users is in the contract and refused at /v1/mcp, so
this tool offered an identity mutation the fleet declines.

The document still supplies each operation's method and path, which a caller
needs to make the request; the generated catalog supplies which are offered. Two
facts, one source each, and the rule is asked of cloud rather than restated. An
unreadable catalog offers nothing, because failing open widens the surface
silently.

Two aliases pointed at names the fleet had left behind, mapping callers off the
live surface. A synonym may only point at a name it serves.

A fixture naming no operations is not exercising the offer rule and now says so,
rather than the default being weakened to keep it green.
2026-08-30 19:52:51 -07:00
zeekay badff3db49 Read the fleet from the same catalog the other runtimes do
Python addressed the cloud through ten hand-written packages while TypeScript and
Rust read a generated catalog, and the hand-written ones had drifted: paas is
platform, storage is s3, auth folded into iam, and commerce asked for plurals the
fleet does not publish.

hanzo-tools-cloud embeds the file those two already embed, so the three cannot
disagree about what the API offers — a test compares them by digest. One routing
tool over 114 subsystems rather than 114 tools, for the reason the fleet groups
its own.

Naming a subsystem the fleet does not serve is answered before the credential
check: demanding a key for something that does not exist sends the caller to fix
the wrong thing.
2026-08-30 17:05:32 -07:00
zeekay f3303e4119 the tag lane's other fault: a ref the Actions user pushed starts nothing 2026-08-14 18:22:42 -07:00
zeekay 0f5676c200 one release path, and the docs say the one that runs
RELEASE.md described GitHub Actions, GitHub secrets and three bin/ scripts
that each took a PyPI token off the environment. None of that publishes this
repo — .hanzo/workflows/publish-pypi.yml does, reading the token from KMS —
so the scripts are gone and the page describes the lane, its tag mapping, and
what to check before a tag makes a version permanent.

LLM.md carries the published state: hanzoai 3.2.13 with get_keys/get_tools,
and the KMS route that a 404 will otherwise misreport as a missing secret.
2026-08-14 17:51:02 -07:00
zeekay 4998a916fd the client sends the credential the document now declares
hanzoai/cloud@113cf7d6 declares components.securitySchemes.bearer and applies it
document-wide, so regenerating writes a populated Configuration.auth_settings()
and auth_settings=['bearer'] into 2498 call sites; ApiClient._apply_auth_params
sets Authorization: Bearer <token> from access_token, which was read by nothing
before. Four call sites carry an empty list — exactly the operations the document
marks security: [] (get_models, get_models_providers, get_commands,
get_openapi_json) — so the public routes still go out with no credential.

examples/client.py drops the header_name/header_value compensation and passes
access_token. examples/models is a new flow on the public catalog: it runs before
a reader has a key, which makes the install checkable in one command.

Proven on api.hanzo.ai, not by reading: examples.models prints 112 models with no
credential; examples.hello returns the caller's keys with a real sk- and 403
"sign in to manage API keys" with a bogus one.
2026-08-14 17:22:30 -07:00
zeekay 7a5968f9dc a pk- key authenticates nobody, so stop telling readers it reads
The README offered the two key shapes as a permission split — pk- to read,
sk- to write. cloud's own identity boundary says otherwise: middleware_identity
returns nil for any pk- before it consults the key store, because a key meant
for a browser bundle must not read. A pk- resolves to an org so a beacon can be
attributed to a tenant, and that is all it does.

Following the old sentence, a reader exports a pk- and every one of the five
flows answers 403 — /v1/tools spells out why, "a validated principal is
required" — and that refusal reads like a revoked key rather than the wrong
shape of key. examples/client.py now refuses a pk- before the request goes out,
the same argument that already rejects an unsendable access_token.

Also: three status codes in example docstrings had drifted from what the API
answers now (/v1/kv/keys/* 404s on every verb, /v1/automations/mcp 404s), and
two dead files went with them — examples/.keep, whose directory has held real
flows for a while, and scripts/mock, which reads a .stats.yml this repo does
not have and which scripts/test already says is unnecessary.
2026-08-14 14:45:57 -07:00
zeekay 241ca6dc59 the front door tells the truth about the client behind it
README: PyPI serves 3.2.1 and this tree is 3.2.11, so the install line is the
git one. Between the two the default version left the operation ids — 3.2.1
carries get_v1_keys/get_v1_tools (182 api modules, 2172 models) and this carries
get_keys/get_tools — so `pip install hanzoai` above a `get_keys()` quickstart
hands the reader a client that cannot run it. Counts are measured off the pinned
document: 1814 paths, 2479 operations, 191 tags + default_api = 192 API classes
over 2460 models, 2659 importable modules.

Auth was wrong everywhere and it was the same root cause: the document declares
no securityScheme, so every operation got an empty auth_settings and
Configuration(access_token=…) is read by nothing. Serializing get_keys from such
a Configuration produces a request with no Authorization header at all — the
five flows have been calling anonymously and reading the refusal as a bad key.
examples/client.py now passes the bearer through ApiClient's header_name/value
pair, verified against a local server that echoes what it received, and a smoke
test pins both halves so the day cloud emits the scheme, the test fails and the
header moves back into Configuration.

The examples gate resolved class names and stopped there, which is how `money`
and `tools` once passed it while every call in them named a method that no
longer existed. It now walks the syntax tree for attribute accesses on anything
bound to a *Api and asks the class: ten names resolve, and a stale get_v1_tools
fails in the same second the import passes.

Deleted five example scripts that imported hanzoai.Hanzo, hanzoai.cluster,
hanzoai.agents and hanzoai.completion — none of which exist — or were dict
literals captioned "in practice this would be called through the MCP interface";
compileall passed all five, because syntax is not a claim. Deleted three docs
that were fabrication rather than drift and that nothing rendered: FEATURES.md,
QUICKSTART.md, GPT5_ORCHESTRATION.md.

pkg/hanzo-agent said `pip install hanzoai` and `from hanzoai import Agent,
Runner`; the client has no Agent. Its own `full` extra resolved to
hanzoai[web3,tee,marketplace,cli], extras the client does not declare, so it
installed the client and none of the extensions. pyproject said BSD-3-Clause
while LICENSE is the Apache 2.0 text. test_smoke imported tracker_api and no
/v1/tracker path is emitted any more, so the suite carried a failing test naming
a module the client does not have; it reads analytics_api now, off the client.
2026-08-14 14:31:08 -07:00
zeekay d1fa0676d1 3.2.10 — the client says what the wire says
Regenerated from hanzoai/cloud@6a719cdc (.spec-lock carries the ref and the
digest). 3.2.9 was a projection of a document 343 commits old, and in between
the operationId stopped carrying the default version: 5196 method names the
client published — `get_v1_tools`, `post_v1_agent`, every one of them — name
operations the document no longer declares. None remain.

The id is one identity, not four. An operationId is the SDK method, the MCP
tool name, the CLI spelling and the by-name key, so a client that keeps the
old spelling does not merely look stale — it teaches a name nothing answers to.

Shape: 1813 paths / 2479 operations -> 192 api modules + 2460 model modules,
all 2659 importable.
2026-08-14 11:01:13 -07:00
zeekayandHanzo Dev 3a09eb4319 regenerate from cloud@5126939 — the client leaves the hand-merged lineage
pkg/hanzoai/cloud goes +2186 -2126 ~175: 1700 paths / 2354 operations / 2186
schemas, 182 api modules and 2172 model modules, against the 239/1116 it carried
from hanzo.yaml. The document is hanzoai/cloud's own emission now, pinned in a
new .spec-lock by commit and sha256, and generate.py reaches it by value —
--skip-validate-spec means the 1012 missing-`responses` errors no longer write
zero files, so a client can be cut from the authority instead of a projection of
it.

Two renamings come with that, and both are the fix rather than the damage. IAM's
types are namespace-qualified (iam.Role, iam.Application, 95 of them) because a
bare `Role` was two unrelated shapes wearing one name. And the <svc>_ prefix is
gone from every operationId, so every generated method lost it.

The prefix is what broke the examples, and the gate saw only two thirds of it.
Four flows failed on their imports; `money` and `tools` PASSED while every call
in them named a method that no longer existed — an import resolves the names in
the `from … import` line and a method is looked up at call time. All five flows
now name operations that exist, checked by resolving each one as an attribute,
and hanzo.yml records that ceiling so the next reader does not trust the gate
for more than it says.

`chat` is removed, which is a measurement and not a preference: cloud declares
POST /v1/chat/completions with no requestBody and no responses at all, so the
generated method takes no body and returns None — the one call a chat example
exists to make cannot be expressed. Inventing the type, or hand-rolling the HTTP
inside a generated client, is the drift these SDKs exist to prevent; js-sdk
dropped its own chat flow at 2.0.7 for exactly this. It returns the release
cloud gives that route a body.

Two smoke assertions were already red before this regeneration and are now true
again: they pinned AIApi/APIKeysApi/MCPApi and AdminApi.plugin_admin_*, names
from the retired lineage. The surface is all still there under AiApi/KeysApi/
McpApi and AdminApi.admin_plugins.

tests/test_zap_transport.py still errors, exactly as it did before: it imports
`Hanzo` from hanzoai, which the hand-written package does not define. That is
not this document's business and is untouched.

Minor rather than patch: every generated method changed name.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 11:00:03 -07:00
zeekayandHanzo Dev ccc14a5dd2 kms.lux.cloud is the brand host; kms.lux.network is retired
Caught on a second pass: grep -I treats these as binary and skipped them, so the
first sweep reported clean while they still named a host that no longer resolves.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 08:12:55 -07:00
zeekayandHanzo Dev bcffc56ee3 pypi: one canonical hanzo, and superseded packages that say where the real one is
Three published packages declared a console script named `hanzo`, competing with
the native CLI and with each other. Measured on a clean venv at hanzo==0.4.3:
`hanzo --help` printed hanzo-cli's program, not the one pkg/hanzo/README.md
documents. Renamed to `hanzo-py` and `hanzo-cli` — script named after its
distribution. Nothing is yanked; both packages still install and run.

- pkg/hanzo 0.4.4 — summary and README now say the CLI is a native binary
  (curl -fsSL https://hanzo.sh | sh). Dropped the invented command tour
  (`hanzo chat --model gpt-4`, `hanzo node start`, `hanzo router start` on
  localhost:4000): none of those verbs exist in the program this package
  installs. Kept the three library entry points, each import-checked.
- pkg/hanzo-cli 0.2.4 — `hanzo login` replaced with the native `hanzo auth login`
  plus its own `hanzo-cli login`. Verified `hanzo iam users list` and
  `hanzo kms secrets list` against the shipped v1.9.18 binary.
- pkg/hanzo-node 0.1.1 — states the two meanings of the name: the `hanzo-node`
  COMMAND is a symlink to the Hanzo CLI; this package fetches a different binary
  from hanzoai/node, which is private, so the download 404s for the public.
  Documentation URL moved off docs.hanzo.ai/node (404) to hanzo.sh.
- root README (published as `hanzoai`) — `pip install hanzo` no longer advertised
  as the CLI; model ids zen-coder -> zen5-coder (zen-coder is not in the catalog);
  "2452 operations, 1798 schemas" dropped for the spec URL, since the live
  surface is 1058 paths / 1465 operations / 1139 schemas.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 13:35:20 -07:00
zeekayandHanzo Dev cd441d2362 regen the cloud client, add the six canonical flows, and gate them in CI
REGENERATED pkg/hanzoai/cloud/ from hanzo.yaml @ 07783f5 via the canonical
driver (hanzoai/openapi generate.py python) — 1132 paths, 1519 operations, 779
schemas, landing as 239 api + 1116 model modules. `generate.py python --check`
reports `[python] clean`: the committed tree is byte-identical to a fresh
generation, with no local strip of any kind.

The path count fell from 1885 because the spec deleted 18 products it authored
and served nowhere. Nothing here was lost to a collapse — LLM.md carried an open
defect saying 127 of 411 operations went missing to 23 case-variant tag groups
(AI/ai, Users/users). That is now fixed upstream, and this regeneration proves
it: 239 distinct tags produce 239 api modules, exactly 1:1.

Generating at all required two spec fixes. Both landed in hanzoai/openapi first,
neither is patched here:
  - fc0c17a  35 /v1/platform operations carried no `responses`. OAS 3.x requires
             it and openapi-generator aborts the whole document, so hanzo.yaml
             was producing no client in ANY language.
  - 07783f5  ChatCompletionResponse.choices was `items: {type: object}` — so
             choices[0].message.content came out List[object], unusable without
             a cast, on the most-called route in the API.

SIX EXAMPLES under examples/{hello,chat,money,store,agent,tools}, plus
examples/client.py as the single place a base URL or an env var is resolved.
Same six, same names, same order as the TypeScript set, so a reader who knows
one can navigate the other. They import from hanzoai.cloud — the generated
surface new work targets — not the frozen pkg/hanzoai/{api,models}.

Each flow's call sits behind `if __name__ == "__main__":` deliberately: that is
what lets the gate IMPORT all six to prove every `from hanzoai.cloud import X`
still resolves, with no API key and no socket. A spec change that renames or
drops an operation goes red in CI instead of in a user's app.

CI is the fleet convention and this repo had none: root hanzo.yml + a 7-line
cicd.yml importing hanzoai/ci. The gate is two blocks — import every generated
module (for generated code that IS the build step; there is no compiler to catch
a bad $ref or a model referencing a class the generator declined to emit), then
import the six flows. Both provision an interpreter with uv, because the arc
runner image promises none and a gate that silently no-ops is worse than none.

Scope is deliberate: the cloud client and its flows, not all 65 packages, so a
red gate means "the client the spec just produced is broken" rather than
"something, somewhere". Publishing is untouched — .hanzo/workflows/publish-pypi.yml
stays canonical because it reads the PyPI token from KMS. Two publish paths is
one too many.

3.1.4 -> 3.1.5. PyPI still serves 3.1.1; the tree has been ahead since the KMS
secret at hanzo/prod/python-sdk-publish went unseeded.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-30 23:10:34 -07:00
zeekayandHanzo Dev 22b00e135a sdk: regen — the KMS org left the URL, so 3.1.2's secrets routes were dead
Regenerated pkg/hanzoai/cloud from hanzo.yaml. Consumed: cloud 8143fc0e,
openapi 2861089 (was f581a0e when 3.1.2 was cut).

openapi 3300cda took {org} out of the KMS secrets contract — the org is read from
the token now — and 3.1.2 shipped from before it, so its KMS methods addressed a
path the server no longer serves:

    /v1/kms/orgs/{org}/secrets         -> /v1/kms/secrets
    /v1/kms/orgs/{org}/secrets/{rest}  -> /v1/kms/secrets/{rest}
    kms_get_v1_kms_orgs_org_secrets    -> kms_get_v1_kms_secrets
    kms_post_v1_kms_orgs_org_secrets   -> kms_post_v1_kms_secrets

Five request/response models renamed with them. Nothing else in the 1885-path
surface moved: 5 models added, 5 removed, 6 files changed.

3.1.2 never reached PyPI (the tag cannot get to git.hanzo.ai — sync-from-github
fast-forwards main only, never tags), so this is fix-forward, not a replacement.
PyPI still serves 3.1.1; 3.1.2 stays a tag.

The upstream tag-casing collision came back exactly as LLM.md said it would: the
regen rmtree'd the tree and `import hanzoai.cloud` raised ImportError again on
AiApi/ApiKeysApi/McpApi. Stripped the same 9 dead lines. It will return on every
regen until one tag spelling per service lands in the per-service specs — that
also recovers the 127 of 411 operations those 23 colliding groups still drop.

No test pins the KMS route: asserting spec content here would make this repo a
second source of truth for routes and fight the pull-only contract. test_smoke
pins structure instead — import, version, surface breadth. 6 passed.

Version 3.1.2 -> 3.1.3, patch only.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 10:34:39 -07:00
zeekayandHanzo Dev 915b2701c9 sdk: ship the generated cloud client — /v1/admin/plugins is reachable
pkg/hanzoai/cloud/ (2163 files) has existed on disk untracked and never shipped.
This commits it, so the operator surface cloud already serves is finally callable:

    AdminApi.plugin_admin_plugins / _enable_plugin / _disable_plugin / _reload_plugin

Generated by hanzoai/openapi `generate.py python` from hanzo.yaml (1885 paths,
69 merged per-service specs). Consumed: cloud 8143fc0e, openapi f581a0e.

One driver, so delete the second one. scripts/generate.sh fetched the spec itself
and then did `rm -rf pkg/hanzoai` + copied generator output over it — which would
have deleted the hand-written config/mcp/protocols/session/zap/api_response
modules AND the new cloud/ tree. Nothing referenced it. hanzoai/openapi's
generate.py + sdks.yaml is the only way now; LLM.md records the pipeline.

`import hanzoai.cloud` raised ImportError as generated. hanzo.yaml has 23 tag
groups differing only by case; openapi-generator maps both spellings to one
module, and for AI/ai, API Keys/api-keys and MCP/mcp it then emits imports for
classes it never wrote (AiApi vs AIApi). Stripped those 9 dead lines. The same
collision silently drops 127 of the 411 operations in those groups — that one is
upstream and only a single tag spelling per service fixes it; noted in LLM.md.

Version: 3.1.1 -> 3.1.2, patch only. pkg/hanzoai/__init__.py said 1.0.0 (the
generator default) — now resolved from the installed distribution, same pattern
as hanzo_cli/hanzo_iam in f453dae8. uv.lock refreshed to match committed pkg
versions. test_smoke.py locks the cloud import, the four plugin ops, and that
__version__ equals the distribution: 6 passed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 10:27:07 -07:00
zeekayandHanzo Dev c80140810a kms: migrate hanzo-kms off Infisical paths onto the luxfi/kms surface
kms.hanzo.ai runs luxfi/kms, which has never served an /api/* route. This
SDK was sending Infisical's: /api/v3/secrets/raw for every secret operation,
/api/v3/auth/login for user auth, /api/v1/auth/kubernetes-auth/login for k8s.
Only one call in the whole package — /v1/kms/auth/login — was ever real.

It stayed invisible because older luxfi/kms builds embedded a console SPA
behind a root catch-all that answered every unmatched path with 200
text/html. A wrong URL came back as a JSON decode error, which reads like a
parsing bug in the client rather than "this endpoint does not exist." That
catch-all is gone (cmd/kms/main.go ends in notFoundJSON), so those paths now
return honest JSON 404s and the SDK fails outright.

The route table is not the only thing that was wrong. The data model was
Infisical's too — project_id, workspace, secret version, secret comment,
shared-vs-personal type — and luxfi/kms has none of those concepts. It keys
one value by (org, path, name, env) in ZapDB at kms/secrets/{path}/{env}/{name}
and a write upserts it in place. Half-migrating would have left response
models that cannot validate what the server sends, so the vocabulary moves
with the paths:

  list_secrets(path, env) -> names   GET  /v1/kms/orgs/{org}/secrets
  get_secret(path, name, env)        GET  /v1/kms/orgs/{org}/secrets/{path}/{name}
  put_secret(path, name, value, env) POST /v1/kms/orgs/{org}/secrets   (create AND replace)
  delete_secret(path, name, env)     DELETE .../secrets/{path}/{name}
  health()                           GET  /v1/kms/healthz

Two server behaviors now have one home, hanzo_kms/routes.py, shared by the
sync and async clients so they stay mirror images:

  - The server splits the trailing path at its LAST slash into (path, name),
    so each segment is escaped individually. Escaping the joined string
    encodes the separators away and the server reads one long name. A name
    containing "/" is rejected outright: it would be written under one key
    and read back under another, so the write looks like it succeeded and
    the read never finds it.
  - There is no versioned read. get_secret(version=N) raises
    VersionUnsupportedError rather than quietly returning the current value.

org is new and required — it scopes both the URL and the JWT owner claim.
Constructor field, HANZO_KMS_ORG, defaults to "hanzo".

Auth collapses to what the server actually offers: client credentials
exchanged at /v1/kms/auth/login, or a pre-issued IAM bearer token. The AWS,
Azure, GCP, Kubernetes and SRP methods were Infisical's and none were served.
That also fixes a divergence where the async client silently ignored
HANZO_KMS_TOKEN — env parsing is now one function.

Callers migrated with it: hanzo-cli's `hanzo kms`, the hanzo-tools-kms MCP
tool, and hanzo-tools-auth's session client, all of which were building the
removed auth models and calling the removed create/update pair. Each now
constructs KMSClient() and lets it read the environment.

Tests: pkg/hanzo-kms/tests pins the wire shape — no request URL may contain
"/api/", per-segment escaping survives the server's last-slash split, sync
and async emit byte-identical request lines, and a decoder rejects the
200-HTML/JSON-404 shapes instead of reading them as empty results. Wired into
hanzo-packages-ci without `|| true`, so a regression turns CI red instead of
waiting for a decode error in production.

hanzo-kms 1.1.0 -> 1.1.1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 08:29:59 -07:00
zeekay bfafe0f020 docs: modernize + LLM.md + cross-links (one-way SDK model) 2026-07-24 12:29:42 -07:00
zooqueen 6244bbd1cd docs: tidy LLM.md content (remove heading prefix) 2026-06-19 17:46:14 -07:00
248e5e0ea9 fix(browser-mcp): migrate CDP bridge → zapd consumer; disk authoritative (#35)
* 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]>
2026-06-16 22:12:07 -07:00
zeekay 90e6e4f0d9 feat(tools-browser): ZAP-native 2-process architecture (0.5.0)
Each hanzo-mcp now hosts a ZAP server directly on the lowest free port
from [9999..9995], using a POSIX flock for cross-MCP arbitration. The
browser extension discovers and connects to this server without any
node-side bridge in the critical path.

- New hanzo_tools/browser/zap_server.py: wire format, server, lifecycle,
  client registry, leases, cluster-visibility config registry.
- _extension_command tries ZAP first (sub-1ms median round-trip on
  loopback), falls back to legacy HTTP bridge on :9224 only when no
  ZAP-connected client matches. Pinnable via BROWSER_TRANSPORT.
- New tool actions: list_mcp_instances, claim_browser, release_browser
  for sub-agent coordination across multiple parallel MCPs.
- Legacy CDP HTTP bridge no longer auto-starts (opt in with
  HANZO_CDP_BRIDGE_ENABLED=1). It remains available for non-ZAP MCP
  clients.

Tests: 30 new unit tests cover wire format, port arbitration,
multi-MCP coexistence, lease semantics, and cluster registry.
Bench shows 0.35ms median round-trip on local loopback.

Total tests: 4 baseline -> 35 passing (30 zap_server + 1 bench + 4 imports).
2026-05-07 21:32:43 -07:00
zeekay 5bc70da951 docs: add LLM.md project guide 2026-03-11 10:14:51 -07:00