hanzo-mcp 0.7.6__py3-none-any.whl → 0.8.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 (178) hide show
  1. hanzo_mcp/__init__.py +7 -1
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
@@ -4,49 +4,50 @@ This module implements the AgentTool that allows Claude to delegate tasks to sub
4
4
  enabling concurrent execution of multiple operations and specialized processing.
5
5
  """
6
6
 
7
- import asyncio
8
- import json
9
7
  import re
8
+ import json
10
9
  import time
11
- from collections.abc import Iterable
12
- from typing import Annotated, TypedDict, Unpack, final, override
10
+ import asyncio
13
11
 
14
12
  # Import litellm with warnings suppressed
15
13
  import warnings
14
+ from typing import Unpack, Annotated, TypedDict, final, override
15
+ from collections.abc import Iterable
16
+
16
17
  with warnings.catch_warnings():
17
18
  warnings.simplefilter("ignore", DeprecationWarning)
18
19
  import litellm
19
- from mcp.server.fastmcp import Context as MCPContext
20
- from mcp.server import FastMCP
21
- from openai.types.chat import ChatCompletionMessageParam, ChatCompletionToolParam
22
20
  from pydantic import Field
21
+ from mcp.server import FastMCP
22
+ from openai.types.chat import ChatCompletionToolParam, ChatCompletionMessageParam
23
+ from mcp.server.fastmcp import Context as MCPContext
23
24
 
25
+ from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
26
+ from hanzo_mcp.tools.filesystem import Edit, MultiEdit, get_read_only_filesystem_tools
27
+ from hanzo_mcp.tools.common.base import BaseTool
24
28
  from hanzo_mcp.tools.agent.prompt import (
25
- get_allowed_agent_tools,
26
29
  get_default_model,
27
- get_model_parameters,
28
30
  get_system_prompt,
31
+ get_model_parameters,
32
+ get_allowed_agent_tools,
29
33
  )
30
- from hanzo_mcp.tools.agent.tool_adapter import (
31
- convert_tools_to_openai_functions,
32
- )
33
- from hanzo_mcp.tools.agent.clarification_protocol import (
34
- AgentClarificationMixin,
35
- ClarificationType,
34
+ from hanzo_mcp.tools.common.context import (
35
+ ToolContext,
36
+ create_tool_context,
36
37
  )
37
- from hanzo_mcp.tools.agent.clarification_tool import ClarificationTool
38
38
  from hanzo_mcp.tools.agent.critic_tool import CriticTool, CriticProtocol
39
- from hanzo_mcp.tools.agent.review_tool import ReviewTool, ReviewProtocol
40
39
  from hanzo_mcp.tools.agent.iching_tool import IChingTool
41
- from hanzo_mcp.tools.common.base import BaseTool
40
+ from hanzo_mcp.tools.agent.review_tool import ReviewTool, ReviewProtocol
42
41
  from hanzo_mcp.tools.common.batch_tool import BatchTool
43
- from hanzo_mcp.tools.common.context import (
44
- ToolContext,
45
- create_tool_context,
42
+ from hanzo_mcp.tools.agent.tool_adapter import (
43
+ convert_tools_to_openai_functions,
46
44
  )
47
45
  from hanzo_mcp.tools.common.permissions import PermissionManager
48
- from hanzo_mcp.tools.filesystem import get_read_only_filesystem_tools, Edit, MultiEdit
49
- from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
46
+ from hanzo_mcp.tools.agent.clarification_tool import ClarificationTool
47
+ from hanzo_mcp.tools.agent.clarification_protocol import (
48
+ ClarificationType,
49
+ AgentClarificationMixin,
50
+ )
50
51
 
51
52
  Prompt = Annotated[
52
53
  str,
@@ -73,7 +74,7 @@ class AgentTool(AgentClarificationMixin, BaseTool):
73
74
 
74
75
  The AgentTool allows Claude to create and manage sub-agents for performing
75
76
  specialized tasks concurrently, such as code search, analysis, and more.
76
-
77
+
77
78
  Agents can request clarification from the main loop up to once per task.
78
79
  """
79
80
 
@@ -156,27 +157,27 @@ Usage notes:
156
157
  self.available_tools.extend(
157
158
  get_read_only_jupyter_tools(self.permission_manager)
158
159
  )
159
-
160
+
160
161
  # Always add edit tools - agents should have edit access
161
162
  self.available_tools.append(Edit(self.permission_manager))
162
163
  self.available_tools.append(MultiEdit(self.permission_manager))
163
-
164
+
164
165
  # Add clarification tool for agents
165
166
  self.available_tools.append(ClarificationTool())
166
-
167
+
167
168
  # Add critic tool for agents (devil's advocate)
168
169
  self.available_tools.append(CriticTool())
169
-
170
+
170
171
  # Add review tool for agents (balanced review)
171
172
  self.available_tools.append(ReviewTool())
172
-
173
+
173
174
  # Add I Ching tool for creative guidance
174
175
  self.available_tools.append(IChingTool())
175
-
176
+
176
177
  self.available_tools.append(
177
178
  BatchTool({t.name: t for t in self.available_tools})
178
179
  )
179
-
180
+
180
181
  # Initialize protocols
181
182
  self.critic_protocol = CriticProtocol()
182
183
  self.review_protocol = ReviewProtocol()
@@ -204,7 +205,7 @@ Usage notes:
204
205
 
205
206
  # Extract and validate parameters
206
207
  prompts = params.get("prompts")
207
-
208
+
208
209
  if prompts is None:
209
210
  await tool_ctx.error("No prompts provided")
210
211
  return """Error: At least one prompt must be provided.
@@ -238,7 +239,9 @@ Example of correct usage:
238
239
  absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
239
240
  for prompt in prompt_list:
240
241
  if not re.search(absolute_path_pattern, prompt):
241
- await tool_ctx.error(f"Prompt does not contain absolute path: {prompt[:50]}...")
242
+ await tool_ctx.error(
243
+ f"Prompt does not contain absolute path: {prompt[:50]}..."
244
+ )
242
245
  return """Error: All prompts must contain at least one absolute path.
243
246
 
244
247
  IMPORTANT REMINDER FOR CLAUDE:
@@ -268,7 +271,9 @@ AGENT RESPONSE:
268
271
 
269
272
  AGENT RESPONSES:
270
273
  {result}"""
271
- await tool_ctx.info(f"Multi-agent execution completed in {execution_time:.2f}s")
274
+ await tool_ctx.info(
275
+ f"Multi-agent execution completed in {execution_time:.2f}s"
276
+ )
272
277
  return formatted_result
273
278
 
274
279
  async def _execute_agent(self, prompt: str, tool_ctx: ToolContext) -> str:
@@ -316,7 +321,9 @@ AGENT RESPONSES:
316
321
 
317
322
  return result if result else "No results returned from agent"
318
323
 
319
- async def _execute_multiple_agents(self, prompts: list[str], tool_ctx: ToolContext) -> str:
324
+ async def _execute_multiple_agents(
325
+ self, prompts: list[str], tool_ctx: ToolContext
326
+ ) -> str:
320
327
  """Execute multiple agents concurrently.
321
328
 
322
329
  Args:
@@ -344,7 +351,7 @@ AGENT RESPONSES:
344
351
  # Create tasks for parallel execution
345
352
  tasks = []
346
353
  for i, prompt in enumerate(prompts):
347
- await tool_ctx.info(f"Creating agent task {i+1}: {prompt[:50]}...")
354
+ await tool_ctx.info(f"Creating agent task {i + 1}: {prompt[:50]}...")
348
355
  task = self._execute_agent_with_tools(
349
356
  system_prompt, prompt, agent_tools, openai_tools, tool_ctx
350
357
  )
@@ -365,10 +372,10 @@ AGENT RESPONSES:
365
372
  formatted_results = []
366
373
  for i, result in enumerate(results):
367
374
  if isinstance(result, Exception):
368
- formatted_results.append(f"Agent {i+1} Error:\n{str(result)}")
369
- await tool_ctx.error(f"Agent {i+1} failed: {str(result)}")
375
+ formatted_results.append(f"Agent {i + 1} Error:\n{str(result)}")
376
+ await tool_ctx.error(f"Agent {i + 1} failed: {str(result)}")
370
377
  else:
371
- formatted_results.append(f"Agent {i+1} Result:\n{result}")
378
+ formatted_results.append(f"Agent {i + 1} Result:\n{result}")
372
379
 
373
380
  return "\n\n---\n\n".join(formatted_results)
374
381
 
@@ -438,9 +445,7 @@ AGENT RESPONSES:
438
445
  completion_params["base_url"] = self.base_url_override
439
446
 
440
447
  # Make the model call
441
- response = litellm.completion(
442
- **completion_params # pyright: ignore
443
- )
448
+ response = litellm.completion(**completion_params) # pyright: ignore
444
449
 
445
450
  if len(response.choices) == 0: # pyright: ignore
446
451
  raise ValueError("No response choices returned")
@@ -487,20 +492,22 @@ AGENT RESPONSES:
487
492
  request_type = function_args.get("type", "ADDITIONAL_INFO")
488
493
  question = function_args.get("question", "")
489
494
  context = function_args.get("context", {})
490
- options = function_args.get("options", None)
491
-
495
+ options = function_args.get("options")
496
+
492
497
  # Convert string type to enum
493
498
  clarification_type = ClarificationType[request_type]
494
-
499
+
495
500
  # Request clarification
496
501
  answer = await self.request_clarification(
497
502
  request_type=clarification_type,
498
503
  question=question,
499
504
  context=context,
500
- options=options
505
+ options=options,
506
+ )
507
+
508
+ tool_result = self.format_clarification_in_output(
509
+ question, answer
501
510
  )
502
-
503
- tool_result = self.format_clarification_in_output(question, answer)
504
511
  except Exception as e:
505
512
  tool_result = f"Error processing clarification: {str(e)}"
506
513
  # Special handling for critic requests
@@ -509,17 +516,17 @@ AGENT RESPONSES:
509
516
  # Extract critic parameters
510
517
  review_type = function_args.get("review_type", "GENERAL")
511
518
  work_description = function_args.get("work_description", "")
512
- code_snippets = function_args.get("code_snippets", None)
513
- file_paths = function_args.get("file_paths", None)
514
- specific_concerns = function_args.get("specific_concerns", None)
515
-
519
+ code_snippets = function_args.get("code_snippets")
520
+ file_paths = function_args.get("file_paths")
521
+ specific_concerns = function_args.get("specific_concerns")
522
+
516
523
  # Request critical review
517
524
  tool_result = self.critic_protocol.request_review(
518
525
  review_type=review_type,
519
526
  work_description=work_description,
520
527
  code_snippets=code_snippets,
521
528
  file_paths=file_paths,
522
- specific_concerns=specific_concerns
529
+ specific_concerns=specific_concerns,
523
530
  )
524
531
  except Exception as e:
525
532
  tool_result = f"Error processing critic review: {str(e)}"
@@ -529,17 +536,17 @@ AGENT RESPONSES:
529
536
  # Extract review parameters
530
537
  focus = function_args.get("focus", "GENERAL")
531
538
  work_description = function_args.get("work_description", "")
532
- code_snippets = function_args.get("code_snippets", None)
533
- file_paths = function_args.get("file_paths", None)
534
- context = function_args.get("context", None)
535
-
539
+ code_snippets = function_args.get("code_snippets")
540
+ file_paths = function_args.get("file_paths")
541
+ context = function_args.get("context")
542
+
536
543
  # Request balanced review
537
544
  tool_result = self.review_protocol.request_review(
538
545
  focus=focus,
539
546
  work_description=work_description,
540
547
  code_snippets=code_snippets,
541
548
  file_paths=file_paths,
542
- context=context
549
+ context=context,
543
550
  )
544
551
  except Exception as e:
545
552
  tool_result = f"Error processing review: {str(e)}"
@@ -634,8 +641,5 @@ AGENT RESPONSE:
634
641
  tool_self = self # Create a reference to self for use in the closure
635
642
 
636
643
  @mcp_server.tool(name=self.name, description=self.description)
637
- async def dispatch_agent(
638
- prompts: str | list[str],
639
- ctx: MCPContext
640
- ) -> str:
644
+ async def dispatch_agent(prompts: str | list[str], ctx: MCPContext) -> str:
641
645
  return await tool_self.call(ctx, prompts=prompts)
@@ -5,13 +5,14 @@ from the main loop without human intervention.
5
5
  """
6
6
 
7
7
  import json
8
- from dataclasses import dataclass
9
- from typing import Any, Dict, List, Optional
10
8
  from enum import Enum
9
+ from typing import Any, Dict, List, Optional
10
+ from dataclasses import dataclass
11
11
 
12
12
 
13
13
  class ClarificationType(Enum):
14
14
  """Types of clarification requests."""
15
+
15
16
  AMBIGUOUS_INSTRUCTION = "ambiguous_instruction"
16
17
  MISSING_CONTEXT = "missing_context"
17
18
  MULTIPLE_OPTIONS = "multiple_options"
@@ -22,23 +23,25 @@ class ClarificationType(Enum):
22
23
  @dataclass
23
24
  class ClarificationRequest:
24
25
  """A request for clarification from an agent."""
25
-
26
+
26
27
  agent_id: str
27
28
  request_type: ClarificationType
28
29
  question: str
29
30
  context: Dict[str, Any]
30
31
  options: Optional[List[str]] = None
31
-
32
+
32
33
  def to_json(self) -> str:
33
34
  """Convert to JSON for transport."""
34
- return json.dumps({
35
- "agent_id": self.agent_id,
36
- "request_type": self.request_type.value,
37
- "question": self.question,
38
- "context": self.context,
39
- "options": self.options
40
- })
41
-
35
+ return json.dumps(
36
+ {
37
+ "agent_id": self.agent_id,
38
+ "request_type": self.request_type.value,
39
+ "question": self.question,
40
+ "context": self.context,
41
+ "options": self.options,
42
+ }
43
+ )
44
+
42
45
  @classmethod
43
46
  def from_json(cls, data: str) -> "ClarificationRequest":
44
47
  """Create from JSON string."""
@@ -48,44 +51,46 @@ class ClarificationRequest:
48
51
  request_type=ClarificationType(obj["request_type"]),
49
52
  question=obj["question"],
50
53
  context=obj["context"],
51
- options=obj.get("options")
54
+ options=obj.get("options"),
52
55
  )
53
56
 
54
57
 
55
58
  @dataclass
56
59
  class ClarificationResponse:
57
60
  """A response to a clarification request."""
58
-
61
+
59
62
  request_id: str
60
63
  answer: str
61
64
  additional_context: Optional[Dict[str, Any]] = None
62
-
65
+
63
66
  def to_json(self) -> str:
64
67
  """Convert to JSON for transport."""
65
- return json.dumps({
66
- "request_id": self.request_id,
67
- "answer": self.answer,
68
- "additional_context": self.additional_context
69
- })
68
+ return json.dumps(
69
+ {
70
+ "request_id": self.request_id,
71
+ "answer": self.answer,
72
+ "additional_context": self.additional_context,
73
+ }
74
+ )
70
75
 
71
76
 
72
77
  class ClarificationHandler:
73
78
  """Handles clarification requests from agents."""
74
-
79
+
75
80
  def __init__(self):
76
81
  self.pending_requests: Dict[str, ClarificationRequest] = {}
77
82
  self.request_counter = 0
78
-
83
+
79
84
  def create_request(
80
85
  self,
81
86
  agent_id: str,
82
87
  request_type: ClarificationType,
83
88
  question: str,
84
89
  context: Dict[str, Any],
85
- options: Optional[List[str]] = None
90
+ options: Optional[List[str]] = None,
86
91
  ) -> str:
87
92
  """Create a new clarification request.
88
-
93
+
89
94
  Returns:
90
95
  Request ID for tracking
91
96
  """
@@ -94,23 +99,23 @@ class ClarificationHandler:
94
99
  request_type=request_type,
95
100
  question=question,
96
101
  context=context,
97
- options=options
102
+ options=options,
98
103
  )
99
-
104
+
100
105
  request_id = f"clarify_{self.request_counter}"
101
106
  self.request_counter += 1
102
107
  self.pending_requests[request_id] = request
103
-
108
+
104
109
  return request_id
105
-
110
+
106
111
  def handle_request(self, request: ClarificationRequest) -> ClarificationResponse:
107
112
  """Handle a clarification request automatically.
108
-
113
+
109
114
  This method implements automatic clarification resolution
110
115
  based on context and common patterns.
111
116
  """
112
117
  request_id = f"clarify_{len(self.pending_requests)}"
113
-
118
+
114
119
  # Handle different types of clarification
115
120
  if request.request_type == ClarificationType.AMBIGUOUS_INSTRUCTION:
116
121
  # Try to clarify based on context
@@ -123,7 +128,7 @@ class ClarificationHandler:
123
128
  answer = "Add imports according to the language's conventions."
124
129
  else:
125
130
  answer = "Proceed with the most reasonable interpretation based on the context."
126
-
131
+
127
132
  elif request.request_type == ClarificationType.MISSING_CONTEXT:
128
133
  # Provide additional context based on what's missing
129
134
  if "import_path" in request.question.lower():
@@ -132,7 +137,7 @@ class ClarificationHandler:
132
137
  answer = "Match the existing code style in the file. Use the same indentation and formatting patterns."
133
138
  else:
134
139
  answer = "Analyze the surrounding code and project structure to infer the missing information."
135
-
140
+
136
141
  elif request.request_type == ClarificationType.MULTIPLE_OPTIONS:
137
142
  # Choose the best option based on context
138
143
  if request.options:
@@ -145,7 +150,7 @@ class ClarificationHandler:
145
150
  answer = f"Choose option: {request.options[0]}"
146
151
  else:
147
152
  answer = "Choose the most conventional approach based on the codebase patterns."
148
-
153
+
149
154
  elif request.request_type == ClarificationType.CONFIRMATION_NEEDED:
150
155
  # Auto-confirm safe operations
151
156
  if "add import" in request.question.lower():
@@ -154,67 +159,67 @@ class ClarificationHandler:
154
159
  answer = "Yes, use multi_edit for efficiency."
155
160
  else:
156
161
  answer = "Proceed if the operation is safe and reversible."
157
-
162
+
158
163
  else: # ADDITIONAL_INFO
159
164
  answer = "Continue with available information and make reasonable assumptions based on context."
160
-
165
+
161
166
  return ClarificationResponse(
162
167
  request_id=request_id,
163
168
  answer=answer,
164
- additional_context={"auto_resolved": True}
169
+ additional_context={"auto_resolved": True},
165
170
  )
166
171
 
167
172
 
168
173
  class AgentClarificationMixin:
169
174
  """Mixin for agents to request clarification."""
170
-
175
+
171
176
  def __init__(self, *args, **kwargs):
172
177
  super().__init__(*args, **kwargs)
173
178
  self.clarification_handler = ClarificationHandler()
174
179
  self.clarification_count = 0
175
180
  self.max_clarifications = 1 # Allow up to 1 clarification per task
176
-
181
+
177
182
  async def request_clarification(
178
183
  self,
179
184
  request_type: ClarificationType,
180
185
  question: str,
181
186
  context: Dict[str, Any],
182
- options: Optional[List[str]] = None
187
+ options: Optional[List[str]] = None,
183
188
  ) -> str:
184
189
  """Request clarification from the main loop.
185
-
190
+
186
191
  Args:
187
192
  request_type: Type of clarification needed
188
193
  question: The question to ask
189
194
  context: Relevant context for the question
190
195
  options: Optional list of choices
191
-
196
+
192
197
  Returns:
193
198
  The clarification response
194
-
199
+
195
200
  Raises:
196
201
  RuntimeError: If clarification limit exceeded
197
202
  """
198
203
  if self.clarification_count >= self.max_clarifications:
199
204
  raise RuntimeError("Clarification limit exceeded")
200
-
205
+
201
206
  self.clarification_count += 1
202
-
207
+
203
208
  # Create request
204
209
  request = ClarificationRequest(
205
- agent_id=getattr(self, 'agent_id', 'unknown'),
210
+ agent_id=getattr(self, "agent_id", "unknown"),
206
211
  request_type=request_type,
207
212
  question=question,
208
213
  context=context,
209
- options=options
214
+ options=options,
210
215
  )
211
-
216
+
212
217
  # In real implementation, this would communicate with main loop
213
218
  # For now, use the automatic handler
214
219
  response = self.clarification_handler.handle_request(request)
215
-
220
+
216
221
  return response.answer
217
-
222
+
218
223
  def format_clarification_in_output(self, question: str, answer: str) -> str:
219
224
  """Format clarification exchange for output."""
220
- return f"\nšŸ¤” Clarification needed: {question}\nāœ… Resolved: {answer}\n"
225
+ return f"\nšŸ¤” Clarification needed: {question}\nāœ… Resolved: {answer}\n"
@@ -2,16 +2,17 @@
2
2
 
3
3
  from typing import Any, Dict, List, Optional, override
4
4
 
5
- from hanzo_mcp.tools.common.base import BaseTool
6
- from mcp.server.fastmcp import Context as MCPContext
7
5
  from mcp.server import FastMCP
6
+ from mcp.server.fastmcp import Context as MCPContext
7
+
8
+ from hanzo_mcp.tools.common.base import BaseTool
8
9
 
9
10
 
10
11
  class ClarificationTool(BaseTool):
11
12
  """Tool for agents to request clarification from the main loop."""
12
-
13
+
13
14
  name = "request_clarification"
14
-
15
+
15
16
  @property
16
17
  @override
17
18
  def description(self) -> str:
@@ -40,29 +41,29 @@ request_clarification(
40
41
  context={"file_path": "/path/to/file.go", "undefined_symbol": "common"},
41
42
  options=["github.com/luxfi/node/common", "github.com/project/common"]
42
43
  )"""
43
-
44
+
44
45
  async def call(
45
46
  self,
46
47
  ctx: MCPContext,
47
48
  type: str,
48
49
  question: str,
49
50
  context: Dict[str, Any],
50
- options: Optional[List[str]] = None
51
+ options: Optional[List[str]] = None,
51
52
  ) -> str:
52
53
  """This is a placeholder - actual implementation happens in AgentTool."""
53
54
  # This tool is handled specially in the agent execution
54
55
  return f"Clarification request: {question}"
55
-
56
+
56
57
  def register(self, server: FastMCP) -> None:
57
58
  """Register the tool with the MCP server."""
58
59
  tool_self = self
59
-
60
+
60
61
  @server.tool(name=self.name, description=self.description)
61
62
  async def request_clarification(
62
63
  ctx: MCPContext,
63
64
  type: str,
64
65
  question: str,
65
66
  context: Dict[str, Any],
66
- options: Optional[List[str]] = None
67
+ options: Optional[List[str]] = None,
67
68
  ) -> str:
68
- return await tool_self.call(ctx, type, question, context, options)
69
+ return await tool_self.call(ctx, type, question, context, options)