hanzo-mcp 0.8.8__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.
- hanzo_mcp/__init__.py +1 -3
- hanzo_mcp/analytics/posthog_analytics.py +4 -17
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +8 -17
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +2 -4
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +6 -7
- hanzo_mcp/tools/__init__.py +29 -32
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +23 -17
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +76 -75
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/path_utils.py +34 -0
- hanzo_mcp/tools/common/permissions.py +14 -13
- hanzo_mcp/tools/common/personality.py +983 -701
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +7 -19
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/compiler/__init__.py +8 -0
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/environment/__init__.py +8 -0
- hanzo_mcp/tools/environment/environment_detector.py +594 -0
- hanzo_mcp/tools/filesystem/__init__.py +28 -26
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
- hanzo_mcp/tools/filesystem/base.py +20 -12
- hanzo_mcp/tools/filesystem/content_replace.py +7 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
- hanzo_mcp/tools/filesystem/edit.py +10 -18
- hanzo_mcp/tools/filesystem/find.py +312 -179
- hanzo_mcp/tools/filesystem/git_search.py +12 -24
- hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
- hanzo_mcp/tools/filesystem/read.py +14 -30
- hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
- hanzo_mcp/tools/filesystem/search.py +1160 -0
- hanzo_mcp/tools/filesystem/watch.py +2 -4
- hanzo_mcp/tools/filesystem/write.py +7 -10
- hanzo_mcp/tools/framework/__init__.py +8 -0
- hanzo_mcp/tools/framework/framework_modes.py +714 -0
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
- hanzo_mcp/tools/mcp/mcp_add.py +3 -5
- hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +33 -40
- hanzo_mcp/tools/memory/conversation_memory.py +636 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +7 -19
- hanzo_mcp/tools/search/find_tool.py +12 -34
- hanzo_mcp/tools/search/unified_search.py +27 -81
- hanzo_mcp/tools/shell/__init__.py +16 -4
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/run_tool.py +56 -0
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/vector/__init__.py +97 -50
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +11 -30
- hanzo_mcp/tools/vector/mock_infinity.py +159 -0
- hanzo_mcp/tools/vector/node_tool.py +538 -0
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/unified_vector.py +384 -0
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
- hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
- hanzo_mcp/tools/agent/swarm_tool.py +0 -723
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
- hanzo_mcp/tools/filesystem/batch_search.py +0 -900
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
- hanzo_mcp/tools/filesystem/find_files.py +0 -369
- hanzo_mcp/tools/filesystem/grep.py +0 -467
- hanzo_mcp/tools/filesystem/search_tool.py +0 -767
- hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
- hanzo_mcp/tools/filesystem/tree.py +0 -270
- hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
- hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
- hanzo_mcp/tools/todo/todo_read.py +0 -143
- hanzo_mcp/tools/todo/todo_write.py +0 -374
- hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""Unified vector tool that consolidates all vector/semantic search functionality."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional, final, override
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from mcp.server import FastMCP
|
|
12
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
13
|
+
|
|
14
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
15
|
+
from hanzo_mcp.tools.common.context import create_tool_context
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Try to import LanceDB and hanzo-memory dependencies
|
|
20
|
+
try:
|
|
21
|
+
import lancedb
|
|
22
|
+
from hanzo_memory.db.lancedb_client import get_lancedb_client
|
|
23
|
+
from hanzo_memory.services.memory import get_memory_service
|
|
24
|
+
from hanzo_memory.models.memory import Memory
|
|
25
|
+
LANCEDB_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
LANCEDB_AVAILABLE = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HanzoNodeClient:
|
|
31
|
+
"""Client for communicating with hanzo-node."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, base_url: str = "http://localhost:3690"):
|
|
34
|
+
"""Initialize hanzo-node client."""
|
|
35
|
+
self.base_url = base_url
|
|
36
|
+
self.client = httpx.AsyncClient(timeout=30.0)
|
|
37
|
+
|
|
38
|
+
async def is_available(self) -> bool:
|
|
39
|
+
"""Check if hanzo-node is running and available."""
|
|
40
|
+
try:
|
|
41
|
+
response = await self.client.get(f"{self.base_url}/health")
|
|
42
|
+
return response.status_code == 200
|
|
43
|
+
except Exception:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
async def search_vectors(self, query: str, limit: int = 10, **kwargs) -> List[Dict[str, Any]]:
|
|
47
|
+
"""Search vectors using hanzo-node."""
|
|
48
|
+
try:
|
|
49
|
+
payload = {
|
|
50
|
+
"query": query,
|
|
51
|
+
"limit": limit,
|
|
52
|
+
**kwargs
|
|
53
|
+
}
|
|
54
|
+
response = await self.client.post(f"{self.base_url}/api/v1/search", json=payload)
|
|
55
|
+
response.raise_for_status()
|
|
56
|
+
return response.json().get("results", [])
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.error(f"Error searching vectors via hanzo-node: {e}")
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
async def index_content(self, content: str, metadata: Optional[Dict] = None) -> bool:
|
|
62
|
+
"""Index content using hanzo-node."""
|
|
63
|
+
try:
|
|
64
|
+
payload = {
|
|
65
|
+
"content": content,
|
|
66
|
+
"metadata": metadata or {}
|
|
67
|
+
}
|
|
68
|
+
response = await self.client.post(f"{self.base_url}/api/v1/index", json=payload)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
return True
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Error indexing content via hanzo-node: {e}")
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@final
|
|
77
|
+
class UnifiedVectorTool(BaseTool):
|
|
78
|
+
"""Unified vector tool that consolidates all vector/semantic search functionality.
|
|
79
|
+
|
|
80
|
+
This tool provides a single interface for vector operations that:
|
|
81
|
+
1. Detects if hanzo-node is running on localhost:3690
|
|
82
|
+
2. Uses hanzo-node for vector operations if available
|
|
83
|
+
3. Falls back to embedded LanceDB if hanzo-node is not available
|
|
84
|
+
4. Provides semantic search, indexing, and memory operations
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, user_id: str = "default", project_id: str = "default"):
|
|
88
|
+
"""Initialize unified vector tool."""
|
|
89
|
+
self.user_id = user_id
|
|
90
|
+
self.project_id = project_id
|
|
91
|
+
self.hanzo_node = HanzoNodeClient()
|
|
92
|
+
|
|
93
|
+
# Initialize LanceDB fallback if available
|
|
94
|
+
if LANCEDB_AVAILABLE:
|
|
95
|
+
self.lancedb_client = get_lancedb_client()
|
|
96
|
+
self.memory_service = get_memory_service()
|
|
97
|
+
else:
|
|
98
|
+
self.lancedb_client = None
|
|
99
|
+
self.memory_service = None
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
@override
|
|
103
|
+
def name(self) -> str:
|
|
104
|
+
"""Get the tool name."""
|
|
105
|
+
return "vector"
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
@override
|
|
109
|
+
def description(self) -> str:
|
|
110
|
+
"""Get the tool description."""
|
|
111
|
+
return """Unified vector search and semantic operations.
|
|
112
|
+
|
|
113
|
+
This tool provides comprehensive vector/semantic search capabilities:
|
|
114
|
+
- Semantic search across indexed content
|
|
115
|
+
- Content indexing for future search
|
|
116
|
+
- Memory storage and retrieval
|
|
117
|
+
- Knowledge base operations
|
|
118
|
+
|
|
119
|
+
The tool automatically detects if hanzo-node is available and uses it for
|
|
120
|
+
optimal performance, falling back to embedded LanceDB if needed.
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
vector(action="search", query="error handling in Python", limit=5)
|
|
124
|
+
vector(action="index", content="Important project documentation", metadata={"type": "docs"})
|
|
125
|
+
vector(action="memory_create", content="User prefers TypeScript over JavaScript")
|
|
126
|
+
vector(action="memory_search", query="user preferences")
|
|
127
|
+
vector(action="status") # Check backend status
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
async def _detect_backend(self) -> str:
|
|
131
|
+
"""Detect which backend to use."""
|
|
132
|
+
if await self.hanzo_node.is_available():
|
|
133
|
+
return "hanzo-node"
|
|
134
|
+
elif LANCEDB_AVAILABLE:
|
|
135
|
+
return "lancedb"
|
|
136
|
+
else:
|
|
137
|
+
return "none"
|
|
138
|
+
|
|
139
|
+
async def _search_hanzo_node(self, query: str, limit: int, **kwargs) -> List[Dict[str, Any]]:
|
|
140
|
+
"""Search using hanzo-node."""
|
|
141
|
+
return await self.hanzo_node.search_vectors(query, limit, **kwargs)
|
|
142
|
+
|
|
143
|
+
async def _search_lancedb(self, query: str, limit: int, project_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
144
|
+
"""Search using embedded LanceDB."""
|
|
145
|
+
if not self.memory_service:
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# Use hanzo-memory service for search
|
|
150
|
+
results = self.memory_service.search_memories(
|
|
151
|
+
user_id=self.user_id,
|
|
152
|
+
query=query,
|
|
153
|
+
project_id=project_id or self.project_id,
|
|
154
|
+
limit=limit
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Convert to standard format
|
|
158
|
+
formatted_results = []
|
|
159
|
+
for result in results:
|
|
160
|
+
formatted_results.append({
|
|
161
|
+
"content": result.content,
|
|
162
|
+
"score": getattr(result, "similarity_score", 0.0),
|
|
163
|
+
"metadata": getattr(result, "metadata", {}),
|
|
164
|
+
"id": result.memory_id
|
|
165
|
+
})
|
|
166
|
+
return formatted_results
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.error(f"Error searching LanceDB: {e}")
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
async def _index_hanzo_node(self, content: str, metadata: Optional[Dict] = None) -> bool:
|
|
172
|
+
"""Index content using hanzo-node."""
|
|
173
|
+
return await self.hanzo_node.index_content(content, metadata)
|
|
174
|
+
|
|
175
|
+
async def _index_lancedb(self, content: str, metadata: Optional[Dict] = None) -> bool:
|
|
176
|
+
"""Index content using embedded LanceDB."""
|
|
177
|
+
if not self.memory_service:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
self.memory_service.create_memory(
|
|
182
|
+
user_id=self.user_id,
|
|
183
|
+
project_id=self.project_id,
|
|
184
|
+
content=content,
|
|
185
|
+
metadata=metadata or {}
|
|
186
|
+
)
|
|
187
|
+
return True
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.error(f"Error indexing to LanceDB: {e}")
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
@override
|
|
193
|
+
async def call(
|
|
194
|
+
self,
|
|
195
|
+
ctx: MCPContext,
|
|
196
|
+
action: str,
|
|
197
|
+
query: Optional[str] = None,
|
|
198
|
+
content: Optional[str] = None,
|
|
199
|
+
limit: int = 10,
|
|
200
|
+
project_id: Optional[str] = None,
|
|
201
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
202
|
+
**kwargs
|
|
203
|
+
) -> str:
|
|
204
|
+
"""Execute vector operations.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
ctx: MCP context
|
|
208
|
+
action: Action to perform (search, index, memory_create, memory_search, status)
|
|
209
|
+
query: Search query (for search actions)
|
|
210
|
+
content: Content to index or store (for index/create actions)
|
|
211
|
+
limit: Maximum results to return
|
|
212
|
+
project_id: Project ID (defaults to tool's project_id)
|
|
213
|
+
metadata: Additional metadata
|
|
214
|
+
**kwargs: Additional action-specific parameters
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Formatted results or status message
|
|
218
|
+
"""
|
|
219
|
+
tool_ctx = create_tool_context(ctx)
|
|
220
|
+
await tool_ctx.set_tool_info(self.name)
|
|
221
|
+
|
|
222
|
+
# Detect backend
|
|
223
|
+
backend = await self._detect_backend()
|
|
224
|
+
await tool_ctx.info(f"Using backend: {backend}")
|
|
225
|
+
|
|
226
|
+
if action == "status":
|
|
227
|
+
return await self._handle_status(tool_ctx, backend)
|
|
228
|
+
elif action == "search":
|
|
229
|
+
return await self._handle_search(tool_ctx, backend, query, limit, project_id, **kwargs)
|
|
230
|
+
elif action == "index":
|
|
231
|
+
return await self._handle_index(tool_ctx, backend, content, metadata)
|
|
232
|
+
elif action == "memory_create":
|
|
233
|
+
return await self._handle_memory_create(tool_ctx, content, metadata)
|
|
234
|
+
elif action == "memory_search":
|
|
235
|
+
return await self._handle_memory_search(tool_ctx, query, limit, project_id)
|
|
236
|
+
else:
|
|
237
|
+
return f"Unknown action: {action}. Available actions: search, index, memory_create, memory_search, status"
|
|
238
|
+
|
|
239
|
+
async def _handle_status(self, tool_ctx, backend: str) -> str:
|
|
240
|
+
"""Handle status check."""
|
|
241
|
+
status_info = [f"Vector backend: {backend}"]
|
|
242
|
+
|
|
243
|
+
if backend == "hanzo-node":
|
|
244
|
+
try:
|
|
245
|
+
node_available = await self.hanzo_node.is_available()
|
|
246
|
+
status_info.append(f"Hanzo-node available: {node_available}")
|
|
247
|
+
status_info.append(f"Hanzo-node URL: {self.hanzo_node.base_url}")
|
|
248
|
+
except Exception as e:
|
|
249
|
+
status_info.append(f"Hanzo-node error: {e}")
|
|
250
|
+
|
|
251
|
+
if backend == "lancedb" or backend == "hanzo-node":
|
|
252
|
+
status_info.append(f"LanceDB available: {LANCEDB_AVAILABLE}")
|
|
253
|
+
if LANCEDB_AVAILABLE and self.lancedb_client:
|
|
254
|
+
try:
|
|
255
|
+
# Check LanceDB status
|
|
256
|
+
db_path = Path(self.lancedb_client.db_path)
|
|
257
|
+
status_info.append(f"LanceDB path: {db_path}")
|
|
258
|
+
status_info.append(f"LanceDB exists: {db_path.exists()}")
|
|
259
|
+
if db_path.exists():
|
|
260
|
+
status_info.append(f"Table count: {len(self.lancedb_client.db.table_names())}")
|
|
261
|
+
except Exception as e:
|
|
262
|
+
status_info.append(f"LanceDB error: {e}")
|
|
263
|
+
|
|
264
|
+
if backend == "none":
|
|
265
|
+
status_info.append("No vector backend available. Install hanzo-memory or start hanzo-node.")
|
|
266
|
+
|
|
267
|
+
return "\n".join(status_info)
|
|
268
|
+
|
|
269
|
+
async def _handle_search(self, tool_ctx, backend: str, query: Optional[str], limit: int, project_id: Optional[str], **kwargs) -> str:
|
|
270
|
+
"""Handle search operations."""
|
|
271
|
+
if not query:
|
|
272
|
+
return "Error: Query is required for search action"
|
|
273
|
+
|
|
274
|
+
await tool_ctx.info(f"Searching for: {query} (limit: {limit})")
|
|
275
|
+
|
|
276
|
+
if backend == "hanzo-node":
|
|
277
|
+
results = await self._search_hanzo_node(query, limit, **kwargs)
|
|
278
|
+
elif backend == "lancedb":
|
|
279
|
+
results = await self._search_lancedb(query, limit, project_id)
|
|
280
|
+
else:
|
|
281
|
+
return "Error: No vector backend available"
|
|
282
|
+
|
|
283
|
+
if not results:
|
|
284
|
+
return f"No results found for query: {query}"
|
|
285
|
+
|
|
286
|
+
# Format results
|
|
287
|
+
formatted = [f"Found {len(results)} results for '{query}':\n"]
|
|
288
|
+
for i, result in enumerate(results, 1):
|
|
289
|
+
content = result.get("content", "")
|
|
290
|
+
score = result.get("score", 0.0)
|
|
291
|
+
formatted.append(f"{i}. {content} (score: {score:.3f})")
|
|
292
|
+
|
|
293
|
+
return "\n".join(formatted)
|
|
294
|
+
|
|
295
|
+
async def _handle_index(self, tool_ctx, backend: str, content: Optional[str], metadata: Optional[Dict]) -> str:
|
|
296
|
+
"""Handle indexing operations."""
|
|
297
|
+
if not content:
|
|
298
|
+
return "Error: Content is required for index action"
|
|
299
|
+
|
|
300
|
+
await tool_ctx.info(f"Indexing content: {content[:100]}...")
|
|
301
|
+
|
|
302
|
+
if backend == "hanzo-node":
|
|
303
|
+
success = await self._index_hanzo_node(content, metadata)
|
|
304
|
+
elif backend == "lancedb":
|
|
305
|
+
success = await self._index_lancedb(content, metadata)
|
|
306
|
+
else:
|
|
307
|
+
return "Error: No vector backend available"
|
|
308
|
+
|
|
309
|
+
if success:
|
|
310
|
+
return f"Successfully indexed content (backend: {backend})"
|
|
311
|
+
else:
|
|
312
|
+
return f"Failed to index content (backend: {backend})"
|
|
313
|
+
|
|
314
|
+
async def _handle_memory_create(self, tool_ctx, content: Optional[str], metadata: Optional[Dict]) -> str:
|
|
315
|
+
"""Handle memory creation."""
|
|
316
|
+
if not content:
|
|
317
|
+
return "Error: Content is required for memory_create action"
|
|
318
|
+
|
|
319
|
+
if not self.memory_service:
|
|
320
|
+
return "Error: Memory service not available"
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
memory = self.memory_service.create_memory(
|
|
324
|
+
user_id=self.user_id,
|
|
325
|
+
project_id=self.project_id,
|
|
326
|
+
content=content,
|
|
327
|
+
metadata=metadata or {}
|
|
328
|
+
)
|
|
329
|
+
return f"Created memory: {memory.memory_id}"
|
|
330
|
+
except Exception as e:
|
|
331
|
+
return f"Error creating memory: {e}"
|
|
332
|
+
|
|
333
|
+
async def _handle_memory_search(self, tool_ctx, query: Optional[str], limit: int, project_id: Optional[str]) -> str:
|
|
334
|
+
"""Handle memory search."""
|
|
335
|
+
if not query:
|
|
336
|
+
return "Error: Query is required for memory_search action"
|
|
337
|
+
|
|
338
|
+
if not self.memory_service:
|
|
339
|
+
return "Error: Memory service not available"
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
results = self.memory_service.search_memories(
|
|
343
|
+
user_id=self.user_id,
|
|
344
|
+
query=query,
|
|
345
|
+
project_id=project_id or self.project_id,
|
|
346
|
+
limit=limit
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
if not results:
|
|
350
|
+
return f"No memories found for query: {query}"
|
|
351
|
+
|
|
352
|
+
formatted = [f"Found {len(results)} memories for '{query}':\n"]
|
|
353
|
+
for i, memory in enumerate(results, 1):
|
|
354
|
+
score = getattr(memory, "similarity_score", 0.0)
|
|
355
|
+
formatted.append(f"{i}. {memory.content} (score: {score:.3f})")
|
|
356
|
+
|
|
357
|
+
return "\n".join(formatted)
|
|
358
|
+
except Exception as e:
|
|
359
|
+
return f"Error searching memories: {e}"
|
|
360
|
+
|
|
361
|
+
@override
|
|
362
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
363
|
+
"""Register this tool with the MCP server."""
|
|
364
|
+
tool_self = self
|
|
365
|
+
|
|
366
|
+
@mcp_server.tool(name=self.name, description=self.description)
|
|
367
|
+
async def vector(
|
|
368
|
+
ctx: MCPContext,
|
|
369
|
+
action: str,
|
|
370
|
+
query: Optional[str] = None,
|
|
371
|
+
content: Optional[str] = None,
|
|
372
|
+
limit: int = 10,
|
|
373
|
+
project_id: Optional[str] = None,
|
|
374
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
375
|
+
) -> str:
|
|
376
|
+
return await tool_self.call(
|
|
377
|
+
ctx,
|
|
378
|
+
action=action,
|
|
379
|
+
query=query,
|
|
380
|
+
content=content,
|
|
381
|
+
limit=limit,
|
|
382
|
+
project_id=project_id,
|
|
383
|
+
metadata=metadata,
|
|
384
|
+
)
|
hanzo_mcp/tools/vector/vector.py
CHANGED
|
@@ -249,9 +249,7 @@ vector --action clear --path ./old_code
|
|
|
249
249
|
output.append(f"Chunks created: {stats.get('chunks_created', 0)}")
|
|
250
250
|
if stats.get("git_commits"):
|
|
251
251
|
output.append(f"Git commits indexed: {stats['git_commits']}")
|
|
252
|
-
output.append(
|
|
253
|
-
f"Total documents: {project.get_stats().get('total_documents', 0)}"
|
|
254
|
-
)
|
|
252
|
+
output.append(f"Total documents: {project.get_stats().get('total_documents', 0)}")
|
|
255
253
|
|
|
256
254
|
return "\n".join(output)
|
|
257
255
|
|
|
@@ -283,9 +281,7 @@ vector --action clear --path ./old_code
|
|
|
283
281
|
if stats.get("projects"):
|
|
284
282
|
output.append(f"\nProjects indexed: {len(stats['projects'])}")
|
|
285
283
|
for proj in stats["projects"]:
|
|
286
|
-
output.append(
|
|
287
|
-
f" - {proj['name']}: {proj['documents']} docs, {proj['size_mb']:.1f} MB"
|
|
288
|
-
)
|
|
284
|
+
output.append(f" - {proj['name']}: {proj['documents']} docs, {proj['size_mb']:.1f} MB")
|
|
289
285
|
|
|
290
286
|
return "\n".join(output)
|
|
291
287
|
|
|
@@ -90,18 +90,18 @@ directories alongside them. Use this to build searchable knowledge bases per pro
|
|
|
90
90
|
return f"Error: File does not exist: {file_path}"
|
|
91
91
|
|
|
92
92
|
# Index file using project-aware manager
|
|
93
|
-
doc_ids, project_info = (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
metadata=metadata,
|
|
99
|
-
)
|
|
93
|
+
doc_ids, project_info = self.project_manager.add_file_to_appropriate_store(
|
|
94
|
+
file_path=file_path,
|
|
95
|
+
chunk_size=chunk_size,
|
|
96
|
+
chunk_overlap=chunk_overlap,
|
|
97
|
+
metadata=metadata,
|
|
100
98
|
)
|
|
101
99
|
|
|
102
100
|
file_name = Path(file_path).name
|
|
103
101
|
if project_info:
|
|
104
|
-
return
|
|
102
|
+
return (
|
|
103
|
+
f"Successfully indexed {file_name} with {len(doc_ids)} chunks in project '{project_info.name}'"
|
|
104
|
+
)
|
|
105
105
|
else:
|
|
106
106
|
return f"Successfully indexed {file_name} with {len(doc_ids)} chunks in global database"
|
|
107
107
|
|
|
@@ -135,9 +135,7 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
|
|
|
135
135
|
break
|
|
136
136
|
|
|
137
137
|
if project_info:
|
|
138
|
-
vector_store = self.project_manager.get_vector_store(
|
|
139
|
-
project_info
|
|
140
|
-
)
|
|
138
|
+
vector_store = self.project_manager.get_vector_store(project_info)
|
|
141
139
|
results = vector_store.search(
|
|
142
140
|
query=query,
|
|
143
141
|
limit=limit,
|
|
@@ -145,9 +143,7 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
|
|
|
145
143
|
)
|
|
146
144
|
for result in results:
|
|
147
145
|
result.document.metadata = result.document.metadata or {}
|
|
148
|
-
result.document.metadata["search_project"] =
|
|
149
|
-
project_info.name
|
|
150
|
-
)
|
|
146
|
+
result.document.metadata["search_project"] = project_info.name
|
|
151
147
|
else:
|
|
152
148
|
return f"Project '{search_scope}' not found"
|
|
153
149
|
else:
|
|
@@ -155,14 +151,10 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
|
|
|
155
151
|
import os
|
|
156
152
|
|
|
157
153
|
current_dir = os.getcwd()
|
|
158
|
-
project_info = self.project_manager.get_project_for_path(
|
|
159
|
-
current_dir
|
|
160
|
-
)
|
|
154
|
+
project_info = self.project_manager.get_project_for_path(current_dir)
|
|
161
155
|
|
|
162
156
|
if project_info:
|
|
163
|
-
vector_store = self.project_manager.get_vector_store(
|
|
164
|
-
project_info
|
|
165
|
-
)
|
|
157
|
+
vector_store = self.project_manager.get_vector_store(project_info)
|
|
166
158
|
results = vector_store.search(
|
|
167
159
|
query=query,
|
|
168
160
|
limit=limit,
|
|
@@ -170,9 +162,7 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
|
|
|
170
162
|
)
|
|
171
163
|
for result in results:
|
|
172
164
|
result.document.metadata = result.document.metadata or {}
|
|
173
|
-
result.document.metadata["search_project"] =
|
|
174
|
-
project_info.name
|
|
175
|
-
)
|
|
165
|
+
result.document.metadata["search_project"] = project_info.name
|
|
176
166
|
else:
|
|
177
167
|
# Fall back to global store
|
|
178
168
|
global_store = self.project_manager._get_global_store()
|
|
@@ -190,9 +180,7 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
|
|
|
190
180
|
|
|
191
181
|
# Filter by file if requested
|
|
192
182
|
if file_filter:
|
|
193
|
-
results = [
|
|
194
|
-
r for r in results if file_filter in (r.document.file_path or "")
|
|
195
|
-
]
|
|
183
|
+
results = [r for r in results if file_filter in (r.document.file_path or "")]
|
|
196
184
|
|
|
197
185
|
# Format results
|
|
198
186
|
output_lines = [f"Found {len(results)} results for query: '{query}'\n"]
|
|
@@ -220,9 +208,7 @@ Use 'grep' for exact text/pattern matching, 'vector_search' for semantic similar
|
|
|
220
208
|
if k not in ["chunk_number", "total_chunks", "search_project"]
|
|
221
209
|
}
|
|
222
210
|
if relevant_metadata:
|
|
223
|
-
output_lines.append(
|
|
224
|
-
f"Metadata: {json.dumps(relevant_metadata, indent=2)}"
|
|
225
|
-
)
|
|
211
|
+
output_lines.append(f"Metadata: {json.dumps(relevant_metadata, indent=2)}")
|
|
226
212
|
|
|
227
213
|
# Add content if requested
|
|
228
214
|
if include_content:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hanzo-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: The Zen of Hanzo MCP: One server to rule them all. The ultimate MCP that orchestrates all others.
|
|
5
5
|
Author-email: Hanzo Industries Inc <dev@hanzo.ai>
|
|
6
6
|
License: MIT
|
|
@@ -34,7 +34,7 @@ Requires-Dist: ffind>=1.3.0
|
|
|
34
34
|
Provides-Extra: dev
|
|
35
35
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
36
36
|
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
37
|
-
Requires-Dist: ruff>=0.
|
|
37
|
+
Requires-Dist: ruff>=0.13.0; extra == "dev"
|
|
38
38
|
Requires-Dist: black>=23.3.0; extra == "dev"
|
|
39
39
|
Requires-Dist: sphinx>=8.0.0; extra == "dev"
|
|
40
40
|
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "dev"
|