hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.1__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 +6 -0
- 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.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/shell/logs.py
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
"""Tool for viewing process logs."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Annotated, Optional, TypedDict, Unpack, final, override
|
|
6
5
|
|
|
7
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
8
6
|
from pydantic import Field
|
|
7
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
9
8
|
|
|
10
9
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
11
10
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
12
11
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
13
12
|
from hanzo_mcp.tools.shell.run_background import RunBackgroundTool
|
|
14
13
|
|
|
15
|
-
|
|
16
14
|
ProcessId = Annotated[
|
|
17
15
|
Optional[str],
|
|
18
16
|
Field(
|
|
@@ -142,20 +140,20 @@ Use run_command with 'tail -f' for continuous monitoring.
|
|
|
142
140
|
process = RunBackgroundTool.get_process(process_id)
|
|
143
141
|
if not process:
|
|
144
142
|
return f"Process with ID '{process_id}' not found."
|
|
145
|
-
|
|
143
|
+
|
|
146
144
|
if not process.log_file:
|
|
147
145
|
return f"Process '{process_id}' does not have logging enabled."
|
|
148
|
-
|
|
146
|
+
|
|
149
147
|
log_path = process.log_file
|
|
150
|
-
|
|
148
|
+
|
|
151
149
|
elif log_file:
|
|
152
150
|
# Use specified log file
|
|
153
151
|
log_path = Path(log_file)
|
|
154
|
-
|
|
152
|
+
|
|
155
153
|
# Check if it's in the logs directory
|
|
156
154
|
if not log_path.is_absolute():
|
|
157
155
|
log_path = self.log_dir / log_path
|
|
158
|
-
|
|
156
|
+
|
|
159
157
|
else:
|
|
160
158
|
return "Error: Must specify --id or --file"
|
|
161
159
|
|
|
@@ -169,13 +167,15 @@ Use run_command with 'tail -f' for continuous monitoring.
|
|
|
169
167
|
|
|
170
168
|
# Note about follow mode
|
|
171
169
|
if follow:
|
|
172
|
-
await tool_ctx.warning(
|
|
170
|
+
await tool_ctx.warning(
|
|
171
|
+
"Follow mode not supported in MCP. Showing latest lines instead."
|
|
172
|
+
)
|
|
173
173
|
|
|
174
174
|
# Read log file
|
|
175
175
|
await tool_ctx.info(f"Reading log file: {log_path}")
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
try:
|
|
178
|
-
with open(log_path,
|
|
178
|
+
with open(log_path, "r") as f:
|
|
179
179
|
if lines == -1:
|
|
180
180
|
# Read entire file
|
|
181
181
|
content = f.read()
|
|
@@ -183,13 +183,13 @@ Use run_command with 'tail -f' for continuous monitoring.
|
|
|
183
183
|
# Read last N lines
|
|
184
184
|
all_lines = f.readlines()
|
|
185
185
|
if len(all_lines) <= lines:
|
|
186
|
-
content =
|
|
186
|
+
content = "".join(all_lines)
|
|
187
187
|
else:
|
|
188
|
-
content =
|
|
189
|
-
|
|
188
|
+
content = "".join(all_lines[-lines:])
|
|
189
|
+
|
|
190
190
|
if not content:
|
|
191
191
|
return f"Log file is empty: {log_path}"
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
# Add header
|
|
194
194
|
header = f"=== Log: {log_path.name} ===\n"
|
|
195
195
|
if process_id:
|
|
@@ -197,12 +197,16 @@ Use run_command with 'tail -f' for continuous monitoring.
|
|
|
197
197
|
if process:
|
|
198
198
|
header += f"Process: {process.name} (ID: {process_id})\n"
|
|
199
199
|
header += f"Command: {process.command}\n"
|
|
200
|
-
status =
|
|
200
|
+
status = (
|
|
201
|
+
"running"
|
|
202
|
+
if process.is_running
|
|
203
|
+
else f"finished (code: {process.return_code})"
|
|
204
|
+
)
|
|
201
205
|
header += f"Status: {status}\n"
|
|
202
206
|
header += f"{'=' * 50}\n"
|
|
203
|
-
|
|
207
|
+
|
|
204
208
|
return header + content
|
|
205
|
-
|
|
209
|
+
|
|
206
210
|
except Exception as e:
|
|
207
211
|
return f"Error reading log file: {str(e)}"
|
|
208
212
|
|
|
@@ -213,48 +217,54 @@ Use run_command with 'tail -f' for continuous monitoring.
|
|
|
213
217
|
async def _list_logs(self, tool_ctx) -> str:
|
|
214
218
|
"""List all available log files."""
|
|
215
219
|
await tool_ctx.info("Listing available log files")
|
|
216
|
-
|
|
220
|
+
|
|
217
221
|
if not self.log_dir.exists():
|
|
218
222
|
return "No logs directory found."
|
|
219
|
-
|
|
223
|
+
|
|
220
224
|
# Get all log files
|
|
221
225
|
log_files = list(self.log_dir.glob("*.log"))
|
|
222
|
-
|
|
226
|
+
|
|
223
227
|
if not log_files:
|
|
224
228
|
return "No log files found."
|
|
225
|
-
|
|
229
|
+
|
|
226
230
|
# Sort by modification time (newest first)
|
|
227
231
|
log_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
|
|
228
|
-
|
|
232
|
+
|
|
229
233
|
# Check which logs belong to active processes
|
|
230
234
|
active_processes = RunBackgroundTool.get_processes()
|
|
231
|
-
active_log_files = {
|
|
232
|
-
|
|
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
|
+
}
|
|
240
|
+
|
|
233
241
|
# Build output
|
|
234
242
|
output = []
|
|
235
243
|
output.append("=== Available Log Files ===\n")
|
|
236
|
-
|
|
244
|
+
|
|
237
245
|
for log_file in log_files[:50]: # Limit to 50 most recent
|
|
238
246
|
size = log_file.stat().st_size
|
|
239
247
|
size_str = self._format_size(size)
|
|
240
|
-
|
|
248
|
+
|
|
241
249
|
# Check if this belongs to an active process
|
|
242
250
|
if str(log_file) in active_log_files:
|
|
243
251
|
pid, process = active_log_files[str(log_file)]
|
|
244
252
|
status = "active" if process.is_running else "finished"
|
|
245
|
-
output.append(
|
|
253
|
+
output.append(
|
|
254
|
+
f"{log_file.name:<50} {size_str:>10} [{status}] (ID: {pid})"
|
|
255
|
+
)
|
|
246
256
|
else:
|
|
247
257
|
output.append(f"{log_file.name:<50} {size_str:>10}")
|
|
248
|
-
|
|
258
|
+
|
|
249
259
|
output.append(f"\nTotal: {len(log_files)} log file(s)")
|
|
250
260
|
output.append("\nUse 'logs --file <filename>' to view a specific log")
|
|
251
261
|
output.append("Use 'logs --id <process-id>' to view logs for a running process")
|
|
252
|
-
|
|
262
|
+
|
|
253
263
|
return "\n".join(output)
|
|
254
264
|
|
|
255
265
|
def _format_size(self, size: int) -> str:
|
|
256
266
|
"""Format file size in human-readable format."""
|
|
257
|
-
for unit in [
|
|
267
|
+
for unit in ["B", "KB", "MB", "GB"]:
|
|
258
268
|
if size < 1024.0:
|
|
259
269
|
return f"{size:.1f} {unit}"
|
|
260
270
|
size /= 1024.0
|
|
@@ -262,4 +272,4 @@ Use run_command with 'tail -f' for continuous monitoring.
|
|
|
262
272
|
|
|
263
273
|
def register(self, mcp_server) -> None:
|
|
264
274
|
"""Register this tool with the MCP server."""
|
|
265
|
-
pass
|
|
275
|
+
pass
|
hanzo_mcp/tools/shell/npx.py
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""Run Node.js packages with npx."""
|
|
2
2
|
|
|
3
|
-
import subprocess
|
|
4
3
|
import shutil
|
|
5
|
-
|
|
4
|
+
import subprocess
|
|
5
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
6
6
|
|
|
7
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
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
|
|
|
14
|
-
|
|
15
14
|
Package = Annotated[
|
|
16
15
|
str,
|
|
17
16
|
Field(
|
|
@@ -144,16 +143,17 @@ Or download from: https://nodejs.org/"""
|
|
|
144
143
|
|
|
145
144
|
# Build command
|
|
146
145
|
cmd = ["npx"]
|
|
147
|
-
|
|
146
|
+
|
|
148
147
|
if yes:
|
|
149
148
|
cmd.append("--yes")
|
|
150
|
-
|
|
149
|
+
|
|
151
150
|
cmd.append(package)
|
|
152
|
-
|
|
151
|
+
|
|
153
152
|
# Add package arguments
|
|
154
153
|
if args:
|
|
155
154
|
# Split args properly (basic parsing)
|
|
156
155
|
import shlex
|
|
156
|
+
|
|
157
157
|
cmd.extend(shlex.split(args))
|
|
158
158
|
|
|
159
159
|
await tool_ctx.info(f"Running: {' '.join(cmd)}")
|
|
@@ -161,11 +161,7 @@ Or download from: https://nodejs.org/"""
|
|
|
161
161
|
try:
|
|
162
162
|
# Execute command
|
|
163
163
|
result = subprocess.run(
|
|
164
|
-
cmd,
|
|
165
|
-
capture_output=True,
|
|
166
|
-
text=True,
|
|
167
|
-
timeout=timeout,
|
|
168
|
-
check=True
|
|
164
|
+
cmd, capture_output=True, text=True, timeout=timeout, check=True
|
|
169
165
|
)
|
|
170
166
|
|
|
171
167
|
output = []
|
|
@@ -174,7 +170,11 @@ Or download from: https://nodejs.org/"""
|
|
|
174
170
|
if result.stderr:
|
|
175
171
|
output.append(f"\nSTDERR:\n{result.stderr}")
|
|
176
172
|
|
|
177
|
-
return
|
|
173
|
+
return (
|
|
174
|
+
"\n".join(output)
|
|
175
|
+
if output
|
|
176
|
+
else "Command completed successfully with no output."
|
|
177
|
+
)
|
|
178
178
|
|
|
179
179
|
except subprocess.TimeoutExpired:
|
|
180
180
|
return f"Error: Command timed out after {timeout} seconds. Use npx_background for long-running processes."
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"""Run Node.js packages in background with npx."""
|
|
2
2
|
|
|
3
|
-
import subprocess
|
|
4
|
-
import shutil
|
|
5
3
|
import uuid
|
|
6
|
-
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
7
7
|
|
|
8
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
9
8
|
from pydantic import Field
|
|
9
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
10
10
|
|
|
11
11
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
12
12
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
13
13
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
14
14
|
from hanzo_mcp.tools.shell.run_background import BackgroundProcess, RunBackgroundTool
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
Package = Annotated[
|
|
18
17
|
str,
|
|
19
18
|
Field(
|
|
@@ -163,25 +162,27 @@ Or download from: https://nodejs.org/"""
|
|
|
163
162
|
|
|
164
163
|
# Build command
|
|
165
164
|
cmd = ["npx"]
|
|
166
|
-
|
|
165
|
+
|
|
167
166
|
if yes:
|
|
168
167
|
cmd.append("--yes")
|
|
169
|
-
|
|
168
|
+
|
|
170
169
|
cmd.append(package)
|
|
171
|
-
|
|
170
|
+
|
|
172
171
|
# Add package arguments
|
|
173
172
|
if args:
|
|
174
173
|
# Split args properly (basic parsing)
|
|
175
174
|
import shlex
|
|
175
|
+
|
|
176
176
|
cmd.extend(shlex.split(args))
|
|
177
177
|
|
|
178
178
|
# Generate process ID
|
|
179
179
|
process_id = str(uuid.uuid4())[:8]
|
|
180
|
-
|
|
180
|
+
|
|
181
181
|
# Prepare log file if needed
|
|
182
182
|
log_file = None
|
|
183
183
|
if log_output:
|
|
184
184
|
from pathlib import Path
|
|
185
|
+
|
|
185
186
|
log_dir = Path.home() / ".hanzo" / "logs"
|
|
186
187
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
187
188
|
log_file = log_dir / f"{name}_{process_id}.log"
|
|
@@ -197,7 +198,7 @@ Or download from: https://nodejs.org/"""
|
|
|
197
198
|
stdout=f,
|
|
198
199
|
stderr=subprocess.STDOUT,
|
|
199
200
|
cwd=working_dir,
|
|
200
|
-
start_new_session=True
|
|
201
|
+
start_new_session=True,
|
|
201
202
|
)
|
|
202
203
|
else:
|
|
203
204
|
process = subprocess.Popen(
|
|
@@ -205,7 +206,7 @@ Or download from: https://nodejs.org/"""
|
|
|
205
206
|
stdout=subprocess.DEVNULL,
|
|
206
207
|
stderr=subprocess.DEVNULL,
|
|
207
208
|
cwd=working_dir,
|
|
208
|
-
start_new_session=True
|
|
209
|
+
start_new_session=True,
|
|
209
210
|
)
|
|
210
211
|
|
|
211
212
|
# Create background process object
|
|
@@ -215,7 +216,7 @@ Or download from: https://nodejs.org/"""
|
|
|
215
216
|
name=name,
|
|
216
217
|
process=process,
|
|
217
218
|
log_file=str(log_file) if log_file else None,
|
|
218
|
-
working_dir=working_dir
|
|
219
|
+
working_dir=working_dir,
|
|
219
220
|
)
|
|
220
221
|
|
|
221
222
|
# Register with RunBackgroundTool
|
|
@@ -229,19 +230,21 @@ Or download from: https://nodejs.org/"""
|
|
|
229
230
|
f" PID: {process.pid}",
|
|
230
231
|
f" Command: {' '.join(cmd)}",
|
|
231
232
|
]
|
|
232
|
-
|
|
233
|
+
|
|
233
234
|
if working_dir:
|
|
234
235
|
output.append(f" Working Dir: {working_dir}")
|
|
235
|
-
|
|
236
|
+
|
|
236
237
|
if log_file:
|
|
237
238
|
output.append(f" Log: {log_file}")
|
|
238
|
-
|
|
239
|
-
output.extend(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
239
|
+
|
|
240
|
+
output.extend(
|
|
241
|
+
[
|
|
242
|
+
"",
|
|
243
|
+
"Use 'processes' to list running processes.",
|
|
244
|
+
f"Use 'logs --process-id {process_id}' to view output.",
|
|
245
|
+
f"Use 'pkill --process-id {process_id}' to stop.",
|
|
246
|
+
]
|
|
247
|
+
)
|
|
245
248
|
|
|
246
249
|
return "\n".join(output)
|
|
247
250
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
"""NPX tool for both sync and background execution."""
|
|
2
2
|
|
|
3
|
-
from pathlib import Path
|
|
4
3
|
from typing import Optional, override
|
|
4
|
+
from pathlib import Path
|
|
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.shell.base_process import BaseBinaryTool
|
|
9
|
-
from mcp.server import FastMCP
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class NpxTool(BaseBinaryTool):
|
|
13
13
|
"""Tool for running npx commands."""
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
name = "npx"
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
@property
|
|
18
18
|
@override
|
|
19
19
|
def description(self) -> str:
|
|
@@ -27,12 +27,12 @@ npx create-react-app my-app
|
|
|
27
27
|
npx http-server -p 8080 # Auto-backgrounds after 2 minutes
|
|
28
28
|
npx prettier --write "**/*.js"
|
|
29
29
|
npx json-server db.json # Auto-backgrounds if needed"""
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
@override
|
|
32
32
|
def get_binary_name(self) -> str:
|
|
33
33
|
"""Get the binary name."""
|
|
34
34
|
return "npx"
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
@override
|
|
37
37
|
async def run(
|
|
38
38
|
self,
|
|
@@ -43,57 +43,53 @@ npx json-server db.json # Auto-backgrounds if needed"""
|
|
|
43
43
|
yes: bool = True,
|
|
44
44
|
) -> str:
|
|
45
45
|
"""Run an npx command with auto-backgrounding.
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
Args:
|
|
48
48
|
ctx: MCP context
|
|
49
49
|
package: NPX package to run
|
|
50
50
|
args: Additional arguments
|
|
51
51
|
cwd: Working directory
|
|
52
52
|
yes: Auto-confirm package installation
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
Returns:
|
|
55
55
|
Command output or background status
|
|
56
56
|
"""
|
|
57
57
|
# Prepare working directory
|
|
58
58
|
work_dir = Path(cwd).resolve() if cwd else Path.cwd()
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
# Prepare flags
|
|
61
61
|
flags = []
|
|
62
62
|
if yes:
|
|
63
63
|
flags.append("-y")
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
# Build full command
|
|
66
66
|
full_args = args.split() if args else []
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
# Always use execute_sync which now has auto-backgrounding
|
|
69
69
|
return await self.execute_sync(
|
|
70
70
|
package,
|
|
71
71
|
cwd=work_dir,
|
|
72
72
|
flags=flags,
|
|
73
73
|
args=full_args,
|
|
74
|
-
timeout=None # Let auto-backgrounding handle timeout
|
|
74
|
+
timeout=None, # Let auto-backgrounding handle timeout
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
def register(self, server: FastMCP) -> None:
|
|
78
78
|
"""Register the tool with the MCP server."""
|
|
79
79
|
tool_self = self
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
@server.tool(name=self.name, description=self.description)
|
|
82
82
|
async def npx(
|
|
83
83
|
ctx: MCPContext,
|
|
84
84
|
package: str,
|
|
85
85
|
args: str = "",
|
|
86
86
|
cwd: Optional[str] = None,
|
|
87
|
-
yes: bool = True
|
|
87
|
+
yes: bool = True,
|
|
88
88
|
) -> str:
|
|
89
89
|
return await tool_self.run(
|
|
90
|
-
ctx,
|
|
91
|
-
package=package,
|
|
92
|
-
args=args,
|
|
93
|
-
cwd=cwd,
|
|
94
|
-
yes=yes
|
|
90
|
+
ctx, package=package, args=args, cwd=cwd, yes=yes
|
|
95
91
|
)
|
|
96
|
-
|
|
92
|
+
|
|
97
93
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
98
94
|
"""Call the tool with arguments."""
|
|
99
95
|
return await self.run(
|
|
@@ -101,9 +97,9 @@ npx json-server db.json # Auto-backgrounds if needed"""
|
|
|
101
97
|
package=params["package"],
|
|
102
98
|
args=params.get("args", ""),
|
|
103
99
|
cwd=params.get("cwd"),
|
|
104
|
-
yes=params.get("yes", True)
|
|
100
|
+
yes=params.get("yes", True),
|
|
105
101
|
)
|
|
106
102
|
|
|
107
103
|
|
|
108
104
|
# Create tool instance
|
|
109
|
-
npx_tool = NpxTool()
|
|
105
|
+
npx_tool = NpxTool()
|
hanzo_mcp/tools/shell/open.py
CHANGED
|
@@ -3,32 +3,29 @@
|
|
|
3
3
|
import platform
|
|
4
4
|
import subprocess
|
|
5
5
|
import webbrowser
|
|
6
|
-
from pathlib import Path
|
|
7
6
|
from typing import override
|
|
7
|
+
from pathlib import Path
|
|
8
8
|
from urllib.parse import urlparse
|
|
9
9
|
|
|
10
|
+
from mcp.server import FastMCP
|
|
10
11
|
from mcp.server.fastmcp import Context as MCPContext
|
|
11
12
|
|
|
12
13
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
13
|
-
from mcp.server import FastMCP
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class OpenTool(BaseTool):
|
|
17
17
|
"""Tool for opening files or URLs in the default application."""
|
|
18
18
|
|
|
19
19
|
name = "open"
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
def register(self, server: FastMCP) -> None:
|
|
22
22
|
"""Register the tool with the MCP server."""
|
|
23
23
|
tool_self = self
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
@server.tool(name=self.name, description=self.description)
|
|
26
|
-
async def open(
|
|
27
|
-
path: str,
|
|
28
|
-
ctx: MCPContext
|
|
29
|
-
) -> str:
|
|
26
|
+
async def open(path: str, ctx: MCPContext) -> str:
|
|
30
27
|
return await tool_self.run(ctx, path)
|
|
31
|
-
|
|
28
|
+
|
|
32
29
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
33
30
|
"""Call the tool with arguments."""
|
|
34
31
|
return await self.run(ctx, params["path"])
|
|
@@ -47,21 +44,21 @@ open /path/to/image.png"""
|
|
|
47
44
|
@override
|
|
48
45
|
async def run(self, ctx: MCPContext, path: str) -> str:
|
|
49
46
|
"""Open a file or URL in the default application.
|
|
50
|
-
|
|
47
|
+
|
|
51
48
|
Args:
|
|
52
49
|
ctx: MCP context
|
|
53
50
|
path: File path or URL to open
|
|
54
|
-
|
|
51
|
+
|
|
55
52
|
Returns:
|
|
56
53
|
Success message
|
|
57
|
-
|
|
54
|
+
|
|
58
55
|
Raises:
|
|
59
56
|
RuntimeError: If opening fails
|
|
60
57
|
"""
|
|
61
58
|
# Check if it's a URL
|
|
62
59
|
parsed = urlparse(path)
|
|
63
|
-
is_url = parsed.scheme in (
|
|
64
|
-
|
|
60
|
+
is_url = parsed.scheme in ("http", "https", "ftp", "file")
|
|
61
|
+
|
|
65
62
|
if is_url:
|
|
66
63
|
# Open URL in default browser
|
|
67
64
|
try:
|
|
@@ -69,15 +66,15 @@ open /path/to/image.png"""
|
|
|
69
66
|
return f"Opened URL in browser: {path}"
|
|
70
67
|
except Exception as e:
|
|
71
68
|
raise RuntimeError(f"Failed to open URL: {e}")
|
|
72
|
-
|
|
69
|
+
|
|
73
70
|
# It's a file path
|
|
74
71
|
file_path = Path(path).expanduser().resolve()
|
|
75
|
-
|
|
72
|
+
|
|
76
73
|
if not file_path.exists():
|
|
77
74
|
raise RuntimeError(f"File not found: {file_path}")
|
|
78
|
-
|
|
75
|
+
|
|
79
76
|
system = platform.system().lower()
|
|
80
|
-
|
|
77
|
+
|
|
81
78
|
try:
|
|
82
79
|
if system == "darwin": # macOS
|
|
83
80
|
subprocess.run(["open", str(file_path)], check=True)
|
|
@@ -98,12 +95,13 @@ open /path/to/image.png"""
|
|
|
98
95
|
elif system == "windows":
|
|
99
96
|
# Use os.startfile on Windows
|
|
100
97
|
import os
|
|
98
|
+
|
|
101
99
|
os.startfile(str(file_path))
|
|
102
100
|
else:
|
|
103
101
|
raise RuntimeError(f"Unsupported platform: {system}")
|
|
104
|
-
|
|
102
|
+
|
|
105
103
|
return f"Opened file: {file_path}"
|
|
106
|
-
|
|
104
|
+
|
|
107
105
|
except subprocess.CalledProcessError as e:
|
|
108
106
|
raise RuntimeError(f"Failed to open file: {e}")
|
|
109
107
|
except Exception as e:
|
|
@@ -111,4 +109,4 @@ open /path/to/image.png"""
|
|
|
111
109
|
|
|
112
110
|
|
|
113
111
|
# Create tool instance
|
|
114
|
-
open_tool = OpenTool()
|
|
112
|
+
open_tool = OpenTool()
|