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
@@ -1,20 +1,17 @@
1
1
  """Tool for terminating background processes."""
2
2
 
3
- import os
4
- import signal
5
- import psutil
3
+ from typing import Unpack, Optional, Annotated, TypedDict, final, override
6
4
  from datetime import datetime
7
- from typing import Annotated, Optional, TypedDict, Unpack, final, override
8
5
 
9
- from mcp.server.fastmcp import Context as MCPContext
6
+ import psutil
10
7
  from pydantic import Field
8
+ from mcp.server.fastmcp import Context as MCPContext
11
9
 
12
10
  from hanzo_mcp.tools.common.base import BaseTool
13
11
  from hanzo_mcp.tools.common.context import create_tool_context
14
12
  from hanzo_mcp.tools.common.permissions import PermissionManager
15
13
  from hanzo_mcp.tools.shell.run_background import RunBackgroundTool
16
14
 
17
-
18
15
  ProcessId = Annotated[
19
16
  Optional[str],
20
17
  Field(
@@ -145,7 +142,7 @@ Examples:
145
142
  if kill_all:
146
143
  await tool_ctx.info("Killing all background processes")
147
144
  processes = RunBackgroundTool.get_processes()
148
-
145
+
149
146
  for proc_id, process in list(processes.items()):
150
147
  if process.is_running:
151
148
  try:
@@ -154,10 +151,12 @@ Examples:
154
151
  else:
155
152
  process.terminate()
156
153
  killed_count += 1
157
- await tool_ctx.info(f"Killed process {proc_id} ({process.name})")
154
+ await tool_ctx.info(
155
+ f"Killed process {proc_id} ({process.name})"
156
+ )
158
157
  except Exception as e:
159
158
  errors.append(f"Failed to kill {proc_id}: {str(e)}")
160
-
159
+
161
160
  if killed_count == 0:
162
161
  return "No running background processes to kill."
163
162
 
@@ -165,13 +164,13 @@ Examples:
165
164
  elif process_id:
166
165
  await tool_ctx.info(f"Killing process with ID: {process_id}")
167
166
  process = RunBackgroundTool.get_process(process_id)
168
-
167
+
169
168
  if not process:
170
169
  return f"Process with ID '{process_id}' not found."
171
-
170
+
172
171
  if not process.is_running:
173
172
  return f"Process '{process_id}' is not running (return code: {process.return_code})."
174
-
173
+
175
174
  try:
176
175
  if force:
177
176
  process.kill()
@@ -187,21 +186,21 @@ Examples:
187
186
  await tool_ctx.info(f"Killing process with PID: {pid}")
188
187
  try:
189
188
  p = psutil.Process(pid)
190
-
189
+
191
190
  if force:
192
191
  p.kill()
193
192
  else:
194
193
  p.terminate()
195
-
194
+
196
195
  killed_count += 1
197
196
  await tool_ctx.info(f"Successfully killed PID {pid}")
198
-
197
+
199
198
  # Check if this was a background process and update it
200
199
  for proc_id, process in RunBackgroundTool.get_processes().items():
201
200
  if process.pid == pid:
202
201
  process.end_time = datetime.now()
203
202
  break
204
-
203
+
205
204
  except psutil.NoSuchProcess:
206
205
  return f"Process with PID {pid} not found."
207
206
  except psutil.AccessDenied:
@@ -212,7 +211,7 @@ Examples:
212
211
  # Kill by name
213
212
  elif name:
214
213
  await tool_ctx.info(f"Killing all processes matching: {name}")
215
-
214
+
216
215
  # First check background processes
217
216
  bg_processes = RunBackgroundTool.get_processes()
218
217
  for proc_id, process in list(bg_processes.items()):
@@ -223,34 +222,40 @@ Examples:
223
222
  else:
224
223
  process.terminate()
225
224
  killed_count += 1
226
- await tool_ctx.info(f"Killed background process {proc_id} ({process.name})")
225
+ await tool_ctx.info(
226
+ f"Killed background process {proc_id} ({process.name})"
227
+ )
227
228
  except Exception as e:
228
229
  errors.append(f"Failed to kill {proc_id}: {str(e)}")
229
-
230
+
230
231
  # Also check system processes
231
- for proc in psutil.process_iter(['pid', 'name']):
232
+ for proc in psutil.process_iter(["pid", "name"]):
232
233
  try:
233
- if name.lower() in proc.info['name'].lower():
234
+ if name.lower() in proc.info["name"].lower():
234
235
  if force:
235
236
  proc.kill()
236
237
  else:
237
238
  proc.terminate()
238
239
  killed_count += 1
239
- await tool_ctx.info(f"Killed {proc.info['name']} (PID: {proc.info['pid']})")
240
+ await tool_ctx.info(
241
+ f"Killed {proc.info['name']} (PID: {proc.info['pid']})"
242
+ )
240
243
  except (psutil.NoSuchProcess, psutil.AccessDenied):
241
244
  continue
242
245
  except Exception as e:
243
- errors.append(f"Failed to kill PID {proc.info['pid']}: {str(e)}")
246
+ errors.append(
247
+ f"Failed to kill PID {proc.info['pid']}: {str(e)}"
248
+ )
244
249
 
245
250
  # Build result message
246
251
  if killed_count > 0:
247
252
  result = f"Successfully killed {killed_count} process(es)."
248
253
  else:
249
254
  result = "No processes were killed."
250
-
255
+
251
256
  if errors:
252
257
  result += f"\n\nErrors:\n" + "\n".join(errors)
253
-
258
+
254
259
  return result
255
260
 
256
261
  except Exception as e:
@@ -259,4 +264,4 @@ Examples:
259
264
 
260
265
  def register(self, mcp_server) -> None:
261
266
  """Register this tool with the MCP server."""
262
- pass
267
+ pass
@@ -3,23 +3,23 @@
3
3
  import signal
4
4
  from typing import Optional, override
5
5
 
6
+ from mcp.server import FastMCP
6
7
  from mcp.server.fastmcp import Context as MCPContext
7
8
 
8
9
  from hanzo_mcp.tools.common.base import BaseTool
9
10
  from hanzo_mcp.tools.shell.base_process import ProcessManager
10
- from mcp.server import FastMCP
11
11
 
12
12
 
13
13
  class ProcessTool(BaseTool):
14
14
  """Tool for process management."""
15
-
15
+
16
16
  name = "process"
17
-
17
+
18
18
  def __init__(self):
19
19
  """Initialize the process tool."""
20
20
  super().__init__()
21
21
  self.process_manager = ProcessManager()
22
-
22
+
23
23
  @property
24
24
  @override
25
25
  def description(self) -> str:
@@ -32,7 +32,7 @@ process --action list
32
32
  process --action kill --id npx_abc123
33
33
  process --action logs --id uvx_def456
34
34
  process --action logs --id bash_ghi789 --lines 50"""
35
-
35
+
36
36
  @override
37
37
  async def run(
38
38
  self,
@@ -43,14 +43,14 @@ process --action logs --id bash_ghi789 --lines 50"""
43
43
  lines: int = 100,
44
44
  ) -> str:
45
45
  """Manage background processes.
46
-
46
+
47
47
  Args:
48
48
  ctx: MCP context
49
49
  action: Action to perform (list, kill, logs)
50
50
  id: Process ID (for kill/logs actions)
51
51
  signal_type: Signal type for kill (TERM, KILL, INT)
52
52
  lines: Number of log lines to show
53
-
53
+
54
54
  Returns:
55
55
  Action result
56
56
  """
@@ -58,86 +58,86 @@ process --action logs --id bash_ghi789 --lines 50"""
58
58
  processes = self.process_manager.list_processes()
59
59
  if not processes:
60
60
  return "No background processes running"
61
-
61
+
62
62
  output = ["Background processes:"]
63
63
  for proc_id, info in processes.items():
64
- status = "running" if info["running"] else f"stopped (exit code: {info.get('return_code', 'unknown')})"
64
+ status = (
65
+ "running"
66
+ if info["running"]
67
+ else f"stopped (exit code: {info.get('return_code', 'unknown')})"
68
+ )
65
69
  output.append(f"- {proc_id}: PID {info['pid']} - {status}")
66
70
  if info.get("log_file"):
67
71
  output.append(f" Log: {info['log_file']}")
68
-
72
+
69
73
  return "\n".join(output)
70
-
74
+
71
75
  elif action == "kill":
72
76
  if not id:
73
77
  return "Error: Process ID required for kill action"
74
-
78
+
75
79
  process = self.process_manager.get_process(id)
76
80
  if not process:
77
81
  return f"Process {id} not found"
78
-
82
+
79
83
  # Map signal names to signal numbers
80
84
  signal_map = {
81
85
  "TERM": signal.SIGTERM,
82
86
  "KILL": signal.SIGKILL,
83
87
  "INT": signal.SIGINT,
84
88
  }
85
-
89
+
86
90
  sig = signal_map.get(signal_type.upper(), signal.SIGTERM)
87
-
91
+
88
92
  try:
89
93
  process.send_signal(sig)
90
94
  return f"Sent {signal_type} signal to process {id} (PID: {process.pid})"
91
95
  except Exception as e:
92
96
  return f"Failed to kill process {id}: {e}"
93
-
97
+
94
98
  elif action == "logs":
95
99
  if not id:
96
100
  return "Error: Process ID required for logs action"
97
-
101
+
98
102
  log_file = self.process_manager.get_log_file(id)
99
103
  if not log_file or not log_file.exists():
100
104
  return f"No log file found for process {id}"
101
-
105
+
102
106
  try:
103
107
  with open(log_file, "r") as f:
104
108
  log_lines = f.readlines()
105
-
109
+
106
110
  # Get last N lines
107
111
  if len(log_lines) > lines:
108
112
  log_lines = log_lines[-lines:]
109
-
113
+
110
114
  output = [f"Logs for process {id} (last {lines} lines):"]
111
115
  output.append("-" * 50)
112
116
  output.extend(line.rstrip() for line in log_lines)
113
-
117
+
114
118
  return "\n".join(output)
115
119
  except Exception as e:
116
120
  return f"Error reading logs: {e}"
117
-
121
+
118
122
  else:
119
123
  return f"Unknown action: {action}. Use 'list', 'kill', or 'logs'"
120
124
 
121
125
  def register(self, server: FastMCP) -> None:
122
126
  """Register the tool with the MCP server."""
123
127
  tool_self = self
124
-
128
+
125
129
  @server.tool(name=self.name, description=self.description)
126
130
  async def process(
127
131
  ctx: MCPContext,
128
132
  action: str = "list",
129
133
  id: Optional[str] = None,
130
134
  signal_type: str = "TERM",
131
- lines: int = 100
135
+ lines: int = 100,
132
136
  ) -> str:
133
137
  return await tool_self.run(
134
- ctx,
135
- action=action,
136
- id=id,
137
- signal_type=signal_type,
138
- lines=lines
138
+ ctx, action=action, id=id, signal_type=signal_type, lines=lines
139
139
  )
140
-
140
+
141
141
  async def call(self, ctx: MCPContext, **params) -> str:
142
142
  """Call the tool with arguments."""
143
143
  return await self.run(
@@ -145,9 +145,9 @@ process --action logs --id bash_ghi789 --lines 50"""
145
145
  action=params.get("action", "list"),
146
146
  id=params.get("id"),
147
147
  signal_type=params.get("signal_type", "TERM"),
148
- lines=params.get("lines", 100)
148
+ lines=params.get("lines", 100),
149
149
  )
150
150
 
151
151
 
152
152
  # Create tool instance
153
- process_tool = ProcessTool()
153
+ process_tool = ProcessTool()
@@ -1,18 +1,17 @@
1
1
  """Tool for listing running background processes."""
2
2
 
3
- import psutil
3
+ from typing import Unpack, Optional, Annotated, TypedDict, final, override
4
4
  from datetime import datetime
5
- from typing import Annotated, Optional, TypedDict, Unpack, final, override
6
5
 
7
- from mcp.server.fastmcp import Context as MCPContext
6
+ import psutil
8
7
  from pydantic import Field
8
+ from mcp.server.fastmcp import Context as MCPContext
9
9
 
10
10
  from hanzo_mcp.tools.common.base import BaseTool
11
11
  from hanzo_mcp.tools.common.context import create_tool_context
12
12
  from hanzo_mcp.tools.common.permissions import PermissionManager
13
13
  from hanzo_mcp.tools.shell.run_background import RunBackgroundTool
14
14
 
15
-
16
15
  ShowAll = Annotated[
17
16
  bool,
18
17
  Field(
@@ -132,10 +131,10 @@ Examples:
132
131
  ) -> str:
133
132
  """List background processes started with run_background."""
134
133
  processes = RunBackgroundTool.get_processes()
135
-
134
+
136
135
  if not processes:
137
136
  return "No background processes are currently running."
138
-
137
+
139
138
  # Filter if requested
140
139
  filtered_processes = []
141
140
  for proc_id, process in processes.items():
@@ -143,22 +142,26 @@ Examples:
143
142
  if filter_name.lower() not in process.name.lower():
144
143
  continue
145
144
  filtered_processes.append((proc_id, process))
146
-
145
+
147
146
  if not filtered_processes:
148
147
  return f"No background processes found matching '{filter_name}'."
149
-
148
+
150
149
  # Build output
151
150
  output = []
152
151
  output.append("=== Background Processes ===\n")
153
-
152
+
154
153
  # Sort by start time (newest first)
155
154
  filtered_processes.sort(key=lambda x: x[1].start_time, reverse=True)
156
-
155
+
157
156
  for proc_id, process in filtered_processes:
158
- status = "running" if process.is_running else f"finished (code: {process.return_code})"
157
+ status = (
158
+ "running"
159
+ if process.is_running
160
+ else f"finished (code: {process.return_code})"
161
+ )
159
162
  runtime = datetime.now() - process.start_time
160
- runtime_str = str(runtime).split('.')[0] # Remove microseconds
161
-
163
+ runtime_str = str(runtime).split(".")[0] # Remove microseconds
164
+
162
165
  output.append(f"ID: {proc_id}")
163
166
  output.append(f"Name: {process.name}")
164
167
  output.append(f"Status: {status}")
@@ -166,10 +169,10 @@ Examples:
166
169
  output.append(f"Runtime: {runtime_str}")
167
170
  output.append(f"Command: {process.command}")
168
171
  output.append(f"Working Dir: {process.working_dir}")
169
-
172
+
170
173
  if process.log_file:
171
174
  output.append(f"Log File: {process.log_file}")
172
-
175
+
173
176
  if show_details and process.is_running:
174
177
  try:
175
178
  # Get process details using psutil
@@ -179,13 +182,13 @@ Examples:
179
182
  output.append(f"Threads: {p.num_threads()}")
180
183
  except (psutil.NoSuchProcess, psutil.AccessDenied):
181
184
  output.append("Process details unavailable")
182
-
185
+
183
186
  output.append("-" * 40)
184
-
187
+
185
188
  output.append(f"\nTotal: {len(filtered_processes)} process(es)")
186
189
  output.append("\nUse 'pkill --id <ID>' to stop a process")
187
190
  output.append("Use 'logs --id <ID>' to view process logs")
188
-
191
+
189
192
  return "\n".join(output)
190
193
 
191
194
  def _list_system_processes(
@@ -194,86 +197,82 @@ Examples:
194
197
  """List all system processes."""
195
198
  try:
196
199
  processes = []
197
-
200
+
198
201
  # Get all running processes
199
- for proc in psutil.process_iter(['pid', 'name', 'cmdline', 'create_time']):
202
+ for proc in psutil.process_iter(["pid", "name", "cmdline", "create_time"]):
200
203
  try:
201
204
  info = proc.info
202
- name = info['name']
203
-
205
+ name = info["name"]
206
+
204
207
  # Filter if requested
205
208
  if filter_name:
206
209
  if filter_name.lower() not in name.lower():
207
210
  continue
208
-
211
+
209
212
  # Get command line
210
- cmdline = info.get('cmdline')
213
+ cmdline = info.get("cmdline")
211
214
  if cmdline:
212
- cmd = ' '.join(cmdline)
215
+ cmd = " ".join(cmdline)
213
216
  else:
214
217
  cmd = name
215
-
218
+
216
219
  # Truncate long commands
217
220
  if len(cmd) > 80:
218
221
  cmd = cmd[:77] + "..."
219
-
222
+
220
223
  process_info = {
221
- 'pid': info['pid'],
222
- 'name': name,
223
- 'cmd': cmd,
224
- 'create_time': info['create_time'],
224
+ "pid": info["pid"],
225
+ "name": name,
226
+ "cmd": cmd,
227
+ "create_time": info["create_time"],
225
228
  }
226
-
229
+
227
230
  if show_details:
228
- process_info['cpu'] = proc.cpu_percent(interval=0.1)
229
- process_info['memory'] = proc.memory_info().rss / 1024 / 1024 # MB
230
-
231
+ process_info["cpu"] = proc.cpu_percent(interval=0.1)
232
+ process_info["memory"] = (
233
+ proc.memory_info().rss / 1024 / 1024
234
+ ) # MB
235
+
231
236
  processes.append(process_info)
232
-
237
+
233
238
  except (psutil.NoSuchProcess, psutil.AccessDenied):
234
239
  continue
235
-
240
+
236
241
  if not processes:
237
242
  return f"No processes found matching '{filter_name}'."
238
-
243
+
239
244
  # Sort by PID
240
- processes.sort(key=lambda x: x['pid'])
241
-
245
+ processes.sort(key=lambda x: x["pid"])
246
+
242
247
  # Build output
243
248
  output = []
244
249
  output.append("=== System Processes ===\n")
245
-
250
+
246
251
  # Header
247
252
  if show_details:
248
- output.append(f"{'PID':>7} {'CPU%':>5} {'MEM(MB)':>8} {'NAME':<20} COMMAND")
253
+ output.append(
254
+ f"{'PID':>7} {'CPU%':>5} {'MEM(MB)':>8} {'NAME':<20} COMMAND"
255
+ )
249
256
  output.append("-" * 80)
250
-
257
+
251
258
  for proc in processes:
252
259
  output.append(
253
- f"{proc['pid']:>7} "
254
- f"{proc['cpu']:>5.1f} "
255
- f"{proc['memory']:>8.1f} "
256
- f"{proc['name']:<20} "
257
- f"{proc['cmd']}"
260
+ f"{proc['pid']:>7} {proc['cpu']:>5.1f} {proc['memory']:>8.1f} {proc['name']:<20} {proc['cmd']}"
258
261
  )
259
262
  else:
260
263
  output.append(f"{'PID':>7} {'NAME':<20} COMMAND")
261
264
  output.append("-" * 80)
262
-
265
+
263
266
  for proc in processes:
264
- output.append(
265
- f"{proc['pid']:>7} "
266
- f"{proc['name']:<20} "
267
- f"{proc['cmd']}"
268
- )
269
-
267
+ output.append(f"{proc['pid']:>7} {proc['name']:<20} {proc['cmd']}")
268
+
270
269
  output.append(f"\nTotal: {len(processes)} process(es)")
271
-
270
+
272
271
  return "\n".join(output)
273
-
272
+
274
273
  except Exception as e:
275
274
  return f"Error listing system processes: {str(e)}\nYou may need elevated permissions."
276
275
 
277
276
  def register(self, mcp_server) -> None:
278
277
  """Register this tool with the MCP server."""
279
- pass
278
+ pass