style: Fix import sorting and formatting in hanzo-mcp

Run ruff check --fix and ruff format on pkg/hanzo-mcp
This commit is contained in:
2025-12-09 14:03:15 -08:00
parent 428728232c
commit 857fd3b928
127 changed files with 300 additions and 707 deletions
+4 -12
View File
@@ -198,18 +198,12 @@ async def run_agent_grinding_demo():
# Show the key benefits
print("\n=== Key Benefits of Agent Grinding ===")
print(
"1. Parallel Processing: Multiple agents work on different files simultaneously"
)
print(
"2. Full Context: Each agent has all tools - can read, search, understand before editing"
)
print("1. Parallel Processing: Multiple agents work on different files simultaneously")
print("2. Full Context: Each agent has all tools - can read, search, understand before editing")
print("3. Smart Filtering: Agents skip files that don't need changes")
print("4. Precise Edits: Agents make surgical changes based on understanding")
print("5. Scalable: Add more agents to handle larger codebases")
print(
"6. Flexible: Different patterns (pipeline, parallel, consensus) for different tasks"
)
print("6. Flexible: Different patterns (pipeline, parallel, consensus) for different tasks")
# Show example swarm command
print("\n=== Example Swarm Command ===")
@@ -231,9 +225,7 @@ swarm_tool(
print("\nThe agents will:")
print("- finder: Search and categorize all FIXMEs")
print("- grinders: Work in parallel on different directories")
print(
"- Each grinder: Read file → Understand context → Fix if needed → Move to next"
)
print("- Each grinder: Read file → Understand context → Fix if needed → Move to next")
print("- reviewer: Ensure all fixes are appropriate")
@@ -191,9 +191,7 @@ async def demonstrate_performance_gains():
speedup = sequential_total / parallel_total
print(f"\nSPEEDUP: {speedup:.1f}x faster!")
print(
f"Time saved: {sequential_total - parallel_total}s ({(sequential_total - parallel_total) / 60:.1f} minutes)"
)
print(f"Time saved: {sequential_total - parallel_total}s ({(sequential_total - parallel_total) / 60:.1f} minutes)")
print("\n" + "=" * 60)
print("KEY INSIGHTS:")
+2 -6
View File
@@ -69,9 +69,7 @@ def show_usage_examples():
print("\n1. Claude Code CLI:")
print(" claude_cli(prompts='Fix the type errors in main.py')")
print(
" claude_cli(prompts='Refactor this function', model='claude-3-opus-20240229')"
)
print(" claude_cli(prompts='Refactor this function', model='claude-3-opus-20240229')")
print("\n2. OpenAI Codex CLI:")
print(" codex_cli(prompts='Generate unit tests for the Calculator class')")
@@ -79,9 +77,7 @@ def show_usage_examples():
print("\n3. Google Gemini CLI:")
print(" gemini_cli(prompts='Create a REST API with FastAPI')")
print(
" gemini_cli(prompts='Analyze security vulnerabilities', model='gemini-1.5-flash')"
)
print(" gemini_cli(prompts='Analyze security vulnerabilities', model='gemini-1.5-flash')")
print("\n4. xAI Grok CLI:")
print(" grok_cli(prompts='Explain this regex pattern')")
@@ -133,16 +133,12 @@ Work with the file_agent to read code files.""",
# Example 2: Read and analyze
print("\n📖 Example 2: Read and analyze a file")
result = await network.run(
prompt="Read the pyproject.toml file and tell me what this project is about"
)
result = await network.run(prompt="Read the pyproject.toml file and tell me what this project is about")
print(f"Response: {result['final_output']}")
# Example 3: Multi-agent collaboration
print("\n🤝 Example 3: Multi-agent collaboration")
result = await network.run(
prompt="Find all Python files that might contain the main entry point and analyze them"
)
result = await network.run(prompt="Find all Python files that might contain the main entry point and analyze them")
print(f"Response: {result['final_output']}")
print(f"Agents used: {result['iterations']}")
@@ -107,9 +107,7 @@ IMPORTANT: Use multi_edit for efficiency - add all imports in one operation!
ctx = type("Context", (), {})() # Mock context for example
start_time = asyncio.get_event_loop().time()
result = await self.swarm.call(
ctx, tasks=tasks, max_concurrency=max_concurrency
)
result = await self.swarm.call(ctx, tasks=tasks, max_concurrency=max_concurrency)
end_time = asyncio.get_event_loop().time()
# Parse and display results
@@ -140,9 +138,7 @@ IMPORTANT: Use multi_edit for efficiency - add all imports in one operation!
print(f" Sequential estimate: {sequential_estimate:.1f}s")
print(f" Parallel actual: {parallel_time:.1f}s")
print(f" Speedup: {speedup:.1f}x")
print(
f" Efficiency: {(speedup / min(max_concurrency, len(tasks))) * 100:.1f}%"
)
print(f" Efficiency: {(speedup / min(max_concurrency, len(tasks))) * 100:.1f}%")
async def main():
+5 -17
View File
@@ -166,11 +166,7 @@ class TestModels(unittest.TestCase):
],
)
print(
analysis_result[:1000] + "..."
if len(analysis_result) > 1000
else analysis_result
)
print(analysis_result[:1000] + "..." if len(analysis_result) > 1000 else analysis_result)
print("\n" + "=" * 60)
print("PHASE 2: Parallel editing with swarm")
@@ -225,9 +221,7 @@ class TestModels(unittest.TestCase):
max_concurrent=2,
)
print(
review_result[:1500] + "..." if len(review_result) > 1500 else review_result
)
print(review_result[:1500] + "..." if len(review_result) > 1500 else review_result)
print("\n" + "=" * 60)
print("PHASE 4: Final verification with batch")
@@ -240,22 +234,16 @@ class TestModels(unittest.TestCase):
invocations=[
{
"tool_name": "read",
"input": {
"file_path": os.path.join(test_dir, "src/models/user.py")
},
"input": {"file_path": os.path.join(test_dir, "src/models/user.py")},
},
{
"tool_name": "agent",
"input": {
"prompts": f"Verify that all files in {test_dir}/src/models/ now have proper type hints"
},
"input": {"prompts": f"Verify that all files in {test_dir}/src/models/ now have proper type hints"},
},
],
)
print(
verify_result[:1000] + "..." if len(verify_result) > 1000 else verify_result
)
print(verify_result[:1000] + "..." if len(verify_result) > 1000 else verify_result)
print("\n" + "=" * 60)
print("TEST COMPLETE!")
@@ -124,9 +124,7 @@ class HanzoMCPSwarmLauncher:
# Code Generator Agent
def generate_code(spec: str) -> str:
return (
f"Generated code for: {spec}\ndef example():\n return 'Hello World'"
)
return f"Generated code for: {spec}\ndef example():\n return 'Hello World'"
generator_agent = create_local_agent(
name="generator_agent",
@@ -227,14 +225,10 @@ class HanzoMCPSwarmLauncher:
print(f"\n🎯 Coordinating swarms for: {complex_task}")
# Phase 1: Analysis
analysis_result = await self.launch_swarm(
"analysis", f"Analyze the codebase for: {complex_task}"
)
analysis_result = await self.launch_swarm("analysis", f"Analyze the codebase for: {complex_task}")
# Phase 2: Development based on analysis
dev_result = await self.launch_swarm(
"development", f"Based on analysis, implement: {complex_task}"
)
dev_result = await self.launch_swarm("development", f"Based on analysis, implement: {complex_task}")
return {"analysis": analysis_result, "development": dev_result}
@@ -263,9 +257,7 @@ async def main():
# Demo 1: Simple swarm launch
print("\n\n📋 Demo 1: Simple Analysis Swarm")
await launcher.launch_swarm(
"analysis", "Find all authentication-related code and analyze security"
)
await launcher.launch_swarm("analysis", "Find all authentication-related code and analyze security")
# Demo 2: Development swarm
print("\n\n📋 Demo 2: Development Swarm")
@@ -278,9 +270,7 @@ async def main():
# Show final statistics
print("\n\n📊 Session Statistics:")
print(f"Total swarms launched: {len(launcher.networks)}")
print(
f"Total agents created: {sum(len(n.agents) for n in launcher.networks.values())}"
)
print(f"Total agents created: {sum(len(n.agents) for n in launcher.networks.values())}")
print("Inference engine: hanzo/net (local)")
print("External API calls: 0")
print("Privacy: 100% on-device execution")
+1 -5
View File
@@ -48,11 +48,7 @@ async def main():
name="code_analyzer",
description="Analyzes code using distributed inference",
system="You are powered by hanzo/net distributed inference. Analyze code efficiently.",
tools=[
create_tool(
name="analyze_code", description="Analyze code", handler=analyze_code
)
],
tools=[create_tool(name="analyze_code", description="Analyze code", handler=analyze_code)],
local_model="llama3.2", # Will use hanzo/net
)
+8 -24
View File
@@ -60,9 +60,7 @@ async def main():
search_agent = create_agent(
name="search_agent",
description="Searches through codebase",
model=ModelConfig(
provider=ModelProvider.LOCAL, model="llama3.2", temperature=0.3
),
model=ModelConfig(provider=ModelProvider.LOCAL, model="llama3.2", temperature=0.3),
system="""You are a code search specialist. Use the search_code tool to find patterns in code.
Be precise and helpful in locating code elements.""",
tools=[
@@ -78,9 +76,7 @@ Be precise and helpful in locating code elements.""",
analyzer_agent = create_agent(
name="analyzer",
description="Analyzes code structure and quality",
model=ModelConfig(
provider=ModelProvider.LOCAL, model="llama3.2", temperature=0.5
),
model=ModelConfig(provider=ModelProvider.LOCAL, model="llama3.2", temperature=0.5),
system="""You are a code analysis expert. Use your tools to analyze functions and provide insights.
Focus on code quality, performance, and best practices.""",
tools=[
@@ -96,9 +92,7 @@ Focus on code quality, performance, and best practices.""",
test_agent = create_agent(
name="test_generator",
description="Generates unit tests",
model=ModelConfig(
provider=ModelProvider.LOCAL, model="llama3.2", temperature=0.7
),
model=ModelConfig(provider=ModelProvider.LOCAL, model="llama3.2", temperature=0.7),
system="""You are a test generation specialist. Create comprehensive unit tests for given code.
Follow pytest conventions and ensure good coverage.""",
tools=[
@@ -114,9 +108,7 @@ Follow pytest conventions and ensure good coverage.""",
teacher_agent = create_agent(
name="teacher",
description="Explains programming concepts",
model=ModelConfig(
provider=ModelProvider.LOCAL, model="llama3.2", temperature=0.8
),
model=ModelConfig(provider=ModelProvider.LOCAL, model="llama3.2", temperature=0.8),
system="""You are a programming teacher. Explain concepts clearly and provide examples.
Make complex topics accessible to learners.""",
tools=[
@@ -158,23 +150,17 @@ Make complex topics accessible to learners.""",
# Test 2: Code Analysis
print("\n📊 Test 2: Code Analysis")
result = await network.run(
prompt="Analyze the add function implementation", initial_agent=analyzer_agent
)
result = await network.run(prompt="Analyze the add function implementation", initial_agent=analyzer_agent)
print(f"Result: {result['final_output']}")
# Test 3: Test Generation
print("\n🧪 Test 3: Test Generation")
result = await network.run(
prompt="Generate tests for an add function", initial_agent=test_agent
)
result = await network.run(prompt="Generate tests for an add function", initial_agent=test_agent)
print(f"Result: {result['final_output']}")
# Test 4: Concept Explanation
print("\n📚 Test 4: Concept Explanation")
result = await network.run(
prompt="Explain what recursion is", initial_agent=teacher_agent
)
result = await network.run(prompt="Explain what recursion is", initial_agent=teacher_agent)
print(f"Result: {result['final_output']}")
# Test 5: Multi-Agent Collaboration
@@ -187,9 +173,7 @@ Make complex topics accessible to learners.""",
# Test 6: Complex Query
print("\n🎯 Test 6: Complex Development Task")
result = await network.run(
prompt="I need help understanding and testing a binary search implementation"
)
result = await network.run(prompt="I need help understanding and testing a binary search implementation")
print(f"Result: {result['final_output']}")
print("\n✅ Local agent demo complete!")
+4 -12
View File
@@ -115,13 +115,9 @@ async def main():
coder = create_agent(
name="coder",
description="Code writing assistant using MLX",
model=ModelConfig(
provider=ModelProvider.LOCAL, model="mlx", temperature=0.3, max_tokens=200
),
model=ModelConfig(provider=ModelProvider.LOCAL, model="mlx", temperature=0.3, max_tokens=200),
system="You are a code writing assistant. Write clean, efficient code.",
tools=[
create_tool(name="write_code", description="Write code", handler=write_code)
],
tools=[create_tool(name="write_code", description="Write code", handler=write_code)],
)
# Create network
@@ -137,9 +133,7 @@ async def main():
# Test 1: Simple question
print("\n💬 Test 1: Simple Question")
result = await network.run(
prompt="What is machine learning?", initial_agent=assistant
)
result = await network.run(prompt="What is machine learning?", initial_agent=assistant)
print(f"Response: {result['final_output']}")
# Test 2: Summarization
@@ -152,9 +146,7 @@ async def main():
# Test 3: Code generation
print("\n💻 Test 3: Code Generation")
result = await network.run(
prompt="Write a Python function to calculate factorial", initial_agent=coder
)
result = await network.run(prompt="Write a Python function to calculate factorial", initial_agent=coder)
print(f"Response: {result['final_output']}")
# Test 4: Multi-agent
+1 -3
View File
@@ -113,9 +113,7 @@ def main():
print("=" * 60)
# Check for API key
has_key = bool(
os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("CLAUDE_API_KEY")
)
has_key = bool(os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("CLAUDE_API_KEY"))
if has_key:
print("\n✅ Claude API key detected!")
print(" In real usage, actual Claude agents would edit the files.")
+1 -3
View File
@@ -38,9 +38,7 @@ async def main():
# Check LLM status
ollama_status = await check_local_llm_status("ollama")
print(
f"\n📡 Ollama status: {'✅ Available' if ollama_status['available'] else '❌ Not available'}"
)
print(f"\n📡 Ollama status: {'✅ Available' if ollama_status['available'] else '❌ Not available'}")
# Create AI agents
analyzer = create_local_agent(
+4 -12
View File
@@ -80,17 +80,13 @@ async def main():
# Test with tool-aware prompt
messages_with_tools = [
Message(
role="system", content="You are a helpful assistant with access to tools."
),
Message(role="system", content="You are a helpful assistant with access to tools."),
Message(role="user", content="Calculate 100 divided by 4"),
]
tools = [{"name": "calculate", "description": "Calculate mathematical expressions"}]
response = await provider.generate(
messages_with_tools, model="llama3.2", tools=tools
)
response = await provider.generate(messages_with_tools, model="llama3.2", tools=tools)
print("\nTool-aware response:")
print(f"Output: {response['output'][0]['content']}")
@@ -100,9 +96,7 @@ async def main():
print("✅ All inference done locally via hanzo/net")
print("✅ No external API calls made")
print("✅ Using distributed inference engine: dummy")
print(
"\nNote: In production, this would use MLX (Apple Silicon) or Tinygrad engines"
)
print("\nNote: In production, this would use MLX (Apple Silicon) or Tinygrad engines")
print(" with actual model weights loaded locally.")
# Test 5: Concurrent inference
@@ -111,9 +105,7 @@ async def main():
async def run_inference(prompt: str, id: int):
"""Run a single inference."""
start = asyncio.get_event_loop().time()
response = await provider.generate(
[Message(role="user", content=prompt)], model="llama3.2"
)
response = await provider.generate([Message(role="user", content=prompt)], model="llama3.2")
end = asyncio.get_event_loop().time()
return {
"id": id,
+6 -18
View File
@@ -28,18 +28,14 @@ async def demo_unified_search():
if result.data and "results" in result.data:
print(f" Found {len(result.data['results'])} results")
print(
f" Search types used: {', '.join(result.data['statistics']['search_types_used'])}"
)
print(f" Search types used: {', '.join(result.data['statistics']['search_types_used'])}")
for r in result.data["results"][:3]:
print(f" - {r['file']}:{r['line']} ({r['type']})")
print()
# Demo 2: Natural language query
print("2. Natural language query:")
result = await search.run(
pattern="how does pagination work in search results", max_results_per_type=3
)
result = await search.run(pattern="how does pagination work in search results", max_results_per_type=3)
if result.data:
stats = result.data["statistics"]
@@ -63,9 +59,7 @@ async def demo_unified_search():
# Demo 4: File search
print("4. Find Python test files:")
result = await search.run(
pattern="test_*.py", search_files=True, max_results_per_type=5
)
result = await search.run(pattern="test_*.py", search_files=True, max_results_per_type=5)
if result.data:
stats = result.data["statistics"]
@@ -85,9 +79,7 @@ async def demo_unified_search():
if result.data:
# Should find symbol definitions
symbol_results = [
r for r in result.data["results"] if r.get("context", {}).get("node_type")
]
symbol_results = [r for r in result.data["results"] if r.get("context", {}).get("node_type")]
if symbol_results:
print(f" Found {len(symbol_results)} symbol matches")
else:
@@ -101,9 +93,7 @@ async def demo_unified_search():
if result.data and "pagination" in result.data:
pag = result.data["pagination"]
print(f" Page {pag['page']} of {pag['total_pages']}")
print(
f" Showing {len(result.data['results'])} of {pag['total_results']} total results"
)
print(f" Showing {len(result.data['results'])} of {pag['total_results']} total results")
print(f" Has next page: {pag['has_next']}")
print()
@@ -112,9 +102,7 @@ async def demo_unified_search():
result = await search.run(pattern="import", include="*.py", max_results_per_type=5)
if result.data:
print(
f" Found {len(result.data['results'])} import statements in Python files"
)
print(f" Found {len(result.data['results'])} import statements in Python files")
stats = result.data["statistics"]
print(f" Search completed in {stats['time_ms'].get('text', 0)}ms")
print()
@@ -23,9 +23,7 @@ class LocalAgentSwarm:
# Code Scanner Agent
def scan_files(pattern: str) -> str:
return (
f"Scanned files matching '{pattern}': Found 15 Python files, 8 JS files"
)
return f"Scanned files matching '{pattern}': Found 15 Python files, 8 JS files"
self.agents["scanner"] = create_local_agent(
name="scanner",
@@ -43,9 +41,7 @@ class LocalAgentSwarm:
# Vulnerability Detector Agent
def detect_vulnerabilities(code: str) -> str:
return (
"Found 2 potential issues: SQL injection risk, missing input validation"
)
return "Found 2 potential issues: SQL injection risk, missing input validation"
self.agents["detector"] = create_local_agent(
name="detector",
@@ -197,9 +193,7 @@ class LocalAgentSwarm:
yes_count = sum(1 for v in votes.values() if "yes" in v.lower())
no_count = len(votes) - yes_count
print(
f"\n🗳️ Consensus: {'YES' if yes_count > no_count else 'NO'} ({yes_count} yes, {no_count} no)"
)
print(f"\n🗳️ Consensus: {'YES' if yes_count > no_count else 'NO'} ({yes_count} yes, {no_count} no)")
return votes
@@ -16,7 +16,7 @@ import traceback
from typing import Any, Dict, TypeVar, Callable, Optional
from datetime import datetime
from dataclasses import dataclass
from importlib.metadata import version, PackageNotFoundError
from importlib.metadata import PackageNotFoundError, version
# Try to import PostHog, but make it optional
try:
@@ -183,11 +183,12 @@ class Analytics:
def _get_package_version(self) -> str:
"""Get the current package version."""
try:
return version('hanzo-mcp')
return version("hanzo-mcp")
except PackageNotFoundError:
# Fallback to hardcoded version if package not installed
try:
from hanzo_mcp import __version__
return __version__
except ImportError:
return "0.8.14"
+10 -6
View File
@@ -15,11 +15,13 @@ import argparse
from typing import Any, cast
from pathlib import Path
# Import timeout parser (deferred to avoid early imports)
def _parse_timeout_arg(timeout_str: str) -> float:
"""Parse timeout argument with human-readable format support."""
try:
from hanzo_mcp.tools.common.timeout_parser import parse_timeout
return parse_timeout(timeout_str)
except ImportError:
# Fallback if parser not available
@@ -169,7 +171,8 @@ def main() -> None:
)
_ = parser.add_argument(
"--timeout", "-t",
"--timeout",
"-t",
dest="tool_timeout",
type=str,
help="Default timeout for MCP tool operations (default: 2min). Supports: 2min, 5m, 120s, 30sec, 1.5h",
@@ -288,19 +291,19 @@ def main() -> None:
command_timeout = _parse_timeout_arg(str(args.command_timeout))
# Set timeout environment variables from CLI args
if hasattr(args, 'tool_timeout') and args.tool_timeout:
if hasattr(args, "tool_timeout") and args.tool_timeout:
tool_timeout = _parse_timeout_arg(args.tool_timeout)
os.environ["HANZO_MCP_TOOL_TIMEOUT"] = str(tool_timeout)
if hasattr(args, 'search_timeout') and args.search_timeout:
if hasattr(args, "search_timeout") and args.search_timeout:
search_timeout = _parse_timeout_arg(args.search_timeout)
os.environ["HANZO_MCP_SEARCH_TIMEOUT"] = str(search_timeout)
if hasattr(args, 'find_timeout') and args.find_timeout:
if hasattr(args, "find_timeout") and args.find_timeout:
find_timeout = _parse_timeout_arg(args.find_timeout)
os.environ["HANZO_MCP_FIND_TIMEOUT"] = str(find_timeout)
if hasattr(args, 'ast_timeout') and args.ast_timeout:
if hasattr(args, "ast_timeout") and args.ast_timeout:
ast_timeout = _parse_timeout_arg(args.ast_timeout)
os.environ["HANZO_MCP_AST_TIMEOUT"] = str(ast_timeout)
@@ -382,8 +385,9 @@ def main() -> None:
# Ensure only one daemon runs per socket
try:
import fcntl
lock_file = f"{socket_path}.lock"
with open(lock_file, 'w') as f:
with open(lock_file, "w") as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except (ImportError, IOError):
# Fallback for systems without fcntl or if lock fails
+2 -2
View File
@@ -3,8 +3,8 @@
import os
import atexit
import signal
import secrets
import logging
import secrets
import warnings
import threading
from typing import Literal, cast, final
@@ -83,7 +83,7 @@ class HanzoMCPServer:
self.mcp = mcp_instance if mcp_instance is not None else EnhancedFastMCP(name)
# Initialize authentication token
self.auth_token = auth_token or os.environ.get('HANZO_MCP_TOKEN')
self.auth_token = auth_token or os.environ.get("HANZO_MCP_TOKEN")
if not self.auth_token:
# Generate a secure random token if none provided
self.auth_token = secrets.token_urlsafe(32)
@@ -6,8 +6,6 @@ enabling concurrent execution of multiple operations and specialized processing.
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
# Import unified CLI tools (single source of truth)
@@ -29,6 +27,7 @@ from hanzo_mcp.tools.agent.cli_tools import (
from hanzo_mcp.tools.agent.agent_tool import AgentTool
from hanzo_mcp.tools.agent.network_tool import NetworkTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.agent.code_auth_tool import CodeAuthTool
@@ -81,8 +80,6 @@ def register_agent_tools(
self._agent = agent_tool
@auto_timeout("__init__")
async def call(self, ctx, **params): # type: ignore[override]
# Default to 5 agents unless explicitly provided
params = dict(params)
+1 -4
View File
@@ -30,8 +30,6 @@ from pydantic import Field
from openai.types.chat import ChatCompletionMessageParam
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
from hanzo_mcp.tools.filesystem import get_read_only_filesystem_tools
from hanzo_mcp.tools.common.base import BaseTool
@@ -46,6 +44,7 @@ from hanzo_mcp.tools.common.context import (
)
from hanzo_mcp.tools.common.batch_tool import BatchTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Parameter types
Action = Annotated[
@@ -215,8 +214,6 @@ Modes:
@override
@auto_timeout("agent")
async def call(
self,
ctx: MCPContext,
@@ -19,8 +19,6 @@ with warnings.catch_warnings():
import litellm
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from openai.types.chat import ChatCompletionToolParam, ChatCompletionMessageParam
from mcp.server.fastmcp import Context as MCPContext
@@ -45,6 +43,7 @@ from hanzo_mcp.tools.agent.tool_adapter import (
convert_tools_to_openai_functions,
)
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.agent.clarification_tool import ClarificationTool
from hanzo_mcp.tools.agent.clarification_protocol import (
ClarificationType,
@@ -180,8 +179,6 @@ Usage notes:
@override
@auto_timeout("agent_tool_v1_deprecated")
async def call(
self,
ctx: MCPContext,
@@ -5,8 +5,8 @@ from typing import Any, Dict, List, Optional, override
from mcp.server import FastMCP
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class ClarificationTool(BaseTool):
@@ -1,5 +1,5 @@
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
"""Claude Desktop authentication management.
This module provides tools to automate Claude Desktop login/logout,
@@ -451,8 +451,6 @@ claude_auth ensure_agent swarm_agent_1"""
self.auth = ClaudeDesktopAuth()
@auto_timeout("claude_desktop_auth")
async def call(self, ctx, action: str = "status", **kwargs) -> str:
"""Execute auth action."""
tool_ctx = create_tool_context(ctx)
@@ -13,11 +13,10 @@ from typing import List, Optional
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class CLIAgentBase(BaseTool):
@@ -169,8 +168,6 @@ class CLIAgentBase(BaseTool):
return f"Error: {str(e)}"
@auto_timeout("cli_agent_base")
async def call(self, ctx: MCPContext, prompts: str, **kwargs) -> str:
"""Execute the CLI agent.
@@ -13,13 +13,12 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Parameter types for CLI tools
Prompt = Annotated[
@@ -189,8 +188,6 @@ class ClaudeCLITool(BaseCLITool):
return "Execute Claude CLI for AI assistance using Anthropic's models"
@auto_timeout("cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
prompt: str = params.get("prompt", "")
model: Optional[str] = params.get("model") or self.default_model
@@ -242,8 +239,6 @@ class CodexCLITool(BaseCLITool):
return "Execute OpenAI Codex/GPT-4 CLI for code generation and AI assistance"
@auto_timeout("cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
prompt: str = params.get("prompt", "")
model: Optional[str] = params.get("model") or self.default_model
@@ -283,8 +278,6 @@ class GeminiCLITool(BaseCLITool):
return "Execute Google Gemini CLI for multimodal AI assistance"
@auto_timeout("cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
prompt: str = params.get("prompt", "")
model: Optional[str] = params.get("model") or self.default_model
@@ -324,8 +317,6 @@ class GrokCLITool(BaseCLITool):
return "Execute xAI Grok CLI for real-time AI assistance"
@auto_timeout("cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
prompt: str = params.get("prompt", "")
model: Optional[str] = params.get("model") or self.default_model
@@ -365,8 +356,6 @@ class OpenHandsCLITool(BaseCLITool):
return "Execute OpenHands (OpenDevin) for autonomous coding assistance"
@auto_timeout("cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
prompt = params.get("prompt", "")
model = params.get("model") or self.default_model
@@ -418,8 +407,6 @@ class HanzoDevCLITool(BaseCLITool):
return "Execute Hanzo Dev for AI-powered code editing and development"
@auto_timeout("cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
prompt = params.get("prompt", "")
model = params.get("model") or self.default_model
@@ -459,8 +446,6 @@ class ClineCLITool(BaseCLITool):
return "Execute Cline for autonomous coding with Claude"
@auto_timeout("cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
prompt = params.get("prompt", "")
working_dir: str = params.get("working_dir") or os.getcwd()
@@ -497,8 +482,6 @@ class AiderCLITool(BaseCLITool):
return "Execute Aider for AI pair programming"
@auto_timeout("cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
prompt = params.get("prompt", "")
model = params.get("model") or self.default_model
@@ -6,13 +6,12 @@ This tool manages API keys and accounts for Claude Code and other AI coding tool
from typing import Unpack, Optional, TypedDict, final, override
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.agent.code_auth import CodeAuthManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class CodeAuthParams(TypedDict, total=False):
@@ -70,8 +69,6 @@ Providers: claude, openai, azure, deepseek, google, groq"""
@override
@auto_timeout("code_auth")
async def call(
self,
ctx: MCPContext,
@@ -6,8 +6,8 @@ from typing import List, Optional, override
from mcp.server import FastMCP
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class ReviewType(Enum):
@@ -5,11 +5,10 @@ from enum import Enum
from typing import List, override
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class HanzoPrinciple(Enum):
@@ -602,8 +601,6 @@ Use this when you need:
self.oracle = IChing()
@auto_timeout("iching")
async def call(self, ctx: MCPContext, challenge: str) -> str:
"""Cast I Ching and provide guidance."""
# Cast hexagram
@@ -18,12 +18,11 @@ from typing import (
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Import hanzo cluster if available
try:
@@ -107,8 +106,6 @@ class NetworkTool(BaseTool):
@override
@auto_timeout("network")
async def call(self, ctx: MCPContext, **params: Unpack[NetworkToolParams]) -> str:
"""Execute a task on the agent network.
@@ -6,8 +6,8 @@ from typing import List, Optional, override
from mcp.server import FastMCP
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class ReviewFocus(Enum):
@@ -1,5 +1,5 @@
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
"""Swarm tool as an alias to Network tool for backward compatibility.
This module makes swarm an alias to the network tool, as network is the
@@ -66,8 +66,6 @@ For new code, prefer using 'network' directly."""
super().__init__(permission_manager=permission_manager, default_mode=default_mode, **kwargs)
@auto_timeout("swarm_alias")
async def call(self, **kwargs) -> str:
"""Execute swarm via network tool.
@@ -18,14 +18,13 @@ from typing import (
)
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.agent.agent_tool import AgentTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class AgentNode(TypedDict):
@@ -206,8 +205,6 @@ Models can be specified as:
@override
@auto_timeout("swarm_tool_v1_deprecated")
async def call(
self,
ctx: MCPContext,
@@ -11,9 +11,9 @@ from typing import Any, Dict, List, Optional
from pathlib import Path
from mcp.server import FastMCP
from mcp.server.fastmcp import Context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context
from ..common.base import BaseTool
from ...core.base_agent import CLIAgent, AgentConfig
@@ -145,8 +145,6 @@ class UnifiedCLITool(BaseTool, CLIAgent):
return command
@auto_timeout("unified_cli_tools")
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
"""Execute the CLI tool via MCP interface.
@@ -4,29 +4,30 @@ This module provides automatic timeout and backgrounding for any MCP tool operat
that takes longer than the configured threshold (default: 2 minutes).
"""
import asyncio
import functools
import os
import json
import time
import uuid
import os
import asyncio
import functools
from typing import Any, Tuple, Callable, Optional
from pathlib import Path
from typing import Any, Callable, Optional, Tuple
from collections.abc import Awaitable
from mcp.server.fastmcp import Context as MCPContext
from .timeout_parser import parse_timeout, format_timeout
class MCPToolTimeoutManager:
"""Manager for MCP tool timeouts and backgrounding."""
# Default timeout before auto-backgrounding (2 minutes)
DEFAULT_TIMEOUT = 120.0
# Environment variable to configure timeout
TIMEOUT_ENV_VAR = "HANZO_MCP_TOOL_TIMEOUT"
def __init__(self, process_manager: Optional[Any] = None):
"""Initialize the timeout manager.
@@ -37,13 +38,14 @@ class MCPToolTimeoutManager:
# Lazy import to avoid circular imports
try:
from hanzo_mcp.tools.shell.base_process import ProcessManager
self.process_manager = ProcessManager()
except ImportError:
# If ProcessManager is not available, disable backgrounding
self.process_manager = None
else:
self.process_manager = process_manager
# Get timeout from environment or use default
env_timeout = os.getenv(self.TIMEOUT_ENV_VAR)
if env_timeout:
@@ -53,13 +55,13 @@ class MCPToolTimeoutManager:
self.timeout = self.DEFAULT_TIMEOUT
else:
self.timeout = self.DEFAULT_TIMEOUT
def _get_timeout_for_tool(self, tool_name: str) -> float:
"""Get timeout setting for a specific tool.
Args:
tool_name: Name of the tool
Returns:
Timeout in seconds
"""
@@ -71,20 +73,14 @@ class MCPToolTimeoutManager:
return parse_timeout(tool_timeout)
except ValueError:
pass
return self.timeout
async def _background_tool_execution(
self,
tool_func: Callable,
tool_name: str,
ctx: MCPContext,
process_id: str,
log_file: Path,
**params: Any
self, tool_func: Callable, tool_name: str, ctx: MCPContext, process_id: str, log_file: Path, **params: Any
) -> None:
"""Execute tool in background and log results.
Args:
tool_func: The tool function to execute
tool_name: Name of the tool
@@ -99,10 +95,10 @@ class MCPToolTimeoutManager:
f.write(f"=== Background execution started for {tool_name} ===\\n")
f.write(f"Parameters: {json.dumps(params, indent=2, default=str)}\\n")
f.write(f"Started at: {time.strftime('%Y-%m-%d %H:%M:%S')}\\n\\n")
# Execute the tool
result = await tool_func(ctx, **params)
# Log completion
with open(log_file, "a") as f:
f.write(f"\\n\\n=== Tool execution completed ===\\n")
@@ -111,10 +107,10 @@ class MCPToolTimeoutManager:
f.write("\\n=== RESULT ===\\n")
f.write(str(result))
f.write("\\n=== END RESULT ===\\n")
# Mark as completed
self.process_manager.mark_completed(process_id, 0)
except Exception as e:
# Log error
with open(log_file, "a") as f:
@@ -122,7 +118,7 @@ class MCPToolTimeoutManager:
f.write(f"Failed at: {time.strftime('%Y-%m-%d %H:%M:%S')}\\n")
f.write(f"Error: {str(e)}\\n")
f.write(f"Error type: {type(e).__name__}\\n")
self.process_manager.mark_completed(process_id, 1)
@@ -201,6 +197,7 @@ def with_auto_timeout(tool_name: str, timeout_manager: Optional[MCPToolTimeoutMa
)
return wrapper
return decorator
@@ -210,7 +207,7 @@ _global_timeout_manager = None
def get_global_timeout_manager() -> MCPToolTimeoutManager:
"""Get the global timeout manager instance.
Returns:
Global timeout manager
"""
@@ -222,7 +219,7 @@ def get_global_timeout_manager() -> MCPToolTimeoutManager:
def set_global_timeout(timeout_seconds: float) -> None:
"""Set the global timeout for all MCP tools.
Args:
timeout_seconds: Timeout in seconds
"""
@@ -232,7 +229,7 @@ def set_global_timeout(timeout_seconds: float) -> None:
def set_tool_timeout(tool_name: str, timeout_seconds: float) -> None:
"""Set timeout for a specific tool via environment variable.
Args:
tool_name: Name of the tool
timeout_seconds: Timeout in seconds
@@ -244,11 +241,11 @@ def set_tool_timeout(tool_name: str, timeout_seconds: float) -> None:
# Convenience decorator using global manager
def auto_timeout(tool_name: str):
"""Convenience decorator using the global timeout manager.
Args:
tool_name: Name of the tool
Returns:
Decorator function
"""
return with_auto_timeout(tool_name, get_global_timeout_manager())
return with_auto_timeout(tool_name, get_global_timeout_manager())
+5 -10
View File
@@ -5,15 +5,13 @@ for all tools used in Hanzo AI. These abstractions help ensure consistent tool
behavior and provide a foundation for tool registration and management.
"""
import functools
import inspect
import functools
from abc import ABC, abstractmethod
from typing import Any, Callable, final
from collections.abc import Awaitable
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.validation import (
@@ -21,6 +19,7 @@ from hanzo_mcp.tools.common.validation import (
validate_path_parameter,
)
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.error_logger import log_tool_error, log_call_signature_error
@@ -33,6 +32,7 @@ def with_error_logging(tool_name: str) -> Callable:
Returns:
Decorator function
"""
def decorator(func: Callable[..., Awaitable[str]]) -> Callable[..., Awaitable[str]]:
@functools.wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> str:
@@ -49,12 +49,7 @@ def with_error_logging(tool_name: str) -> Callable:
log_call_signature_error(tool_name, expected, actual, e)
# Log the error
log_tool_error(
tool_name,
e,
params=kwargs,
context=f"Call signature mismatch or type error"
)
log_tool_error(tool_name, e, params=kwargs, context=f"Call signature mismatch or type error")
# Return user-friendly error message
return (
@@ -74,6 +69,7 @@ def with_error_logging(tool_name: str) -> Callable:
)
return wrapper
return decorator
@@ -146,7 +142,6 @@ class BaseTool(ABC):
@abstractmethod
@auto_timeout("base")
async def call(self, ctx: MCPContext, **params: Any) -> Any:
"""Execute the tool with the given parameters.
@@ -9,13 +9,12 @@ from typing import Any, Unpack, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.truncate import truncate_response
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.fastmcp_pagination import (
CursorData,
create_paginated_response,
@@ -157,8 +156,6 @@ Not available: think,write,edit,multi_edit,notebook_edit
@override
@auto_timeout("batch")
async def call(
self,
ctx: MCPContext,
@@ -5,8 +5,6 @@ from typing import Any, Dict, Unpack, Optional, TypedDict, final
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.config.settings import (
ProjectConfig,
MCPServerConfig,
@@ -16,6 +14,7 @@ from hanzo_mcp.config.settings import (
)
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class ConfigToolParams(TypedDict, total=False):
@@ -65,8 +64,6 @@ Perfect for AI-driven configuration where users can say things like:
Automatically detects projects based on LLM.md files and manages .hanzo/ directories."""
@auto_timeout("config")
async def call(
self,
ctx: MCPContext,
@@ -7,12 +7,11 @@ from typing import Unpack, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Analysis = Annotated[
str,
@@ -134,8 +133,6 @@ Recommendations:
@override
@auto_timeout("critic")
async def call(
self,
ctx: MCPContext,
@@ -4,13 +4,13 @@ This module provides comprehensive error logging for all MCP tool operations,
writing errors to ~/.hanzo/mcp/logs/ for debugging and analysis.
"""
import os
import json
import logging
import traceback
from datetime import datetime
from pathlib import Path
from typing import Any, Optional
import os
from pathlib import Path
from datetime import datetime
class MCPErrorLogger:
@@ -58,8 +58,7 @@ class MCPErrorLogger:
# Formatter
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
"%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
)
file_handler.setFormatter(formatter)
general_handler.setFormatter(formatter)
@@ -106,16 +105,16 @@ class MCPErrorLogger:
# Also log to standard logger
self.logger.error(
f"Tool '{tool_name}' error: {type(error).__name__}: {str(error)}",
extra={"tool": tool_name, "params": error_data.get("params")}
extra={"tool": tool_name, "params": error_data.get("params")},
)
# Write detailed error to tool-specific file
tool_log_file = self.log_dir / f"{tool_name}-errors.log"
try:
with open(tool_log_file, "a") as f:
f.write(f"\n{'='*80}\n")
f.write(f"\n{'=' * 80}\n")
f.write(f"ERROR at {error_data['timestamp']}\n")
f.write(f"{'='*80}\n")
f.write(f"{'=' * 80}\n")
f.write(f"Tool: {tool_name}\n")
f.write(f"Error Type: {error_data['error_type']}\n")
f.write(f"Error Message: {error_data['error_message']}\n")
@@ -156,9 +155,9 @@ class MCPErrorLogger:
sig_log_file = self.log_dir / "signature-errors.log"
try:
with open(sig_log_file, "a") as f:
f.write(f"\n{'='*80}\n")
f.write(f"\n{'=' * 80}\n")
f.write(f"CALL SIGNATURE ERROR at {error_data['timestamp']}\n")
f.write(f"{'='*80}\n")
f.write(f"{'=' * 80}\n")
f.write(f"Tool: {tool_name}\n")
f.write(f"Expected: {expected_signature}\n")
f.write(f"Actual: {actual_call}\n")
@@ -169,9 +168,7 @@ class MCPErrorLogger:
# Also log as regular tool error
self.log_tool_error(
tool_name,
error,
context=f"Call signature mismatch - Expected: {expected_signature}, Got: {actual_call}"
tool_name, error, context=f"Call signature mismatch - Expected: {expected_signature}, Got: {actual_call}"
)
def _sanitize_params(self, params: dict[str, Any]) -> dict[str, Any]:
@@ -185,8 +182,16 @@ class MCPErrorLogger:
"""
sanitized = {}
sensitive_keys = {
"password", "token", "key", "secret", "api_key", "auth",
"credential", "private", "ssh_key", "passphrase"
"password",
"token",
"key",
"secret",
"api_key",
"auth",
"credential",
"private",
"ssh_key",
"passphrase",
}
for key, value in params.items():
@@ -214,7 +219,7 @@ class MCPErrorLogger:
Returns:
List of error dictionaries
"""
today = datetime.now().strftime('%Y-%m-%d')
today = datetime.now().strftime("%Y-%m-%d")
json_log_file = self.log_dir / f"tool-errors-{today}.jsonl"
if not json_log_file.exists():
@@ -9,10 +9,9 @@ from typing import Any, Dict, Union
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool, handle_connection_errors
from hanzo_mcp.tools.common.pagination import CursorManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.paginated_response import paginate_if_needed
@@ -47,7 +46,6 @@ class PaginatedBaseTool(BaseTool):
@handle_connection_errors
@auto_timeout("paginated_base")
async def call(self, ctx: MCPContext, **params: Any) -> Union[str, Dict[str, Any]]:
"""Execute the tool with automatic pagination support.
@@ -123,11 +123,11 @@ class PermissionManager:
# Security check: Reject paths with traversal attempts
if ".." in str(path) or "~" in str(path):
return False
try:
# Resolve the path (follows symlinks and makes absolute)
resolved_path: Path = Path(path).resolve(strict=False)
# Security check: Ensure resolved path doesn't escape allowed directories
# by checking if it's actually under an allowed path after resolution
original_path = Path(path)
@@ -160,7 +160,7 @@ class PermissionManager:
continue
return False
def _is_subpath(self, child: Path, parent: Path) -> bool:
"""Check if child is a subpath of parent."""
try:
@@ -7,11 +7,10 @@ from datetime import datetime
import psutil
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.shell.run_background import RunBackgroundTool
from hanzo_mcp.tools.database.database_manager import DatabaseManager
@@ -60,8 +59,6 @@ Example:
@override
@auto_timeout("stats")
async def call(
self,
ctx: MCPContext,
@@ -7,12 +7,11 @@ from typing import Unpack, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Thought = Annotated[
str,
@@ -96,8 +95,6 @@ Feature Implementation Planning
@override
@auto_timeout("thinking")
async def call(
self,
ctx: MCPContext,
@@ -37,11 +37,11 @@ def parse_timeout(timeout_str: Union[str, int, float]) -> float:
# Regex patterns for different time units
patterns = [
# Hours: 1h, 1.5hr, 2hour, 3hours
(r'^(\d*\.?\d+)\s*h(?:r|our|ours)?$', 3600),
(r"^(\d*\.?\d+)\s*h(?:r|our|ours)?$", 3600),
# Minutes: 2m, 5min, 10mins, 1.5minute
(r'^(\d*\.?\d+)\s*m(?:in|ins|inute|inutes)?$', 60),
(r"^(\d*\.?\d+)\s*m(?:in|ins|inute|inutes)?$", 60),
# Seconds: 30s, 120sec, 45secs, 60second, 90seconds
(r'^(\d*\.?\d+)\s*s(?:ec|ecs|econd|econds)?$', 1),
(r"^(\d*\.?\d+)\s*s(?:ec|ecs|econd|econds)?$", 1),
]
for pattern, multiplier in patterns:
@@ -89,10 +89,7 @@ def format_timeout(seconds: float) -> str:
# Test the parser
if __name__ == "__main__":
test_cases = [
"2min", "5m", "120s", "30sec", "1.5h", "0.5hr",
"90", 120, 3600.0, "1hour", "2hours", "30seconds"
]
test_cases = ["2min", "5m", "120s", "30sec", "1.5h", "0.5hr", "90", 120, 3600.0, "1hour", "2hours", "30seconds"]
for case in test_cases:
try:
@@ -100,4 +97,4 @@ if __name__ == "__main__":
formatted = format_timeout(result)
print(f"{case} -> {result}s ({formatted})")
except ValueError as e:
print(f"{case} -> ERROR: {e}")
print(f"{case} -> ERROR: {e}")
@@ -5,11 +5,10 @@ from typing import Unpack, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
ToolName = Annotated[
str,
@@ -75,8 +74,6 @@ Use 'tool_enable' to re-enable disabled tools.
@override
@auto_timeout("tool_disable")
async def call(
self,
ctx: MCPContext,
@@ -7,10 +7,9 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
ToolName = Annotated[
str,
@@ -122,8 +121,6 @@ Use 'tool_list' to see all available tools and their status.
@override
@auto_timeout("tool_enable")
async def call(
self,
ctx: MCPContext,
@@ -5,11 +5,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
ShowDisabled = Annotated[
bool,
@@ -161,8 +160,6 @@ Use 'tool_enable' and 'tool_disable' to change tool status.
@override
@auto_timeout("tool_list")
async def call(
self,
ctx: MCPContext,
@@ -9,11 +9,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.config import load_settings, save_settings
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.config.index_config import IndexScope, IndexConfig
# Parameter types
@@ -107,8 +106,6 @@ config --action toggle index.scope --path ./project"""
@override
@auto_timeout("config")
async def call(
self,
ctx: MCPContext,
@@ -3,12 +3,11 @@
from typing import Optional, override
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.mode import ModeRegistry, register_default_modes
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class ModeTool(BaseTool):
@@ -313,8 +312,6 @@ mode --action current"""
return await tool_self.run(ctx, action=action, name=name)
@auto_timeout("mode")
async def call(self, ctx: MCPContext, **params) -> str:
"""Call the tool with arguments."""
return await self.run(ctx, action=params.get("action", "list"), name=params.get("name"))
@@ -15,10 +15,9 @@ from typing import (
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
# Parameter types
@@ -149,8 +148,6 @@ graph --action search --pattern "John" --node-type User
@override
@auto_timeout("graph")
async def call(
self,
ctx: MCPContext,
@@ -6,11 +6,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
NodeId = Annotated[
@@ -140,8 +139,6 @@ Examples:
@override
@auto_timeout("graph_add")
async def call(
self,
ctx: MCPContext,
@@ -15,11 +15,10 @@ from collections import deque
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
Query = Annotated[
@@ -150,8 +149,6 @@ Examples:
@override
@auto_timeout("graph_query")
async def call(
self,
ctx: MCPContext,
@@ -5,11 +5,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
NodeId = Annotated[
@@ -119,8 +118,6 @@ Examples:
@override
@auto_timeout("graph_remove")
async def call(
self,
ctx: MCPContext,
@@ -7,11 +7,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
Pattern = Annotated[
@@ -120,8 +119,6 @@ Examples:
@override
@auto_timeout("graph_search")
async def call(
self,
ctx: MCPContext,
@@ -7,11 +7,10 @@ from collections import defaultdict
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
ProjectPath = Annotated[
@@ -100,8 +99,6 @@ Examples:
@override
@auto_timeout("graph_stats")
async def call(
self,
ctx: MCPContext,
@@ -15,10 +15,9 @@ from typing import (
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
# Parameter types
@@ -103,8 +102,6 @@ sql --action stats --table users
@override
@auto_timeout("sql")
async def call(
self,
ctx: MCPContext,
@@ -6,10 +6,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
Query = Annotated[
@@ -6,11 +6,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
SearchPattern = Annotated[
@@ -110,8 +109,6 @@ Use sql_query for complex queries with joins, conditions, etc."""
@override
@auto_timeout("sql_search")
async def call(
self,
ctx: MCPContext,
@@ -6,11 +6,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.database.database_manager import DatabaseManager
ProjectPath = Annotated[
@@ -78,8 +77,6 @@ Examples:
@override
@auto_timeout("sql_stats")
async def call(
self,
ctx: MCPContext,
@@ -9,11 +9,10 @@ from typing import List, Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Command = Annotated[
Optional[str],
@@ -131,8 +130,6 @@ Note: Requires Neovim to be installed.
@override
@auto_timeout("neovim_command")
async def call(
self,
ctx: MCPContext,
@@ -8,11 +8,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
FilePath = Annotated[
str,
@@ -128,8 +127,6 @@ Note: Requires Neovim to be installed and available in PATH.
@override
@auto_timeout("neovim_edit")
async def call(
self,
ctx: MCPContext,
@@ -11,10 +11,9 @@ from datetime import datetime
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Action = Annotated[
str,
@@ -117,8 +116,6 @@ Note: Requires Neovim to be installed.
@override
@auto_timeout("neovim_session")
async def call(
self,
ctx: MCPContext,
@@ -21,13 +21,12 @@ from typing_extensions import Unpack, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.filesystem.grep import Grep
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.filesystem.ast_tool import ASTTool
from hanzo_mcp.tools.vector.ast_analyzer import Symbol, ASTAnalyzer
from hanzo_mcp.tools.vector.vector_search import VectorSearchTool
@@ -542,8 +541,6 @@ Perfect for comprehensive code analysis and refactoring tasks."""
@override
@auto_timeout("batch_search")
async def call(self, ctx: MCPContext, **params: Unpack[BatchSearchParams]) -> str:
"""Execute batch search with multiple queries in parallel."""
import time
@@ -9,11 +9,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Pattern = Annotated[
str,
@@ -104,8 +103,6 @@ Only works within allowed directories."""
@override
@auto_timeout("content_replace")
async def call(
self,
ctx: MCPContext,
@@ -5,12 +5,11 @@ from typing import override
from pathlib import Path
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class DiffTool(BaseTool):
@@ -203,8 +202,6 @@ diff a.json b.json --ignore-whitespace"""
)
@auto_timeout("diff")
async def call(self, ctx: MCPContext, **params) -> str:
"""Call the tool with arguments."""
return await self.run(
@@ -8,12 +8,11 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.truncate import truncate_response
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
DirectoryPath = Annotated[
str,
@@ -88,8 +87,6 @@ requested. Only works within allowed directories."""
@override
@auto_timeout("directory_tree")
async def call(
self,
ctx: MCPContext,
@@ -19,8 +19,6 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
@@ -28,6 +26,7 @@ from hanzo_mcp.tools.common.pagination import (
CursorManager,
paginate_list,
)
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
DirectoryPath = Annotated[
str,
@@ -119,8 +118,6 @@ Returns nextCursor if more entries are available."""
@override
@auto_timeout("directory_tree_paginated")
async def call(
self,
ctx: MCPContext,
@@ -9,11 +9,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
FilePath = Annotated[
str,
@@ -91,8 +90,6 @@ Usage:
@override
@auto_timeout("edit")
async def call(
self,
ctx: MCPContext,
@@ -23,9 +23,8 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Parameter types
Pattern = Annotated[
@@ -150,8 +149,6 @@ Fast, intuitive file content search."""
@override
@auto_timeout("find")
async def call(
self,
ctx: MCPContext,
@@ -6,11 +6,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
try:
import ffind
@@ -141,8 +140,6 @@ For database search, use 'sql_search' or 'vector_search'.
@override
@auto_timeout("find_files")
async def call(
self,
ctx: MCPContext,
@@ -8,11 +8,10 @@ from typing import Unpack, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Pattern = Annotated[
str,
@@ -156,8 +155,6 @@ Examples:
@override
@auto_timeout("git_search")
async def call(
self,
ctx: MCPContext,
@@ -14,13 +14,12 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.context import ToolContext
from hanzo_mcp.tools.common.truncate import truncate_response
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Pattern = Annotated[
str,
@@ -356,8 +355,6 @@ When you are doing an open ended search that may require multiple rounds of glob
@override
@auto_timeout("grep")
async def call(
self,
ctx: MCPContext,
@@ -9,11 +9,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
FilePath = Annotated[
str,
@@ -135,8 +134,6 @@ If you want to create a new file, use:
@override
@auto_timeout("multi_edit")
async def call(
self,
ctx: MCPContext,
@@ -3,19 +3,18 @@
This module provides the ReadTool for reading the contents of files.
"""
import os
from typing import Unpack, Annotated, TypedDict, final, override
from pathlib import Path
import os
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import with_error_logging
from hanzo_mcp.tools.common.truncate import truncate_response
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.base import with_error_logging
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
FilePath = Annotated[
str,
@@ -9,11 +9,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
SearchPath = Annotated[
str,
@@ -77,8 +76,6 @@ understand project-specific requirements and preferences."""
@override
@auto_timeout("rules")
async def call(
self,
ctx: MCPContext,
@@ -27,13 +27,12 @@ from dataclasses import dataclass
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.filesystem.grep import Grep
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.vector.vector_search import VectorSearchTool
from hanzo_mcp.tools.filesystem.git_search import GitSearchTool
from hanzo_mcp.tools.vector.project_manager import ProjectVectorManager
@@ -531,8 +530,6 @@ This is the recommended search tool for comprehensive results."""
@override
@auto_timeout("search")
async def call(
self,
ctx: MCPContext,
@@ -23,9 +23,8 @@ from pydantic import Field
from grep_ast.grep_ast import TreeContext
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Parameter types
Action = Annotated[
@@ -129,8 +128,6 @@ Finds code structures (functions, classes, methods) with full context."""
@override
@auto_timeout("symbols")
async def call(
self,
ctx: MCPContext,
@@ -9,9 +9,8 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Parameter types
TreePath = Annotated[
@@ -98,8 +97,6 @@ tree --pattern "*.py" --show-size"""
@override
@auto_timeout("tree")
async def call(
self,
ctx: MCPContext,
@@ -7,11 +7,10 @@ from pathlib import Path
from datetime import datetime
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class WatchTool(BaseTool):
@@ -44,8 +43,6 @@ class WatchTool(BaseTool):
)
@auto_timeout("watch")
async def call(self, ctx: MCPContext, **params) -> str:
"""Call the tool with arguments."""
return await self.run(
@@ -8,11 +8,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
FilePath = Annotated[
str,
@@ -75,8 +74,6 @@ Usage:
@override
@auto_timeout("write")
async def call(
self,
ctx: MCPContext,
@@ -16,9 +16,8 @@ import nbformat
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.jupyter.base import JupyterBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Parameter types
Action = Annotated[
@@ -114,8 +113,6 @@ jupyter --action create "new.ipynb"
@override
@auto_timeout("jupyter")
async def call(
self,
ctx: MCPContext,
@@ -325,9 +322,7 @@ jupyter --action create "new.ipynb"
# Create a notebook client with default kernel
client = NotebookClient(
nb,
timeout=params.get('timeout', 600),
kernel_name=params.get('kernel_name', 'python3')
nb, timeout=params.get("timeout", 600), kernel_name=params.get("kernel_name", "python3")
)
# Execute the notebook
@@ -9,11 +9,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.jupyter.base import JupyterBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
NotebookPath = Annotated[
str,
@@ -99,8 +98,6 @@ class NoteBookEditTool(JupyterBaseTool):
@override
@auto_timeout("notebook_edit")
async def call(
self,
ctx: MCPContext,
@@ -9,11 +9,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.jupyter.base import JupyterBaseTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
NotebookPath = Annotated[
str,
@@ -59,8 +58,6 @@ class NotebookReadTool(JupyterBaseTool):
@override
@auto_timeout("notebook_read")
async def call(
self,
ctx: MCPContext,
@@ -15,10 +15,10 @@ from typing import (
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.llm.llm_tool import LLMTool
from hanzo_mcp.tools.common.context import create_tool_context, ToolContext
from hanzo_mcp.tools.common.context import ToolContext, create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Prompt = Annotated[
str,
@@ -7,11 +7,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.llm.llm_tool import LLMTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Action = Annotated[
str,
@@ -113,8 +112,6 @@ Providers are automatically detected based on environment variables:
@override
@auto_timeout("llm_manage")
async def call(
self,
ctx: MCPContext,
@@ -19,10 +19,9 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Check if litellm is available
try:
@@ -266,8 +265,6 @@ Available: {", ".join(available) if available else "None"}"""
@override
@auto_timeout("llm_unified")
async def call(
self,
ctx: MCPContext,
@@ -5,10 +5,9 @@ from typing import Dict, Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.llm.llm_tool import LLMTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Prompt = Annotated[
str,
@@ -101,8 +100,6 @@ class BaseProviderTool(BaseTool):
@override
@auto_timeout("provider_tools")
async def call(
self,
ctx: MCPContext,
@@ -1,5 +1,5 @@
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
"""Language Server Protocol (LSP) tool for code intelligence.
This tool provides on-demand LSP configuration and installation for various
@@ -475,8 +475,6 @@ class LSPTool(BaseTool):
return MCPResourceDocument(data=result)
@auto_timeout("lsp")
async def call(self, **kwargs) -> str:
"""Tool interface for MCP - converts result to JSON string."""
result = await self.run(**kwargs)
+1 -4
View File
@@ -8,10 +8,9 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
ServerCommand = Annotated[
str,
@@ -134,8 +133,6 @@ Use 'mcp_stats' to see all added servers and their status.
@override
@auto_timeout("mcp_add")
async def call(
self,
ctx: MCPContext,
@@ -5,11 +5,10 @@ from typing import Unpack, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
ServerName = Annotated[
str,
@@ -67,8 +66,6 @@ Use 'mcp_stats' to see all servers before removing.
@override
@auto_timeout("mcp_remove")
async def call(
self,
ctx: MCPContext,
@@ -112,8 +109,9 @@ Use 'mcp_stats' to see all servers before removing.
process_id = server.get("process_id")
if process_id:
try:
import signal
import os
import signal
os.kill(process_id, signal.SIGTERM)
await tool_ctx.info(f"Stopped running server '{name}' (PID: {process_id})")
except ProcessLookupError:
@@ -4,11 +4,10 @@ from typing import Unpack, TypedDict, final, override
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class McpStatsParams(TypedDict, total=False):
@@ -50,8 +49,6 @@ Example:
@override
@auto_timeout("mcp_stats")
async def call(
self,
ctx: MCPContext,
@@ -20,10 +20,9 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Parameter types
Action = Annotated[
@@ -273,8 +272,6 @@ Status: {enabled} enabled, {running} running"""
@override
@auto_timeout("mcp")
async def call(
self,
ctx: MCPContext,
@@ -7,12 +7,11 @@ supporting hierarchical organization (session, project, global).
from typing import Any, Dict, List, Optional, final, override
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Import from hanzo-memory package
try:
@@ -72,8 +71,6 @@ recall_facts(queries=["company policies"], scope="global", limit=5)
@override
@auto_timeout("knowledge_tools")
async def call(
self,
ctx: MCPContext,
@@ -196,8 +193,6 @@ store_facts(facts=["Company founded in 2020"], scope="global", kb_name="company_
@override
@auto_timeout("knowledge_tools")
async def call(
self,
ctx: MCPContext,
@@ -295,8 +290,6 @@ summarize_to_memory(content="Company guidelines...", topic="Guidelines", scope="
@override
@auto_timeout("knowledge_tools")
async def call(
self,
ctx: MCPContext,
@@ -402,8 +395,6 @@ manage_knowledge_bases(action="delete", kb_name="old_docs")
@override
@auto_timeout("knowledge_tools")
async def call(
self,
ctx: MCPContext,
@@ -7,12 +7,11 @@ The hanzo-memory package provides embedded database and vector search capabiliti
from typing import Dict, List, Optional, final, override
from mcp.server import FastMCP
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
# Import from hanzo-memory package
try:
@@ -72,8 +71,6 @@ recall_memories(queries=["coding standards"], scope="global")
@override
@auto_timeout("memory_tools")
async def call(
self,
ctx: MCPContext,
@@ -161,8 +158,6 @@ create_memories(statements=["User prefers dark mode", "User works in Python"])
@override
@auto_timeout("memory_tools")
async def call(self, ctx: MCPContext, statements: List[str]) -> str:
"""Create new memories.
@@ -228,8 +223,6 @@ update_memories(updates=[
@override
@auto_timeout("memory_tools")
async def call(self, ctx: MCPContext, updates: List[Dict[str, str]]) -> str:
"""Update memories.
@@ -295,8 +288,6 @@ delete_memories(ids=["mem_1", "mem_2"])
@override
@auto_timeout("memory_tools")
async def call(self, ctx: MCPContext, ids: List[str]) -> str:
"""Delete memories.
@@ -360,8 +351,6 @@ manage_memories(
@override
@auto_timeout("memory_tools")
async def call(
self,
ctx: MCPContext,
+3 -3
View File
@@ -11,8 +11,8 @@ from typing import Any, final
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.truncate import truncate_response
from hanzo_mcp.tools.common.permissions import PermissionManager
class BashCommandStatus(Enum):
@@ -139,7 +139,7 @@ class CommandResult:
return truncate_response(
result,
max_tokens=max_tokens,
truncation_message="\n\n[Shell output truncated due to token limit. Use pagination, filtering, or limit parameters to reduce output size.]"
truncation_message="\n\n[Shell output truncated due to token limit. Use pagination, filtering, or limit parameters to reduce output size.]",
)
def to_agent_observation(self, max_tokens: int = 25000) -> str:
@@ -164,7 +164,7 @@ class CommandResult:
return truncate_response(
content,
max_tokens=max_tokens,
truncation_message="\n\n[Shell output truncated due to token limit. Use pagination, filtering, or limit parameters to reduce output size.]"
truncation_message="\n\n[Shell output truncated due to token limit. Use pagination, filtering, or limit parameters to reduce output size.]",
)
@@ -8,8 +8,8 @@ from pathlib import Path
from mcp.server import FastMCP
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.shell.base_process import BaseScriptTool
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
class BashTool(BaseScriptTool):
@@ -112,9 +112,9 @@ class CommandExecutor:
# Use shlex.quote for proper escaping to prevent command injection
escaped_command = shlex.quote(command)
if use_login_shell:
formatted_command = f'{user_shell} bash -l -c {escaped_command}'
formatted_command = f"{user_shell} bash -l -c {escaped_command}"
else:
formatted_command = f'{user_shell} bash -c {escaped_command}'
formatted_command = f"{user_shell} bash -c {escaped_command}"
else:
# # For simple commands without special characters
# # Still respect login shell preference
@@ -126,7 +126,7 @@ class CommandExecutor:
elif shell_basename in ["powershell", "powershell.exe", "pwsh", "pwsh.exe"]:
# Use proper escaping for PowerShell to prevent injection
# PowerShell requires different escaping than POSIX shells
escaped_command = command.replace('"', '`"').replace("'", "``'").replace('$', '`$')
escaped_command = command.replace('"', '`"').replace("'", "``'").replace("$", "`$")
formatted_command = f'"{user_shell}" -Command "{escaped_command}"'
else:
+1 -4
View File
@@ -6,11 +6,10 @@ from pathlib import Path
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.shell.run_background import RunBackgroundTool
ProcessId = Annotated[
@@ -108,8 +107,6 @@ Use run_command with 'tail -f' for continuous monitoring.
@override
@auto_timeout("logs")
async def call(
self,
ctx: MCPContext,
+1 -4
View File
@@ -7,11 +7,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
Package = Annotated[
str,
@@ -106,8 +105,6 @@ For long-running servers, use npx_background instead.
@override
@auto_timeout("npx")
async def call(
self,
ctx: MCPContext,
@@ -8,11 +8,10 @@ from typing import Unpack, Optional, Annotated, TypedDict, final, override
from pydantic import Field
from mcp.server.fastmcp import Context as MCPContext
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.common.context import create_tool_context
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
from hanzo_mcp.tools.shell.run_background import BackgroundProcess, RunBackgroundTool
Package = Annotated[
@@ -123,8 +122,6 @@ Use 'processes' to list running processes and 'pkill' to stop them.
@override
@auto_timeout("npx")
async def call(
self,
ctx: MCPContext,

Some files were not shown because too many files have changed in this diff Show More