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
@@ -27,9 +27,7 @@ class CommandExecutor:
27
27
  comprehensive error handling, permissions checking, and progress tracking.
28
28
  """
29
29
 
30
- def __init__(
31
- self, permission_manager: PermissionManager, verbose: bool = False
32
- ) -> None:
30
+ def __init__(self, permission_manager: PermissionManager, verbose: bool = False) -> None:
33
31
  """Initialize command execution.
34
32
 
35
33
  Args:
@@ -94,9 +92,7 @@ class CommandExecutor:
94
92
  user_shell = os.environ.get("SHELL", "/bin/bash")
95
93
  return os.path.basename(user_shell).lower(), user_shell
96
94
 
97
- def _format_win32_shell_command(
98
- self, shell_basename, user_shell, command, use_login_shell=True
99
- ):
95
+ def _format_win32_shell_command(self, shell_basename, user_shell, command, use_login_shell=True):
100
96
  """Format a command for execution with the appropriate Windows shell.
101
97
 
102
98
  Args:
@@ -250,9 +246,7 @@ class CommandExecutor:
250
246
 
251
247
  # Check if the command is allowed
252
248
  if not self.is_command_allowed(command):
253
- return CommandResult(
254
- return_code=1, error_message=f"Command not allowed: {command}"
255
- )
249
+ return CommandResult(return_code=1, error_message=f"Command not allowed: {command}")
256
250
 
257
251
  # Check working directory permissions if specified
258
252
  if cwd:
@@ -263,9 +257,7 @@ class CommandExecutor:
263
257
  )
264
258
 
265
259
  if not self.permission_manager.is_path_allowed(cwd):
266
- return CommandResult(
267
- return_code=1, error_message=f"Working directory not allowed: {cwd}"
268
- )
260
+ return CommandResult(return_code=1, error_message=f"Working directory not allowed: {cwd}")
269
261
 
270
262
  # Set up environment
271
263
  command_env: dict[str, str] = os.environ.copy()
@@ -281,9 +273,7 @@ class CommandExecutor:
281
273
  self._log(f"Using shell on Windows: {user_shell} ({shell_basename})")
282
274
 
283
275
  # Format command using helper method
284
- shell_cmd = self._format_win32_shell_command(
285
- shell_basename, user_shell, command, use_login_shell
286
- )
276
+ shell_cmd = self._format_win32_shell_command(shell_basename, user_shell, command, use_login_shell)
287
277
 
288
278
  # Use shell for command execution
289
279
  process = await asyncio.create_subprocess_shell(
@@ -312,19 +302,13 @@ class CommandExecutor:
312
302
  self._log(f"Escaped command: {escaped_command}")
313
303
 
314
304
  # Wrap command with appropriate shell invocation
315
- if (
316
- shell_basename == "zsh"
317
- or shell_basename == "bash"
318
- or shell_basename == "fish"
319
- ):
305
+ if shell_basename == "zsh" or shell_basename == "bash" or shell_basename == "fish":
320
306
  shell_cmd = f"{user_shell} -l -c '{escaped_command}'"
321
307
  else:
322
308
  # Default fallback
323
309
  shell_cmd = f"{user_shell} -c '{escaped_command}'"
324
310
  else:
325
- self._log(
326
- f"Using shell for command with shell operators: {command}"
327
- )
311
+ self._log(f"Using shell for command with shell operators: {command}")
328
312
  # Escape single quotes in command for shell execution
329
313
  escaped_command = command.replace("'", "'\\''")
330
314
  self._log(f"Original command: {command}")
@@ -354,9 +338,7 @@ class CommandExecutor:
354
338
 
355
339
  # Wait for the process to complete with timeout
356
340
  try:
357
- stdout_bytes, stderr_bytes = await asyncio.wait_for(
358
- process.communicate(), timeout=timeout
359
- )
341
+ stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(), timeout=timeout)
360
342
 
361
343
  return CommandResult(
362
344
  return_code=process.returncode or 0,
@@ -376,9 +358,7 @@ class CommandExecutor:
376
358
  )
377
359
  except Exception as e:
378
360
  self._log(f"Command execution error: {str(e)}")
379
- return CommandResult(
380
- return_code=1, error_message=f"Error executing command: {str(e)}"
381
- )
361
+ return CommandResult(return_code=1, error_message=f"Error executing command: {str(e)}")
382
362
 
383
363
  async def execute_script(
384
364
  self,
@@ -415,9 +395,7 @@ class CommandExecutor:
415
395
  )
416
396
 
417
397
  if not self.permission_manager.is_path_allowed(cwd):
418
- return CommandResult(
419
- return_code=1, error_message=f"Working directory not allowed: {cwd}"
420
- )
398
+ return CommandResult(return_code=1, error_message=f"Working directory not allowed: {cwd}")
421
399
 
422
400
  # Check if we need special handling for this interpreter
423
401
  interpreter_name = interpreter.split()[0].lower()
@@ -469,9 +447,7 @@ class CommandExecutor:
469
447
  self._log(f"Using shell on Windows for interpreter: {user_shell}")
470
448
 
471
449
  # Format command using helper method for the interpreter
472
- shell_cmd = self._format_win32_shell_command(
473
- shell_basename, user_shell, interpreter, use_login_shell
474
- )
450
+ shell_cmd = self._format_win32_shell_command(shell_basename, user_shell, interpreter, use_login_shell)
475
451
 
476
452
  # Create and run the process with shell
477
453
  process = await asyncio.create_subprocess_shell(
@@ -516,9 +492,7 @@ class CommandExecutor:
516
492
  # Wait for the process to complete with timeout
517
493
  try:
518
494
  script_bytes: bytes = script.encode("utf-8")
519
- stdout_bytes, stderr_bytes = await asyncio.wait_for(
520
- process.communicate(script_bytes), timeout=timeout
521
- )
495
+ stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(script_bytes), timeout=timeout)
522
496
 
523
497
  return CommandResult(
524
498
  return_code=process.returncode or 0,
@@ -538,9 +512,7 @@ class CommandExecutor:
538
512
  )
539
513
  except Exception as e:
540
514
  self._log(f"Script execution error: {str(e)}")
541
- return CommandResult(
542
- return_code=1, error_message=f"Error executing script: {str(e)}"
543
- )
515
+ return CommandResult(return_code=1, error_message=f"Error executing script: {str(e)}")
544
516
 
545
517
  async def _handle_fish_script(
546
518
  self,
@@ -591,9 +563,7 @@ class CommandExecutor:
591
563
 
592
564
  # Wait for the process to complete with timeout
593
565
  try:
594
- stdout_bytes, stderr_bytes = await asyncio.wait_for(
595
- process.communicate(), timeout=timeout
596
- )
566
+ stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(), timeout=timeout)
597
567
 
598
568
  return CommandResult(
599
569
  return_code=process.returncode or 0,
@@ -613,9 +583,7 @@ class CommandExecutor:
613
583
  )
614
584
  except Exception as e:
615
585
  self._log(f"Fish script execution error: {str(e)}")
616
- return CommandResult(
617
- return_code=1, error_message=f"Error executing Fish script: {str(e)}"
618
- )
586
+ return CommandResult(return_code=1, error_message=f"Error executing Fish script: {str(e)}")
619
587
 
620
588
  async def execute_script_from_file(
621
589
  self,
@@ -671,9 +639,7 @@ class CommandExecutor:
671
639
  command_env.update(env)
672
640
 
673
641
  # Create a temporary file for the script
674
- with tempfile.NamedTemporaryFile(
675
- suffix=extension, mode="w", delete=False
676
- ) as temp:
642
+ with tempfile.NamedTemporaryFile(suffix=extension, mode="w", delete=False) as temp:
677
643
  temp_path = temp.name
678
644
  _ = temp.write(script) # Explicitly ignore the return value
679
645
 
@@ -696,9 +662,7 @@ class CommandExecutor:
696
662
  wsl_path = temp_path.replace("\\", "/")
697
663
  self._log(f"WSL path conversion may be incomplete: {wsl_path}")
698
664
 
699
- self._log(
700
- f"Converted Windows path '{temp_path}' to WSL path '{wsl_path}'"
701
- )
665
+ self._log(f"Converted Windows path '{temp_path}' to WSL path '{wsl_path}'")
702
666
  temp_path = wsl_path
703
667
 
704
668
  # Build the command including args
@@ -709,13 +673,9 @@ class CommandExecutor:
709
673
  cmd += " " + " ".join(args)
710
674
 
711
675
  # Format command using helper method
712
- shell_cmd = self._format_win32_shell_command(
713
- shell_basename, user_shell, cmd, use_login_shell
714
- )
676
+ shell_cmd = self._format_win32_shell_command(shell_basename, user_shell, cmd, use_login_shell)
715
677
 
716
- self._log(
717
- f"Executing script from file on Windows with shell: {shell_cmd}"
718
- )
678
+ self._log(f"Executing script from file on Windows with shell: {shell_cmd}")
719
679
 
720
680
  # Create and run the process with shell
721
681
  process = await asyncio.create_subprocess_shell(
@@ -741,9 +701,7 @@ class CommandExecutor:
741
701
  # Create command that runs script through login shell
742
702
  shell_cmd = f"{user_shell} -l -c '{cmd}'"
743
703
 
744
- self._log(
745
- f"Executing script from file with login shell: {shell_cmd}"
746
- )
704
+ self._log(f"Executing script from file with login shell: {shell_cmd}")
747
705
 
748
706
  # Create and run the process with shell
749
707
  process = await asyncio.create_subprocess_shell(
@@ -772,9 +730,7 @@ class CommandExecutor:
772
730
 
773
731
  # Wait for the process to complete with timeout
774
732
  try:
775
- stdout_bytes, stderr_bytes = await asyncio.wait_for(
776
- process.communicate(), timeout=timeout
777
- )
733
+ stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(), timeout=timeout)
778
734
 
779
735
  return CommandResult(
780
736
  return_code=process.returncode or 0,
@@ -794,9 +750,7 @@ class CommandExecutor:
794
750
  )
795
751
  except Exception as e:
796
752
  self._log(f"Script file execution error: {str(e)}")
797
- return CommandResult(
798
- return_code=1, error_message=f"Error executing script: {str(e)}"
799
- )
753
+ return CommandResult(return_code=1, error_message=f"Error executing script: {str(e)}")
800
754
  finally:
801
755
  # Clean up temporary file
802
756
  try:
@@ -863,9 +817,7 @@ class CommandExecutor:
863
817
  },
864
818
  }
865
819
 
866
- def _get_interpreter_path(
867
- self, language: str, shell_type: str | None = None
868
- ) -> tuple[str, list[str]]:
820
+ def _get_interpreter_path(self, language: str, shell_type: str | None = None) -> tuple[str, list[str]]:
869
821
  """Get the full path to the interpreter for the given language.
870
822
 
871
823
  Attempts to find the full path to the interpreter command, but only for
@@ -901,25 +853,20 @@ class CommandExecutor:
901
853
 
902
854
  # For Windows shell types, try to find the full path
903
855
  if sys.platform == "win32" and (
904
- not shell_type
905
- or shell_type.lower() in ["cmd", "powershell", "cmd.exe", "powershell.exe"]
856
+ not shell_type or shell_type.lower() in ["cmd", "powershell", "cmd.exe", "powershell.exe"]
906
857
  ):
907
858
  try:
908
859
  # Try to find the full path to the command
909
860
  full_path = shutil.which(command)
910
861
  if full_path:
911
- self._log(
912
- f"Found full path for {language} interpreter: {full_path}"
913
- )
862
+ self._log(f"Found full path for {language} interpreter: {full_path}")
914
863
  return full_path, args
915
864
 
916
865
  # If primary command not found, try alternatives
917
866
  for alt_command in alternatives:
918
867
  alt_path = shutil.which(alt_command)
919
868
  if alt_path:
920
- self._log(
921
- f"Found alternative path for {language} interpreter: {alt_path}"
922
- )
869
+ self._log(f"Found alternative path for {language} interpreter: {alt_path}")
923
870
  return alt_path, args
924
871
  except Exception as e:
925
872
  self._log(f"Error finding path for {language} interpreter: {str(e)}")
@@ -167,9 +167,7 @@ Use run_command with 'tail -f' for continuous monitoring.
167
167
 
168
168
  # Note about follow mode
169
169
  if follow:
170
- await tool_ctx.warning(
171
- "Follow mode not supported in MCP. Showing latest lines instead."
172
- )
170
+ await tool_ctx.warning("Follow mode not supported in MCP. Showing latest lines instead.")
173
171
 
174
172
  # Read log file
175
173
  await tool_ctx.info(f"Reading log file: {log_path}")
@@ -197,11 +195,7 @@ Use run_command with 'tail -f' for continuous monitoring.
197
195
  if process:
198
196
  header += f"Process: {process.name} (ID: {process_id})\n"
199
197
  header += f"Command: {process.command}\n"
200
- status = (
201
- "running"
202
- if process.is_running
203
- else f"finished (code: {process.return_code})"
204
- )
198
+ status = "running" if process.is_running else f"finished (code: {process.return_code})"
205
199
  header += f"Status: {status}\n"
206
200
  header += f"{'=' * 50}\n"
207
201
 
@@ -232,11 +226,7 @@ Use run_command with 'tail -f' for continuous monitoring.
232
226
 
233
227
  # Check which logs belong to active processes
234
228
  active_processes = RunBackgroundTool.get_processes()
235
- active_log_files = {
236
- str(p.log_file): (pid, p)
237
- for pid, p in active_processes.items()
238
- if p.log_file
239
- }
229
+ active_log_files = {str(p.log_file): (pid, p) for pid, p in active_processes.items() if p.log_file}
240
230
 
241
231
  # Build output
242
232
  output = []
@@ -250,9 +240,7 @@ Use run_command with 'tail -f' for continuous monitoring.
250
240
  if str(log_file) in active_log_files:
251
241
  pid, process = active_log_files[str(log_file)]
252
242
  status = "active" if process.is_running else "finished"
253
- output.append(
254
- f"{log_file.name:<50} {size_str:>10} [{status}] (ID: {pid})"
255
- )
243
+ output.append(f"{log_file.name:<50} {size_str:>10} [{status}] (ID: {pid})")
256
244
  else:
257
245
  output.append(f"{log_file.name:<50} {size_str:>10}")
258
246
 
@@ -160,9 +160,7 @@ Or download from: https://nodejs.org/"""
160
160
 
161
161
  try:
162
162
  # Execute command
163
- result = subprocess.run(
164
- cmd, capture_output=True, text=True, timeout=timeout, check=True
165
- )
163
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=True)
166
164
 
167
165
  output = []
168
166
  if result.stdout:
@@ -170,11 +168,7 @@ Or download from: https://nodejs.org/"""
170
168
  if result.stderr:
171
169
  output.append(f"\nSTDERR:\n{result.stderr}")
172
170
 
173
- return (
174
- "\n".join(output)
175
- if output
176
- else "Command completed successfully with no output."
177
- )
171
+ return "\n".join(output) if output else "Command completed successfully with no output."
178
172
 
179
173
  except subprocess.TimeoutExpired:
180
174
  return f"Error: Command timed out after {timeout} seconds. Use npx_background for long-running processes."
@@ -86,9 +86,7 @@ npx json-server db.json # Auto-backgrounds if needed"""
86
86
  cwd: Optional[str] = None,
87
87
  yes: bool = True,
88
88
  ) -> str:
89
- return await tool_self.run(
90
- ctx, package=package, args=args, cwd=cwd, yes=yes
91
- )
89
+ return await tool_self.run(ctx, package=package, args=args, cwd=cwd, yes=yes)
92
90
 
93
91
  async def call(self, ctx: MCPContext, **params) -> str:
94
92
  """Call the tool with arguments."""
@@ -151,9 +151,7 @@ Examples:
151
151
  else:
152
152
  process.terminate()
153
153
  killed_count += 1
154
- await tool_ctx.info(
155
- f"Killed process {proc_id} ({process.name})"
156
- )
154
+ await tool_ctx.info(f"Killed process {proc_id} ({process.name})")
157
155
  except Exception as e:
158
156
  errors.append(f"Failed to kill {proc_id}: {str(e)}")
159
157
 
@@ -222,9 +220,7 @@ Examples:
222
220
  else:
223
221
  process.terminate()
224
222
  killed_count += 1
225
- await tool_ctx.info(
226
- f"Killed background process {proc_id} ({process.name})"
227
- )
223
+ await tool_ctx.info(f"Killed background process {proc_id} ({process.name})")
228
224
  except Exception as e:
229
225
  errors.append(f"Failed to kill {proc_id}: {str(e)}")
230
226
 
@@ -237,15 +233,11 @@ Examples:
237
233
  else:
238
234
  proc.terminate()
239
235
  killed_count += 1
240
- await tool_ctx.info(
241
- f"Killed {proc.info['name']} (PID: {proc.info['pid']})"
242
- )
236
+ await tool_ctx.info(f"Killed {proc.info['name']} (PID: {proc.info['pid']})")
243
237
  except (psutil.NoSuchProcess, psutil.AccessDenied):
244
238
  continue
245
239
  except Exception as e:
246
- errors.append(
247
- f"Failed to kill PID {proc.info['pid']}: {str(e)}"
248
- )
240
+ errors.append(f"Failed to kill PID {proc.info['pid']}: {str(e)}")
249
241
 
250
242
  # Build result message
251
243
  if killed_count > 0:
@@ -61,11 +61,7 @@ process --action logs --id bash_ghi789 --lines 50"""
61
61
 
62
62
  output = ["Background processes:"]
63
63
  for proc_id, info in processes.items():
64
- status = (
65
- "running"
66
- if info["running"]
67
- else f"stopped (exit code: {info.get('return_code', 'unknown')})"
68
- )
64
+ status = "running" if info["running"] else f"stopped (exit code: {info.get('return_code', 'unknown')})"
69
65
  output.append(f"- {proc_id}: PID {info['pid']} - {status}")
70
66
  if info.get("log_file"):
71
67
  output.append(f" Log: {info['log_file']}")
@@ -134,9 +130,7 @@ process --action logs --id bash_ghi789 --lines 50"""
134
130
  signal_type: str = "TERM",
135
131
  lines: int = 100,
136
132
  ) -> str:
137
- return await tool_self.run(
138
- ctx, action=action, id=id, signal_type=signal_type, lines=lines
139
- )
133
+ return await tool_self.run(ctx, action=action, id=id, signal_type=signal_type, lines=lines)
140
134
 
141
135
  async def call(self, ctx: MCPContext, **params) -> str:
142
136
  """Call the tool with arguments."""
@@ -126,9 +126,7 @@ Examples:
126
126
  await tool_ctx.error(f"Failed to list processes: {str(e)}")
127
127
  return f"Error listing processes: {str(e)}"
128
128
 
129
- def _list_background_processes(
130
- self, filter_name: Optional[str], show_details: bool
131
- ) -> str:
129
+ def _list_background_processes(self, filter_name: Optional[str], show_details: bool) -> str:
132
130
  """List background processes started with run_background."""
133
131
  processes = RunBackgroundTool.get_processes()
134
132
 
@@ -154,11 +152,7 @@ Examples:
154
152
  filtered_processes.sort(key=lambda x: x[1].start_time, reverse=True)
155
153
 
156
154
  for proc_id, process in filtered_processes:
157
- status = (
158
- "running"
159
- if process.is_running
160
- else f"finished (code: {process.return_code})"
161
- )
155
+ status = "running" if process.is_running else f"finished (code: {process.return_code})"
162
156
  runtime = datetime.now() - process.start_time
163
157
  runtime_str = str(runtime).split(".")[0] # Remove microseconds
164
158
 
@@ -191,9 +185,7 @@ Examples:
191
185
 
192
186
  return "\n".join(output)
193
187
 
194
- def _list_system_processes(
195
- self, filter_name: Optional[str], show_details: bool
196
- ) -> str:
188
+ def _list_system_processes(self, filter_name: Optional[str], show_details: bool) -> str:
197
189
  """List all system processes."""
198
190
  try:
199
191
  processes = []
@@ -229,9 +221,7 @@ Examples:
229
221
 
230
222
  if show_details:
231
223
  process_info["cpu"] = proc.cpu_percent(interval=0.1)
232
- process_info["memory"] = (
233
- proc.memory_info().rss / 1024 / 1024
234
- ) # MB
224
+ process_info["memory"] = proc.memory_info().rss / 1024 / 1024 # MB
235
225
 
236
226
  processes.append(process_info)
237
227
 
@@ -250,9 +240,7 @@ Examples:
250
240
 
251
241
  # Header
252
242
  if show_details:
253
- output.append(
254
- f"{'PID':>7} {'CPU%':>5} {'MEM(MB)':>8} {'NAME':<20} COMMAND"
255
- )
243
+ output.append(f"{'PID':>7} {'CPU%':>5} {'MEM(MB)':>8} {'NAME':<20} COMMAND")
256
244
  output.append("-" * 80)
257
245
 
258
246
  for proc in processes:
@@ -273,9 +273,7 @@ Examples:
273
273
  # Clean up finished processes
274
274
  self._cleanup_finished_processes()
275
275
 
276
- await tool_ctx.info(
277
- f"Process started with ID: {process_id}, PID: {process.pid}"
278
- )
276
+ await tool_ctx.info(f"Process started with ID: {process_id}, PID: {process.pid}")
279
277
 
280
278
  # Return process information
281
279
  return f"""Background process started successfully!
@@ -77,9 +77,7 @@ class RunCommandToolParams(TypedDict):
77
77
  class RunCommandTool(ShellBaseTool):
78
78
  """Tool for executing shell commands."""
79
79
 
80
- def __init__(
81
- self, permission_manager: Any, command_executor: BashSessionExecutor
82
- ) -> None:
80
+ def __init__(self, permission_manager: Any, command_executor: BashSessionExecutor) -> None:
83
81
  """Initialize the run command tool.
84
82
 
85
83
  Args:
@@ -20,9 +20,7 @@ from hanzo_mcp.tools.shell.command_executor import CommandExecutor
20
20
  class RunCommandTool(ShellBaseTool):
21
21
  """Tool for executing shell commands."""
22
22
 
23
- def __init__(
24
- self, permission_manager: Any, command_executor: CommandExecutor
25
- ) -> None:
23
+ def __init__(self, permission_manager: Any, command_executor: CommandExecutor) -> None:
26
24
  """Initialize the run command tool.
27
25
 
28
26
  Args:
@@ -24,9 +24,7 @@ class SessionManager:
24
24
  _instance: Self | None = None
25
25
  _lock = threading.Lock()
26
26
 
27
- def __new__(
28
- cls, use_singleton: bool = True, session_storage: SessionStorage | None = None
29
- ) -> "SessionManager":
27
+ def __new__(cls, use_singleton: bool = True, session_storage: SessionStorage | None = None) -> "SessionManager":
30
28
  """Create SessionManager instance.
31
29
 
32
30
  Args:
@@ -45,9 +43,7 @@ class SessionManager:
45
43
  cls._instance._initialized = False
46
44
  return cls._instance
47
45
 
48
- def __init__(
49
- self, use_singleton: bool = True, session_storage: SessionStorage | None = None
50
- ) -> None:
46
+ def __init__(self, use_singleton: bool = True, session_storage: SessionStorage | None = None) -> None:
51
47
  """Initialize the session manager.
52
48
 
53
49
  Args:
@@ -152,9 +152,7 @@ class SessionStorageInstance:
152
152
  Returns:
153
153
  Number of sessions cleaned up
154
154
  """
155
- max_age = (
156
- max_age_seconds if max_age_seconds is not None else self.default_ttl_seconds
157
- )
155
+ max_age = max_age_seconds if max_age_seconds is not None else self.default_ttl_seconds
158
156
  current_time = time.time()
159
157
  expired_sessions: list[str] = []
160
158
 
@@ -201,9 +199,7 @@ class SessionStorageInstance:
201
199
  return {
202
200
  "total_sessions": len(self._sessions),
203
201
  "max_sessions": self.max_sessions,
204
- "utilization": (
205
- len(self._sessions) / self.max_sessions if self.max_sessions > 0 else 0
206
- ),
202
+ "utilization": (len(self._sessions) / self.max_sessions if self.max_sessions > 0 else 0),
207
203
  "default_ttl_seconds": self.default_ttl_seconds,
208
204
  }
209
205
 
@@ -117,9 +117,7 @@ class StreamingCommandTool(BaseProcessTool):
117
117
  try:
118
118
  with open(meta_file, "r") as f:
119
119
  meta = json.load(f)
120
- last_accessed = datetime.fromisoformat(
121
- meta.get("last_accessed", "")
122
- )
120
+ last_accessed = datetime.fromisoformat(meta.get("last_accessed", ""))
123
121
  if last_accessed < cutoff:
124
122
  shutil.rmtree(session_dir)
125
123
  except Exception:
@@ -240,9 +238,7 @@ class StreamingCommandTool(BaseProcessTool):
240
238
  }
241
239
 
242
240
  # Execute new command
243
- return await self._execute_new_command(
244
- command, working_dir, timeout, chunk_size
245
- )
241
+ return await self._execute_new_command(command, working_dir, timeout, chunk_size)
246
242
 
247
243
  async def _execute_new_command(
248
244
  self,
@@ -296,18 +292,12 @@ class StreamingCommandTool(BaseProcessTool):
296
292
  f.flush() # Ensure immediate write
297
293
 
298
294
  # Start streaming tasks
299
- stdout_task = asyncio.create_task(
300
- stream_to_file(process.stdout, output_file)
301
- )
302
- stderr_task = asyncio.create_task(
303
- stream_to_file(process.stderr, error_file)
304
- )
295
+ stdout_task = asyncio.create_task(stream_to_file(process.stdout, output_file))
296
+ stderr_task = asyncio.create_task(stream_to_file(process.stderr, error_file))
305
297
 
306
298
  # Wait for initial output or timeout
307
299
  start_time = time.time()
308
- initial_timeout = min(
309
- timeout or 5, 5
310
- ) # Wait max 5 seconds for initial output
300
+ initial_timeout = min(timeout or 5, 5) # Wait max 5 seconds for initial output
311
301
 
312
302
  while time.time() - start_time < initial_timeout:
313
303
  if output_file.stat().st_size > 0 or error_file.stat().st_size > 0:
@@ -487,9 +477,7 @@ class StreamingCommandTool(BaseProcessTool):
487
477
  """Get list of recent commands for hints."""
488
478
  commands = []
489
479
 
490
- for cmd_dir in sorted(
491
- self.commands_dir.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True
492
- )[:limit]:
480
+ for cmd_dir in sorted(self.commands_dir.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True)[:limit]:
493
481
  try:
494
482
  with open(cmd_dir / "metadata.json", "r") as f:
495
483
  meta = json.load(f)
@@ -502,11 +490,7 @@ class StreamingCommandTool(BaseProcessTool):
502
490
  commands.append(
503
491
  {
504
492
  "id": meta["command_id"][:8],
505
- "command": (
506
- meta["command"][:50] + "..."
507
- if len(meta["command"]) > 50
508
- else meta["command"]
509
- ),
493
+ "command": (meta["command"][:50] + "..." if len(meta["command"]) > 50 else meta["command"]),
510
494
  "status": meta.get("status", "unknown"),
511
495
  "output_size": output_size,
512
496
  "time": meta.get("start_time", ""),