hanzo-mcp 0.6.12__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +2 -2
- hanzo_mcp/analytics/__init__.py +5 -0
- hanzo_mcp/analytics/posthog_analytics.py +364 -0
- hanzo_mcp/cli.py +5 -5
- hanzo_mcp/cli_enhanced.py +7 -7
- hanzo_mcp/cli_plugin.py +91 -0
- hanzo_mcp/config/__init__.py +1 -1
- hanzo_mcp/config/settings.py +70 -7
- hanzo_mcp/config/tool_config.py +20 -6
- hanzo_mcp/dev_server.py +3 -3
- hanzo_mcp/prompts/project_system.py +1 -1
- hanzo_mcp/server.py +40 -3
- hanzo_mcp/server_enhanced.py +69 -0
- hanzo_mcp/tools/__init__.py +140 -31
- hanzo_mcp/tools/agent/__init__.py +85 -4
- hanzo_mcp/tools/agent/agent_tool.py +104 -6
- hanzo_mcp/tools/agent/agent_tool_v2.py +459 -0
- hanzo_mcp/tools/agent/clarification_protocol.py +220 -0
- hanzo_mcp/tools/agent/clarification_tool.py +68 -0
- hanzo_mcp/tools/agent/claude_cli_tool.py +125 -0
- hanzo_mcp/tools/agent/claude_desktop_auth.py +508 -0
- hanzo_mcp/tools/agent/cli_agent_base.py +191 -0
- hanzo_mcp/tools/agent/code_auth.py +436 -0
- hanzo_mcp/tools/agent/code_auth_tool.py +194 -0
- hanzo_mcp/tools/agent/codex_cli_tool.py +123 -0
- hanzo_mcp/tools/agent/critic_tool.py +376 -0
- hanzo_mcp/tools/agent/gemini_cli_tool.py +128 -0
- hanzo_mcp/tools/agent/grok_cli_tool.py +128 -0
- hanzo_mcp/tools/agent/iching_tool.py +380 -0
- hanzo_mcp/tools/agent/network_tool.py +273 -0
- hanzo_mcp/tools/agent/prompt.py +62 -20
- hanzo_mcp/tools/agent/review_tool.py +433 -0
- hanzo_mcp/tools/agent/swarm_tool.py +535 -0
- hanzo_mcp/tools/agent/swarm_tool_v2.py +594 -0
- hanzo_mcp/tools/common/__init__.py +15 -1
- hanzo_mcp/tools/common/base.py +5 -4
- hanzo_mcp/tools/common/batch_tool.py +103 -11
- hanzo_mcp/tools/common/config_tool.py +2 -2
- hanzo_mcp/tools/common/context.py +2 -2
- hanzo_mcp/tools/common/context_fix.py +26 -0
- hanzo_mcp/tools/common/critic_tool.py +196 -0
- hanzo_mcp/tools/common/decorators.py +208 -0
- hanzo_mcp/tools/common/enhanced_base.py +106 -0
- hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
- hanzo_mcp/tools/common/forgiving_edit.py +243 -0
- hanzo_mcp/tools/common/mode.py +116 -0
- hanzo_mcp/tools/common/mode_loader.py +105 -0
- hanzo_mcp/tools/common/paginated_base.py +230 -0
- hanzo_mcp/tools/common/paginated_response.py +307 -0
- hanzo_mcp/tools/common/pagination.py +226 -0
- hanzo_mcp/tools/common/permissions.py +1 -1
- hanzo_mcp/tools/common/personality.py +936 -0
- hanzo_mcp/tools/common/plugin_loader.py +287 -0
- hanzo_mcp/tools/common/stats.py +4 -4
- hanzo_mcp/tools/common/tool_list.py +4 -1
- hanzo_mcp/tools/common/truncate.py +101 -0
- hanzo_mcp/tools/common/validation.py +1 -1
- hanzo_mcp/tools/config/__init__.py +3 -1
- hanzo_mcp/tools/config/config_tool.py +1 -1
- hanzo_mcp/tools/config/mode_tool.py +209 -0
- hanzo_mcp/tools/database/__init__.py +1 -1
- hanzo_mcp/tools/editor/__init__.py +1 -1
- hanzo_mcp/tools/filesystem/__init__.py +48 -14
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
- hanzo_mcp/tools/filesystem/batch_search.py +3 -3
- hanzo_mcp/tools/filesystem/diff.py +2 -2
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
- hanzo_mcp/tools/filesystem/rules_tool.py +235 -0
- hanzo_mcp/tools/filesystem/{unified_search.py → search_tool.py} +12 -12
- hanzo_mcp/tools/filesystem/{symbols_unified.py → symbols_tool.py} +104 -5
- hanzo_mcp/tools/filesystem/watch.py +3 -2
- hanzo_mcp/tools/jupyter/__init__.py +2 -2
- hanzo_mcp/tools/jupyter/jupyter.py +1 -1
- hanzo_mcp/tools/llm/__init__.py +3 -3
- hanzo_mcp/tools/llm/llm_tool.py +648 -143
- hanzo_mcp/tools/lsp/__init__.py +5 -0
- hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
- hanzo_mcp/tools/mcp/__init__.py +2 -2
- hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
- hanzo_mcp/tools/memory/__init__.py +76 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +518 -0
- hanzo_mcp/tools/memory/memory_tools.py +456 -0
- hanzo_mcp/tools/search/__init__.py +6 -0
- hanzo_mcp/tools/search/find_tool.py +581 -0
- hanzo_mcp/tools/search/unified_search.py +953 -0
- hanzo_mcp/tools/shell/__init__.py +11 -6
- hanzo_mcp/tools/shell/auto_background.py +203 -0
- hanzo_mcp/tools/shell/base_process.py +57 -29
- hanzo_mcp/tools/shell/bash_session_executor.py +1 -1
- hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +18 -34
- hanzo_mcp/tools/shell/command_executor.py +2 -2
- hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +16 -33
- hanzo_mcp/tools/shell/open.py +2 -2
- hanzo_mcp/tools/shell/{process_unified.py → process_tool.py} +1 -1
- hanzo_mcp/tools/shell/run_command_windows.py +1 -1
- hanzo_mcp/tools/shell/streaming_command.py +594 -0
- hanzo_mcp/tools/shell/uvx.py +47 -2
- hanzo_mcp/tools/shell/uvx_background.py +47 -2
- hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +16 -33
- hanzo_mcp/tools/todo/__init__.py +14 -19
- hanzo_mcp/tools/todo/todo.py +22 -1
- hanzo_mcp/tools/vector/__init__.py +1 -1
- hanzo_mcp/tools/vector/infinity_store.py +2 -2
- hanzo_mcp/tools/vector/project_manager.py +1 -1
- hanzo_mcp/types.py +23 -0
- hanzo_mcp-0.7.0.dist-info/METADATA +516 -0
- hanzo_mcp-0.7.0.dist-info/RECORD +180 -0
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +1 -0
- hanzo_mcp/tools/common/palette.py +0 -344
- hanzo_mcp/tools/common/palette_loader.py +0 -108
- hanzo_mcp/tools/config/palette_tool.py +0 -179
- hanzo_mcp/tools/llm/llm_unified.py +0 -851
- hanzo_mcp-0.6.12.dist-info/METADATA +0 -339
- hanzo_mcp-0.6.12.dist-info/RECORD +0 -135
- hanzo_mcp-0.6.12.dist-info/licenses/LICENSE +0 -21
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Claude Code authentication tool.
|
|
2
|
+
|
|
3
|
+
This tool manages API keys and accounts for Claude Code and other AI coding tools.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Annotated, Optional, TypedDict, Unpack, final, override
|
|
7
|
+
from mcp.server import FastMCP
|
|
8
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
12
|
+
from hanzo_mcp.tools.common.context import create_tool_context
|
|
13
|
+
from hanzo_mcp.tools.agent.code_auth import CodeAuthManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CodeAuthParams(TypedDict, total=False):
|
|
17
|
+
"""Parameters for code auth tool."""
|
|
18
|
+
action: str
|
|
19
|
+
account: Optional[str]
|
|
20
|
+
provider: Optional[str]
|
|
21
|
+
api_key: Optional[str]
|
|
22
|
+
model: Optional[str]
|
|
23
|
+
description: Optional[str]
|
|
24
|
+
agent_id: Optional[str]
|
|
25
|
+
parent_account: Optional[str]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@final
|
|
29
|
+
class CodeAuthTool(BaseTool):
|
|
30
|
+
"""Tool for managing Claude Code authentication and API keys."""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
@override
|
|
34
|
+
def name(self) -> str:
|
|
35
|
+
"""Get the tool name."""
|
|
36
|
+
return "code_auth"
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
@override
|
|
40
|
+
def description(self) -> str:
|
|
41
|
+
"""Get the tool description."""
|
|
42
|
+
return """Manage Claude Code and AI provider authentication.
|
|
43
|
+
|
|
44
|
+
Actions:
|
|
45
|
+
- status: Show current login status
|
|
46
|
+
- list: List all accounts
|
|
47
|
+
- create: Create a new account
|
|
48
|
+
- login: Login to an account
|
|
49
|
+
- logout: Logout current account
|
|
50
|
+
- switch: Switch between accounts
|
|
51
|
+
- agent: Create/get agent account
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
code_auth status
|
|
55
|
+
code_auth list
|
|
56
|
+
code_auth create --account work --provider claude
|
|
57
|
+
code_auth login --account work
|
|
58
|
+
code_auth logout
|
|
59
|
+
code_auth switch --account personal
|
|
60
|
+
code_auth agent --agent_id swarm_1 --parent_account work
|
|
61
|
+
|
|
62
|
+
Providers: claude, openai, azure, deepseek, google, groq"""
|
|
63
|
+
|
|
64
|
+
def __init__(self):
|
|
65
|
+
"""Initialize the code auth tool."""
|
|
66
|
+
self.auth_manager = CodeAuthManager()
|
|
67
|
+
|
|
68
|
+
@override
|
|
69
|
+
async def call(
|
|
70
|
+
self,
|
|
71
|
+
ctx: MCPContext,
|
|
72
|
+
**params: Unpack[CodeAuthParams],
|
|
73
|
+
) -> str:
|
|
74
|
+
"""Execute the code auth tool.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
ctx: MCP context
|
|
78
|
+
**params: Tool parameters
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Result message
|
|
82
|
+
"""
|
|
83
|
+
tool_ctx = create_tool_context(ctx)
|
|
84
|
+
await tool_ctx.set_tool_info(self.name)
|
|
85
|
+
|
|
86
|
+
action = params.get("action", "status")
|
|
87
|
+
|
|
88
|
+
if action == "status":
|
|
89
|
+
current = self.auth_manager.get_active_account()
|
|
90
|
+
if current:
|
|
91
|
+
info = self.auth_manager.get_account_info(current)
|
|
92
|
+
if info:
|
|
93
|
+
return f"Logged in as: {current} ({info['provider']})"
|
|
94
|
+
return "Not logged in"
|
|
95
|
+
|
|
96
|
+
elif action == "list":
|
|
97
|
+
accounts = self.auth_manager.list_accounts()
|
|
98
|
+
if not accounts:
|
|
99
|
+
return "No accounts configured"
|
|
100
|
+
|
|
101
|
+
current = self.auth_manager.get_active_account()
|
|
102
|
+
lines = ["Configured accounts:"]
|
|
103
|
+
for account in accounts:
|
|
104
|
+
info = self.auth_manager.get_account_info(account)
|
|
105
|
+
marker = " (active)" if account == current else ""
|
|
106
|
+
lines.append(f" - {account}: {info['provider']}{marker}")
|
|
107
|
+
return "\n".join(lines)
|
|
108
|
+
|
|
109
|
+
elif action == "create":
|
|
110
|
+
account = params.get("account")
|
|
111
|
+
if not account:
|
|
112
|
+
return "Error: account name required"
|
|
113
|
+
|
|
114
|
+
provider = params.get("provider", "claude")
|
|
115
|
+
api_key = params.get("api_key")
|
|
116
|
+
model = params.get("model")
|
|
117
|
+
description = params.get("description")
|
|
118
|
+
|
|
119
|
+
success, msg = self.auth_manager.create_account(
|
|
120
|
+
account, provider, api_key, model, description
|
|
121
|
+
)
|
|
122
|
+
return msg
|
|
123
|
+
|
|
124
|
+
elif action == "login":
|
|
125
|
+
account = params.get("account", "default")
|
|
126
|
+
success, msg = self.auth_manager.login(account)
|
|
127
|
+
return msg
|
|
128
|
+
|
|
129
|
+
elif action == "logout":
|
|
130
|
+
success, msg = self.auth_manager.logout()
|
|
131
|
+
return msg
|
|
132
|
+
|
|
133
|
+
elif action == "switch":
|
|
134
|
+
account = params.get("account")
|
|
135
|
+
if not account:
|
|
136
|
+
return "Error: account name required"
|
|
137
|
+
|
|
138
|
+
success, msg = self.auth_manager.switch_account(account)
|
|
139
|
+
return msg
|
|
140
|
+
|
|
141
|
+
elif action == "agent":
|
|
142
|
+
agent_id = params.get("agent_id")
|
|
143
|
+
if not agent_id:
|
|
144
|
+
return "Error: agent_id required"
|
|
145
|
+
|
|
146
|
+
provider = params.get("provider", "claude")
|
|
147
|
+
parent_account = params.get("parent_account")
|
|
148
|
+
|
|
149
|
+
# Try to create agent account
|
|
150
|
+
success, result = self.auth_manager.create_agent_account(
|
|
151
|
+
agent_id, provider, parent_account
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if success:
|
|
155
|
+
# Get credentials
|
|
156
|
+
creds = self.auth_manager.get_agent_credentials(agent_id)
|
|
157
|
+
if creds:
|
|
158
|
+
return f"Agent account ready: {result} ({creds.provider})"
|
|
159
|
+
else:
|
|
160
|
+
return f"Agent account created but no credentials: {result}"
|
|
161
|
+
else:
|
|
162
|
+
return f"Failed to create agent account: {result}"
|
|
163
|
+
|
|
164
|
+
else:
|
|
165
|
+
return f"Unknown action: {action}. Use: status, list, create, login, logout, switch, agent"
|
|
166
|
+
|
|
167
|
+
@override
|
|
168
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
169
|
+
"""Register this tool with the MCP server."""
|
|
170
|
+
tool_self = self
|
|
171
|
+
|
|
172
|
+
@mcp_server.tool(name=self.name, description=self.description)
|
|
173
|
+
async def code_auth(
|
|
174
|
+
ctx: MCPContext,
|
|
175
|
+
action: str = "status",
|
|
176
|
+
account: Optional[str] = None,
|
|
177
|
+
provider: Optional[str] = None,
|
|
178
|
+
api_key: Optional[str] = None,
|
|
179
|
+
model: Optional[str] = None,
|
|
180
|
+
description: Optional[str] = None,
|
|
181
|
+
agent_id: Optional[str] = None,
|
|
182
|
+
parent_account: Optional[str] = None,
|
|
183
|
+
) -> str:
|
|
184
|
+
return await tool_self.call(
|
|
185
|
+
ctx,
|
|
186
|
+
action=action,
|
|
187
|
+
account=account,
|
|
188
|
+
provider=provider,
|
|
189
|
+
api_key=api_key,
|
|
190
|
+
model=model,
|
|
191
|
+
description=description,
|
|
192
|
+
agent_id=agent_id,
|
|
193
|
+
parent_account=parent_account,
|
|
194
|
+
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""OpenAI Codex CLI agent tool.
|
|
2
|
+
|
|
3
|
+
This tool provides integration with OpenAI's CLI (openai command),
|
|
4
|
+
allowing programmatic execution of GPT-4 and other models for code tasks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Optional, override, final
|
|
8
|
+
from mcp.server import FastMCP
|
|
9
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
10
|
+
|
|
11
|
+
from hanzo_mcp.tools.agent.cli_agent_base import CLIAgentBase
|
|
12
|
+
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@final
|
|
16
|
+
class CodexCLITool(CLIAgentBase):
|
|
17
|
+
"""Tool for executing OpenAI CLI (formerly Codex)."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
permission_manager: PermissionManager,
|
|
22
|
+
model: Optional[str] = None,
|
|
23
|
+
**kwargs
|
|
24
|
+
):
|
|
25
|
+
"""Initialize Codex CLI tool.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
permission_manager: Permission manager for access control
|
|
29
|
+
model: Optional model override (defaults to gpt-4o)
|
|
30
|
+
**kwargs: Additional arguments
|
|
31
|
+
"""
|
|
32
|
+
super().__init__(
|
|
33
|
+
permission_manager=permission_manager,
|
|
34
|
+
command_name="openai",
|
|
35
|
+
provider_name="OpenAI",
|
|
36
|
+
default_model=model or "gpt-4o",
|
|
37
|
+
env_vars=["OPENAI_API_KEY"],
|
|
38
|
+
**kwargs
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@override
|
|
43
|
+
def name(self) -> str:
|
|
44
|
+
"""Get the tool name."""
|
|
45
|
+
return "codex_cli"
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
@override
|
|
49
|
+
def description(self) -> str:
|
|
50
|
+
"""Get the tool description."""
|
|
51
|
+
return """Execute OpenAI CLI for code generation and analysis.
|
|
52
|
+
|
|
53
|
+
This tool runs the OpenAI CLI (openai command) for code generation,
|
|
54
|
+
completion, and analysis tasks. It uses GPT-4o by default but supports
|
|
55
|
+
all OpenAI models.
|
|
56
|
+
|
|
57
|
+
Features:
|
|
58
|
+
- GPT-4 and GPT-4o for advanced reasoning
|
|
59
|
+
- Code generation and completion
|
|
60
|
+
- Multi-modal support (with gpt-4-vision)
|
|
61
|
+
- Function calling capabilities
|
|
62
|
+
|
|
63
|
+
Usage:
|
|
64
|
+
codex_cli(prompts="Generate a Python function to sort a binary tree")
|
|
65
|
+
codex_cli(prompts="Explain this code and suggest improvements", model="gpt-4-turbo")
|
|
66
|
+
|
|
67
|
+
Requirements:
|
|
68
|
+
- OpenAI CLI must be installed: pip install openai
|
|
69
|
+
- OPENAI_API_KEY environment variable
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@override
|
|
73
|
+
def get_cli_args(self, prompt: str, **kwargs) -> List[str]:
|
|
74
|
+
"""Get CLI arguments for OpenAI.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
prompt: The prompt to send
|
|
78
|
+
**kwargs: Additional arguments (model, temperature, etc.)
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of command arguments
|
|
82
|
+
"""
|
|
83
|
+
args = ["api", "chat.completions.create"]
|
|
84
|
+
|
|
85
|
+
# Add model
|
|
86
|
+
model = kwargs.get("model", self.default_model)
|
|
87
|
+
args.extend(["-m", model])
|
|
88
|
+
|
|
89
|
+
# Add temperature if specified
|
|
90
|
+
if "temperature" in kwargs:
|
|
91
|
+
args.extend(["--temperature", str(kwargs["temperature"])])
|
|
92
|
+
|
|
93
|
+
# Add max tokens if specified
|
|
94
|
+
if "max_tokens" in kwargs:
|
|
95
|
+
args.extend(["--max-tokens", str(kwargs["max_tokens"])])
|
|
96
|
+
|
|
97
|
+
# Add the prompt as a message
|
|
98
|
+
args.extend(["-g", prompt])
|
|
99
|
+
|
|
100
|
+
return args
|
|
101
|
+
|
|
102
|
+
@override
|
|
103
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
104
|
+
"""Register this tool with the MCP server."""
|
|
105
|
+
tool_self = self
|
|
106
|
+
|
|
107
|
+
@mcp_server.tool(name=self.name, description=self.description)
|
|
108
|
+
async def codex_cli(
|
|
109
|
+
ctx: MCPContext,
|
|
110
|
+
prompts: str,
|
|
111
|
+
model: Optional[str] = None,
|
|
112
|
+
temperature: Optional[float] = None,
|
|
113
|
+
max_tokens: Optional[int] = None,
|
|
114
|
+
working_dir: Optional[str] = None,
|
|
115
|
+
) -> str:
|
|
116
|
+
return await tool_self.call(
|
|
117
|
+
ctx,
|
|
118
|
+
prompts=prompts,
|
|
119
|
+
model=model,
|
|
120
|
+
temperature=temperature,
|
|
121
|
+
max_tokens=max_tokens,
|
|
122
|
+
working_dir=working_dir,
|
|
123
|
+
)
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""Critic tool for agents to request critical review from main loop."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, List, Optional, override
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
8
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
9
|
+
from mcp.server import FastMCP
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ReviewType(Enum):
|
|
13
|
+
"""Types of review requests."""
|
|
14
|
+
CODE_QUALITY = "code_quality"
|
|
15
|
+
CORRECTNESS = "correctness"
|
|
16
|
+
PERFORMANCE = "performance"
|
|
17
|
+
SECURITY = "security"
|
|
18
|
+
COMPLETENESS = "completeness"
|
|
19
|
+
BEST_PRACTICES = "best_practices"
|
|
20
|
+
GENERAL = "general"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CriticTool(BaseTool):
|
|
24
|
+
"""Tool for agents to request critical review from the main loop."""
|
|
25
|
+
|
|
26
|
+
name = "critic"
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
@override
|
|
30
|
+
def description(self) -> str:
|
|
31
|
+
"""Get the tool description."""
|
|
32
|
+
return """Request critical review and feedback from the main loop.
|
|
33
|
+
|
|
34
|
+
Use this tool to get automated critical analysis of your work. The main loop will:
|
|
35
|
+
- Review your implementation for bugs, edge cases, and improvements
|
|
36
|
+
- Check for security issues and best practices
|
|
37
|
+
- Suggest performance optimizations
|
|
38
|
+
- Ensure completeness and correctness
|
|
39
|
+
- Provide actionable feedback for improvements
|
|
40
|
+
|
|
41
|
+
Parameters:
|
|
42
|
+
- review_type: Type of review (CODE_QUALITY, CORRECTNESS, PERFORMANCE, SECURITY, COMPLETENESS, BEST_PRACTICES, GENERAL)
|
|
43
|
+
- work_description: Clear description of what you've done
|
|
44
|
+
- code_snippets: Optional code snippets to review (as a list of strings)
|
|
45
|
+
- file_paths: Optional list of file paths you've modified
|
|
46
|
+
- specific_concerns: Optional specific areas you want reviewed
|
|
47
|
+
|
|
48
|
+
The critic will provide harsh but constructive feedback to ensure high quality.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
critic(
|
|
52
|
+
review_type="CODE_QUALITY",
|
|
53
|
+
work_description="Added import statements to fix undefined symbols in Go files",
|
|
54
|
+
code_snippets=["import (\n \"fmt\"\n \"github.com/luxfi/node/common\"\n)"],
|
|
55
|
+
file_paths=["/path/to/atomic.go", "/path/to/network.go"],
|
|
56
|
+
specific_concerns="Are the imports in the correct format and location?"
|
|
57
|
+
)"""
|
|
58
|
+
|
|
59
|
+
async def call(
|
|
60
|
+
self,
|
|
61
|
+
ctx: MCPContext,
|
|
62
|
+
review_type: str,
|
|
63
|
+
work_description: str,
|
|
64
|
+
code_snippets: Optional[List[str]] = None,
|
|
65
|
+
file_paths: Optional[List[str]] = None,
|
|
66
|
+
specific_concerns: Optional[str] = None
|
|
67
|
+
) -> str:
|
|
68
|
+
"""This is a placeholder - actual implementation happens in AgentTool."""
|
|
69
|
+
# This tool is handled specially in the agent execution
|
|
70
|
+
return f"Critic review requested for: {work_description}"
|
|
71
|
+
|
|
72
|
+
def register(self, server: FastMCP) -> None:
|
|
73
|
+
"""Register the tool with the MCP server."""
|
|
74
|
+
tool_self = self
|
|
75
|
+
|
|
76
|
+
@server.tool(name=self.name, description=self.description)
|
|
77
|
+
async def critic(
|
|
78
|
+
ctx: MCPContext,
|
|
79
|
+
review_type: str,
|
|
80
|
+
work_description: str,
|
|
81
|
+
code_snippets: Optional[List[str]] = None,
|
|
82
|
+
file_paths: Optional[List[str]] = None,
|
|
83
|
+
specific_concerns: Optional[str] = None
|
|
84
|
+
) -> str:
|
|
85
|
+
return await tool_self.call(
|
|
86
|
+
ctx,
|
|
87
|
+
review_type,
|
|
88
|
+
work_description,
|
|
89
|
+
code_snippets,
|
|
90
|
+
file_paths,
|
|
91
|
+
specific_concerns
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class AutoCritic:
|
|
96
|
+
"""Automated critic that provides harsh but constructive feedback."""
|
|
97
|
+
|
|
98
|
+
def __init__(self):
|
|
99
|
+
self.review_patterns = {
|
|
100
|
+
ReviewType.CODE_QUALITY: self._review_code_quality,
|
|
101
|
+
ReviewType.CORRECTNESS: self._review_correctness,
|
|
102
|
+
ReviewType.PERFORMANCE: self._review_performance,
|
|
103
|
+
ReviewType.SECURITY: self._review_security,
|
|
104
|
+
ReviewType.COMPLETENESS: self._review_completeness,
|
|
105
|
+
ReviewType.BEST_PRACTICES: self._review_best_practices,
|
|
106
|
+
ReviewType.GENERAL: self._review_general,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def review(
|
|
110
|
+
self,
|
|
111
|
+
review_type: ReviewType,
|
|
112
|
+
work_description: str,
|
|
113
|
+
code_snippets: Optional[List[str]] = None,
|
|
114
|
+
file_paths: Optional[List[str]] = None,
|
|
115
|
+
specific_concerns: Optional[str] = None
|
|
116
|
+
) -> str:
|
|
117
|
+
"""Perform automated critical review."""
|
|
118
|
+
review_func = self.review_patterns.get(review_type, self._review_general)
|
|
119
|
+
return review_func(work_description, code_snippets, file_paths, specific_concerns)
|
|
120
|
+
|
|
121
|
+
def _review_code_quality(
|
|
122
|
+
self,
|
|
123
|
+
work_description: str,
|
|
124
|
+
code_snippets: Optional[List[str]],
|
|
125
|
+
file_paths: Optional[List[str]],
|
|
126
|
+
specific_concerns: Optional[str]
|
|
127
|
+
) -> str:
|
|
128
|
+
"""Review code quality aspects."""
|
|
129
|
+
issues = []
|
|
130
|
+
suggestions = []
|
|
131
|
+
|
|
132
|
+
# Check for common code quality issues
|
|
133
|
+
if code_snippets:
|
|
134
|
+
for snippet in code_snippets:
|
|
135
|
+
# Check for proper error handling
|
|
136
|
+
if "error" in snippet.lower() and "if err" not in snippet:
|
|
137
|
+
issues.append("❌ Missing error handling - always check errors in Go")
|
|
138
|
+
|
|
139
|
+
# Check for magic numbers
|
|
140
|
+
if any(char.isdigit() for char in snippet) and "const" not in snippet:
|
|
141
|
+
suggestions.append("💡 Consider extracting magic numbers to named constants")
|
|
142
|
+
|
|
143
|
+
# Check for proper imports
|
|
144
|
+
if "import" in snippet:
|
|
145
|
+
if '"fmt"' in snippet and snippet.count("fmt.") == 0:
|
|
146
|
+
issues.append("❌ Unused import 'fmt' - remove unused imports")
|
|
147
|
+
if not snippet.strip().endswith(")") and "import (" in snippet:
|
|
148
|
+
issues.append("❌ Import block not properly closed")
|
|
149
|
+
|
|
150
|
+
# General quality checks
|
|
151
|
+
if "fix" in work_description.lower():
|
|
152
|
+
suggestions.append("💡 Ensure you've tested the fix thoroughly")
|
|
153
|
+
suggestions.append("💡 Consider edge cases and error scenarios")
|
|
154
|
+
|
|
155
|
+
if file_paths and len(file_paths) > 5:
|
|
156
|
+
suggestions.append("💡 Large number of files modified - consider breaking into smaller PRs")
|
|
157
|
+
|
|
158
|
+
# Build response
|
|
159
|
+
response = "🔍 CODE QUALITY REVIEW:\n\n"
|
|
160
|
+
|
|
161
|
+
if issues:
|
|
162
|
+
response += "Issues Found:\n" + "\n".join(issues) + "\n\n"
|
|
163
|
+
else:
|
|
164
|
+
response += "✅ No major code quality issues detected.\n\n"
|
|
165
|
+
|
|
166
|
+
if suggestions:
|
|
167
|
+
response += "Suggestions for Improvement:\n" + "\n".join(suggestions) + "\n\n"
|
|
168
|
+
|
|
169
|
+
if specific_concerns:
|
|
170
|
+
response += f"Regarding your concern: '{specific_concerns}'\n"
|
|
171
|
+
if "import" in specific_concerns.lower():
|
|
172
|
+
response += "→ Imports look properly formatted. Ensure they're in the standard order: stdlib, external, internal.\n"
|
|
173
|
+
|
|
174
|
+
response += "\nOverall: " + ("⚠️ Address the issues before proceeding." if issues else "✅ Good work, but always room for improvement!")
|
|
175
|
+
|
|
176
|
+
return response
|
|
177
|
+
|
|
178
|
+
def _review_correctness(
|
|
179
|
+
self,
|
|
180
|
+
work_description: str,
|
|
181
|
+
code_snippets: Optional[List[str]],
|
|
182
|
+
file_paths: Optional[List[str]],
|
|
183
|
+
specific_concerns: Optional[str]
|
|
184
|
+
) -> str:
|
|
185
|
+
"""Review correctness aspects."""
|
|
186
|
+
return """🔍 CORRECTNESS REVIEW:
|
|
187
|
+
|
|
188
|
+
Critical Questions:
|
|
189
|
+
❓ Have you verified the changes compile without errors?
|
|
190
|
+
❓ Do the changes actually fix the reported issue?
|
|
191
|
+
❓ Have you introduced any new bugs or regressions?
|
|
192
|
+
❓ Are all edge cases handled properly?
|
|
193
|
+
|
|
194
|
+
Specific Checks:
|
|
195
|
+
- If fixing imports: Verify the import paths are correct for the project
|
|
196
|
+
- If modifying logic: Ensure the logic is sound and handles all cases
|
|
197
|
+
- If refactoring: Confirm behavior is preserved
|
|
198
|
+
|
|
199
|
+
⚠️ Remember: Working code > elegant code. Make sure it works first!"""
|
|
200
|
+
|
|
201
|
+
def _review_performance(
|
|
202
|
+
self,
|
|
203
|
+
work_description: str,
|
|
204
|
+
code_snippets: Optional[List[str]],
|
|
205
|
+
file_paths: Optional[List[str]],
|
|
206
|
+
specific_concerns: Optional[str]
|
|
207
|
+
) -> str:
|
|
208
|
+
"""Review performance aspects."""
|
|
209
|
+
return """🔍 PERFORMANCE REVIEW:
|
|
210
|
+
|
|
211
|
+
Performance Considerations:
|
|
212
|
+
- Are you doing any operations in loops that could be moved outside?
|
|
213
|
+
- Are there any unnecessary allocations or copies?
|
|
214
|
+
- Could any synchronous operations be made concurrent?
|
|
215
|
+
- Are you caching results that might be reused?
|
|
216
|
+
|
|
217
|
+
For file operations:
|
|
218
|
+
- Consider batch operations over individual ones
|
|
219
|
+
- Use buffered I/O for large files
|
|
220
|
+
- Avoid reading entire files into memory if possible
|
|
221
|
+
|
|
222
|
+
💡 Remember: Premature optimization is evil, but obvious inefficiencies should be fixed."""
|
|
223
|
+
|
|
224
|
+
def _review_security(
|
|
225
|
+
self,
|
|
226
|
+
work_description: str,
|
|
227
|
+
code_snippets: Optional[List[str]],
|
|
228
|
+
file_paths: Optional[List[str]],
|
|
229
|
+
specific_concerns: Optional[str]
|
|
230
|
+
) -> str:
|
|
231
|
+
"""Review security aspects."""
|
|
232
|
+
return """🔍 SECURITY REVIEW:
|
|
233
|
+
|
|
234
|
+
Security Checklist:
|
|
235
|
+
🔐 No hardcoded secrets or credentials
|
|
236
|
+
🔐 All user inputs are validated/sanitized
|
|
237
|
+
🔐 File paths are properly validated
|
|
238
|
+
🔐 No SQL injection vulnerabilities
|
|
239
|
+
🔐 Proper access control checks
|
|
240
|
+
🔐 Sensitive data is not logged
|
|
241
|
+
|
|
242
|
+
⚠️ If in doubt, err on the side of caution!"""
|
|
243
|
+
|
|
244
|
+
def _review_completeness(
|
|
245
|
+
self,
|
|
246
|
+
work_description: str,
|
|
247
|
+
code_snippets: Optional[List[str]],
|
|
248
|
+
file_paths: Optional[List[str]],
|
|
249
|
+
specific_concerns: Optional[str]
|
|
250
|
+
) -> str:
|
|
251
|
+
"""Review completeness aspects."""
|
|
252
|
+
tasks_mentioned = work_description.lower()
|
|
253
|
+
|
|
254
|
+
response = "🔍 COMPLETENESS REVIEW:\n\n"
|
|
255
|
+
|
|
256
|
+
if "fix" in tasks_mentioned and "test" not in tasks_mentioned:
|
|
257
|
+
response += "❌ No mention of tests - have you verified the fix with tests?\n"
|
|
258
|
+
|
|
259
|
+
if "import" in tasks_mentioned:
|
|
260
|
+
response += "✓ Import fixes mentioned\n"
|
|
261
|
+
response += "❓ Have you checked for other files with similar issues?\n"
|
|
262
|
+
response += "❓ Are all undefined symbols now resolved?\n"
|
|
263
|
+
|
|
264
|
+
if file_paths:
|
|
265
|
+
response += f"\n✓ Modified {len(file_paths)} files\n"
|
|
266
|
+
response += "❓ Are there any related files that also need updates?\n"
|
|
267
|
+
|
|
268
|
+
response += "\n💡 Completeness means not just fixing the immediate issue, but considering the broader impact."
|
|
269
|
+
|
|
270
|
+
return response
|
|
271
|
+
|
|
272
|
+
def _review_best_practices(
|
|
273
|
+
self,
|
|
274
|
+
work_description: str,
|
|
275
|
+
code_snippets: Optional[List[str]],
|
|
276
|
+
file_paths: Optional[List[str]],
|
|
277
|
+
specific_concerns: Optional[str]
|
|
278
|
+
) -> str:
|
|
279
|
+
"""Review best practices."""
|
|
280
|
+
return """🔍 BEST PRACTICES REVIEW:
|
|
281
|
+
|
|
282
|
+
Go Best Practices (if applicable):
|
|
283
|
+
✓ Imports are grouped: stdlib, external, internal
|
|
284
|
+
✓ Error handling follows Go idioms
|
|
285
|
+
✓ Variable names are clear and idiomatic
|
|
286
|
+
✓ Comments explain why, not what
|
|
287
|
+
✓ Functions do one thing well
|
|
288
|
+
|
|
289
|
+
General Best Practices:
|
|
290
|
+
✓ Code is self-documenting
|
|
291
|
+
✓ DRY principle is followed
|
|
292
|
+
✓ SOLID principles are respected
|
|
293
|
+
✓ Changes are minimal and focused
|
|
294
|
+
|
|
295
|
+
💡 Good code is code that others (including future you) can understand and modify."""
|
|
296
|
+
|
|
297
|
+
def _review_general(
|
|
298
|
+
self,
|
|
299
|
+
work_description: str,
|
|
300
|
+
code_snippets: Optional[List[str]],
|
|
301
|
+
file_paths: Optional[List[str]],
|
|
302
|
+
specific_concerns: Optional[str]
|
|
303
|
+
) -> str:
|
|
304
|
+
"""General review covering multiple aspects."""
|
|
305
|
+
response = "🔍 GENERAL CRITICAL REVIEW:\n\n"
|
|
306
|
+
response += f"Work Description: {work_description}\n\n"
|
|
307
|
+
|
|
308
|
+
# Quick assessment
|
|
309
|
+
response += "Quick Assessment:\n"
|
|
310
|
+
|
|
311
|
+
if "fix" in work_description.lower():
|
|
312
|
+
response += "- Type: Bug fix / Error resolution\n"
|
|
313
|
+
response += "- Critical: Ensure the fix is complete and tested\n"
|
|
314
|
+
elif "add" in work_description.lower():
|
|
315
|
+
response += "- Type: Feature addition\n"
|
|
316
|
+
response += "- Critical: Ensure no regressions introduced\n"
|
|
317
|
+
elif "refactor" in work_description.lower():
|
|
318
|
+
response += "- Type: Code refactoring\n"
|
|
319
|
+
response += "- Critical: Ensure behavior is preserved\n"
|
|
320
|
+
|
|
321
|
+
if file_paths:
|
|
322
|
+
response += f"- Scope: {len(file_paths)} files affected\n"
|
|
323
|
+
if len(file_paths) > 10:
|
|
324
|
+
response += "- ⚠️ Large scope - consider breaking down\n"
|
|
325
|
+
|
|
326
|
+
response += "\nCritical Questions:\n"
|
|
327
|
+
response += "1. Is this the minimal change needed?\n"
|
|
328
|
+
response += "2. Have you considered all edge cases?\n"
|
|
329
|
+
response += "3. Will this work in production?\n"
|
|
330
|
+
response += "4. Is there a simpler solution?\n"
|
|
331
|
+
|
|
332
|
+
if specific_concerns:
|
|
333
|
+
response += f"\nYour Concern: {specific_concerns}\n"
|
|
334
|
+
response += "→ Valid concern. Double-check this area carefully.\n"
|
|
335
|
+
|
|
336
|
+
response += "\n🎯 Bottom Line: Good work needs critical thinking. Question everything, verify everything."
|
|
337
|
+
|
|
338
|
+
return response
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class CriticProtocol:
|
|
342
|
+
"""Protocol for critic interactions."""
|
|
343
|
+
|
|
344
|
+
def __init__(self):
|
|
345
|
+
self.auto_critic = AutoCritic()
|
|
346
|
+
self.review_count = 0
|
|
347
|
+
self.max_reviews = 2 # Allow up to 2 reviews per task
|
|
348
|
+
|
|
349
|
+
def request_review(
|
|
350
|
+
self,
|
|
351
|
+
review_type: str,
|
|
352
|
+
work_description: str,
|
|
353
|
+
code_snippets: Optional[List[str]] = None,
|
|
354
|
+
file_paths: Optional[List[str]] = None,
|
|
355
|
+
specific_concerns: Optional[str] = None
|
|
356
|
+
) -> str:
|
|
357
|
+
"""Request a critical review."""
|
|
358
|
+
if self.review_count >= self.max_reviews:
|
|
359
|
+
return "❌ Review limit exceeded. Time to move forward with what you have."
|
|
360
|
+
|
|
361
|
+
self.review_count += 1
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
review_enum = ReviewType[review_type.upper()]
|
|
365
|
+
except KeyError:
|
|
366
|
+
review_enum = ReviewType.GENERAL
|
|
367
|
+
|
|
368
|
+
review = self.auto_critic.review(
|
|
369
|
+
review_enum,
|
|
370
|
+
work_description,
|
|
371
|
+
code_snippets,
|
|
372
|
+
file_paths,
|
|
373
|
+
specific_concerns
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return f"Review {self.review_count}/{self.max_reviews}:\n\n{review}"
|