massgen 0.1.0a3__py3-none-any.whl → 0.1.2__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 massgen might be problematic. Click here for more details.

Files changed (120) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/agent_config.py +17 -0
  3. massgen/api_params_handler/_api_params_handler_base.py +1 -0
  4. massgen/api_params_handler/_chat_completions_api_params_handler.py +15 -2
  5. massgen/api_params_handler/_claude_api_params_handler.py +8 -1
  6. massgen/api_params_handler/_gemini_api_params_handler.py +73 -0
  7. massgen/api_params_handler/_response_api_params_handler.py +8 -1
  8. massgen/backend/base.py +83 -0
  9. massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +286 -15
  10. massgen/backend/capabilities.py +6 -6
  11. massgen/backend/chat_completions.py +200 -103
  12. massgen/backend/claude.py +115 -18
  13. massgen/backend/claude_code.py +378 -14
  14. massgen/backend/docs/CLAUDE_API_RESEARCH.md +3 -3
  15. massgen/backend/gemini.py +1333 -1629
  16. massgen/backend/gemini_mcp_manager.py +545 -0
  17. massgen/backend/gemini_trackers.py +344 -0
  18. massgen/backend/gemini_utils.py +43 -0
  19. massgen/backend/grok.py +39 -6
  20. massgen/backend/response.py +147 -81
  21. massgen/cli.py +605 -110
  22. massgen/config_builder.py +376 -27
  23. massgen/configs/README.md +123 -80
  24. massgen/configs/basic/multi/three_agents_default.yaml +3 -3
  25. massgen/configs/basic/single/single_agent.yaml +1 -1
  26. massgen/configs/providers/openai/gpt5_nano.yaml +3 -3
  27. massgen/configs/tools/custom_tools/claude_code_custom_tool_example.yaml +32 -0
  28. massgen/configs/tools/custom_tools/claude_code_custom_tool_example_no_path.yaml +28 -0
  29. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +40 -0
  30. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_wrong_mcp_example.yaml +38 -0
  31. massgen/configs/tools/custom_tools/claude_code_wrong_custom_tool_with_mcp_example.yaml +38 -0
  32. massgen/configs/tools/custom_tools/claude_custom_tool_example.yaml +24 -0
  33. massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +22 -0
  34. massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +35 -0
  35. massgen/configs/tools/custom_tools/claude_custom_tool_with_wrong_mcp_example.yaml +33 -0
  36. massgen/configs/tools/custom_tools/claude_wrong_custom_tool_with_mcp_example.yaml +33 -0
  37. massgen/configs/tools/custom_tools/gemini_custom_tool_example.yaml +24 -0
  38. massgen/configs/tools/custom_tools/gemini_custom_tool_example_no_path.yaml +22 -0
  39. massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +35 -0
  40. massgen/configs/tools/custom_tools/gemini_custom_tool_with_wrong_mcp_example.yaml +33 -0
  41. massgen/configs/tools/custom_tools/gemini_wrong_custom_tool_with_mcp_example.yaml +33 -0
  42. massgen/configs/tools/custom_tools/github_issue_market_analysis.yaml +94 -0
  43. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example.yaml +24 -0
  44. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example_no_path.yaml +22 -0
  45. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +35 -0
  46. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_wrong_mcp_example.yaml +33 -0
  47. massgen/configs/tools/custom_tools/gpt5_nano_wrong_custom_tool_with_mcp_example.yaml +33 -0
  48. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example.yaml +25 -0
  49. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example_no_path.yaml +23 -0
  50. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +34 -0
  51. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_wrong_mcp_example.yaml +34 -0
  52. massgen/configs/tools/custom_tools/gpt_oss_wrong_custom_tool_with_mcp_example.yaml +34 -0
  53. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example.yaml +24 -0
  54. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example_no_path.yaml +22 -0
  55. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +35 -0
  56. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_wrong_mcp_example.yaml +33 -0
  57. massgen/configs/tools/custom_tools/grok3_mini_wrong_custom_tool_with_mcp_example.yaml +33 -0
  58. massgen/configs/tools/custom_tools/qwen_api_custom_tool_example.yaml +25 -0
  59. massgen/configs/tools/custom_tools/qwen_api_custom_tool_example_no_path.yaml +23 -0
  60. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +36 -0
  61. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_wrong_mcp_example.yaml +34 -0
  62. massgen/configs/tools/custom_tools/qwen_api_wrong_custom_tool_with_mcp_example.yaml +34 -0
  63. massgen/configs/tools/custom_tools/qwen_local_custom_tool_example.yaml +24 -0
  64. massgen/configs/tools/custom_tools/qwen_local_custom_tool_example_no_path.yaml +22 -0
  65. massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_mcp_example.yaml +35 -0
  66. massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_wrong_mcp_example.yaml +33 -0
  67. massgen/configs/tools/custom_tools/qwen_local_wrong_custom_tool_with_mcp_example.yaml +33 -0
  68. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +1 -1
  69. massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +7 -29
  70. massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +5 -6
  71. massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +4 -4
  72. massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +4 -4
  73. massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +2 -2
  74. massgen/configs/voting/gemini_gpt_voting_sensitivity.yaml +67 -0
  75. massgen/formatter/_chat_completions_formatter.py +104 -0
  76. massgen/formatter/_claude_formatter.py +120 -0
  77. massgen/formatter/_gemini_formatter.py +448 -0
  78. massgen/formatter/_response_formatter.py +88 -0
  79. massgen/frontend/coordination_ui.py +4 -2
  80. massgen/logger_config.py +35 -3
  81. massgen/message_templates.py +56 -6
  82. massgen/orchestrator.py +512 -16
  83. massgen/stream_chunk/base.py +3 -0
  84. massgen/tests/custom_tools_example.py +392 -0
  85. massgen/tests/mcp_test_server.py +17 -7
  86. massgen/tests/test_config_builder.py +423 -0
  87. massgen/tests/test_custom_tools.py +401 -0
  88. massgen/tests/test_intelligent_planning_mode.py +643 -0
  89. massgen/tests/test_tools.py +127 -0
  90. massgen/token_manager/token_manager.py +13 -4
  91. massgen/tool/README.md +935 -0
  92. massgen/tool/__init__.py +39 -0
  93. massgen/tool/_async_helpers.py +70 -0
  94. massgen/tool/_basic/__init__.py +8 -0
  95. massgen/tool/_basic/_two_num_tool.py +24 -0
  96. massgen/tool/_code_executors/__init__.py +10 -0
  97. massgen/tool/_code_executors/_python_executor.py +74 -0
  98. massgen/tool/_code_executors/_shell_executor.py +61 -0
  99. massgen/tool/_exceptions.py +39 -0
  100. massgen/tool/_file_handlers/__init__.py +10 -0
  101. massgen/tool/_file_handlers/_file_operations.py +218 -0
  102. massgen/tool/_manager.py +634 -0
  103. massgen/tool/_registered_tool.py +88 -0
  104. massgen/tool/_result.py +66 -0
  105. massgen/tool/_self_evolution/_github_issue_analyzer.py +369 -0
  106. massgen/tool/docs/builtin_tools.md +681 -0
  107. massgen/tool/docs/exceptions.md +794 -0
  108. massgen/tool/docs/execution_results.md +691 -0
  109. massgen/tool/docs/manager.md +887 -0
  110. massgen/tool/docs/workflow_toolkits.md +529 -0
  111. massgen/tool/workflow_toolkits/__init__.py +57 -0
  112. massgen/tool/workflow_toolkits/base.py +55 -0
  113. massgen/tool/workflow_toolkits/new_answer.py +126 -0
  114. massgen/tool/workflow_toolkits/vote.py +167 -0
  115. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/METADATA +87 -129
  116. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/RECORD +120 -44
  117. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/WHEEL +0 -0
  118. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/entry_points.txt +0 -0
  119. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/licenses/LICENSE +0 -0
  120. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/top_level.txt +0 -0
massgen/__init__.py CHANGED
@@ -68,7 +68,7 @@ from .chat_agent import (
68
68
  from .message_templates import MessageTemplates, get_templates
69
69
  from .orchestrator import Orchestrator, create_orchestrator
70
70
 
71
- __version__ = "0.1.0a3"
71
+ __version__ = "0.1.2"
72
72
  __author__ = "MassGen Contributors"
73
73
 
74
74
 
massgen/agent_config.py CHANGED
@@ -58,6 +58,9 @@ class AgentConfig:
58
58
  timeout_config: Timeout and resource limit configuration
59
59
  coordination_config: Coordination behavior configuration (e.g., planning mode)
60
60
  skip_coordination_rounds: Debug/test mode - skip voting rounds and go straight to final presentation (default: False)
61
+ voting_sensitivity: Controls how critical agents are when voting ("lenient", "balanced", "strict")
62
+ max_new_answers_per_agent: Maximum number of new answers each agent can provide (None = unlimited)
63
+ answer_novelty_requirement: How different new answers must be from existing ones ("lenient", "balanced", "strict")
61
64
  """
62
65
 
63
66
  # Core backend configuration (includes tool enablement)
@@ -66,6 +69,11 @@ class AgentConfig:
66
69
  # Framework configuration
67
70
  message_templates: Optional["MessageTemplates"] = None
68
71
 
72
+ # Voting behavior configuration
73
+ voting_sensitivity: str = "lenient"
74
+ max_new_answers_per_agent: Optional[int] = None
75
+ answer_novelty_requirement: str = "lenient"
76
+
69
77
  # Agent customization
70
78
  agent_id: Optional[str] = None
71
79
  _custom_system_instruction: Optional[str] = field(default=None, init=False)
@@ -696,6 +704,9 @@ class AgentConfig:
696
704
  "backend_params": self.backend_params,
697
705
  "agent_id": self.agent_id,
698
706
  "custom_system_instruction": self.custom_system_instruction,
707
+ "voting_sensitivity": self.voting_sensitivity,
708
+ "max_new_answers_per_agent": self.max_new_answers_per_agent,
709
+ "answer_novelty_requirement": self.answer_novelty_requirement,
699
710
  "timeout_config": {
700
711
  "orchestrator_timeout_seconds": self.timeout_config.orchestrator_timeout_seconds,
701
712
  },
@@ -730,6 +741,9 @@ class AgentConfig:
730
741
  backend_params = data.get("backend_params", {})
731
742
  agent_id = data.get("agent_id")
732
743
  custom_system_instruction = data.get("custom_system_instruction")
744
+ voting_sensitivity = data.get("voting_sensitivity", "lenient")
745
+ max_new_answers_per_agent = data.get("max_new_answers_per_agent")
746
+ answer_novelty_requirement = data.get("answer_novelty_requirement", "lenient")
733
747
 
734
748
  # Handle timeout_config
735
749
  timeout_config = TimeoutConfig()
@@ -756,6 +770,9 @@ class AgentConfig:
756
770
  message_templates=message_templates,
757
771
  agent_id=agent_id,
758
772
  custom_system_instruction=custom_system_instruction,
773
+ voting_sensitivity=voting_sensitivity,
774
+ max_new_answers_per_agent=max_new_answers_per_agent,
775
+ answer_novelty_requirement=answer_novelty_requirement,
759
776
  timeout_config=timeout_config,
760
777
  coordination_config=coordination_config,
761
778
  )
@@ -21,6 +21,7 @@ class APIParamsHandlerBase(ABC):
21
21
  """
22
22
  self.backend = backend_instance
23
23
  self.formatter = backend_instance.formatter
24
+ self.custom_tool_manager = backend_instance.custom_tool_manager
24
25
 
25
26
  @abstractmethod
26
27
  async def build_api_params(
@@ -23,6 +23,7 @@ class ChatCompletionsAPIParamsHandler(APIParamsHandlerBase):
23
23
  "enable_code_interpreter",
24
24
  "allowed_tools",
25
25
  "exclude_tools",
26
+ "custom_tools", # Custom tools configuration (processed separately)
26
27
  },
27
28
  )
28
29
 
@@ -30,7 +31,13 @@ class ChatCompletionsAPIParamsHandler(APIParamsHandlerBase):
30
31
  """Get provider tools for Chat Completions format."""
31
32
  provider_tools = []
32
33
 
33
- if all_params.get("enable_web_search", False):
34
+ # Check if this is Grok backend - Grok uses extra_body.search_parameters instead of function tools
35
+ backend_provider = getattr(self.backend, "get_provider_name", lambda: "")()
36
+ is_grok = backend_provider.lower() == "grok"
37
+
38
+ # Add web_search function tool for non-Grok backends
39
+ # Grok handles web search via extra_body.search_parameters (set in grok.py)
40
+ if all_params.get("enable_web_search", False) and not is_grok:
34
41
  provider_tools.append(
35
42
  {
36
43
  "type": "function",
@@ -114,11 +121,17 @@ class ChatCompletionsAPIParamsHandler(APIParamsHandlerBase):
114
121
  if provider_tools:
115
122
  combined_tools.extend(provider_tools)
116
123
 
117
- # User-defined tools
124
+ # Workflow tools
118
125
  if tools:
119
126
  converted_tools = self.formatter.format_tools(tools)
120
127
  combined_tools.extend(converted_tools)
121
128
 
129
+ # Add custom tools
130
+ custom_tools = self.custom_tool_manager.registered_tools
131
+ if custom_tools:
132
+ converted_custom_tools = self.formatter.format_custom_tools(custom_tools)
133
+ combined_tools.extend(converted_custom_tools)
134
+
122
135
  # MCP tools
123
136
  mcp_tools = self.get_mcp_tools()
124
137
  if mcp_tools:
@@ -22,6 +22,7 @@ class ClaudeAPIParamsHandler(APIParamsHandlerBase):
22
22
  "enable_code_execution",
23
23
  "allowed_tools",
24
24
  "exclude_tools",
25
+ "custom_tools", # Custom tools configuration (processed separately)
25
26
  "_has_files_api_files",
26
27
  },
27
28
  )
@@ -97,11 +98,17 @@ class ClaudeAPIParamsHandler(APIParamsHandlerBase):
97
98
  if provider_tools:
98
99
  combined_tools.extend(provider_tools)
99
100
 
100
- # User-defined tools
101
+ # Workflow tools
101
102
  if tools:
102
103
  converted_tools = self.formatter.format_tools(tools)
103
104
  combined_tools.extend(converted_tools)
104
105
 
106
+ # Add custom tools
107
+ custom_tools = self.custom_tool_manager.registered_tools
108
+ if custom_tools:
109
+ converted_custom_tools = self.formatter.format_custom_tools(custom_tools)
110
+ combined_tools.extend(converted_custom_tools)
111
+
105
112
  # MCP tools
106
113
  mcp_tools = self.get_mcp_tools()
107
114
  if mcp_tools:
@@ -0,0 +1,73 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Gemini API parameters handler building SDK config with parameter mapping and exclusions.
4
+ """
5
+
6
+ from typing import Any, Dict, List, Set
7
+
8
+ from ._api_params_handler_base import APIParamsHandlerBase
9
+
10
+
11
+ class GeminiAPIParamsHandler(APIParamsHandlerBase):
12
+ def get_excluded_params(self) -> Set[str]:
13
+ base = self.get_base_excluded_params()
14
+ extra = {
15
+ "enable_web_search",
16
+ "enable_code_execution",
17
+ "use_multi_mcp",
18
+ "mcp_sdk_auto",
19
+ "allowed_tools",
20
+ "exclude_tools",
21
+ "custom_tools",
22
+ }
23
+ return set(base) | extra
24
+
25
+ def get_provider_tools(self, all_params: Dict[str, Any]) -> List[Dict[str, Any]]:
26
+ """
27
+ These are SDK Tool objects (from google.genai.types), not JSON tool declarations.
28
+ """
29
+ tools: List[Any] = []
30
+
31
+ if all_params.get("enable_web_search", False):
32
+ try:
33
+ from google.genai.types import GoogleSearch, Tool
34
+
35
+ tools.append(Tool(google_search=GoogleSearch()))
36
+ except Exception:
37
+ # Gracefully ignore if SDK not available
38
+ pass
39
+
40
+ if all_params.get("enable_code_execution", False):
41
+ try:
42
+ from google.genai.types import Tool, ToolCodeExecution
43
+
44
+ tools.append(Tool(code_execution=ToolCodeExecution()))
45
+ except Exception:
46
+ # Gracefully ignore if SDK not available
47
+ pass
48
+
49
+ return tools
50
+
51
+ async def build_api_params(self, messages: List[Dict[str, Any]], tools: List[Dict[str, Any]], all_params: Dict[str, Any]) -> Dict[str, Any]:
52
+ """
53
+ Build a config dict for google-genai Client.generate_content_stream.
54
+ - Map max_tokens -> max_output_tokens
55
+ - Do not include 'model' here; caller extracts it
56
+ - Do not add builtin tools; stream logic handles them
57
+ - Do not handle MCP tools or coordination schema here
58
+ """
59
+ excluded = self.get_excluded_params()
60
+ config: Dict[str, Any] = {}
61
+
62
+ for key, value in all_params.items():
63
+ if key in excluded or value is None:
64
+ continue
65
+ if key == "max_tokens":
66
+ config["max_output_tokens"] = value
67
+ elif key == "model":
68
+ # Caller will extract model separately
69
+ continue
70
+ else:
71
+ config[key] = value
72
+
73
+ return config
@@ -22,6 +22,7 @@ class ResponseAPIParamsHandler(APIParamsHandlerBase):
22
22
  "enable_code_interpreter",
23
23
  "allowed_tools",
24
24
  "exclude_tools",
25
+ "custom_tools", # Custom tools configuration (processed separately)
25
26
  "_has_file_search_files", # Internal flag for file search tracking
26
27
  },
27
28
  )
@@ -88,11 +89,17 @@ class ResponseAPIParamsHandler(APIParamsHandlerBase):
88
89
  if provider_tools:
89
90
  combined_tools.extend(provider_tools)
90
91
 
91
- # Add framework tools
92
+ # Add workflow tools
92
93
  if tools:
93
94
  converted_tools = self.formatter.format_tools(tools)
94
95
  combined_tools.extend(converted_tools)
95
96
 
97
+ # Add custom tools
98
+ custom_tools = self.custom_tool_manager.registered_tools
99
+ if custom_tools:
100
+ converted_custom_tools = self.formatter.format_custom_tools(custom_tools)
101
+ combined_tools.extend(converted_custom_tools)
102
+
96
103
  # Add MCP tools (use OpenAI format)
97
104
  mcp_tools = self._convert_mcp_tools_to_openai_format()
98
105
  if mcp_tools:
massgen/backend/base.py CHANGED
@@ -59,9 +59,22 @@ class LLMBackend(ABC):
59
59
  # Initialize utility classes
60
60
  self.token_usage = TokenUsage()
61
61
 
62
+ # # Initialize tool manager
63
+ # self.custom_tool_manager = ToolManager()
64
+
65
+ # # Register custom tools if specified
66
+ # custom_tools = kwargs.get("custom_tools", [])
67
+ # if custom_tools:
68
+ # self._register_custom_tools(custom_tools)
69
+
62
70
  # Planning mode flag - when True, MCP tools should be blocked during coordination
63
71
  self._planning_mode_enabled: bool = False
64
72
 
73
+ # Selective tool blocking - list of specific MCP tools to block during planning mode
74
+ # When planning_mode is enabled, only these specific tools are blocked
75
+ # If empty, ALL MCP tools are blocked (backward compatible behavior)
76
+ self._planning_mode_blocked_tools: set = set()
77
+
65
78
  self.token_calculator = TokenCostCalculator()
66
79
 
67
80
  # Filesystem manager integration
@@ -125,6 +138,29 @@ class LLMBackend(ABC):
125
138
  self.api_params_handler = None
126
139
  self.coordination_stage = None
127
140
 
141
+ # def _register_custom_tools(self, tool_names: list[str]) -> None:
142
+ # """Register custom tool functions.
143
+
144
+ # Args:
145
+ # tool_names: List of tool names to register
146
+ # """
147
+ # import importlib
148
+
149
+ # for tool_name in tool_names:
150
+ # try:
151
+ # # Try to import from tool module
152
+ # module = importlib.import_module("massgen.tool")
153
+ # if hasattr(module, tool_name):
154
+ # tool_func = getattr(module, tool_name)
155
+ # self.custom_tool_manager.add_tool_function(tool_func)
156
+ # print(f"Successfully registered custom tool: {tool_name}")
157
+ # else:
158
+ # print(f"Warning: Tool '{tool_name}' not found in massgen.tool")
159
+ # except ImportError as e:
160
+ # print(f"Warning: Could not import tool module: {e}")
161
+ # except Exception as e:
162
+ # print(f"Error registering tool '{tool_name}': {e}")
163
+
128
164
  def _setup_permission_hooks(self):
129
165
  """Setup permission hooks for function-based backends (default behavior)."""
130
166
  # Create per-agent hook manager
@@ -434,6 +470,53 @@ class LLMBackend(ABC):
434
470
  """
435
471
  return self._planning_mode_enabled
436
472
 
473
+ def set_planning_mode_blocked_tools(self, tool_names: set) -> None:
474
+ """
475
+ Set specific MCP tools to block during planning mode.
476
+
477
+ This enables selective tool blocking - only the specified tools will be blocked
478
+ when planning mode is enabled, allowing other MCP tools to be used.
479
+
480
+ Args:
481
+ tool_names: Set of MCP tool names to block (e.g., {'mcp__discord__discord_send'})
482
+ If empty set, ALL MCP tools are blocked (backward compatible)
483
+ """
484
+ self._planning_mode_blocked_tools = set(tool_names)
485
+
486
+ def get_planning_mode_blocked_tools(self) -> set:
487
+ """
488
+ Get the set of MCP tools currently blocked in planning mode.
489
+
490
+ Returns:
491
+ Set of blocked MCP tool names. Empty set means ALL MCP tools are blocked.
492
+ """
493
+ return self._planning_mode_blocked_tools.copy()
494
+
495
+ def is_mcp_tool_blocked(self, tool_name: str) -> bool:
496
+ """
497
+ Check if a specific MCP tool is blocked in planning mode.
498
+
499
+ Args:
500
+ tool_name: Name of the MCP tool to check (e.g., 'mcp__discord__discord_send')
501
+
502
+ Returns:
503
+ True if the tool should be blocked, False otherwise
504
+
505
+ Note:
506
+ - If planning mode is disabled, returns False (no blocking)
507
+ - If planning mode is enabled and blocked_tools is empty, returns True (block ALL)
508
+ - If planning mode is enabled and blocked_tools is set, returns True only if tool is in the set
509
+ """
510
+ if not self._planning_mode_enabled:
511
+ return False
512
+
513
+ # Empty set means block ALL MCP tools (backward compatible behavior)
514
+ if not self._planning_mode_blocked_tools:
515
+ return True
516
+
517
+ # Otherwise, block only if tool is in the blocked set
518
+ return tool_name in self._planning_mode_blocked_tools
519
+
437
520
  async def _cleanup_client(self, client: Any) -> None:
438
521
  """Clean up OpenAI client resources."""
439
522
  try: