massgen 0.0.3__py3-none-any.whl → 0.1.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 massgen might be problematic. Click here for more details.

Files changed (268) hide show
  1. massgen/__init__.py +142 -8
  2. massgen/adapters/__init__.py +29 -0
  3. massgen/adapters/ag2_adapter.py +483 -0
  4. massgen/adapters/base.py +183 -0
  5. massgen/adapters/tests/__init__.py +0 -0
  6. massgen/adapters/tests/test_ag2_adapter.py +439 -0
  7. massgen/adapters/tests/test_agent_adapter.py +128 -0
  8. massgen/adapters/utils/__init__.py +2 -0
  9. massgen/adapters/utils/ag2_utils.py +236 -0
  10. massgen/adapters/utils/tests/__init__.py +0 -0
  11. massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
  12. massgen/agent_config.py +329 -55
  13. massgen/api_params_handler/__init__.py +10 -0
  14. massgen/api_params_handler/_api_params_handler_base.py +99 -0
  15. massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
  16. massgen/api_params_handler/_claude_api_params_handler.py +113 -0
  17. massgen/api_params_handler/_response_api_params_handler.py +130 -0
  18. massgen/backend/__init__.py +39 -4
  19. massgen/backend/azure_openai.py +385 -0
  20. massgen/backend/base.py +341 -69
  21. massgen/backend/base_with_mcp.py +1102 -0
  22. massgen/backend/capabilities.py +386 -0
  23. massgen/backend/chat_completions.py +577 -130
  24. massgen/backend/claude.py +1033 -537
  25. massgen/backend/claude_code.py +1203 -0
  26. massgen/backend/cli_base.py +209 -0
  27. massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
  28. massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
  29. massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
  30. massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
  31. massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
  32. massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
  33. massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
  34. massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
  35. massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
  36. massgen/backend/docs/inference_backend.md +257 -0
  37. massgen/backend/docs/permissions_and_context_files.md +1085 -0
  38. massgen/backend/external.py +126 -0
  39. massgen/backend/gemini.py +1850 -241
  40. massgen/backend/grok.py +40 -156
  41. massgen/backend/inference.py +156 -0
  42. massgen/backend/lmstudio.py +171 -0
  43. massgen/backend/response.py +1095 -322
  44. massgen/chat_agent.py +131 -113
  45. massgen/cli.py +1560 -275
  46. massgen/config_builder.py +2396 -0
  47. massgen/configs/BACKEND_CONFIGURATION.md +458 -0
  48. massgen/configs/README.md +559 -216
  49. massgen/configs/ag2/ag2_case_study.yaml +27 -0
  50. massgen/configs/ag2/ag2_coder.yaml +34 -0
  51. massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
  52. massgen/configs/ag2/ag2_gemini.yaml +27 -0
  53. massgen/configs/ag2/ag2_groupchat.yaml +108 -0
  54. massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
  55. massgen/configs/ag2/ag2_single_agent.yaml +21 -0
  56. massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
  57. massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
  58. massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
  59. massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
  60. massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
  61. massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
  62. massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
  63. massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
  64. massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
  65. massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
  66. massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
  67. massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
  68. massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
  69. massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
  70. massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
  71. massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
  72. massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
  73. massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
  74. massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
  75. massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
  76. massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
  77. massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
  78. massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
  79. massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
  80. massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
  81. massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
  82. massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
  83. massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
  84. massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
  85. massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
  86. massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
  87. massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
  88. massgen/configs/debug/skip_coordination_test.yaml +27 -0
  89. massgen/configs/debug/test_sdk_migration.yaml +17 -0
  90. massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
  91. massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
  92. massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
  93. massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
  94. massgen/configs/providers/claude/claude.yaml +14 -0
  95. massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
  96. massgen/configs/providers/local/lmstudio.yaml +11 -0
  97. massgen/configs/providers/openai/gpt5.yaml +46 -0
  98. massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
  99. massgen/configs/providers/others/grok_single_agent.yaml +19 -0
  100. massgen/configs/providers/others/zai_coding_team.yaml +108 -0
  101. massgen/configs/providers/others/zai_glm45.yaml +12 -0
  102. massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
  103. massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
  104. massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
  105. massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
  106. massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
  107. massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
  108. massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
  109. massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
  110. massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
  111. massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
  112. massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
  113. massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
  114. massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
  115. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
  116. massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
  117. massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
  118. massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
  119. massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
  120. massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
  121. massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
  122. massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
  123. massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
  124. massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
  125. massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
  126. massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
  127. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
  128. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
  129. massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
  130. massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
  131. massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
  132. massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
  133. massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
  134. massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
  135. massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
  136. massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
  137. massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
  138. massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
  139. massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
  140. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
  141. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
  142. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
  143. massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
  144. massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
  145. massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
  146. massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
  147. massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
  148. massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
  149. massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
  150. massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
  151. massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
  152. massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
  153. massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
  154. massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
  155. massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
  156. massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
  157. massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
  158. massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
  159. massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
  160. massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
  161. massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
  162. massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
  163. massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
  164. massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
  165. massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
  166. massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
  167. massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
  168. massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
  169. massgen/coordination_tracker.py +708 -0
  170. massgen/docker/README.md +462 -0
  171. massgen/filesystem_manager/__init__.py +21 -0
  172. massgen/filesystem_manager/_base.py +9 -0
  173. massgen/filesystem_manager/_code_execution_server.py +545 -0
  174. massgen/filesystem_manager/_docker_manager.py +477 -0
  175. massgen/filesystem_manager/_file_operation_tracker.py +248 -0
  176. massgen/filesystem_manager/_filesystem_manager.py +813 -0
  177. massgen/filesystem_manager/_path_permission_manager.py +1261 -0
  178. massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
  179. massgen/formatter/__init__.py +10 -0
  180. massgen/formatter/_chat_completions_formatter.py +284 -0
  181. massgen/formatter/_claude_formatter.py +235 -0
  182. massgen/formatter/_formatter_base.py +156 -0
  183. massgen/formatter/_response_formatter.py +263 -0
  184. massgen/frontend/__init__.py +1 -2
  185. massgen/frontend/coordination_ui.py +471 -286
  186. massgen/frontend/displays/base_display.py +56 -11
  187. massgen/frontend/displays/create_coordination_table.py +1956 -0
  188. massgen/frontend/displays/rich_terminal_display.py +1259 -619
  189. massgen/frontend/displays/simple_display.py +9 -4
  190. massgen/frontend/displays/terminal_display.py +27 -68
  191. massgen/logger_config.py +681 -0
  192. massgen/mcp_tools/README.md +232 -0
  193. massgen/mcp_tools/__init__.py +105 -0
  194. massgen/mcp_tools/backend_utils.py +1035 -0
  195. massgen/mcp_tools/circuit_breaker.py +195 -0
  196. massgen/mcp_tools/client.py +894 -0
  197. massgen/mcp_tools/config_validator.py +138 -0
  198. massgen/mcp_tools/docs/circuit_breaker.md +646 -0
  199. massgen/mcp_tools/docs/client.md +950 -0
  200. massgen/mcp_tools/docs/config_validator.md +478 -0
  201. massgen/mcp_tools/docs/exceptions.md +1165 -0
  202. massgen/mcp_tools/docs/security.md +854 -0
  203. massgen/mcp_tools/exceptions.py +338 -0
  204. massgen/mcp_tools/hooks.py +212 -0
  205. massgen/mcp_tools/security.py +780 -0
  206. massgen/message_templates.py +342 -64
  207. massgen/orchestrator.py +1515 -241
  208. massgen/stream_chunk/__init__.py +35 -0
  209. massgen/stream_chunk/base.py +92 -0
  210. massgen/stream_chunk/multimodal.py +237 -0
  211. massgen/stream_chunk/text.py +162 -0
  212. massgen/tests/mcp_test_server.py +150 -0
  213. massgen/tests/multi_turn_conversation_design.md +0 -8
  214. massgen/tests/test_azure_openai_backend.py +156 -0
  215. massgen/tests/test_backend_capabilities.py +262 -0
  216. massgen/tests/test_backend_event_loop_all.py +179 -0
  217. massgen/tests/test_chat_completions_refactor.py +142 -0
  218. massgen/tests/test_claude_backend.py +15 -28
  219. massgen/tests/test_claude_code.py +268 -0
  220. massgen/tests/test_claude_code_context_sharing.py +233 -0
  221. massgen/tests/test_claude_code_orchestrator.py +175 -0
  222. massgen/tests/test_cli_backends.py +180 -0
  223. massgen/tests/test_code_execution.py +679 -0
  224. massgen/tests/test_external_agent_backend.py +134 -0
  225. massgen/tests/test_final_presentation_fallback.py +237 -0
  226. massgen/tests/test_gemini_planning_mode.py +351 -0
  227. massgen/tests/test_grok_backend.py +7 -10
  228. massgen/tests/test_http_mcp_server.py +42 -0
  229. massgen/tests/test_integration_simple.py +198 -0
  230. massgen/tests/test_mcp_blocking.py +125 -0
  231. massgen/tests/test_message_context_building.py +29 -47
  232. massgen/tests/test_orchestrator_final_presentation.py +48 -0
  233. massgen/tests/test_path_permission_manager.py +2087 -0
  234. massgen/tests/test_rich_terminal_display.py +14 -13
  235. massgen/tests/test_timeout.py +133 -0
  236. massgen/tests/test_v3_3agents.py +11 -12
  237. massgen/tests/test_v3_simple.py +8 -13
  238. massgen/tests/test_v3_three_agents.py +11 -18
  239. massgen/tests/test_v3_two_agents.py +8 -13
  240. massgen/token_manager/__init__.py +7 -0
  241. massgen/token_manager/token_manager.py +400 -0
  242. massgen/utils.py +52 -16
  243. massgen/v1/agent.py +45 -91
  244. massgen/v1/agents.py +18 -53
  245. massgen/v1/backends/gemini.py +50 -153
  246. massgen/v1/backends/grok.py +21 -54
  247. massgen/v1/backends/oai.py +39 -111
  248. massgen/v1/cli.py +36 -93
  249. massgen/v1/config.py +8 -12
  250. massgen/v1/logging.py +43 -127
  251. massgen/v1/main.py +18 -32
  252. massgen/v1/orchestrator.py +68 -209
  253. massgen/v1/streaming_display.py +62 -163
  254. massgen/v1/tools.py +8 -12
  255. massgen/v1/types.py +9 -23
  256. massgen/v1/utils.py +5 -23
  257. massgen-0.1.0.dist-info/METADATA +1245 -0
  258. massgen-0.1.0.dist-info/RECORD +273 -0
  259. massgen-0.1.0.dist-info/entry_points.txt +2 -0
  260. massgen/frontend/logging/__init__.py +0 -9
  261. massgen/frontend/logging/realtime_logger.py +0 -197
  262. massgen-0.0.3.dist-info/METADATA +0 -568
  263. massgen-0.0.3.dist-info/RECORD +0 -76
  264. massgen-0.0.3.dist-info/entry_points.txt +0 -2
  265. /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
  266. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
  267. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
  268. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,338 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ MCP-specific exceptions with enhanced error handling and context preservation.
4
+ """
5
+
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Dict, Optional, Union
8
+
9
+ from ..logger_config import logger
10
+
11
+
12
+ class MCPError(Exception):
13
+ """
14
+ Base exception for MCP-related errors.
15
+
16
+ Provides structured error information and context preservation
17
+ with enhanced debugging capabilities.
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ message: str,
23
+ context: Optional[Dict[str, Any]] = None,
24
+ error_code: Optional[str] = None,
25
+ timestamp: Optional[datetime] = None,
26
+ ):
27
+ super().__init__(message)
28
+ self.context = self._sanitize_context(context or {})
29
+ self.error_code = error_code
30
+ self.timestamp = timestamp or datetime.now(timezone.utc)
31
+ self.original_message = message
32
+
33
+ def _sanitize_context(self, context: Dict[str, Any]) -> Dict[str, Any]:
34
+ """
35
+ Sanitize context to remove sensitive information and ensure serializability.
36
+ """
37
+ sanitized = {}
38
+ sensitive_keys = {"password", "token", "secret", "key", "auth", "credential"}
39
+
40
+ for key, value in context.items():
41
+ if any(sensitive in key.lower() for sensitive in sensitive_keys):
42
+ sanitized[key] = "[REDACTED]"
43
+ elif isinstance(value, (str, int, float, bool, type(None))):
44
+ sanitized[key] = value
45
+ else:
46
+ sanitized[key] = str(value)
47
+
48
+ return sanitized
49
+
50
+ def _build_context_from_kwargs(self, base_context: Optional[Dict[str, Any]] = None, **kwargs: Any) -> Dict[str, Any]:
51
+ """
52
+ Merge base context with kwargs, ignoring None values.
53
+
54
+ Copies the provided base_context (or initializes an empty dict) and updates it
55
+ with key/value pairs from kwargs where the value is not None. Returns the
56
+ resulting context dict for use in specialized error classes.
57
+ """
58
+ context: Dict[str, Any] = dict(base_context or {})
59
+ for key, value in kwargs.items():
60
+ if value is None:
61
+ continue
62
+ context[key] = value
63
+ return context
64
+
65
+ def __str__(self) -> str:
66
+ parts = [self.original_message]
67
+
68
+ if self.error_code:
69
+ parts.append(f"Code: {self.error_code}")
70
+
71
+ if self.context:
72
+ context_items = [f"{k}={v}" for k, v in self.context.items()]
73
+ parts.append(f"Context: {', '.join(context_items)}")
74
+
75
+ return " | ".join(parts)
76
+
77
+ def to_dict(self) -> Dict[str, Any]:
78
+ return {
79
+ "error_type": self.__class__.__name__,
80
+ "message": self.original_message,
81
+ "error_code": self.error_code,
82
+ "context": self.context,
83
+ "timestamp": self.timestamp.isoformat(),
84
+ }
85
+
86
+ def log_error(self) -> None:
87
+ """Log the error with appropriate level and context."""
88
+ logger.error(
89
+ f"{self.__class__.__name__}: {self.original_message}",
90
+ extra={"mcp_error": self.to_dict()},
91
+ )
92
+
93
+
94
+ class MCPConnectionError(MCPError):
95
+ """
96
+ Raised when MCP server connection fails.
97
+
98
+ Includes connection details for debugging and retry logic.
99
+ """
100
+
101
+ def __init__(
102
+ self,
103
+ message: str,
104
+ server_name: Optional[str] = None,
105
+ transport_type: Optional[str] = None,
106
+ host: Optional[str] = None,
107
+ port: Optional[int] = None,
108
+ retry_count: Optional[int] = None,
109
+ context: Optional[Dict[str, Any]] = None,
110
+ error_code: Optional[str] = None,
111
+ ):
112
+ ctx = self._build_context_from_kwargs(
113
+ context or {},
114
+ server_name=server_name,
115
+ transport_type=transport_type,
116
+ host=host,
117
+ port=port,
118
+ retry_count=retry_count,
119
+ )
120
+
121
+ super().__init__(message, ctx, error_code)
122
+
123
+ # Store as instance attributes for easy access
124
+ self.server_name = server_name
125
+ self.transport_type = transport_type
126
+ self.host = host
127
+ self.port = port
128
+ self.retry_count = retry_count
129
+
130
+
131
+ class MCPServerError(MCPError):
132
+ """
133
+ Raised when MCP server returns an error.
134
+
135
+ Includes server error codes, HTTP status codes, and additional context.
136
+ """
137
+
138
+ def __init__(
139
+ self,
140
+ message: str,
141
+ code: Optional[Union[int, str]] = None,
142
+ server_name: Optional[str] = None,
143
+ http_status: Optional[int] = None,
144
+ response_data: Optional[Dict[str, Any]] = None,
145
+ context: Optional[Dict[str, Any]] = None,
146
+ error_code: Optional[str] = None,
147
+ ):
148
+ ctx = self._build_context_from_kwargs(
149
+ context or {},
150
+ server_error_code=code,
151
+ server_name=server_name,
152
+ http_status=http_status,
153
+ response_data=response_data,
154
+ )
155
+
156
+ super().__init__(message, ctx, error_code)
157
+
158
+ # Store as instance attributes
159
+ self.code = code
160
+ self.server_name = server_name
161
+ self.http_status = http_status
162
+ self.response_data = response_data
163
+
164
+
165
+ class MCPValidationError(MCPError):
166
+ """
167
+ Raised when MCP configuration or input validation fails.
168
+
169
+ Includes detailed validation information for debugging.
170
+ """
171
+
172
+ def __init__(
173
+ self,
174
+ message: str,
175
+ field: Optional[str] = None,
176
+ value: Optional[Any] = None,
177
+ expected_type: Optional[str] = None,
178
+ validation_rule: Optional[str] = None,
179
+ context: Optional[Dict[str, Any]] = None,
180
+ error_code: Optional[str] = None,
181
+ ):
182
+ value_str: Optional[str] = None
183
+ if value is not None:
184
+ try:
185
+ value_str = str(value)
186
+ except Exception:
187
+ value_str = "[UNCONVERTIBLE]"
188
+ if len(value_str) > 100:
189
+ value_str = value_str[:100]
190
+
191
+ ctx = self._build_context_from_kwargs(
192
+ context or {},
193
+ field=field,
194
+ value=value_str,
195
+ expected_type=expected_type,
196
+ validation_rule=validation_rule,
197
+ )
198
+
199
+ super().__init__(message, ctx, error_code)
200
+
201
+ # Store as instance attributes
202
+ self.field = field
203
+ self.value = value
204
+ self.expected_type = expected_type
205
+ self.validation_rule = validation_rule
206
+
207
+
208
+ class MCPTimeoutError(MCPError):
209
+ """
210
+ Raised when MCP operations timeout.
211
+
212
+ Includes timeout details and operation context for retry logic.
213
+ """
214
+
215
+ def __init__(
216
+ self,
217
+ message: str,
218
+ timeout_seconds: Optional[float] = None,
219
+ operation: Optional[str] = None,
220
+ elapsed_seconds: Optional[float] = None,
221
+ server_name: Optional[str] = None,
222
+ context: Optional[Dict[str, Any]] = None,
223
+ error_code: Optional[str] = None,
224
+ ):
225
+ ctx = self._build_context_from_kwargs(
226
+ context or {},
227
+ timeout_seconds=timeout_seconds,
228
+ operation=operation,
229
+ elapsed_seconds=elapsed_seconds,
230
+ server_name=server_name,
231
+ )
232
+
233
+ super().__init__(message, ctx, error_code)
234
+
235
+ # Store as instance attributes
236
+ self.timeout_seconds = timeout_seconds
237
+ self.operation = operation
238
+ self.elapsed_seconds = elapsed_seconds
239
+ self.server_name = server_name
240
+
241
+
242
+ class MCPAuthenticationError(MCPError):
243
+ """
244
+ Raised when MCP authentication or authorization fails.
245
+
246
+ Includes authentication context without exposing sensitive information.
247
+ """
248
+
249
+ def __init__(
250
+ self,
251
+ message: str,
252
+ auth_type: Optional[str] = None,
253
+ username: Optional[str] = None,
254
+ server_name: Optional[str] = None,
255
+ permission_required: Optional[str] = None,
256
+ context: Optional[Dict[str, Any]] = None,
257
+ error_code: Optional[str] = None,
258
+ ):
259
+ ctx = self._build_context_from_kwargs(
260
+ context or {},
261
+ auth_type=auth_type,
262
+ username=username,
263
+ server_name=server_name,
264
+ permission_required=permission_required,
265
+ )
266
+
267
+ super().__init__(message, ctx, error_code)
268
+
269
+ # Store as instance attributes
270
+ self.auth_type = auth_type
271
+ self.username = username
272
+ self.server_name = server_name
273
+ self.permission_required = permission_required
274
+
275
+
276
+ class MCPConfigurationError(MCPError):
277
+ """
278
+ Raised when MCP configuration is invalid or missing.
279
+
280
+ Includes configuration details for troubleshooting.
281
+ """
282
+
283
+ def __init__(
284
+ self,
285
+ message: str,
286
+ config_file: Optional[str] = None,
287
+ config_section: Optional[str] = None,
288
+ missing_keys: Optional[list] = None,
289
+ context: Optional[Dict[str, Any]] = None,
290
+ error_code: Optional[str] = None,
291
+ ):
292
+ ctx = self._build_context_from_kwargs(
293
+ context or {},
294
+ config_file=config_file,
295
+ config_section=config_section,
296
+ missing_keys=missing_keys,
297
+ )
298
+
299
+ super().__init__(message, ctx, error_code)
300
+
301
+ # Store as instance attributes
302
+ self.config_file = config_file
303
+ self.config_section = config_section
304
+ self.missing_keys = missing_keys
305
+
306
+
307
+ class MCPResourceError(MCPError):
308
+ """
309
+ Raised when MCP resource operations fail.
310
+
311
+ Includes resource details and operation context.
312
+ """
313
+
314
+ def __init__(
315
+ self,
316
+ message: str,
317
+ resource_type: Optional[str] = None,
318
+ resource_id: Optional[str] = None,
319
+ operation: Optional[str] = None,
320
+ server_name: Optional[str] = None,
321
+ context: Optional[Dict[str, Any]] = None,
322
+ error_code: Optional[str] = None,
323
+ ):
324
+ ctx = self._build_context_from_kwargs(
325
+ context or {},
326
+ resource_type=resource_type,
327
+ resource_id=resource_id,
328
+ operation=operation,
329
+ server_name=server_name,
330
+ )
331
+
332
+ super().__init__(message, ctx, error_code)
333
+
334
+ # Store as instance attributes
335
+ self.resource_type = resource_type
336
+ self.resource_id = resource_id
337
+ self.operation = operation
338
+ self.server_name = server_name
@@ -0,0 +1,212 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Hook system for MCP tool call interception.
4
+
5
+ This module provides the infrastructure for intercepting MCP tool calls
6
+ across different backend architectures:
7
+
8
+ 1. Function-based backends (OpenAI, Claude, etc.) - use FunctionHook
9
+ 2. Session-based backends (Gemini) - use PermissionClientSession
10
+
11
+ The actual permission logic is implemented in filesystem_manager.py
12
+ """
13
+
14
+ from abc import ABC, abstractmethod
15
+ from datetime import timedelta
16
+ from enum import Enum
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ from ..logger_config import logger
20
+
21
+ # MCP imports for session-based backends
22
+ try:
23
+ from mcp import ClientSession, types
24
+ from mcp.client.session import ProgressFnT
25
+
26
+ MCP_AVAILABLE = True
27
+ except ImportError:
28
+ MCP_AVAILABLE = False
29
+ ClientSession = object
30
+ types = None
31
+ ProgressFnT = None
32
+
33
+
34
+ class HookType(Enum):
35
+ """Types of function call hooks."""
36
+
37
+ PRE_CALL = "pre_call"
38
+ # Future: POST_CALL = "post_call"
39
+
40
+
41
+ class HookResult:
42
+ """Result of a hook execution."""
43
+
44
+ def __init__(self, allowed: bool, metadata: Optional[Dict[str, Any]] = None, modified_args: Optional[str] = None):
45
+ self.allowed = allowed
46
+ self.metadata = metadata or {}
47
+ self.modified_args = modified_args
48
+
49
+
50
+ class FunctionHook(ABC):
51
+ """Base class for function call hooks."""
52
+
53
+ def __init__(self, name: str):
54
+ self.name = name
55
+
56
+ @abstractmethod
57
+ async def execute(self, function_name: str, arguments: str, context: Optional[Dict[str, Any]] = None, **kwargs) -> HookResult:
58
+ """
59
+ Execute the hook.
60
+
61
+ Args:
62
+ function_name: Name of the function being called
63
+ arguments: JSON string of arguments
64
+ context: Additional context (backend, timestamp, etc.)
65
+
66
+ Returns:
67
+ HookResult with allowed flag and optional modifications
68
+ """
69
+
70
+
71
+ class FunctionHookManager:
72
+ """Manages registration and execution of function hooks."""
73
+
74
+ def __init__(self):
75
+ self._hooks: Dict[HookType, List[FunctionHook]] = {hook_type: [] for hook_type in HookType}
76
+ self._global_hooks: Dict[HookType, List[FunctionHook]] = {hook_type: [] for hook_type in HookType}
77
+
78
+ def register_hook(self, function_name: str, hook_type: HookType, hook: FunctionHook):
79
+ """Register a hook for a specific function."""
80
+ if function_name not in self._hooks:
81
+ self._hooks[function_name] = {hook_type: [] for hook_type in HookType}
82
+
83
+ if hook_type not in self._hooks[function_name]:
84
+ self._hooks[function_name][hook_type] = []
85
+
86
+ self._hooks[function_name][hook_type].append(hook)
87
+
88
+ def register_global_hook(self, hook_type: HookType, hook: FunctionHook):
89
+ """Register a hook that applies to all functions."""
90
+ self._global_hooks[hook_type].append(hook)
91
+
92
+ def get_hooks_for_function(self, function_name: str) -> Dict[HookType, List[FunctionHook]]:
93
+ """Get all hooks (function-specific + global) for a function."""
94
+ result = {hook_type: [] for hook_type in HookType}
95
+
96
+ # Add global hooks first
97
+ for hook_type in HookType:
98
+ result[hook_type].extend(self._global_hooks[hook_type])
99
+
100
+ # Add function-specific hooks
101
+ if function_name in self._hooks:
102
+ for hook_type in HookType:
103
+ if hook_type in self._hooks[function_name]:
104
+ result[hook_type].extend(self._hooks[function_name][hook_type])
105
+
106
+ return result
107
+
108
+ def clear_hooks(self):
109
+ """Clear all registered hooks."""
110
+ self._hooks.clear()
111
+ self._global_hooks = {hook_type: [] for hook_type in HookType}
112
+
113
+
114
+ class PermissionClientSession(ClientSession):
115
+ """
116
+ ClientSession subclass that intercepts tool calls to apply permission hooks.
117
+
118
+ This inherits from ClientSession instead of wrapping it, which ensures
119
+ compatibility with SDK type checking and attribute access.
120
+ """
121
+
122
+ def __init__(self, wrapped_session: ClientSession, permission_manager):
123
+ """
124
+ Initialize by copying state from an existing ClientSession.
125
+
126
+ Args:
127
+ wrapped_session: The actual ClientSession to copy state from
128
+ permission_manager: Object with pre_tool_use_hook method for validation
129
+ """
130
+ # Store the permission manager
131
+ self._permission_manager = permission_manager
132
+
133
+ # Copy all attributes from the wrapped session to this instance
134
+ # This is a bit hacky but necessary to preserve the session state
135
+ self.__dict__.update(wrapped_session.__dict__)
136
+
137
+ logger.debug(f"[PermissionClientSession] Created permission session from {id(wrapped_session)}")
138
+
139
+ async def call_tool(
140
+ self,
141
+ name: str,
142
+ arguments: dict[str, Any] | None = None,
143
+ read_timeout_seconds: timedelta | None = None,
144
+ progress_callback: ProgressFnT | None = None,
145
+ ) -> types.CallToolResult:
146
+ """
147
+ Override call_tool to apply permission hooks before calling the actual tool.
148
+ """
149
+ tool_args = arguments or {}
150
+
151
+ # Log tool call for debugging
152
+ logger.debug(f"[PermissionClientSession] Intercepted tool call: {name} with args: {tool_args}")
153
+
154
+ # Apply permission hook if available
155
+ if self._permission_manager and hasattr(self._permission_manager, "pre_tool_use_hook"):
156
+ try:
157
+ allowed, reason = await self._permission_manager.pre_tool_use_hook(name, tool_args)
158
+
159
+ if not allowed:
160
+ error_msg = f"Permission denied for tool '{name}'"
161
+ if reason:
162
+ error_msg += f": {reason}"
163
+ logger.warning(f"🚫 [PermissionClientSession] {error_msg}")
164
+
165
+ # Return an error result instead of calling the tool
166
+ return types.CallToolResult(content=[types.TextContent(type="text", text=f"Error: {error_msg}")], isError=True)
167
+ else:
168
+ logger.debug(f"[PermissionClientSession] Tool '{name}' permission check passed")
169
+
170
+ except Exception as e:
171
+ logger.error(f"[PermissionClientSession] Error in permission hook: {e}")
172
+ # Continue with the call if hook fails - don't break functionality
173
+
174
+ # Call the parent's call_tool method
175
+ try:
176
+ result = await super().call_tool(name=name, arguments=arguments, read_timeout_seconds=read_timeout_seconds, progress_callback=progress_callback)
177
+ logger.debug(f"[PermissionClientSession] Tool '{name}' completed successfully")
178
+ return result
179
+ except Exception as e:
180
+ logger.error(f"[PermissionClientSession] Tool '{name}' failed: {e}")
181
+ raise
182
+
183
+
184
+ def convert_sessions_to_permission_sessions(sessions: List[ClientSession], permission_manager) -> List[PermissionClientSession]:
185
+ """
186
+ Convert a list of ClientSession objects to PermissionClientSession subclasses.
187
+
188
+ Args:
189
+ sessions: List of ClientSession objects to convert
190
+ permission_manager: Object with pre_tool_use_hook method
191
+
192
+ Returns:
193
+ List of PermissionClientSession objects that apply permission hooks
194
+ """
195
+ logger.debug(f"[PermissionClientSession] Converting {len(sessions)} sessions to permission sessions")
196
+ converted = []
197
+ for session in sessions:
198
+ # Create a new PermissionClientSession that inherits from ClientSession
199
+ perm_session = PermissionClientSession(session, permission_manager)
200
+ converted.append(perm_session)
201
+ logger.debug(f"[PermissionClientSession] Successfully converted {len(converted)} sessions")
202
+ return converted
203
+
204
+
205
+ __all__ = [
206
+ "HookType",
207
+ "HookResult",
208
+ "FunctionHook",
209
+ "FunctionHookManager",
210
+ "PermissionClientSession",
211
+ "convert_sessions_to_permission_sessions",
212
+ ]