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

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

Potentially problematic release.


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

Files changed (166) hide show
  1. hanzo_mcp/__init__.py +1 -3
  2. hanzo_mcp/analytics/posthog_analytics.py +3 -9
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +6 -15
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +1 -3
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +2 -6
  17. hanzo_mcp/tools/__init__.py +26 -27
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +22 -15
  21. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  22. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  23. hanzo_mcp/tools/agent/cli_tools.py +75 -74
  24. hanzo_mcp/tools/agent/code_auth.py +1 -3
  25. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  26. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  27. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  28. hanzo_mcp/tools/agent/network_tool.py +7 -18
  29. hanzo_mcp/tools/agent/prompt.py +1 -5
  30. hanzo_mcp/tools/agent/review_tool.py +10 -25
  31. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  32. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  33. hanzo_mcp/tools/common/batch_tool.py +15 -45
  34. hanzo_mcp/tools/common/config_tool.py +9 -28
  35. hanzo_mcp/tools/common/context.py +1 -3
  36. hanzo_mcp/tools/common/critic_tool.py +1 -3
  37. hanzo_mcp/tools/common/decorators.py +2 -6
  38. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  39. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  40. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  41. hanzo_mcp/tools/common/mode.py +1 -5
  42. hanzo_mcp/tools/common/paginated_base.py +3 -11
  43. hanzo_mcp/tools/common/paginated_response.py +10 -30
  44. hanzo_mcp/tools/common/pagination.py +3 -9
  45. hanzo_mcp/tools/common/path_utils.py +34 -0
  46. hanzo_mcp/tools/common/permissions.py +14 -13
  47. hanzo_mcp/tools/common/personality.py +983 -701
  48. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  49. hanzo_mcp/tools/common/stats.py +6 -18
  50. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  51. hanzo_mcp/tools/common/tool_disable.py +2 -6
  52. hanzo_mcp/tools/common/tool_list.py +2 -6
  53. hanzo_mcp/tools/common/validation.py +1 -3
  54. hanzo_mcp/tools/compiler/__init__.py +8 -0
  55. hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  72. hanzo_mcp/tools/environment/__init__.py +8 -0
  73. hanzo_mcp/tools/environment/environment_detector.py +594 -0
  74. hanzo_mcp/tools/filesystem/__init__.py +28 -26
  75. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  76. hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
  77. hanzo_mcp/tools/filesystem/base.py +20 -12
  78. hanzo_mcp/tools/filesystem/content_replace.py +7 -12
  79. hanzo_mcp/tools/filesystem/diff.py +2 -10
  80. hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
  81. hanzo_mcp/tools/filesystem/edit.py +10 -18
  82. hanzo_mcp/tools/filesystem/find.py +312 -179
  83. hanzo_mcp/tools/filesystem/git_search.py +12 -24
  84. hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
  85. hanzo_mcp/tools/filesystem/read.py +14 -30
  86. hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
  87. hanzo_mcp/tools/filesystem/search.py +1160 -0
  88. hanzo_mcp/tools/filesystem/watch.py +2 -4
  89. hanzo_mcp/tools/filesystem/write.py +7 -10
  90. hanzo_mcp/tools/framework/__init__.py +8 -0
  91. hanzo_mcp/tools/framework/framework_modes.py +714 -0
  92. hanzo_mcp/tools/jupyter/base.py +6 -20
  93. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  94. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  95. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  96. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  97. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  98. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  99. hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
  100. hanzo_mcp/tools/mcp/mcp_add.py +1 -3
  101. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  102. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  103. hanzo_mcp/tools/memory/__init__.py +10 -27
  104. hanzo_mcp/tools/memory/conversation_memory.py +636 -0
  105. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  106. hanzo_mcp/tools/memory/memory_tools.py +6 -18
  107. hanzo_mcp/tools/search/find_tool.py +12 -34
  108. hanzo_mcp/tools/search/unified_search.py +24 -78
  109. hanzo_mcp/tools/shell/__init__.py +16 -4
  110. hanzo_mcp/tools/shell/auto_background.py +2 -6
  111. hanzo_mcp/tools/shell/base.py +1 -5
  112. hanzo_mcp/tools/shell/base_process.py +5 -7
  113. hanzo_mcp/tools/shell/bash_session.py +7 -24
  114. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  115. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  116. hanzo_mcp/tools/shell/command_executor.py +26 -79
  117. hanzo_mcp/tools/shell/logs.py +4 -16
  118. hanzo_mcp/tools/shell/npx.py +2 -8
  119. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  120. hanzo_mcp/tools/shell/pkill.py +4 -12
  121. hanzo_mcp/tools/shell/process_tool.py +2 -8
  122. hanzo_mcp/tools/shell/processes.py +5 -17
  123. hanzo_mcp/tools/shell/run_background.py +1 -3
  124. hanzo_mcp/tools/shell/run_command.py +1 -3
  125. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  126. hanzo_mcp/tools/shell/run_tool.py +56 -0
  127. hanzo_mcp/tools/shell/session_manager.py +2 -6
  128. hanzo_mcp/tools/shell/session_storage.py +2 -6
  129. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  130. hanzo_mcp/tools/shell/uvx.py +4 -14
  131. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  132. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  133. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  134. hanzo_mcp/tools/todo/todo.py +1 -3
  135. hanzo_mcp/tools/vector/__init__.py +97 -50
  136. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  137. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  138. hanzo_mcp/tools/vector/index_tool.py +3 -9
  139. hanzo_mcp/tools/vector/infinity_store.py +7 -27
  140. hanzo_mcp/tools/vector/mock_infinity.py +1 -3
  141. hanzo_mcp/tools/vector/node_tool.py +538 -0
  142. hanzo_mcp/tools/vector/project_manager.py +4 -12
  143. hanzo_mcp/tools/vector/unified_vector.py +384 -0
  144. hanzo_mcp/tools/vector/vector.py +2 -6
  145. hanzo_mcp/tools/vector/vector_index.py +8 -8
  146. hanzo_mcp/tools/vector/vector_search.py +7 -21
  147. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
  148. hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
  149. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
  150. hanzo_mcp/tools/agent/swarm_tool.py +0 -718
  151. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
  152. hanzo_mcp/tools/filesystem/batch_search.py +0 -900
  153. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
  154. hanzo_mcp/tools/filesystem/find_files.py +0 -369
  155. hanzo_mcp/tools/filesystem/grep.py +0 -467
  156. hanzo_mcp/tools/filesystem/search_tool.py +0 -767
  157. hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
  158. hanzo_mcp/tools/filesystem/tree.py +0 -270
  159. hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
  160. hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
  161. hanzo_mcp/tools/todo/todo_read.py +0 -143
  162. hanzo_mcp/tools/todo/todo_write.py +0 -374
  163. hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
  164. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
  165. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
  166. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
@@ -153,9 +153,7 @@ class ProjectVectorManager:
153
153
 
154
154
  return None
155
155
 
156
- def get_vector_store(
157
- self, project_info: Optional[ProjectInfo] = None
158
- ) -> InfinityVectorStore:
156
+ def get_vector_store(self, project_info: Optional[ProjectInfo] = None) -> InfinityVectorStore:
159
157
  """Get vector store for a project or global store.
160
158
 
161
159
  Args:
@@ -178,9 +176,7 @@ class ProjectVectorManager:
178
176
 
179
177
  if project_key not in self.vector_stores:
180
178
  # Get index path based on configuration
181
- index_path = self.index_config.get_index_path(
182
- "vector", str(project_info.root_path)
183
- )
179
+ index_path = self.index_config.get_index_path("vector", str(project_info.root_path))
184
180
  index_path.mkdir(parents=True, exist_ok=True)
185
181
 
186
182
  self.vector_stores[project_key] = InfinityVectorStore(
@@ -270,9 +266,7 @@ class ProjectVectorManager:
270
266
  search_tasks.append(
271
267
  asyncio.get_event_loop().run_in_executor(
272
268
  self.executor,
273
- lambda: global_store.search(
274
- query, limit_per_project, score_threshold
275
- ),
269
+ lambda: global_store.search(query, limit_per_project, score_threshold),
276
270
  )
277
271
  )
278
272
  project_names.append("global")
@@ -287,9 +281,7 @@ class ProjectVectorManager:
287
281
  search_tasks.append(
288
282
  asyncio.get_event_loop().run_in_executor(
289
283
  self.executor,
290
- lambda vs=vector_store: vs.search(
291
- query, limit_per_project, score_threshold
292
- ),
284
+ lambda vs=vector_store: vs.search(query, limit_per_project, score_threshold),
293
285
  )
294
286
  )
295
287
  project_names.append(project_info.name)
@@ -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
+ )
@@ -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
- 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
- )
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 f"Successfully indexed {file_name} with {len(doc_ids)} chunks in project '{project_info.name}'"
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.8.11
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.1.0; extra == "dev"
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"