fix: lint — ruff format and fix for hanzo-tools-ui and hanzo-mcp

This commit is contained in:
2026-03-27 20:00:21 -07:00
parent 7767986eea
commit 7e47625094
12 changed files with 115 additions and 121 deletions
@@ -116,9 +116,7 @@ class SQLiteBackendPlugin:
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_memories_namespace ON memories(namespace);"
)
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_memories_key ON memories(key);"
)
conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_key ON memories(key);")
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_memories_ns_key ON memories(namespace, key);"
)
@@ -473,7 +471,9 @@ class SQLiteBackendPlugin:
self._conn.commit()
return True
async def history(self, key: str, namespace: str = "default") -> List[Dict[str, Any]]:
async def history(
self, key: str, namespace: str = "default"
) -> List[Dict[str, Any]]:
"""Show all versions/entries for a key within a namespace, ordered by creation time."""
if not self._conn:
raise RuntimeError("Plugin not initialized")
+3 -1
View File
@@ -226,7 +226,9 @@ class PluginMemoryService:
plugin = self._get_plugin()
return await plugin.namespaces()
async def history(self, key: str, namespace: str = "default") -> List[Dict[str, Any]]:
async def history(
self, key: str, namespace: str = "default"
) -> List[Dict[str, Any]]:
"""Show all versions of a key."""
await self._ensure_init()
plugin = self._get_plugin()
+7 -7
View File
@@ -15,18 +15,18 @@ import time
from typing import Any, Awaitable, Callable
from zap.protocol import (
HEADER_SIZE,
MAX_MESSAGE_SIZE,
MSG_HANDSHAKE,
MSG_HANDSHAKE_OK,
MSG_PING,
MSG_PONG,
MSG_REQUEST,
MSG_RESPONSE,
ZAP_PORTS,
HEADER_SIZE,
MAX_MESSAGE_SIZE,
ZAP_MAGIC,
encode,
ZAP_PORTS,
decode,
encode,
)
logger = logging.getLogger(__name__)
@@ -88,9 +88,7 @@ class ZapServer:
for t in self.tools
]
async def _read_frame(
self, reader: asyncio.StreamReader
) -> tuple[int, Any] | None:
async def _read_frame(self, reader: asyncio.StreamReader) -> tuple[int, Any] | None:
"""Read a single ZAP frame from the stream."""
try:
header = await reader.readexactly(HEADER_SIZE)
@@ -102,6 +100,7 @@ class ZapServer:
msg_type = header[4]
import struct
length = struct.unpack("!L", header[5:9])[0]
if length > MAX_MESSAGE_SIZE:
@@ -113,6 +112,7 @@ class ZapServer:
return None
import json
try:
payload = json.loads(payload_bytes.decode("utf-8"))
return (msg_type, payload)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "hanzo-mcp"
version = "0.15.0"
version = "0.15.1"
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"
+9 -3
View File
@@ -11,8 +11,8 @@ import pytest
@pytest.fixture
async def memory_service():
"""Create a PluginMemoryService with in-memory SQLite for testing."""
from hanzo_mcp.memory_service import PluginMemoryService
from hanzo_mcp.backends.sqlite_plugin import SQLiteBackendPlugin
from hanzo_mcp.memory_service import PluginMemoryService
svc = PluginMemoryService()
# Use true in-memory db — each test gets a fresh database
@@ -152,7 +152,11 @@ class TestKeyBasedRetrieval:
content="line1", metadata={}, key="append-key", namespace="test"
)
await memory_service.store_memory(
content="\nline2", metadata={}, key="append-key", namespace="test", append=True
content="\nline2",
metadata={},
key="append-key",
namespace="test",
append=True,
)
result = await memory_service.get_by_key(key="append-key", namespace="test")
@@ -176,7 +180,9 @@ class TestTagsSupport:
async def test_list_memories_filter_by_tag(self, memory_service):
await memory_service.store_memory(content="a", metadata={}, tags=["blue"])
await memory_service.store_memory(content="b", metadata={}, tags=["red"])
await memory_service.store_memory(content="c", metadata={}, tags=["blue", "red"])
await memory_service.store_memory(
content="c", metadata={}, tags=["blue", "red"]
)
results = await memory_service.list_memories(tag="blue")
assert len(results) == 2
@@ -184,9 +184,7 @@ class GitHubAPIClient:
else:
raise RuntimeError(f"GitHub API error: {resp.status_code}")
async def get_raw_content(
self, owner: str, repo: str, path: str, branch: str
) -> str:
async def get_raw_content(self, owner: str, repo: str, path: str, branch: str) -> str:
url = f"{GITHUB_RAW_BASE}/{owner}/{repo}/{branch}/{path}"
cached = self._cache_get(url)
if cached is not None:
@@ -220,9 +218,7 @@ class GitHubAPIClient:
config.owner, config.repo, index_path, config.branch
)
except FileNotFoundError:
raise FileNotFoundError(
f"Component '{name}' not found in {framework} repository"
)
raise FileNotFoundError(f"Component '{name}' not found in {framework} repository")
async def fetch_component_demo(self, name: str, framework: str = "hanzo") -> str:
config = FRAMEWORK_CONFIGS[framework]
@@ -246,6 +242,7 @@ class GitHubAPIClient:
path = f"{config.components_path}/{name}/metadata.json"
try:
import json
content = await self.get_raw_content(config.owner, config.repo, path, config.branch)
return json.loads(content)
except (FileNotFoundError, Exception):
@@ -271,9 +268,7 @@ class GitHubAPIClient:
config.owner, config.repo, index_path, config.branch
)
except FileNotFoundError:
raise FileNotFoundError(
f"Block '{name}' not found in {framework} repository"
)
raise FileNotFoundError(f"Block '{name}' not found in {framework} repository")
async def list_components(self, framework: str = "hanzo") -> list[dict]:
config = FRAMEWORK_CONFIGS[framework]
@@ -306,9 +301,7 @@ class GitHubAPIClient:
async def get_directory_structure(self, path: str, framework: str = "hanzo") -> dict:
config = FRAMEWORK_CONFIGS[framework]
contents = await self.get_directory_contents(
config.owner, config.repo, path, config.branch
)
contents = await self.get_directory_contents(config.owner, config.repo, path, config.branch)
children = []
for item in contents:
entry = {"name": item["name"], "type": item["type"], "path": item["path"]}
@@ -80,17 +80,21 @@ class LocalUIClient:
if entry.name.startswith("index"):
continue
if entry.is_dir():
components.append({
"name": entry.name,
"type": "dir",
"path": str(entry.relative_to(self._ui_path)),
})
components.append(
{
"name": entry.name,
"type": "dir",
"path": str(entry.relative_to(self._ui_path)),
}
)
elif entry.is_file() and entry.suffix in COMPONENT_EXTENSIONS:
components.append({
"name": entry.stem,
"type": "file",
"path": str(entry.relative_to(self._ui_path)),
})
components.append(
{
"name": entry.stem,
"type": "file",
"path": str(entry.relative_to(self._ui_path)),
}
)
return components
async def fetch_component(self, name: str, framework: str = "hanzo") -> str:
@@ -279,12 +283,14 @@ class LocalUIClient:
name = entry.stem if entry.is_file() else entry.name
if query_lower in name.lower():
results.append({
"name": name,
"type": "dir" if entry.is_dir() else "file",
"path": str(entry.relative_to(self._ui_path)),
"match": "name",
})
results.append(
{
"name": name,
"type": "dir" if entry.is_dir() else "file",
"path": str(entry.relative_to(self._ui_path)),
"match": "name",
}
)
# Also search src modules
src_dir = self._ui_path / "pkg" / "ui" / "src"
@@ -294,11 +300,13 @@ class LocalUIClient:
continue
name = entry.stem if entry.is_file() else entry.name
if query_lower in name.lower() and name not in [r["name"] for r in results]:
results.append({
"name": name,
"type": "dir" if entry.is_dir() else "file",
"path": str(entry.relative_to(self._ui_path)),
"match": "name",
})
results.append(
{
"name": name,
"type": "dir" if entry.is_dir() else "file",
"path": str(entry.relative_to(self._ui_path)),
"match": "name",
}
)
return results
@@ -140,17 +140,13 @@ class RegistryCache:
# Metadata
try:
entry.metadata = await self._github.fetch_component_metadata(
name, framework
)
entry.metadata = await self._github.fetch_component_metadata(name, framework)
except Exception:
pass
# Demo
try:
entry.demo = await self._github.fetch_component_demo(
name, framework
)
entry.demo = await self._github.fetch_component_demo(name, framework)
except Exception:
pass
@@ -175,9 +171,7 @@ class RegistryCache:
b_entry = ComponentEntry(name=name, type=block.get("type", "file"))
try:
async with sem:
b_entry.source = await self._github.fetch_block(
name, framework
)
b_entry.source = await self._github.fetch_block(name, framework)
except Exception:
pass
fw_data.blocks[name] = b_entry
@@ -260,9 +254,7 @@ class RegistryCache:
entry = self._fw(framework).components.get(name)
return entry.demo if entry else None
def get_component_metadata(
self, name: str, framework: str = "hanzo"
) -> dict | None:
def get_component_metadata(self, name: str, framework: str = "hanzo") -> dict | None:
entry = self._fw(framework).components.get(name)
return entry.metadata if entry else None
@@ -287,14 +279,10 @@ class RegistryCache:
or q in entry.description.lower()
or q in entry.category.lower()
):
results.append(
{"name": entry.name, "type": entry.type, "match": "name"}
)
results.append({"name": entry.name, "type": entry.type, "match": "name"})
return results
def get_structure(
self, path: str, framework: str = "hanzo"
) -> dict[str, Any] | None:
def get_structure(self, path: str, framework: str = "hanzo") -> dict[str, Any] | None:
return self._fw(framework).directory_cache.get(path)
def build_index(self, framework: str = "hanzo") -> dict:
@@ -29,9 +29,7 @@ class RegistryClient:
def __init__(self, base_url: str | None = None):
self._base_url = (
base_url
or os.environ.get("HANZO_UI_REGISTRY_URL")
or DEFAULT_REGISTRY_URL
base_url or os.environ.get("HANZO_UI_REGISTRY_URL") or DEFAULT_REGISTRY_URL
).rstrip("/")
self._client: httpx.AsyncClient | None = None
self._cache: dict[str, tuple[Any, float]] = {}
@@ -130,7 +128,7 @@ class RegistryClient:
try:
data = await self._get("/api/registry/components.json")
except Exception:
data = await self._get("/api/registry", {"type": f"components:ui"})
data = await self._get("/api/registry", {"type": "components:ui"})
return data.get("components", [])
async def fetch_component(self, name: str, framework: str = "hanzo") -> str:
@@ -160,9 +158,7 @@ class RegistryClient:
raise FileNotFoundError(f"Demo for '{name}' not available")
return source
async def fetch_component_metadata(
self, name: str, framework: str = "hanzo"
) -> dict:
async def fetch_component_metadata(self, name: str, framework: str = "hanzo") -> dict:
try:
data = await self._get(f"/api/registry/components/{name}.json")
except Exception:
@@ -183,9 +179,7 @@ class RegistryClient:
async def fetch_block(self, name: str, framework: str = "hanzo") -> str:
return await self.fetch_component(name, framework)
async def search_components(
self, query: str, framework: str = "hanzo"
) -> list[dict]:
async def search_components(self, query: str, framework: str = "hanzo") -> list[dict]:
try:
# Try server-mode search
data = await self._get("/api/registry/search", {"q": query})
@@ -208,16 +202,12 @@ class RegistryClient:
if q in item.get("n", "").lower()
]
async def get_directory_structure(
self, path: str, framework: str = "hanzo"
) -> dict:
async def get_directory_structure(self, path: str, framework: str = "hanzo") -> dict:
# Not available as static — return component list as structure
comps = await self.list_components(framework)
return {
"path": path or "/",
"children": [
{"name": c["name"], "type": "file"} for c in comps
],
"children": [{"name": c["name"], "type": "file"} for c in comps],
}
async def fetch_full_index(self, framework: str = "hanzo") -> dict:
@@ -9,14 +9,13 @@ Run:
uvicorn hanzo_tools.ui.registry.server:app --port 8787
"""
import asyncio
import logging
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, ORJSONResponse
from fastapi.responses import JSONResponse
from .cache import RegistryCache
@@ -105,9 +104,7 @@ async def get_component_demo(name: str, framework: str = Query(default="hanzo"))
cache = get_cache()
demo = cache.get_component_demo(name, framework)
if demo is None:
raise HTTPException(
404, detail={"error": "Demo not found", "name": name}
)
raise HTTPException(404, detail={"error": "Demo not found", "name": name})
return {"name": name, "demo": demo}
@@ -146,15 +143,11 @@ async def search_components(
@app.get("/api/structure")
async def get_structure(
path: str = Query(default=""), framework: str = Query(default="hanzo")
):
async def get_structure(path: str = Query(default=""), framework: str = Query(default="hanzo")):
cache = get_cache()
structure = cache.get_structure(path, framework)
if structure is None:
raise HTTPException(
404, detail={"error": "Path not found in cache", "path": path}
)
raise HTTPException(404, detail={"error": "Path not found in cache", "path": path})
return structure
+13 -4
View File
@@ -24,7 +24,6 @@ Actions:
import asyncio
import logging
import os
from typing import ClassVar
from mcp.server.fastmcp import Context as MCPContext
@@ -131,7 +130,9 @@ Frameworks: hanzo (default), hanzo-native, hanzo-vue, hanzo-svelte,
) -> dict:
fw = framework or _current_framework
if fw not in FRAMEWORK_CONFIGS:
return {"error": f"Unknown framework: {fw}. Available: {', '.join(FRAMEWORK_CONFIGS)}"}
return {
"error": f"Unknown framework: {fw}. Available: {', '.join(FRAMEWORK_CONFIGS)}"
}
if self._use_local(fw):
components = await self._local.list_components(fw)
@@ -491,7 +492,9 @@ Frameworks: hanzo (default), hanzo-native, hanzo-vue, hanzo-svelte,
@self.action("list_packages", "List all UI packages (local only)")
async def list_packages(ctx: MCPContext) -> dict:
if not self._local.available:
return {"error": "Local hanzo/ui repo not found. Set HANZO_UI_PATH or clone to ~/work/hanzo/ui"}
return {
"error": "Local hanzo/ui repo not found. Set HANZO_UI_PATH or clone to ~/work/hanzo/ui"
}
packages = await self._local.list_packages()
return {
@@ -511,7 +514,9 @@ Frameworks: hanzo (default), hanzo-native, hanzo-vue, hanzo-svelte,
return {"error": "File path is required (relative to hanzo/ui root)"}
if not self._local.available:
return {"error": "Local hanzo/ui repo not found. Set HANZO_UI_PATH or clone to ~/work/hanzo/ui"}
return {
"error": "Local hanzo/ui repo not found. Set HANZO_UI_PATH or clone to ~/work/hanzo/ui"
}
content = await self._local.read_file(file_path)
return {
@@ -533,6 +538,7 @@ Frameworks: hanzo (default), hanzo-native, hanzo-vue, hanzo-svelte,
return {"error": "Question is required"}
from .vector_index import chat_about_components
return await chat_about_components(q)
@self.action("semantic_search", "Semantic search over UI components (Hanzo Cloud)")
@@ -548,17 +554,20 @@ Frameworks: hanzo (default), hanzo-native, hanzo-vue, hanzo-svelte,
return {"error": "Query is required"}
from .vector_index import search_components
results = await search_components(q, limit=limit, tags=tags)
return {"query": q, "results": results}
@self.action("index_status", "Check search index status")
async def index_status(ctx: MCPContext) -> dict:
from .vector_index import get_index_stats
return await get_index_stats()
@self.action("rebuild_index", "Re-index UI components into Hanzo Cloud search")
async def rebuild_index(ctx: MCPContext) -> dict:
from .vector_index import index_from_local, index_from_registry
if self._local.available:
return await index_from_local()
return await index_from_registry()
@@ -8,7 +8,6 @@ Uses the Hanzo Cloud RAG pipeline:
Authentication: publishable key (pk-*) for read, API key (hk-*) for write.
"""
import json
import logging
import os
from typing import Any
@@ -146,15 +145,17 @@ async def index_components(
if source:
content_parts.append(f"\nSource code:\n{source}")
documents.append({
"id": f"component-{name}",
"page_id": name,
"title": name.replace("-", " ").title(),
"url": f"https://ui.hanzo.ai/components/{name}",
"content": "\n".join(content_parts),
"section": comp_type.split(":")[-1] if ":" in comp_type else "ui",
"tag": comp_type,
})
documents.append(
{
"id": f"component-{name}",
"page_id": name,
"title": name.replace("-", " ").title(),
"url": f"https://ui.hanzo.ai/components/{name}",
"content": "\n".join(content_parts),
"section": comp_type.split(":")[-1] if ":" in comp_type else "ui",
"tag": comp_type,
}
)
admin_key = ADMIN_KEY or os.environ.get("HANZO_API_KEY", "")
if not admin_key:
@@ -204,11 +205,13 @@ async def index_from_local(ui_path: str | None = None) -> dict:
name = comp["name"]
try:
source = await local.fetch_component(name)
components.append({
"name": name,
"type": comp.get("type", "file"),
"source": source,
})
components.append(
{
"name": name,
"type": comp.get("type", "file"),
"source": source,
}
)
except Exception as e:
logger.debug("Skipping %s: %s", name, e)
@@ -244,13 +247,15 @@ async def index_from_registry(registry_url: str | None = None) -> dict:
if isinstance(first, dict):
source = first.get("content", "")
components.append({
"name": name,
"type": data.get("type", ""),
"source": source,
"dependencies": data.get("dependencies", []),
"registryDependencies": data.get("registryDependencies", []),
})
components.append(
{
"name": name,
"type": data.get("type", ""),
"source": source,
"dependencies": data.get("dependencies", []),
"registryDependencies": data.get("registryDependencies", []),
}
)
await client.close()