hanzo-mcp 0.8.11__py3-none-any.whl → 0.8.14__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 (154) hide show
  1. hanzo_mcp/__init__.py +2 -4
  2. hanzo_mcp/analytics/posthog_analytics.py +3 -9
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +6 -15
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +1 -3
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +13 -6
  17. hanzo_mcp/tools/__init__.py +10 -24
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +5 -15
  21. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +14 -41
  22. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  23. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  24. hanzo_mcp/tools/agent/cli_tools.py +75 -74
  25. hanzo_mcp/tools/agent/code_auth.py +1 -3
  26. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  27. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  28. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  29. hanzo_mcp/tools/agent/network_tool.py +7 -18
  30. hanzo_mcp/tools/agent/prompt.py +1 -5
  31. hanzo_mcp/tools/agent/review_tool.py +10 -25
  32. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  33. hanzo_mcp/tools/agent/swarm_tool.py +9 -29
  34. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
  35. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  36. hanzo_mcp/tools/common/batch_tool.py +15 -45
  37. hanzo_mcp/tools/common/config_tool.py +9 -28
  38. hanzo_mcp/tools/common/context.py +1 -3
  39. hanzo_mcp/tools/common/critic_tool.py +1 -3
  40. hanzo_mcp/tools/common/decorators.py +2 -6
  41. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  42. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  43. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  44. hanzo_mcp/tools/common/mode.py +1 -5
  45. hanzo_mcp/tools/common/paginated_base.py +3 -11
  46. hanzo_mcp/tools/common/paginated_response.py +10 -30
  47. hanzo_mcp/tools/common/pagination.py +3 -9
  48. hanzo_mcp/tools/common/permissions.py +38 -11
  49. hanzo_mcp/tools/common/personality.py +9 -34
  50. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  51. hanzo_mcp/tools/common/stats.py +6 -18
  52. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  53. hanzo_mcp/tools/common/tool_disable.py +2 -6
  54. hanzo_mcp/tools/common/tool_list.py +2 -6
  55. hanzo_mcp/tools/common/validation.py +1 -3
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_edit.py +2 -2
  72. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  73. hanzo_mcp/tools/filesystem/__init__.py +2 -3
  74. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  75. hanzo_mcp/tools/filesystem/base.py +4 -12
  76. hanzo_mcp/tools/filesystem/batch_search.py +35 -115
  77. hanzo_mcp/tools/filesystem/content_replace.py +4 -12
  78. hanzo_mcp/tools/filesystem/diff.py +2 -10
  79. hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
  80. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
  81. hanzo_mcp/tools/filesystem/edit.py +6 -18
  82. hanzo_mcp/tools/filesystem/find.py +3 -9
  83. hanzo_mcp/tools/filesystem/find_files.py +2 -6
  84. hanzo_mcp/tools/filesystem/git_search.py +9 -24
  85. hanzo_mcp/tools/filesystem/grep.py +9 -27
  86. hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
  87. hanzo_mcp/tools/filesystem/read.py +8 -26
  88. hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
  89. hanzo_mcp/tools/filesystem/search_tool.py +18 -62
  90. hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
  91. hanzo_mcp/tools/filesystem/tree.py +1 -3
  92. hanzo_mcp/tools/filesystem/watch.py +1 -3
  93. hanzo_mcp/tools/filesystem/write.py +1 -3
  94. hanzo_mcp/tools/jupyter/base.py +6 -20
  95. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  96. hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
  97. hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
  98. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  99. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  100. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  101. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  102. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  103. hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
  104. hanzo_mcp/tools/mcp/mcp_add.py +1 -3
  105. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  106. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  107. hanzo_mcp/tools/memory/__init__.py +10 -27
  108. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  109. hanzo_mcp/tools/memory/memory_tools.py +6 -18
  110. hanzo_mcp/tools/search/find_tool.py +10 -32
  111. hanzo_mcp/tools/search/unified_search.py +24 -78
  112. hanzo_mcp/tools/shell/__init__.py +2 -2
  113. hanzo_mcp/tools/shell/auto_background.py +2 -6
  114. hanzo_mcp/tools/shell/base.py +1 -5
  115. hanzo_mcp/tools/shell/base_process.py +5 -7
  116. hanzo_mcp/tools/shell/bash_session.py +7 -24
  117. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  118. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  119. hanzo_mcp/tools/shell/command_executor.py +33 -86
  120. hanzo_mcp/tools/shell/logs.py +4 -16
  121. hanzo_mcp/tools/shell/npx.py +2 -8
  122. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  123. hanzo_mcp/tools/shell/pkill.py +4 -12
  124. hanzo_mcp/tools/shell/process_tool.py +2 -8
  125. hanzo_mcp/tools/shell/processes.py +5 -17
  126. hanzo_mcp/tools/shell/run_background.py +1 -3
  127. hanzo_mcp/tools/shell/run_command.py +1 -3
  128. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  129. hanzo_mcp/tools/shell/session_manager.py +2 -6
  130. hanzo_mcp/tools/shell/session_storage.py +2 -6
  131. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  132. hanzo_mcp/tools/shell/uvx.py +4 -14
  133. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  134. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  135. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  136. hanzo_mcp/tools/todo/todo.py +1 -3
  137. hanzo_mcp/tools/todo/todo_read.py +3 -9
  138. hanzo_mcp/tools/todo/todo_write.py +6 -18
  139. hanzo_mcp/tools/vector/__init__.py +3 -9
  140. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  141. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  142. hanzo_mcp/tools/vector/index_tool.py +3 -9
  143. hanzo_mcp/tools/vector/infinity_store.py +7 -27
  144. hanzo_mcp/tools/vector/mock_infinity.py +1 -3
  145. hanzo_mcp/tools/vector/project_manager.py +4 -12
  146. hanzo_mcp/tools/vector/vector.py +2 -6
  147. hanzo_mcp/tools/vector/vector_index.py +8 -8
  148. hanzo_mcp/tools/vector/vector_search.py +7 -21
  149. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/METADATA +2 -2
  150. hanzo_mcp-0.8.14.dist-info/RECORD +193 -0
  151. hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
  152. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/WHEEL +0 -0
  153. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/entry_points.txt +0 -0
  154. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,7 @@ from dataclasses import field, dataclass
15
15
 
16
16
  class ModelProvider(Enum):
17
17
  """Enumeration of AI model providers."""
18
-
18
+
19
19
  ANTHROPIC = "anthropic"
20
20
  OPENAI = "openai"
21
21
  GOOGLE = "google"
@@ -30,7 +30,7 @@ class ModelProvider(Enum):
30
30
  @dataclass(frozen=True)
31
31
  class ModelConfig:
32
32
  """Configuration for a single AI model."""
33
-
33
+
34
34
  full_name: str
35
35
  provider: ModelProvider
36
36
  aliases: Set[str] = field(default_factory=set)
@@ -46,16 +46,16 @@ class ModelConfig:
46
46
 
47
47
  class ModelRegistry:
48
48
  """Centralized registry for all AI models.
49
-
49
+
50
50
  Thread-safe singleton implementation ensuring single source of truth
51
51
  for model configurations across the codebase.
52
52
  """
53
-
53
+
54
54
  _instance: Optional[ModelRegistry] = None
55
55
  _lock = threading.Lock()
56
56
  _models: Dict[str, ModelConfig] = {}
57
57
  _initialized = False
58
-
58
+
59
59
  def __new__(cls) -> ModelRegistry:
60
60
  """Thread-safe singleton pattern."""
61
61
  if cls._instance is None:
@@ -63,7 +63,7 @@ class ModelRegistry:
63
63
  if cls._instance is None:
64
64
  cls._instance = super().__new__(cls)
65
65
  return cls._instance
66
-
66
+
67
67
  def __init__(self) -> None:
68
68
  """Initialize model registry once."""
69
69
  if not self._initialized:
@@ -71,225 +71,253 @@ class ModelRegistry:
71
71
  if not self._initialized:
72
72
  self._initialize_models()
73
73
  self._initialized = True
74
-
74
+
75
75
  def _initialize_models(self) -> None:
76
76
  """Initialize all model configurations."""
77
77
  # Claude models
78
- self._register(ModelConfig(
79
- full_name="claude-3-5-sonnet-20241022",
80
- provider=ModelProvider.ANTHROPIC,
81
- aliases={"claude", "cc", "claude-code", "sonnet", "sonnet-4.1"},
82
- supports_vision=True,
83
- supports_tools=True,
84
- context_window=200000,
85
- max_output=8192,
86
- api_key_env="ANTHROPIC_API_KEY",
87
- cli_command="claude",
88
- ))
89
-
90
- self._register(ModelConfig(
91
- full_name="claude-opus-4-1-20250805",
92
- provider=ModelProvider.ANTHROPIC,
93
- aliases={"opus", "opus-4.1", "claude-opus"},
94
- supports_vision=True,
95
- supports_tools=True,
96
- context_window=200000,
97
- max_output=8192,
98
- api_key_env="ANTHROPIC_API_KEY",
99
- cli_command="claude",
100
- ))
101
-
102
- self._register(ModelConfig(
103
- full_name="claude-3-haiku-20240307",
104
- provider=ModelProvider.ANTHROPIC,
105
- aliases={"haiku", "claude-haiku"},
106
- supports_vision=True,
107
- supports_tools=True,
108
- context_window=200000,
109
- max_output=4096,
110
- api_key_env="ANTHROPIC_API_KEY",
111
- cli_command="claude",
112
- ))
113
-
78
+ self._register(
79
+ ModelConfig(
80
+ full_name="claude-3-5-sonnet-20241022",
81
+ provider=ModelProvider.ANTHROPIC,
82
+ aliases={"claude", "cc", "claude-code", "sonnet", "sonnet-4.1"},
83
+ supports_vision=True,
84
+ supports_tools=True,
85
+ context_window=200000,
86
+ max_output=8192,
87
+ api_key_env="ANTHROPIC_API_KEY",
88
+ cli_command="claude",
89
+ )
90
+ )
91
+
92
+ self._register(
93
+ ModelConfig(
94
+ full_name="claude-opus-4-1-20250805",
95
+ provider=ModelProvider.ANTHROPIC,
96
+ aliases={"opus", "opus-4.1", "claude-opus"},
97
+ supports_vision=True,
98
+ supports_tools=True,
99
+ context_window=200000,
100
+ max_output=8192,
101
+ api_key_env="ANTHROPIC_API_KEY",
102
+ cli_command="claude",
103
+ )
104
+ )
105
+
106
+ self._register(
107
+ ModelConfig(
108
+ full_name="claude-3-haiku-20240307",
109
+ provider=ModelProvider.ANTHROPIC,
110
+ aliases={"haiku", "claude-haiku"},
111
+ supports_vision=True,
112
+ supports_tools=True,
113
+ context_window=200000,
114
+ max_output=4096,
115
+ api_key_env="ANTHROPIC_API_KEY",
116
+ cli_command="claude",
117
+ )
118
+ )
119
+
114
120
  # OpenAI models
115
- self._register(ModelConfig(
116
- full_name="gpt-4-turbo",
117
- provider=ModelProvider.OPENAI,
118
- aliases={"gpt4", "gpt-4", "codex"},
119
- supports_vision=True,
120
- supports_tools=True,
121
- context_window=128000,
122
- max_output=4096,
123
- api_key_env="OPENAI_API_KEY",
124
- cli_command="openai",
125
- ))
126
-
127
- self._register(ModelConfig(
128
- full_name="gpt-5-turbo",
129
- provider=ModelProvider.OPENAI,
130
- aliases={"gpt5", "gpt-5"},
131
- supports_vision=True,
132
- supports_tools=True,
133
- context_window=256000,
134
- max_output=16384,
135
- api_key_env="OPENAI_API_KEY",
136
- cli_command="openai",
137
- ))
138
-
139
- self._register(ModelConfig(
140
- full_name="o1-preview",
141
- provider=ModelProvider.OPENAI,
142
- aliases={"o1", "openai-o1"},
143
- supports_vision=False,
144
- supports_tools=False,
145
- context_window=128000,
146
- max_output=32768,
147
- api_key_env="OPENAI_API_KEY",
148
- cli_command="openai",
149
- ))
150
-
121
+ self._register(
122
+ ModelConfig(
123
+ full_name="gpt-4-turbo",
124
+ provider=ModelProvider.OPENAI,
125
+ aliases={"gpt4", "gpt-4", "codex"},
126
+ supports_vision=True,
127
+ supports_tools=True,
128
+ context_window=128000,
129
+ max_output=4096,
130
+ api_key_env="OPENAI_API_KEY",
131
+ cli_command="openai",
132
+ )
133
+ )
134
+
135
+ self._register(
136
+ ModelConfig(
137
+ full_name="gpt-5-turbo",
138
+ provider=ModelProvider.OPENAI,
139
+ aliases={"gpt5", "gpt-5"},
140
+ supports_vision=True,
141
+ supports_tools=True,
142
+ context_window=256000,
143
+ max_output=16384,
144
+ api_key_env="OPENAI_API_KEY",
145
+ cli_command="openai",
146
+ )
147
+ )
148
+
149
+ self._register(
150
+ ModelConfig(
151
+ full_name="o1-preview",
152
+ provider=ModelProvider.OPENAI,
153
+ aliases={"o1", "openai-o1"},
154
+ supports_vision=False,
155
+ supports_tools=False,
156
+ context_window=128000,
157
+ max_output=32768,
158
+ api_key_env="OPENAI_API_KEY",
159
+ cli_command="openai",
160
+ )
161
+ )
162
+
151
163
  # Google models
152
- self._register(ModelConfig(
153
- full_name="gemini-2.0-flash-exp",
154
- provider=ModelProvider.GOOGLE,
155
- aliases={"gemini-2", "gemini-2.0", "gemini2"},
156
- supports_vision=True,
157
- supports_tools=True,
158
- context_window=1000000,
159
- max_output=8192,
160
- api_key_env="GEMINI_API_KEY",
161
- cli_command="gemini",
162
- ))
163
-
164
- self._register(ModelConfig(
165
- full_name="gemini-exp-1206",
166
- provider=ModelProvider.GOOGLE,
167
- aliases={"gemini-2.5", "gemini-2.5-pro", "gemini-pro-2.5"},
168
- supports_vision=True,
169
- supports_tools=True,
170
- context_window=2000000,
171
- max_output=8192,
172
- api_key_env="GEMINI_API_KEY",
173
- cli_command="gemini",
174
- ))
175
-
176
- self._register(ModelConfig(
177
- full_name="gemini-1.5-pro",
178
- provider=ModelProvider.GOOGLE,
179
- aliases={"gemini", "gemini-pro", "gemini-1.5"},
180
- supports_vision=True,
181
- supports_tools=True,
182
- context_window=2000000,
183
- max_output=8192,
184
- api_key_env="GEMINI_API_KEY",
185
- cli_command="gemini",
186
- ))
187
-
188
- self._register(ModelConfig(
189
- full_name="gemini-1.5-flash",
190
- provider=ModelProvider.GOOGLE,
191
- aliases={"gemini-flash", "flash"},
192
- supports_vision=True,
193
- supports_tools=True,
194
- context_window=1000000,
195
- max_output=8192,
196
- api_key_env="GEMINI_API_KEY",
197
- cli_command="gemini",
198
- ))
199
-
164
+ self._register(
165
+ ModelConfig(
166
+ full_name="gemini-2.0-flash-exp",
167
+ provider=ModelProvider.GOOGLE,
168
+ aliases={"gemini-2", "gemini-2.0", "gemini2"},
169
+ supports_vision=True,
170
+ supports_tools=True,
171
+ context_window=1000000,
172
+ max_output=8192,
173
+ api_key_env="GEMINI_API_KEY",
174
+ cli_command="gemini",
175
+ )
176
+ )
177
+
178
+ self._register(
179
+ ModelConfig(
180
+ full_name="gemini-exp-1206",
181
+ provider=ModelProvider.GOOGLE,
182
+ aliases={"gemini-2.5", "gemini-2.5-pro", "gemini-pro-2.5"},
183
+ supports_vision=True,
184
+ supports_tools=True,
185
+ context_window=2000000,
186
+ max_output=8192,
187
+ api_key_env="GEMINI_API_KEY",
188
+ cli_command="gemini",
189
+ )
190
+ )
191
+
192
+ self._register(
193
+ ModelConfig(
194
+ full_name="gemini-1.5-pro",
195
+ provider=ModelProvider.GOOGLE,
196
+ aliases={"gemini", "gemini-pro", "gemini-1.5"},
197
+ supports_vision=True,
198
+ supports_tools=True,
199
+ context_window=2000000,
200
+ max_output=8192,
201
+ api_key_env="GEMINI_API_KEY",
202
+ cli_command="gemini",
203
+ )
204
+ )
205
+
206
+ self._register(
207
+ ModelConfig(
208
+ full_name="gemini-1.5-flash",
209
+ provider=ModelProvider.GOOGLE,
210
+ aliases={"gemini-flash", "flash"},
211
+ supports_vision=True,
212
+ supports_tools=True,
213
+ context_window=1000000,
214
+ max_output=8192,
215
+ api_key_env="GEMINI_API_KEY",
216
+ cli_command="gemini",
217
+ )
218
+ )
219
+
200
220
  # xAI models
201
- self._register(ModelConfig(
202
- full_name="grok-4",
203
- provider=ModelProvider.XAI,
204
- aliases={"grok", "xai-grok", "grok-2"}, # grok-2 for backward compat
205
- supports_vision=True, # Grok-4 supports multimodal
206
- supports_tools=True,
207
- context_window=128000,
208
- max_output=8192,
209
- api_key_env="XAI_API_KEY",
210
- cli_command="grok",
211
- ))
212
-
221
+ self._register(
222
+ ModelConfig(
223
+ full_name="grok-4",
224
+ provider=ModelProvider.XAI,
225
+ aliases={"grok", "xai-grok", "grok-2"}, # grok-2 for backward compat
226
+ supports_vision=True, # Grok-4 supports multimodal
227
+ supports_tools=True,
228
+ context_window=128000,
229
+ max_output=8192,
230
+ api_key_env="XAI_API_KEY",
231
+ cli_command="grok",
232
+ )
233
+ )
234
+
213
235
  # Ollama models
214
- self._register(ModelConfig(
215
- full_name="ollama/llama-3.2-3b",
216
- provider=ModelProvider.OLLAMA,
217
- aliases={"llama", "llama-3.2", "llama3"},
218
- supports_vision=False,
219
- supports_tools=False,
220
- context_window=128000,
221
- max_output=4096,
222
- api_key_env=None, # Local model
223
- cli_command="ollama",
224
- ))
225
-
226
- self._register(ModelConfig(
227
- full_name="ollama/mistral:7b",
228
- provider=ModelProvider.MISTRAL,
229
- aliases={"mistral", "mistral-7b"},
230
- supports_vision=False,
231
- supports_tools=False,
232
- context_window=32000,
233
- max_output=4096,
234
- api_key_env=None, # Local model
235
- cli_command="ollama",
236
- ))
237
-
236
+ self._register(
237
+ ModelConfig(
238
+ full_name="ollama/llama-3.2-3b",
239
+ provider=ModelProvider.OLLAMA,
240
+ aliases={"llama", "llama-3.2", "llama3"},
241
+ supports_vision=False,
242
+ supports_tools=False,
243
+ context_window=128000,
244
+ max_output=4096,
245
+ api_key_env=None, # Local model
246
+ cli_command="ollama",
247
+ )
248
+ )
249
+
250
+ self._register(
251
+ ModelConfig(
252
+ full_name="ollama/mistral:7b",
253
+ provider=ModelProvider.MISTRAL,
254
+ aliases={"mistral", "mistral-7b"},
255
+ supports_vision=False,
256
+ supports_tools=False,
257
+ context_window=32000,
258
+ max_output=4096,
259
+ api_key_env=None, # Local model
260
+ cli_command="ollama",
261
+ )
262
+ )
263
+
238
264
  # DeepSeek models
239
- self._register(ModelConfig(
240
- full_name="deepseek-coder-v2",
241
- provider=ModelProvider.DEEPSEEK,
242
- aliases={"deepseek", "deepseek-coder"},
243
- supports_vision=False,
244
- supports_tools=True,
245
- context_window=128000,
246
- max_output=8192,
247
- api_key_env="DEEPSEEK_API_KEY",
248
- cli_command="deepseek",
249
- ))
250
-
265
+ self._register(
266
+ ModelConfig(
267
+ full_name="deepseek-coder-v2",
268
+ provider=ModelProvider.DEEPSEEK,
269
+ aliases={"deepseek", "deepseek-coder"},
270
+ supports_vision=False,
271
+ supports_tools=True,
272
+ context_window=128000,
273
+ max_output=8192,
274
+ api_key_env="DEEPSEEK_API_KEY",
275
+ cli_command="deepseek",
276
+ )
277
+ )
278
+
251
279
  def _register(self, config: ModelConfig) -> None:
252
280
  """Register a model configuration.
253
-
281
+
254
282
  Args:
255
283
  config: Model configuration to register
256
284
  """
257
285
  # Register by full name
258
286
  self._models[config.full_name] = config
259
-
287
+
260
288
  # Register all aliases
261
289
  for alias in config.aliases:
262
290
  self._models[alias.lower()] = config
263
-
291
+
264
292
  def get(self, model_name: str) -> Optional[ModelConfig]:
265
293
  """Get model configuration by name or alias.
266
-
294
+
267
295
  Args:
268
296
  model_name: Model name or alias
269
-
297
+
270
298
  Returns:
271
299
  Model configuration or None if not found
272
300
  """
273
301
  return self._models.get(model_name.lower())
274
-
302
+
275
303
  def resolve(self, model_name: str) -> str:
276
304
  """Resolve model name or alias to full model name.
277
-
305
+
278
306
  Args:
279
307
  model_name: Model name or alias
280
-
308
+
281
309
  Returns:
282
310
  Full model name, or original if not found
283
311
  """
284
312
  config = self.get(model_name)
285
313
  return config.full_name if config else model_name
286
-
314
+
287
315
  def get_by_provider(self, provider: ModelProvider) -> List[ModelConfig]:
288
316
  """Get all unique models for a specific provider.
289
-
317
+
290
318
  Args:
291
319
  provider: Model provider
292
-
320
+
293
321
  Returns:
294
322
  List of unique model configurations
295
323
  """
@@ -300,7 +328,7 @@ class ModelRegistry:
300
328
  seen_names.add(config.full_name)
301
329
  results.append(config)
302
330
  return results
303
-
331
+
304
332
  def get_models_supporting(
305
333
  self,
306
334
  vision: Optional[bool] = None,
@@ -308,61 +336,61 @@ class ModelRegistry:
308
336
  streaming: Optional[bool] = None,
309
337
  ) -> List[ModelConfig]:
310
338
  """Get unique models supporting specific features.
311
-
339
+
312
340
  Args:
313
341
  vision: Filter by vision support
314
342
  tools: Filter by tool support
315
343
  streaming: Filter by streaming support
316
-
344
+
317
345
  Returns:
318
346
  List of unique matching model configurations
319
347
  """
320
348
  seen_names = set()
321
349
  results = []
322
-
350
+
323
351
  for config in self._models.values():
324
352
  if config.full_name in seen_names:
325
353
  continue
326
-
354
+
327
355
  if vision is not None and config.supports_vision != vision:
328
356
  continue
329
357
  if tools is not None and config.supports_tools != tools:
330
358
  continue
331
359
  if streaming is not None and config.supports_streaming != streaming:
332
360
  continue
333
-
361
+
334
362
  seen_names.add(config.full_name)
335
363
  results.append(config)
336
-
364
+
337
365
  return results
338
-
366
+
339
367
  def get_api_key_env(self, model_name: str) -> Optional[str]:
340
368
  """Get the API key environment variable for a model.
341
-
369
+
342
370
  Args:
343
371
  model_name: Model name or alias
344
-
372
+
345
373
  Returns:
346
374
  Environment variable name or None
347
375
  """
348
376
  config = self.get(model_name)
349
377
  return config.api_key_env if config else None
350
-
378
+
351
379
  def get_cli_command(self, model_name: str) -> Optional[str]:
352
380
  """Get the CLI command for a model.
353
-
381
+
354
382
  Args:
355
383
  model_name: Model name or alias
356
-
384
+
357
385
  Returns:
358
386
  CLI command or None
359
387
  """
360
388
  config = self.get(model_name)
361
389
  return config.cli_command if config else None
362
-
390
+
363
391
  def list_all_models(self) -> List[str]:
364
392
  """List all unique model full names.
365
-
393
+
366
394
  Returns:
367
395
  Sorted list of unique full model names
368
396
  """
@@ -370,10 +398,10 @@ class ModelRegistry:
370
398
  for config in self._models.values():
371
399
  seen_names.add(config.full_name)
372
400
  return sorted(list(seen_names))
373
-
401
+
374
402
  def list_all_aliases(self) -> Dict[str, str]:
375
403
  """List all aliases and their full names.
376
-
404
+
377
405
  Returns:
378
406
  Dictionary mapping aliases to full names
379
407
  """
@@ -391,10 +419,10 @@ registry = ModelRegistry()
391
419
  # Convenience functions
392
420
  def resolve_model(model_name: str) -> str:
393
421
  """Resolve model name or alias to full model name.
394
-
422
+
395
423
  Args:
396
424
  model_name: Model name or alias
397
-
425
+
398
426
  Returns:
399
427
  Full model name
400
428
  """
@@ -403,10 +431,10 @@ def resolve_model(model_name: str) -> str:
403
431
 
404
432
  def get_model_config(model_name: str) -> Optional[ModelConfig]:
405
433
  """Get model configuration.
406
-
434
+
407
435
  Args:
408
436
  model_name: Model name or alias
409
-
437
+
410
438
  Returns:
411
439
  Model configuration or None
412
440
  """
@@ -415,10 +443,10 @@ def get_model_config(model_name: str) -> Optional[ModelConfig]:
415
443
 
416
444
  def get_api_key_env(model_name: str) -> Optional[str]:
417
445
  """Get API key environment variable for model.
418
-
446
+
419
447
  Args:
420
448
  model_name: Model name or alias
421
-
449
+
422
450
  Returns:
423
451
  Environment variable name or None
424
452
  """
@@ -433,4 +461,4 @@ __all__ = [
433
461
  "resolve_model",
434
462
  "get_model_config",
435
463
  "get_api_key_env",
436
- ]
464
+ ]
hanzo_mcp/dev_server.py CHANGED
@@ -159,12 +159,8 @@ class DevServer:
159
159
  # Since MCP servers run in the same process, we need to handle this differently
160
160
  # For now, we'll log a message indicating a restart is needed
161
161
  logger = logging.getLogger(__name__)
162
- logger.warning(
163
- "\n⚠️ Server restart required. Please restart the MCP client to reload changes."
164
- )
165
- logger.info(
166
- "💡 Tip: In development, consider using the MCP test client for easier reloading."
167
- )
162
+ logger.warning("\n⚠️ Server restart required. Please restart the MCP client to reload changes.")
163
+ logger.info("💡 Tip: In development, consider using the MCP test client for easier reloading.")
168
164
 
169
165
  async def run_async(self, transport: str = "stdio"):
170
166
  """Run the development server asynchronously."""
@@ -218,12 +214,8 @@ def run_dev_server():
218
214
  """Entry point for development server."""
219
215
  import argparse
220
216
 
221
- parser = argparse.ArgumentParser(
222
- description="Run Hanzo AI in development mode with hot reload"
223
- )
224
- parser.add_argument(
225
- "--name", type=str, default="hanzo-dev", help="Name of the MCP server"
226
- )
217
+ parser = argparse.ArgumentParser(description="Run Hanzo AI in development mode with hot reload")
218
+ parser.add_argument("--name", type=str, default="hanzo-dev", help="Name of the MCP server")
227
219
  parser.add_argument("--project-dir", type=str, help="Project directory to serve")
228
220
  parser.add_argument(
229
221
  "--allowed-path",
@@ -239,9 +231,7 @@ def run_dev_server():
239
231
  choices=["stdio", "sse"],
240
232
  help="Transport type (default: stdio)",
241
233
  )
242
- parser.add_argument(
243
- "--host", type=str, default="127.0.0.1", help="Host for SSE transport"
244
- )
234
+ parser.add_argument("--host", type=str, default="127.0.0.1", help="Host for SSE transport")
245
235
  parser.add_argument("--port", type=int, default=3000, help="Port for SSE transport")
246
236
 
247
237
  args = parser.parse_args()