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.
- massgen/__init__.py +142 -8
- massgen/adapters/__init__.py +29 -0
- massgen/adapters/ag2_adapter.py +483 -0
- massgen/adapters/base.py +183 -0
- massgen/adapters/tests/__init__.py +0 -0
- massgen/adapters/tests/test_ag2_adapter.py +439 -0
- massgen/adapters/tests/test_agent_adapter.py +128 -0
- massgen/adapters/utils/__init__.py +2 -0
- massgen/adapters/utils/ag2_utils.py +236 -0
- massgen/adapters/utils/tests/__init__.py +0 -0
- massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
- massgen/agent_config.py +329 -55
- massgen/api_params_handler/__init__.py +10 -0
- massgen/api_params_handler/_api_params_handler_base.py +99 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
- massgen/api_params_handler/_claude_api_params_handler.py +113 -0
- massgen/api_params_handler/_response_api_params_handler.py +130 -0
- massgen/backend/__init__.py +39 -4
- massgen/backend/azure_openai.py +385 -0
- massgen/backend/base.py +341 -69
- massgen/backend/base_with_mcp.py +1102 -0
- massgen/backend/capabilities.py +386 -0
- massgen/backend/chat_completions.py +577 -130
- massgen/backend/claude.py +1033 -537
- massgen/backend/claude_code.py +1203 -0
- massgen/backend/cli_base.py +209 -0
- massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
- massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
- massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
- massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
- massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
- massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
- massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
- massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
- massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
- massgen/backend/docs/inference_backend.md +257 -0
- massgen/backend/docs/permissions_and_context_files.md +1085 -0
- massgen/backend/external.py +126 -0
- massgen/backend/gemini.py +1850 -241
- massgen/backend/grok.py +40 -156
- massgen/backend/inference.py +156 -0
- massgen/backend/lmstudio.py +171 -0
- massgen/backend/response.py +1095 -322
- massgen/chat_agent.py +131 -113
- massgen/cli.py +1560 -275
- massgen/config_builder.py +2396 -0
- massgen/configs/BACKEND_CONFIGURATION.md +458 -0
- massgen/configs/README.md +559 -216
- massgen/configs/ag2/ag2_case_study.yaml +27 -0
- massgen/configs/ag2/ag2_coder.yaml +34 -0
- massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
- massgen/configs/ag2/ag2_gemini.yaml +27 -0
- massgen/configs/ag2/ag2_groupchat.yaml +108 -0
- massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
- massgen/configs/ag2/ag2_single_agent.yaml +21 -0
- massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
- massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
- massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
- massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
- massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
- massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
- massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
- massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
- massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
- massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
- massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
- massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
- massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
- massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
- massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
- massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
- massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
- massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
- massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
- massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
- massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
- massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
- massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
- massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
- massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
- massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
- massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
- massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
- massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
- massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
- massgen/configs/debug/skip_coordination_test.yaml +27 -0
- massgen/configs/debug/test_sdk_migration.yaml +17 -0
- massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
- massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
- massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
- massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
- massgen/configs/providers/claude/claude.yaml +14 -0
- massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
- massgen/configs/providers/local/lmstudio.yaml +11 -0
- massgen/configs/providers/openai/gpt5.yaml +46 -0
- massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
- massgen/configs/providers/others/grok_single_agent.yaml +19 -0
- massgen/configs/providers/others/zai_coding_team.yaml +108 -0
- massgen/configs/providers/others/zai_glm45.yaml +12 -0
- massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
- massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
- massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
- massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
- massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
- massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
- massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
- massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
- massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
- massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
- massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
- massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
- massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
- massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
- massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
- massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
- massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
- massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
- massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
- massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
- massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
- massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
- massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
- massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
- massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
- massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
- massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
- massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
- massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
- massgen/coordination_tracker.py +708 -0
- massgen/docker/README.md +462 -0
- massgen/filesystem_manager/__init__.py +21 -0
- massgen/filesystem_manager/_base.py +9 -0
- massgen/filesystem_manager/_code_execution_server.py +545 -0
- massgen/filesystem_manager/_docker_manager.py +477 -0
- massgen/filesystem_manager/_file_operation_tracker.py +248 -0
- massgen/filesystem_manager/_filesystem_manager.py +813 -0
- massgen/filesystem_manager/_path_permission_manager.py +1261 -0
- massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
- massgen/formatter/__init__.py +10 -0
- massgen/formatter/_chat_completions_formatter.py +284 -0
- massgen/formatter/_claude_formatter.py +235 -0
- massgen/formatter/_formatter_base.py +156 -0
- massgen/formatter/_response_formatter.py +263 -0
- massgen/frontend/__init__.py +1 -2
- massgen/frontend/coordination_ui.py +471 -286
- massgen/frontend/displays/base_display.py +56 -11
- massgen/frontend/displays/create_coordination_table.py +1956 -0
- massgen/frontend/displays/rich_terminal_display.py +1259 -619
- massgen/frontend/displays/simple_display.py +9 -4
- massgen/frontend/displays/terminal_display.py +27 -68
- massgen/logger_config.py +681 -0
- massgen/mcp_tools/README.md +232 -0
- massgen/mcp_tools/__init__.py +105 -0
- massgen/mcp_tools/backend_utils.py +1035 -0
- massgen/mcp_tools/circuit_breaker.py +195 -0
- massgen/mcp_tools/client.py +894 -0
- massgen/mcp_tools/config_validator.py +138 -0
- massgen/mcp_tools/docs/circuit_breaker.md +646 -0
- massgen/mcp_tools/docs/client.md +950 -0
- massgen/mcp_tools/docs/config_validator.md +478 -0
- massgen/mcp_tools/docs/exceptions.md +1165 -0
- massgen/mcp_tools/docs/security.md +854 -0
- massgen/mcp_tools/exceptions.py +338 -0
- massgen/mcp_tools/hooks.py +212 -0
- massgen/mcp_tools/security.py +780 -0
- massgen/message_templates.py +342 -64
- massgen/orchestrator.py +1515 -241
- massgen/stream_chunk/__init__.py +35 -0
- massgen/stream_chunk/base.py +92 -0
- massgen/stream_chunk/multimodal.py +237 -0
- massgen/stream_chunk/text.py +162 -0
- massgen/tests/mcp_test_server.py +150 -0
- massgen/tests/multi_turn_conversation_design.md +0 -8
- massgen/tests/test_azure_openai_backend.py +156 -0
- massgen/tests/test_backend_capabilities.py +262 -0
- massgen/tests/test_backend_event_loop_all.py +179 -0
- massgen/tests/test_chat_completions_refactor.py +142 -0
- massgen/tests/test_claude_backend.py +15 -28
- massgen/tests/test_claude_code.py +268 -0
- massgen/tests/test_claude_code_context_sharing.py +233 -0
- massgen/tests/test_claude_code_orchestrator.py +175 -0
- massgen/tests/test_cli_backends.py +180 -0
- massgen/tests/test_code_execution.py +679 -0
- massgen/tests/test_external_agent_backend.py +134 -0
- massgen/tests/test_final_presentation_fallback.py +237 -0
- massgen/tests/test_gemini_planning_mode.py +351 -0
- massgen/tests/test_grok_backend.py +7 -10
- massgen/tests/test_http_mcp_server.py +42 -0
- massgen/tests/test_integration_simple.py +198 -0
- massgen/tests/test_mcp_blocking.py +125 -0
- massgen/tests/test_message_context_building.py +29 -47
- massgen/tests/test_orchestrator_final_presentation.py +48 -0
- massgen/tests/test_path_permission_manager.py +2087 -0
- massgen/tests/test_rich_terminal_display.py +14 -13
- massgen/tests/test_timeout.py +133 -0
- massgen/tests/test_v3_3agents.py +11 -12
- massgen/tests/test_v3_simple.py +8 -13
- massgen/tests/test_v3_three_agents.py +11 -18
- massgen/tests/test_v3_two_agents.py +8 -13
- massgen/token_manager/__init__.py +7 -0
- massgen/token_manager/token_manager.py +400 -0
- massgen/utils.py +52 -16
- massgen/v1/agent.py +45 -91
- massgen/v1/agents.py +18 -53
- massgen/v1/backends/gemini.py +50 -153
- massgen/v1/backends/grok.py +21 -54
- massgen/v1/backends/oai.py +39 -111
- massgen/v1/cli.py +36 -93
- massgen/v1/config.py +8 -12
- massgen/v1/logging.py +43 -127
- massgen/v1/main.py +18 -32
- massgen/v1/orchestrator.py +68 -209
- massgen/v1/streaming_display.py +62 -163
- massgen/v1/tools.py +8 -12
- massgen/v1/types.py +9 -23
- massgen/v1/utils.py +5 -23
- massgen-0.1.0.dist-info/METADATA +1245 -0
- massgen-0.1.0.dist-info/RECORD +273 -0
- massgen-0.1.0.dist-info/entry_points.txt +2 -0
- massgen/frontend/logging/__init__.py +0 -9
- massgen/frontend/logging/realtime_logger.py +0 -197
- massgen-0.0.3.dist-info/METADATA +0 -568
- massgen-0.0.3.dist-info/RECORD +0 -76
- massgen-0.0.3.dist-info/entry_points.txt +0 -2
- /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
]
|