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.
- hanzo_mcp/analytics/__init__.py +5 -0
- hanzo_mcp/analytics/posthog_analytics.py +364 -0
- hanzo_mcp/cli.py +3 -3
- hanzo_mcp/cli_enhanced.py +3 -3
- hanzo_mcp/config/settings.py +1 -1
- hanzo_mcp/config/tool_config.py +18 -4
- hanzo_mcp/server.py +34 -1
- hanzo_mcp/tools/__init__.py +65 -2
- hanzo_mcp/tools/agent/__init__.py +84 -3
- hanzo_mcp/tools/agent/agent_tool.py +102 -4
- hanzo_mcp/tools/agent/agent_tool_v2.py +459 -0
- hanzo_mcp/tools/agent/clarification_protocol.py +220 -0
- hanzo_mcp/tools/agent/clarification_tool.py +68 -0
- hanzo_mcp/tools/agent/claude_cli_tool.py +125 -0
- hanzo_mcp/tools/agent/claude_desktop_auth.py +508 -0
- hanzo_mcp/tools/agent/cli_agent_base.py +191 -0
- hanzo_mcp/tools/agent/code_auth.py +436 -0
- hanzo_mcp/tools/agent/code_auth_tool.py +194 -0
- hanzo_mcp/tools/agent/codex_cli_tool.py +123 -0
- hanzo_mcp/tools/agent/critic_tool.py +376 -0
- hanzo_mcp/tools/agent/gemini_cli_tool.py +128 -0
- hanzo_mcp/tools/agent/grok_cli_tool.py +128 -0
- hanzo_mcp/tools/agent/iching_tool.py +380 -0
- hanzo_mcp/tools/agent/network_tool.py +273 -0
- hanzo_mcp/tools/agent/prompt.py +62 -20
- hanzo_mcp/tools/agent/review_tool.py +433 -0
- hanzo_mcp/tools/agent/swarm_tool.py +535 -0
- hanzo_mcp/tools/agent/swarm_tool_v2.py +594 -0
- hanzo_mcp/tools/common/base.py +1 -0
- hanzo_mcp/tools/common/batch_tool.py +102 -10
- hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
- hanzo_mcp/tools/common/forgiving_edit.py +243 -0
- hanzo_mcp/tools/common/paginated_base.py +230 -0
- hanzo_mcp/tools/common/paginated_response.py +307 -0
- hanzo_mcp/tools/common/pagination.py +226 -0
- hanzo_mcp/tools/common/tool_list.py +3 -0
- hanzo_mcp/tools/common/truncate.py +101 -0
- hanzo_mcp/tools/filesystem/__init__.py +29 -0
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
- hanzo_mcp/tools/lsp/__init__.py +5 -0
- hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
- hanzo_mcp/tools/memory/__init__.py +76 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +518 -0
- hanzo_mcp/tools/memory/memory_tools.py +456 -0
- hanzo_mcp/tools/search/__init__.py +6 -0
- hanzo_mcp/tools/search/find_tool.py +581 -0
- hanzo_mcp/tools/search/unified_search.py +953 -0
- hanzo_mcp/tools/shell/__init__.py +5 -0
- hanzo_mcp/tools/shell/auto_background.py +203 -0
- hanzo_mcp/tools/shell/base_process.py +53 -27
- hanzo_mcp/tools/shell/bash_tool.py +17 -33
- hanzo_mcp/tools/shell/npx_tool.py +15 -32
- hanzo_mcp/tools/shell/streaming_command.py +594 -0
- hanzo_mcp/tools/shell/uvx_tool.py +15 -32
- hanzo_mcp/types.py +23 -0
- {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/METADATA +228 -71
- {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/RECORD +61 -24
- hanzo_mcp-0.6.13.dist-info/licenses/LICENSE +0 -21
- {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -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=
|
|
161
|
-
help="Port for SSE server (default:
|
|
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 =
|
|
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=
|
|
63
|
-
help="Port for SSE server (default:
|
|
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 !=
|
|
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
|
hanzo_mcp/config/settings.py
CHANGED
|
@@ -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 =
|
|
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
|
hanzo_mcp/config/tool_config.py
CHANGED
|
@@ -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
|
-
"
|
|
75
|
-
name="
|
|
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-
|
|
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 (
|
|
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 =
|
|
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()
|
hanzo_mcp/tools/__init__.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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):
|