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.

Files changed (117) hide show
  1. hanzo_mcp/__init__.py +2 -2
  2. hanzo_mcp/analytics/__init__.py +5 -0
  3. hanzo_mcp/analytics/posthog_analytics.py +364 -0
  4. hanzo_mcp/cli.py +5 -5
  5. hanzo_mcp/cli_enhanced.py +7 -7
  6. hanzo_mcp/cli_plugin.py +91 -0
  7. hanzo_mcp/config/__init__.py +1 -1
  8. hanzo_mcp/config/settings.py +70 -7
  9. hanzo_mcp/config/tool_config.py +20 -6
  10. hanzo_mcp/dev_server.py +3 -3
  11. hanzo_mcp/prompts/project_system.py +1 -1
  12. hanzo_mcp/server.py +40 -3
  13. hanzo_mcp/server_enhanced.py +69 -0
  14. hanzo_mcp/tools/__init__.py +140 -31
  15. hanzo_mcp/tools/agent/__init__.py +85 -4
  16. hanzo_mcp/tools/agent/agent_tool.py +104 -6
  17. hanzo_mcp/tools/agent/agent_tool_v2.py +459 -0
  18. hanzo_mcp/tools/agent/clarification_protocol.py +220 -0
  19. hanzo_mcp/tools/agent/clarification_tool.py +68 -0
  20. hanzo_mcp/tools/agent/claude_cli_tool.py +125 -0
  21. hanzo_mcp/tools/agent/claude_desktop_auth.py +508 -0
  22. hanzo_mcp/tools/agent/cli_agent_base.py +191 -0
  23. hanzo_mcp/tools/agent/code_auth.py +436 -0
  24. hanzo_mcp/tools/agent/code_auth_tool.py +194 -0
  25. hanzo_mcp/tools/agent/codex_cli_tool.py +123 -0
  26. hanzo_mcp/tools/agent/critic_tool.py +376 -0
  27. hanzo_mcp/tools/agent/gemini_cli_tool.py +128 -0
  28. hanzo_mcp/tools/agent/grok_cli_tool.py +128 -0
  29. hanzo_mcp/tools/agent/iching_tool.py +380 -0
  30. hanzo_mcp/tools/agent/network_tool.py +273 -0
  31. hanzo_mcp/tools/agent/prompt.py +62 -20
  32. hanzo_mcp/tools/agent/review_tool.py +433 -0
  33. hanzo_mcp/tools/agent/swarm_tool.py +535 -0
  34. hanzo_mcp/tools/agent/swarm_tool_v2.py +594 -0
  35. hanzo_mcp/tools/common/__init__.py +15 -1
  36. hanzo_mcp/tools/common/base.py +5 -4
  37. hanzo_mcp/tools/common/batch_tool.py +103 -11
  38. hanzo_mcp/tools/common/config_tool.py +2 -2
  39. hanzo_mcp/tools/common/context.py +2 -2
  40. hanzo_mcp/tools/common/context_fix.py +26 -0
  41. hanzo_mcp/tools/common/critic_tool.py +196 -0
  42. hanzo_mcp/tools/common/decorators.py +208 -0
  43. hanzo_mcp/tools/common/enhanced_base.py +106 -0
  44. hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
  45. hanzo_mcp/tools/common/forgiving_edit.py +243 -0
  46. hanzo_mcp/tools/common/mode.py +116 -0
  47. hanzo_mcp/tools/common/mode_loader.py +105 -0
  48. hanzo_mcp/tools/common/paginated_base.py +230 -0
  49. hanzo_mcp/tools/common/paginated_response.py +307 -0
  50. hanzo_mcp/tools/common/pagination.py +226 -0
  51. hanzo_mcp/tools/common/permissions.py +1 -1
  52. hanzo_mcp/tools/common/personality.py +936 -0
  53. hanzo_mcp/tools/common/plugin_loader.py +287 -0
  54. hanzo_mcp/tools/common/stats.py +4 -4
  55. hanzo_mcp/tools/common/tool_list.py +4 -1
  56. hanzo_mcp/tools/common/truncate.py +101 -0
  57. hanzo_mcp/tools/common/validation.py +1 -1
  58. hanzo_mcp/tools/config/__init__.py +3 -1
  59. hanzo_mcp/tools/config/config_tool.py +1 -1
  60. hanzo_mcp/tools/config/mode_tool.py +209 -0
  61. hanzo_mcp/tools/database/__init__.py +1 -1
  62. hanzo_mcp/tools/editor/__init__.py +1 -1
  63. hanzo_mcp/tools/filesystem/__init__.py +48 -14
  64. hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
  65. hanzo_mcp/tools/filesystem/batch_search.py +3 -3
  66. hanzo_mcp/tools/filesystem/diff.py +2 -2
  67. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
  68. hanzo_mcp/tools/filesystem/rules_tool.py +235 -0
  69. hanzo_mcp/tools/filesystem/{unified_search.py → search_tool.py} +12 -12
  70. hanzo_mcp/tools/filesystem/{symbols_unified.py → symbols_tool.py} +104 -5
  71. hanzo_mcp/tools/filesystem/watch.py +3 -2
  72. hanzo_mcp/tools/jupyter/__init__.py +2 -2
  73. hanzo_mcp/tools/jupyter/jupyter.py +1 -1
  74. hanzo_mcp/tools/llm/__init__.py +3 -3
  75. hanzo_mcp/tools/llm/llm_tool.py +648 -143
  76. hanzo_mcp/tools/lsp/__init__.py +5 -0
  77. hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
  78. hanzo_mcp/tools/mcp/__init__.py +2 -2
  79. hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
  80. hanzo_mcp/tools/memory/__init__.py +76 -0
  81. hanzo_mcp/tools/memory/knowledge_tools.py +518 -0
  82. hanzo_mcp/tools/memory/memory_tools.py +456 -0
  83. hanzo_mcp/tools/search/__init__.py +6 -0
  84. hanzo_mcp/tools/search/find_tool.py +581 -0
  85. hanzo_mcp/tools/search/unified_search.py +953 -0
  86. hanzo_mcp/tools/shell/__init__.py +11 -6
  87. hanzo_mcp/tools/shell/auto_background.py +203 -0
  88. hanzo_mcp/tools/shell/base_process.py +57 -29
  89. hanzo_mcp/tools/shell/bash_session_executor.py +1 -1
  90. hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +18 -34
  91. hanzo_mcp/tools/shell/command_executor.py +2 -2
  92. hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +16 -33
  93. hanzo_mcp/tools/shell/open.py +2 -2
  94. hanzo_mcp/tools/shell/{process_unified.py → process_tool.py} +1 -1
  95. hanzo_mcp/tools/shell/run_command_windows.py +1 -1
  96. hanzo_mcp/tools/shell/streaming_command.py +594 -0
  97. hanzo_mcp/tools/shell/uvx.py +47 -2
  98. hanzo_mcp/tools/shell/uvx_background.py +47 -2
  99. hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +16 -33
  100. hanzo_mcp/tools/todo/__init__.py +14 -19
  101. hanzo_mcp/tools/todo/todo.py +22 -1
  102. hanzo_mcp/tools/vector/__init__.py +1 -1
  103. hanzo_mcp/tools/vector/infinity_store.py +2 -2
  104. hanzo_mcp/tools/vector/project_manager.py +1 -1
  105. hanzo_mcp/types.py +23 -0
  106. hanzo_mcp-0.7.0.dist-info/METADATA +516 -0
  107. hanzo_mcp-0.7.0.dist-info/RECORD +180 -0
  108. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +1 -0
  109. hanzo_mcp/tools/common/palette.py +0 -344
  110. hanzo_mcp/tools/common/palette_loader.py +0 -108
  111. hanzo_mcp/tools/config/palette_tool.py +0 -179
  112. hanzo_mcp/tools/llm/llm_unified.py +0 -851
  113. hanzo_mcp-0.6.12.dist-info/METADATA +0 -339
  114. hanzo_mcp-0.6.12.dist-info/RECORD +0 -135
  115. hanzo_mcp-0.6.12.dist-info/licenses/LICENSE +0 -21
  116. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
  117. {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 MCP - Implementation of Hanzo capabilities using MCP."""
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"
12
+ __version__ = "0.6.13"
@@ -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
@@ -1,4 +1,4 @@
1
- """Command-line interface for the Hanzo MCP server."""
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 MCP server."""
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=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
@@ -1,4 +1,4 @@
1
- """Enhanced command-line interface for the Hanzo MCP server with full tool configuration."""
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 MCP server with comprehensive tool configuration",
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=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
@@ -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 MCP Tools Status:")
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 MCP server."""
374
+ """Run the enhanced CLI for the Hanzo AI server."""
375
375
  parser = create_parser()
376
376
  args = parser.parse_args()
377
377
 
@@ -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()
@@ -1,4 +1,4 @@
1
- """Configuration management for Hanzo MCP.
1
+ """Configuration management for Hanzo AI.
2
2
 
3
3
  This module provides a comprehensive configuration system that supports:
4
4
  - CLI arguments for individual tool control