hanzo-mcp 0.7.7__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 +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.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.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.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -6,17 +6,25 @@ and other code structures with full context.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
Dict,
|
|
12
|
+
List,
|
|
13
|
+
Unpack,
|
|
14
|
+
Optional,
|
|
15
|
+
Annotated,
|
|
16
|
+
TypedDict,
|
|
17
|
+
final,
|
|
18
|
+
override,
|
|
19
|
+
)
|
|
9
20
|
from pathlib import Path
|
|
10
|
-
from typing import Annotated, TypedDict, Unpack, final, override, Optional, List, Dict, Any
|
|
11
|
-
import json
|
|
12
21
|
|
|
13
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
14
|
-
from grep_ast.grep_ast import TreeContext
|
|
15
22
|
from pydantic import Field
|
|
23
|
+
from grep_ast.grep_ast import TreeContext
|
|
24
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
16
25
|
|
|
17
26
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
18
27
|
|
|
19
|
-
|
|
20
28
|
# Parameter types
|
|
21
29
|
Action = Annotated[
|
|
22
30
|
str,
|
|
@@ -77,6 +85,7 @@ Limit = Annotated[
|
|
|
77
85
|
|
|
78
86
|
class SymbolsParams(TypedDict, total=False):
|
|
79
87
|
"""Parameters for symbols tool."""
|
|
88
|
+
|
|
80
89
|
action: str
|
|
81
90
|
pattern: Optional[str]
|
|
82
91
|
path: str
|
|
@@ -89,7 +98,7 @@ class SymbolsParams(TypedDict, total=False):
|
|
|
89
98
|
@final
|
|
90
99
|
class SymbolsTool(FilesystemBaseTool):
|
|
91
100
|
"""Tool for code symbol operations using tree-sitter."""
|
|
92
|
-
|
|
101
|
+
|
|
93
102
|
def __init__(self, permission_manager):
|
|
94
103
|
"""Initialize the symbols tool."""
|
|
95
104
|
super().__init__(permission_manager)
|
|
@@ -128,11 +137,13 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
128
137
|
|
|
129
138
|
# Extract action
|
|
130
139
|
action = params.get("action", "search")
|
|
131
|
-
|
|
140
|
+
|
|
132
141
|
# Route to appropriate handler
|
|
133
142
|
if action == "search":
|
|
134
143
|
return await self._handle_search(params, tool_ctx)
|
|
135
|
-
elif
|
|
144
|
+
elif (
|
|
145
|
+
action == "ast" or action == "grep_ast"
|
|
146
|
+
): # Support both for backward compatibility
|
|
136
147
|
return await self._handle_ast(params, tool_ctx)
|
|
137
148
|
elif action == "index":
|
|
138
149
|
return await self._handle_index(params, tool_ctx)
|
|
@@ -148,12 +159,12 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
148
159
|
pattern = params.get("pattern")
|
|
149
160
|
if not pattern:
|
|
150
161
|
return "Error: pattern required for search action"
|
|
151
|
-
|
|
162
|
+
|
|
152
163
|
path = params.get("path", ".")
|
|
153
164
|
ignore_case = params.get("ignore_case", False)
|
|
154
165
|
show_context = params.get("show_context", True)
|
|
155
166
|
limit = params.get("limit", 50)
|
|
156
|
-
|
|
167
|
+
|
|
157
168
|
# Validate path
|
|
158
169
|
path_validation = self.validate_path(path)
|
|
159
170
|
if not path_validation.is_valid:
|
|
@@ -180,11 +191,11 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
180
191
|
# Process files
|
|
181
192
|
results = []
|
|
182
193
|
match_count = 0
|
|
183
|
-
|
|
194
|
+
|
|
184
195
|
for file_path in files_to_process:
|
|
185
196
|
if match_count >= limit:
|
|
186
197
|
break
|
|
187
|
-
|
|
198
|
+
|
|
188
199
|
try:
|
|
189
200
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
190
201
|
code = f.read()
|
|
@@ -199,7 +210,7 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
199
210
|
|
|
200
211
|
# Find matches
|
|
201
212
|
loi = tc.grep(pattern, ignore_case)
|
|
202
|
-
|
|
213
|
+
|
|
203
214
|
if loi:
|
|
204
215
|
if show_context:
|
|
205
216
|
tc.add_lines_of_interest(loi)
|
|
@@ -207,11 +218,13 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
207
218
|
output = tc.format()
|
|
208
219
|
else:
|
|
209
220
|
# Just show matching lines
|
|
210
|
-
output = "\n".join(
|
|
211
|
-
|
|
221
|
+
output = "\n".join(
|
|
222
|
+
[f"{line}: {code.splitlines()[line - 1]}" for line in loi]
|
|
223
|
+
)
|
|
224
|
+
|
|
212
225
|
results.append(f"\n{file_path}:\n{output}\n")
|
|
213
226
|
match_count += len(loi)
|
|
214
|
-
|
|
227
|
+
|
|
215
228
|
except Exception as e:
|
|
216
229
|
await tool_ctx.warning(f"Could not parse {file_path}: {str(e)}")
|
|
217
230
|
|
|
@@ -221,10 +234,10 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
221
234
|
output = [f"=== Symbol Search Results for '{pattern}' ==="]
|
|
222
235
|
output.append(f"Found {match_count} matches in {len(results)} files\n")
|
|
223
236
|
output.extend(results)
|
|
224
|
-
|
|
237
|
+
|
|
225
238
|
if match_count >= limit:
|
|
226
239
|
output.append(f"\n(Results limited to {limit} matches)")
|
|
227
|
-
|
|
240
|
+
|
|
228
241
|
return "\n".join(output)
|
|
229
242
|
|
|
230
243
|
async def _handle_ast(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
@@ -264,7 +277,7 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
264
277
|
# Process files
|
|
265
278
|
results = []
|
|
266
279
|
match_count = 0
|
|
267
|
-
|
|
280
|
+
|
|
268
281
|
for file_path in files_to_process:
|
|
269
282
|
if match_count >= limit:
|
|
270
283
|
break
|
|
@@ -284,7 +297,6 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
284
297
|
|
|
285
298
|
# Find matches with case sensitivity option
|
|
286
299
|
if ignore_case:
|
|
287
|
-
import re
|
|
288
300
|
loi = tc.grep(pattern, ignore_case=True)
|
|
289
301
|
else:
|
|
290
302
|
loi = tc.grep(pattern, ignore_case=False)
|
|
@@ -293,17 +305,17 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
293
305
|
# Always show AST context for grep_ast
|
|
294
306
|
tc.add_lines_of_interest(loi)
|
|
295
307
|
tc.add_context()
|
|
296
|
-
|
|
308
|
+
|
|
297
309
|
# Get the formatted output with structure
|
|
298
310
|
output = tc.format()
|
|
299
|
-
|
|
311
|
+
|
|
300
312
|
# Add section separator and file info
|
|
301
|
-
results.append(f"\n{'='*60}")
|
|
313
|
+
results.append(f"\n{'=' * 60}")
|
|
302
314
|
results.append(f"File: {file_path}")
|
|
303
315
|
results.append(f"Matches: {len(loi)}")
|
|
304
|
-
results.append(f"{'='*60}\n")
|
|
316
|
+
results.append(f"{'=' * 60}\n")
|
|
305
317
|
results.append(output)
|
|
306
|
-
|
|
318
|
+
|
|
307
319
|
match_count += len(loi)
|
|
308
320
|
|
|
309
321
|
except Exception as e:
|
|
@@ -313,7 +325,9 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
313
325
|
return f"No matches found for '{pattern}' in {path}"
|
|
314
326
|
|
|
315
327
|
output = [f"=== AST-aware Grep Results for '{pattern}' ==="]
|
|
316
|
-
output.append(
|
|
328
|
+
output.append(
|
|
329
|
+
f"Total matches: {match_count} in {len([r for r in results if '===' in str(r)]) // 4} files\n"
|
|
330
|
+
)
|
|
317
331
|
output.extend(results)
|
|
318
332
|
|
|
319
333
|
if match_count >= limit:
|
|
@@ -324,14 +338,14 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
324
338
|
async def _handle_index(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
325
339
|
"""Index symbols in a codebase."""
|
|
326
340
|
path = params.get("path", ".")
|
|
327
|
-
|
|
341
|
+
|
|
328
342
|
# Validate path
|
|
329
343
|
is_allowed, error_message = await self.check_path_allowed(path, tool_ctx)
|
|
330
344
|
if not is_allowed:
|
|
331
345
|
return error_message
|
|
332
346
|
|
|
333
347
|
await tool_ctx.info(f"Indexing symbols in {path}...")
|
|
334
|
-
|
|
348
|
+
|
|
335
349
|
files_to_process = self._get_source_files(path)
|
|
336
350
|
if not files_to_process:
|
|
337
351
|
return f"No source code files found in {path}"
|
|
@@ -343,38 +357,38 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
343
357
|
"methods": [],
|
|
344
358
|
"variables": [],
|
|
345
359
|
}
|
|
346
|
-
|
|
360
|
+
|
|
347
361
|
indexed_count = 0
|
|
348
362
|
symbol_count = 0
|
|
349
|
-
|
|
363
|
+
|
|
350
364
|
for file_path in files_to_process:
|
|
351
365
|
try:
|
|
352
366
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
353
367
|
code = f.read()
|
|
354
368
|
|
|
355
369
|
tc = TreeContext(file_path, code, color=False, verbose=False)
|
|
356
|
-
|
|
370
|
+
|
|
357
371
|
# Extract symbols (simplified - would need proper tree-sitter queries)
|
|
358
372
|
# This is a placeholder for actual symbol extraction
|
|
359
373
|
symbols = self._extract_symbols(tc, file_path)
|
|
360
|
-
|
|
374
|
+
|
|
361
375
|
for symbol_type, syms in symbols.items():
|
|
362
376
|
self._symbol_cache[path][symbol_type].extend(syms)
|
|
363
377
|
symbol_count += len(syms)
|
|
364
|
-
|
|
378
|
+
|
|
365
379
|
indexed_count += 1
|
|
366
|
-
|
|
380
|
+
|
|
367
381
|
except Exception as e:
|
|
368
382
|
await tool_ctx.warning(f"Could not index {file_path}: {str(e)}")
|
|
369
383
|
|
|
370
384
|
output = [f"=== Symbol Indexing Complete ==="]
|
|
371
385
|
output.append(f"Indexed {indexed_count} files")
|
|
372
386
|
output.append(f"Found {symbol_count} total symbols:")
|
|
373
|
-
|
|
387
|
+
|
|
374
388
|
for symbol_type, symbols in self._symbol_cache[path].items():
|
|
375
389
|
if symbols:
|
|
376
390
|
output.append(f" {symbol_type}: {len(symbols)}")
|
|
377
|
-
|
|
391
|
+
|
|
378
392
|
return "\n".join(output)
|
|
379
393
|
|
|
380
394
|
async def _handle_query(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
@@ -383,14 +397,14 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
383
397
|
symbol_type = params.get("symbol_type")
|
|
384
398
|
pattern = params.get("pattern")
|
|
385
399
|
limit = params.get("limit", 50)
|
|
386
|
-
|
|
400
|
+
|
|
387
401
|
# Check if we have indexed this path
|
|
388
402
|
if path not in self._symbol_cache:
|
|
389
403
|
return f"No symbols indexed for {path}. Run 'symbols --action index --path {path}' first."
|
|
390
|
-
|
|
404
|
+
|
|
391
405
|
symbols = self._symbol_cache[path]
|
|
392
406
|
results = []
|
|
393
|
-
|
|
407
|
+
|
|
394
408
|
# Filter by type if specified
|
|
395
409
|
if symbol_type:
|
|
396
410
|
if symbol_type in symbols:
|
|
@@ -402,7 +416,7 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
402
416
|
candidates = []
|
|
403
417
|
for syms in symbols.values():
|
|
404
418
|
candidates.extend(syms)
|
|
405
|
-
|
|
419
|
+
|
|
406
420
|
# Filter by pattern if specified
|
|
407
421
|
if pattern:
|
|
408
422
|
filtered = []
|
|
@@ -410,23 +424,23 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
410
424
|
if pattern.lower() in sym["name"].lower():
|
|
411
425
|
filtered.append(sym)
|
|
412
426
|
candidates = filtered
|
|
413
|
-
|
|
427
|
+
|
|
414
428
|
# Limit results
|
|
415
429
|
candidates = candidates[:limit]
|
|
416
|
-
|
|
430
|
+
|
|
417
431
|
if not candidates:
|
|
418
432
|
return "No symbols found matching criteria"
|
|
419
|
-
|
|
433
|
+
|
|
420
434
|
output = [f"=== Symbol Query Results ==="]
|
|
421
435
|
output.append(f"Found {len(candidates)} symbols\n")
|
|
422
|
-
|
|
436
|
+
|
|
423
437
|
for sym in candidates:
|
|
424
438
|
output.append(f"{sym['type']}: {sym['name']}")
|
|
425
439
|
output.append(f" File: {sym['file']}:{sym['line']}")
|
|
426
440
|
if sym.get("signature"):
|
|
427
441
|
output.append(f" Signature: {sym['signature']}")
|
|
428
442
|
output.append("")
|
|
429
|
-
|
|
443
|
+
|
|
430
444
|
return "\n".join(output)
|
|
431
445
|
|
|
432
446
|
async def _handle_list(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
@@ -439,14 +453,36 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
439
453
|
"""Get all source code files in a path."""
|
|
440
454
|
path_obj = Path(path)
|
|
441
455
|
files_to_process = []
|
|
442
|
-
|
|
456
|
+
|
|
443
457
|
# Common source file extensions
|
|
444
458
|
extensions = {
|
|
445
|
-
".py",
|
|
446
|
-
".
|
|
447
|
-
".
|
|
459
|
+
".py",
|
|
460
|
+
".js",
|
|
461
|
+
".ts",
|
|
462
|
+
".jsx",
|
|
463
|
+
".tsx",
|
|
464
|
+
".java",
|
|
465
|
+
".cpp",
|
|
466
|
+
".c",
|
|
467
|
+
".h",
|
|
468
|
+
".hpp",
|
|
469
|
+
".cs",
|
|
470
|
+
".rb",
|
|
471
|
+
".go",
|
|
472
|
+
".rs",
|
|
473
|
+
".swift",
|
|
474
|
+
".kt",
|
|
475
|
+
".scala",
|
|
476
|
+
".php",
|
|
477
|
+
".lua",
|
|
478
|
+
".r",
|
|
479
|
+
".jl",
|
|
480
|
+
".ex",
|
|
481
|
+
".exs",
|
|
482
|
+
".clj",
|
|
483
|
+
".cljs",
|
|
448
484
|
}
|
|
449
|
-
|
|
485
|
+
|
|
450
486
|
if path_obj.is_file():
|
|
451
487
|
if path_obj.suffix in extensions:
|
|
452
488
|
files_to_process.append(str(path_obj))
|
|
@@ -454,12 +490,16 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
454
490
|
for root, _, files in os.walk(path_obj):
|
|
455
491
|
for file in files:
|
|
456
492
|
file_path = Path(root) / file
|
|
457
|
-
if file_path.suffix in extensions and self.is_path_allowed(
|
|
493
|
+
if file_path.suffix in extensions and self.is_path_allowed(
|
|
494
|
+
str(file_path)
|
|
495
|
+
):
|
|
458
496
|
files_to_process.append(str(file_path))
|
|
459
|
-
|
|
497
|
+
|
|
460
498
|
return files_to_process
|
|
461
499
|
|
|
462
|
-
def _extract_symbols(
|
|
500
|
+
def _extract_symbols(
|
|
501
|
+
self, tc: TreeContext, file_path: str
|
|
502
|
+
) -> Dict[str, List[Dict[str, Any]]]:
|
|
463
503
|
"""Extract symbols from a TreeContext (placeholder implementation)."""
|
|
464
504
|
# This would need proper tree-sitter queries to extract symbols
|
|
465
505
|
# For now, return empty structure
|
|
@@ -472,4 +512,4 @@ Finds code structures (functions, classes, methods) with full context."""
|
|
|
472
512
|
|
|
473
513
|
def register(self, mcp_server) -> None:
|
|
474
514
|
"""Register this tool with the MCP server."""
|
|
475
|
-
pass
|
|
515
|
+
pass
|
|
@@ -3,16 +3,14 @@
|
|
|
3
3
|
Unix-style tree command for directory visualization.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
from typing import List, Unpack, Optional, Annotated, TypedDict, final, override
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Annotated, TypedDict, Unpack, final, override, Optional, List
|
|
9
8
|
|
|
10
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
11
9
|
from pydantic import Field
|
|
10
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
12
11
|
|
|
13
12
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
14
13
|
|
|
15
|
-
|
|
16
14
|
# Parameter types
|
|
17
15
|
TreePath = Annotated[
|
|
18
16
|
str,
|
|
@@ -65,6 +63,7 @@ Pattern = Annotated[
|
|
|
65
63
|
|
|
66
64
|
class TreeParams(TypedDict, total=False):
|
|
67
65
|
"""Parameters for tree tool."""
|
|
66
|
+
|
|
68
67
|
path: str
|
|
69
68
|
depth: Optional[int]
|
|
70
69
|
show_hidden: bool
|
|
@@ -103,7 +102,7 @@ tree --pattern "*.py" --show-size"""
|
|
|
103
102
|
) -> str:
|
|
104
103
|
"""Execute tree command."""
|
|
105
104
|
tool_ctx = self.create_tool_context(ctx)
|
|
106
|
-
|
|
105
|
+
|
|
107
106
|
# Extract parameters
|
|
108
107
|
path = params.get("path", ".")
|
|
109
108
|
max_depth = params.get("depth")
|
|
@@ -111,33 +110,33 @@ tree --pattern "*.py" --show-size"""
|
|
|
111
110
|
dirs_only = params.get("dirs_only", False)
|
|
112
111
|
show_size = params.get("show_size", False)
|
|
113
112
|
pattern = params.get("pattern")
|
|
114
|
-
|
|
113
|
+
|
|
115
114
|
# Validate path
|
|
116
115
|
path_validation = self.validate_path(path)
|
|
117
116
|
if path_validation.is_error:
|
|
118
117
|
return f"Error: {path_validation.error_message}"
|
|
119
|
-
|
|
118
|
+
|
|
120
119
|
# Check permissions
|
|
121
120
|
allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
|
|
122
121
|
if not allowed:
|
|
123
122
|
return error_msg
|
|
124
|
-
|
|
123
|
+
|
|
125
124
|
# Check existence
|
|
126
125
|
exists, error_msg = await self.check_path_exists(path, tool_ctx)
|
|
127
126
|
if not exists:
|
|
128
127
|
return error_msg
|
|
129
|
-
|
|
128
|
+
|
|
130
129
|
path_obj = Path(path)
|
|
131
130
|
if not path_obj.is_dir():
|
|
132
131
|
return f"Error: {path} is not a directory"
|
|
133
|
-
|
|
132
|
+
|
|
134
133
|
# Build tree
|
|
135
134
|
output = [str(path_obj)]
|
|
136
135
|
stats = {"dirs": 0, "files": 0}
|
|
137
|
-
|
|
136
|
+
|
|
138
137
|
self._build_tree(
|
|
139
|
-
path_obj,
|
|
140
|
-
output,
|
|
138
|
+
path_obj,
|
|
139
|
+
output,
|
|
141
140
|
stats,
|
|
142
141
|
prefix="",
|
|
143
142
|
is_last=True,
|
|
@@ -146,16 +145,16 @@ tree --pattern "*.py" --show-size"""
|
|
|
146
145
|
show_hidden=show_hidden,
|
|
147
146
|
dirs_only=dirs_only,
|
|
148
147
|
show_size=show_size,
|
|
149
|
-
pattern=pattern
|
|
148
|
+
pattern=pattern,
|
|
150
149
|
)
|
|
151
|
-
|
|
150
|
+
|
|
152
151
|
# Add summary
|
|
153
152
|
output.append("")
|
|
154
153
|
if dirs_only:
|
|
155
154
|
output.append(f"{stats['dirs']} directories")
|
|
156
155
|
else:
|
|
157
156
|
output.append(f"{stats['dirs']} directories, {stats['files']} files")
|
|
158
|
-
|
|
157
|
+
|
|
159
158
|
return "\n".join(output)
|
|
160
159
|
|
|
161
160
|
def _build_tree(
|
|
@@ -176,35 +175,38 @@ tree --pattern "*.py" --show-size"""
|
|
|
176
175
|
# Check depth limit
|
|
177
176
|
if max_depth is not None and current_depth >= max_depth:
|
|
178
177
|
return
|
|
179
|
-
|
|
178
|
+
|
|
180
179
|
try:
|
|
181
180
|
# Get entries
|
|
182
181
|
entries = list(path.iterdir())
|
|
183
|
-
|
|
182
|
+
|
|
184
183
|
# Filter hidden files
|
|
185
184
|
if not show_hidden:
|
|
186
185
|
entries = [e for e in entries if not e.name.startswith(".")]
|
|
187
|
-
|
|
186
|
+
|
|
188
187
|
# Filter by pattern
|
|
189
188
|
if pattern:
|
|
190
189
|
import fnmatch
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
|
|
191
|
+
entries = [
|
|
192
|
+
e for e in entries if fnmatch.fnmatch(e.name, pattern) or e.is_dir()
|
|
193
|
+
]
|
|
194
|
+
|
|
193
195
|
# Filter dirs only
|
|
194
196
|
if dirs_only:
|
|
195
197
|
entries = [e for e in entries if e.is_dir()]
|
|
196
|
-
|
|
198
|
+
|
|
197
199
|
# Sort entries (dirs first, then alphabetically)
|
|
198
200
|
entries.sort(key=lambda e: (not e.is_dir(), e.name.lower()))
|
|
199
|
-
|
|
201
|
+
|
|
200
202
|
# Process each entry
|
|
201
203
|
for i, entry in enumerate(entries):
|
|
202
204
|
is_last_entry = i == len(entries) - 1
|
|
203
|
-
|
|
205
|
+
|
|
204
206
|
# Skip if not allowed
|
|
205
207
|
if not self.is_path_allowed(str(entry)):
|
|
206
208
|
continue
|
|
207
|
-
|
|
209
|
+
|
|
208
210
|
# Build the tree branch
|
|
209
211
|
if prefix:
|
|
210
212
|
if is_last_entry:
|
|
@@ -216,20 +218,20 @@ tree --pattern "*.py" --show-size"""
|
|
|
216
218
|
else:
|
|
217
219
|
branch = ""
|
|
218
220
|
extension = ""
|
|
219
|
-
|
|
221
|
+
|
|
220
222
|
# Build entry line
|
|
221
223
|
line = branch + entry.name
|
|
222
|
-
|
|
224
|
+
|
|
223
225
|
# Add size if requested
|
|
224
226
|
if show_size and entry.is_file():
|
|
225
227
|
try:
|
|
226
228
|
size = entry.stat().st_size
|
|
227
229
|
line += f" ({self._format_size(size)})"
|
|
228
|
-
except:
|
|
230
|
+
except Exception:
|
|
229
231
|
pass
|
|
230
|
-
|
|
232
|
+
|
|
231
233
|
output.append(line)
|
|
232
|
-
|
|
234
|
+
|
|
233
235
|
# Update stats
|
|
234
236
|
if entry.is_dir():
|
|
235
237
|
stats["dirs"] += 1
|
|
@@ -245,11 +247,11 @@ tree --pattern "*.py" --show-size"""
|
|
|
245
247
|
show_hidden=show_hidden,
|
|
246
248
|
dirs_only=dirs_only,
|
|
247
249
|
show_size=show_size,
|
|
248
|
-
pattern=pattern
|
|
250
|
+
pattern=pattern,
|
|
249
251
|
)
|
|
250
252
|
else:
|
|
251
253
|
stats["files"] += 1
|
|
252
|
-
|
|
254
|
+
|
|
253
255
|
except PermissionError:
|
|
254
256
|
output.append(prefix + "[Permission Denied]")
|
|
255
257
|
except Exception as e:
|
|
@@ -265,4 +267,4 @@ tree --pattern "*.py" --show-size"""
|
|
|
265
267
|
|
|
266
268
|
def register(self, mcp_server) -> None:
|
|
267
269
|
"""Register this tool with the MCP server."""
|
|
268
|
-
pass
|
|
270
|
+
pass
|
|
@@ -3,20 +3,21 @@
|
|
|
3
3
|
Provides familiar Unix command names that map to MCP tools.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import Dict
|
|
6
|
+
from typing import Dict
|
|
7
|
+
|
|
7
8
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def get_unix_aliases() -> Dict[str, str]:
|
|
11
12
|
"""Get mapping of Unix commands to MCP tool names.
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
Returns:
|
|
14
15
|
Dictionary mapping Unix command names to MCP tool names
|
|
15
16
|
"""
|
|
16
17
|
return {
|
|
17
18
|
# File operations
|
|
18
19
|
"ls": "list_directory",
|
|
19
|
-
"cat": "read_file",
|
|
20
|
+
"cat": "read_file",
|
|
20
21
|
"head": "read_file", # With line limit
|
|
21
22
|
"tail": "read_file", # With offset
|
|
22
23
|
"cp": "copy_path",
|
|
@@ -24,7 +25,6 @@ def get_unix_aliases() -> Dict[str, str]:
|
|
|
24
25
|
"rm": "delete_path",
|
|
25
26
|
"mkdir": "create_directory",
|
|
26
27
|
"touch": "write_file", # Create empty file
|
|
27
|
-
|
|
28
28
|
# Search operations
|
|
29
29
|
"grep": "find", # Our unified find tool
|
|
30
30
|
"find": "glob", # For finding files by name
|
|
@@ -32,23 +32,18 @@ def get_unix_aliases() -> Dict[str, str]:
|
|
|
32
32
|
"rg": "find", # Ripgrep alias
|
|
33
33
|
"ag": "find", # Silver searcher alias
|
|
34
34
|
"ack": "find", # Ack alias
|
|
35
|
-
|
|
36
35
|
# Directory operations
|
|
37
36
|
"tree": "tree", # Already named correctly
|
|
38
37
|
"pwd": "get_working_directory",
|
|
39
38
|
"cd": "change_directory",
|
|
40
|
-
|
|
41
39
|
# Git operations (if git tools enabled)
|
|
42
40
|
"git": "git_command",
|
|
43
|
-
|
|
44
41
|
# Process operations
|
|
45
42
|
"ps": "list_processes",
|
|
46
43
|
"kill": "kill_process",
|
|
47
|
-
|
|
48
44
|
# Archive operations
|
|
49
45
|
"tar": "archive",
|
|
50
46
|
"unzip": "extract",
|
|
51
|
-
|
|
52
47
|
# Network operations
|
|
53
48
|
"curl": "http_request",
|
|
54
49
|
"wget": "download_file",
|
|
@@ -57,13 +52,13 @@ def get_unix_aliases() -> Dict[str, str]:
|
|
|
57
52
|
|
|
58
53
|
class UnixAliasRegistry:
|
|
59
54
|
"""Registry for Unix command aliases."""
|
|
60
|
-
|
|
55
|
+
|
|
61
56
|
def __init__(self):
|
|
62
57
|
self.aliases = get_unix_aliases()
|
|
63
|
-
|
|
58
|
+
|
|
64
59
|
def register_aliases(self, mcp_server, tools: Dict[str, BaseTool]) -> None:
|
|
65
60
|
"""Register Unix aliases for tools.
|
|
66
|
-
|
|
61
|
+
|
|
67
62
|
Args:
|
|
68
63
|
mcp_server: The MCP server instance
|
|
69
64
|
tools: Dictionary of tool name to tool instance
|
|
@@ -73,10 +68,10 @@ class UnixAliasRegistry:
|
|
|
73
68
|
tool = tools[tool_name]
|
|
74
69
|
# Register the tool under its alias name
|
|
75
70
|
self._register_alias(mcp_server, alias, tool)
|
|
76
|
-
|
|
71
|
+
|
|
77
72
|
def _register_alias(self, mcp_server, alias: str, tool: BaseTool) -> None:
|
|
78
73
|
"""Register a single alias for a tool.
|
|
79
|
-
|
|
74
|
+
|
|
80
75
|
Args:
|
|
81
76
|
mcp_server: The MCP server instance
|
|
82
77
|
alias: The Unix command alias
|
|
@@ -86,14 +81,14 @@ class UnixAliasRegistry:
|
|
|
86
81
|
# but registers under the alias name
|
|
87
82
|
original_name = tool.name
|
|
88
83
|
original_description = tool.description
|
|
89
|
-
|
|
84
|
+
|
|
90
85
|
# Temporarily change the tool's name for registration
|
|
91
86
|
tool.name = alias
|
|
92
87
|
tool.description = f"{original_description}\n\n(Unix alias for {original_name})"
|
|
93
|
-
|
|
88
|
+
|
|
94
89
|
# Register the tool
|
|
95
90
|
tool.register(mcp_server)
|
|
96
|
-
|
|
91
|
+
|
|
97
92
|
# Restore original name
|
|
98
93
|
tool.name = original_name
|
|
99
|
-
tool.description = original_description
|
|
94
|
+
tool.description = original_description
|