refactor(hanzo-mcp): clean up ZAP server bridge

This commit is contained in:
2026-03-13 16:52:56 -07:00
parent 205cc38299
commit e30fa43710
3 changed files with 25 additions and 60 deletions
+20 -59
View File
@@ -3,72 +3,34 @@
Allows Hanzo browser extensions and agents to discover this MCP server
and call tools directly over the native ZAP binary protocol.
Wire format: 9-byte header + payload
[0x5A 0x41 0x50 0x01] magic "ZAP\x01"
[type] 1 byte message type
[length] 4 bytes big-endian payload length
[payload] UTF-8 JSON (Cap'n Proto in future)
Transport: Raw TCP on ports 9999-9995 (first available).
Uses the ``zap-protocol`` package for wire format and transport,
adding hanzo-mcp-specific request routing on top.
"""
from __future__ import annotations
import asyncio
import json
import logging
import struct
import time
from typing import Any, Awaitable, Callable
from zap.protocol import (
MSG_HANDSHAKE,
MSG_HANDSHAKE_OK,
MSG_PING,
MSG_PONG,
MSG_REQUEST,
MSG_RESPONSE,
ZAP_PORTS,
HEADER_SIZE,
MAX_MESSAGE_SIZE,
ZAP_MAGIC,
encode,
decode,
)
logger = logging.getLogger(__name__)
# ── Protocol Constants ──────────────────────────────────────────────────
ZAP_MAGIC = b"\x5a\x41\x50\x01" # "ZAP\x01"
MSG_HANDSHAKE = 0x01
MSG_HANDSHAKE_OK = 0x02
MSG_REQUEST = 0x10
MSG_RESPONSE = 0x11
MSG_STREAM = 0x20
MSG_PING = 0xFE
MSG_PONG = 0xFF
ZAP_PORTS = [9999, 9998, 9997, 9996, 9995]
HEADER_SIZE = 9 # 4 (magic) + 1 (type) + 4 (length)
MAX_MESSAGE_SIZE = 16 * 1024 * 1024 # 16 MB
# ── Encode / Decode ─────────────────────────────────────────────────────
def encode(msg_type: int, payload: Any) -> bytes:
"""Encode a ZAP message: magic + type + length + JSON payload."""
data = json.dumps(payload).encode("utf-8")
header = ZAP_MAGIC + struct.pack("!BL", msg_type, len(data))
return header + data
def decode(buf: bytes) -> tuple[int, Any] | None:
"""Decode a ZAP message from a complete frame.
Expects exactly one ZAP frame: [magic:4][type:1][length:4 BE][JSON].
"""
if len(buf) < HEADER_SIZE:
return None
if buf[:4] != ZAP_MAGIC:
return None
msg_type = buf[4]
length = struct.unpack("!L", buf[5:9])[0]
if len(buf) < HEADER_SIZE + length:
return None
try:
payload = json.loads(buf[HEADER_SIZE : HEADER_SIZE + length].decode("utf-8"))
return (msg_type, payload)
except (json.JSONDecodeError, UnicodeDecodeError):
return None
# ── Client tracking ─────────────────────────────────────────────────────
@@ -139,6 +101,7 @@ class ZapServer:
return None
msg_type = header[4]
import struct
length = struct.unpack("!L", header[5:9])[0]
if length > MAX_MESSAGE_SIZE:
@@ -149,6 +112,7 @@ class ZapServer:
except (asyncio.IncompleteReadError, ConnectionError):
return None
import json
try:
payload = json.loads(payload_bytes.decode("utf-8"))
return (msg_type, payload)
@@ -240,12 +204,9 @@ class ZapServer:
result = await self.call_tool(tool_name, args)
elif method.startswith("notifications/"):
# Client -> server notifications (fire-and-forget)
result = {"acknowledged": True}
else:
# Pass-through to MCP server for full protocol parity
# (resources/list, resources/read, prompts/list, prompts/get, etc.)
if self.handle_method:
result = await self.handle_method(method, params)
else:
@@ -270,7 +231,7 @@ class ZapServer:
ports = (
[preferred_port, *[p for p in ZAP_PORTS if p != preferred_port]]
if preferred_port
else ZAP_PORTS
else list(ZAP_PORTS)
)
for port in ports:
+2
View File
@@ -17,6 +17,8 @@ classifiers = [
]
keywords = ["mcp", "claude", "hanzo", "code", "agent"]
dependencies = [
# ZAP native protocol
"zap-protocol>=0.3.0",
# Core MCP dependencies
"mcp>=1.25.0",
"fastmcp>=2.14.4",
+3 -1
View File
@@ -1291,7 +1291,7 @@ wheels = [
[[package]]
name = "hanzo-mcp"
version = "0.13.1"
version = "0.14.0"
source = { editable = "." }
dependencies = [
{ name = "fastmcp" },
@@ -1340,6 +1340,7 @@ dev = [
{ name = "types-aiofiles" },
{ name = "types-psutil" },
{ name = "types-setuptools" },
{ name = "watchdog" },
]
docs = [
{ name = "myst-parser" },
@@ -1449,6 +1450,7 @@ requires-dist = [
{ name = "typing-extensions", specifier = ">=4.13.0" },
{ name = "ujson", marker = "extra == 'performance'", specifier = ">=5.7.0" },
{ name = "uvloop", marker = "sys_platform != 'win32'", specifier = ">=0.22.1" },
{ name = "watchdog", marker = "extra == 'dev'", specifier = ">=3.0.0" },
{ name = "websockets", marker = "extra == 'ide'", specifier = ">=12.0" },
]
provides-extras = ["memory", "repl", "ide", "interactive", "dev", "docs", "test", "performance", "publish"]