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,1165 @@
|
|
|
1
|
+
# MCP Exception System Documentation
|
|
2
|
+
|
|
3
|
+
The MCP (Model Context Protocol) exception system provides a comprehensive error handling framework with structured context preservation, automatic sanitization of sensitive information, and enhanced debugging capabilities for MCP integrations.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Exception Hierarchy](#exception-hierarchy)
|
|
9
|
+
- [Base Exception Class](#base-exception-class)
|
|
10
|
+
- [Specialized Exception Classes](#specialized-exception-classes)
|
|
11
|
+
- [Utility Functions](#utility-functions)
|
|
12
|
+
- [Usage Examples](#usage-examples)
|
|
13
|
+
- [Error Context and Sanitization](#error-context-and-sanitization)
|
|
14
|
+
- [Integration Patterns](#integration-patterns)
|
|
15
|
+
- [Troubleshooting Guide](#troubleshooting-guide)
|
|
16
|
+
- [Best Practices](#best-practices)
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
The MCP exception system is built around a hierarchical structure that provides:
|
|
21
|
+
|
|
22
|
+
- **Structured Error Information**: All exceptions carry detailed context about the error condition
|
|
23
|
+
- **Automatic Sanitization**: Sensitive information is automatically redacted from error contexts
|
|
24
|
+
- **Enhanced Debugging**: Rich error information with timestamps, error codes, and contextual data
|
|
25
|
+
- **Consistent Logging**: Standardized error logging with structured data
|
|
26
|
+
- **Error Chain Formatting**: Clear representation of exception chains for debugging
|
|
27
|
+
|
|
28
|
+
### Architecture
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
MCPError (Base)
|
|
32
|
+
├── MCPConnectionError (Connection failures)
|
|
33
|
+
├── MCPServerError (Server-side errors)
|
|
34
|
+
├── MCPValidationError (Input validation)
|
|
35
|
+
├── MCPTimeoutError (Operation timeouts)
|
|
36
|
+
├── MCPAuthenticationError (Auth failures)
|
|
37
|
+
├── MCPConfigurationError (Config issues)
|
|
38
|
+
└── MCPResourceError (Resource operations)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Exception Hierarchy
|
|
42
|
+
|
|
43
|
+
### MCPError (Base Class)
|
|
44
|
+
|
|
45
|
+
The foundation of all MCP exceptions, providing core functionality for context preservation and sanitization.
|
|
46
|
+
|
|
47
|
+
**Constructor Parameters:**
|
|
48
|
+
- `message` (str): Human-readable error message
|
|
49
|
+
- `context` (Optional[Dict[str, Any]]): Additional error context
|
|
50
|
+
- `error_code` (Optional[str]): Machine-readable error code
|
|
51
|
+
- `timestamp` (Optional[datetime]): Error timestamp (defaults to current UTC time)
|
|
52
|
+
|
|
53
|
+
**Instance Attributes:**
|
|
54
|
+
- `context`: Sanitized error context dictionary
|
|
55
|
+
- `error_code`: Optional error code for programmatic handling
|
|
56
|
+
- `timestamp`: UTC timestamp when error occurred
|
|
57
|
+
- `original_message`: Original error message before formatting
|
|
58
|
+
|
|
59
|
+
**Methods:**
|
|
60
|
+
- `to_dict()`: Convert exception to dictionary representation
|
|
61
|
+
- `log_error(logger)`: Log error with structured context
|
|
62
|
+
- `_sanitize_context(context)`: Remove sensitive information from context
|
|
63
|
+
|
|
64
|
+
## Base Exception Class
|
|
65
|
+
|
|
66
|
+
### MCPError
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from massgen.mcp_tools.exceptions import MCPError
|
|
70
|
+
|
|
71
|
+
# Basic usage
|
|
72
|
+
error = MCPError(
|
|
73
|
+
"Connection failed",
|
|
74
|
+
context={"server": "example-server", "port": 8080},
|
|
75
|
+
error_code="CONN_FAILED"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Convert to dictionary for logging/serialization
|
|
79
|
+
error_dict = error.to_dict()
|
|
80
|
+
# {
|
|
81
|
+
# 'error_type': 'MCPError',
|
|
82
|
+
# 'message': 'Connection failed',
|
|
83
|
+
# 'error_code': 'CONN_FAILED',
|
|
84
|
+
# 'context': {'server': 'example-server', 'port': 8080},
|
|
85
|
+
# 'timestamp': '2024-01-15T10:30:00.000000+00:00'
|
|
86
|
+
# }
|
|
87
|
+
|
|
88
|
+
# Log the error
|
|
89
|
+
import logging
|
|
90
|
+
logger = logging.getLogger(__name__)
|
|
91
|
+
error.log_error(logger)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Context Sanitization
|
|
95
|
+
|
|
96
|
+
The base class automatically sanitizes sensitive information:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# Sensitive data is automatically redacted
|
|
100
|
+
error = MCPError(
|
|
101
|
+
"Authentication failed",
|
|
102
|
+
context={
|
|
103
|
+
"username": "john_doe",
|
|
104
|
+
"password": "secret123", # Will be redacted
|
|
105
|
+
"api_key": "sk-1234567890", # Will be redacted
|
|
106
|
+
"server": "api.example.com"
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
print(error.context)
|
|
111
|
+
# {
|
|
112
|
+
# 'username': 'john_doe',
|
|
113
|
+
# 'password': '[REDACTED]',
|
|
114
|
+
# 'api_key': '[REDACTED]',
|
|
115
|
+
# 'server': 'api.example.com'
|
|
116
|
+
# }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Specialized Exception Classes
|
|
120
|
+
|
|
121
|
+
### MCPConnectionError
|
|
122
|
+
|
|
123
|
+
Raised when MCP server connections fail. Includes connection-specific details for debugging and retry logic.
|
|
124
|
+
|
|
125
|
+
**Additional Constructor Parameters:**
|
|
126
|
+
- `server_name` (Optional[str]): Name of the server
|
|
127
|
+
- `transport_type` (Optional[str]): Transport type (stdio, streamable-http)
|
|
128
|
+
- `host` (Optional[str]): Server hostname
|
|
129
|
+
- `port` (Optional[int]): Server port
|
|
130
|
+
- `retry_count` (Optional[int]): Number of retry attempts
|
|
131
|
+
|
|
132
|
+
**Example:**
|
|
133
|
+
```python
|
|
134
|
+
from massgen.mcp_tools.exceptions import MCPConnectionError
|
|
135
|
+
|
|
136
|
+
# Connection failure with detailed context
|
|
137
|
+
error = MCPConnectionError(
|
|
138
|
+
"Failed to connect to MCP server",
|
|
139
|
+
server_name="file-manager",
|
|
140
|
+
transport_type="stdio",
|
|
141
|
+
host="localhost",
|
|
142
|
+
port=8080,
|
|
143
|
+
retry_count=3,
|
|
144
|
+
error_code="CONN_TIMEOUT"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Access connection-specific attributes
|
|
148
|
+
print(f"Server: {error.server_name}")
|
|
149
|
+
print(f"Transport: {error.transport_type}")
|
|
150
|
+
print(f"Retries: {error.retry_count}")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### MCPServerError
|
|
154
|
+
|
|
155
|
+
Raised when MCP servers return errors. Includes server error codes and response data.
|
|
156
|
+
|
|
157
|
+
**Additional Constructor Parameters:**
|
|
158
|
+
- `code` (Optional[Union[int, str]]): Server error code
|
|
159
|
+
- `server_name` (Optional[str]): Name of the server
|
|
160
|
+
- `http_status` (Optional[int]): HTTP status code (for HTTP transport)
|
|
161
|
+
- `response_data` (Optional[Dict[str, Any]]): Server response data
|
|
162
|
+
|
|
163
|
+
**Example:**
|
|
164
|
+
```python
|
|
165
|
+
from massgen.mcp_tools.exceptions import MCPServerError
|
|
166
|
+
|
|
167
|
+
# Server error with response details
|
|
168
|
+
error = MCPServerError(
|
|
169
|
+
"Tool execution failed",
|
|
170
|
+
code=-32603,
|
|
171
|
+
server_name="code-executor",
|
|
172
|
+
http_status=500,
|
|
173
|
+
response_data={"error": "Internal server error", "details": "..."},
|
|
174
|
+
error_code="TOOL_EXEC_FAILED"
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### MCPValidationError
|
|
179
|
+
|
|
180
|
+
Raised when configuration or input validation fails. Includes detailed validation information.
|
|
181
|
+
|
|
182
|
+
**Additional Constructor Parameters:**
|
|
183
|
+
- `field` (Optional[str]): Field that failed validation
|
|
184
|
+
- `value` (Optional[Any]): Invalid value
|
|
185
|
+
- `expected_type` (Optional[str]): Expected data type
|
|
186
|
+
- `validation_rule` (Optional[str]): Validation rule that failed
|
|
187
|
+
|
|
188
|
+
**Example:**
|
|
189
|
+
```python
|
|
190
|
+
from massgen.mcp_tools.exceptions import MCPValidationError
|
|
191
|
+
|
|
192
|
+
# Validation error with field details
|
|
193
|
+
error = MCPValidationError(
|
|
194
|
+
"Invalid tool argument type",
|
|
195
|
+
field="timeout",
|
|
196
|
+
value="not_a_number",
|
|
197
|
+
expected_type="int",
|
|
198
|
+
validation_rule="must_be_positive_integer"
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### MCPTimeoutError
|
|
203
|
+
|
|
204
|
+
Raised when MCP operations timeout. Includes timing information for retry logic.
|
|
205
|
+
|
|
206
|
+
**Additional Constructor Parameters:**
|
|
207
|
+
- `timeout_seconds` (Optional[float]): Configured timeout
|
|
208
|
+
- `operation` (Optional[str]): Operation that timed out
|
|
209
|
+
- `elapsed_seconds` (Optional[float]): Actual elapsed time
|
|
210
|
+
- `server_name` (Optional[str]): Server name
|
|
211
|
+
|
|
212
|
+
**Example:**
|
|
213
|
+
```python
|
|
214
|
+
from massgen.mcp_tools.exceptions import MCPTimeoutError
|
|
215
|
+
|
|
216
|
+
# Timeout error with timing details
|
|
217
|
+
error = MCPTimeoutError(
|
|
218
|
+
"Tool call timed out",
|
|
219
|
+
timeout_seconds=30.0,
|
|
220
|
+
operation="call_tool(file_read)",
|
|
221
|
+
elapsed_seconds=30.5,
|
|
222
|
+
server_name="file-manager"
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### MCPAuthenticationError
|
|
227
|
+
|
|
228
|
+
Raised when authentication or authorization fails. Excludes sensitive authentication data.
|
|
229
|
+
|
|
230
|
+
**Additional Constructor Parameters:**
|
|
231
|
+
- `auth_type` (Optional[str]): Authentication type
|
|
232
|
+
- `username` (Optional[str]): Username (non-sensitive)
|
|
233
|
+
- `server_name` (Optional[str]): Server name
|
|
234
|
+
- `permission_required` (Optional[str]): Required permission
|
|
235
|
+
|
|
236
|
+
**Example:**
|
|
237
|
+
```python
|
|
238
|
+
from massgen.mcp_tools.exceptions import MCPAuthenticationError
|
|
239
|
+
|
|
240
|
+
# Authentication error without exposing credentials
|
|
241
|
+
error = MCPAuthenticationError(
|
|
242
|
+
"Insufficient permissions",
|
|
243
|
+
auth_type="api_key",
|
|
244
|
+
username="service_account",
|
|
245
|
+
server_name="secure-server",
|
|
246
|
+
permission_required="file:write"
|
|
247
|
+
)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### MCPConfigurationError
|
|
251
|
+
|
|
252
|
+
Raised when MCP configuration is invalid or missing. Includes configuration context.
|
|
253
|
+
|
|
254
|
+
**Additional Constructor Parameters:**
|
|
255
|
+
- `config_file` (Optional[str]): Configuration file path
|
|
256
|
+
- `config_section` (Optional[str]): Configuration section
|
|
257
|
+
- `missing_keys` (Optional[list]): List of missing required keys
|
|
258
|
+
|
|
259
|
+
**Example:**
|
|
260
|
+
```python
|
|
261
|
+
from massgen.mcp_tools.exceptions import MCPConfigurationError
|
|
262
|
+
|
|
263
|
+
# Configuration error with file details
|
|
264
|
+
error = MCPConfigurationError(
|
|
265
|
+
"Missing required configuration keys",
|
|
266
|
+
config_file="/path/to/config.yaml",
|
|
267
|
+
config_section="mcp_servers",
|
|
268
|
+
missing_keys=["name", "command"]
|
|
269
|
+
)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### MCPResourceError
|
|
273
|
+
|
|
274
|
+
Raised when MCP resource operations fail. Includes resource and operation details.
|
|
275
|
+
|
|
276
|
+
**Additional Constructor Parameters:**
|
|
277
|
+
- `resource_type` (Optional[str]): Type of resource
|
|
278
|
+
- `resource_id` (Optional[str]): Resource identifier
|
|
279
|
+
- `operation` (Optional[str]): Failed operation
|
|
280
|
+
- `server_name` (Optional[str]): Server name
|
|
281
|
+
|
|
282
|
+
**Example:**
|
|
283
|
+
```python
|
|
284
|
+
from massgen.mcp_tools.exceptions import MCPResourceError
|
|
285
|
+
|
|
286
|
+
# Resource operation error
|
|
287
|
+
error = MCPResourceError(
|
|
288
|
+
"Failed to read resource",
|
|
289
|
+
resource_type="file",
|
|
290
|
+
resource_id="file:///path/to/file.txt",
|
|
291
|
+
operation="read",
|
|
292
|
+
server_name="file-server"
|
|
293
|
+
)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Utility Functions
|
|
297
|
+
|
|
298
|
+
### handle_mcp_error Decorator (Sync Only)
|
|
299
|
+
|
|
300
|
+
Automatically catches and logs MCP errors for synchronous functions, converting unexpected exceptions to MCPError.
|
|
301
|
+
|
|
302
|
+
**Note:** This decorator is for synchronous functions only. For async functions, use `async_handle_mcp_error` or manual error handling.
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from massgen.mcp_tools.exceptions import handle_mcp_error, MCPError
|
|
306
|
+
|
|
307
|
+
@handle_mcp_error
|
|
308
|
+
def risky_sync_operation():
|
|
309
|
+
# This function's exceptions will be automatically handled
|
|
310
|
+
raise ValueError("Something went wrong")
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
risky_sync_operation()
|
|
314
|
+
except MCPError as e:
|
|
315
|
+
# ValueError was converted to MCPError and logged
|
|
316
|
+
print(f"Caught MCP error: {e}")
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### async_handle_mcp_error Decorator
|
|
320
|
+
|
|
321
|
+
Automatically catches and logs MCP errors for asynchronous functions, properly awaiting the result before handling exceptions.
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from massgen.mcp_tools.exceptions import async_handle_mcp_error, MCPError
|
|
325
|
+
|
|
326
|
+
@async_handle_mcp_error
|
|
327
|
+
async def risky_async_operation():
|
|
328
|
+
# This async function's exceptions will be automatically handled
|
|
329
|
+
raise ValueError("Something went wrong")
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
await risky_async_operation()
|
|
333
|
+
except MCPError as e:
|
|
334
|
+
# ValueError was converted to MCPError and logged
|
|
335
|
+
print(f"Caught MCP error: {e}")
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Manual Async Error Handling
|
|
339
|
+
|
|
340
|
+
For cases where you prefer manual error handling in async functions:
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from massgen.mcp_tools.exceptions import MCPError
|
|
344
|
+
|
|
345
|
+
async def manual_async_operation():
|
|
346
|
+
try:
|
|
347
|
+
# Your async operation here
|
|
348
|
+
raise ValueError("Something went wrong")
|
|
349
|
+
except MCPError as e:
|
|
350
|
+
e.log_error()
|
|
351
|
+
raise
|
|
352
|
+
except Exception as e:
|
|
353
|
+
# Convert unexpected exceptions to MCPError
|
|
354
|
+
mcp_error = MCPError(
|
|
355
|
+
f"Unexpected error in manual_async_operation: {str(e)}",
|
|
356
|
+
context={"function": "manual_async_operation", "original_error": type(e).__name__}
|
|
357
|
+
)
|
|
358
|
+
mcp_error.log_error()
|
|
359
|
+
raise mcp_error from e
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
await manual_async_operation()
|
|
363
|
+
except MCPError as e:
|
|
364
|
+
print(f"Caught MCP error: {e}")
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### format_error_chain Function
|
|
368
|
+
|
|
369
|
+
Formats exception chains for better error reporting.
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
from massgen.mcp_tools.exceptions import format_error_chain, MCPConnectionError
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
# Nested exception scenario
|
|
376
|
+
try:
|
|
377
|
+
raise ConnectionError("Network unreachable")
|
|
378
|
+
except ConnectionError as e:
|
|
379
|
+
raise MCPConnectionError("Failed to connect to server") from e
|
|
380
|
+
except MCPConnectionError as e:
|
|
381
|
+
error_chain = format_error_chain(e)
|
|
382
|
+
print(error_chain)
|
|
383
|
+
# Output: "MCPConnectionError: Failed to connect to server -> ConnectionError: Network unreachable"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Usage Examples
|
|
387
|
+
|
|
388
|
+
**Note on Command Configuration**: The examples below use the preferred `command` + `args` pattern for MCP server configuration (e.g., `"command": "python", "args": ["-m", "server_module"]`). While the security validator also accepts a single `command` field as a string or list, the separate `command` and `args` fields provide better clarity and are recommended for consistency with other MCP tools documentation.
|
|
389
|
+
|
|
390
|
+
### Basic Exception Handling
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
import asyncio
|
|
394
|
+
from massgen.mcp_tools.client import MCPClient
|
|
395
|
+
from massgen.mcp_tools.exceptions import (
|
|
396
|
+
MCPConnectionError, MCPServerError, MCPTimeoutError, MCPError
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
async def connect_with_error_handling():
|
|
400
|
+
server_config = {
|
|
401
|
+
"name": "example-server",
|
|
402
|
+
"type": "stdio",
|
|
403
|
+
"command": "python",
|
|
404
|
+
"args": ["-m", "example_server"]
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
async with MCPClient(server_config) as client:
|
|
409
|
+
result = await client.call_tool("example_tool", {"param": "value"})
|
|
410
|
+
return result
|
|
411
|
+
|
|
412
|
+
except MCPConnectionError as e:
|
|
413
|
+
print(f"Connection failed: {e}")
|
|
414
|
+
print(f"Server: {e.server_name}, Transport: {e.transport_type}")
|
|
415
|
+
# Implement retry logic
|
|
416
|
+
|
|
417
|
+
except MCPTimeoutError as e:
|
|
418
|
+
print(f"Operation timed out: {e}")
|
|
419
|
+
print(f"Operation: {e.operation}, Timeout: {e.timeout_seconds}s")
|
|
420
|
+
# Implement timeout handling
|
|
421
|
+
|
|
422
|
+
except MCPServerError as e:
|
|
423
|
+
print(f"Server error: {e}")
|
|
424
|
+
print(f"Server code: {e.code}, HTTP status: {e.http_status}")
|
|
425
|
+
# Handle server-side errors
|
|
426
|
+
|
|
427
|
+
except MCPError as e:
|
|
428
|
+
print(f"General MCP error: {e}")
|
|
429
|
+
e.log_error() # Log with structured context
|
|
430
|
+
# Handle any other MCP errors
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Error Recovery Patterns
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
import asyncio
|
|
437
|
+
from massgen.mcp_tools.exceptions import MCPConnectionError, MCPTimeoutError
|
|
438
|
+
|
|
439
|
+
async def robust_tool_call(client, tool_name, arguments, max_retries=3):
|
|
440
|
+
"""Call tool with automatic retry on certain errors."""
|
|
441
|
+
|
|
442
|
+
for attempt in range(max_retries):
|
|
443
|
+
try:
|
|
444
|
+
return await client.call_tool(tool_name, arguments)
|
|
445
|
+
|
|
446
|
+
except MCPTimeoutError as e:
|
|
447
|
+
if attempt < max_retries - 1:
|
|
448
|
+
wait_time = 2 ** attempt # Exponential backoff
|
|
449
|
+
print(f"Timeout on attempt {attempt + 1}, retrying in {wait_time}s...")
|
|
450
|
+
await asyncio.sleep(wait_time)
|
|
451
|
+
continue
|
|
452
|
+
else:
|
|
453
|
+
print(f"Tool call failed after {max_retries} attempts")
|
|
454
|
+
raise
|
|
455
|
+
|
|
456
|
+
except MCPConnectionError as e:
|
|
457
|
+
if attempt < max_retries - 1:
|
|
458
|
+
print(f"Connection error on attempt {attempt + 1}, reconnecting...")
|
|
459
|
+
await client.reconnect()
|
|
460
|
+
continue
|
|
461
|
+
else:
|
|
462
|
+
raise
|
|
463
|
+
|
|
464
|
+
except Exception as e:
|
|
465
|
+
# Don't retry on other errors
|
|
466
|
+
print(f"Non-retryable error: {e}")
|
|
467
|
+
raise
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Structured Error Logging
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
import logging
|
|
474
|
+
import json
|
|
475
|
+
from massgen.mcp_tools.exceptions import MCPError
|
|
476
|
+
|
|
477
|
+
# Configure structured logging
|
|
478
|
+
logging.basicConfig(
|
|
479
|
+
level=logging.INFO,
|
|
480
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
class StructuredErrorHandler(logging.Handler):
|
|
484
|
+
"""Custom handler for structured MCP error logging."""
|
|
485
|
+
|
|
486
|
+
def emit(self, record):
|
|
487
|
+
if hasattr(record, 'mcp_error'):
|
|
488
|
+
error_data = record.mcp_error
|
|
489
|
+
structured_log = {
|
|
490
|
+
'timestamp': record.created,
|
|
491
|
+
'level': record.levelname,
|
|
492
|
+
'message': record.getMessage(),
|
|
493
|
+
'mcp_error': error_data
|
|
494
|
+
}
|
|
495
|
+
print(json.dumps(structured_log, indent=2))
|
|
496
|
+
|
|
497
|
+
# Set up logger with structured handler
|
|
498
|
+
logger = logging.getLogger('mcp_client')
|
|
499
|
+
logger.addHandler(StructuredErrorHandler())
|
|
500
|
+
|
|
501
|
+
# Use with MCP errors
|
|
502
|
+
try:
|
|
503
|
+
# Some MCP operation
|
|
504
|
+
pass
|
|
505
|
+
except MCPError as e:
|
|
506
|
+
e.log_error(logger) # Will use structured logging
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Error Context and Sanitization
|
|
510
|
+
|
|
511
|
+
### Automatic Sanitization
|
|
512
|
+
|
|
513
|
+
The exception system automatically removes sensitive information from error contexts:
|
|
514
|
+
|
|
515
|
+
```python
|
|
516
|
+
from massgen.mcp_tools.exceptions import MCPError
|
|
517
|
+
|
|
518
|
+
# Sensitive keys are automatically detected and redacted
|
|
519
|
+
sensitive_context = {
|
|
520
|
+
"username": "user123",
|
|
521
|
+
"password": "secret", # Redacted
|
|
522
|
+
"api_key": "sk-abc123", # Redacted
|
|
523
|
+
"auth_token": "bearer...", # Redacted
|
|
524
|
+
"secret_key": "hidden", # Redacted
|
|
525
|
+
"credential": "creds", # Redacted
|
|
526
|
+
"server_url": "https://api.example.com", # Preserved
|
|
527
|
+
"timeout": 30 # Preserved
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
error = MCPError("Operation failed", context=sensitive_context)
|
|
531
|
+
|
|
532
|
+
# Only non-sensitive data is preserved
|
|
533
|
+
print(error.context)
|
|
534
|
+
# {
|
|
535
|
+
# 'username': 'user123',
|
|
536
|
+
# 'password': '[REDACTED]',
|
|
537
|
+
# 'api_key': '[REDACTED]',
|
|
538
|
+
# 'auth_token': '[REDACTED]',
|
|
539
|
+
# 'secret_key': '[REDACTED]',
|
|
540
|
+
# 'credential': '[REDACTED]',
|
|
541
|
+
# 'server_url': 'https://api.example.com',
|
|
542
|
+
# 'timeout': 30
|
|
543
|
+
# }
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Custom Context Handling
|
|
547
|
+
|
|
548
|
+
```python
|
|
549
|
+
from massgen.mcp_tools.exceptions import MCPError
|
|
550
|
+
|
|
551
|
+
class CustomMCPError(MCPError):
|
|
552
|
+
"""Custom MCP error with additional sanitization."""
|
|
553
|
+
|
|
554
|
+
def _sanitize_context(self, context):
|
|
555
|
+
# Call parent sanitization first
|
|
556
|
+
sanitized = super()._sanitize_context(context)
|
|
557
|
+
|
|
558
|
+
# Add custom sanitization rules
|
|
559
|
+
custom_sensitive = {'internal_id', 'session_token', 'private_key'}
|
|
560
|
+
|
|
561
|
+
for key, value in sanitized.items():
|
|
562
|
+
if any(sensitive in key.lower() for sensitive in custom_sensitive):
|
|
563
|
+
sanitized[key] = "[CUSTOM_REDACTED]"
|
|
564
|
+
|
|
565
|
+
return sanitized
|
|
566
|
+
|
|
567
|
+
# Usage
|
|
568
|
+
error = CustomMCPError(
|
|
569
|
+
"Custom error",
|
|
570
|
+
context={
|
|
571
|
+
"internal_id": "12345",
|
|
572
|
+
"session_token": "sess_abc",
|
|
573
|
+
"public_data": "visible"
|
|
574
|
+
}
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
print(error.context)
|
|
578
|
+
# {
|
|
579
|
+
# 'internal_id': '[CUSTOM_REDACTED]',
|
|
580
|
+
# 'session_token': '[CUSTOM_REDACTED]',
|
|
581
|
+
# 'public_data': 'visible'
|
|
582
|
+
# }
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
## Integration Patterns
|
|
586
|
+
|
|
587
|
+
### Circuit Breaker Integration
|
|
588
|
+
|
|
589
|
+
The exception system integrates with the circuit breaker for failure tracking:
|
|
590
|
+
|
|
591
|
+
```python
|
|
592
|
+
from massgen.mcp_tools.circuit_breaker import MCPCircuitBreaker
|
|
593
|
+
from massgen.mcp_tools.exceptions import MCPConnectionError, MCPServerError
|
|
594
|
+
|
|
595
|
+
async def call_with_circuit_breaker(client, circuit_breaker, server_name, tool_name, args):
|
|
596
|
+
"""Call tool with circuit breaker protection."""
|
|
597
|
+
|
|
598
|
+
# Check if server should be skipped
|
|
599
|
+
if circuit_breaker.should_skip_server(server_name):
|
|
600
|
+
raise MCPConnectionError(
|
|
601
|
+
f"Server {server_name} is circuit broken",
|
|
602
|
+
server_name=server_name,
|
|
603
|
+
context={"circuit_breaker": "open"}
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
try:
|
|
607
|
+
result = await client.call_tool(tool_name, args)
|
|
608
|
+
# Record success
|
|
609
|
+
circuit_breaker.record_success(server_name)
|
|
610
|
+
return result
|
|
611
|
+
|
|
612
|
+
except (MCPConnectionError, MCPServerError) as e:
|
|
613
|
+
# Record failure for circuit breaker
|
|
614
|
+
circuit_breaker.record_failure(server_name)
|
|
615
|
+
|
|
616
|
+
# Add circuit breaker context to error
|
|
617
|
+
e.context = e.context or {}
|
|
618
|
+
e.context.update({
|
|
619
|
+
"circuit_breaker_failures": circuit_breaker.get_server_status(server_name)[0]
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
raise
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### Configuration Validator Integration
|
|
626
|
+
|
|
627
|
+
Exception handling with configuration validation:
|
|
628
|
+
|
|
629
|
+
```python
|
|
630
|
+
from massgen.mcp_tools.config_validator import MCPConfigValidator
|
|
631
|
+
from massgen.mcp_tools.exceptions import MCPConfigurationError, MCPValidationError
|
|
632
|
+
|
|
633
|
+
def validate_and_handle_config(config):
|
|
634
|
+
"""Validate configuration with proper error handling."""
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
validated_config = MCPConfigValidator.validate_server_config(config)
|
|
638
|
+
return validated_config
|
|
639
|
+
|
|
640
|
+
except MCPConfigurationError as e:
|
|
641
|
+
# Configuration-specific error handling
|
|
642
|
+
print(f"Configuration error: {e}")
|
|
643
|
+
|
|
644
|
+
if e.missing_keys:
|
|
645
|
+
print(f"Missing required keys: {e.missing_keys}")
|
|
646
|
+
|
|
647
|
+
if e.config_file:
|
|
648
|
+
print(f"Check configuration file: {e.config_file}")
|
|
649
|
+
|
|
650
|
+
# Log structured error
|
|
651
|
+
e.log_error()
|
|
652
|
+
raise
|
|
653
|
+
|
|
654
|
+
except MCPValidationError as e:
|
|
655
|
+
# Validation-specific error handling
|
|
656
|
+
print(f"Validation error in field '{e.field}': {e}")
|
|
657
|
+
print(f"Expected: {e.expected_type}, Got: {type(e.value).__name__}")
|
|
658
|
+
|
|
659
|
+
# Log with additional context
|
|
660
|
+
e.log_error()
|
|
661
|
+
raise
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
## Troubleshooting Guide
|
|
665
|
+
|
|
666
|
+
### Common Error Scenarios
|
|
667
|
+
|
|
668
|
+
#### Connection Failures
|
|
669
|
+
|
|
670
|
+
**Symptoms:**
|
|
671
|
+
- `MCPConnectionError` with transport-specific details
|
|
672
|
+
- Connection timeouts or refused connections
|
|
673
|
+
|
|
674
|
+
**Common Causes:**
|
|
675
|
+
1. **Stdio Transport Issues:**
|
|
676
|
+
- Incorrect command path
|
|
677
|
+
- Missing executable permissions
|
|
678
|
+
- Environment variable issues
|
|
679
|
+
|
|
680
|
+
2. **HTTP Transport Issues:**
|
|
681
|
+
- Invalid URL or port
|
|
682
|
+
- Network connectivity problems
|
|
683
|
+
- SSL/TLS certificate issues
|
|
684
|
+
|
|
685
|
+
**Resolution Steps:**
|
|
686
|
+
```python
|
|
687
|
+
# Debug stdio connection
|
|
688
|
+
try:
|
|
689
|
+
async with MCPClient(stdio_config) as client:
|
|
690
|
+
pass
|
|
691
|
+
except MCPConnectionError as e:
|
|
692
|
+
if e.transport_type == "stdio":
|
|
693
|
+
print(f"Check command: {stdio_config.get('command')}")
|
|
694
|
+
print(f"Check environment: {stdio_config.get('env', {})}")
|
|
695
|
+
|
|
696
|
+
# Test command manually
|
|
697
|
+
import subprocess
|
|
698
|
+
try:
|
|
699
|
+
result = subprocess.run(
|
|
700
|
+
stdio_config['command'],
|
|
701
|
+
capture_output=True,
|
|
702
|
+
text=True,
|
|
703
|
+
timeout=10
|
|
704
|
+
)
|
|
705
|
+
print(f"Command output: {result.stdout}")
|
|
706
|
+
print(f"Command errors: {result.stderr}")
|
|
707
|
+
except Exception as cmd_error:
|
|
708
|
+
print(f"Command execution failed: {cmd_error}")
|
|
709
|
+
|
|
710
|
+
# Debug HTTP connection
|
|
711
|
+
try:
|
|
712
|
+
async with MCPClient(http_config) as client:
|
|
713
|
+
pass
|
|
714
|
+
except MCPConnectionError as e:
|
|
715
|
+
if e.transport_type == "streamable-http":
|
|
716
|
+
print(f"Check URL: {http_config.get('url')}")
|
|
717
|
+
print(f"Check headers: {http_config.get('headers', {})}")
|
|
718
|
+
|
|
719
|
+
# Test HTTP connectivity
|
|
720
|
+
import aiohttp
|
|
721
|
+
async with aiohttp.ClientSession() as session:
|
|
722
|
+
try:
|
|
723
|
+
async with session.get(http_config['url']) as response:
|
|
724
|
+
print(f"HTTP status: {response.status}")
|
|
725
|
+
except Exception as http_error:
|
|
726
|
+
print(f"HTTP test failed: {http_error}")
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
#### Server Errors
|
|
730
|
+
|
|
731
|
+
**Symptoms:**
|
|
732
|
+
- `MCPServerError` with server error codes
|
|
733
|
+
- Tool execution failures
|
|
734
|
+
|
|
735
|
+
**Common Causes:**
|
|
736
|
+
1. Invalid tool arguments
|
|
737
|
+
2. Server-side bugs or crashes
|
|
738
|
+
3. Resource access issues
|
|
739
|
+
4. Permission problems
|
|
740
|
+
|
|
741
|
+
**Resolution Steps:**
|
|
742
|
+
```python
|
|
743
|
+
try:
|
|
744
|
+
result = await client.call_tool("problematic_tool", args)
|
|
745
|
+
except MCPServerError as e:
|
|
746
|
+
print(f"Server error code: {e.code}")
|
|
747
|
+
print(f"HTTP status: {e.http_status}")
|
|
748
|
+
print(f"Response data: {e.response_data}")
|
|
749
|
+
|
|
750
|
+
# Check for specific error patterns
|
|
751
|
+
if e.code == -32602: # Invalid params
|
|
752
|
+
print("Check tool arguments format")
|
|
753
|
+
elif e.code == -32601: # Method not found
|
|
754
|
+
print("Tool may not be available on this server")
|
|
755
|
+
elif e.http_status == 500:
|
|
756
|
+
print("Internal server error - check server logs")
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
#### Timeout Issues
|
|
760
|
+
|
|
761
|
+
**Symptoms:**
|
|
762
|
+
- `MCPTimeoutError` with timing information
|
|
763
|
+
- Operations hanging or taking too long
|
|
764
|
+
|
|
765
|
+
**Common Causes:**
|
|
766
|
+
1. Network latency
|
|
767
|
+
2. Server overload
|
|
768
|
+
3. Large data processing
|
|
769
|
+
4. Insufficient timeout values
|
|
770
|
+
|
|
771
|
+
**Resolution Steps:**
|
|
772
|
+
```python
|
|
773
|
+
try:
|
|
774
|
+
result = await client.call_tool("slow_tool", args)
|
|
775
|
+
except MCPTimeoutError as e:
|
|
776
|
+
print(f"Timeout: {e.timeout_seconds}s, Elapsed: {e.elapsed_seconds}s")
|
|
777
|
+
|
|
778
|
+
# Adjust timeout for slow operations
|
|
779
|
+
client.timeout_seconds = 60 # Increase timeout
|
|
780
|
+
|
|
781
|
+
# Or implement chunked processing
|
|
782
|
+
if "large_file" in args:
|
|
783
|
+
# Process in smaller chunks
|
|
784
|
+
pass
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
#### Configuration Issues
|
|
788
|
+
|
|
789
|
+
**Symptoms:**
|
|
790
|
+
- `MCPConfigurationError` or `MCPValidationError`
|
|
791
|
+
- Missing or invalid configuration values
|
|
792
|
+
|
|
793
|
+
**Common Causes:**
|
|
794
|
+
1. Missing required configuration keys
|
|
795
|
+
2. Invalid data types
|
|
796
|
+
3. Malformed YAML/JSON
|
|
797
|
+
4. Environment variable issues
|
|
798
|
+
|
|
799
|
+
**Resolution Steps:**
|
|
800
|
+
```python
|
|
801
|
+
try:
|
|
802
|
+
config = MCPConfigValidator.validate_server_config(raw_config)
|
|
803
|
+
except MCPConfigurationError as e:
|
|
804
|
+
print(f"Configuration error: {e}")
|
|
805
|
+
|
|
806
|
+
if e.missing_keys:
|
|
807
|
+
print(f"Add missing keys: {e.missing_keys}")
|
|
808
|
+
|
|
809
|
+
if e.config_file:
|
|
810
|
+
print(f"Check file: {e.config_file}")
|
|
811
|
+
|
|
812
|
+
# Show expected format
|
|
813
|
+
example_config = {
|
|
814
|
+
"name": "server-name",
|
|
815
|
+
"type": "stdio",
|
|
816
|
+
"command": "python",
|
|
817
|
+
"args": ["-m", "server_module"]
|
|
818
|
+
}
|
|
819
|
+
print(f"Expected format: {example_config}")
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### Error Pattern Analysis
|
|
823
|
+
|
|
824
|
+
#### Analyzing Error Chains
|
|
825
|
+
|
|
826
|
+
```python
|
|
827
|
+
from massgen.mcp_tools.exceptions import format_error_chain
|
|
828
|
+
|
|
829
|
+
def analyze_error_chain(exception):
|
|
830
|
+
"""Analyze and report on exception chains."""
|
|
831
|
+
|
|
832
|
+
chain = format_error_chain(exception)
|
|
833
|
+
print(f"Error chain: {chain}")
|
|
834
|
+
|
|
835
|
+
# Extract root cause
|
|
836
|
+
current = exception
|
|
837
|
+
while current.__cause__ or current.__context__:
|
|
838
|
+
current = current.__cause__ or current.__context__
|
|
839
|
+
|
|
840
|
+
print(f"Root cause: {type(current).__name__}: {current}")
|
|
841
|
+
|
|
842
|
+
# Check for common patterns
|
|
843
|
+
if "ConnectionRefusedError" in chain:
|
|
844
|
+
print("Suggestion: Check if server is running and port is correct")
|
|
845
|
+
elif "TimeoutError" in chain:
|
|
846
|
+
print("Suggestion: Increase timeout or check network connectivity")
|
|
847
|
+
elif "PermissionError" in chain:
|
|
848
|
+
print("Suggestion: Check file permissions or authentication")
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
#### Error Frequency Analysis
|
|
852
|
+
|
|
853
|
+
```python
|
|
854
|
+
from collections import defaultdict
|
|
855
|
+
from massgen.mcp_tools.exceptions import MCPError
|
|
856
|
+
|
|
857
|
+
class ErrorAnalyzer:
|
|
858
|
+
"""Analyze error patterns for debugging."""
|
|
859
|
+
|
|
860
|
+
def __init__(self):
|
|
861
|
+
self.error_counts = defaultdict(int)
|
|
862
|
+
self.error_contexts = []
|
|
863
|
+
|
|
864
|
+
def record_error(self, error: MCPError):
|
|
865
|
+
"""Record error for analysis."""
|
|
866
|
+
error_type = type(error).__name__
|
|
867
|
+
self.error_counts[error_type] += 1
|
|
868
|
+
self.error_contexts.append(error.to_dict())
|
|
869
|
+
|
|
870
|
+
def get_summary(self):
|
|
871
|
+
"""Get error summary."""
|
|
872
|
+
total_errors = sum(self.error_counts.values())
|
|
873
|
+
|
|
874
|
+
summary = {
|
|
875
|
+
"total_errors": total_errors,
|
|
876
|
+
"error_types": dict(self.error_counts),
|
|
877
|
+
"most_common": max(self.error_counts.items(), key=lambda x: x[1]) if self.error_counts else None
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return summary
|
|
881
|
+
|
|
882
|
+
def get_server_errors(self):
|
|
883
|
+
"""Get errors by server."""
|
|
884
|
+
server_errors = defaultdict(int)
|
|
885
|
+
|
|
886
|
+
for context in self.error_contexts:
|
|
887
|
+
server_name = context.get("context", {}).get("server_name")
|
|
888
|
+
if server_name:
|
|
889
|
+
server_errors[server_name] += 1
|
|
890
|
+
|
|
891
|
+
return dict(server_errors)
|
|
892
|
+
|
|
893
|
+
# Usage
|
|
894
|
+
analyzer = ErrorAnalyzer()
|
|
895
|
+
|
|
896
|
+
try:
|
|
897
|
+
# MCP operations
|
|
898
|
+
pass
|
|
899
|
+
except MCPError as e:
|
|
900
|
+
analyzer.record_error(e)
|
|
901
|
+
|
|
902
|
+
# Analyze patterns
|
|
903
|
+
summary = analyzer.get_summary()
|
|
904
|
+
print(f"Error summary: {summary}")
|
|
905
|
+
|
|
906
|
+
server_errors = analyzer.get_server_errors()
|
|
907
|
+
print(f"Errors by server: {server_errors}")
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
## Best Practices
|
|
911
|
+
|
|
912
|
+
### Error Handling in Async Contexts
|
|
913
|
+
|
|
914
|
+
```python
|
|
915
|
+
import asyncio
|
|
916
|
+
from massgen.mcp_tools.exceptions import MCPError, MCPTimeoutError
|
|
917
|
+
|
|
918
|
+
async def robust_async_operation():
|
|
919
|
+
"""Best practices for async error handling."""
|
|
920
|
+
|
|
921
|
+
tasks = []
|
|
922
|
+
|
|
923
|
+
try:
|
|
924
|
+
# Create multiple tasks
|
|
925
|
+
for i in range(5):
|
|
926
|
+
task = asyncio.create_task(some_mcp_operation(i))
|
|
927
|
+
tasks.append(task)
|
|
928
|
+
|
|
929
|
+
# Wait for all tasks with timeout
|
|
930
|
+
results = await asyncio.wait_for(
|
|
931
|
+
asyncio.gather(*tasks, return_exceptions=True),
|
|
932
|
+
timeout=30.0
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
# Process results and handle exceptions
|
|
936
|
+
for i, result in enumerate(results):
|
|
937
|
+
if isinstance(result, MCPError):
|
|
938
|
+
print(f"Task {i} failed: {result}")
|
|
939
|
+
result.log_error()
|
|
940
|
+
elif isinstance(result, Exception):
|
|
941
|
+
print(f"Task {i} unexpected error: {result}")
|
|
942
|
+
else:
|
|
943
|
+
print(f"Task {i} succeeded: {result}")
|
|
944
|
+
|
|
945
|
+
except asyncio.TimeoutError:
|
|
946
|
+
# Cancel remaining tasks
|
|
947
|
+
for task in tasks:
|
|
948
|
+
if not task.done():
|
|
949
|
+
task.cancel()
|
|
950
|
+
|
|
951
|
+
# Wait for cancellation
|
|
952
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
953
|
+
|
|
954
|
+
raise MCPTimeoutError(
|
|
955
|
+
"Batch operation timed out",
|
|
956
|
+
timeout_seconds=30.0,
|
|
957
|
+
operation="batch_mcp_operations"
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
except Exception as e:
|
|
961
|
+
# Handle unexpected errors
|
|
962
|
+
print(f"Unexpected error in async operation: {e}")
|
|
963
|
+
raise
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
### Logging Configuration
|
|
967
|
+
|
|
968
|
+
```python
|
|
969
|
+
import logging
|
|
970
|
+
import json
|
|
971
|
+
from datetime import datetime
|
|
972
|
+
|
|
973
|
+
# Configure structured logging for MCP errors
|
|
974
|
+
def setup_mcp_logging():
|
|
975
|
+
"""Set up logging configuration for MCP operations."""
|
|
976
|
+
|
|
977
|
+
# Create formatter for structured logs
|
|
978
|
+
class MCPFormatter(logging.Formatter):
|
|
979
|
+
def format(self, record):
|
|
980
|
+
log_entry = {
|
|
981
|
+
'timestamp': datetime.utcnow().isoformat(),
|
|
982
|
+
'level': record.levelname,
|
|
983
|
+
'logger': record.name,
|
|
984
|
+
'message': record.getMessage(),
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
# Add MCP error context if present
|
|
988
|
+
if hasattr(record, 'mcp_error'):
|
|
989
|
+
log_entry['mcp_error'] = record.mcp_error
|
|
990
|
+
|
|
991
|
+
# Add exception info if present
|
|
992
|
+
if record.exc_info:
|
|
993
|
+
log_entry['exception'] = self.formatException(record.exc_info)
|
|
994
|
+
|
|
995
|
+
return json.dumps(log_entry)
|
|
996
|
+
|
|
997
|
+
# Set up handler
|
|
998
|
+
handler = logging.StreamHandler()
|
|
999
|
+
handler.setFormatter(MCPFormatter())
|
|
1000
|
+
|
|
1001
|
+
# Configure MCP logger
|
|
1002
|
+
mcp_logger = logging.getLogger('massgen.mcp_tools')
|
|
1003
|
+
mcp_logger.setLevel(logging.INFO)
|
|
1004
|
+
mcp_logger.addHandler(handler)
|
|
1005
|
+
|
|
1006
|
+
return mcp_logger
|
|
1007
|
+
|
|
1008
|
+
# Usage
|
|
1009
|
+
logger = setup_mcp_logging()
|
|
1010
|
+
|
|
1011
|
+
try:
|
|
1012
|
+
# MCP operations
|
|
1013
|
+
pass
|
|
1014
|
+
except MCPError as e:
|
|
1015
|
+
e.log_error(logger)
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
### Error Propagation Patterns
|
|
1019
|
+
|
|
1020
|
+
```python
|
|
1021
|
+
from massgen.mcp_tools.exceptions import MCPError, MCPConnectionError
|
|
1022
|
+
|
|
1023
|
+
class MCPService:
|
|
1024
|
+
"""Service class demonstrating error propagation patterns."""
|
|
1025
|
+
|
|
1026
|
+
def __init__(self, client):
|
|
1027
|
+
self.client = client
|
|
1028
|
+
|
|
1029
|
+
async def high_level_operation(self, data):
|
|
1030
|
+
"""High-level operation that may fail."""
|
|
1031
|
+
try:
|
|
1032
|
+
return await self._process_data(data)
|
|
1033
|
+
except MCPError as e:
|
|
1034
|
+
# Add service-level context
|
|
1035
|
+
e.context = e.context or {}
|
|
1036
|
+
e.context.update({
|
|
1037
|
+
'service': 'MCPService',
|
|
1038
|
+
'operation': 'high_level_operation',
|
|
1039
|
+
'data_size': len(str(data))
|
|
1040
|
+
})
|
|
1041
|
+
|
|
1042
|
+
# Re-raise with additional context
|
|
1043
|
+
raise
|
|
1044
|
+
|
|
1045
|
+
async def _process_data(self, data):
|
|
1046
|
+
"""Internal processing that may fail."""
|
|
1047
|
+
try:
|
|
1048
|
+
return await self.client.call_tool("process", {"data": data})
|
|
1049
|
+
except MCPConnectionError as e:
|
|
1050
|
+
# Convert to service-specific error
|
|
1051
|
+
raise MCPError(
|
|
1052
|
+
f"Service unavailable: {e.original_message}",
|
|
1053
|
+
context={
|
|
1054
|
+
'service_operation': 'data_processing',
|
|
1055
|
+
'original_error': e.to_dict()
|
|
1056
|
+
},
|
|
1057
|
+
error_code="SERVICE_UNAVAILABLE"
|
|
1058
|
+
) from e
|
|
1059
|
+
|
|
1060
|
+
# Usage with proper error handling
|
|
1061
|
+
service = MCPService(client)
|
|
1062
|
+
|
|
1063
|
+
try:
|
|
1064
|
+
result = await service.high_level_operation({"key": "value"})
|
|
1065
|
+
except MCPError as e:
|
|
1066
|
+
# Handle service-level errors
|
|
1067
|
+
print(f"Service error: {e}")
|
|
1068
|
+
|
|
1069
|
+
# Check for specific error codes
|
|
1070
|
+
if e.error_code == "SERVICE_UNAVAILABLE":
|
|
1071
|
+
print("Service is temporarily unavailable")
|
|
1072
|
+
|
|
1073
|
+
# Log with full context
|
|
1074
|
+
e.log_error()
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
### Testing Error Conditions
|
|
1078
|
+
|
|
1079
|
+
```python
|
|
1080
|
+
import pytest
|
|
1081
|
+
from unittest.mock import AsyncMock, patch
|
|
1082
|
+
from massgen.mcp_tools.exceptions import MCPConnectionError, MCPTimeoutError
|
|
1083
|
+
|
|
1084
|
+
class TestMCPErrorHandling:
|
|
1085
|
+
"""Test cases for MCP error handling."""
|
|
1086
|
+
|
|
1087
|
+
@pytest.mark.asyncio
|
|
1088
|
+
async def test_connection_error_handling(self):
|
|
1089
|
+
"""Test connection error scenarios."""
|
|
1090
|
+
|
|
1091
|
+
# Mock client that raises connection error
|
|
1092
|
+
mock_client = AsyncMock()
|
|
1093
|
+
mock_client.call_tool.side_effect = MCPConnectionError(
|
|
1094
|
+
"Connection failed",
|
|
1095
|
+
server_name="test-server",
|
|
1096
|
+
transport_type="stdio"
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
# Test error handling
|
|
1100
|
+
with pytest.raises(MCPConnectionError) as exc_info:
|
|
1101
|
+
await mock_client.call_tool("test_tool", {})
|
|
1102
|
+
|
|
1103
|
+
error = exc_info.value
|
|
1104
|
+
assert error.server_name == "test-server"
|
|
1105
|
+
assert error.transport_type == "stdio"
|
|
1106
|
+
assert "Connection failed" in str(error)
|
|
1107
|
+
|
|
1108
|
+
@pytest.mark.asyncio
|
|
1109
|
+
async def test_timeout_error_handling(self):
|
|
1110
|
+
"""Test timeout error scenarios."""
|
|
1111
|
+
|
|
1112
|
+
mock_client = AsyncMock()
|
|
1113
|
+
mock_client.call_tool.side_effect = MCPTimeoutError(
|
|
1114
|
+
"Operation timed out",
|
|
1115
|
+
timeout_seconds=30.0,
|
|
1116
|
+
operation="call_tool",
|
|
1117
|
+
elapsed_seconds=30.5
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
with pytest.raises(MCPTimeoutError) as exc_info:
|
|
1121
|
+
await mock_client.call_tool("slow_tool", {})
|
|
1122
|
+
|
|
1123
|
+
error = exc_info.value
|
|
1124
|
+
assert error.timeout_seconds == 30.0
|
|
1125
|
+
assert error.elapsed_seconds == 30.5
|
|
1126
|
+
assert error.operation == "call_tool"
|
|
1127
|
+
|
|
1128
|
+
def test_error_sanitization(self):
|
|
1129
|
+
"""Test that sensitive data is properly sanitized."""
|
|
1130
|
+
|
|
1131
|
+
error = MCPConnectionError(
|
|
1132
|
+
"Auth failed",
|
|
1133
|
+
context={
|
|
1134
|
+
"username": "test_user",
|
|
1135
|
+
"password": "secret123",
|
|
1136
|
+
"api_key": "sk-abcdef",
|
|
1137
|
+
"server": "api.example.com"
|
|
1138
|
+
}
|
|
1139
|
+
)
|
|
1140
|
+
|
|
1141
|
+
# Check sanitization
|
|
1142
|
+
assert error.context["username"] == "test_user"
|
|
1143
|
+
assert error.context["password"] == "[REDACTED]"
|
|
1144
|
+
assert error.context["api_key"] == "[REDACTED]"
|
|
1145
|
+
assert error.context["server"] == "api.example.com"
|
|
1146
|
+
|
|
1147
|
+
def test_error_serialization(self):
|
|
1148
|
+
"""Test error serialization to dict."""
|
|
1149
|
+
|
|
1150
|
+
error = MCPConnectionError(
|
|
1151
|
+
"Test error",
|
|
1152
|
+
server_name="test-server",
|
|
1153
|
+
error_code="TEST_ERROR"
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
error_dict = error.to_dict()
|
|
1157
|
+
|
|
1158
|
+
assert error_dict["error_type"] == "MCPConnectionError"
|
|
1159
|
+
assert error_dict["message"] == "Test error"
|
|
1160
|
+
assert error_dict["error_code"] == "TEST_ERROR"
|
|
1161
|
+
assert "timestamp" in error_dict
|
|
1162
|
+
assert "context" in error_dict
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
This comprehensive documentation provides developers with everything they need to effectively handle errors in MCP integrations, from basic exception handling to advanced error analysis and recovery patterns.
|