hanzo-mcp 0.7.7__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 +6 -0
- 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.7.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.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info ā hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info ā hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info ā hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -4,49 +4,50 @@ This module implements the AgentTool that allows Claude to delegate tasks to sub
|
|
|
4
4
|
enabling concurrent execution of multiple operations and specialized processing.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import asyncio
|
|
8
|
-
import json
|
|
9
7
|
import re
|
|
8
|
+
import json
|
|
10
9
|
import time
|
|
11
|
-
|
|
12
|
-
from typing import Annotated, TypedDict, Unpack, final, override
|
|
10
|
+
import asyncio
|
|
13
11
|
|
|
14
12
|
# Import litellm with warnings suppressed
|
|
15
13
|
import warnings
|
|
14
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
15
|
+
from collections.abc import Iterable
|
|
16
|
+
|
|
16
17
|
with warnings.catch_warnings():
|
|
17
18
|
warnings.simplefilter("ignore", DeprecationWarning)
|
|
18
19
|
import litellm
|
|
19
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
20
|
-
from mcp.server import FastMCP
|
|
21
|
-
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionToolParam
|
|
22
20
|
from pydantic import Field
|
|
21
|
+
from mcp.server import FastMCP
|
|
22
|
+
from openai.types.chat import ChatCompletionToolParam, ChatCompletionMessageParam
|
|
23
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
23
24
|
|
|
25
|
+
from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
|
|
26
|
+
from hanzo_mcp.tools.filesystem import Edit, MultiEdit, get_read_only_filesystem_tools
|
|
27
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
24
28
|
from hanzo_mcp.tools.agent.prompt import (
|
|
25
|
-
get_allowed_agent_tools,
|
|
26
29
|
get_default_model,
|
|
27
|
-
get_model_parameters,
|
|
28
30
|
get_system_prompt,
|
|
31
|
+
get_model_parameters,
|
|
32
|
+
get_allowed_agent_tools,
|
|
29
33
|
)
|
|
30
|
-
from hanzo_mcp.tools.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
from hanzo_mcp.tools.agent.clarification_protocol import (
|
|
34
|
-
AgentClarificationMixin,
|
|
35
|
-
ClarificationType,
|
|
34
|
+
from hanzo_mcp.tools.common.context import (
|
|
35
|
+
ToolContext,
|
|
36
|
+
create_tool_context,
|
|
36
37
|
)
|
|
37
|
-
from hanzo_mcp.tools.agent.clarification_tool import ClarificationTool
|
|
38
38
|
from hanzo_mcp.tools.agent.critic_tool import CriticTool, CriticProtocol
|
|
39
|
-
from hanzo_mcp.tools.agent.review_tool import ReviewTool, ReviewProtocol
|
|
40
39
|
from hanzo_mcp.tools.agent.iching_tool import IChingTool
|
|
41
|
-
from hanzo_mcp.tools.
|
|
40
|
+
from hanzo_mcp.tools.agent.review_tool import ReviewTool, ReviewProtocol
|
|
42
41
|
from hanzo_mcp.tools.common.batch_tool import BatchTool
|
|
43
|
-
from hanzo_mcp.tools.
|
|
44
|
-
|
|
45
|
-
create_tool_context,
|
|
42
|
+
from hanzo_mcp.tools.agent.tool_adapter import (
|
|
43
|
+
convert_tools_to_openai_functions,
|
|
46
44
|
)
|
|
47
45
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
48
|
-
from hanzo_mcp.tools.
|
|
49
|
-
from hanzo_mcp.tools.
|
|
46
|
+
from hanzo_mcp.tools.agent.clarification_tool import ClarificationTool
|
|
47
|
+
from hanzo_mcp.tools.agent.clarification_protocol import (
|
|
48
|
+
ClarificationType,
|
|
49
|
+
AgentClarificationMixin,
|
|
50
|
+
)
|
|
50
51
|
|
|
51
52
|
Prompt = Annotated[
|
|
52
53
|
str,
|
|
@@ -73,7 +74,7 @@ class AgentTool(AgentClarificationMixin, BaseTool):
|
|
|
73
74
|
|
|
74
75
|
The AgentTool allows Claude to create and manage sub-agents for performing
|
|
75
76
|
specialized tasks concurrently, such as code search, analysis, and more.
|
|
76
|
-
|
|
77
|
+
|
|
77
78
|
Agents can request clarification from the main loop up to once per task.
|
|
78
79
|
"""
|
|
79
80
|
|
|
@@ -156,27 +157,27 @@ Usage notes:
|
|
|
156
157
|
self.available_tools.extend(
|
|
157
158
|
get_read_only_jupyter_tools(self.permission_manager)
|
|
158
159
|
)
|
|
159
|
-
|
|
160
|
+
|
|
160
161
|
# Always add edit tools - agents should have edit access
|
|
161
162
|
self.available_tools.append(Edit(self.permission_manager))
|
|
162
163
|
self.available_tools.append(MultiEdit(self.permission_manager))
|
|
163
|
-
|
|
164
|
+
|
|
164
165
|
# Add clarification tool for agents
|
|
165
166
|
self.available_tools.append(ClarificationTool())
|
|
166
|
-
|
|
167
|
+
|
|
167
168
|
# Add critic tool for agents (devil's advocate)
|
|
168
169
|
self.available_tools.append(CriticTool())
|
|
169
|
-
|
|
170
|
+
|
|
170
171
|
# Add review tool for agents (balanced review)
|
|
171
172
|
self.available_tools.append(ReviewTool())
|
|
172
|
-
|
|
173
|
+
|
|
173
174
|
# Add I Ching tool for creative guidance
|
|
174
175
|
self.available_tools.append(IChingTool())
|
|
175
|
-
|
|
176
|
+
|
|
176
177
|
self.available_tools.append(
|
|
177
178
|
BatchTool({t.name: t for t in self.available_tools})
|
|
178
179
|
)
|
|
179
|
-
|
|
180
|
+
|
|
180
181
|
# Initialize protocols
|
|
181
182
|
self.critic_protocol = CriticProtocol()
|
|
182
183
|
self.review_protocol = ReviewProtocol()
|
|
@@ -204,7 +205,7 @@ Usage notes:
|
|
|
204
205
|
|
|
205
206
|
# Extract and validate parameters
|
|
206
207
|
prompts = params.get("prompts")
|
|
207
|
-
|
|
208
|
+
|
|
208
209
|
if prompts is None:
|
|
209
210
|
await tool_ctx.error("No prompts provided")
|
|
210
211
|
return """Error: At least one prompt must be provided.
|
|
@@ -238,7 +239,9 @@ Example of correct usage:
|
|
|
238
239
|
absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
|
|
239
240
|
for prompt in prompt_list:
|
|
240
241
|
if not re.search(absolute_path_pattern, prompt):
|
|
241
|
-
await tool_ctx.error(
|
|
242
|
+
await tool_ctx.error(
|
|
243
|
+
f"Prompt does not contain absolute path: {prompt[:50]}..."
|
|
244
|
+
)
|
|
242
245
|
return """Error: All prompts must contain at least one absolute path.
|
|
243
246
|
|
|
244
247
|
IMPORTANT REMINDER FOR CLAUDE:
|
|
@@ -268,7 +271,9 @@ AGENT RESPONSE:
|
|
|
268
271
|
|
|
269
272
|
AGENT RESPONSES:
|
|
270
273
|
{result}"""
|
|
271
|
-
await tool_ctx.info(
|
|
274
|
+
await tool_ctx.info(
|
|
275
|
+
f"Multi-agent execution completed in {execution_time:.2f}s"
|
|
276
|
+
)
|
|
272
277
|
return formatted_result
|
|
273
278
|
|
|
274
279
|
async def _execute_agent(self, prompt: str, tool_ctx: ToolContext) -> str:
|
|
@@ -316,7 +321,9 @@ AGENT RESPONSES:
|
|
|
316
321
|
|
|
317
322
|
return result if result else "No results returned from agent"
|
|
318
323
|
|
|
319
|
-
async def _execute_multiple_agents(
|
|
324
|
+
async def _execute_multiple_agents(
|
|
325
|
+
self, prompts: list[str], tool_ctx: ToolContext
|
|
326
|
+
) -> str:
|
|
320
327
|
"""Execute multiple agents concurrently.
|
|
321
328
|
|
|
322
329
|
Args:
|
|
@@ -344,7 +351,7 @@ AGENT RESPONSES:
|
|
|
344
351
|
# Create tasks for parallel execution
|
|
345
352
|
tasks = []
|
|
346
353
|
for i, prompt in enumerate(prompts):
|
|
347
|
-
await tool_ctx.info(f"Creating agent task {i+1}: {prompt[:50]}...")
|
|
354
|
+
await tool_ctx.info(f"Creating agent task {i + 1}: {prompt[:50]}...")
|
|
348
355
|
task = self._execute_agent_with_tools(
|
|
349
356
|
system_prompt, prompt, agent_tools, openai_tools, tool_ctx
|
|
350
357
|
)
|
|
@@ -365,10 +372,10 @@ AGENT RESPONSES:
|
|
|
365
372
|
formatted_results = []
|
|
366
373
|
for i, result in enumerate(results):
|
|
367
374
|
if isinstance(result, Exception):
|
|
368
|
-
formatted_results.append(f"Agent {i+1} Error:\n{str(result)}")
|
|
369
|
-
await tool_ctx.error(f"Agent {i+1} failed: {str(result)}")
|
|
375
|
+
formatted_results.append(f"Agent {i + 1} Error:\n{str(result)}")
|
|
376
|
+
await tool_ctx.error(f"Agent {i + 1} failed: {str(result)}")
|
|
370
377
|
else:
|
|
371
|
-
formatted_results.append(f"Agent {i+1} Result:\n{result}")
|
|
378
|
+
formatted_results.append(f"Agent {i + 1} Result:\n{result}")
|
|
372
379
|
|
|
373
380
|
return "\n\n---\n\n".join(formatted_results)
|
|
374
381
|
|
|
@@ -438,9 +445,7 @@ AGENT RESPONSES:
|
|
|
438
445
|
completion_params["base_url"] = self.base_url_override
|
|
439
446
|
|
|
440
447
|
# Make the model call
|
|
441
|
-
response = litellm.completion(
|
|
442
|
-
**completion_params # pyright: ignore
|
|
443
|
-
)
|
|
448
|
+
response = litellm.completion(**completion_params) # pyright: ignore
|
|
444
449
|
|
|
445
450
|
if len(response.choices) == 0: # pyright: ignore
|
|
446
451
|
raise ValueError("No response choices returned")
|
|
@@ -487,20 +492,22 @@ AGENT RESPONSES:
|
|
|
487
492
|
request_type = function_args.get("type", "ADDITIONAL_INFO")
|
|
488
493
|
question = function_args.get("question", "")
|
|
489
494
|
context = function_args.get("context", {})
|
|
490
|
-
options = function_args.get("options"
|
|
491
|
-
|
|
495
|
+
options = function_args.get("options")
|
|
496
|
+
|
|
492
497
|
# Convert string type to enum
|
|
493
498
|
clarification_type = ClarificationType[request_type]
|
|
494
|
-
|
|
499
|
+
|
|
495
500
|
# Request clarification
|
|
496
501
|
answer = await self.request_clarification(
|
|
497
502
|
request_type=clarification_type,
|
|
498
503
|
question=question,
|
|
499
504
|
context=context,
|
|
500
|
-
options=options
|
|
505
|
+
options=options,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
tool_result = self.format_clarification_in_output(
|
|
509
|
+
question, answer
|
|
501
510
|
)
|
|
502
|
-
|
|
503
|
-
tool_result = self.format_clarification_in_output(question, answer)
|
|
504
511
|
except Exception as e:
|
|
505
512
|
tool_result = f"Error processing clarification: {str(e)}"
|
|
506
513
|
# Special handling for critic requests
|
|
@@ -509,17 +516,17 @@ AGENT RESPONSES:
|
|
|
509
516
|
# Extract critic parameters
|
|
510
517
|
review_type = function_args.get("review_type", "GENERAL")
|
|
511
518
|
work_description = function_args.get("work_description", "")
|
|
512
|
-
code_snippets = function_args.get("code_snippets"
|
|
513
|
-
file_paths = function_args.get("file_paths"
|
|
514
|
-
specific_concerns = function_args.get("specific_concerns"
|
|
515
|
-
|
|
519
|
+
code_snippets = function_args.get("code_snippets")
|
|
520
|
+
file_paths = function_args.get("file_paths")
|
|
521
|
+
specific_concerns = function_args.get("specific_concerns")
|
|
522
|
+
|
|
516
523
|
# Request critical review
|
|
517
524
|
tool_result = self.critic_protocol.request_review(
|
|
518
525
|
review_type=review_type,
|
|
519
526
|
work_description=work_description,
|
|
520
527
|
code_snippets=code_snippets,
|
|
521
528
|
file_paths=file_paths,
|
|
522
|
-
specific_concerns=specific_concerns
|
|
529
|
+
specific_concerns=specific_concerns,
|
|
523
530
|
)
|
|
524
531
|
except Exception as e:
|
|
525
532
|
tool_result = f"Error processing critic review: {str(e)}"
|
|
@@ -529,17 +536,17 @@ AGENT RESPONSES:
|
|
|
529
536
|
# Extract review parameters
|
|
530
537
|
focus = function_args.get("focus", "GENERAL")
|
|
531
538
|
work_description = function_args.get("work_description", "")
|
|
532
|
-
code_snippets = function_args.get("code_snippets"
|
|
533
|
-
file_paths = function_args.get("file_paths"
|
|
534
|
-
context = function_args.get("context"
|
|
535
|
-
|
|
539
|
+
code_snippets = function_args.get("code_snippets")
|
|
540
|
+
file_paths = function_args.get("file_paths")
|
|
541
|
+
context = function_args.get("context")
|
|
542
|
+
|
|
536
543
|
# Request balanced review
|
|
537
544
|
tool_result = self.review_protocol.request_review(
|
|
538
545
|
focus=focus,
|
|
539
546
|
work_description=work_description,
|
|
540
547
|
code_snippets=code_snippets,
|
|
541
548
|
file_paths=file_paths,
|
|
542
|
-
context=context
|
|
549
|
+
context=context,
|
|
543
550
|
)
|
|
544
551
|
except Exception as e:
|
|
545
552
|
tool_result = f"Error processing review: {str(e)}"
|
|
@@ -634,8 +641,5 @@ AGENT RESPONSE:
|
|
|
634
641
|
tool_self = self # Create a reference to self for use in the closure
|
|
635
642
|
|
|
636
643
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
637
|
-
async def dispatch_agent(
|
|
638
|
-
prompts: str | list[str],
|
|
639
|
-
ctx: MCPContext
|
|
640
|
-
) -> str:
|
|
644
|
+
async def dispatch_agent(prompts: str | list[str], ctx: MCPContext) -> str:
|
|
641
645
|
return await tool_self.call(ctx, prompts=prompts)
|
|
@@ -5,13 +5,14 @@ from the main loop without human intervention.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from typing import Any, Dict, List, Optional
|
|
10
8
|
from enum import Enum
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
from dataclasses import dataclass
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class ClarificationType(Enum):
|
|
14
14
|
"""Types of clarification requests."""
|
|
15
|
+
|
|
15
16
|
AMBIGUOUS_INSTRUCTION = "ambiguous_instruction"
|
|
16
17
|
MISSING_CONTEXT = "missing_context"
|
|
17
18
|
MULTIPLE_OPTIONS = "multiple_options"
|
|
@@ -22,23 +23,25 @@ class ClarificationType(Enum):
|
|
|
22
23
|
@dataclass
|
|
23
24
|
class ClarificationRequest:
|
|
24
25
|
"""A request for clarification from an agent."""
|
|
25
|
-
|
|
26
|
+
|
|
26
27
|
agent_id: str
|
|
27
28
|
request_type: ClarificationType
|
|
28
29
|
question: str
|
|
29
30
|
context: Dict[str, Any]
|
|
30
31
|
options: Optional[List[str]] = None
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
def to_json(self) -> str:
|
|
33
34
|
"""Convert to JSON for transport."""
|
|
34
|
-
return json.dumps(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
return json.dumps(
|
|
36
|
+
{
|
|
37
|
+
"agent_id": self.agent_id,
|
|
38
|
+
"request_type": self.request_type.value,
|
|
39
|
+
"question": self.question,
|
|
40
|
+
"context": self.context,
|
|
41
|
+
"options": self.options,
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
42
45
|
@classmethod
|
|
43
46
|
def from_json(cls, data: str) -> "ClarificationRequest":
|
|
44
47
|
"""Create from JSON string."""
|
|
@@ -48,44 +51,46 @@ class ClarificationRequest:
|
|
|
48
51
|
request_type=ClarificationType(obj["request_type"]),
|
|
49
52
|
question=obj["question"],
|
|
50
53
|
context=obj["context"],
|
|
51
|
-
options=obj.get("options")
|
|
54
|
+
options=obj.get("options"),
|
|
52
55
|
)
|
|
53
56
|
|
|
54
57
|
|
|
55
58
|
@dataclass
|
|
56
59
|
class ClarificationResponse:
|
|
57
60
|
"""A response to a clarification request."""
|
|
58
|
-
|
|
61
|
+
|
|
59
62
|
request_id: str
|
|
60
63
|
answer: str
|
|
61
64
|
additional_context: Optional[Dict[str, Any]] = None
|
|
62
|
-
|
|
65
|
+
|
|
63
66
|
def to_json(self) -> str:
|
|
64
67
|
"""Convert to JSON for transport."""
|
|
65
|
-
return json.dumps(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
return json.dumps(
|
|
69
|
+
{
|
|
70
|
+
"request_id": self.request_id,
|
|
71
|
+
"answer": self.answer,
|
|
72
|
+
"additional_context": self.additional_context,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
70
75
|
|
|
71
76
|
|
|
72
77
|
class ClarificationHandler:
|
|
73
78
|
"""Handles clarification requests from agents."""
|
|
74
|
-
|
|
79
|
+
|
|
75
80
|
def __init__(self):
|
|
76
81
|
self.pending_requests: Dict[str, ClarificationRequest] = {}
|
|
77
82
|
self.request_counter = 0
|
|
78
|
-
|
|
83
|
+
|
|
79
84
|
def create_request(
|
|
80
85
|
self,
|
|
81
86
|
agent_id: str,
|
|
82
87
|
request_type: ClarificationType,
|
|
83
88
|
question: str,
|
|
84
89
|
context: Dict[str, Any],
|
|
85
|
-
options: Optional[List[str]] = None
|
|
90
|
+
options: Optional[List[str]] = None,
|
|
86
91
|
) -> str:
|
|
87
92
|
"""Create a new clarification request.
|
|
88
|
-
|
|
93
|
+
|
|
89
94
|
Returns:
|
|
90
95
|
Request ID for tracking
|
|
91
96
|
"""
|
|
@@ -94,23 +99,23 @@ class ClarificationHandler:
|
|
|
94
99
|
request_type=request_type,
|
|
95
100
|
question=question,
|
|
96
101
|
context=context,
|
|
97
|
-
options=options
|
|
102
|
+
options=options,
|
|
98
103
|
)
|
|
99
|
-
|
|
104
|
+
|
|
100
105
|
request_id = f"clarify_{self.request_counter}"
|
|
101
106
|
self.request_counter += 1
|
|
102
107
|
self.pending_requests[request_id] = request
|
|
103
|
-
|
|
108
|
+
|
|
104
109
|
return request_id
|
|
105
|
-
|
|
110
|
+
|
|
106
111
|
def handle_request(self, request: ClarificationRequest) -> ClarificationResponse:
|
|
107
112
|
"""Handle a clarification request automatically.
|
|
108
|
-
|
|
113
|
+
|
|
109
114
|
This method implements automatic clarification resolution
|
|
110
115
|
based on context and common patterns.
|
|
111
116
|
"""
|
|
112
117
|
request_id = f"clarify_{len(self.pending_requests)}"
|
|
113
|
-
|
|
118
|
+
|
|
114
119
|
# Handle different types of clarification
|
|
115
120
|
if request.request_type == ClarificationType.AMBIGUOUS_INSTRUCTION:
|
|
116
121
|
# Try to clarify based on context
|
|
@@ -123,7 +128,7 @@ class ClarificationHandler:
|
|
|
123
128
|
answer = "Add imports according to the language's conventions."
|
|
124
129
|
else:
|
|
125
130
|
answer = "Proceed with the most reasonable interpretation based on the context."
|
|
126
|
-
|
|
131
|
+
|
|
127
132
|
elif request.request_type == ClarificationType.MISSING_CONTEXT:
|
|
128
133
|
# Provide additional context based on what's missing
|
|
129
134
|
if "import_path" in request.question.lower():
|
|
@@ -132,7 +137,7 @@ class ClarificationHandler:
|
|
|
132
137
|
answer = "Match the existing code style in the file. Use the same indentation and formatting patterns."
|
|
133
138
|
else:
|
|
134
139
|
answer = "Analyze the surrounding code and project structure to infer the missing information."
|
|
135
|
-
|
|
140
|
+
|
|
136
141
|
elif request.request_type == ClarificationType.MULTIPLE_OPTIONS:
|
|
137
142
|
# Choose the best option based on context
|
|
138
143
|
if request.options:
|
|
@@ -145,7 +150,7 @@ class ClarificationHandler:
|
|
|
145
150
|
answer = f"Choose option: {request.options[0]}"
|
|
146
151
|
else:
|
|
147
152
|
answer = "Choose the most conventional approach based on the codebase patterns."
|
|
148
|
-
|
|
153
|
+
|
|
149
154
|
elif request.request_type == ClarificationType.CONFIRMATION_NEEDED:
|
|
150
155
|
# Auto-confirm safe operations
|
|
151
156
|
if "add import" in request.question.lower():
|
|
@@ -154,67 +159,67 @@ class ClarificationHandler:
|
|
|
154
159
|
answer = "Yes, use multi_edit for efficiency."
|
|
155
160
|
else:
|
|
156
161
|
answer = "Proceed if the operation is safe and reversible."
|
|
157
|
-
|
|
162
|
+
|
|
158
163
|
else: # ADDITIONAL_INFO
|
|
159
164
|
answer = "Continue with available information and make reasonable assumptions based on context."
|
|
160
|
-
|
|
165
|
+
|
|
161
166
|
return ClarificationResponse(
|
|
162
167
|
request_id=request_id,
|
|
163
168
|
answer=answer,
|
|
164
|
-
additional_context={"auto_resolved": True}
|
|
169
|
+
additional_context={"auto_resolved": True},
|
|
165
170
|
)
|
|
166
171
|
|
|
167
172
|
|
|
168
173
|
class AgentClarificationMixin:
|
|
169
174
|
"""Mixin for agents to request clarification."""
|
|
170
|
-
|
|
175
|
+
|
|
171
176
|
def __init__(self, *args, **kwargs):
|
|
172
177
|
super().__init__(*args, **kwargs)
|
|
173
178
|
self.clarification_handler = ClarificationHandler()
|
|
174
179
|
self.clarification_count = 0
|
|
175
180
|
self.max_clarifications = 1 # Allow up to 1 clarification per task
|
|
176
|
-
|
|
181
|
+
|
|
177
182
|
async def request_clarification(
|
|
178
183
|
self,
|
|
179
184
|
request_type: ClarificationType,
|
|
180
185
|
question: str,
|
|
181
186
|
context: Dict[str, Any],
|
|
182
|
-
options: Optional[List[str]] = None
|
|
187
|
+
options: Optional[List[str]] = None,
|
|
183
188
|
) -> str:
|
|
184
189
|
"""Request clarification from the main loop.
|
|
185
|
-
|
|
190
|
+
|
|
186
191
|
Args:
|
|
187
192
|
request_type: Type of clarification needed
|
|
188
193
|
question: The question to ask
|
|
189
194
|
context: Relevant context for the question
|
|
190
195
|
options: Optional list of choices
|
|
191
|
-
|
|
196
|
+
|
|
192
197
|
Returns:
|
|
193
198
|
The clarification response
|
|
194
|
-
|
|
199
|
+
|
|
195
200
|
Raises:
|
|
196
201
|
RuntimeError: If clarification limit exceeded
|
|
197
202
|
"""
|
|
198
203
|
if self.clarification_count >= self.max_clarifications:
|
|
199
204
|
raise RuntimeError("Clarification limit exceeded")
|
|
200
|
-
|
|
205
|
+
|
|
201
206
|
self.clarification_count += 1
|
|
202
|
-
|
|
207
|
+
|
|
203
208
|
# Create request
|
|
204
209
|
request = ClarificationRequest(
|
|
205
|
-
agent_id=getattr(self,
|
|
210
|
+
agent_id=getattr(self, "agent_id", "unknown"),
|
|
206
211
|
request_type=request_type,
|
|
207
212
|
question=question,
|
|
208
213
|
context=context,
|
|
209
|
-
options=options
|
|
214
|
+
options=options,
|
|
210
215
|
)
|
|
211
|
-
|
|
216
|
+
|
|
212
217
|
# In real implementation, this would communicate with main loop
|
|
213
218
|
# For now, use the automatic handler
|
|
214
219
|
response = self.clarification_handler.handle_request(request)
|
|
215
|
-
|
|
220
|
+
|
|
216
221
|
return response.answer
|
|
217
|
-
|
|
222
|
+
|
|
218
223
|
def format_clarification_in_output(self, question: str, answer: str) -> str:
|
|
219
224
|
"""Format clarification exchange for output."""
|
|
220
|
-
return f"\nš¤ Clarification needed: {question}\nā
Resolved: {answer}\n"
|
|
225
|
+
return f"\nš¤ Clarification needed: {question}\nā
Resolved: {answer}\n"
|
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Dict, List, Optional, override
|
|
4
4
|
|
|
5
|
-
from hanzo_mcp.tools.common.base import BaseTool
|
|
6
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
7
5
|
from mcp.server import FastMCP
|
|
6
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
7
|
+
|
|
8
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ClarificationTool(BaseTool):
|
|
11
12
|
"""Tool for agents to request clarification from the main loop."""
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
name = "request_clarification"
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
@property
|
|
16
17
|
@override
|
|
17
18
|
def description(self) -> str:
|
|
@@ -40,29 +41,29 @@ request_clarification(
|
|
|
40
41
|
context={"file_path": "/path/to/file.go", "undefined_symbol": "common"},
|
|
41
42
|
options=["github.com/luxfi/node/common", "github.com/project/common"]
|
|
42
43
|
)"""
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
async def call(
|
|
45
46
|
self,
|
|
46
47
|
ctx: MCPContext,
|
|
47
48
|
type: str,
|
|
48
49
|
question: str,
|
|
49
50
|
context: Dict[str, Any],
|
|
50
|
-
options: Optional[List[str]] = None
|
|
51
|
+
options: Optional[List[str]] = None,
|
|
51
52
|
) -> str:
|
|
52
53
|
"""This is a placeholder - actual implementation happens in AgentTool."""
|
|
53
54
|
# This tool is handled specially in the agent execution
|
|
54
55
|
return f"Clarification request: {question}"
|
|
55
|
-
|
|
56
|
+
|
|
56
57
|
def register(self, server: FastMCP) -> None:
|
|
57
58
|
"""Register the tool with the MCP server."""
|
|
58
59
|
tool_self = self
|
|
59
|
-
|
|
60
|
+
|
|
60
61
|
@server.tool(name=self.name, description=self.description)
|
|
61
62
|
async def request_clarification(
|
|
62
63
|
ctx: MCPContext,
|
|
63
64
|
type: str,
|
|
64
65
|
question: str,
|
|
65
66
|
context: Dict[str, Any],
|
|
66
|
-
options: Optional[List[str]] = None
|
|
67
|
+
options: Optional[List[str]] = None,
|
|
67
68
|
) -> str:
|
|
68
|
-
return await tool_self.call(ctx, type, question, context, options)
|
|
69
|
+
return await tool_self.call(ctx, type, question, context, options)
|