hanzo-mcp 0.3.8__py3-none-any.whl → 0.5.1__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 (93) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/cli.py +118 -170
  3. hanzo_mcp/cli_enhanced.py +438 -0
  4. hanzo_mcp/config/__init__.py +19 -0
  5. hanzo_mcp/config/settings.py +449 -0
  6. hanzo_mcp/config/tool_config.py +197 -0
  7. hanzo_mcp/prompts/__init__.py +117 -0
  8. hanzo_mcp/prompts/compact_conversation.py +77 -0
  9. hanzo_mcp/prompts/create_release.py +38 -0
  10. hanzo_mcp/prompts/project_system.py +120 -0
  11. hanzo_mcp/prompts/project_todo_reminder.py +111 -0
  12. hanzo_mcp/prompts/utils.py +286 -0
  13. hanzo_mcp/server.py +117 -99
  14. hanzo_mcp/tools/__init__.py +121 -33
  15. hanzo_mcp/tools/agent/__init__.py +8 -11
  16. hanzo_mcp/tools/agent/agent_tool.py +290 -224
  17. hanzo_mcp/tools/agent/prompt.py +16 -13
  18. hanzo_mcp/tools/agent/tool_adapter.py +9 -9
  19. hanzo_mcp/tools/common/__init__.py +17 -16
  20. hanzo_mcp/tools/common/base.py +79 -110
  21. hanzo_mcp/tools/common/batch_tool.py +330 -0
  22. hanzo_mcp/tools/common/config_tool.py +396 -0
  23. hanzo_mcp/tools/common/context.py +26 -292
  24. hanzo_mcp/tools/common/permissions.py +12 -12
  25. hanzo_mcp/tools/common/thinking_tool.py +153 -0
  26. hanzo_mcp/tools/common/validation.py +1 -63
  27. hanzo_mcp/tools/filesystem/__init__.py +97 -57
  28. hanzo_mcp/tools/filesystem/base.py +32 -24
  29. hanzo_mcp/tools/filesystem/content_replace.py +114 -107
  30. hanzo_mcp/tools/filesystem/directory_tree.py +129 -105
  31. hanzo_mcp/tools/filesystem/edit.py +279 -0
  32. hanzo_mcp/tools/filesystem/grep.py +458 -0
  33. hanzo_mcp/tools/filesystem/grep_ast_tool.py +250 -0
  34. hanzo_mcp/tools/filesystem/multi_edit.py +362 -0
  35. hanzo_mcp/tools/filesystem/read.py +255 -0
  36. hanzo_mcp/tools/filesystem/unified_search.py +689 -0
  37. hanzo_mcp/tools/filesystem/write.py +156 -0
  38. hanzo_mcp/tools/jupyter/__init__.py +41 -29
  39. hanzo_mcp/tools/jupyter/base.py +66 -57
  40. hanzo_mcp/tools/jupyter/{edit_notebook.py → notebook_edit.py} +162 -139
  41. hanzo_mcp/tools/jupyter/notebook_read.py +152 -0
  42. hanzo_mcp/tools/shell/__init__.py +29 -20
  43. hanzo_mcp/tools/shell/base.py +87 -45
  44. hanzo_mcp/tools/shell/bash_session.py +731 -0
  45. hanzo_mcp/tools/shell/bash_session_executor.py +295 -0
  46. hanzo_mcp/tools/shell/command_executor.py +435 -384
  47. hanzo_mcp/tools/shell/run_command.py +284 -131
  48. hanzo_mcp/tools/shell/run_command_windows.py +328 -0
  49. hanzo_mcp/tools/shell/session_manager.py +196 -0
  50. hanzo_mcp/tools/shell/session_storage.py +325 -0
  51. hanzo_mcp/tools/todo/__init__.py +66 -0
  52. hanzo_mcp/tools/todo/base.py +319 -0
  53. hanzo_mcp/tools/todo/todo_read.py +148 -0
  54. hanzo_mcp/tools/todo/todo_write.py +378 -0
  55. hanzo_mcp/tools/vector/__init__.py +99 -0
  56. hanzo_mcp/tools/vector/ast_analyzer.py +459 -0
  57. hanzo_mcp/tools/vector/git_ingester.py +482 -0
  58. hanzo_mcp/tools/vector/infinity_store.py +731 -0
  59. hanzo_mcp/tools/vector/mock_infinity.py +162 -0
  60. hanzo_mcp/tools/vector/project_manager.py +361 -0
  61. hanzo_mcp/tools/vector/vector_index.py +116 -0
  62. hanzo_mcp/tools/vector/vector_search.py +225 -0
  63. hanzo_mcp-0.5.1.dist-info/METADATA +276 -0
  64. hanzo_mcp-0.5.1.dist-info/RECORD +68 -0
  65. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.1.dist-info}/WHEEL +1 -1
  66. hanzo_mcp/tools/agent/base_provider.py +0 -73
  67. hanzo_mcp/tools/agent/litellm_provider.py +0 -45
  68. hanzo_mcp/tools/agent/lmstudio_agent.py +0 -385
  69. hanzo_mcp/tools/agent/lmstudio_provider.py +0 -219
  70. hanzo_mcp/tools/agent/provider_registry.py +0 -120
  71. hanzo_mcp/tools/common/error_handling.py +0 -86
  72. hanzo_mcp/tools/common/logging_config.py +0 -115
  73. hanzo_mcp/tools/common/session.py +0 -91
  74. hanzo_mcp/tools/common/think_tool.py +0 -123
  75. hanzo_mcp/tools/common/version_tool.py +0 -120
  76. hanzo_mcp/tools/filesystem/edit_file.py +0 -287
  77. hanzo_mcp/tools/filesystem/get_file_info.py +0 -170
  78. hanzo_mcp/tools/filesystem/read_files.py +0 -199
  79. hanzo_mcp/tools/filesystem/search_content.py +0 -275
  80. hanzo_mcp/tools/filesystem/write_file.py +0 -162
  81. hanzo_mcp/tools/jupyter/notebook_operations.py +0 -514
  82. hanzo_mcp/tools/jupyter/read_notebook.py +0 -165
  83. hanzo_mcp/tools/project/__init__.py +0 -64
  84. hanzo_mcp/tools/project/analysis.py +0 -886
  85. hanzo_mcp/tools/project/base.py +0 -66
  86. hanzo_mcp/tools/project/project_analyze.py +0 -173
  87. hanzo_mcp/tools/shell/run_script.py +0 -215
  88. hanzo_mcp/tools/shell/script_tool.py +0 -244
  89. hanzo_mcp-0.3.8.dist-info/METADATA +0 -196
  90. hanzo_mcp-0.3.8.dist-info/RECORD +0 -53
  91. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.1.dist-info}/entry_points.txt +0 -0
  92. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.1.dist-info}/licenses/LICENSE +0 -0
  93. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,449 @@
1
+ """Settings management for Hanzo MCP.
2
+
3
+ Handles loading and saving configuration from multiple sources:
4
+ 1. Default settings
5
+ 2. Global config file (~/.config/hanzo/mcp-settings.json)
6
+ 3. Project-specific config file
7
+ 4. Environment variables
8
+ 5. CLI arguments (highest priority)
9
+ """
10
+
11
+ import json
12
+ import os
13
+ from pathlib import Path
14
+ from typing import Any, Dict, List, Optional, Union
15
+ from dataclasses import dataclass, asdict, field
16
+
17
+ from .tool_config import TOOL_REGISTRY, ToolCategory
18
+
19
+
20
+ @dataclass
21
+ class MCPServerConfig:
22
+ """Configuration for an external MCP server."""
23
+ name: str
24
+ command: str
25
+ args: List[str] = field(default_factory=list)
26
+ enabled: bool = True
27
+ trusted: bool = False # Whether this server is trusted
28
+ description: str = ""
29
+ capabilities: List[str] = field(default_factory=list) # tools, prompts, resources
30
+
31
+
32
+ @dataclass
33
+ class VectorStoreConfig:
34
+ """Configuration for the vector store."""
35
+ enabled: bool = False
36
+ provider: str = "infinity" # infinity, chroma, etc.
37
+ data_path: Optional[str] = None # Will default to ~/.config/hanzo/vector-store
38
+ embedding_model: str = "text-embedding-3-small"
39
+ chunk_size: int = 1000
40
+ chunk_overlap: int = 200
41
+
42
+
43
+ @dataclass
44
+ class ProjectConfig:
45
+ """Configuration specific to a project."""
46
+ name: str
47
+ root_path: str
48
+ rules: List[str] = field(default_factory=list)
49
+ workflows: Dict[str, Any] = field(default_factory=dict)
50
+ tasks: List[Dict[str, Any]] = field(default_factory=list)
51
+ enabled_tools: Dict[str, bool] = field(default_factory=dict)
52
+ disabled_tools: List[str] = field(default_factory=list)
53
+ mcp_servers: List[str] = field(default_factory=list) # Names of enabled MCP servers for this project
54
+
55
+
56
+ @dataclass
57
+ class AgentConfig:
58
+ """Configuration for agent tools."""
59
+ enabled: bool = False
60
+ model: Optional[str] = None
61
+ api_key: Optional[str] = None
62
+ base_url: Optional[str] = None
63
+ max_tokens: Optional[int] = None
64
+ max_iterations: int = 10
65
+ max_tool_uses: int = 30
66
+
67
+
68
+ @dataclass
69
+ class ServerConfig:
70
+ """Configuration for the MCP server."""
71
+ name: str = "hanzo-mcp"
72
+ host: str = "127.0.0.1"
73
+ port: int = 3000
74
+ transport: str = "stdio" # stdio or sse
75
+ log_level: str = "INFO"
76
+ command_timeout: float = 120.0
77
+
78
+
79
+ @dataclass
80
+ class HanzoMCPSettings:
81
+ """Complete configuration for Hanzo MCP."""
82
+ # Server settings
83
+ server: ServerConfig = field(default_factory=ServerConfig)
84
+
85
+ # Paths and permissions
86
+ allowed_paths: List[str] = field(default_factory=list)
87
+ project_paths: List[str] = field(default_factory=list)
88
+ project_dir: Optional[str] = None
89
+
90
+ # Tool configuration
91
+ enabled_tools: Dict[str, bool] = field(default_factory=dict)
92
+ disabled_tools: List[str] = field(default_factory=list)
93
+
94
+ # Agent configuration
95
+ agent: AgentConfig = field(default_factory=AgentConfig)
96
+
97
+ # Vector store configuration
98
+ vector_store: VectorStoreConfig = field(default_factory=VectorStoreConfig)
99
+
100
+ # MCP Hub configuration
101
+ mcp_servers: Dict[str, MCPServerConfig] = field(default_factory=dict)
102
+ hub_enabled: bool = False
103
+ trusted_servers: List[str] = field(default_factory=list) # Whitelist of trusted server names
104
+
105
+ # Project-specific configurations
106
+ projects: Dict[str, ProjectConfig] = field(default_factory=dict)
107
+ current_project: Optional[str] = None
108
+
109
+ def __post_init__(self):
110
+ """Initialize default tool states if not specified."""
111
+ if not self.enabled_tools:
112
+ self.enabled_tools = {name: config.enabled for name, config in TOOL_REGISTRY.items()}
113
+
114
+ # Apply disabled_tools list to enabled_tools dict
115
+ for tool_name in self.disabled_tools:
116
+ if tool_name in TOOL_REGISTRY:
117
+ self.enabled_tools[tool_name] = False
118
+
119
+ def is_tool_enabled(self, tool_name: str) -> bool:
120
+ """Check if a specific tool is enabled."""
121
+ # Check disabled_tools list first (highest priority)
122
+ if tool_name in self.disabled_tools:
123
+ return False
124
+ return self.enabled_tools.get(tool_name, TOOL_REGISTRY.get(tool_name, type('obj', (object,), {'enabled': False})).enabled)
125
+
126
+ def enable_tool(self, tool_name: str) -> bool:
127
+ """Enable a specific tool."""
128
+ if tool_name in TOOL_REGISTRY:
129
+ self.enabled_tools[tool_name] = True
130
+ # Remove from disabled list if present
131
+ if tool_name in self.disabled_tools:
132
+ self.disabled_tools.remove(tool_name)
133
+ return True
134
+ return False
135
+
136
+ def disable_tool(self, tool_name: str) -> bool:
137
+ """Disable a specific tool."""
138
+ if tool_name in TOOL_REGISTRY:
139
+ self.enabled_tools[tool_name] = False
140
+ # Add to disabled list if not present
141
+ if tool_name not in self.disabled_tools:
142
+ self.disabled_tools.append(tool_name)
143
+ return True
144
+ return False
145
+
146
+ def get_enabled_tools(self) -> List[str]:
147
+ """Get list of enabled tool names."""
148
+ return [name for name in TOOL_REGISTRY.keys() if self.is_tool_enabled(name)]
149
+
150
+ def get_disabled_tools(self) -> List[str]:
151
+ """Get list of disabled tool names."""
152
+ return [name for name in TOOL_REGISTRY.keys() if not self.is_tool_enabled(name)]
153
+
154
+ # MCP Server Management
155
+ def add_mcp_server(self, server_config: MCPServerConfig) -> bool:
156
+ """Add a new MCP server configuration."""
157
+ if server_config.name in self.mcp_servers:
158
+ return False # Server already exists
159
+ self.mcp_servers[server_config.name] = server_config
160
+ return True
161
+
162
+ def remove_mcp_server(self, server_name: str) -> bool:
163
+ """Remove an MCP server configuration."""
164
+ if server_name in self.mcp_servers:
165
+ del self.mcp_servers[server_name]
166
+ # Remove from trusted list if present
167
+ if server_name in self.trusted_servers:
168
+ self.trusted_servers.remove(server_name)
169
+ return True
170
+ return False
171
+
172
+ def enable_mcp_server(self, server_name: str) -> bool:
173
+ """Enable an MCP server."""
174
+ if server_name in self.mcp_servers:
175
+ self.mcp_servers[server_name].enabled = True
176
+ return True
177
+ return False
178
+
179
+ def disable_mcp_server(self, server_name: str) -> bool:
180
+ """Disable an MCP server."""
181
+ if server_name in self.mcp_servers:
182
+ self.mcp_servers[server_name].enabled = False
183
+ return True
184
+ return False
185
+
186
+ def trust_mcp_server(self, server_name: str) -> bool:
187
+ """Add server to trusted list."""
188
+ if server_name in self.mcp_servers:
189
+ if server_name not in self.trusted_servers:
190
+ self.trusted_servers.append(server_name)
191
+ self.mcp_servers[server_name].trusted = True
192
+ return True
193
+ return False
194
+
195
+ def get_enabled_mcp_servers(self) -> List[MCPServerConfig]:
196
+ """Get list of enabled MCP servers."""
197
+ return [server for server in self.mcp_servers.values() if server.enabled]
198
+
199
+ def get_trusted_mcp_servers(self) -> List[MCPServerConfig]:
200
+ """Get list of trusted MCP servers."""
201
+ return [server for server in self.mcp_servers.values() if server.trusted]
202
+
203
+ # Project Management
204
+ def add_project(self, project_config: ProjectConfig) -> bool:
205
+ """Add a project configuration."""
206
+ if project_config.name in self.projects:
207
+ return False # Project already exists
208
+ self.projects[project_config.name] = project_config
209
+ return True
210
+
211
+ def remove_project(self, project_name: str) -> bool:
212
+ """Remove a project configuration."""
213
+ if project_name in self.projects:
214
+ del self.projects[project_name]
215
+ if self.current_project == project_name:
216
+ self.current_project = None
217
+ return True
218
+ return False
219
+
220
+ def set_current_project(self, project_name: str) -> bool:
221
+ """Set the current active project."""
222
+ if project_name in self.projects:
223
+ self.current_project = project_name
224
+ return True
225
+ return False
226
+
227
+ def get_current_project(self) -> Optional[ProjectConfig]:
228
+ """Get the current project configuration."""
229
+ if self.current_project:
230
+ return self.projects.get(self.current_project)
231
+ return None
232
+
233
+ def get_project_tools(self, project_name: Optional[str] = None) -> Dict[str, bool]:
234
+ """Get tool configuration for a specific project."""
235
+ if not project_name:
236
+ project_name = self.current_project
237
+
238
+ if project_name and project_name in self.projects:
239
+ project = self.projects[project_name]
240
+ # Start with global tool settings
241
+ tools = self.enabled_tools.copy()
242
+ # Apply project-specific settings
243
+ tools.update(project.enabled_tools)
244
+ # Apply project-specific disabled tools
245
+ for tool_name in project.disabled_tools:
246
+ tools[tool_name] = False
247
+ return tools
248
+
249
+ return self.enabled_tools
250
+
251
+
252
+ def get_config_dir() -> Path:
253
+ """Get the configuration directory for Hanzo MCP."""
254
+ if os.name == "nt": # Windows
255
+ config_dir = Path(os.environ.get("APPDATA", "")) / "hanzo"
256
+ else: # Unix/macOS
257
+ config_dir = Path.home() / ".config" / "hanzo"
258
+
259
+ config_dir.mkdir(parents=True, exist_ok=True)
260
+ return config_dir
261
+
262
+
263
+ def get_global_config_path() -> Path:
264
+ """Get the path to the global configuration file."""
265
+ return get_config_dir() / "mcp-settings.json"
266
+
267
+
268
+ def get_project_config_path(project_dir: Optional[str] = None) -> Optional[Path]:
269
+ """Get the path to the project-specific configuration file."""
270
+ if project_dir:
271
+ project_path = Path(project_dir)
272
+ config_candidates = [
273
+ project_path / ".hanzo" / "mcp-settings.json",
274
+ project_path / "hanzo-mcp.json",
275
+ project_path / ".hanzo-mcp.json",
276
+ ]
277
+ for config_path in config_candidates:
278
+ if config_path.exists():
279
+ return config_path
280
+ return None
281
+
282
+
283
+ def ensure_project_hanzo_dir(project_dir: str) -> Path:
284
+ """Ensure .hanzo directory exists in project and return its path."""
285
+ project_path = Path(project_dir)
286
+ hanzo_dir = project_path / ".hanzo"
287
+ hanzo_dir.mkdir(exist_ok=True)
288
+
289
+ # Create default structure
290
+ (hanzo_dir / "db").mkdir(exist_ok=True) # Vector database
291
+
292
+ # Create default project config if it doesn't exist
293
+ config_path = hanzo_dir / "mcp-settings.json"
294
+ if not config_path.exists():
295
+ default_project_config = {
296
+ "name": project_path.name,
297
+ "root_path": str(project_path),
298
+ "rules": [
299
+ "Follow project-specific coding standards",
300
+ "Test all changes before committing",
301
+ "Update documentation for new features"
302
+ ],
303
+ "workflows": {
304
+ "development": {
305
+ "steps": ["edit", "test", "commit"],
306
+ "tools": ["read", "write", "edit", "run_command"]
307
+ },
308
+ "documentation": {
309
+ "steps": ["read", "analyze", "write"],
310
+ "tools": ["read", "write", "vector_search"]
311
+ }
312
+ },
313
+ "tasks": [],
314
+ "enabled_tools": {},
315
+ "disabled_tools": [],
316
+ "mcp_servers": []
317
+ }
318
+
319
+ with open(config_path, 'w') as f:
320
+ json.dump(default_project_config, f, indent=2)
321
+
322
+ return hanzo_dir
323
+
324
+
325
+ def detect_project_from_path(file_path: str) -> Optional[Dict[str, str]]:
326
+ """Detect project information from a file path by looking for LLM.md."""
327
+ path = Path(file_path).resolve()
328
+ current_path = path.parent if path.is_file() else path
329
+
330
+ while current_path != current_path.parent: # Stop at filesystem root
331
+ llm_md_path = current_path / "LLM.md"
332
+ if llm_md_path.exists():
333
+ return {
334
+ "name": current_path.name,
335
+ "root_path": str(current_path),
336
+ "llm_md_path": str(llm_md_path),
337
+ "hanzo_dir": str(ensure_project_hanzo_dir(str(current_path)))
338
+ }
339
+ current_path = current_path.parent
340
+
341
+ return None
342
+
343
+
344
+ def load_settings(
345
+ project_dir: Optional[str] = None,
346
+ config_overrides: Optional[Dict[str, Any]] = None
347
+ ) -> HanzoMCPSettings:
348
+ """Load settings from all sources in priority order.
349
+
350
+ Priority (highest to lowest):
351
+ 1. config_overrides (usually from CLI)
352
+ 2. Project-specific config file
353
+ 3. Global config file
354
+ 4. Defaults
355
+ """
356
+ # Start with defaults
357
+ settings = HanzoMCPSettings()
358
+
359
+ # Load global config
360
+ global_config_path = get_global_config_path()
361
+ if global_config_path.exists():
362
+ try:
363
+ with open(global_config_path) as f:
364
+ global_config = json.load(f)
365
+ settings = _merge_config(settings, global_config)
366
+ except Exception as e:
367
+ print(f"Warning: Failed to load global config: {e}")
368
+
369
+ # Load project config
370
+ project_config_path = get_project_config_path(project_dir)
371
+ if project_config_path:
372
+ try:
373
+ with open(project_config_path) as f:
374
+ project_config = json.load(f)
375
+ settings = _merge_config(settings, project_config)
376
+ except Exception as e:
377
+ print(f"Warning: Failed to load project config: {e}")
378
+
379
+ # Apply CLI overrides
380
+ if config_overrides:
381
+ settings = _merge_config(settings, config_overrides)
382
+
383
+ return settings
384
+
385
+
386
+ def save_settings(settings: HanzoMCPSettings, global_config: bool = True) -> Path:
387
+ """Save settings to configuration file.
388
+
389
+ Args:
390
+ settings: Settings to save
391
+ global_config: If True, save to global config, otherwise save to project config
392
+
393
+ Returns:
394
+ Path where settings were saved
395
+ """
396
+ if global_config:
397
+ config_path = get_global_config_path()
398
+ else:
399
+ # Save to current directory project config
400
+ config_path = Path.cwd() / ".hanzo-mcp.json"
401
+
402
+ config_path.parent.mkdir(parents=True, exist_ok=True)
403
+
404
+ with open(config_path, 'w') as f:
405
+ json.dump(asdict(settings), f, indent=2)
406
+
407
+ return config_path
408
+
409
+
410
+ def _merge_config(base_settings: HanzoMCPSettings, config_dict: Dict[str, Any]) -> HanzoMCPSettings:
411
+ """Merge configuration dictionary into settings object."""
412
+ # Convert to dict, merge, then convert back
413
+ base_dict = asdict(base_settings)
414
+
415
+ def deep_merge(base: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]:
416
+ """Deep merge two dictionaries."""
417
+ for key, value in update.items():
418
+ if key in base and isinstance(base[key], dict) and isinstance(value, dict):
419
+ base[key] = deep_merge(base[key], value)
420
+ else:
421
+ base[key] = value
422
+ return base
423
+
424
+ merged = deep_merge(base_dict, config_dict)
425
+
426
+ # Reconstruct the settings object
427
+ mcp_servers = {}
428
+ for name, server_data in merged.get("mcp_servers", {}).items():
429
+ mcp_servers[name] = MCPServerConfig(**server_data)
430
+
431
+ projects = {}
432
+ for name, project_data in merged.get("projects", {}).items():
433
+ projects[name] = ProjectConfig(**project_data)
434
+
435
+ return HanzoMCPSettings(
436
+ server=ServerConfig(**merged.get("server", {})),
437
+ allowed_paths=merged.get("allowed_paths", []),
438
+ project_paths=merged.get("project_paths", []),
439
+ project_dir=merged.get("project_dir"),
440
+ enabled_tools=merged.get("enabled_tools", {}),
441
+ disabled_tools=merged.get("disabled_tools", []),
442
+ agent=AgentConfig(**merged.get("agent", {})),
443
+ vector_store=VectorStoreConfig(**merged.get("vector_store", {})),
444
+ mcp_servers=mcp_servers,
445
+ hub_enabled=merged.get("hub_enabled", False),
446
+ trusted_servers=merged.get("trusted_servers", []),
447
+ projects=projects,
448
+ current_project=merged.get("current_project"),
449
+ )
@@ -0,0 +1,197 @@
1
+ """Tool configuration definitions for Hanzo MCP."""
2
+
3
+ from enum import Enum
4
+ from typing import Dict, List, Optional
5
+ from dataclasses import dataclass
6
+
7
+
8
+ class ToolCategory(str, Enum):
9
+ """Categories of tools available in Hanzo MCP."""
10
+ FILESYSTEM = "filesystem"
11
+ SHELL = "shell"
12
+ JUPYTER = "jupyter"
13
+ TODO = "todo"
14
+ AGENT = "agent"
15
+ COMMON = "common"
16
+ VECTOR = "vector"
17
+
18
+
19
+ @dataclass
20
+ class ToolConfig:
21
+ """Configuration for an individual tool."""
22
+ name: str
23
+ category: ToolCategory
24
+ enabled: bool = True
25
+ description: str = ""
26
+ requires_permissions: bool = True
27
+ cli_flag: str = ""
28
+
29
+ def __post_init__(self):
30
+ """Generate CLI flag if not provided."""
31
+ if not self.cli_flag:
32
+ self.cli_flag = f"--{'enable' if self.enabled else 'disable'}-{self.name.replace('_', '-')}"
33
+
34
+
35
+ # Complete tool registry with all 17 tools
36
+ TOOL_REGISTRY: Dict[str, ToolConfig] = {
37
+ # Filesystem Tools (8)
38
+ "read": ToolConfig(
39
+ name="read",
40
+ category=ToolCategory.FILESYSTEM,
41
+ description="Read file contents with line numbers and truncation support",
42
+ cli_flag="--disable-read"
43
+ ),
44
+ "write": ToolConfig(
45
+ name="write",
46
+ category=ToolCategory.FILESYSTEM,
47
+ description="Write content to files, create new files or overwrite existing ones",
48
+ cli_flag="--disable-write"
49
+ ),
50
+ "edit": ToolConfig(
51
+ name="edit",
52
+ category=ToolCategory.FILESYSTEM,
53
+ description="Make precise string replacements in files with validation",
54
+ cli_flag="--disable-edit"
55
+ ),
56
+ "multi_edit": ToolConfig(
57
+ name="multi_edit",
58
+ category=ToolCategory.FILESYSTEM,
59
+ description="Perform multiple edits to a single file in one operation",
60
+ cli_flag="--disable-multi-edit"
61
+ ),
62
+ "directory_tree": ToolConfig(
63
+ name="directory_tree",
64
+ category=ToolCategory.FILESYSTEM,
65
+ description="Display directory structure as a tree",
66
+ cli_flag="--disable-directory-tree"
67
+ ),
68
+ "grep": ToolConfig(
69
+ name="grep",
70
+ category=ToolCategory.FILESYSTEM,
71
+ description="Fast content search using ripgrep or fallback Python implementation",
72
+ cli_flag="--disable-grep"
73
+ ),
74
+ "grep_ast": ToolConfig(
75
+ name="grep_ast",
76
+ category=ToolCategory.FILESYSTEM,
77
+ description="Search source code with AST context using tree-sitter",
78
+ cli_flag="--disable-grep-ast"
79
+ ),
80
+ "content_replace": ToolConfig(
81
+ name="content_replace",
82
+ category=ToolCategory.FILESYSTEM,
83
+ description="Bulk text replacement across multiple files",
84
+ cli_flag="--disable-content-replace"
85
+ ),
86
+
87
+ # Shell Tools (1)
88
+ "run_command": ToolConfig(
89
+ name="run_command",
90
+ category=ToolCategory.SHELL,
91
+ description="Execute shell commands with session support",
92
+ cli_flag="--disable-run-command"
93
+ ),
94
+
95
+ # Jupyter Tools (2)
96
+ "notebook_read": ToolConfig(
97
+ name="notebook_read",
98
+ category=ToolCategory.JUPYTER,
99
+ description="Read Jupyter notebook files (.ipynb)",
100
+ cli_flag="--disable-notebook-read"
101
+ ),
102
+ "notebook_edit": ToolConfig(
103
+ name="notebook_edit",
104
+ category=ToolCategory.JUPYTER,
105
+ description="Edit Jupyter notebook cells (replace, insert, delete)",
106
+ cli_flag="--disable-notebook-edit"
107
+ ),
108
+
109
+ # Todo Tools (2)
110
+ "todo_read": ToolConfig(
111
+ name="todo_read",
112
+ category=ToolCategory.TODO,
113
+ description="Read the current todo list for a session",
114
+ cli_flag="--disable-todo-read"
115
+ ),
116
+ "todo_write": ToolConfig(
117
+ name="todo_write",
118
+ category=ToolCategory.TODO,
119
+ description="Create and manage structured task lists",
120
+ cli_flag="--disable-todo-write"
121
+ ),
122
+
123
+ # Agent Tools (1)
124
+ "dispatch_agent": ToolConfig(
125
+ name="dispatch_agent",
126
+ category=ToolCategory.AGENT,
127
+ enabled=False, # Disabled by default
128
+ description="Delegate tasks to sub-agents for concurrent/specialized processing",
129
+ cli_flag="--enable-dispatch-agent"
130
+ ),
131
+
132
+ # Common Tools (3)
133
+ "think": ToolConfig(
134
+ name="think",
135
+ category=ToolCategory.COMMON,
136
+ description="Provide structured thinking space for complex reasoning",
137
+ cli_flag="--disable-think"
138
+ ),
139
+ "batch": ToolConfig(
140
+ name="batch",
141
+ category=ToolCategory.COMMON,
142
+ description="Execute multiple tools in parallel or serial",
143
+ cli_flag="--disable-batch"
144
+ ),
145
+
146
+ # Vector Tools (2)
147
+ "vector_index": ToolConfig(
148
+ name="vector_index",
149
+ category=ToolCategory.VECTOR,
150
+ enabled=False, # Disabled by default
151
+ description="Index documents in local vector database for semantic search",
152
+ cli_flag="--enable-vector-index"
153
+ ),
154
+ "vector_search": ToolConfig(
155
+ name="vector_search",
156
+ category=ToolCategory.VECTOR,
157
+ enabled=False, # Disabled by default
158
+ description="Search documents using semantic similarity in vector database",
159
+ cli_flag="--enable-vector-search"
160
+ ),
161
+ }
162
+
163
+
164
+ def get_tools_by_category(category: ToolCategory) -> List[ToolConfig]:
165
+ """Get all tools in a specific category."""
166
+ return [tool for tool in TOOL_REGISTRY.values() if tool.category == category]
167
+
168
+
169
+ def get_enabled_tools() -> List[ToolConfig]:
170
+ """Get all enabled tools."""
171
+ return [tool for tool in TOOL_REGISTRY.values() if tool.enabled]
172
+
173
+
174
+ def get_disabled_tools() -> List[ToolConfig]:
175
+ """Get all disabled tools."""
176
+ return [tool for tool in TOOL_REGISTRY.values() if not tool.enabled]
177
+
178
+
179
+ def enable_tool(tool_name: str) -> bool:
180
+ """Enable a specific tool. Returns True if successful."""
181
+ if tool_name in TOOL_REGISTRY:
182
+ TOOL_REGISTRY[tool_name].enabled = True
183
+ return True
184
+ return False
185
+
186
+
187
+ def disable_tool(tool_name: str) -> bool:
188
+ """Disable a specific tool. Returns True if successful."""
189
+ if tool_name in TOOL_REGISTRY:
190
+ TOOL_REGISTRY[tool_name].enabled = False
191
+ return True
192
+ return False
193
+
194
+
195
+ def is_tool_enabled(tool_name: str) -> bool:
196
+ """Check if a tool is enabled."""
197
+ return TOOL_REGISTRY.get(tool_name, ToolConfig("", ToolCategory.COMMON, False)).enabled