hanzo-mcp 0.7.6__py3-none-any.whl ā 0.8.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 +7 -1
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.6.dist-info ā hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.6.dist-info ā hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.6.dist-info ā hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.6.dist-info ā hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
This tool manages API keys and accounts for Claude Code and other AI coding tools.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Unpack, Optional, TypedDict, final, override
|
|
7
|
+
|
|
7
8
|
from mcp.server import FastMCP
|
|
8
9
|
from mcp.server.fastmcp import Context as MCPContext
|
|
9
|
-
from pydantic import Field
|
|
10
10
|
|
|
11
11
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
12
12
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
@@ -15,6 +15,7 @@ from hanzo_mcp.tools.agent.code_auth import CodeAuthManager
|
|
|
15
15
|
|
|
16
16
|
class CodeAuthParams(TypedDict, total=False):
|
|
17
17
|
"""Parameters for code auth tool."""
|
|
18
|
+
|
|
18
19
|
action: str
|
|
19
20
|
account: Optional[str]
|
|
20
21
|
provider: Optional[str]
|
|
@@ -28,13 +29,13 @@ class CodeAuthParams(TypedDict, total=False):
|
|
|
28
29
|
@final
|
|
29
30
|
class CodeAuthTool(BaseTool):
|
|
30
31
|
"""Tool for managing Claude Code authentication and API keys."""
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
@property
|
|
33
34
|
@override
|
|
34
35
|
def name(self) -> str:
|
|
35
36
|
"""Get the tool name."""
|
|
36
37
|
return "code_auth"
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
@property
|
|
39
40
|
@override
|
|
40
41
|
def description(self) -> str:
|
|
@@ -60,11 +61,11 @@ code_auth switch --account personal
|
|
|
60
61
|
code_auth agent --agent_id swarm_1 --parent_account work
|
|
61
62
|
|
|
62
63
|
Providers: claude, openai, azure, deepseek, google, groq"""
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
def __init__(self):
|
|
65
66
|
"""Initialize the code auth tool."""
|
|
66
67
|
self.auth_manager = CodeAuthManager()
|
|
67
|
-
|
|
68
|
+
|
|
68
69
|
@override
|
|
69
70
|
async def call(
|
|
70
71
|
self,
|
|
@@ -72,19 +73,19 @@ Providers: claude, openai, azure, deepseek, google, groq"""
|
|
|
72
73
|
**params: Unpack[CodeAuthParams],
|
|
73
74
|
) -> str:
|
|
74
75
|
"""Execute the code auth tool.
|
|
75
|
-
|
|
76
|
+
|
|
76
77
|
Args:
|
|
77
78
|
ctx: MCP context
|
|
78
79
|
**params: Tool parameters
|
|
79
|
-
|
|
80
|
+
|
|
80
81
|
Returns:
|
|
81
82
|
Result message
|
|
82
83
|
"""
|
|
83
84
|
tool_ctx = create_tool_context(ctx)
|
|
84
85
|
await tool_ctx.set_tool_info(self.name)
|
|
85
|
-
|
|
86
|
+
|
|
86
87
|
action = params.get("action", "status")
|
|
87
|
-
|
|
88
|
+
|
|
88
89
|
if action == "status":
|
|
89
90
|
current = self.auth_manager.get_active_account()
|
|
90
91
|
if current:
|
|
@@ -92,12 +93,12 @@ Providers: claude, openai, azure, deepseek, google, groq"""
|
|
|
92
93
|
if info:
|
|
93
94
|
return f"Logged in as: {current} ({info['provider']})"
|
|
94
95
|
return "Not logged in"
|
|
95
|
-
|
|
96
|
+
|
|
96
97
|
elif action == "list":
|
|
97
98
|
accounts = self.auth_manager.list_accounts()
|
|
98
99
|
if not accounts:
|
|
99
100
|
return "No accounts configured"
|
|
100
|
-
|
|
101
|
+
|
|
101
102
|
current = self.auth_manager.get_active_account()
|
|
102
103
|
lines = ["Configured accounts:"]
|
|
103
104
|
for account in accounts:
|
|
@@ -105,52 +106,52 @@ Providers: claude, openai, azure, deepseek, google, groq"""
|
|
|
105
106
|
marker = " (active)" if account == current else ""
|
|
106
107
|
lines.append(f" - {account}: {info['provider']}{marker}")
|
|
107
108
|
return "\n".join(lines)
|
|
108
|
-
|
|
109
|
+
|
|
109
110
|
elif action == "create":
|
|
110
111
|
account = params.get("account")
|
|
111
112
|
if not account:
|
|
112
113
|
return "Error: account name required"
|
|
113
|
-
|
|
114
|
+
|
|
114
115
|
provider = params.get("provider", "claude")
|
|
115
116
|
api_key = params.get("api_key")
|
|
116
117
|
model = params.get("model")
|
|
117
118
|
description = params.get("description")
|
|
118
|
-
|
|
119
|
+
|
|
119
120
|
success, msg = self.auth_manager.create_account(
|
|
120
121
|
account, provider, api_key, model, description
|
|
121
122
|
)
|
|
122
123
|
return msg
|
|
123
|
-
|
|
124
|
+
|
|
124
125
|
elif action == "login":
|
|
125
126
|
account = params.get("account", "default")
|
|
126
127
|
success, msg = self.auth_manager.login(account)
|
|
127
128
|
return msg
|
|
128
|
-
|
|
129
|
+
|
|
129
130
|
elif action == "logout":
|
|
130
131
|
success, msg = self.auth_manager.logout()
|
|
131
132
|
return msg
|
|
132
|
-
|
|
133
|
+
|
|
133
134
|
elif action == "switch":
|
|
134
135
|
account = params.get("account")
|
|
135
136
|
if not account:
|
|
136
137
|
return "Error: account name required"
|
|
137
|
-
|
|
138
|
+
|
|
138
139
|
success, msg = self.auth_manager.switch_account(account)
|
|
139
140
|
return msg
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
elif action == "agent":
|
|
142
143
|
agent_id = params.get("agent_id")
|
|
143
144
|
if not agent_id:
|
|
144
145
|
return "Error: agent_id required"
|
|
145
|
-
|
|
146
|
+
|
|
146
147
|
provider = params.get("provider", "claude")
|
|
147
148
|
parent_account = params.get("parent_account")
|
|
148
|
-
|
|
149
|
+
|
|
149
150
|
# Try to create agent account
|
|
150
151
|
success, result = self.auth_manager.create_agent_account(
|
|
151
152
|
agent_id, provider, parent_account
|
|
152
153
|
)
|
|
153
|
-
|
|
154
|
+
|
|
154
155
|
if success:
|
|
155
156
|
# Get credentials
|
|
156
157
|
creds = self.auth_manager.get_agent_credentials(agent_id)
|
|
@@ -160,15 +161,15 @@ Providers: claude, openai, azure, deepseek, google, groq"""
|
|
|
160
161
|
return f"Agent account created but no credentials: {result}"
|
|
161
162
|
else:
|
|
162
163
|
return f"Failed to create agent account: {result}"
|
|
163
|
-
|
|
164
|
+
|
|
164
165
|
else:
|
|
165
166
|
return f"Unknown action: {action}. Use: status, list, create, login, logout, switch, agent"
|
|
166
|
-
|
|
167
|
+
|
|
167
168
|
@override
|
|
168
169
|
def register(self, mcp_server: FastMCP) -> None:
|
|
169
170
|
"""Register this tool with the MCP server."""
|
|
170
171
|
tool_self = self
|
|
171
|
-
|
|
172
|
+
|
|
172
173
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
173
174
|
async def code_auth(
|
|
174
175
|
ctx: MCPContext,
|
|
@@ -191,4 +192,4 @@ Providers: claude, openai, azure, deepseek, google, groq"""
|
|
|
191
192
|
description=description,
|
|
192
193
|
agent_id=agent_id,
|
|
193
194
|
parent_account=parent_account,
|
|
194
|
-
)
|
|
195
|
+
)
|
|
@@ -4,26 +4,27 @@ This tool provides integration with OpenAI's CLI (openai command),
|
|
|
4
4
|
allowing programmatic execution of GPT-4 and other models for code tasks.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from typing import List, Optional,
|
|
7
|
+
from typing import List, Optional, final, override
|
|
8
|
+
|
|
8
9
|
from mcp.server import FastMCP
|
|
9
10
|
from mcp.server.fastmcp import Context as MCPContext
|
|
10
11
|
|
|
11
|
-
from hanzo_mcp.tools.agent.cli_agent_base import CLIAgentBase
|
|
12
12
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
13
|
+
from hanzo_mcp.tools.agent.cli_agent_base import CLIAgentBase
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@final
|
|
16
17
|
class CodexCLITool(CLIAgentBase):
|
|
17
18
|
"""Tool for executing OpenAI CLI (formerly Codex)."""
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
def __init__(
|
|
20
21
|
self,
|
|
21
22
|
permission_manager: PermissionManager,
|
|
22
23
|
model: Optional[str] = None,
|
|
23
|
-
**kwargs
|
|
24
|
+
**kwargs,
|
|
24
25
|
):
|
|
25
26
|
"""Initialize Codex CLI tool.
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
Args:
|
|
28
29
|
permission_manager: Permission manager for access control
|
|
29
30
|
model: Optional model override (defaults to gpt-4o)
|
|
@@ -35,15 +36,15 @@ class CodexCLITool(CLIAgentBase):
|
|
|
35
36
|
provider_name="OpenAI",
|
|
36
37
|
default_model=model or "gpt-4o",
|
|
37
38
|
env_vars=["OPENAI_API_KEY"],
|
|
38
|
-
**kwargs
|
|
39
|
+
**kwargs,
|
|
39
40
|
)
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
@property
|
|
42
43
|
@override
|
|
43
44
|
def name(self) -> str:
|
|
44
45
|
"""Get the tool name."""
|
|
45
46
|
return "codex_cli"
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
@property
|
|
48
49
|
@override
|
|
49
50
|
def description(self) -> str:
|
|
@@ -68,42 +69,42 @@ Requirements:
|
|
|
68
69
|
- OpenAI CLI must be installed: pip install openai
|
|
69
70
|
- OPENAI_API_KEY environment variable
|
|
70
71
|
"""
|
|
71
|
-
|
|
72
|
+
|
|
72
73
|
@override
|
|
73
74
|
def get_cli_args(self, prompt: str, **kwargs) -> List[str]:
|
|
74
75
|
"""Get CLI arguments for OpenAI.
|
|
75
|
-
|
|
76
|
+
|
|
76
77
|
Args:
|
|
77
78
|
prompt: The prompt to send
|
|
78
79
|
**kwargs: Additional arguments (model, temperature, etc.)
|
|
79
|
-
|
|
80
|
+
|
|
80
81
|
Returns:
|
|
81
82
|
List of command arguments
|
|
82
83
|
"""
|
|
83
84
|
args = ["api", "chat.completions.create"]
|
|
84
|
-
|
|
85
|
+
|
|
85
86
|
# Add model
|
|
86
87
|
model = kwargs.get("model", self.default_model)
|
|
87
88
|
args.extend(["-m", model])
|
|
88
|
-
|
|
89
|
+
|
|
89
90
|
# Add temperature if specified
|
|
90
91
|
if "temperature" in kwargs:
|
|
91
92
|
args.extend(["--temperature", str(kwargs["temperature"])])
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
# Add max tokens if specified
|
|
94
95
|
if "max_tokens" in kwargs:
|
|
95
96
|
args.extend(["--max-tokens", str(kwargs["max_tokens"])])
|
|
96
|
-
|
|
97
|
+
|
|
97
98
|
# Add the prompt as a message
|
|
98
99
|
args.extend(["-g", prompt])
|
|
99
|
-
|
|
100
|
+
|
|
100
101
|
return args
|
|
101
|
-
|
|
102
|
+
|
|
102
103
|
@override
|
|
103
104
|
def register(self, mcp_server: FastMCP) -> None:
|
|
104
105
|
"""Register this tool with the MCP server."""
|
|
105
106
|
tool_self = self
|
|
106
|
-
|
|
107
|
+
|
|
107
108
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
108
109
|
async def codex_cli(
|
|
109
110
|
ctx: MCPContext,
|
|
@@ -120,4 +121,4 @@ Requirements:
|
|
|
120
121
|
temperature=temperature,
|
|
121
122
|
max_tokens=max_tokens,
|
|
122
123
|
working_dir=working_dir,
|
|
123
|
-
)
|
|
124
|
+
)
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"""Critic tool for agents to request critical review from main loop."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
|
-
from typing import Any, Dict, List, Optional, override
|
|
5
3
|
from enum import Enum
|
|
4
|
+
from typing import List, Optional, override
|
|
6
5
|
|
|
7
|
-
from hanzo_mcp.tools.common.base import BaseTool
|
|
8
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
9
6
|
from mcp.server import FastMCP
|
|
7
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
8
|
+
|
|
9
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class ReviewType(Enum):
|
|
13
13
|
"""Types of review requests."""
|
|
14
|
+
|
|
14
15
|
CODE_QUALITY = "code_quality"
|
|
15
16
|
CORRECTNESS = "correctness"
|
|
16
17
|
PERFORMANCE = "performance"
|
|
@@ -22,9 +23,9 @@ class ReviewType(Enum):
|
|
|
22
23
|
|
|
23
24
|
class CriticTool(BaseTool):
|
|
24
25
|
"""Tool for agents to request critical review from the main loop."""
|
|
25
|
-
|
|
26
|
+
|
|
26
27
|
name = "critic"
|
|
27
|
-
|
|
28
|
+
|
|
28
29
|
@property
|
|
29
30
|
@override
|
|
30
31
|
def description(self) -> str:
|
|
@@ -55,7 +56,7 @@ critic(
|
|
|
55
56
|
file_paths=["/path/to/atomic.go", "/path/to/network.go"],
|
|
56
57
|
specific_concerns="Are the imports in the correct format and location?"
|
|
57
58
|
)"""
|
|
58
|
-
|
|
59
|
+
|
|
59
60
|
async def call(
|
|
60
61
|
self,
|
|
61
62
|
ctx: MCPContext,
|
|
@@ -63,16 +64,16 @@ critic(
|
|
|
63
64
|
work_description: str,
|
|
64
65
|
code_snippets: Optional[List[str]] = None,
|
|
65
66
|
file_paths: Optional[List[str]] = None,
|
|
66
|
-
specific_concerns: Optional[str] = None
|
|
67
|
+
specific_concerns: Optional[str] = None,
|
|
67
68
|
) -> str:
|
|
68
69
|
"""This is a placeholder - actual implementation happens in AgentTool."""
|
|
69
70
|
# This tool is handled specially in the agent execution
|
|
70
71
|
return f"Critic review requested for: {work_description}"
|
|
71
|
-
|
|
72
|
+
|
|
72
73
|
def register(self, server: FastMCP) -> None:
|
|
73
74
|
"""Register the tool with the MCP server."""
|
|
74
75
|
tool_self = self
|
|
75
|
-
|
|
76
|
+
|
|
76
77
|
@server.tool(name=self.name, description=self.description)
|
|
77
78
|
async def critic(
|
|
78
79
|
ctx: MCPContext,
|
|
@@ -80,7 +81,7 @@ critic(
|
|
|
80
81
|
work_description: str,
|
|
81
82
|
code_snippets: Optional[List[str]] = None,
|
|
82
83
|
file_paths: Optional[List[str]] = None,
|
|
83
|
-
specific_concerns: Optional[str] = None
|
|
84
|
+
specific_concerns: Optional[str] = None,
|
|
84
85
|
) -> str:
|
|
85
86
|
return await tool_self.call(
|
|
86
87
|
ctx,
|
|
@@ -88,13 +89,13 @@ critic(
|
|
|
88
89
|
work_description,
|
|
89
90
|
code_snippets,
|
|
90
91
|
file_paths,
|
|
91
|
-
specific_concerns
|
|
92
|
+
specific_concerns,
|
|
92
93
|
)
|
|
93
94
|
|
|
94
95
|
|
|
95
96
|
class AutoCritic:
|
|
96
97
|
"""Automated critic that provides harsh but constructive feedback."""
|
|
97
|
-
|
|
98
|
+
|
|
98
99
|
def __init__(self):
|
|
99
100
|
self.review_patterns = {
|
|
100
101
|
ReviewType.CODE_QUALITY: self._review_code_quality,
|
|
@@ -105,82 +106,96 @@ class AutoCritic:
|
|
|
105
106
|
ReviewType.BEST_PRACTICES: self._review_best_practices,
|
|
106
107
|
ReviewType.GENERAL: self._review_general,
|
|
107
108
|
}
|
|
108
|
-
|
|
109
|
+
|
|
109
110
|
def review(
|
|
110
111
|
self,
|
|
111
112
|
review_type: ReviewType,
|
|
112
113
|
work_description: str,
|
|
113
114
|
code_snippets: Optional[List[str]] = None,
|
|
114
115
|
file_paths: Optional[List[str]] = None,
|
|
115
|
-
specific_concerns: Optional[str] = None
|
|
116
|
+
specific_concerns: Optional[str] = None,
|
|
116
117
|
) -> str:
|
|
117
118
|
"""Perform automated critical review."""
|
|
118
119
|
review_func = self.review_patterns.get(review_type, self._review_general)
|
|
119
|
-
return review_func(
|
|
120
|
-
|
|
120
|
+
return review_func(
|
|
121
|
+
work_description, code_snippets, file_paths, specific_concerns
|
|
122
|
+
)
|
|
123
|
+
|
|
121
124
|
def _review_code_quality(
|
|
122
125
|
self,
|
|
123
126
|
work_description: str,
|
|
124
127
|
code_snippets: Optional[List[str]],
|
|
125
128
|
file_paths: Optional[List[str]],
|
|
126
|
-
specific_concerns: Optional[str]
|
|
129
|
+
specific_concerns: Optional[str],
|
|
127
130
|
) -> str:
|
|
128
131
|
"""Review code quality aspects."""
|
|
129
132
|
issues = []
|
|
130
133
|
suggestions = []
|
|
131
|
-
|
|
134
|
+
|
|
132
135
|
# Check for common code quality issues
|
|
133
136
|
if code_snippets:
|
|
134
137
|
for snippet in code_snippets:
|
|
135
138
|
# Check for proper error handling
|
|
136
139
|
if "error" in snippet.lower() and "if err" not in snippet:
|
|
137
|
-
issues.append(
|
|
138
|
-
|
|
140
|
+
issues.append(
|
|
141
|
+
"ā Missing error handling - always check errors in Go"
|
|
142
|
+
)
|
|
143
|
+
|
|
139
144
|
# Check for magic numbers
|
|
140
145
|
if any(char.isdigit() for char in snippet) and "const" not in snippet:
|
|
141
|
-
suggestions.append(
|
|
142
|
-
|
|
146
|
+
suggestions.append(
|
|
147
|
+
"š” Consider extracting magic numbers to named constants"
|
|
148
|
+
)
|
|
149
|
+
|
|
143
150
|
# Check for proper imports
|
|
144
151
|
if "import" in snippet:
|
|
145
152
|
if '"fmt"' in snippet and snippet.count("fmt.") == 0:
|
|
146
153
|
issues.append("ā Unused import 'fmt' - remove unused imports")
|
|
147
154
|
if not snippet.strip().endswith(")") and "import (" in snippet:
|
|
148
155
|
issues.append("ā Import block not properly closed")
|
|
149
|
-
|
|
156
|
+
|
|
150
157
|
# General quality checks
|
|
151
158
|
if "fix" in work_description.lower():
|
|
152
159
|
suggestions.append("š” Ensure you've tested the fix thoroughly")
|
|
153
160
|
suggestions.append("š” Consider edge cases and error scenarios")
|
|
154
|
-
|
|
161
|
+
|
|
155
162
|
if file_paths and len(file_paths) > 5:
|
|
156
|
-
suggestions.append(
|
|
157
|
-
|
|
163
|
+
suggestions.append(
|
|
164
|
+
"š” Large number of files modified - consider breaking into smaller PRs"
|
|
165
|
+
)
|
|
166
|
+
|
|
158
167
|
# Build response
|
|
159
168
|
response = "š CODE QUALITY REVIEW:\n\n"
|
|
160
|
-
|
|
169
|
+
|
|
161
170
|
if issues:
|
|
162
171
|
response += "Issues Found:\n" + "\n".join(issues) + "\n\n"
|
|
163
172
|
else:
|
|
164
173
|
response += "ā
No major code quality issues detected.\n\n"
|
|
165
|
-
|
|
174
|
+
|
|
166
175
|
if suggestions:
|
|
167
|
-
response +=
|
|
168
|
-
|
|
176
|
+
response += (
|
|
177
|
+
"Suggestions for Improvement:\n" + "\n".join(suggestions) + "\n\n"
|
|
178
|
+
)
|
|
179
|
+
|
|
169
180
|
if specific_concerns:
|
|
170
181
|
response += f"Regarding your concern: '{specific_concerns}'\n"
|
|
171
182
|
if "import" in specific_concerns.lower():
|
|
172
183
|
response += "ā Imports look properly formatted. Ensure they're in the standard order: stdlib, external, internal.\n"
|
|
173
|
-
|
|
174
|
-
response += "\nOverall: " + (
|
|
175
|
-
|
|
184
|
+
|
|
185
|
+
response += "\nOverall: " + (
|
|
186
|
+
"ā ļø Address the issues before proceeding."
|
|
187
|
+
if issues
|
|
188
|
+
else "ā
Good work, but always room for improvement!"
|
|
189
|
+
)
|
|
190
|
+
|
|
176
191
|
return response
|
|
177
|
-
|
|
192
|
+
|
|
178
193
|
def _review_correctness(
|
|
179
194
|
self,
|
|
180
195
|
work_description: str,
|
|
181
196
|
code_snippets: Optional[List[str]],
|
|
182
197
|
file_paths: Optional[List[str]],
|
|
183
|
-
specific_concerns: Optional[str]
|
|
198
|
+
specific_concerns: Optional[str],
|
|
184
199
|
) -> str:
|
|
185
200
|
"""Review correctness aspects."""
|
|
186
201
|
return """š CORRECTNESS REVIEW:
|
|
@@ -197,13 +212,13 @@ Specific Checks:
|
|
|
197
212
|
- If refactoring: Confirm behavior is preserved
|
|
198
213
|
|
|
199
214
|
ā ļø Remember: Working code > elegant code. Make sure it works first!"""
|
|
200
|
-
|
|
215
|
+
|
|
201
216
|
def _review_performance(
|
|
202
217
|
self,
|
|
203
218
|
work_description: str,
|
|
204
219
|
code_snippets: Optional[List[str]],
|
|
205
220
|
file_paths: Optional[List[str]],
|
|
206
|
-
specific_concerns: Optional[str]
|
|
221
|
+
specific_concerns: Optional[str],
|
|
207
222
|
) -> str:
|
|
208
223
|
"""Review performance aspects."""
|
|
209
224
|
return """š PERFORMANCE REVIEW:
|
|
@@ -220,13 +235,13 @@ For file operations:
|
|
|
220
235
|
- Avoid reading entire files into memory if possible
|
|
221
236
|
|
|
222
237
|
š” Remember: Premature optimization is evil, but obvious inefficiencies should be fixed."""
|
|
223
|
-
|
|
238
|
+
|
|
224
239
|
def _review_security(
|
|
225
240
|
self,
|
|
226
241
|
work_description: str,
|
|
227
242
|
code_snippets: Optional[List[str]],
|
|
228
243
|
file_paths: Optional[List[str]],
|
|
229
|
-
specific_concerns: Optional[str]
|
|
244
|
+
specific_concerns: Optional[str],
|
|
230
245
|
) -> str:
|
|
231
246
|
"""Review security aspects."""
|
|
232
247
|
return """š SECURITY REVIEW:
|
|
@@ -240,41 +255,43 @@ Security Checklist:
|
|
|
240
255
|
š Sensitive data is not logged
|
|
241
256
|
|
|
242
257
|
ā ļø If in doubt, err on the side of caution!"""
|
|
243
|
-
|
|
258
|
+
|
|
244
259
|
def _review_completeness(
|
|
245
260
|
self,
|
|
246
261
|
work_description: str,
|
|
247
262
|
code_snippets: Optional[List[str]],
|
|
248
263
|
file_paths: Optional[List[str]],
|
|
249
|
-
specific_concerns: Optional[str]
|
|
264
|
+
specific_concerns: Optional[str],
|
|
250
265
|
) -> str:
|
|
251
266
|
"""Review completeness aspects."""
|
|
252
267
|
tasks_mentioned = work_description.lower()
|
|
253
|
-
|
|
268
|
+
|
|
254
269
|
response = "š COMPLETENESS REVIEW:\n\n"
|
|
255
|
-
|
|
270
|
+
|
|
256
271
|
if "fix" in tasks_mentioned and "test" not in tasks_mentioned:
|
|
257
|
-
response +=
|
|
258
|
-
|
|
272
|
+
response += (
|
|
273
|
+
"ā No mention of tests - have you verified the fix with tests?\n"
|
|
274
|
+
)
|
|
275
|
+
|
|
259
276
|
if "import" in tasks_mentioned:
|
|
260
277
|
response += "ā Import fixes mentioned\n"
|
|
261
278
|
response += "ā Have you checked for other files with similar issues?\n"
|
|
262
279
|
response += "ā Are all undefined symbols now resolved?\n"
|
|
263
|
-
|
|
280
|
+
|
|
264
281
|
if file_paths:
|
|
265
282
|
response += f"\nā Modified {len(file_paths)} files\n"
|
|
266
283
|
response += "ā Are there any related files that also need updates?\n"
|
|
267
|
-
|
|
284
|
+
|
|
268
285
|
response += "\nš” Completeness means not just fixing the immediate issue, but considering the broader impact."
|
|
269
|
-
|
|
286
|
+
|
|
270
287
|
return response
|
|
271
|
-
|
|
288
|
+
|
|
272
289
|
def _review_best_practices(
|
|
273
290
|
self,
|
|
274
291
|
work_description: str,
|
|
275
292
|
code_snippets: Optional[List[str]],
|
|
276
293
|
file_paths: Optional[List[str]],
|
|
277
|
-
specific_concerns: Optional[str]
|
|
294
|
+
specific_concerns: Optional[str],
|
|
278
295
|
) -> str:
|
|
279
296
|
"""Review best practices."""
|
|
280
297
|
return """š BEST PRACTICES REVIEW:
|
|
@@ -293,21 +310,21 @@ General Best Practices:
|
|
|
293
310
|
ā Changes are minimal and focused
|
|
294
311
|
|
|
295
312
|
š” Good code is code that others (including future you) can understand and modify."""
|
|
296
|
-
|
|
313
|
+
|
|
297
314
|
def _review_general(
|
|
298
315
|
self,
|
|
299
316
|
work_description: str,
|
|
300
317
|
code_snippets: Optional[List[str]],
|
|
301
318
|
file_paths: Optional[List[str]],
|
|
302
|
-
specific_concerns: Optional[str]
|
|
319
|
+
specific_concerns: Optional[str],
|
|
303
320
|
) -> str:
|
|
304
321
|
"""General review covering multiple aspects."""
|
|
305
322
|
response = "š GENERAL CRITICAL REVIEW:\n\n"
|
|
306
323
|
response += f"Work Description: {work_description}\n\n"
|
|
307
|
-
|
|
324
|
+
|
|
308
325
|
# Quick assessment
|
|
309
326
|
response += "Quick Assessment:\n"
|
|
310
|
-
|
|
327
|
+
|
|
311
328
|
if "fix" in work_description.lower():
|
|
312
329
|
response += "- Type: Bug fix / Error resolution\n"
|
|
313
330
|
response += "- Critical: Ensure the fix is complete and tested\n"
|
|
@@ -317,60 +334,56 @@ General Best Practices:
|
|
|
317
334
|
elif "refactor" in work_description.lower():
|
|
318
335
|
response += "- Type: Code refactoring\n"
|
|
319
336
|
response += "- Critical: Ensure behavior is preserved\n"
|
|
320
|
-
|
|
337
|
+
|
|
321
338
|
if file_paths:
|
|
322
339
|
response += f"- Scope: {len(file_paths)} files affected\n"
|
|
323
340
|
if len(file_paths) > 10:
|
|
324
341
|
response += "- ā ļø Large scope - consider breaking down\n"
|
|
325
|
-
|
|
342
|
+
|
|
326
343
|
response += "\nCritical Questions:\n"
|
|
327
344
|
response += "1. Is this the minimal change needed?\n"
|
|
328
345
|
response += "2. Have you considered all edge cases?\n"
|
|
329
346
|
response += "3. Will this work in production?\n"
|
|
330
347
|
response += "4. Is there a simpler solution?\n"
|
|
331
|
-
|
|
348
|
+
|
|
332
349
|
if specific_concerns:
|
|
333
350
|
response += f"\nYour Concern: {specific_concerns}\n"
|
|
334
351
|
response += "ā Valid concern. Double-check this area carefully.\n"
|
|
335
|
-
|
|
352
|
+
|
|
336
353
|
response += "\nšÆ Bottom Line: Good work needs critical thinking. Question everything, verify everything."
|
|
337
|
-
|
|
354
|
+
|
|
338
355
|
return response
|
|
339
356
|
|
|
340
357
|
|
|
341
358
|
class CriticProtocol:
|
|
342
359
|
"""Protocol for critic interactions."""
|
|
343
|
-
|
|
360
|
+
|
|
344
361
|
def __init__(self):
|
|
345
362
|
self.auto_critic = AutoCritic()
|
|
346
363
|
self.review_count = 0
|
|
347
364
|
self.max_reviews = 2 # Allow up to 2 reviews per task
|
|
348
|
-
|
|
365
|
+
|
|
349
366
|
def request_review(
|
|
350
367
|
self,
|
|
351
368
|
review_type: str,
|
|
352
369
|
work_description: str,
|
|
353
370
|
code_snippets: Optional[List[str]] = None,
|
|
354
371
|
file_paths: Optional[List[str]] = None,
|
|
355
|
-
specific_concerns: Optional[str] = None
|
|
372
|
+
specific_concerns: Optional[str] = None,
|
|
356
373
|
) -> str:
|
|
357
374
|
"""Request a critical review."""
|
|
358
375
|
if self.review_count >= self.max_reviews:
|
|
359
376
|
return "ā Review limit exceeded. Time to move forward with what you have."
|
|
360
|
-
|
|
377
|
+
|
|
361
378
|
self.review_count += 1
|
|
362
|
-
|
|
379
|
+
|
|
363
380
|
try:
|
|
364
381
|
review_enum = ReviewType[review_type.upper()]
|
|
365
382
|
except KeyError:
|
|
366
383
|
review_enum = ReviewType.GENERAL
|
|
367
|
-
|
|
384
|
+
|
|
368
385
|
review = self.auto_critic.review(
|
|
369
|
-
review_enum,
|
|
370
|
-
work_description,
|
|
371
|
-
code_snippets,
|
|
372
|
-
file_paths,
|
|
373
|
-
specific_concerns
|
|
386
|
+
review_enum, work_description, code_snippets, file_paths, specific_concerns
|
|
374
387
|
)
|
|
375
|
-
|
|
376
|
-
return f"Review {self.review_count}/{self.max_reviews}:\n\n{review}"
|
|
388
|
+
|
|
389
|
+
return f"Review {self.review_count}/{self.max_reviews}:\n\n{review}"
|