hanzo-mcp 0.6.13__py3-none-any.whl → 0.7.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 (62) hide show
  1. hanzo_mcp/analytics/__init__.py +5 -0
  2. hanzo_mcp/analytics/posthog_analytics.py +364 -0
  3. hanzo_mcp/cli.py +3 -3
  4. hanzo_mcp/cli_enhanced.py +3 -3
  5. hanzo_mcp/config/settings.py +1 -1
  6. hanzo_mcp/config/tool_config.py +18 -4
  7. hanzo_mcp/server.py +34 -1
  8. hanzo_mcp/tools/__init__.py +65 -2
  9. hanzo_mcp/tools/agent/__init__.py +84 -3
  10. hanzo_mcp/tools/agent/agent_tool.py +102 -4
  11. hanzo_mcp/tools/agent/agent_tool_v2.py +459 -0
  12. hanzo_mcp/tools/agent/clarification_protocol.py +220 -0
  13. hanzo_mcp/tools/agent/clarification_tool.py +68 -0
  14. hanzo_mcp/tools/agent/claude_cli_tool.py +125 -0
  15. hanzo_mcp/tools/agent/claude_desktop_auth.py +508 -0
  16. hanzo_mcp/tools/agent/cli_agent_base.py +191 -0
  17. hanzo_mcp/tools/agent/code_auth.py +436 -0
  18. hanzo_mcp/tools/agent/code_auth_tool.py +194 -0
  19. hanzo_mcp/tools/agent/codex_cli_tool.py +123 -0
  20. hanzo_mcp/tools/agent/critic_tool.py +376 -0
  21. hanzo_mcp/tools/agent/gemini_cli_tool.py +128 -0
  22. hanzo_mcp/tools/agent/grok_cli_tool.py +128 -0
  23. hanzo_mcp/tools/agent/iching_tool.py +380 -0
  24. hanzo_mcp/tools/agent/network_tool.py +273 -0
  25. hanzo_mcp/tools/agent/prompt.py +62 -20
  26. hanzo_mcp/tools/agent/review_tool.py +433 -0
  27. hanzo_mcp/tools/agent/swarm_tool.py +535 -0
  28. hanzo_mcp/tools/agent/swarm_tool_v2.py +594 -0
  29. hanzo_mcp/tools/common/base.py +1 -0
  30. hanzo_mcp/tools/common/batch_tool.py +102 -10
  31. hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
  32. hanzo_mcp/tools/common/forgiving_edit.py +243 -0
  33. hanzo_mcp/tools/common/paginated_base.py +230 -0
  34. hanzo_mcp/tools/common/paginated_response.py +307 -0
  35. hanzo_mcp/tools/common/pagination.py +226 -0
  36. hanzo_mcp/tools/common/tool_list.py +3 -0
  37. hanzo_mcp/tools/common/truncate.py +101 -0
  38. hanzo_mcp/tools/filesystem/__init__.py +29 -0
  39. hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
  40. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
  41. hanzo_mcp/tools/lsp/__init__.py +5 -0
  42. hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
  43. hanzo_mcp/tools/memory/__init__.py +76 -0
  44. hanzo_mcp/tools/memory/knowledge_tools.py +518 -0
  45. hanzo_mcp/tools/memory/memory_tools.py +456 -0
  46. hanzo_mcp/tools/search/__init__.py +6 -0
  47. hanzo_mcp/tools/search/find_tool.py +581 -0
  48. hanzo_mcp/tools/search/unified_search.py +953 -0
  49. hanzo_mcp/tools/shell/__init__.py +5 -0
  50. hanzo_mcp/tools/shell/auto_background.py +203 -0
  51. hanzo_mcp/tools/shell/base_process.py +53 -27
  52. hanzo_mcp/tools/shell/bash_tool.py +17 -33
  53. hanzo_mcp/tools/shell/npx_tool.py +15 -32
  54. hanzo_mcp/tools/shell/streaming_command.py +594 -0
  55. hanzo_mcp/tools/shell/uvx_tool.py +15 -32
  56. hanzo_mcp/types.py +23 -0
  57. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/METADATA +228 -71
  58. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/RECORD +61 -24
  59. hanzo_mcp-0.6.13.dist-info/licenses/LICENSE +0 -21
  60. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
  61. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +0 -0
  62. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,5 @@
1
+ """Analytics module for Hanzo MCP using PostHog."""
2
+
3
+ from .posthog_analytics import Analytics, track_event, track_tool_usage, track_error
4
+
5
+ __all__ = ["Analytics", "track_event", "track_tool_usage", "track_error"]
@@ -0,0 +1,364 @@
1
+ """PostHog analytics integration for Hanzo MCP.
2
+
3
+ This module provides analytics tracking for:
4
+ - Tool usage and performance
5
+ - Error tracking and debugging
6
+ - Feature adoption and user behavior
7
+ - A/B testing and feature flags
8
+ """
9
+
10
+ import os
11
+ import time
12
+ import functools
13
+ import traceback
14
+ from typing import Dict, Any, Optional, Callable, TypeVar
15
+ from datetime import datetime
16
+ import platform
17
+ import asyncio
18
+ from dataclasses import dataclass
19
+
20
+ # Try to import PostHog, but make it optional
21
+ try:
22
+ from posthog import Posthog
23
+ POSTHOG_AVAILABLE = True
24
+ except ImportError:
25
+ POSTHOG_AVAILABLE = False
26
+ Posthog = None
27
+
28
+
29
+ F = TypeVar('F', bound=Callable[..., Any])
30
+
31
+
32
+ @dataclass
33
+ class AnalyticsConfig:
34
+ """Configuration for analytics."""
35
+ api_key: Optional[str] = None
36
+ host: str = "https://us.i.posthog.com"
37
+ enabled: bool = True
38
+ debug: bool = False
39
+ capture_errors: bool = True
40
+ capture_performance: bool = True
41
+ distinct_id: Optional[str] = None
42
+
43
+
44
+ class Analytics:
45
+ """Main analytics class for Hanzo MCP."""
46
+
47
+ def __init__(self, config: Optional[AnalyticsConfig] = None):
48
+ """Initialize analytics with configuration."""
49
+ self.config = config or AnalyticsConfig()
50
+ self._client = None
51
+
52
+ # Load from environment if not provided
53
+ if not self.config.api_key:
54
+ self.config.api_key = os.environ.get("POSTHOG_API_KEY")
55
+
56
+ if not self.config.distinct_id:
57
+ # Use machine ID or generate one
58
+ self.config.distinct_id = self._get_distinct_id()
59
+
60
+ # Initialize PostHog if available and configured
61
+ if POSTHOG_AVAILABLE and self.config.api_key and self.config.enabled:
62
+ self._client = Posthog(
63
+ self.config.api_key,
64
+ host=self.config.host,
65
+ debug=self.config.debug,
66
+ enable_exception_autocapture=self.config.capture_errors
67
+ )
68
+
69
+ def _get_distinct_id(self) -> str:
70
+ """Get a distinct ID for this installation."""
71
+ # Try to get from environment
72
+ distinct_id = os.environ.get("HANZO_DISTINCT_ID")
73
+ if distinct_id:
74
+ return distinct_id
75
+
76
+ # Use hostname + username as fallback
77
+ import socket
78
+ import getpass
79
+ hostname = socket.gethostname()
80
+ username = getpass.getuser()
81
+ return f"{hostname}:{username}"
82
+
83
+ def is_enabled(self) -> bool:
84
+ """Check if analytics is enabled."""
85
+ return bool(self._client and self.config.enabled)
86
+
87
+ def capture(self, event: str, properties: Optional[Dict[str, Any]] = None) -> None:
88
+ """Capture an analytics event."""
89
+ if not self.is_enabled():
90
+ return
91
+
92
+ try:
93
+ # Add common properties
94
+ props = {
95
+ "timestamp": datetime.utcnow().isoformat(),
96
+ "platform": platform.system(),
97
+ "python_version": platform.python_version(),
98
+ "mcp_version": "0.6.13", # TODO: Get from package
99
+ **(properties or {})
100
+ }
101
+
102
+ self._client.capture(
103
+ self.config.distinct_id,
104
+ event,
105
+ properties=props
106
+ )
107
+ except Exception as e:
108
+ if self.config.debug:
109
+ print(f"Analytics error: {e}")
110
+
111
+ def identify(self, properties: Optional[Dict[str, Any]] = None) -> None:
112
+ """Identify the current user/installation."""
113
+ if not self.is_enabled():
114
+ return
115
+
116
+ try:
117
+ self._client.identify(
118
+ self.config.distinct_id,
119
+ properties=properties or {}
120
+ )
121
+ except Exception as e:
122
+ if self.config.debug:
123
+ print(f"Analytics identify error: {e}")
124
+
125
+ def track_tool_usage(self,
126
+ tool_name: str,
127
+ duration_ms: Optional[float] = None,
128
+ success: bool = True,
129
+ error: Optional[str] = None,
130
+ metadata: Optional[Dict[str, Any]] = None) -> None:
131
+ """Track tool usage event."""
132
+ properties = {
133
+ "tool_name": tool_name,
134
+ "success": success,
135
+ **(metadata or {})
136
+ }
137
+
138
+ if duration_ms is not None:
139
+ properties["duration_ms"] = duration_ms
140
+
141
+ if error:
142
+ properties["error"] = str(error)
143
+
144
+ self.capture("tool_used", properties)
145
+
146
+ def track_error(self,
147
+ error: Exception,
148
+ context: Optional[Dict[str, Any]] = None) -> None:
149
+ """Track an error event."""
150
+ if not self.config.capture_errors:
151
+ return
152
+
153
+ properties = {
154
+ "error_type": type(error).__name__,
155
+ "error_message": str(error),
156
+ "error_traceback": traceback.format_exc(),
157
+ **(context or {})
158
+ }
159
+
160
+ self.capture("error_occurred", properties)
161
+
162
+ def feature_enabled(self, flag_key: str, default: bool = False) -> bool:
163
+ """Check if a feature flag is enabled."""
164
+ if not self.is_enabled():
165
+ return default
166
+
167
+ try:
168
+ return self._client.feature_enabled(
169
+ flag_key,
170
+ self.config.distinct_id,
171
+ default=default
172
+ )
173
+ except Exception:
174
+ return default
175
+
176
+ def get_feature_flag(self, flag_key: str, default: Any = None) -> Any:
177
+ """Get feature flag value."""
178
+ if not self.is_enabled():
179
+ return default
180
+
181
+ try:
182
+ return self._client.get_feature_flag(
183
+ flag_key,
184
+ self.config.distinct_id,
185
+ default=default
186
+ )
187
+ except Exception:
188
+ return default
189
+
190
+ def flush(self) -> None:
191
+ """Flush any pending events."""
192
+ if self.is_enabled():
193
+ try:
194
+ self._client.flush()
195
+ except Exception:
196
+ pass
197
+
198
+ def shutdown(self) -> None:
199
+ """Shutdown analytics client."""
200
+ if self.is_enabled():
201
+ try:
202
+ self._client.shutdown()
203
+ except Exception:
204
+ pass
205
+
206
+
207
+ # Global analytics instance
208
+ _analytics = None
209
+
210
+
211
+ def get_analytics() -> Analytics:
212
+ """Get or create the global analytics instance."""
213
+ global _analytics
214
+ if _analytics is None:
215
+ _analytics = Analytics()
216
+ return _analytics
217
+
218
+
219
+ def track_event(event: str, properties: Optional[Dict[str, Any]] = None) -> None:
220
+ """Track a custom event."""
221
+ get_analytics().capture(event, properties)
222
+
223
+
224
+ def track_tool_usage(tool_name: str, **kwargs) -> None:
225
+ """Track tool usage."""
226
+ get_analytics().track_tool_usage(tool_name, **kwargs)
227
+
228
+
229
+ def track_error(error: Exception, context: Optional[Dict[str, Any]] = None) -> None:
230
+ """Track an error."""
231
+ get_analytics().track_error(error, context)
232
+
233
+
234
+ def with_analytics(tool_name: str):
235
+ """Decorator to track tool usage with analytics."""
236
+ def decorator(func: F) -> F:
237
+ @functools.wraps(func)
238
+ async def async_wrapper(*args, **kwargs):
239
+ start_time = time.time()
240
+ error = None
241
+ try:
242
+ result = await func(*args, **kwargs)
243
+ return result
244
+ except Exception as e:
245
+ error = e
246
+ track_error(e, {"tool": tool_name})
247
+ raise
248
+ finally:
249
+ duration_ms = (time.time() - start_time) * 1000
250
+ track_tool_usage(
251
+ tool_name,
252
+ duration_ms=duration_ms,
253
+ success=error is None,
254
+ error=str(error) if error else None
255
+ )
256
+
257
+ @functools.wraps(func)
258
+ def sync_wrapper(*args, **kwargs):
259
+ start_time = time.time()
260
+ error = None
261
+ try:
262
+ result = func(*args, **kwargs)
263
+ return result
264
+ except Exception as e:
265
+ error = e
266
+ track_error(e, {"tool": tool_name})
267
+ raise
268
+ finally:
269
+ duration_ms = (time.time() - start_time) * 1000
270
+ track_tool_usage(
271
+ tool_name,
272
+ duration_ms=duration_ms,
273
+ success=error is None,
274
+ error=str(error) if error else None
275
+ )
276
+
277
+ # Return appropriate wrapper based on function type
278
+ if asyncio.iscoroutinefunction(func):
279
+ return async_wrapper
280
+ else:
281
+ return sync_wrapper
282
+
283
+ return decorator
284
+
285
+
286
+ def feature_flag(flag_key: str, default: bool = False):
287
+ """Decorator to conditionally enable features based on flags."""
288
+ def decorator(func: F) -> F:
289
+ @functools.wraps(func)
290
+ def wrapper(*args, **kwargs):
291
+ if get_analytics().feature_enabled(flag_key, default):
292
+ return func(*args, **kwargs)
293
+ else:
294
+ raise NotImplementedError(f"Feature '{flag_key}' is not enabled")
295
+ return wrapper
296
+ return decorator
297
+
298
+
299
+ # Tool usage context manager
300
+ class ToolUsageTracker:
301
+ """Context manager for tracking tool usage."""
302
+
303
+ def __init__(self, tool_name: str, metadata: Optional[Dict[str, Any]] = None):
304
+ self.tool_name = tool_name
305
+ self.metadata = metadata or {}
306
+ self.start_time = None
307
+ self.error = None
308
+
309
+ def __enter__(self):
310
+ self.start_time = time.time()
311
+ return self
312
+
313
+ def __exit__(self, exc_type, exc_val, exc_tb):
314
+ duration_ms = (time.time() - self.start_time) * 1000
315
+ success = exc_type is None
316
+
317
+ if exc_type:
318
+ self.error = str(exc_val)
319
+ track_error(exc_val, {"tool": self.tool_name, **self.metadata})
320
+
321
+ track_tool_usage(
322
+ self.tool_name,
323
+ duration_ms=duration_ms,
324
+ success=success,
325
+ error=self.error,
326
+ metadata=self.metadata
327
+ )
328
+
329
+ # Don't suppress exceptions
330
+ return False
331
+
332
+
333
+ # Async context manager version
334
+ class AsyncToolUsageTracker:
335
+ """Async context manager for tracking tool usage."""
336
+
337
+ def __init__(self, tool_name: str, metadata: Optional[Dict[str, Any]] = None):
338
+ self.tool_name = tool_name
339
+ self.metadata = metadata or {}
340
+ self.start_time = None
341
+ self.error = None
342
+
343
+ async def __aenter__(self):
344
+ self.start_time = time.time()
345
+ return self
346
+
347
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
348
+ duration_ms = (time.time() - self.start_time) * 1000
349
+ success = exc_type is None
350
+
351
+ if exc_type:
352
+ self.error = str(exc_val)
353
+ track_error(exc_val, {"tool": self.tool_name, **self.metadata})
354
+
355
+ track_tool_usage(
356
+ self.tool_name,
357
+ duration_ms=duration_ms,
358
+ success=success,
359
+ error=self.error,
360
+ metadata=self.metadata
361
+ )
362
+
363
+ # Don't suppress exceptions
364
+ return False
hanzo_mcp/cli.py CHANGED
@@ -157,8 +157,8 @@ def main() -> None:
157
157
  "--port",
158
158
  dest="port",
159
159
  type=int,
160
- default=3000,
161
- help="Port for SSE server (default: 3000)",
160
+ default=8888,
161
+ help="Port for SSE server (default: 8888)",
162
162
  )
163
163
 
164
164
  _ = parser.add_argument(
@@ -324,7 +324,7 @@ def install_claude_desktop_config(
324
324
  disable_write_tools: bool = False,
325
325
  disable_search_tools: bool = False,
326
326
  host: str = "127.0.0.1",
327
- port: int = 3000,
327
+ port: int = 8888,
328
328
  ) -> None:
329
329
  """Install the server configuration in Claude Desktop.
330
330
 
hanzo_mcp/cli_enhanced.py CHANGED
@@ -59,8 +59,8 @@ Examples:
59
59
  server_group.add_argument(
60
60
  "--port",
61
61
  type=int,
62
- default=3000,
63
- help="Port for SSE server (default: 3000)",
62
+ default=8888,
63
+ help="Port for SSE server (default: 8888)",
64
64
  )
65
65
  server_group.add_argument(
66
66
  "--log-level",
@@ -242,7 +242,7 @@ def apply_cli_overrides(args: argparse.Namespace) -> Dict[str, Any]:
242
242
  server_config["name"] = args.name
243
243
  if hasattr(args, 'host') and args.host != "127.0.0.1":
244
244
  server_config["host"] = args.host
245
- if hasattr(args, 'port') and args.port != 3000:
245
+ if hasattr(args, 'port') and args.port != 8888:
246
246
  server_config["port"] = args.port
247
247
  if hasattr(args, 'transport') and args.transport != "stdio":
248
248
  server_config["transport"] = args.transport
@@ -71,7 +71,7 @@ class ServerConfig:
71
71
  """Configuration for the MCP server."""
72
72
  name: str = "hanzo-mcp"
73
73
  host: str = "127.0.0.1"
74
- port: int = 3000
74
+ port: int = 8888
75
75
  transport: str = "stdio" # stdio or sse
76
76
  log_level: str = "INFO"
77
77
  command_timeout: float = 120.0
@@ -71,11 +71,11 @@ TOOL_REGISTRY: Dict[str, ToolConfig] = {
71
71
  description="Fast content search using ripgrep or fallback Python implementation",
72
72
  cli_flag="--disable-grep"
73
73
  ),
74
- "grep_ast": ToolConfig(
75
- name="grep_ast",
74
+ "ast": ToolConfig(
75
+ name="ast",
76
76
  category=ToolCategory.FILESYSTEM,
77
77
  description="Search source code with AST context using tree-sitter",
78
- cli_flag="--disable-grep-ast"
78
+ cli_flag="--disable-ast"
79
79
  ),
80
80
  "content_replace": ToolConfig(
81
81
  name="content_replace",
@@ -120,7 +120,7 @@ TOOL_REGISTRY: Dict[str, ToolConfig] = {
120
120
  cli_flag="--disable-todo-write"
121
121
  ),
122
122
 
123
- # Agent Tools (1)
123
+ # Agent Tools (3)
124
124
  "dispatch_agent": ToolConfig(
125
125
  name="dispatch_agent",
126
126
  category=ToolCategory.AGENT,
@@ -128,6 +128,20 @@ TOOL_REGISTRY: Dict[str, ToolConfig] = {
128
128
  description="Delegate tasks to sub-agents for concurrent/specialized processing",
129
129
  cli_flag="--enable-dispatch-agent"
130
130
  ),
131
+ "swarm": ToolConfig(
132
+ name="swarm",
133
+ category=ToolCategory.AGENT,
134
+ enabled=False, # Disabled by default
135
+ description="Execute multiple agent tasks in parallel across different files",
136
+ cli_flag="--enable-swarm"
137
+ ),
138
+ "hierarchical_swarm": ToolConfig(
139
+ name="hierarchical_swarm",
140
+ category=ToolCategory.AGENT,
141
+ enabled=False, # Disabled by default
142
+ description="Execute hierarchical agent swarms with Claude Code integration",
143
+ cli_flag="--enable-hierarchical-swarm"
144
+ ),
131
145
 
132
146
  # Common Tools (3)
133
147
  "think": ToolConfig(
hanzo_mcp/server.py CHANGED
@@ -48,7 +48,7 @@ class HanzoMCPServer:
48
48
  disable_write_tools: bool = False,
49
49
  disable_search_tools: bool = False,
50
50
  host: str = "127.0.0.1",
51
- port: int = 3000,
51
+ port: int = 8888,
52
52
  enabled_tools: dict[str, bool] | None = None,
53
53
  disabled_tools: list[str] | None = None,
54
54
  ):
@@ -226,3 +226,36 @@ class HanzoMCPServer:
226
226
  # Run the server
227
227
  transport_type = cast(Literal["stdio", "sse"], transport)
228
228
  self.mcp.run(transport=transport_type)
229
+
230
+
231
+ def create_server(
232
+ name: str = "hanzo-mcp",
233
+ allowed_paths: list[str] | None = None,
234
+ enable_all_tools: bool = False,
235
+ **kwargs
236
+ ) -> HanzoMCPServer:
237
+ """Create a Hanzo MCP server instance.
238
+
239
+ Args:
240
+ name: Server name
241
+ allowed_paths: List of allowed file paths
242
+ enable_all_tools: Enable all tools including agent tools
243
+ **kwargs: Additional server configuration
244
+
245
+ Returns:
246
+ HanzoMCPServer instance
247
+ """
248
+ if enable_all_tools:
249
+ kwargs['enable_agent_tool'] = True
250
+
251
+ return HanzoMCPServer(
252
+ name=name,
253
+ allowed_paths=allowed_paths,
254
+ **kwargs
255
+ )
256
+
257
+
258
+ def main():
259
+ """Main entry point for the server."""
260
+ from hanzo_mcp.cli import main as cli_main
261
+ cli_main()
@@ -25,6 +25,14 @@ from hanzo_mcp.tools.shell import register_shell_tools
25
25
  from hanzo_mcp.tools.todo import register_todo_tools
26
26
  from hanzo_mcp.tools.vector import register_vector_tools
27
27
  from hanzo_mcp.tools.database import register_database_tools, DatabaseManager
28
+
29
+ # Try to import memory tools, but don't fail if hanzo-memory is not installed
30
+ try:
31
+ from hanzo_mcp.tools.memory import register_memory_tools
32
+ MEMORY_TOOLS_AVAILABLE = True
33
+ except ImportError:
34
+ MEMORY_TOOLS_AVAILABLE = False
35
+ register_memory_tools = None
28
36
  from hanzo_mcp.tools.mcp import MCPTool, McpAddTool, McpRemoveTool, McpStatsTool
29
37
  from hanzo_mcp.tools.editor import NeovimEditTool, NeovimCommandTool, NeovimSessionTool
30
38
  from hanzo_mcp.tools.llm import LLMTool, LLMTool, ConsensusTool, LLMManageTool, create_provider_tools
@@ -33,6 +41,13 @@ from hanzo_mcp.tools.common.mode_loader import ModeLoader
33
41
  from hanzo_mcp.tools.common.mode import activate_mode_from_env
34
42
  from hanzo_mcp.tools.common.plugin_loader import load_user_plugins
35
43
 
44
+ # Try to import LSP tool
45
+ try:
46
+ from hanzo_mcp.tools.lsp import LSPTool, create_lsp_tool
47
+ LSP_TOOL_AVAILABLE = True
48
+ except ImportError:
49
+ LSP_TOOL_AVAILABLE = False
50
+
36
51
 
37
52
  def register_all_tools(
38
53
  mcp_server: FastMCP,
@@ -122,6 +137,8 @@ def register_all_tools(
122
137
  "find_files": is_tool_enabled("find_files", True),
123
138
  "rules": is_tool_enabled("rules", True),
124
139
  "search": is_tool_enabled("search", not disable_search_tools),
140
+ "unified_search": is_tool_enabled("unified_search", True), # Primary search tool
141
+ "find": is_tool_enabled("find", True), # Fast file finder
125
142
  }
126
143
 
127
144
  # Vector tools setup (needed for search)
@@ -175,7 +192,9 @@ def register_all_tools(
175
192
 
176
193
  # Register agent tools if enabled
177
194
  agent_enabled = enable_agent_tool or is_tool_enabled("agent", False) or is_tool_enabled("dispatch_agent", False)
178
- if agent_enabled:
195
+ swarm_enabled = is_tool_enabled("swarm", False)
196
+
197
+ if agent_enabled or swarm_enabled:
179
198
  agent_tools = register_agent_tools(
180
199
  mcp_server,
181
200
  permission_manager,
@@ -186,8 +205,15 @@ def register_all_tools(
186
205
  agent_max_iterations=agent_max_iterations,
187
206
  agent_max_tool_uses=agent_max_tool_uses,
188
207
  )
208
+ # Filter based on what's enabled
189
209
  for tool in agent_tools:
190
- all_tools[tool.name] = tool
210
+ if tool.name == "agent" and agent_enabled:
211
+ all_tools[tool.name] = tool
212
+ elif tool.name == "swarm" and swarm_enabled:
213
+ all_tools[tool.name] = tool
214
+ elif tool.name in ["claude", "codex", "gemini", "grok", "code_auth"]:
215
+ # CLI tools and auth are always included when agent tools are enabled
216
+ all_tools[tool.name] = tool
191
217
 
192
218
  # Register todo tools if enabled
193
219
  todo_enabled = {
@@ -375,6 +401,43 @@ def register_all_tools(
375
401
  tool.register(mcp_server)
376
402
  all_tools[tool.name] = tool
377
403
 
404
+ # Register memory tools if enabled
405
+ memory_enabled = {
406
+ "recall_memories": is_tool_enabled("recall_memories", True),
407
+ "create_memories": is_tool_enabled("create_memories", True),
408
+ "update_memories": is_tool_enabled("update_memories", True),
409
+ "delete_memories": is_tool_enabled("delete_memories", True),
410
+ "manage_memories": is_tool_enabled("manage_memories", True),
411
+ "recall_facts": is_tool_enabled("recall_facts", True),
412
+ "store_facts": is_tool_enabled("store_facts", True),
413
+ "summarize_to_memory": is_tool_enabled("summarize_to_memory", True),
414
+ "manage_knowledge_bases": is_tool_enabled("manage_knowledge_bases", True),
415
+ }
416
+
417
+ if any(memory_enabled.values()) and MEMORY_TOOLS_AVAILABLE:
418
+ try:
419
+ memory_tools = register_memory_tools(
420
+ mcp_server,
421
+ permission_manager,
422
+ user_id="default",
423
+ project_id="default"
424
+ )
425
+ # Filter based on enabled state
426
+ for tool in memory_tools:
427
+ if memory_enabled.get(tool.name, True):
428
+ all_tools[tool.name] = tool
429
+ except Exception as e:
430
+ logger.warning(f"Failed to register memory tools: {e}")
431
+
432
+ # Register LSP tool if enabled
433
+ if is_tool_enabled("lsp", True) and LSP_TOOL_AVAILABLE:
434
+ try:
435
+ tool = create_lsp_tool()
436
+ tool.register(mcp_server)
437
+ all_tools[tool.name] = tool
438
+ except Exception as e:
439
+ logger.warning(f"Failed to register LSP tool: {e}")
440
+
378
441
  # Register user plugins last (so they can override built-in tools)
379
442
  for plugin_name, plugin in plugins.items():
380
443
  if is_tool_enabled(plugin_name, True):