Files
python-sdk/pkg/hanzo-tools-shell/hanzo_tools/shell/npx_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

"""NPX 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 NpxTool(BaseBinaryTool):
"""Tool for running npx commands."""
name = "npx"
@property
@override
def description(self) -> str:
"""Get the tool description."""
return """Run npx packages with automatic backgrounding for long-running processes.
Commands that run for more than 2 minutes will automatically continue in the background.
Usage:
npx create-react-app my-app
npx http-server -p 8080 # Auto-backgrounds after 2 minutes
npx prettier --write "**/*.js"
npx json-server db.json # Auto-backgrounds if needed"""
@override
def get_binary_name(self) -> str:
"""Get the binary name."""
return "npx"
@override
async def run(
self,
ctx: MCPContext,
package: str,
args: str = "",
cwd: Optional[str] = None,
yes: bool = True,
) -> str:
"""Run an npx command with auto-backgrounding."""
work_dir = Path(cwd).resolve() if cwd else Path.cwd()
flags = []
if yes:
flags.append("-y")
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 npx(
ctx: MCPContext,
package: str,
args: str = "",
cwd: Optional[str] = None,
yes: bool = True,
) -> str:
return await tool_self.run(ctx, package=package, args=args, cwd=cwd, yes=yes)
@auto_timeout("npx")
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"),
yes=params.get("yes", True),
)
# Create tool instance
npx_tool = NpxTool()