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.

Files changed (178) hide show
  1. hanzo_mcp/__init__.py +6 -0
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
@@ -1,16 +1,24 @@
1
1
  """Unified vector store tool."""
2
2
 
3
- from typing import Annotated, TypedDict, Unpack, final, override, Optional, List, Dict, Any
3
+ from typing import (
4
+ Any,
5
+ Dict,
6
+ Unpack,
7
+ Optional,
8
+ Annotated,
9
+ TypedDict,
10
+ final,
11
+ override,
12
+ )
4
13
  from pathlib import Path
5
14
 
6
- from mcp.server.fastmcp import Context as MCPContext
7
15
  from pydantic import Field
16
+ from mcp.server.fastmcp import Context as MCPContext
8
17
 
9
18
  from hanzo_mcp.tools.common.base import BaseTool
10
19
  from hanzo_mcp.tools.common.permissions import PermissionManager
11
20
  from hanzo_mcp.tools.vector.project_manager import ProjectVectorManager
12
21
 
13
-
14
22
  # Parameter types
15
23
  Action = Annotated[
16
24
  str,
@@ -79,6 +87,7 @@ ForceReindex = Annotated[
79
87
 
80
88
  class VectorParams(TypedDict, total=False):
81
89
  """Parameters for vector tool."""
90
+
82
91
  action: str
83
92
  query: Optional[str]
84
93
  path: Optional[str]
@@ -92,8 +101,12 @@ class VectorParams(TypedDict, total=False):
92
101
  @final
93
102
  class VectorTool(BaseTool):
94
103
  """Unified vector store tool for semantic search."""
95
-
96
- def __init__(self, permission_manager: PermissionManager, project_manager: ProjectVectorManager):
104
+
105
+ def __init__(
106
+ self,
107
+ permission_manager: PermissionManager,
108
+ project_manager: ProjectVectorManager,
109
+ ):
97
110
  """Initialize the vector tool."""
98
111
  super().__init__(permission_manager)
99
112
  self.project_manager = project_manager
@@ -128,7 +141,7 @@ vector --action clear --path ./old_code
128
141
 
129
142
  # Extract action
130
143
  action = params.get("action", "search")
131
-
144
+
132
145
  # Route to appropriate handler
133
146
  if action == "search":
134
147
  return await self._handle_search(params, tool_ctx)
@@ -146,52 +159,52 @@ vector --action clear --path ./old_code
146
159
  query = params.get("query")
147
160
  if not query:
148
161
  return "Error: query is required for search action"
149
-
162
+
150
163
  path = params.get("path", ".")
151
164
  limit = params.get("limit", 10)
152
-
165
+
153
166
  # Validate path
154
167
  allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
155
168
  if not allowed:
156
169
  return error_msg
157
-
170
+
158
171
  try:
159
172
  # Determine search scope
160
173
  project = self.project_manager.get_project_for_path(path)
161
174
  if not project:
162
175
  return "Error: No indexed project found for this path. Run 'vector --action index' first."
163
-
176
+
164
177
  # Search
165
178
  await tool_ctx.info(f"Searching for: {query}")
166
179
  results = project.search(query, k=limit)
167
-
180
+
168
181
  if not results:
169
182
  return f"No results found for: {query}"
170
-
183
+
171
184
  # Format results
172
185
  output = [f"=== Vector Search Results for '{query}' ==="]
173
186
  output.append(f"Found {len(results)} matches\n")
174
-
187
+
175
188
  for i, result in enumerate(results, 1):
176
189
  score = result.get("score", 0)
177
190
  file_path = result.get("file_path", "unknown")
178
191
  content = result.get("content", "")
179
192
  chunk_type = result.get("metadata", {}).get("type", "content")
180
-
193
+
181
194
  output.append(f"Result {i} - Score: {score:.1%}")
182
195
  output.append(f"File: {file_path}")
183
196
  if chunk_type != "content":
184
197
  output.append(f"Type: {chunk_type}")
185
198
  output.append("-" * 60)
186
-
199
+
187
200
  # Truncate content if too long
188
201
  if len(content) > 300:
189
202
  content = content[:300] + "..."
190
203
  output.append(content)
191
204
  output.append("")
192
-
205
+
193
206
  return "\n".join(output)
194
-
207
+
195
208
  except Exception as e:
196
209
  await tool_ctx.error(f"Search failed: {str(e)}")
197
210
  return f"Error during search: {str(e)}"
@@ -203,32 +216,32 @@ vector --action clear --path ./old_code
203
216
  exclude = params.get("exclude")
204
217
  include_git = params.get("include_git", True)
205
218
  force = params.get("force_reindex", False)
206
-
219
+
207
220
  # Validate path
208
221
  allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
209
222
  if not allowed:
210
223
  return error_msg
211
-
224
+
212
225
  try:
213
226
  await tool_ctx.info(f"Indexing {path}...")
214
-
227
+
215
228
  # Get or create project
216
229
  project = self.project_manager.get_or_create_project(path)
217
-
230
+
218
231
  # Index files
219
232
  stats = await project.index_directory(
220
233
  path,
221
234
  include_pattern=include,
222
235
  exclude_pattern=exclude,
223
- force_reindex=force
236
+ force_reindex=force,
224
237
  )
225
-
238
+
226
239
  # Index git history if requested
227
240
  if include_git and Path(path).joinpath(".git").exists():
228
241
  await tool_ctx.info("Indexing git history...")
229
242
  git_stats = await project.index_git_history(path)
230
243
  stats["git_commits"] = git_stats.get("commits_indexed", 0)
231
-
244
+
232
245
  # Format output
233
246
  output = [f"=== Indexing Complete ==="]
234
247
  output.append(f"Path: {path}")
@@ -236,10 +249,12 @@ vector --action clear --path ./old_code
236
249
  output.append(f"Chunks created: {stats.get('chunks_created', 0)}")
237
250
  if stats.get("git_commits"):
238
251
  output.append(f"Git commits indexed: {stats['git_commits']}")
239
- output.append(f"Total documents: {project.get_stats().get('total_documents', 0)}")
240
-
252
+ output.append(
253
+ f"Total documents: {project.get_stats().get('total_documents', 0)}"
254
+ )
255
+
241
256
  return "\n".join(output)
242
-
257
+
243
258
  except Exception as e:
244
259
  await tool_ctx.error(f"Indexing failed: {str(e)}")
245
260
  return f"Error during indexing: {str(e)}"
@@ -247,31 +262,33 @@ vector --action clear --path ./old_code
247
262
  async def _handle_stats(self, params: Dict[str, Any], tool_ctx) -> str:
248
263
  """Get vector store statistics."""
249
264
  path = params.get("path")
250
-
265
+
251
266
  try:
252
267
  if path:
253
268
  # Stats for specific project
254
269
  project = self.project_manager.get_project_for_path(path)
255
270
  if not project:
256
271
  return f"No indexed project found for path: {path}"
257
-
272
+
258
273
  stats = project.get_stats()
259
274
  output = [f"=== Vector Store Stats for {project.name} ==="]
260
275
  else:
261
276
  # Global stats
262
277
  stats = self.project_manager.get_global_stats()
263
278
  output = ["=== Global Vector Store Stats ==="]
264
-
279
+
265
280
  output.append(f"Total documents: {stats.get('total_documents', 0)}")
266
281
  output.append(f"Total size: {stats.get('total_size_mb', 0):.1f} MB")
267
-
282
+
268
283
  if stats.get("projects"):
269
284
  output.append(f"\nProjects indexed: {len(stats['projects'])}")
270
285
  for proj in stats["projects"]:
271
- output.append(f" - {proj['name']}: {proj['documents']} docs, {proj['size_mb']:.1f} MB")
272
-
286
+ output.append(
287
+ f" - {proj['name']}: {proj['documents']} docs, {proj['size_mb']:.1f} MB"
288
+ )
289
+
273
290
  return "\n".join(output)
274
-
291
+
275
292
  except Exception as e:
276
293
  await tool_ctx.error(f"Failed to get stats: {str(e)}")
277
294
  return f"Error getting stats: {str(e)}"
@@ -279,33 +296,33 @@ vector --action clear --path ./old_code
279
296
  async def _handle_clear(self, params: Dict[str, Any], tool_ctx) -> str:
280
297
  """Clear vector store."""
281
298
  path = params.get("path")
282
-
299
+
283
300
  if not path:
284
301
  return "Error: path is required for clear action"
285
-
302
+
286
303
  # Validate path
287
304
  allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
288
305
  if not allowed:
289
306
  return error_msg
290
-
307
+
291
308
  try:
292
309
  project = self.project_manager.get_project_for_path(path)
293
310
  if not project:
294
311
  return f"No indexed project found for path: {path}"
295
-
312
+
296
313
  # Get stats before clearing
297
314
  stats = project.get_stats()
298
315
  doc_count = stats.get("total_documents", 0)
299
-
316
+
300
317
  # Clear
301
318
  project.clear()
302
-
319
+
303
320
  return f"Cleared {doc_count} documents from vector store for {project.name}"
304
-
321
+
305
322
  except Exception as e:
306
323
  await tool_ctx.error(f"Failed to clear: {str(e)}")
307
324
  return f"Error clearing vector store: {str(e)}"
308
325
 
309
326
  def register(self, mcp_server) -> None:
310
327
  """Register this tool with the MCP server."""
311
- pass
328
+ pass
@@ -1,22 +1,19 @@
1
1
  """Vector indexing tool for adding documents to vector database."""
2
2
 
3
- from typing import Dict, List, Optional, TypedDict, Unpack, final
3
+ from typing import Dict, Unpack, Optional, TypedDict, final
4
4
  from pathlib import Path
5
5
 
6
6
  from mcp.server.fastmcp import Context as MCPContext
7
- from pydantic import Field
8
7
 
9
8
  from hanzo_mcp.tools.common.base import BaseTool
10
9
  from hanzo_mcp.tools.common.permissions import PermissionManager
11
- from hanzo_mcp.tools.common.validation import validate_path_parameter
12
10
 
13
- from .infinity_store import InfinityVectorStore
14
11
  from .project_manager import ProjectVectorManager
15
12
 
16
13
 
17
14
  class VectorIndexParams(TypedDict, total=False):
18
15
  """Parameters for vector indexing operations."""
19
-
16
+
20
17
  file_path: str
21
18
  content: Optional[str]
22
19
  chunk_size: Optional[int]
@@ -27,22 +24,26 @@ class VectorIndexParams(TypedDict, total=False):
27
24
  @final
28
25
  class VectorIndexTool(BaseTool):
29
26
  """Tool for indexing documents in the vector database."""
30
-
31
- def __init__(self, permission_manager: PermissionManager, project_manager: ProjectVectorManager):
27
+
28
+ def __init__(
29
+ self,
30
+ permission_manager: PermissionManager,
31
+ project_manager: ProjectVectorManager,
32
+ ):
32
33
  """Initialize the vector index tool.
33
-
34
+
34
35
  Args:
35
36
  permission_manager: Permission manager for access control
36
37
  project_manager: Project-aware vector store manager
37
38
  """
38
39
  self.permission_manager = permission_manager
39
40
  self.project_manager = project_manager
40
-
41
+
41
42
  @property
42
43
  def name(self) -> str:
43
44
  """Get the tool name."""
44
45
  return "vector_index"
45
-
46
+
46
47
  @property
47
48
  def description(self) -> str:
48
49
  """Get the tool description."""
@@ -54,18 +55,18 @@ database. Files are chunked for optimal search performance.
54
55
 
55
56
  Projects are detected by finding LLM.md files, with databases stored in .hanzo/db
56
57
  directories alongside them. Use this to build searchable knowledge bases per project."""
57
-
58
+
58
59
  async def call(
59
60
  self,
60
61
  ctx: MCPContext,
61
62
  **params: Unpack[VectorIndexParams],
62
63
  ) -> str:
63
64
  """Index content or files in the vector database.
64
-
65
+
65
66
  Args:
66
67
  ctx: MCP context
67
68
  **params: Tool parameters
68
-
69
+
69
70
  Returns:
70
71
  Indexing result message
71
72
  """
@@ -74,34 +75,36 @@ directories alongside them. Use this to build searchable knowledge bases per pro
74
75
  chunk_size = params.get("chunk_size", 1000)
75
76
  chunk_overlap = params.get("chunk_overlap", 200)
76
77
  metadata = params.get("metadata", {})
77
-
78
+
78
79
  if not file_path and not content:
79
80
  return "Error: Either file_path or content must be provided"
80
-
81
+
81
82
  try:
82
83
  if file_path:
83
84
  # Validate file access
84
85
  # Use permission manager's existing validation
85
86
  if not self.permission_manager.is_path_allowed(file_path):
86
87
  return f"Error: Access denied to path {file_path}"
87
-
88
+
88
89
  if not Path(file_path).exists():
89
90
  return f"Error: File does not exist: {file_path}"
90
-
91
+
91
92
  # Index file using project-aware manager
92
- doc_ids, project_info = self.project_manager.add_file_to_appropriate_store(
93
- file_path=file_path,
94
- chunk_size=chunk_size,
95
- chunk_overlap=chunk_overlap,
96
- metadata=metadata,
93
+ doc_ids, project_info = (
94
+ self.project_manager.add_file_to_appropriate_store(
95
+ file_path=file_path,
96
+ chunk_size=chunk_size,
97
+ chunk_overlap=chunk_overlap,
98
+ metadata=metadata,
99
+ )
97
100
  )
98
-
101
+
99
102
  file_name = Path(file_path).name
100
103
  if project_info:
101
104
  return f"Successfully indexed {file_name} with {len(doc_ids)} chunks in project '{project_info.name}'"
102
105
  else:
103
106
  return f"Successfully indexed {file_name} with {len(doc_ids)} chunks in global database"
104
-
107
+
105
108
  else:
106
109
  # Index content directly in global store (no project context)
107
110
  global_store = self.project_manager._get_global_store()
@@ -109,8 +112,8 @@ directories alongside them. Use this to build searchable knowledge bases per pro
109
112
  content=content,
110
113
  metadata=metadata,
111
114
  )
112
-
115
+
113
116
  return f"Successfully indexed content as document {doc_id} in global database"
114
-
117
+
115
118
  except Exception as e:
116
- return f"Error indexing content: {str(e)}"
119
+ return f"Error indexing content: {str(e)}"
@@ -1,22 +1,19 @@
1
1
  """Vector search tool for semantic document retrieval."""
2
2
 
3
- from typing import Dict, List, Optional, TypedDict, Unpack, final
4
3
  import json
5
- import asyncio
4
+ from typing import List, Unpack, Optional, TypedDict, final
6
5
 
7
6
  from mcp.server.fastmcp import Context as MCPContext
8
- from pydantic import Field
9
7
 
10
8
  from hanzo_mcp.tools.common.base import BaseTool
11
9
  from hanzo_mcp.tools.common.permissions import PermissionManager
12
10
 
13
- from .infinity_store import InfinityVectorStore
14
11
  from .project_manager import ProjectVectorManager
15
12
 
16
13
 
17
14
  class VectorSearchParams(TypedDict, total=False):
18
15
  """Parameters for vector search operations."""
19
-
16
+
20
17
  query: str
21
18
  limit: Optional[int]
22
19
  score_threshold: Optional[float]
@@ -29,22 +26,26 @@ class VectorSearchParams(TypedDict, total=False):
29
26
  @final
30
27
  class VectorSearchTool(BaseTool):
31
28
  """Tool for semantic search in the vector database."""
32
-
33
- def __init__(self, permission_manager: PermissionManager, project_manager: ProjectVectorManager):
29
+
30
+ def __init__(
31
+ self,
32
+ permission_manager: PermissionManager,
33
+ project_manager: ProjectVectorManager,
34
+ ):
34
35
  """Initialize the vector search tool.
35
-
36
+
36
37
  Args:
37
38
  permission_manager: Permission manager for access control
38
39
  project_manager: Project-aware vector store manager
39
40
  """
40
41
  self.permission_manager = permission_manager
41
42
  self.project_manager = project_manager
42
-
43
+
43
44
  @property
44
45
  def name(self) -> str:
45
46
  """Get the tool name."""
46
47
  return "vector_search"
47
-
48
+
48
49
  @property
49
50
  def description(self) -> str:
50
51
  """Get the tool description."""
@@ -60,32 +61,32 @@ Features:
60
61
  - Automatically detects projects via LLM.md files
61
62
 
62
63
  Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similarity."""
63
-
64
+
64
65
  async def call(
65
66
  self,
66
67
  ctx: MCPContext,
67
68
  **params: Unpack[VectorSearchParams],
68
69
  ) -> str:
69
70
  """Search for similar documents in the vector database.
70
-
71
+
71
72
  Args:
72
73
  ctx: MCP context
73
74
  **params: Tool parameters
74
-
75
+
75
76
  Returns:
76
77
  Search results formatted as text
77
78
  """
78
79
  query = params.get("query")
79
80
  if not query:
80
81
  return "Error: query parameter is required"
81
-
82
+
82
83
  limit = params.get("limit", 10)
83
84
  score_threshold = params.get("score_threshold", 0.0)
84
85
  include_content = params.get("include_content", True)
85
86
  file_filter = params.get("file_filter")
86
87
  project_filter = params.get("project_filter")
87
88
  search_scope = params.get("search_scope", "all")
88
-
89
+
89
90
  try:
90
91
  # Determine search strategy based on scope
91
92
  if search_scope == "all":
@@ -97,7 +98,7 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
97
98
  include_global=True,
98
99
  project_filter=project_filter,
99
100
  )
100
-
101
+
101
102
  # Combine and sort all results
102
103
  all_results = []
103
104
  for project_name, results in project_results.items():
@@ -106,11 +107,11 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
106
107
  result.document.metadata = result.document.metadata or {}
107
108
  result.document.metadata["search_project"] = project_name
108
109
  all_results.append(result)
109
-
110
+
110
111
  # Sort by score and limit
111
112
  all_results.sort(key=lambda x: x.score, reverse=True)
112
113
  results = all_results[:limit]
113
-
114
+
114
115
  elif search_scope == "global":
115
116
  # Search only global store
116
117
  global_store = self.project_manager._get_global_store()
@@ -122,19 +123,21 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
122
123
  for result in results:
123
124
  result.document.metadata = result.document.metadata or {}
124
125
  result.document.metadata["search_project"] = "global"
125
-
126
+
126
127
  else:
127
128
  # Search specific project or current context
128
129
  if search_scope != "current":
129
130
  # Search specific project by name
130
131
  project_info = None
131
- for proj_key, proj_info in self.project_manager.projects.items():
132
+ for _proj_key, proj_info in self.project_manager.projects.items():
132
133
  if proj_info.name == search_scope:
133
134
  project_info = proj_info
134
135
  break
135
-
136
+
136
137
  if project_info:
137
- vector_store = self.project_manager.get_vector_store(project_info)
138
+ vector_store = self.project_manager.get_vector_store(
139
+ project_info
140
+ )
138
141
  results = vector_store.search(
139
142
  query=query,
140
143
  limit=limit,
@@ -142,17 +145,24 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
142
145
  )
143
146
  for result in results:
144
147
  result.document.metadata = result.document.metadata or {}
145
- result.document.metadata["search_project"] = project_info.name
148
+ result.document.metadata["search_project"] = (
149
+ project_info.name
150
+ )
146
151
  else:
147
152
  return f"Project '{search_scope}' not found"
148
153
  else:
149
154
  # For "current", try to detect from working directory
150
155
  import os
156
+
151
157
  current_dir = os.getcwd()
152
- project_info = self.project_manager.get_project_for_path(current_dir)
153
-
158
+ project_info = self.project_manager.get_project_for_path(
159
+ current_dir
160
+ )
161
+
154
162
  if project_info:
155
- vector_store = self.project_manager.get_vector_store(project_info)
163
+ vector_store = self.project_manager.get_vector_store(
164
+ project_info
165
+ )
156
166
  results = vector_store.search(
157
167
  query=query,
158
168
  limit=limit,
@@ -160,7 +170,9 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
160
170
  )
161
171
  for result in results:
162
172
  result.document.metadata = result.document.metadata or {}
163
- result.document.metadata["search_project"] = project_info.name
173
+ result.document.metadata["search_project"] = (
174
+ project_info.name
175
+ )
164
176
  else:
165
177
  # Fall back to global store
166
178
  global_store = self.project_manager._get_global_store()
@@ -172,21 +184,23 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
172
184
  for result in results:
173
185
  result.document.metadata = result.document.metadata or {}
174
186
  result.document.metadata["search_project"] = "global"
175
-
187
+
176
188
  if not results:
177
189
  return f"No results found for query: '{query}'"
178
-
190
+
179
191
  # Filter by file if requested
180
192
  if file_filter:
181
- results = [r for r in results if file_filter in (r.document.file_path or "")]
182
-
193
+ results = [
194
+ r for r in results if file_filter in (r.document.file_path or "")
195
+ ]
196
+
183
197
  # Format results
184
198
  output_lines = [f"Found {len(results)} results for query: '{query}'\n"]
185
-
199
+
186
200
  for i, result in enumerate(results, 1):
187
201
  doc = result.document
188
202
  score_percent = result.score * 100
189
-
203
+
190
204
  # Header with score and metadata
191
205
  project_name = doc.metadata.get("search_project", "unknown")
192
206
  header = f"Result {i} (Score: {score_percent:.1f}%) - Project: {project_name}"
@@ -194,37 +208,42 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
194
208
  header += f" - {doc.file_path}"
195
209
  if doc.chunk_index is not None:
196
210
  header += f" [Chunk {doc.chunk_index}]"
197
-
211
+
198
212
  output_lines.append(header)
199
213
  output_lines.append("-" * len(header))
200
-
214
+
201
215
  # Add metadata if available
202
216
  if doc.metadata:
203
- relevant_metadata = {k: v for k, v in doc.metadata.items()
204
- if k not in ['chunk_number', 'total_chunks', 'search_project']}
217
+ relevant_metadata = {
218
+ k: v
219
+ for k, v in doc.metadata.items()
220
+ if k not in ["chunk_number", "total_chunks", "search_project"]
221
+ }
205
222
  if relevant_metadata:
206
- output_lines.append(f"Metadata: {json.dumps(relevant_metadata, indent=2)}")
207
-
223
+ output_lines.append(
224
+ f"Metadata: {json.dumps(relevant_metadata, indent=2)}"
225
+ )
226
+
208
227
  # Add content if requested
209
228
  if include_content:
210
229
  content = doc.content
211
230
  if len(content) > 500:
212
231
  content = content[:500] + "..."
213
232
  output_lines.append(f"Content:\n{content}")
214
-
233
+
215
234
  output_lines.append("") # Empty line between results
216
-
235
+
217
236
  return "\n".join(output_lines)
218
-
237
+
219
238
  except Exception as e:
220
239
  return f"Error searching vector database: {str(e)}"
221
-
240
+
222
241
  def register(self, mcp_server) -> None:
223
242
  """Register this tool with the MCP server.
224
-
243
+
225
244
  Args:
226
245
  mcp_server: The FastMCP server instance
227
246
  """
228
247
  # This is a placeholder - the actual registration would happen
229
248
  # through the MCP server's tool registration mechanism
230
- pass
249
+ pass