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/uvx.py
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""Run Python packages with uvx."""
|
|
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(
|
|
@@ -130,28 +129,27 @@ For long-running servers, use uvx_background instead.
|
|
|
130
129
|
# Check if uvx is available
|
|
131
130
|
if not shutil.which("uvx"):
|
|
132
131
|
await tool_ctx.info("uvx not found, attempting to install...")
|
|
133
|
-
|
|
132
|
+
|
|
134
133
|
# Try to auto-install uvx
|
|
135
134
|
install_cmd = "curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
136
|
-
|
|
135
|
+
|
|
137
136
|
try:
|
|
138
137
|
# Run installation
|
|
139
138
|
install_result = subprocess.run(
|
|
140
|
-
install_cmd,
|
|
141
|
-
shell=True,
|
|
142
|
-
capture_output=True,
|
|
143
|
-
text=True,
|
|
144
|
-
timeout=60
|
|
139
|
+
install_cmd, shell=True, capture_output=True, text=True, timeout=60
|
|
145
140
|
)
|
|
146
|
-
|
|
141
|
+
|
|
147
142
|
if install_result.returncode == 0:
|
|
148
143
|
await tool_ctx.info("uvx installed successfully!")
|
|
149
|
-
|
|
144
|
+
|
|
150
145
|
# Add to PATH for current session
|
|
151
146
|
import os
|
|
147
|
+
|
|
152
148
|
home = os.path.expanduser("~")
|
|
153
|
-
os.environ["PATH"] =
|
|
154
|
-
|
|
149
|
+
os.environ["PATH"] = (
|
|
150
|
+
f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
151
|
+
)
|
|
152
|
+
|
|
155
153
|
# Check again
|
|
156
154
|
if not shutil.which("uvx"):
|
|
157
155
|
return """Error: uvx installed but not found in PATH.
|
|
@@ -170,7 +168,7 @@ Or on macOS:
|
|
|
170
168
|
brew install uv
|
|
171
169
|
|
|
172
170
|
Error details: {install_result.stderr}"""
|
|
173
|
-
|
|
171
|
+
|
|
174
172
|
except subprocess.TimeoutExpired:
|
|
175
173
|
return """Error: Installation timed out. Install uvx manually with:
|
|
176
174
|
curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
@@ -182,16 +180,17 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
182
180
|
|
|
183
181
|
# Build command
|
|
184
182
|
cmd = ["uvx"]
|
|
185
|
-
|
|
183
|
+
|
|
186
184
|
if python_version:
|
|
187
185
|
cmd.extend(["--python", python_version])
|
|
188
|
-
|
|
186
|
+
|
|
189
187
|
cmd.append(package)
|
|
190
|
-
|
|
188
|
+
|
|
191
189
|
# Add package arguments
|
|
192
190
|
if args:
|
|
193
191
|
# Split args properly (basic parsing)
|
|
194
192
|
import shlex
|
|
193
|
+
|
|
195
194
|
cmd.extend(shlex.split(args))
|
|
196
195
|
|
|
197
196
|
await tool_ctx.info(f"Running: {' '.join(cmd)}")
|
|
@@ -199,11 +198,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
199
198
|
try:
|
|
200
199
|
# Execute command
|
|
201
200
|
result = subprocess.run(
|
|
202
|
-
cmd,
|
|
203
|
-
capture_output=True,
|
|
204
|
-
text=True,
|
|
205
|
-
timeout=timeout,
|
|
206
|
-
check=True
|
|
201
|
+
cmd, capture_output=True, text=True, timeout=timeout, check=True
|
|
207
202
|
)
|
|
208
203
|
|
|
209
204
|
output = []
|
|
@@ -212,7 +207,11 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
212
207
|
if result.stderr:
|
|
213
208
|
output.append(f"\nSTDERR:\n{result.stderr}")
|
|
214
209
|
|
|
215
|
-
return
|
|
210
|
+
return (
|
|
211
|
+
"\n".join(output)
|
|
212
|
+
if output
|
|
213
|
+
else "Command completed successfully with no output."
|
|
214
|
+
)
|
|
216
215
|
|
|
217
216
|
except subprocess.TimeoutExpired:
|
|
218
217
|
return f"Error: Command timed out after {timeout} seconds. Use uvx_background for long-running processes."
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"""Run Python packages in background with uvx."""
|
|
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(
|
|
@@ -151,28 +150,27 @@ Use 'processes' to list running processes and 'pkill' to stop them.
|
|
|
151
150
|
# Check if uvx is available
|
|
152
151
|
if not shutil.which("uvx"):
|
|
153
152
|
await tool_ctx.info("uvx not found, attempting to install...")
|
|
154
|
-
|
|
153
|
+
|
|
155
154
|
# Try to auto-install uvx
|
|
156
155
|
install_cmd = "curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
157
|
-
|
|
156
|
+
|
|
158
157
|
try:
|
|
159
158
|
# Run installation
|
|
160
159
|
install_result = subprocess.run(
|
|
161
|
-
install_cmd,
|
|
162
|
-
shell=True,
|
|
163
|
-
capture_output=True,
|
|
164
|
-
text=True,
|
|
165
|
-
timeout=60
|
|
160
|
+
install_cmd, shell=True, capture_output=True, text=True, timeout=60
|
|
166
161
|
)
|
|
167
|
-
|
|
162
|
+
|
|
168
163
|
if install_result.returncode == 0:
|
|
169
164
|
await tool_ctx.info("uvx installed successfully!")
|
|
170
|
-
|
|
165
|
+
|
|
171
166
|
# Add to PATH for current session
|
|
172
167
|
import os
|
|
168
|
+
|
|
173
169
|
home = os.path.expanduser("~")
|
|
174
|
-
os.environ["PATH"] =
|
|
175
|
-
|
|
170
|
+
os.environ["PATH"] = (
|
|
171
|
+
f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
172
|
+
)
|
|
173
|
+
|
|
176
174
|
# Check again
|
|
177
175
|
if not shutil.which("uvx"):
|
|
178
176
|
return """Error: uvx installed but not found in PATH.
|
|
@@ -191,7 +189,7 @@ Or on macOS:
|
|
|
191
189
|
brew install uv
|
|
192
190
|
|
|
193
191
|
Error details: {install_result.stderr}"""
|
|
194
|
-
|
|
192
|
+
|
|
195
193
|
except subprocess.TimeoutExpired:
|
|
196
194
|
return """Error: Installation timed out. Install uvx manually with:
|
|
197
195
|
curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
@@ -203,25 +201,27 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
203
201
|
|
|
204
202
|
# Build command
|
|
205
203
|
cmd = ["uvx"]
|
|
206
|
-
|
|
204
|
+
|
|
207
205
|
if python_version:
|
|
208
206
|
cmd.extend(["--python", python_version])
|
|
209
|
-
|
|
207
|
+
|
|
210
208
|
cmd.append(package)
|
|
211
|
-
|
|
209
|
+
|
|
212
210
|
# Add package arguments
|
|
213
211
|
if args:
|
|
214
212
|
# Split args properly (basic parsing)
|
|
215
213
|
import shlex
|
|
214
|
+
|
|
216
215
|
cmd.extend(shlex.split(args))
|
|
217
216
|
|
|
218
217
|
# Generate process ID
|
|
219
218
|
process_id = str(uuid.uuid4())[:8]
|
|
220
|
-
|
|
219
|
+
|
|
221
220
|
# Prepare log file if needed
|
|
222
221
|
log_file = None
|
|
223
222
|
if log_output:
|
|
224
223
|
from pathlib import Path
|
|
224
|
+
|
|
225
225
|
log_dir = Path.home() / ".hanzo" / "logs"
|
|
226
226
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
227
227
|
log_file = log_dir / f"{name}_{process_id}.log"
|
|
@@ -237,7 +237,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
237
237
|
stdout=f,
|
|
238
238
|
stderr=subprocess.STDOUT,
|
|
239
239
|
cwd=working_dir,
|
|
240
|
-
start_new_session=True
|
|
240
|
+
start_new_session=True,
|
|
241
241
|
)
|
|
242
242
|
else:
|
|
243
243
|
process = subprocess.Popen(
|
|
@@ -245,7 +245,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
245
245
|
stdout=subprocess.DEVNULL,
|
|
246
246
|
stderr=subprocess.DEVNULL,
|
|
247
247
|
cwd=working_dir,
|
|
248
|
-
start_new_session=True
|
|
248
|
+
start_new_session=True,
|
|
249
249
|
)
|
|
250
250
|
|
|
251
251
|
# Create background process object
|
|
@@ -255,7 +255,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
255
255
|
name=name,
|
|
256
256
|
process=process,
|
|
257
257
|
log_file=str(log_file) if log_file else None,
|
|
258
|
-
working_dir=working_dir
|
|
258
|
+
working_dir=working_dir,
|
|
259
259
|
)
|
|
260
260
|
|
|
261
261
|
# Register with RunBackgroundTool
|
|
@@ -269,19 +269,21 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
269
269
|
f" PID: {process.pid}",
|
|
270
270
|
f" Command: {' '.join(cmd)}",
|
|
271
271
|
]
|
|
272
|
-
|
|
272
|
+
|
|
273
273
|
if working_dir:
|
|
274
274
|
output.append(f" Working Dir: {working_dir}")
|
|
275
|
-
|
|
275
|
+
|
|
276
276
|
if log_file:
|
|
277
277
|
output.append(f" Log: {log_file}")
|
|
278
|
-
|
|
279
|
-
output.extend(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
278
|
+
|
|
279
|
+
output.extend(
|
|
280
|
+
[
|
|
281
|
+
"",
|
|
282
|
+
"Use 'processes' to list running processes.",
|
|
283
|
+
f"Use 'logs --process-id {process_id}' to view output.",
|
|
284
|
+
f"Use 'pkill --process-id {process_id}' to stop.",
|
|
285
|
+
]
|
|
286
|
+
)
|
|
285
287
|
|
|
286
288
|
return "\n".join(output)
|
|
287
289
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
"""UVX 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 UvxTool(BaseBinaryTool):
|
|
13
13
|
"""Tool for running uvx commands."""
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
name = "uvx"
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
@property
|
|
18
18
|
@override
|
|
19
19
|
def description(self) -> str:
|
|
@@ -27,12 +27,12 @@ uvx ruff check .
|
|
|
27
27
|
uvx mkdocs serve # Auto-backgrounds after 2 minutes
|
|
28
28
|
uvx black --check src/
|
|
29
29
|
uvx jupyter lab --port 8888 # 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 "uvx"
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
@override
|
|
37
37
|
async def run(
|
|
38
38
|
self,
|
|
@@ -43,57 +43,53 @@ uvx jupyter lab --port 8888 # Auto-backgrounds if needed"""
|
|
|
43
43
|
python: Optional[str] = None,
|
|
44
44
|
) -> str:
|
|
45
45
|
"""Run a uvx command with auto-backgrounding.
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
Args:
|
|
48
48
|
ctx: MCP context
|
|
49
49
|
package: Python package to run
|
|
50
50
|
args: Additional arguments
|
|
51
51
|
cwd: Working directory
|
|
52
52
|
python: Python version constraint
|
|
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 python:
|
|
63
63
|
flags.extend(["--python", python])
|
|
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 uvx(
|
|
83
83
|
ctx: MCPContext,
|
|
84
84
|
package: str,
|
|
85
85
|
args: str = "",
|
|
86
86
|
cwd: Optional[str] = None,
|
|
87
|
-
python: Optional[str] = None
|
|
87
|
+
python: Optional[str] = None,
|
|
88
88
|
) -> str:
|
|
89
89
|
return await tool_self.run(
|
|
90
|
-
ctx,
|
|
91
|
-
package=package,
|
|
92
|
-
args=args,
|
|
93
|
-
cwd=cwd,
|
|
94
|
-
python=python
|
|
90
|
+
ctx, package=package, args=args, cwd=cwd, python=python
|
|
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 @@ uvx jupyter lab --port 8888 # Auto-backgrounds if needed"""
|
|
|
101
97
|
package=params["package"],
|
|
102
98
|
args=params.get("args", ""),
|
|
103
99
|
cwd=params.get("cwd"),
|
|
104
|
-
python=params.get("python")
|
|
100
|
+
python=params.get("python"),
|
|
105
101
|
)
|
|
106
102
|
|
|
107
103
|
|
|
108
104
|
# Create tool instance
|
|
109
|
-
uvx_tool = UvxTool()
|
|
105
|
+
uvx_tool = UvxTool()
|
hanzo_mcp/tools/todo/__init__.py
CHANGED
|
@@ -6,8 +6,8 @@ within Claude Desktop sessions.
|
|
|
6
6
|
|
|
7
7
|
from mcp.server import FastMCP
|
|
8
8
|
|
|
9
|
-
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
10
9
|
from hanzo_mcp.tools.todo.todo import TodoTool
|
|
10
|
+
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
11
11
|
|
|
12
12
|
# Export all tool classes
|
|
13
13
|
__all__ = [
|
|
@@ -51,7 +51,11 @@ def register_todo_tools(
|
|
|
51
51
|
if enabled_tools:
|
|
52
52
|
# Use individual tool configuration
|
|
53
53
|
# Support both old names and new name for backward compatibility
|
|
54
|
-
if
|
|
54
|
+
if (
|
|
55
|
+
enabled_tools.get("todo", True)
|
|
56
|
+
or enabled_tools.get("todo_read", True)
|
|
57
|
+
or enabled_tools.get("todo_write", True)
|
|
58
|
+
):
|
|
55
59
|
tools.append(TodoTool())
|
|
56
60
|
else:
|
|
57
61
|
# Use all tools (backward compatibility)
|
hanzo_mcp/tools/todo/todo.py
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
"""Unified todo tool."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated, TypedDict, Unpack, final, override, Optional, List, Dict, Any
|
|
4
|
-
import json
|
|
5
3
|
import uuid
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Dict,
|
|
7
|
+
Unpack,
|
|
8
|
+
Optional,
|
|
9
|
+
Annotated,
|
|
10
|
+
TypedDict,
|
|
11
|
+
final,
|
|
12
|
+
override,
|
|
13
|
+
)
|
|
6
14
|
from datetime import datetime
|
|
7
|
-
from pathlib import Path
|
|
8
15
|
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
16
|
from pydantic import Field
|
|
17
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
11
18
|
|
|
12
19
|
from hanzo_mcp.tools.todo.base import TodoBaseTool
|
|
13
20
|
|
|
14
|
-
|
|
15
21
|
# Parameter types
|
|
16
22
|
Action = Annotated[
|
|
17
23
|
str,
|
|
@@ -64,6 +70,7 @@ Filter = Annotated[
|
|
|
64
70
|
|
|
65
71
|
class TodoParams(TypedDict, total=False):
|
|
66
72
|
"""Parameters for todo tool."""
|
|
73
|
+
|
|
67
74
|
action: str
|
|
68
75
|
content: Optional[str]
|
|
69
76
|
id: Optional[str]
|
|
@@ -107,7 +114,7 @@ todo --filter in_progress
|
|
|
107
114
|
|
|
108
115
|
# Extract action
|
|
109
116
|
action = params.get("action", "list")
|
|
110
|
-
|
|
117
|
+
|
|
111
118
|
# Route to appropriate handler
|
|
112
119
|
if action == "list":
|
|
113
120
|
return await self._handle_list(params.get("filter"), tool_ctx)
|
|
@@ -125,16 +132,16 @@ todo --filter in_progress
|
|
|
125
132
|
async def _handle_list(self, filter_status: Optional[str], tool_ctx) -> str:
|
|
126
133
|
"""List todos."""
|
|
127
134
|
todos = self.read_todos()
|
|
128
|
-
|
|
135
|
+
|
|
129
136
|
if not todos:
|
|
130
137
|
return "No todos found. Use 'todo \"Your task here\"' to add one."
|
|
131
|
-
|
|
138
|
+
|
|
132
139
|
# Apply filter if specified
|
|
133
140
|
if filter_status:
|
|
134
141
|
todos = [t for t in todos if t.get("status") == filter_status]
|
|
135
142
|
if not todos:
|
|
136
143
|
return f"No todos with status '{filter_status}'"
|
|
137
|
-
|
|
144
|
+
|
|
138
145
|
# Group by status
|
|
139
146
|
by_status = {}
|
|
140
147
|
for todo in todos:
|
|
@@ -142,21 +149,27 @@ todo --filter in_progress
|
|
|
142
149
|
if status not in by_status:
|
|
143
150
|
by_status[status] = []
|
|
144
151
|
by_status[status].append(todo)
|
|
145
|
-
|
|
152
|
+
|
|
146
153
|
# Format output
|
|
147
154
|
output = ["=== Todo List ==="]
|
|
148
|
-
|
|
155
|
+
|
|
149
156
|
# Show in order: in_progress, pending, completed
|
|
150
157
|
for status in ["in_progress", "pending", "completed"]:
|
|
151
158
|
if status in by_status:
|
|
152
159
|
output.append(f"\n{status.replace('_', ' ').title()}:")
|
|
153
160
|
for todo in by_status[status]:
|
|
154
|
-
priority_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
priority_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(
|
|
162
|
+
todo.get("priority", "medium"), "⚪"
|
|
163
|
+
)
|
|
164
|
+
output.append(
|
|
165
|
+
f"{priority_icon} [{todo['id'][:8]}] {todo['content']}"
|
|
166
|
+
)
|
|
167
|
+
|
|
157
168
|
# Summary
|
|
158
|
-
output.append(
|
|
159
|
-
|
|
169
|
+
output.append(
|
|
170
|
+
f"\nTotal: {len(todos)} | In Progress: {len(by_status.get('in_progress', []))} | Pending: {len(by_status.get('pending', []))} | Completed: {len(by_status.get('completed', []))}"
|
|
171
|
+
)
|
|
172
|
+
|
|
160
173
|
return "\n".join(output)
|
|
161
174
|
|
|
162
175
|
async def _handle_add(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
@@ -164,9 +177,9 @@ todo --filter in_progress
|
|
|
164
177
|
content = params.get("content")
|
|
165
178
|
if not content:
|
|
166
179
|
return "Error: content is required for add action"
|
|
167
|
-
|
|
180
|
+
|
|
168
181
|
todos = self.read_todos()
|
|
169
|
-
|
|
182
|
+
|
|
170
183
|
new_todo = {
|
|
171
184
|
"id": str(uuid.uuid4()),
|
|
172
185
|
"content": content,
|
|
@@ -174,10 +187,10 @@ todo --filter in_progress
|
|
|
174
187
|
"priority": params.get("priority", "medium"),
|
|
175
188
|
"created_at": datetime.now().isoformat(),
|
|
176
189
|
}
|
|
177
|
-
|
|
190
|
+
|
|
178
191
|
todos.append(new_todo)
|
|
179
192
|
self.write_todos(todos)
|
|
180
|
-
|
|
193
|
+
|
|
181
194
|
await tool_ctx.info(f"Added todo: {content}")
|
|
182
195
|
return f"Added todo [{new_todo['id'][:8]}]: {content}"
|
|
183
196
|
|
|
@@ -186,19 +199,19 @@ todo --filter in_progress
|
|
|
186
199
|
todo_id = params.get("id")
|
|
187
200
|
if not todo_id:
|
|
188
201
|
return "Error: id is required for update action"
|
|
189
|
-
|
|
202
|
+
|
|
190
203
|
todos = self.read_todos()
|
|
191
|
-
|
|
204
|
+
|
|
192
205
|
# Find todo (support partial ID match)
|
|
193
206
|
todo_found = None
|
|
194
207
|
for todo in todos:
|
|
195
208
|
if todo["id"].startswith(todo_id):
|
|
196
209
|
todo_found = todo
|
|
197
210
|
break
|
|
198
|
-
|
|
211
|
+
|
|
199
212
|
if not todo_found:
|
|
200
213
|
return f"Error: Todo with ID '{todo_id}' not found"
|
|
201
|
-
|
|
214
|
+
|
|
202
215
|
# Update fields
|
|
203
216
|
if params.get("content"):
|
|
204
217
|
todo_found["content"] = params["content"]
|
|
@@ -206,11 +219,11 @@ todo --filter in_progress
|
|
|
206
219
|
todo_found["status"] = params["status"]
|
|
207
220
|
if params.get("priority"):
|
|
208
221
|
todo_found["priority"] = params["priority"]
|
|
209
|
-
|
|
222
|
+
|
|
210
223
|
todo_found["updated_at"] = datetime.now().isoformat()
|
|
211
|
-
|
|
224
|
+
|
|
212
225
|
self.write_todos(todos)
|
|
213
|
-
|
|
226
|
+
|
|
214
227
|
await tool_ctx.info(f"Updated todo: {todo_found['content']}")
|
|
215
228
|
return f"Updated todo [{todo_found['id'][:8]}]: {todo_found['content']} (status: {todo_found['status']})"
|
|
216
229
|
|
|
@@ -218,44 +231,44 @@ todo --filter in_progress
|
|
|
218
231
|
"""Remove todo."""
|
|
219
232
|
if not todo_id:
|
|
220
233
|
return "Error: id is required for remove action"
|
|
221
|
-
|
|
234
|
+
|
|
222
235
|
todos = self.read_todos()
|
|
223
|
-
|
|
236
|
+
|
|
224
237
|
# Find and remove (support partial ID match)
|
|
225
238
|
removed = None
|
|
226
239
|
for i, todo in enumerate(todos):
|
|
227
240
|
if todo["id"].startswith(todo_id):
|
|
228
241
|
removed = todos.pop(i)
|
|
229
242
|
break
|
|
230
|
-
|
|
243
|
+
|
|
231
244
|
if not removed:
|
|
232
245
|
return f"Error: Todo with ID '{todo_id}' not found"
|
|
233
|
-
|
|
246
|
+
|
|
234
247
|
self.write_todos(todos)
|
|
235
|
-
|
|
248
|
+
|
|
236
249
|
await tool_ctx.info(f"Removed todo: {removed['content']}")
|
|
237
250
|
return f"Removed todo [{removed['id'][:8]}]: {removed['content']}"
|
|
238
251
|
|
|
239
252
|
async def _handle_clear(self, filter_status: Optional[str], tool_ctx) -> str:
|
|
240
253
|
"""Clear todos."""
|
|
241
254
|
todos = self.read_todos()
|
|
242
|
-
|
|
255
|
+
|
|
243
256
|
if filter_status:
|
|
244
257
|
# Clear only todos with specific status
|
|
245
258
|
original_count = len(todos)
|
|
246
259
|
todos = [t for t in todos if t.get("status") != filter_status]
|
|
247
260
|
removed_count = original_count - len(todos)
|
|
248
|
-
|
|
261
|
+
|
|
249
262
|
if removed_count == 0:
|
|
250
263
|
return f"No todos with status '{filter_status}' to clear"
|
|
251
|
-
|
|
264
|
+
|
|
252
265
|
self.write_todos(todos)
|
|
253
266
|
return f"Cleared {removed_count} todo(s) with status '{filter_status}'"
|
|
254
267
|
else:
|
|
255
268
|
# Clear all
|
|
256
269
|
if not todos:
|
|
257
270
|
return "No todos to clear"
|
|
258
|
-
|
|
271
|
+
|
|
259
272
|
count = len(todos)
|
|
260
273
|
self.write_todos([])
|
|
261
274
|
return f"Cleared all {count} todo(s)"
|
|
@@ -283,4 +296,4 @@ todo --filter in_progress
|
|
|
283
296
|
status=status,
|
|
284
297
|
priority=priority,
|
|
285
298
|
filter=filter,
|
|
286
|
-
)
|
|
299
|
+
)
|
|
@@ -4,13 +4,13 @@ This module provides the TodoRead tool for reading the current todo list for a s
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
-
from typing import Annotated, TypedDict,
|
|
7
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
8
8
|
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
|
-
from mcp.server import FastMCP
|
|
11
9
|
from pydantic import Field
|
|
10
|
+
from mcp.server import FastMCP
|
|
11
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
12
12
|
|
|
13
|
-
from hanzo_mcp.tools.todo.base import
|
|
13
|
+
from hanzo_mcp.tools.todo.base import TodoStorage, TodoBaseTool
|
|
14
14
|
|
|
15
15
|
SessionId = Annotated[
|
|
16
16
|
str | int | float,
|
|
@@ -139,8 +139,5 @@ Usage:
|
|
|
139
139
|
tool_self = self # Create a reference to self for use in the closure
|
|
140
140
|
|
|
141
141
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
142
|
-
async def todo_read(
|
|
143
|
-
session_id: SessionId,
|
|
144
|
-
ctx: MCPContext
|
|
145
|
-
) -> str:
|
|
142
|
+
async def todo_read(session_id: SessionId, ctx: MCPContext) -> str:
|
|
146
143
|
return await tool_self.call(ctx, session_id=session_id)
|