hanzo-mcp 0.8.8__py3-none-any.whl → 0.8.13__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 (154) hide show
  1. hanzo_mcp/__init__.py +1 -3
  2. hanzo_mcp/analytics/posthog_analytics.py +4 -17
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +8 -17
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +2 -4
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +6 -7
  17. hanzo_mcp/tools/__init__.py +13 -29
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +6 -17
  21. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +15 -42
  22. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  23. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  24. hanzo_mcp/tools/agent/cli_tools.py +76 -75
  25. hanzo_mcp/tools/agent/code_auth.py +1 -3
  26. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  27. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  28. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  29. hanzo_mcp/tools/agent/network_tool.py +7 -18
  30. hanzo_mcp/tools/agent/prompt.py +1 -5
  31. hanzo_mcp/tools/agent/review_tool.py +10 -25
  32. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  33. hanzo_mcp/tools/agent/swarm_tool.py +16 -41
  34. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
  35. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  36. hanzo_mcp/tools/common/batch_tool.py +15 -45
  37. hanzo_mcp/tools/common/config_tool.py +9 -28
  38. hanzo_mcp/tools/common/context.py +1 -3
  39. hanzo_mcp/tools/common/critic_tool.py +1 -3
  40. hanzo_mcp/tools/common/decorators.py +2 -6
  41. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  42. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  43. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  44. hanzo_mcp/tools/common/mode.py +1 -5
  45. hanzo_mcp/tools/common/paginated_base.py +3 -11
  46. hanzo_mcp/tools/common/paginated_response.py +10 -30
  47. hanzo_mcp/tools/common/pagination.py +3 -9
  48. hanzo_mcp/tools/common/permissions.py +3 -9
  49. hanzo_mcp/tools/common/personality.py +9 -34
  50. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  51. hanzo_mcp/tools/common/stats.py +7 -19
  52. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  53. hanzo_mcp/tools/common/tool_disable.py +2 -6
  54. hanzo_mcp/tools/common/tool_list.py +2 -6
  55. hanzo_mcp/tools/common/validation.py +1 -3
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  72. hanzo_mcp/tools/filesystem/__init__.py +2 -3
  73. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  74. hanzo_mcp/tools/filesystem/base.py +4 -12
  75. hanzo_mcp/tools/filesystem/batch_search.py +35 -115
  76. hanzo_mcp/tools/filesystem/content_replace.py +4 -12
  77. hanzo_mcp/tools/filesystem/diff.py +2 -10
  78. hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
  79. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
  80. hanzo_mcp/tools/filesystem/edit.py +6 -18
  81. hanzo_mcp/tools/filesystem/find.py +3 -9
  82. hanzo_mcp/tools/filesystem/find_files.py +2 -6
  83. hanzo_mcp/tools/filesystem/git_search.py +9 -24
  84. hanzo_mcp/tools/filesystem/grep.py +9 -27
  85. hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
  86. hanzo_mcp/tools/filesystem/read.py +8 -26
  87. hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
  88. hanzo_mcp/tools/filesystem/search_tool.py +18 -62
  89. hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
  90. hanzo_mcp/tools/filesystem/tree.py +1 -3
  91. hanzo_mcp/tools/filesystem/watch.py +1 -3
  92. hanzo_mcp/tools/filesystem/write.py +1 -3
  93. hanzo_mcp/tools/jupyter/base.py +6 -20
  94. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  95. hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
  96. hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
  97. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  98. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  99. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  100. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  101. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  102. hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
  103. hanzo_mcp/tools/mcp/mcp_add.py +3 -5
  104. hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
  105. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  106. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  107. hanzo_mcp/tools/memory/__init__.py +33 -40
  108. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  109. hanzo_mcp/tools/memory/memory_tools.py +7 -19
  110. hanzo_mcp/tools/search/find_tool.py +10 -32
  111. hanzo_mcp/tools/search/unified_search.py +27 -81
  112. hanzo_mcp/tools/shell/__init__.py +2 -2
  113. hanzo_mcp/tools/shell/auto_background.py +2 -6
  114. hanzo_mcp/tools/shell/base.py +1 -5
  115. hanzo_mcp/tools/shell/base_process.py +5 -7
  116. hanzo_mcp/tools/shell/bash_session.py +7 -24
  117. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  118. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  119. hanzo_mcp/tools/shell/command_executor.py +26 -79
  120. hanzo_mcp/tools/shell/logs.py +4 -16
  121. hanzo_mcp/tools/shell/npx.py +2 -8
  122. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  123. hanzo_mcp/tools/shell/pkill.py +4 -12
  124. hanzo_mcp/tools/shell/process_tool.py +2 -8
  125. hanzo_mcp/tools/shell/processes.py +5 -17
  126. hanzo_mcp/tools/shell/run_background.py +1 -3
  127. hanzo_mcp/tools/shell/run_command.py +1 -3
  128. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  129. hanzo_mcp/tools/shell/session_manager.py +2 -6
  130. hanzo_mcp/tools/shell/session_storage.py +2 -6
  131. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  132. hanzo_mcp/tools/shell/uvx.py +4 -14
  133. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  134. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  135. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  136. hanzo_mcp/tools/todo/todo.py +1 -3
  137. hanzo_mcp/tools/todo/todo_read.py +3 -9
  138. hanzo_mcp/tools/todo/todo_write.py +6 -18
  139. hanzo_mcp/tools/vector/__init__.py +3 -9
  140. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  141. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  142. hanzo_mcp/tools/vector/index_tool.py +3 -9
  143. hanzo_mcp/tools/vector/infinity_store.py +11 -30
  144. hanzo_mcp/tools/vector/mock_infinity.py +159 -0
  145. hanzo_mcp/tools/vector/project_manager.py +4 -12
  146. hanzo_mcp/tools/vector/vector.py +2 -6
  147. hanzo_mcp/tools/vector/vector_index.py +8 -8
  148. hanzo_mcp/tools/vector/vector_search.py +7 -21
  149. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/METADATA +2 -2
  150. hanzo_mcp-0.8.13.dist-info/RECORD +193 -0
  151. hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
  152. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/WHEEL +0 -0
  153. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/entry_points.txt +0 -0
  154. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/top_level.txt +0 -0
@@ -52,9 +52,7 @@ def create_project_system_prompt(project_path: str):
52
52
  platform, _, os_version = get_os_info()
53
53
 
54
54
  # Get directory structure
55
- directory_structure = get_directory_structure(
56
- working_directory, max_depth=3, include_filtered=False
57
- )
55
+ directory_structure = get_directory_structure(working_directory, max_depth=3, include_filtered=False)
58
56
 
59
57
  # Get git information
60
58
  git_info = get_git_info(working_directory)
@@ -78,9 +76,7 @@ def create_project_system_prompt(project_path: str):
78
76
  return project_system_prompt
79
77
 
80
78
 
81
- def register_all_prompts(
82
- mcp_server: FastMCP, projects: list[str] | None = None
83
- ) -> None:
79
+ def register_all_prompts(mcp_server: FastMCP, projects: list[str] | None = None) -> None:
84
80
  @mcp_server.prompt(name="Compact current conversation")
85
81
  def compact() -> str:
86
82
  """
@@ -49,13 +49,9 @@ def format_todo_list_concise(todos: list[dict[str, Any]]) -> str:
49
49
  }.get(status, "[?]")
50
50
 
51
51
  # Create priority indicator
52
- priority_indicator = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(
53
- priority, "⚪"
54
- )
52
+ priority_indicator = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(priority, "⚪")
55
53
 
56
- formatted_lines.append(
57
- f"{status_indicator} {priority_indicator} {content} (id: {todo_id})"
58
- )
54
+ formatted_lines.append(f"{status_indicator} {priority_indicator} {content} (id: {todo_id})")
59
55
 
60
56
  return "\n".join(formatted_lines)
61
57
 
@@ -105,6 +101,4 @@ def get_project_todo_reminder(session_id: str | None = None) -> str:
105
101
 
106
102
  # Format the todo list and return the reminder with content
107
103
  formatted_todos = format_todo_list_concise(todos)
108
- return PROJECT_TODO_REMINDER.format(
109
- session_id=session_id, todo_list=formatted_todos
110
- )
104
+ return PROJECT_TODO_REMINDER.format(session_id=session_id, todo_list=formatted_todos)
@@ -596,9 +596,7 @@ def create_tool_category_prompt(category: str, tools: list[str]):
596
596
  "batch": BATCH_TOOL_EXAMPLES,
597
597
  }
598
598
 
599
- base_prompt = tool_descriptions.get(
600
- category, f"# {category.title()} Tools\n\nAvailable tools in this category:\n"
601
- )
599
+ base_prompt = tool_descriptions.get(category, f"# {category.title()} Tools\n\nAvailable tools in this category:\n")
602
600
 
603
601
  if category not in tool_descriptions:
604
602
  base_prompt += "\n".join(f"- **{tool}**: [Tool description]" for tool in tools)
@@ -51,9 +51,7 @@ def get_os_info() -> tuple[str, str, str]:
51
51
  return system, release, version
52
52
 
53
53
 
54
- def get_directory_structure(
55
- path: str, max_depth: int = 3, include_filtered: bool = False
56
- ) -> str:
54
+ def get_directory_structure(path: str, max_depth: int = 3, include_filtered: bool = False) -> str:
57
55
  """Get a directory structure similar to directory_tree tool.
58
56
 
59
57
  Args:
@@ -102,9 +100,7 @@ def get_directory_structure(
102
100
 
103
101
  try:
104
102
  # Sort entries: directories first, then files alphabetically
105
- entries = sorted(
106
- current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name)
107
- )
103
+ entries = sorted(current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
108
104
 
109
105
  for entry in entries:
110
106
  if entry.is_dir():
@@ -147,9 +143,7 @@ def get_directory_structure(
147
143
  # Format based on type
148
144
  if item["type"] == "directory":
149
145
  if "skipped" in item:
150
- lines.append(
151
- f"{indent}{item['name']}/ [skipped - {item['skipped']}]"
152
- )
146
+ lines.append(f"{indent}{item['name']}/ [skipped - {item['skipped']}]")
153
147
  else:
154
148
  lines.append(f"{indent}{item['name']}/")
155
149
  # Add children with increased indentation if present
@@ -228,9 +222,7 @@ def get_git_info(path: str) -> dict[str, str | None]:
228
222
  change_type = item.change_type
229
223
  status_lines.append(f"{change_type[0].upper()} {item.a_path}")
230
224
  if len(staged_files) > 25:
231
- status_lines.append(
232
- f"... and {len(staged_files) - 25} more staged files"
233
- )
225
+ status_lines.append(f"... and {len(staged_files) - 25} more staged files")
234
226
 
235
227
  # Check for unstaged changes
236
228
  unstaged_files = list(repo.index.diff(None))
@@ -238,9 +230,7 @@ def get_git_info(path: str) -> dict[str, str | None]:
238
230
  for item in unstaged_files[:25]: # Limit to first 25
239
231
  status_lines.append(f"M {item.a_path}")
240
232
  if len(unstaged_files) > 25:
241
- status_lines.append(
242
- f"... and {len(unstaged_files) - 25} more modified files"
243
- )
233
+ status_lines.append(f"... and {len(unstaged_files) - 25} more modified files")
244
234
 
245
235
  # Check for untracked files
246
236
  untracked_files = repo.untracked_files
@@ -248,13 +238,9 @@ def get_git_info(path: str) -> dict[str, str | None]:
248
238
  for file in untracked_files[:25]: # Limit to first 25
249
239
  status_lines.append(f"?? {file}")
250
240
  if len(untracked_files) > 25:
251
- status_lines.append(
252
- f"... and {len(untracked_files) - 25} more untracked files"
253
- )
241
+ status_lines.append(f"... and {len(untracked_files) - 25} more untracked files")
254
242
 
255
- git_status = (
256
- "\n".join(status_lines) if status_lines else "Working tree clean"
257
- )
243
+ git_status = "\n".join(status_lines) if status_lines else "Working tree clean"
258
244
 
259
245
  except Exception:
260
246
  git_status = "Unable to get git status"
hanzo_mcp/server.py CHANGED
@@ -9,15 +9,16 @@ import threading
9
9
  from typing import Literal, cast, final
10
10
 
11
11
  # Suppress litellm deprecation warnings about event loop
12
- warnings.filterwarnings(
13
- "ignore", message="There is no current event loop", category=DeprecationWarning
14
- )
12
+ warnings.filterwarnings("ignore", message="There is no current event loop", category=DeprecationWarning)
15
13
 
16
14
  try:
17
15
  from fastmcp import FastMCP
18
16
  except ImportError:
19
17
  # Fallback for older MCP versions
20
- from mcp.server import FastMCP
18
+ try:
19
+ from mcp.server import FastMCP
20
+ except ImportError:
21
+ from mcp import FastMCP
21
22
 
22
23
  # Import our enhanced server
23
24
  from hanzo_mcp.tools import register_all_tools
@@ -173,9 +174,7 @@ class HanzoMCPServer:
173
174
  signal.signal(signal.SIGINT, signal_handler)
174
175
 
175
176
  # Start background cleanup thread for periodic cleanup
176
- self._cleanup_thread = threading.Thread(
177
- target=self._background_cleanup, daemon=True
178
- )
177
+ self._cleanup_thread = threading.Thread(target=self._background_cleanup, daemon=True)
179
178
  self._cleanup_thread.start()
180
179
 
181
180
  self._cleanup_registered = True
@@ -35,6 +35,7 @@ try: # pragma: no cover
35
35
  from hanzo_mcp.tools.todo import register_todo_tools
36
36
  from hanzo_mcp.tools.agent import register_agent_tools
37
37
  from hanzo_mcp.tools.shell import register_shell_tools
38
+ from hanzo_mcp.tools.common import register_batch_tool, register_critic_tool, register_thinking_tool
38
39
  from hanzo_mcp.tools.editor import (
39
40
  NeovimEditTool,
40
41
  NeovimCommandTool,
@@ -64,19 +65,19 @@ try: # pragma: no cover
64
65
  register_memory_tools = None # type: ignore
65
66
  except Exception:
66
67
  # Minimal surface to allow submodule imports elsewhere
67
- # Define fallback functions for required imports
68
+ # Define stub functions for required imports
68
69
  def activate_mode_from_env():
69
- """Fallback: No mode activation when imports fail."""
70
- return None
70
+ pass
71
+
71
72
  class ModeLoader:
72
73
  @staticmethod
73
74
  def get_enabled_tools_from_mode(base_enabled_tools=None, force_mode=None):
74
- """Fallback: Return base tools when imports fail."""
75
75
  return base_enabled_tools or {}
76
+
76
77
  @staticmethod
77
78
  def apply_environment_from_mode():
78
- """Fallback: No environment changes when imports fail."""
79
- return None
79
+ pass
80
+
80
81
 
81
82
  # Try to import LSP tool
82
83
  try:
@@ -136,9 +137,7 @@ def register_all_tools(
136
137
 
137
138
  logger = logging.getLogger(__name__)
138
139
  if plugins:
139
- logger.info(
140
- f"Loaded {len(plugins)} user plugin tools: {', '.join(plugins.keys())}"
141
- )
140
+ logger.info(f"Loaded {len(plugins)} user plugin tools: {', '.join(plugins.keys())}")
142
141
  except Exception as e:
143
142
  import logging
144
143
 
@@ -151,9 +150,7 @@ def register_all_tools(
151
150
  # First check for mode activation from environment
152
151
  activate_mode_from_env()
153
152
 
154
- tool_config = ModeLoader.get_enabled_tools_from_mode(
155
- base_enabled_tools=enabled_tools, force_mode=force_mode
156
- )
153
+ tool_config = ModeLoader.get_enabled_tools_from_mode(base_enabled_tools=enabled_tools, force_mode=force_mode)
157
154
  # Apply mode environment variables
158
155
  ModeLoader.apply_environment_from_mode()
159
156
  else:
@@ -198,9 +195,7 @@ def register_all_tools(
198
195
  search_paths = [str(path) for path in permission_manager.allowed_paths]
199
196
  project_manager = ProjectVectorManager(
200
197
  global_db_path=vector_config.get("data_path"),
201
- embedding_model=vector_config.get(
202
- "embedding_model", "text-embedding-3-small"
203
- ),
198
+ embedding_model=vector_config.get("embedding_model", "text-embedding-3-small"),
204
199
  dimension=vector_config.get("dimension", 1536),
205
200
  )
206
201
  # Auto-detect projects from search paths
@@ -227,9 +222,7 @@ def register_all_tools(
227
222
  }
228
223
 
229
224
  if any(jupyter_enabled.values()):
230
- jupyter_tools = register_jupyter_tools(
231
- mcp_server, permission_manager, enabled_tools=jupyter_enabled
232
- )
225
+ jupyter_tools = register_jupyter_tools(mcp_server, permission_manager, enabled_tools=jupyter_enabled)
233
226
  for tool in jupyter_tools:
234
227
  all_tools[tool.name] = tool
235
228
 
@@ -240,11 +233,7 @@ def register_all_tools(
240
233
  all_tools[tool.name] = tool
241
234
 
242
235
  # Register agent tools if enabled
243
- agent_enabled = (
244
- enable_agent_tool
245
- or is_tool_enabled("agent", False)
246
- or is_tool_enabled("dispatch_agent", False)
247
- )
236
+ agent_enabled = enable_agent_tool or is_tool_enabled("agent", False) or is_tool_enabled("dispatch_agent", False)
248
237
  swarm_enabled = is_tool_enabled("swarm", False)
249
238
 
250
239
  if agent_enabled or swarm_enabled:
@@ -260,12 +249,7 @@ def register_all_tools(
260
249
  )
261
250
  # Filter based on what's enabled
262
251
  for tool in agent_tools:
263
- if (
264
- tool.name == "agent"
265
- and agent_enabled
266
- or tool.name == "swarm"
267
- and swarm_enabled
268
- ):
252
+ if tool.name == "agent" and agent_enabled or tool.name == "swarm" and swarm_enabled:
269
253
  all_tools[tool.name] = tool
270
254
  elif tool.name in ["claude", "codex", "gemini", "grok", "code_auth"]:
271
255
  # CLI tools and auth are always included when agent tools are enabled
@@ -108,6 +108,7 @@ def register_agent_tools(
108
108
  if memory_backend is not None:
109
109
  p["memory_backend"] = memory_backend
110
110
  return await tool_self.call(ctx, **p)
111
+
111
112
  return tool_self
112
113
 
113
114
  # Create auth management tool
@@ -127,6 +128,6 @@ def register_agent_tools(
127
128
 
128
129
  # Register all CLI tools (includes claude, codex, gemini, grok, etc.)
129
130
  cli_tools = register_cli_tools(mcp_server, permission_manager)
130
-
131
+
131
132
  # Return list of registered tools
132
133
  return [agent_tool, network_tool, code_auth_tool] + cli_tools
@@ -118,23 +118,17 @@ class AgentParams(TypedDict, total=False):
118
118
  class RPCAgent:
119
119
  """Long-running RPC agent."""
120
120
 
121
- def __init__(
122
- self, agent_id: str, model: str, system_prompt: str, tools: List[BaseTool]
123
- ):
121
+ def __init__(self, agent_id: str, model: str, system_prompt: str, tools: List[BaseTool]):
124
122
  self.agent_id = agent_id
125
123
  self.model = model
126
124
  self.system_prompt = system_prompt
127
125
  self.tools = tools
128
- self.messages: List[ChatCompletionMessageParam] = [
129
- {"role": "system", "content": system_prompt}
130
- ]
126
+ self.messages: List[ChatCompletionMessageParam] = [{"role": "system", "content": system_prompt}]
131
127
  self.created_at = time.time()
132
128
  self.last_used = time.time()
133
129
  self.call_count = 0
134
130
 
135
- async def call_method(
136
- self, method: str, args: Dict[str, Any], tool_ctx: ToolContext
137
- ) -> str:
131
+ async def call_method(self, method: str, args: Dict[str, Any], tool_ctx: ToolContext) -> str:
138
132
  """Call a method on the RPC agent."""
139
133
  self.last_used = time.time()
140
134
  self.call_count += 1
@@ -189,15 +183,9 @@ class AgentTool(BaseTool):
189
183
 
190
184
  # Available tools
191
185
  self.available_tools: list[BaseTool] = []
192
- self.available_tools.extend(
193
- get_read_only_filesystem_tools(self.permission_manager)
194
- )
195
- self.available_tools.extend(
196
- get_read_only_jupyter_tools(self.permission_manager)
197
- )
198
- self.available_tools.append(
199
- BatchTool({t.name: t for t in self.available_tools})
200
- )
186
+ self.available_tools.extend(get_read_only_filesystem_tools(self.permission_manager))
187
+ self.available_tools.extend(get_read_only_jupyter_tools(self.permission_manager))
188
+ self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))
201
189
 
202
190
  @property
203
191
  @override
@@ -272,14 +260,10 @@ Modes:
272
260
 
273
261
  if len(prompt_list) == 1:
274
262
  await tool_ctx.info("Launching agent")
275
- result = await self._execute_agent(
276
- prompt_list[0], params.get("model"), tool_ctx
277
- )
263
+ result = await self._execute_agent(prompt_list[0], params.get("model"), tool_ctx)
278
264
  else:
279
265
  await tool_ctx.info(f"Launching {len(prompt_list)} agents in parallel")
280
- result = await self._execute_multiple_agents(
281
- prompt_list, params.get("model"), tool_ctx
282
- )
266
+ result = await self._execute_multiple_agents(prompt_list, params.get("model"), tool_ctx)
283
267
 
284
268
  execution_time = time.time() - start_time
285
269
 
@@ -389,17 +373,13 @@ Use 'agent --action call --agent-id {agent_id} --method <method> --args <args>'
389
373
  absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
390
374
  return bool(re.search(absolute_path_pattern, prompt))
391
375
 
392
- async def _execute_agent(
393
- self, prompt: str, model: Optional[str], tool_ctx: ToolContext
394
- ) -> str:
376
+ async def _execute_agent(self, prompt: str, model: Optional[str], tool_ctx: ToolContext) -> str:
395
377
  """Execute a single agent (simplified - would use full logic from agent_tool.py)."""
396
378
  # This would integrate the full agent execution logic from agent_tool.py
397
379
  # For now, return a placeholder
398
380
  return f"Executed agent with prompt: {prompt[:100]}..."
399
381
 
400
- async def _execute_multiple_agents(
401
- self, prompts: List[str], model: Optional[str], tool_ctx: ToolContext
402
- ) -> str:
382
+ async def _execute_multiple_agents(self, prompts: List[str], model: Optional[str], tool_ctx: ToolContext) -> str:
403
383
  """Execute multiple agents in parallel."""
404
384
  tasks = []
405
385
  for prompt in prompts:
@@ -49,8 +49,7 @@ except ImportError:
49
49
  """Stub State class when hanzo-agents is not available."""
50
50
 
51
51
  def __init__(self):
52
- """Initialize empty state."""
53
- self.data = {}
52
+ pass
54
53
 
55
54
  def to_dict(self):
56
55
  return {}
@@ -198,9 +197,7 @@ class MCPAgent(Agent):
198
197
  adapter = MCPToolAdapter(mcp_tool, ctx)
199
198
  self.register_tool(adapter)
200
199
 
201
- async def run(
202
- self, state: MCPAgentState, history: History, network: Network
203
- ) -> InferenceResult:
200
+ async def run(self, state: MCPAgentState, history: History, network: Network) -> InferenceResult:
204
201
  """Execute the agent."""
205
202
  # Get current prompt
206
203
  if state.current_prompt_index >= len(state.prompts):
@@ -325,12 +322,8 @@ Usage notes:
325
322
 
326
323
  # Set up available tools
327
324
  self.available_tools: list[BaseTool] = []
328
- self.available_tools.extend(
329
- get_read_only_filesystem_tools(self.permission_manager)
330
- )
331
- self.available_tools.extend(
332
- get_read_only_jupyter_tools(self.permission_manager)
333
- )
325
+ self.available_tools.extend(get_read_only_filesystem_tools(self.permission_manager))
326
+ self.available_tools.extend(get_read_only_jupyter_tools(self.permission_manager))
334
327
 
335
328
  # Add edit tools
336
329
  self.available_tools.append(Edit(self.permission_manager))
@@ -342,9 +335,7 @@ Usage notes:
342
335
  self.available_tools.append(ReviewTool())
343
336
  self.available_tools.append(IChingTool())
344
337
 
345
- self.available_tools.append(
346
- BatchTool({t.name: t for t in self.available_tools})
347
- )
338
+ self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))
348
339
 
349
340
  @override
350
341
  async def call(
@@ -400,9 +391,7 @@ Usage notes:
400
391
  times = (concurrency + len(prompt_list) - 1) // len(prompt_list)
401
392
  prompt_list = (prompt_list * times)[:concurrency]
402
393
 
403
- await tool_ctx.info(
404
- f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK"
405
- )
394
+ await tool_ctx.info(f"Launching {len(prompt_list)} agent(s) using hanzo-agents SDK")
406
395
 
407
396
  # Determine model and agent type
408
397
  model = params.get("model", self.model_override)
@@ -96,7 +96,7 @@ class AgentTool(AgentClarificationMixin, BaseTool):
96
96
  Returns:
97
97
  Tool description
98
98
  """
99
- # Glob is now implemented via find_files tool
99
+ # TODO: Add glob when it is implemented
100
100
  at = [t.name for t in self.available_tools]
101
101
 
102
102
  return f"""Launch a new agent that has access to the following tools: {at}. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries, use the Agent tool to perform the search for you.
@@ -151,12 +151,8 @@ Usage notes:
151
151
  self.max_iterations = max_iterations
152
152
  self.max_tool_uses = max_tool_uses
153
153
  self.available_tools: list[BaseTool] = []
154
- self.available_tools.extend(
155
- get_read_only_filesystem_tools(self.permission_manager)
156
- )
157
- self.available_tools.extend(
158
- get_read_only_jupyter_tools(self.permission_manager)
159
- )
154
+ self.available_tools.extend(get_read_only_filesystem_tools(self.permission_manager))
155
+ self.available_tools.extend(get_read_only_jupyter_tools(self.permission_manager))
160
156
 
161
157
  # Always add edit tools - agents should have edit access
162
158
  self.available_tools.append(Edit(self.permission_manager))
@@ -174,9 +170,7 @@ Usage notes:
174
170
  # Add I Ching tool for creative guidance
175
171
  self.available_tools.append(IChingTool())
176
172
 
177
- self.available_tools.append(
178
- BatchTool({t.name: t for t in self.available_tools})
179
- )
173
+ self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))
180
174
 
181
175
  # Initialize protocols
182
176
  self.critic_protocol = CriticProtocol()
@@ -239,9 +233,7 @@ Example of correct usage:
239
233
  absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
240
234
  for prompt in prompt_list:
241
235
  if not re.search(absolute_path_pattern, prompt):
242
- await tool_ctx.error(
243
- f"Prompt does not contain absolute path: {prompt[:50]}..."
244
- )
236
+ await tool_ctx.error(f"Prompt does not contain absolute path: {prompt[:50]}...")
245
237
  return """Error: All prompts must contain at least one absolute path.
246
238
 
247
239
  IMPORTANT REMINDER FOR CLAUDE:
@@ -271,9 +263,7 @@ AGENT RESPONSE:
271
263
 
272
264
  AGENT RESPONSES:
273
265
  {result}"""
274
- await tool_ctx.info(
275
- f"Multi-agent execution completed in {execution_time:.2f}s"
276
- )
266
+ await tool_ctx.info(f"Multi-agent execution completed in {execution_time:.2f}s")
277
267
  return formatted_result
278
268
 
279
269
  async def _execute_agent(self, prompt: str, tool_ctx: ToolContext) -> str:
@@ -310,9 +300,7 @@ AGENT RESPONSES:
310
300
 
311
301
  # Execute agent
312
302
  await tool_ctx.info(f"Executing agent task: {prompt[:50]}...")
313
- result = await self._execute_agent_with_tools(
314
- system_prompt, prompt, agent_tools, openai_tools, tool_ctx
315
- )
303
+ result = await self._execute_agent_with_tools(system_prompt, prompt, agent_tools, openai_tools, tool_ctx)
316
304
  except Exception as e:
317
305
  # Log and return error result
318
306
  error_message = f"Error executing agent: {str(e)}"
@@ -321,9 +309,7 @@ AGENT RESPONSES:
321
309
 
322
310
  return result if result else "No results returned from agent"
323
311
 
324
- async def _execute_multiple_agents(
325
- self, prompts: list[str], tool_ctx: ToolContext
326
- ) -> str:
312
+ async def _execute_multiple_agents(self, prompts: list[str], tool_ctx: ToolContext) -> str:
327
313
  """Execute multiple agents concurrently.
328
314
 
329
315
  Args:
@@ -352,9 +338,7 @@ AGENT RESPONSES:
352
338
  tasks = []
353
339
  for i, prompt in enumerate(prompts):
354
340
  await tool_ctx.info(f"Creating agent task {i + 1}: {prompt[:50]}...")
355
- task = self._execute_agent_with_tools(
356
- system_prompt, prompt, agent_tools, openai_tools, tool_ctx
357
- )
341
+ task = self._execute_agent_with_tools(system_prompt, prompt, agent_tools, openai_tools, tool_ctx)
358
342
  tasks.append(task)
359
343
 
360
344
  # Execute all agents concurrently
@@ -413,9 +397,7 @@ AGENT RESPONSES:
413
397
  total_tool_use_count = 0
414
398
  iteration_count = 0
415
399
  max_tool_uses = self.max_tool_uses # Safety limit to prevent infinite loops
416
- max_iterations = (
417
- self.max_iterations
418
- ) # Add a maximum number of iterations for safety
400
+ max_iterations = self.max_iterations # Add a maximum number of iterations for safety
419
401
 
420
402
  # Execute until the agent completes or reaches the limit
421
403
  while total_tool_use_count < max_tool_uses and iteration_count < max_iterations:
@@ -480,9 +462,7 @@ AGENT RESPONSES:
480
462
  function_args = {}
481
463
 
482
464
  # Find the matching tool
483
- tool = next(
484
- (t for t in available_tools if t.name == function_name), None
485
- )
465
+ tool = next((t for t in available_tools if t.name == function_name), None)
486
466
  if not tool:
487
467
  tool_result = f"Error: Tool '{function_name}' not found"
488
468
  # Special handling for clarification requests
@@ -505,9 +485,7 @@ AGENT RESPONSES:
505
485
  options=options,
506
486
  )
507
487
 
508
- tool_result = self.format_clarification_in_output(
509
- question, answer
510
- )
488
+ tool_result = self.format_clarification_in_output(question, answer)
511
489
  except Exception as e:
512
490
  tool_result = f"Error processing clarification: {str(e)}"
513
491
  # Special handling for critic requests
@@ -552,9 +530,7 @@ AGENT RESPONSES:
552
530
  tool_result = f"Error processing review: {str(e)}"
553
531
  else:
554
532
  try:
555
- tool_result = await tool.call(
556
- ctx=tool_ctx.mcp_context, **function_args
557
- )
533
+ tool_result = await tool.call(ctx=tool_ctx.mcp_context, **function_args)
558
534
  except Exception as e:
559
535
  tool_result = f"Error executing {function_name}: {str(e)}"
560
536
 
@@ -572,9 +548,7 @@ AGENT RESPONSES:
572
548
  )
573
549
 
574
550
  # Log progress
575
- await tool_ctx.info(
576
- f"Processed {len(message.tool_calls)} tool calls. Total: {total_tool_use_count}"
577
- )
551
+ await tool_ctx.info(f"Processed {len(message.tool_calls)} tool calls. Total: {total_tool_use_count}")
578
552
 
579
553
  except Exception as e:
580
554
  await tool_ctx.error(f"Error in model call: {str(e)}")
@@ -602,8 +576,7 @@ AGENT RESPONSES:
602
576
  )
603
577
 
604
578
  return (
605
- final_response.choices[0].message.content
606
- or "Agent reached max iteration limit without a response."
579
+ final_response.choices[0].message.content or "Agent reached max iteration limit without a response."
607
580
  ) # pyright: ignore
608
581
  except Exception as e:
609
582
  await tool_ctx.error(f"Error in final model call: {str(e)}")
@@ -96,9 +96,7 @@ class ClaudeDesktopAuth:
96
96
  except Exception:
97
97
  return None
98
98
 
99
- async def login_interactive(
100
- self, account: Optional[str] = None, headless: bool = False
101
- ) -> Tuple[bool, str]:
99
+ async def login_interactive(self, account: Optional[str] = None, headless: bool = False) -> Tuple[bool, str]:
102
100
  """Login to Claude Desktop interactively.
103
101
 
104
102
  Args:
@@ -192,9 +190,7 @@ class ClaudeDesktopAuth:
192
190
  # For now, return a placeholder
193
191
  return False, "Headless login not yet implemented"
194
192
 
195
- async def _exchange_code_for_session(
196
- self, code: str, account: Optional[str]
197
- ) -> bool:
193
+ async def _exchange_code_for_session(self, code: str, account: Optional[str]) -> bool:
198
194
  """Exchange auth code for session token."""
199
195
  # This would make API calls to exchange the code
200
196
  # For now, create a mock session
@@ -357,9 +353,7 @@ class ClaudeDesktopAuth:
357
353
  # Generate agent-specific account
358
354
  return f"agent_{agent_id}@claude.local"
359
355
 
360
- async def ensure_agent_auth(
361
- self, agent_id: str, force_new: bool = False
362
- ) -> Tuple[bool, str]:
356
+ async def ensure_agent_auth(self, agent_id: str, force_new: bool = False) -> Tuple[bool, str]:
363
357
  """Ensure an agent is authenticated with its own account.
364
358
 
365
359
  Args: