Files
python-sdk/pkg/hanzo-tools-shell/hanzo_tools/shell/uvx_tool.py
T
zeekay d5f8e3f2b5 feat(hanzo-tools): add modular tool packages with 53 tools across 13 packages
Introduces hanzo-tools-* namespace packages for independent tool installation:

Core packages:
- hanzo-tools-core: BaseTool, ToolRegistry, PermissionManager
- hanzo-tools-filesystem: read, write, edit, tree, find, search, ast (7 tools)
- hanzo-tools-shell: dag, ps, zsh, shell, npx, uvx, open (7 tools)

AI/Agent packages:
- hanzo-tools-agent: critic, iching, review, network, CLI agents (12 tools)
- hanzo-tools-memory: recall/create/update/delete memories, facts, KB (9 tools)
- hanzo-tools-reasoning: think, critic (2 tools)

Editor/Dev packages:
- hanzo-tools-lsp: Language Server Protocol tool (1 tool)
- hanzo-tools-refactor: rename, extract, inline, move, change_signature (1 tool)
- hanzo-tools-database: SQL + graph database tools (8 tools)
- hanzo-tools-editor: neovim integration (3 tools)
- hanzo-tools-jupyter: notebook read/edit (1 tool)
- hanzo-tools-browser: Playwright browser automation (1 tool)
- hanzo-tools-todo: unified todo management (1 tool)

Key improvements:
- All subprocess.run calls have timeout protection
- All tool .call() methods are async
- Namespace package pattern for mix-and-match installation
- Entry point discovery via hanzo.tools
- No blocking imports (all < 2s)

Also updates hanzo-mcp:
- Browser tool integration
- Tool install/registry for dynamic tool management
- Auto-background timeout reduced to 60s
2025-12-25 22:38:26 -08:00

91 lines
2.4 KiB
Python

"""UVX tool for both sync and background execution."""
from typing import Optional, override
from pathlib import Path
from mcp.server import FastMCP
from mcp.server.fastmcp import Context as MCPContext
from hanzo_tools.core import auto_timeout
from hanzo_tools.shell.base_process import BaseBinaryTool
class UvxTool(BaseBinaryTool):
"""Tool for running uvx commands."""
name = "uvx"
@property
@override
def description(self) -> str:
"""Get the tool description."""
return """Run Python packages with uvx with automatic backgrounding for long-running processes.
Commands that run for more than 2 minutes will automatically continue in the background.
Usage:
uvx ruff check .
uvx mkdocs serve # Auto-backgrounds after 2 minutes
uvx black --check src/
uvx jupyter lab --port 8888 # Auto-backgrounds if needed"""
@override
def get_binary_name(self) -> str:
"""Get the binary name."""
return "uvx"
@override
async def run(
self,
ctx: MCPContext,
package: str,
args: str = "",
cwd: Optional[str] = None,
python: Optional[str] = None,
) -> str:
"""Run a uvx command with auto-backgrounding."""
work_dir = Path(cwd).resolve() if cwd else Path.cwd()
flags = []
if python:
flags.extend(["--python", python])
full_args = args.split() if args else []
return await self.execute_sync(
package,
cwd=work_dir,
flags=flags,
args=full_args,
timeout=None,
)
def register(self, server: FastMCP) -> None:
"""Register the tool with the MCP server."""
tool_self = self
@server.tool(name=self.name, description=self.description)
async def uvx(
ctx: MCPContext,
package: str,
args: str = "",
cwd: Optional[str] = None,
python: Optional[str] = None,
) -> str:
return await tool_self.run(ctx, package=package, args=args, cwd=cwd, python=python)
@auto_timeout("uvx")
async def call(self, ctx: MCPContext, **params) -> str:
"""Call the tool with arguments."""
return await self.run(
ctx,
package=params["package"],
args=params.get("args", ""),
cwd=params.get("cwd"),
python=params.get("python"),
)
# Create tool instance
uvx_tool = UvxTool()