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.
- hanzo_mcp/__init__.py +7 -1
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/shell/pkill.py
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
"""Tool for terminating background processes."""
|
|
2
2
|
|
|
3
|
-
import
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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([
|
|
232
|
+
for proc in psutil.process_iter(["pid", "name"]):
|
|
232
233
|
try:
|
|
233
|
-
if name.lower() in proc.info[
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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([
|
|
202
|
+
for proc in psutil.process_iter(["pid", "name", "cmdline", "create_time"]):
|
|
200
203
|
try:
|
|
201
204
|
info = proc.info
|
|
202
|
-
name = info[
|
|
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(
|
|
213
|
+
cmdline = info.get("cmdline")
|
|
211
214
|
if cmdline:
|
|
212
|
-
cmd =
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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[
|
|
229
|
-
process_info[
|
|
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[
|
|
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(
|
|
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
|
-
|
|
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
|