hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +6 -0
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/vector/vector.py
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
"""Unified vector store tool."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
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__(
|
|
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(
|
|
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(
|
|
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,
|
|
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__(
|
|
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 =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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__(
|
|
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
|
|
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(
|
|
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"] =
|
|
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(
|
|
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(
|
|
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"] =
|
|
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 = [
|
|
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 = {
|
|
204
|
-
|
|
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(
|
|
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
|