hanzo-mcp 0.6.12__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/__init__.py +2 -2
- hanzo_mcp/analytics/__init__.py +5 -0
- hanzo_mcp/analytics/posthog_analytics.py +364 -0
- hanzo_mcp/cli.py +5 -5
- hanzo_mcp/cli_enhanced.py +7 -7
- hanzo_mcp/cli_plugin.py +91 -0
- hanzo_mcp/config/__init__.py +1 -1
- hanzo_mcp/config/settings.py +70 -7
- hanzo_mcp/config/tool_config.py +20 -6
- hanzo_mcp/dev_server.py +3 -3
- hanzo_mcp/prompts/project_system.py +1 -1
- hanzo_mcp/server.py +40 -3
- hanzo_mcp/server_enhanced.py +69 -0
- hanzo_mcp/tools/__init__.py +140 -31
- hanzo_mcp/tools/agent/__init__.py +85 -4
- hanzo_mcp/tools/agent/agent_tool.py +104 -6
- 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/__init__.py +15 -1
- hanzo_mcp/tools/common/base.py +5 -4
- hanzo_mcp/tools/common/batch_tool.py +103 -11
- hanzo_mcp/tools/common/config_tool.py +2 -2
- hanzo_mcp/tools/common/context.py +2 -2
- hanzo_mcp/tools/common/context_fix.py +26 -0
- hanzo_mcp/tools/common/critic_tool.py +196 -0
- hanzo_mcp/tools/common/decorators.py +208 -0
- hanzo_mcp/tools/common/enhanced_base.py +106 -0
- hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
- hanzo_mcp/tools/common/forgiving_edit.py +243 -0
- hanzo_mcp/tools/common/mode.py +116 -0
- hanzo_mcp/tools/common/mode_loader.py +105 -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/permissions.py +1 -1
- hanzo_mcp/tools/common/personality.py +936 -0
- hanzo_mcp/tools/common/plugin_loader.py +287 -0
- hanzo_mcp/tools/common/stats.py +4 -4
- hanzo_mcp/tools/common/tool_list.py +4 -1
- hanzo_mcp/tools/common/truncate.py +101 -0
- hanzo_mcp/tools/common/validation.py +1 -1
- hanzo_mcp/tools/config/__init__.py +3 -1
- hanzo_mcp/tools/config/config_tool.py +1 -1
- hanzo_mcp/tools/config/mode_tool.py +209 -0
- hanzo_mcp/tools/database/__init__.py +1 -1
- hanzo_mcp/tools/editor/__init__.py +1 -1
- hanzo_mcp/tools/filesystem/__init__.py +48 -14
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
- hanzo_mcp/tools/filesystem/batch_search.py +3 -3
- hanzo_mcp/tools/filesystem/diff.py +2 -2
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
- hanzo_mcp/tools/filesystem/rules_tool.py +235 -0
- hanzo_mcp/tools/filesystem/{unified_search.py → search_tool.py} +12 -12
- hanzo_mcp/tools/filesystem/{symbols_unified.py → symbols_tool.py} +104 -5
- hanzo_mcp/tools/filesystem/watch.py +3 -2
- hanzo_mcp/tools/jupyter/__init__.py +2 -2
- hanzo_mcp/tools/jupyter/jupyter.py +1 -1
- hanzo_mcp/tools/llm/__init__.py +3 -3
- hanzo_mcp/tools/llm/llm_tool.py +648 -143
- hanzo_mcp/tools/lsp/__init__.py +5 -0
- hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
- hanzo_mcp/tools/mcp/__init__.py +2 -2
- hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
- 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 +11 -6
- hanzo_mcp/tools/shell/auto_background.py +203 -0
- hanzo_mcp/tools/shell/base_process.py +57 -29
- hanzo_mcp/tools/shell/bash_session_executor.py +1 -1
- hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +18 -34
- hanzo_mcp/tools/shell/command_executor.py +2 -2
- hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +16 -33
- hanzo_mcp/tools/shell/open.py +2 -2
- hanzo_mcp/tools/shell/{process_unified.py → process_tool.py} +1 -1
- hanzo_mcp/tools/shell/run_command_windows.py +1 -1
- hanzo_mcp/tools/shell/streaming_command.py +594 -0
- hanzo_mcp/tools/shell/uvx.py +47 -2
- hanzo_mcp/tools/shell/uvx_background.py +47 -2
- hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +16 -33
- hanzo_mcp/tools/todo/__init__.py +14 -19
- hanzo_mcp/tools/todo/todo.py +22 -1
- hanzo_mcp/tools/vector/__init__.py +1 -1
- hanzo_mcp/tools/vector/infinity_store.py +2 -2
- hanzo_mcp/tools/vector/project_manager.py +1 -1
- hanzo_mcp/types.py +23 -0
- hanzo_mcp-0.7.0.dist-info/METADATA +516 -0
- hanzo_mcp-0.7.0.dist-info/RECORD +180 -0
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +1 -0
- hanzo_mcp/tools/common/palette.py +0 -344
- hanzo_mcp/tools/common/palette_loader.py +0 -108
- hanzo_mcp/tools/config/palette_tool.py +0 -179
- hanzo_mcp/tools/llm/llm_unified.py +0 -851
- hanzo_mcp-0.6.12.dist-info/METADATA +0 -339
- hanzo_mcp-0.6.12.dist-info/RECORD +0 -135
- hanzo_mcp-0.6.12.dist-info/licenses/LICENSE +0 -21
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/top_level.txt +0 -0
hanzo_mcp/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Hanzo
|
|
1
|
+
"""Hanzo AI - Implementation of Hanzo capabilities using MCP."""
|
|
2
2
|
|
|
3
3
|
# Configure FastMCP logging globally for stdio transport
|
|
4
4
|
import os
|
|
@@ -9,4 +9,4 @@ if os.environ.get("HANZO_MCP_TRANSPORT") == "stdio":
|
|
|
9
9
|
except ImportError:
|
|
10
10
|
pass
|
|
11
11
|
|
|
12
|
-
__version__ = "0.6.
|
|
12
|
+
__version__ = "0.6.13"
|
|
@@ -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Command-line interface for the Hanzo
|
|
1
|
+
"""Command-line interface for the Hanzo AI server."""
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import json
|
|
@@ -13,7 +13,7 @@ from hanzo_mcp.server import HanzoMCPServer
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def main() -> None:
|
|
16
|
-
"""Run the CLI for the Hanzo
|
|
16
|
+
"""Run the CLI for the Hanzo AI server."""
|
|
17
17
|
|
|
18
18
|
# Pre-parse arguments to check transport type early
|
|
19
19
|
import sys
|
|
@@ -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Enhanced command-line interface for the Hanzo
|
|
1
|
+
"""Enhanced command-line interface for the Hanzo AI server with full tool configuration."""
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import json
|
|
@@ -15,7 +15,7 @@ from hanzo_mcp.server import HanzoMCPServer
|
|
|
15
15
|
def create_parser() -> argparse.ArgumentParser:
|
|
16
16
|
"""Create the argument parser with all tool configuration options."""
|
|
17
17
|
parser = argparse.ArgumentParser(
|
|
18
|
-
description="Hanzo
|
|
18
|
+
description="Hanzo AI server with comprehensive tool configuration",
|
|
19
19
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
20
20
|
epilog="""
|
|
21
21
|
Tool Configuration:
|
|
@@ -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
|
|
@@ -346,7 +346,7 @@ def apply_cli_overrides(args: argparse.Namespace) -> Dict[str, Any]:
|
|
|
346
346
|
def list_tools(settings: HanzoMCPSettings) -> None:
|
|
347
347
|
"""List all tools and their current status."""
|
|
348
348
|
logger = logging.getLogger(__name__)
|
|
349
|
-
logger.info("Hanzo
|
|
349
|
+
logger.info("Hanzo AI Tools Status:")
|
|
350
350
|
logger.info("=" * 50)
|
|
351
351
|
|
|
352
352
|
categories = {}
|
|
@@ -371,7 +371,7 @@ def list_tools(settings: HanzoMCPSettings) -> None:
|
|
|
371
371
|
|
|
372
372
|
|
|
373
373
|
def main() -> None:
|
|
374
|
-
"""Run the enhanced CLI for the Hanzo
|
|
374
|
+
"""Run the enhanced CLI for the Hanzo AI server."""
|
|
375
375
|
parser = create_parser()
|
|
376
376
|
args = parser.parse_args()
|
|
377
377
|
|
hanzo_mcp/cli_plugin.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CLI for managing Hanzo MCP plugins."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from hanzo_mcp.tools.common.plugin_loader import create_plugin_template, list_plugin_tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
"""Main CLI entry point."""
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
description="Hanzo MCP Plugin Manager",
|
|
15
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
16
|
+
epilog="""
|
|
17
|
+
Examples:
|
|
18
|
+
# Create a new plugin template
|
|
19
|
+
hanzo-plugin create mytool
|
|
20
|
+
|
|
21
|
+
# List installed plugins
|
|
22
|
+
hanzo-plugin list
|
|
23
|
+
|
|
24
|
+
# Create plugin in specific directory
|
|
25
|
+
hanzo-plugin create mytool --output /path/to/plugins
|
|
26
|
+
"""
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
30
|
+
|
|
31
|
+
# Create command
|
|
32
|
+
create_parser = subparsers.add_parser("create", help="Create a new plugin template")
|
|
33
|
+
create_parser.add_argument("name", help="Name of the tool (e.g., 'mytool')")
|
|
34
|
+
create_parser.add_argument(
|
|
35
|
+
"--output", "-o",
|
|
36
|
+
type=Path,
|
|
37
|
+
default=Path.home() / ".hanzo" / "plugins",
|
|
38
|
+
help="Output directory for the plugin (default: ~/.hanzo/plugins)"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# List command
|
|
42
|
+
list_parser = subparsers.add_parser("list", help="List installed plugins")
|
|
43
|
+
|
|
44
|
+
args = parser.parse_args()
|
|
45
|
+
|
|
46
|
+
if args.command == "create":
|
|
47
|
+
# Create plugin template
|
|
48
|
+
output_dir = args.output / args.name
|
|
49
|
+
try:
|
|
50
|
+
create_plugin_template(output_dir, args.name)
|
|
51
|
+
print(f"\n✅ Plugin template created successfully!")
|
|
52
|
+
print(f"\nTo use your plugin:")
|
|
53
|
+
print(f"1. Edit the tool implementation in {output_dir / f'{args.name}_tool.py'}")
|
|
54
|
+
print(f"2. Restart Hanzo MCP to load the plugin")
|
|
55
|
+
print(f"3. Add '{args.name}' to your mode's tool list")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"❌ Error creating plugin: {e}", file=sys.stderr)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
elif args.command == "list":
|
|
61
|
+
# List installed plugins
|
|
62
|
+
try:
|
|
63
|
+
from hanzo_mcp.tools.common.plugin_loader import load_user_plugins
|
|
64
|
+
plugins = load_user_plugins()
|
|
65
|
+
|
|
66
|
+
if not plugins:
|
|
67
|
+
print("No plugins installed.")
|
|
68
|
+
print("\nPlugin directories:")
|
|
69
|
+
print(" ~/.hanzo/plugins/")
|
|
70
|
+
print(" ./.hanzo/plugins/")
|
|
71
|
+
print(" $HANZO_PLUGIN_PATH")
|
|
72
|
+
else:
|
|
73
|
+
print(f"Installed plugins ({len(plugins)}):")
|
|
74
|
+
for name, plugin in plugins.items():
|
|
75
|
+
print(f"\n {name}:")
|
|
76
|
+
print(f" Source: {plugin.source_path}")
|
|
77
|
+
if plugin.metadata:
|
|
78
|
+
print(f" Version: {plugin.metadata.get('version', 'unknown')}")
|
|
79
|
+
print(f" Author: {plugin.metadata.get('author', 'unknown')}")
|
|
80
|
+
print(f" Description: {plugin.metadata.get('description', '')}")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"❌ Error listing plugins: {e}", file=sys.stderr)
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
85
|
+
else:
|
|
86
|
+
parser.print_help()
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
hanzo_mcp/config/__init__.py
CHANGED