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.

Files changed (167) hide show
  1. hanzo_mcp/__init__.py +1 -3
  2. hanzo_mcp/analytics/posthog_analytics.py +4 -17
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +8 -17
  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 +2 -4
  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 +6 -7
  17. hanzo_mcp/tools/__init__.py +29 -32
  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 +23 -17
  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 +76 -75
  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 +7 -19
  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 +3 -5
  101. hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
  102. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  103. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  104. hanzo_mcp/tools/memory/__init__.py +33 -40
  105. hanzo_mcp/tools/memory/conversation_memory.py +636 -0
  106. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  107. hanzo_mcp/tools/memory/memory_tools.py +7 -19
  108. hanzo_mcp/tools/search/find_tool.py +12 -34
  109. hanzo_mcp/tools/search/unified_search.py +27 -81
  110. hanzo_mcp/tools/shell/__init__.py +16 -4
  111. hanzo_mcp/tools/shell/auto_background.py +2 -6
  112. hanzo_mcp/tools/shell/base.py +1 -5
  113. hanzo_mcp/tools/shell/base_process.py +5 -7
  114. hanzo_mcp/tools/shell/bash_session.py +7 -24
  115. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  116. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  117. hanzo_mcp/tools/shell/command_executor.py +26 -79
  118. hanzo_mcp/tools/shell/logs.py +4 -16
  119. hanzo_mcp/tools/shell/npx.py +2 -8
  120. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  121. hanzo_mcp/tools/shell/pkill.py +4 -12
  122. hanzo_mcp/tools/shell/process_tool.py +2 -8
  123. hanzo_mcp/tools/shell/processes.py +5 -17
  124. hanzo_mcp/tools/shell/run_background.py +1 -3
  125. hanzo_mcp/tools/shell/run_command.py +1 -3
  126. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  127. hanzo_mcp/tools/shell/run_tool.py +56 -0
  128. hanzo_mcp/tools/shell/session_manager.py +2 -6
  129. hanzo_mcp/tools/shell/session_storage.py +2 -6
  130. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  131. hanzo_mcp/tools/shell/uvx.py +4 -14
  132. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  133. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  134. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  135. hanzo_mcp/tools/todo/todo.py +1 -3
  136. hanzo_mcp/tools/vector/__init__.py +97 -50
  137. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  138. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  139. hanzo_mcp/tools/vector/index_tool.py +3 -9
  140. hanzo_mcp/tools/vector/infinity_store.py +11 -30
  141. hanzo_mcp/tools/vector/mock_infinity.py +159 -0
  142. hanzo_mcp/tools/vector/node_tool.py +538 -0
  143. hanzo_mcp/tools/vector/project_manager.py +4 -12
  144. hanzo_mcp/tools/vector/unified_vector.py +384 -0
  145. hanzo_mcp/tools/vector/vector.py +2 -6
  146. hanzo_mcp/tools/vector/vector_index.py +8 -8
  147. hanzo_mcp/tools/vector/vector_search.py +7 -21
  148. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
  149. hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
  150. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
  151. hanzo_mcp/tools/agent/swarm_tool.py +0 -723
  152. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
  153. hanzo_mcp/tools/filesystem/batch_search.py +0 -900
  154. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
  155. hanzo_mcp/tools/filesystem/find_files.py +0 -369
  156. hanzo_mcp/tools/filesystem/grep.py +0 -467
  157. hanzo_mcp/tools/filesystem/search_tool.py +0 -767
  158. hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
  159. hanzo_mcp/tools/filesystem/tree.py +0 -270
  160. hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
  161. hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
  162. hanzo_mcp/tools/todo/todo_read.py +0 -143
  163. hanzo_mcp/tools/todo/todo_write.py +0 -374
  164. hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
  165. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
  166. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
  167. {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
+ )
@@ -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.8
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"