hanzo-mcp 0.6.12__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (117) hide show
  1. hanzo_mcp/__init__.py +2 -2
  2. hanzo_mcp/analytics/__init__.py +5 -0
  3. hanzo_mcp/analytics/posthog_analytics.py +364 -0
  4. hanzo_mcp/cli.py +5 -5
  5. hanzo_mcp/cli_enhanced.py +7 -7
  6. hanzo_mcp/cli_plugin.py +91 -0
  7. hanzo_mcp/config/__init__.py +1 -1
  8. hanzo_mcp/config/settings.py +70 -7
  9. hanzo_mcp/config/tool_config.py +20 -6
  10. hanzo_mcp/dev_server.py +3 -3
  11. hanzo_mcp/prompts/project_system.py +1 -1
  12. hanzo_mcp/server.py +40 -3
  13. hanzo_mcp/server_enhanced.py +69 -0
  14. hanzo_mcp/tools/__init__.py +140 -31
  15. hanzo_mcp/tools/agent/__init__.py +85 -4
  16. hanzo_mcp/tools/agent/agent_tool.py +104 -6
  17. hanzo_mcp/tools/agent/agent_tool_v2.py +459 -0
  18. hanzo_mcp/tools/agent/clarification_protocol.py +220 -0
  19. hanzo_mcp/tools/agent/clarification_tool.py +68 -0
  20. hanzo_mcp/tools/agent/claude_cli_tool.py +125 -0
  21. hanzo_mcp/tools/agent/claude_desktop_auth.py +508 -0
  22. hanzo_mcp/tools/agent/cli_agent_base.py +191 -0
  23. hanzo_mcp/tools/agent/code_auth.py +436 -0
  24. hanzo_mcp/tools/agent/code_auth_tool.py +194 -0
  25. hanzo_mcp/tools/agent/codex_cli_tool.py +123 -0
  26. hanzo_mcp/tools/agent/critic_tool.py +376 -0
  27. hanzo_mcp/tools/agent/gemini_cli_tool.py +128 -0
  28. hanzo_mcp/tools/agent/grok_cli_tool.py +128 -0
  29. hanzo_mcp/tools/agent/iching_tool.py +380 -0
  30. hanzo_mcp/tools/agent/network_tool.py +273 -0
  31. hanzo_mcp/tools/agent/prompt.py +62 -20
  32. hanzo_mcp/tools/agent/review_tool.py +433 -0
  33. hanzo_mcp/tools/agent/swarm_tool.py +535 -0
  34. hanzo_mcp/tools/agent/swarm_tool_v2.py +594 -0
  35. hanzo_mcp/tools/common/__init__.py +15 -1
  36. hanzo_mcp/tools/common/base.py +5 -4
  37. hanzo_mcp/tools/common/batch_tool.py +103 -11
  38. hanzo_mcp/tools/common/config_tool.py +2 -2
  39. hanzo_mcp/tools/common/context.py +2 -2
  40. hanzo_mcp/tools/common/context_fix.py +26 -0
  41. hanzo_mcp/tools/common/critic_tool.py +196 -0
  42. hanzo_mcp/tools/common/decorators.py +208 -0
  43. hanzo_mcp/tools/common/enhanced_base.py +106 -0
  44. hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
  45. hanzo_mcp/tools/common/forgiving_edit.py +243 -0
  46. hanzo_mcp/tools/common/mode.py +116 -0
  47. hanzo_mcp/tools/common/mode_loader.py +105 -0
  48. hanzo_mcp/tools/common/paginated_base.py +230 -0
  49. hanzo_mcp/tools/common/paginated_response.py +307 -0
  50. hanzo_mcp/tools/common/pagination.py +226 -0
  51. hanzo_mcp/tools/common/permissions.py +1 -1
  52. hanzo_mcp/tools/common/personality.py +936 -0
  53. hanzo_mcp/tools/common/plugin_loader.py +287 -0
  54. hanzo_mcp/tools/common/stats.py +4 -4
  55. hanzo_mcp/tools/common/tool_list.py +4 -1
  56. hanzo_mcp/tools/common/truncate.py +101 -0
  57. hanzo_mcp/tools/common/validation.py +1 -1
  58. hanzo_mcp/tools/config/__init__.py +3 -1
  59. hanzo_mcp/tools/config/config_tool.py +1 -1
  60. hanzo_mcp/tools/config/mode_tool.py +209 -0
  61. hanzo_mcp/tools/database/__init__.py +1 -1
  62. hanzo_mcp/tools/editor/__init__.py +1 -1
  63. hanzo_mcp/tools/filesystem/__init__.py +48 -14
  64. hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
  65. hanzo_mcp/tools/filesystem/batch_search.py +3 -3
  66. hanzo_mcp/tools/filesystem/diff.py +2 -2
  67. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
  68. hanzo_mcp/tools/filesystem/rules_tool.py +235 -0
  69. hanzo_mcp/tools/filesystem/{unified_search.py → search_tool.py} +12 -12
  70. hanzo_mcp/tools/filesystem/{symbols_unified.py → symbols_tool.py} +104 -5
  71. hanzo_mcp/tools/filesystem/watch.py +3 -2
  72. hanzo_mcp/tools/jupyter/__init__.py +2 -2
  73. hanzo_mcp/tools/jupyter/jupyter.py +1 -1
  74. hanzo_mcp/tools/llm/__init__.py +3 -3
  75. hanzo_mcp/tools/llm/llm_tool.py +648 -143
  76. hanzo_mcp/tools/lsp/__init__.py +5 -0
  77. hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
  78. hanzo_mcp/tools/mcp/__init__.py +2 -2
  79. hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
  80. hanzo_mcp/tools/memory/__init__.py +76 -0
  81. hanzo_mcp/tools/memory/knowledge_tools.py +518 -0
  82. hanzo_mcp/tools/memory/memory_tools.py +456 -0
  83. hanzo_mcp/tools/search/__init__.py +6 -0
  84. hanzo_mcp/tools/search/find_tool.py +581 -0
  85. hanzo_mcp/tools/search/unified_search.py +953 -0
  86. hanzo_mcp/tools/shell/__init__.py +11 -6
  87. hanzo_mcp/tools/shell/auto_background.py +203 -0
  88. hanzo_mcp/tools/shell/base_process.py +57 -29
  89. hanzo_mcp/tools/shell/bash_session_executor.py +1 -1
  90. hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +18 -34
  91. hanzo_mcp/tools/shell/command_executor.py +2 -2
  92. hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +16 -33
  93. hanzo_mcp/tools/shell/open.py +2 -2
  94. hanzo_mcp/tools/shell/{process_unified.py → process_tool.py} +1 -1
  95. hanzo_mcp/tools/shell/run_command_windows.py +1 -1
  96. hanzo_mcp/tools/shell/streaming_command.py +594 -0
  97. hanzo_mcp/tools/shell/uvx.py +47 -2
  98. hanzo_mcp/tools/shell/uvx_background.py +47 -2
  99. hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +16 -33
  100. hanzo_mcp/tools/todo/__init__.py +14 -19
  101. hanzo_mcp/tools/todo/todo.py +22 -1
  102. hanzo_mcp/tools/vector/__init__.py +1 -1
  103. hanzo_mcp/tools/vector/infinity_store.py +2 -2
  104. hanzo_mcp/tools/vector/project_manager.py +1 -1
  105. hanzo_mcp/types.py +23 -0
  106. hanzo_mcp-0.7.0.dist-info/METADATA +516 -0
  107. hanzo_mcp-0.7.0.dist-info/RECORD +180 -0
  108. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +1 -0
  109. hanzo_mcp/tools/common/palette.py +0 -344
  110. hanzo_mcp/tools/common/palette_loader.py +0 -108
  111. hanzo_mcp/tools/config/palette_tool.py +0 -179
  112. hanzo_mcp/tools/llm/llm_unified.py +0 -851
  113. hanzo_mcp-0.6.12.dist-info/METADATA +0 -339
  114. hanzo_mcp-0.6.12.dist-info/RECORD +0 -135
  115. hanzo_mcp-0.6.12.dist-info/licenses/LICENSE +0 -21
  116. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
  117. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,273 @@
1
+ """Network tool for dispatching work to agent networks.
2
+
3
+ This tool enables distributed AI workloads across local and remote agent networks,
4
+ with support for both local-only execution (via hanzo-miner) and cloud fallback.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import os
10
+ from typing import Annotated, TypedDict, Unpack, final, override, Optional, Dict, Any, List
11
+
12
+ from mcp.server.fastmcp import Context as MCPContext
13
+ from mcp.server import FastMCP
14
+ from pydantic import Field
15
+
16
+ from hanzo_mcp.tools.common.base import BaseTool
17
+ from hanzo_mcp.tools.common.context import ToolContext, create_tool_context
18
+ from hanzo_mcp.tools.common.permissions import PermissionManager
19
+
20
+
21
+ # Import hanzo cluster if available
22
+ try:
23
+ from hanzoai import cluster
24
+ CLUSTER_AVAILABLE = True
25
+ except ImportError:
26
+ CLUSTER_AVAILABLE = False
27
+
28
+
29
+ class NetworkToolParams(TypedDict, total=False):
30
+ """Parameters for the network tool."""
31
+ task: str
32
+ agents: Optional[List[str]]
33
+ mode: Optional[str] # "local", "distributed", "hybrid"
34
+ model: Optional[str]
35
+ routing: Optional[str] # "sequential", "parallel", "consensus"
36
+ require_local: Optional[bool]
37
+
38
+
39
+ @final
40
+ class NetworkTool(BaseTool):
41
+ """Dispatch work to agent networks for distributed AI processing.
42
+
43
+ Modes:
44
+ - local: Use only local compute (via hanzo-cluster/miner)
45
+ - distributed: Use available network resources
46
+ - hybrid: Prefer local, fallback to cloud
47
+
48
+ This tool is the evolution of the swarm tool, providing:
49
+ - True distributed execution across devices
50
+ - Local-first privacy-preserving AI
51
+ - Automatic routing and load balancing
52
+ - Integration with hanzo-miner for compute contribution
53
+ """
54
+
55
+ name = "network"
56
+ description = "Dispatch tasks to agent networks for distributed AI processing"
57
+
58
+ def __init__(
59
+ self,
60
+ permission_manager: PermissionManager,
61
+ default_mode: str = "hybrid",
62
+ cluster_endpoint: str = None,
63
+ ):
64
+ """Initialize the network tool.
65
+
66
+ Args:
67
+ permission_manager: Permission manager
68
+ default_mode: Default execution mode
69
+ cluster_endpoint: Optional cluster endpoint
70
+ """
71
+ super().__init__(permission_manager)
72
+ self.default_mode = default_mode
73
+ self.cluster_endpoint = cluster_endpoint or os.environ.get(
74
+ "HANZO_CLUSTER_ENDPOINT",
75
+ "http://localhost:8000"
76
+ )
77
+ self._cluster = None
78
+
79
+ async def _ensure_cluster(self):
80
+ """Ensure we have a cluster connection."""
81
+ if not CLUSTER_AVAILABLE:
82
+ return None
83
+
84
+ if not self._cluster:
85
+ try:
86
+ # Try to connect to existing cluster
87
+ self._cluster = cluster.HanzoCluster()
88
+ # Check if cluster is running
89
+ import httpx
90
+ async with httpx.AsyncClient() as client:
91
+ response = await client.get(f"{self.cluster_endpoint}/health")
92
+ if response.status_code != 200:
93
+ # Start local cluster if not running
94
+ await self._cluster.start()
95
+ except Exception:
96
+ # Cluster not available
97
+ self._cluster = None
98
+
99
+ return self._cluster
100
+
101
+ @override
102
+ async def call(
103
+ self, ctx: MCPContext, **params: Unpack[NetworkToolParams]
104
+ ) -> str:
105
+ """Execute a task on the agent network.
106
+
107
+ Args:
108
+ ctx: MCP context
109
+ task: Task description to execute
110
+ agents: Optional list of specific agents to use
111
+ mode: Execution mode (local/distributed/hybrid)
112
+ model: Optional model preference
113
+ routing: Routing strategy
114
+ require_local: Require local-only execution
115
+
116
+ Returns:
117
+ JSON string with results
118
+ """
119
+ task = params.get("task", "")
120
+ if not task:
121
+ return json.dumps({
122
+ "error": "Task description required",
123
+ "success": False
124
+ })
125
+
126
+ mode = params.get("mode", self.default_mode)
127
+ agents_list = params.get("agents", [])
128
+ model_pref = params.get("model")
129
+ routing = params.get("routing", "sequential")
130
+ require_local = params.get("require_local", False)
131
+
132
+ # Check if we should use local cluster
133
+ use_local = mode in ["local", "hybrid"] or require_local
134
+
135
+ results = {
136
+ "task": task,
137
+ "mode": mode,
138
+ "routing": routing,
139
+ "agents_used": [],
140
+ "results": [],
141
+ "success": False
142
+ }
143
+
144
+ try:
145
+ # Try local execution first if requested
146
+ if use_local:
147
+ cluster = await self._ensure_cluster()
148
+ if cluster:
149
+ try:
150
+ # Execute on local cluster
151
+ local_result = await cluster.inference(
152
+ prompt=task,
153
+ model=model_pref or "llama-3.2-3b",
154
+ max_tokens=4000
155
+ )
156
+
157
+ results["agents_used"].append("local-cluster")
158
+ results["results"].append({
159
+ "agent": "local-cluster",
160
+ "response": local_result.get("choices", [{}])[0].get("text", ""),
161
+ "local": True
162
+ })
163
+ results["success"] = True
164
+
165
+ # If local succeeded and not hybrid, return
166
+ if mode == "local" or (mode == "hybrid" and results["results"]):
167
+ return json.dumps(results, indent=2)
168
+
169
+ except Exception as e:
170
+ if require_local:
171
+ results["error"] = f"Local execution failed: {str(e)}"
172
+ return json.dumps(results, indent=2)
173
+
174
+ # Fallback to agent-based execution
175
+ # This would use hanzo-agents or the existing swarm implementation
176
+ if not results["success"] or mode in ["distributed", "hybrid"]:
177
+ # Import swarm tool as fallback
178
+ from hanzo_mcp.tools.agent.swarm_tool import SwarmTool
179
+
180
+ # Create temporary swarm tool
181
+ swarm = SwarmTool(
182
+ permission_manager=self.permission_manager,
183
+ model=model_pref
184
+ )
185
+
186
+ # Convert network params to swarm params
187
+ swarm_params = {
188
+ "prompts": [task] if not agents_list else agents_list,
189
+ "consensus": routing == "consensus",
190
+ "parallel": routing == "parallel",
191
+ }
192
+
193
+ # Execute via swarm
194
+ swarm_result = await swarm.call(ctx, **swarm_params)
195
+ swarm_data = json.loads(swarm_result)
196
+
197
+ # Merge results
198
+ if swarm_data.get("success"):
199
+ results["agents_used"].extend([r["agent"] for r in swarm_data.get("results", [])])
200
+ results["results"].extend(swarm_data.get("results", []))
201
+ results["success"] = True
202
+ else:
203
+ results["error"] = swarm_data.get("error", "Unknown error")
204
+
205
+ except Exception as e:
206
+ results["error"] = str(e)
207
+
208
+ return json.dumps(results, indent=2)
209
+
210
+ @classmethod
211
+ def register(cls, server: FastMCP, permission_manager: PermissionManager):
212
+ """Register the network tool with the server.
213
+
214
+ Args:
215
+ server: FastMCP server instance
216
+ permission_manager: Permission manager
217
+ """
218
+ tool = cls(permission_manager=permission_manager)
219
+
220
+ @server.tool(
221
+ name=tool.name,
222
+ description=tool.description
223
+ )
224
+ async def network_handler(
225
+ ctx: MCPContext,
226
+ task: Annotated[str, Field(description="Task to execute on the network")],
227
+ agents: Annotated[Optional[List[str]], Field(description="Specific agents to use")] = None,
228
+ mode: Annotated[Optional[str], Field(description="Execution mode: local, distributed, or hybrid")] = None,
229
+ model: Annotated[Optional[str], Field(description="Model preference")] = None,
230
+ routing: Annotated[Optional[str], Field(description="Routing strategy: sequential, parallel, or consensus")] = None,
231
+ require_local: Annotated[Optional[bool], Field(description="Require local-only execution")] = None,
232
+ ) -> str:
233
+ """Dispatch work to agent networks."""
234
+ params = NetworkToolParams(
235
+ task=task,
236
+ agents=agents,
237
+ mode=mode,
238
+ model=model,
239
+ routing=routing,
240
+ require_local=require_local,
241
+ )
242
+ return await tool.call(ctx, **params)
243
+
244
+ return tool
245
+
246
+
247
+ # Alias swarm to use network tool with local-only mode
248
+ @final
249
+ class LocalSwarmTool(NetworkTool):
250
+ """Local-only version of the network tool (swarm compatibility).
251
+
252
+ This provides backward compatibility with the swarm tool
253
+ while using local compute resources only.
254
+ """
255
+
256
+ name = "swarm"
257
+ description = "Run agent swarms locally using hanzo-miner compute"
258
+
259
+ def __init__(self, permission_manager: PermissionManager, **kwargs):
260
+ """Initialize as local-only network."""
261
+ super().__init__(
262
+ permission_manager=permission_manager,
263
+ default_mode="local",
264
+ **kwargs
265
+ )
266
+
267
+ @override
268
+ async def call(self, ctx: MCPContext, **params: Unpack[NetworkToolParams]) -> str:
269
+ """Execute with local-only mode."""
270
+ # Force local mode
271
+ params["mode"] = "local"
272
+ params["require_local"] = True
273
+ return await super().call(ctx, **params)
@@ -49,23 +49,63 @@ def get_system_prompt(
49
49
  # Extract tool names for display
50
50
  tool_names = ", ".join(f"`{tool.name}`" for tool in filtered_tools)
51
51
 
52
- # Base system prompt
53
- system_prompt = f"""You are a sub-agent assistant with access to these tools: {tool_names}.
54
-
55
- GUIDELINES:
56
- 1. You work autonomously - you cannot ask follow-up questions
57
- 2. You have access to read-only tools - you cannot modify files or execute commands
58
- 3. Your response is returned directly to the main assistant, not the user
59
- 4. Be concise and focus on the specific task assigned
60
- 5. When relevant, share file names and code snippets relevant to the query
61
- 6. Any file paths you return in your final response MUST be absolute. DO NOT use relative paths.
62
- 7. CRITICAL: You can only work with the absolute paths provided in your task prompt. You cannot infer or guess other locations.
52
+ # Base system prompt - agents always have edit tools
53
+ system_prompt = f"""You are a Claude sub-agent with access to these tools: {tool_names}.
54
+
55
+ CAPABILITIES:
56
+ 1. You have FULL read and write access - you can create, edit, and modify files
57
+ 2. You can ask clarifying questions if needed - your response goes to the coordinating agent
58
+ 3. You work as part of a team of specialized agents
59
+ 4. Other agents may be available via MCP tools (look for tools named after agents)
60
+ 5. When relevant, share file names and code snippets
61
+ 6. Any file paths you return MUST be absolute. DO NOT use relative paths.
62
+ 7. You can only work with the absolute paths provided in your task prompt.
63
+
64
+ CLARIFICATION:
65
+ - You can request clarification ONCE per task using the request_clarification tool
66
+ - Use this when instructions are ambiguous or you need additional context
67
+ - Types: AMBIGUOUS_INSTRUCTION, MISSING_CONTEXT, MULTIPLE_OPTIONS, CONFIRMATION_NEEDED, ADDITIONAL_INFO
68
+ - The main loop will provide automated guidance based on context
69
+
70
+ CRITICAL REVIEW (Devil's Advocate):
71
+ - Use the critic tool to get harsh, challenging feedback that attacks assumptions
72
+ - The critic will find flaws and push for improvements aggressively
73
+ - You can request up to 2 critic reviews per task
74
+ - Review types: CODE_QUALITY, CORRECTNESS, PERFORMANCE, SECURITY, COMPLETENESS, BEST_PRACTICES, GENERAL
75
+ - Use when you need someone to find what's wrong with your approach
76
+
77
+ BALANCED REVIEW:
78
+ - Use the review tool for constructive, balanced code review
79
+ - Provides objective assessment without predetermined bias
80
+ - You can request up to 3 reviews per task
81
+ - Focus areas: GENERAL, FUNCTIONALITY, READABILITY, MAINTAINABILITY, TESTING, DOCUMENTATION, ARCHITECTURE
82
+ - Use for regular code review and feedback
83
+
84
+ CREATIVE GUIDANCE:
85
+ - Use the iching tool when you need creative problem-solving approaches
86
+ - Combines ancient I Ching wisdom with Hanzo engineering principles
87
+ - Provides unique perspectives and actionable guidance
88
+ - Use when stuck, need fresh ideas, or want philosophical alignment
89
+
90
+ EDITING GUIDELINES:
91
+ - ALWAYS read the file first before attempting any edits
92
+ - For edit tool: The old_string must match EXACTLY including all whitespace, tabs, and newlines
93
+ - When copying text from read output, be careful with line numbers and indentation
94
+ - If an edit fails due to whitespace mismatch, try reading the specific lines again
95
+ - Prefer multi_edit when making multiple changes to the same file
96
+ - Test your edits by verifying the exact string exists in the file first
97
+
98
+ COLLABORATION:
99
+ - If you see MCP tools named after other agents, you can communicate with them
100
+ - Use agent MCP tools to delegate specialized tasks or get expert opinions
101
+ - Share context when communicating with other agents
63
102
 
64
103
  RESPONSE FORMAT:
65
- - Begin with a summary of findings
66
- - Include relevant details and context
67
- - Organize information logically
68
- - End with clear conclusions
104
+ - Begin with a summary of what you did or found
105
+ - If you have questions, ask them clearly
106
+ - Include details of any edits performed
107
+ - Report any errors encountered
108
+ - End with clear conclusions or next steps
69
109
  """
70
110
 
71
111
  return system_prompt
@@ -95,21 +135,23 @@ def get_default_model(model_override: str | None = None) -> str:
95
135
  return f"{provider}/{model_override}"
96
136
 
97
137
  # Fall back to environment variables
98
- model = os.environ.get("AGENT_MODEL", "gpt-4o")
138
+ # Default to Sonnet for cost efficiency
139
+ model = os.environ.get("AGENT_MODEL", "claude-3-5-sonnet-20241022")
99
140
 
100
141
  # Special cases for tests
101
142
  if (
102
143
  model.startswith("test-model")
103
- or model == "gpt-4o"
104
- and "TEST_MODE" in os.environ
144
+ or "TEST_MODE" in os.environ and model == "claude-3-5-sonnet-20241022"
105
145
  ):
106
146
  return model
107
147
 
108
- provider = os.environ.get("AGENT_PROVIDER", "openai")
148
+ provider = os.environ.get("AGENT_PROVIDER", "anthropic")
109
149
 
110
150
  # Only add provider prefix if it's not already in the model name
111
- if "/" not in model and provider != "openai":
151
+ if "/" not in model and provider != "anthropic":
112
152
  return f"{provider}/{model}"
153
+ elif "/" not in model and provider == "anthropic":
154
+ return f"anthropic/{model}"
113
155
  elif "/" not in model:
114
156
  return f"openai/{model}"
115
157
  else: