hanzo-mcp 0.8.11__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (166) hide show
  1. hanzo_mcp/__init__.py +1 -3
  2. hanzo_mcp/analytics/posthog_analytics.py +3 -9
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +6 -15
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +1 -3
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +2 -6
  17. hanzo_mcp/tools/__init__.py +26 -27
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +22 -15
  21. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  22. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  23. hanzo_mcp/tools/agent/cli_tools.py +75 -74
  24. hanzo_mcp/tools/agent/code_auth.py +1 -3
  25. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  26. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  27. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  28. hanzo_mcp/tools/agent/network_tool.py +7 -18
  29. hanzo_mcp/tools/agent/prompt.py +1 -5
  30. hanzo_mcp/tools/agent/review_tool.py +10 -25
  31. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  32. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  33. hanzo_mcp/tools/common/batch_tool.py +15 -45
  34. hanzo_mcp/tools/common/config_tool.py +9 -28
  35. hanzo_mcp/tools/common/context.py +1 -3
  36. hanzo_mcp/tools/common/critic_tool.py +1 -3
  37. hanzo_mcp/tools/common/decorators.py +2 -6
  38. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  39. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  40. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  41. hanzo_mcp/tools/common/mode.py +1 -5
  42. hanzo_mcp/tools/common/paginated_base.py +3 -11
  43. hanzo_mcp/tools/common/paginated_response.py +10 -30
  44. hanzo_mcp/tools/common/pagination.py +3 -9
  45. hanzo_mcp/tools/common/path_utils.py +34 -0
  46. hanzo_mcp/tools/common/permissions.py +14 -13
  47. hanzo_mcp/tools/common/personality.py +983 -701
  48. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  49. hanzo_mcp/tools/common/stats.py +6 -18
  50. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  51. hanzo_mcp/tools/common/tool_disable.py +2 -6
  52. hanzo_mcp/tools/common/tool_list.py +2 -6
  53. hanzo_mcp/tools/common/validation.py +1 -3
  54. hanzo_mcp/tools/compiler/__init__.py +8 -0
  55. hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  72. hanzo_mcp/tools/environment/__init__.py +8 -0
  73. hanzo_mcp/tools/environment/environment_detector.py +594 -0
  74. hanzo_mcp/tools/filesystem/__init__.py +28 -26
  75. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  76. hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
  77. hanzo_mcp/tools/filesystem/base.py +20 -12
  78. hanzo_mcp/tools/filesystem/content_replace.py +7 -12
  79. hanzo_mcp/tools/filesystem/diff.py +2 -10
  80. hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
  81. hanzo_mcp/tools/filesystem/edit.py +10 -18
  82. hanzo_mcp/tools/filesystem/find.py +312 -179
  83. hanzo_mcp/tools/filesystem/git_search.py +12 -24
  84. hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
  85. hanzo_mcp/tools/filesystem/read.py +14 -30
  86. hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
  87. hanzo_mcp/tools/filesystem/search.py +1160 -0
  88. hanzo_mcp/tools/filesystem/watch.py +2 -4
  89. hanzo_mcp/tools/filesystem/write.py +7 -10
  90. hanzo_mcp/tools/framework/__init__.py +8 -0
  91. hanzo_mcp/tools/framework/framework_modes.py +714 -0
  92. hanzo_mcp/tools/jupyter/base.py +6 -20
  93. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  94. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  95. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  96. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  97. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  98. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  99. hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
  100. hanzo_mcp/tools/mcp/mcp_add.py +1 -3
  101. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  102. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  103. hanzo_mcp/tools/memory/__init__.py +10 -27
  104. hanzo_mcp/tools/memory/conversation_memory.py +636 -0
  105. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  106. hanzo_mcp/tools/memory/memory_tools.py +6 -18
  107. hanzo_mcp/tools/search/find_tool.py +12 -34
  108. hanzo_mcp/tools/search/unified_search.py +24 -78
  109. hanzo_mcp/tools/shell/__init__.py +16 -4
  110. hanzo_mcp/tools/shell/auto_background.py +2 -6
  111. hanzo_mcp/tools/shell/base.py +1 -5
  112. hanzo_mcp/tools/shell/base_process.py +5 -7
  113. hanzo_mcp/tools/shell/bash_session.py +7 -24
  114. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  115. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  116. hanzo_mcp/tools/shell/command_executor.py +26 -79
  117. hanzo_mcp/tools/shell/logs.py +4 -16
  118. hanzo_mcp/tools/shell/npx.py +2 -8
  119. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  120. hanzo_mcp/tools/shell/pkill.py +4 -12
  121. hanzo_mcp/tools/shell/process_tool.py +2 -8
  122. hanzo_mcp/tools/shell/processes.py +5 -17
  123. hanzo_mcp/tools/shell/run_background.py +1 -3
  124. hanzo_mcp/tools/shell/run_command.py +1 -3
  125. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  126. hanzo_mcp/tools/shell/run_tool.py +56 -0
  127. hanzo_mcp/tools/shell/session_manager.py +2 -6
  128. hanzo_mcp/tools/shell/session_storage.py +2 -6
  129. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  130. hanzo_mcp/tools/shell/uvx.py +4 -14
  131. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  132. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  133. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  134. hanzo_mcp/tools/todo/todo.py +1 -3
  135. hanzo_mcp/tools/vector/__init__.py +97 -50
  136. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  137. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  138. hanzo_mcp/tools/vector/index_tool.py +3 -9
  139. hanzo_mcp/tools/vector/infinity_store.py +7 -27
  140. hanzo_mcp/tools/vector/mock_infinity.py +1 -3
  141. hanzo_mcp/tools/vector/node_tool.py +538 -0
  142. hanzo_mcp/tools/vector/project_manager.py +4 -12
  143. hanzo_mcp/tools/vector/unified_vector.py +384 -0
  144. hanzo_mcp/tools/vector/vector.py +2 -6
  145. hanzo_mcp/tools/vector/vector_index.py +8 -8
  146. hanzo_mcp/tools/vector/vector_search.py +7 -21
  147. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
  148. hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
  149. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
  150. hanzo_mcp/tools/agent/swarm_tool.py +0 -718
  151. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
  152. hanzo_mcp/tools/filesystem/batch_search.py +0 -900
  153. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
  154. hanzo_mcp/tools/filesystem/find_files.py +0 -369
  155. hanzo_mcp/tools/filesystem/grep.py +0 -467
  156. hanzo_mcp/tools/filesystem/search_tool.py +0 -767
  157. hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
  158. hanzo_mcp/tools/filesystem/tree.py +0 -270
  159. hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
  160. hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
  161. hanzo_mcp/tools/todo/todo_read.py +0 -143
  162. hanzo_mcp/tools/todo/todo_write.py +0 -374
  163. hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
  164. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
  165. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
  166. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
@@ -1,515 +0,0 @@
1
- """Unified symbols tool implementation.
2
-
3
- This module provides the SymbolsTool for searching, indexing, and querying code symbols
4
- using tree-sitter AST parsing. It can find function definitions, class declarations,
5
- and other code structures with full context.
6
- """
7
-
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
- )
20
- from pathlib import Path
21
-
22
- from pydantic import Field
23
- from grep_ast.grep_ast import TreeContext
24
- from mcp.server.fastmcp import Context as MCPContext
25
-
26
- from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
27
-
28
- # Parameter types
29
- Action = Annotated[
30
- str,
31
- Field(
32
- description="Action: search (default), ast, index, query, list",
33
- default="search",
34
- ),
35
- ]
36
-
37
- Pattern = Annotated[
38
- Optional[str],
39
- Field(
40
- description="Pattern to search for in code",
41
- default=None,
42
- ),
43
- ]
44
-
45
- SearchPath = Annotated[
46
- str,
47
- Field(
48
- description="Path to search/index (file or directory)",
49
- default=".",
50
- ),
51
- ]
52
-
53
- SymbolType = Annotated[
54
- Optional[str],
55
- Field(
56
- description="Symbol type: function, class, method, variable",
57
- default=None,
58
- ),
59
- ]
60
-
61
- IgnoreCase = Annotated[
62
- bool,
63
- Field(
64
- description="Ignore case when matching",
65
- default=False,
66
- ),
67
- ]
68
-
69
- ShowContext = Annotated[
70
- bool,
71
- Field(
72
- description="Show AST context around matches",
73
- default=True,
74
- ),
75
- ]
76
-
77
- Limit = Annotated[
78
- int,
79
- Field(
80
- description="Maximum results to return",
81
- default=50,
82
- ),
83
- ]
84
-
85
-
86
- class SymbolsParams(TypedDict, total=False):
87
- """Parameters for symbols tool."""
88
-
89
- action: str
90
- pattern: Optional[str]
91
- path: str
92
- symbol_type: Optional[str]
93
- ignore_case: bool
94
- show_context: bool
95
- limit: int
96
-
97
-
98
- @final
99
- class SymbolsTool(FilesystemBaseTool):
100
- """Tool for code symbol operations using tree-sitter."""
101
-
102
- def __init__(self, permission_manager):
103
- """Initialize the symbols tool."""
104
- super().__init__(permission_manager)
105
- self._symbol_cache = {} # Cache for indexed symbols
106
-
107
- @property
108
- @override
109
- def name(self) -> str:
110
- """Get the tool name."""
111
- return "symbols"
112
-
113
- @property
114
- @override
115
- def description(self) -> str:
116
- """Get the tool description."""
117
- return """Code symbols search with tree-sitter AST. Actions: search (default), ast, index, query, list.
118
-
119
- Usage:
120
- symbols "function_name"
121
- symbols --action ast --pattern "TODO" --path ./src
122
- symbols --action query --symbol-type function --path ./src
123
- symbols --action index --path ./project
124
- symbols --action list --path ./src --symbol-type class
125
-
126
- Finds code structures (functions, classes, methods) with full context."""
127
-
128
- @override
129
- async def call(
130
- self,
131
- ctx: MCPContext,
132
- **params: Unpack[SymbolsParams],
133
- ) -> str:
134
- """Execute symbols operation."""
135
- tool_ctx = self.create_tool_context(ctx)
136
- self.set_tool_context_info(tool_ctx)
137
-
138
- # Extract action
139
- action = params.get("action", "search")
140
-
141
- # Route to appropriate handler
142
- if action == "search":
143
- return await self._handle_search(params, tool_ctx)
144
- elif (
145
- action == "ast" or action == "grep_ast"
146
- ): # Support both for backward compatibility
147
- return await self._handle_ast(params, tool_ctx)
148
- elif action == "index":
149
- return await self._handle_index(params, tool_ctx)
150
- elif action == "query":
151
- return await self._handle_query(params, tool_ctx)
152
- elif action == "list":
153
- return await self._handle_list(params, tool_ctx)
154
- else:
155
- return f"Error: Unknown action '{action}'. Valid actions: search, ast, index, query, list"
156
-
157
- async def _handle_search(self, params: Dict[str, Any], tool_ctx) -> str:
158
- """Search for pattern in code with AST context."""
159
- pattern = params.get("pattern")
160
- if not pattern:
161
- return "Error: pattern required for search action"
162
-
163
- path = params.get("path", ".")
164
- ignore_case = params.get("ignore_case", False)
165
- show_context = params.get("show_context", True)
166
- limit = params.get("limit", 50)
167
-
168
- # Validate path
169
- path_validation = self.validate_path(path)
170
- if not path_validation.is_valid:
171
- await tool_ctx.error(f"Invalid path: {path_validation.error_message}")
172
- return f"Error: Invalid path: {path_validation.error_message}"
173
-
174
- # Check permissions
175
- is_allowed, error_message = await self.check_path_allowed(path, tool_ctx)
176
- if not is_allowed:
177
- return error_message
178
-
179
- # Check existence
180
- is_exists, error_message = await self.check_path_exists(path, tool_ctx)
181
- if not is_exists:
182
- return error_message
183
-
184
- await tool_ctx.info(f"Searching for '{pattern}' in {path}")
185
-
186
- # Get files to process
187
- files_to_process = self._get_source_files(path)
188
- if not files_to_process:
189
- return f"No source code files found in {path}"
190
-
191
- # Process files
192
- results = []
193
- match_count = 0
194
-
195
- for file_path in files_to_process:
196
- if match_count >= limit:
197
- break
198
-
199
- try:
200
- with open(file_path, "r", encoding="utf-8") as f:
201
- code = f.read()
202
-
203
- tc = TreeContext(
204
- file_path,
205
- code,
206
- color=False,
207
- verbose=False,
208
- line_number=True,
209
- )
210
-
211
- # Find matches
212
- loi = tc.grep(pattern, ignore_case)
213
-
214
- if loi:
215
- if show_context:
216
- tc.add_lines_of_interest(loi)
217
- tc.add_context()
218
- output = tc.format()
219
- else:
220
- # Just show matching lines
221
- output = "\n".join(
222
- [f"{line}: {code.splitlines()[line - 1]}" for line in loi]
223
- )
224
-
225
- results.append(f"\n{file_path}:\n{output}\n")
226
- match_count += len(loi)
227
-
228
- except Exception as e:
229
- await tool_ctx.warning(f"Could not parse {file_path}: {str(e)}")
230
-
231
- if not results:
232
- return f"No matches found for '{pattern}' in {path}"
233
-
234
- output = [f"=== Symbol Search Results for '{pattern}' ==="]
235
- output.append(f"Found {match_count} matches in {len(results)} files\n")
236
- output.extend(results)
237
-
238
- if match_count >= limit:
239
- output.append(f"\n(Results limited to {limit} matches)")
240
-
241
- return "\n".join(output)
242
-
243
- async def _handle_ast(self, params: Dict[str, Any], tool_ctx) -> str:
244
- """AST-aware grep - shows code structure context around matches."""
245
- pattern = params.get("pattern")
246
- if not pattern:
247
- return "Error: pattern required for ast action"
248
-
249
- path = params.get("path", ".")
250
- ignore_case = params.get("ignore_case", False)
251
- show_context = params.get("show_context", True)
252
- limit = params.get("limit", 50)
253
-
254
- # Validate path
255
- path_validation = self.validate_path(path)
256
- if not path_validation.is_valid:
257
- await tool_ctx.error(f"Invalid path: {path_validation.error_message}")
258
- return f"Error: Invalid path: {path_validation.error_message}"
259
-
260
- # Check permissions
261
- is_allowed, error_message = await self.check_path_allowed(path, tool_ctx)
262
- if not is_allowed:
263
- return error_message
264
-
265
- # Check existence
266
- is_exists, error_message = await self.check_path_exists(path, tool_ctx)
267
- if not is_exists:
268
- return error_message
269
-
270
- await tool_ctx.info(f"Running AST-aware grep for '{pattern}' in {path}")
271
-
272
- # Get files to process
273
- files_to_process = self._get_source_files(path)
274
- if not files_to_process:
275
- return f"No source code files found in {path}"
276
-
277
- # Process files
278
- results = []
279
- match_count = 0
280
-
281
- for file_path in files_to_process:
282
- if match_count >= limit:
283
- break
284
-
285
- try:
286
- with open(file_path, "r", encoding="utf-8") as f:
287
- code = f.read()
288
-
289
- # Create TreeContext for AST parsing
290
- tc = TreeContext(
291
- file_path,
292
- code,
293
- color=False,
294
- verbose=False,
295
- line_number=True,
296
- )
297
-
298
- # Find matches with case sensitivity option
299
- if ignore_case:
300
- loi = tc.grep(pattern, ignore_case=True)
301
- else:
302
- loi = tc.grep(pattern, ignore_case=False)
303
-
304
- if loi:
305
- # Always show AST context for grep_ast
306
- tc.add_lines_of_interest(loi)
307
- tc.add_context()
308
-
309
- # Get the formatted output with structure
310
- output = tc.format()
311
-
312
- # Add section separator and file info
313
- results.append(f"\n{'=' * 60}")
314
- results.append(f"File: {file_path}")
315
- results.append(f"Matches: {len(loi)}")
316
- results.append(f"{'=' * 60}\n")
317
- results.append(output)
318
-
319
- match_count += len(loi)
320
-
321
- except Exception as e:
322
- await tool_ctx.warning(f"Could not parse {file_path}: {str(e)}")
323
-
324
- if not results:
325
- return f"No matches found for '{pattern}' in {path}"
326
-
327
- output = [f"=== AST-aware Grep Results for '{pattern}' ==="]
328
- output.append(
329
- f"Total matches: {match_count} in {len([r for r in results if '===' in str(r)]) // 4} files\n"
330
- )
331
- output.extend(results)
332
-
333
- if match_count >= limit:
334
- output.append(f"\n(Results limited to {limit} matches)")
335
-
336
- return "\n".join(output)
337
-
338
- async def _handle_index(self, params: Dict[str, Any], tool_ctx) -> str:
339
- """Index symbols in a codebase."""
340
- path = params.get("path", ".")
341
-
342
- # Validate path
343
- is_allowed, error_message = await self.check_path_allowed(path, tool_ctx)
344
- if not is_allowed:
345
- return error_message
346
-
347
- await tool_ctx.info(f"Indexing symbols in {path}...")
348
-
349
- files_to_process = self._get_source_files(path)
350
- if not files_to_process:
351
- return f"No source code files found in {path}"
352
-
353
- # Clear cache for this path
354
- self._symbol_cache[path] = {
355
- "functions": [],
356
- "classes": [],
357
- "methods": [],
358
- "variables": [],
359
- }
360
-
361
- indexed_count = 0
362
- symbol_count = 0
363
-
364
- for file_path in files_to_process:
365
- try:
366
- with open(file_path, "r", encoding="utf-8") as f:
367
- code = f.read()
368
-
369
- tc = TreeContext(file_path, code, color=False, verbose=False)
370
-
371
- # Extract symbols (simplified - would need proper tree-sitter queries)
372
- # This is a placeholder for actual symbol extraction
373
- symbols = self._extract_symbols(tc, file_path)
374
-
375
- for symbol_type, syms in symbols.items():
376
- self._symbol_cache[path][symbol_type].extend(syms)
377
- symbol_count += len(syms)
378
-
379
- indexed_count += 1
380
-
381
- except Exception as e:
382
- await tool_ctx.warning(f"Could not index {file_path}: {str(e)}")
383
-
384
- output = [f"=== Symbol Indexing Complete ==="]
385
- output.append(f"Indexed {indexed_count} files")
386
- output.append(f"Found {symbol_count} total symbols:")
387
-
388
- for symbol_type, symbols in self._symbol_cache[path].items():
389
- if symbols:
390
- output.append(f" {symbol_type}: {len(symbols)}")
391
-
392
- return "\n".join(output)
393
-
394
- async def _handle_query(self, params: Dict[str, Any], tool_ctx) -> str:
395
- """Query indexed symbols."""
396
- path = params.get("path", ".")
397
- symbol_type = params.get("symbol_type")
398
- pattern = params.get("pattern")
399
- limit = params.get("limit", 50)
400
-
401
- # Check if we have indexed this path
402
- if path not in self._symbol_cache:
403
- return f"No symbols indexed for {path}. Run 'symbols --action index --path {path}' first."
404
-
405
- symbols = self._symbol_cache[path]
406
- results = []
407
-
408
- # Filter by type if specified
409
- if symbol_type:
410
- if symbol_type in symbols:
411
- candidates = symbols[symbol_type]
412
- else:
413
- return f"Unknown symbol type: {symbol_type}. Valid types: {', '.join(symbols.keys())}"
414
- else:
415
- # Combine all symbol types
416
- candidates = []
417
- for syms in symbols.values():
418
- candidates.extend(syms)
419
-
420
- # Filter by pattern if specified
421
- if pattern:
422
- filtered = []
423
- for sym in candidates:
424
- if pattern.lower() in sym["name"].lower():
425
- filtered.append(sym)
426
- candidates = filtered
427
-
428
- # Limit results
429
- candidates = candidates[:limit]
430
-
431
- if not candidates:
432
- return "No symbols found matching criteria"
433
-
434
- output = [f"=== Symbol Query Results ==="]
435
- output.append(f"Found {len(candidates)} symbols\n")
436
-
437
- for sym in candidates:
438
- output.append(f"{sym['type']}: {sym['name']}")
439
- output.append(f" File: {sym['file']}:{sym['line']}")
440
- if sym.get("signature"):
441
- output.append(f" Signature: {sym['signature']}")
442
- output.append("")
443
-
444
- return "\n".join(output)
445
-
446
- async def _handle_list(self, params: Dict[str, Any], tool_ctx) -> str:
447
- """List all symbols in a path."""
448
- # Similar to query but shows all symbols
449
- params["pattern"] = None
450
- return await self._handle_query(params, tool_ctx)
451
-
452
- def _get_source_files(self, path: str) -> List[str]:
453
- """Get all source code files in a path."""
454
- path_obj = Path(path)
455
- files_to_process = []
456
-
457
- # Common source file extensions
458
- extensions = {
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",
484
- }
485
-
486
- if path_obj.is_file():
487
- if path_obj.suffix in extensions:
488
- files_to_process.append(str(path_obj))
489
- elif path_obj.is_dir():
490
- for root, _, files in os.walk(path_obj):
491
- for file in files:
492
- file_path = Path(root) / file
493
- if file_path.suffix in extensions and self.is_path_allowed(
494
- str(file_path)
495
- ):
496
- files_to_process.append(str(file_path))
497
-
498
- return files_to_process
499
-
500
- def _extract_symbols(
501
- self, tc: TreeContext, file_path: str
502
- ) -> Dict[str, List[Dict[str, Any]]]:
503
- """Extract symbols from a TreeContext (placeholder implementation)."""
504
- # This would need proper tree-sitter queries to extract symbols
505
- # For now, return empty structure
506
- return {
507
- "functions": [],
508
- "classes": [],
509
- "methods": [],
510
- "variables": [],
511
- }
512
-
513
- def register(self, mcp_server) -> None:
514
- """Register this tool with the MCP server."""
515
- pass