mcp-proxy-adapter 4.1.0__py3-none-any.whl → 6.0.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.
Files changed (101) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +138 -11
  3. mcp_proxy_adapter/api/handlers.py +16 -1
  4. mcp_proxy_adapter/api/middleware/__init__.py +30 -29
  5. mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +219 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
  10. mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
  11. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  12. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
  13. mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
  14. mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
  15. mcp_proxy_adapter/api/middleware/security.py +376 -0
  16. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
  17. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  18. mcp_proxy_adapter/commands/__init__.py +13 -4
  19. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  20. mcp_proxy_adapter/commands/base.py +61 -30
  21. mcp_proxy_adapter/commands/builtin_commands.py +89 -0
  22. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  23. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  24. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  25. mcp_proxy_adapter/commands/command_registry.py +705 -345
  26. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  27. mcp_proxy_adapter/commands/health_command.py +7 -0
  28. mcp_proxy_adapter/commands/hooks.py +200 -167
  29. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  30. mcp_proxy_adapter/commands/load_command.py +176 -0
  31. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  32. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  33. mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
  34. mcp_proxy_adapter/commands/reload_command.py +48 -50
  35. mcp_proxy_adapter/commands/result.py +1 -0
  36. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  37. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  38. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  39. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  40. mcp_proxy_adapter/commands/unload_command.py +158 -0
  41. mcp_proxy_adapter/config.py +99 -2
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/config_converter.py +405 -0
  45. mcp_proxy_adapter/core/config_validator.py +218 -0
  46. mcp_proxy_adapter/core/logging.py +11 -0
  47. mcp_proxy_adapter/core/protocol_manager.py +226 -0
  48. mcp_proxy_adapter/core/proxy_registration.py +270 -0
  49. mcp_proxy_adapter/core/role_utils.py +426 -0
  50. mcp_proxy_adapter/core/security_adapter.py +373 -0
  51. mcp_proxy_adapter/core/security_factory.py +239 -0
  52. mcp_proxy_adapter/core/settings.py +1 -0
  53. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  54. mcp_proxy_adapter/core/transport_manager.py +292 -0
  55. mcp_proxy_adapter/custom_openapi.py +22 -11
  56. mcp_proxy_adapter/examples/basic_server/config.json +58 -23
  57. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
  58. mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
  59. mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
  60. mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
  61. mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
  62. mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
  63. mcp_proxy_adapter/examples/basic_server/server.py +17 -1
  64. mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
  65. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
  66. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
  67. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
  68. mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
  69. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
  70. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
  71. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
  72. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
  73. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
  74. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
  75. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
  76. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
  77. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
  78. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
  79. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
  80. mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
  81. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
  82. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
  83. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
  84. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
  85. mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
  86. mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
  87. mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
  88. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
  89. mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
  90. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
  91. mcp_proxy_adapter/main.py +175 -0
  92. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  93. mcp_proxy_adapter/tests/unit/test_config.py +53 -0
  94. mcp_proxy_adapter/version.py +1 -1
  95. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
  96. mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
  97. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  98. mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
  99. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
  100. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
  101. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,235 @@
1
+ """
2
+ Module with plugins command implementation.
3
+ """
4
+
5
+ from typing import Dict, Any, Optional, List
6
+
7
+ from mcp_proxy_adapter.commands.base import Command
8
+ from mcp_proxy_adapter.commands.result import CommandResult, SuccessResult
9
+ from mcp_proxy_adapter.commands.command_registry import registry
10
+ from mcp_proxy_adapter.config import config as config_instance
11
+
12
+
13
+ class PluginsResult(SuccessResult):
14
+ """
15
+ Result of the plugins command execution.
16
+ """
17
+
18
+ def __init__(self, success: bool, plugins_server: str, plugins: list, total_plugins: int, error: Optional[str] = None):
19
+ """
20
+ Initialize plugins command result.
21
+
22
+ Args:
23
+ success: Whether operation was successful
24
+ plugins_server: URL of the plugins server
25
+ plugins: List of available plugins
26
+ total_plugins: Total number of plugins
27
+ error: Error message if operation failed
28
+ """
29
+ data = {
30
+ "success": success,
31
+ "plugins_server": plugins_server,
32
+ "plugins": plugins,
33
+ "total_plugins": total_plugins
34
+ }
35
+ if error:
36
+ data["error"] = error
37
+
38
+ message = f"Found {total_plugins} plugins from {plugins_server}"
39
+ if error:
40
+ message = f"Failed to load plugins from {plugins_server}: {error}"
41
+
42
+ super().__init__(data=data, message=message)
43
+
44
+ @classmethod
45
+ def get_schema(cls) -> Dict[str, Any]:
46
+ """
47
+ Get JSON schema for result validation.
48
+
49
+ Returns:
50
+ Dict[str, Any]: JSON schema
51
+ """
52
+ return {
53
+ "type": "object",
54
+ "properties": {
55
+ "data": {
56
+ "type": "object",
57
+ "properties": {
58
+ "success": {"type": "boolean"},
59
+ "plugins_server": {"type": "string"},
60
+ "plugins": {
61
+ "type": "array",
62
+ "items": {
63
+ "type": "object",
64
+ "properties": {
65
+ "name": {"type": "string"},
66
+ "description": {"type": "string"},
67
+ "url": {"type": "string"},
68
+ "version": {"type": "string"},
69
+ "author": {"type": "string"}
70
+ }
71
+ }
72
+ },
73
+ "total_plugins": {"type": "integer"},
74
+ "error": {"type": "string"}
75
+ },
76
+ "required": ["success", "plugins_server", "plugins", "total_plugins"]
77
+ }
78
+ },
79
+ "required": ["data"]
80
+ }
81
+
82
+
83
+ class PluginsCommand(Command):
84
+ """
85
+ Command that reads and displays available plugins from a plugins server.
86
+
87
+ This command fetches a JSON file from a configured plugins server URL that contains
88
+ a list of available plugins. Each plugin in the list typically contains metadata
89
+ such as name, description, URL, version, and author information.
90
+
91
+ The plugins server URL is configured in the system configuration under
92
+ 'commands.plugins_server'. The JSON file should contain an array of plugin objects
93
+ with the following structure:
94
+
95
+ {
96
+ "plugins": [
97
+ {
98
+ "name": "plugin_name",
99
+ "description": "Plugin description",
100
+ "url": "https://server.com/plugin.py",
101
+ "version": "1.0.0",
102
+ "author": "Author Name"
103
+ }
104
+ ]
105
+ }
106
+
107
+ This command is useful for:
108
+ - Discovering available plugins without manually browsing the server
109
+ - Getting metadata about plugins before loading them
110
+ - Building plugin management interfaces
111
+ - Checking plugin availability and versions
112
+
113
+ The command will return the list of all available plugins along with their
114
+ metadata, making it easy to choose which plugins to load.
115
+ """
116
+
117
+ name = "plugins"
118
+ result_class = PluginsResult
119
+
120
+ async def execute(self, **kwargs) -> PluginsResult:
121
+ """
122
+ Execute plugins command.
123
+
124
+ Args:
125
+ **kwargs: Additional parameters
126
+
127
+ Returns:
128
+ PluginsResult: Plugins command result
129
+ """
130
+ try:
131
+ # Get configuration from the global config instance
132
+ plugins_server_url = config_instance.get("commands.plugins_server")
133
+
134
+ if not plugins_server_url:
135
+ return PluginsResult(
136
+ success=False,
137
+ plugins_server="",
138
+ plugins=[],
139
+ total_plugins=0,
140
+ error="Plugins server URL not configured"
141
+ )
142
+
143
+ # Import requests if available
144
+ try:
145
+ import requests
146
+ except ImportError:
147
+ return PluginsResult(
148
+ success=False,
149
+ plugins_server=plugins_server_url,
150
+ plugins=[],
151
+ total_plugins=0,
152
+ error="requests library not available"
153
+ )
154
+
155
+ # Fetch plugins list
156
+ response = requests.get(plugins_server_url, timeout=30)
157
+ response.raise_for_status()
158
+
159
+ # Parse JSON response
160
+ plugins_data = response.json()
161
+
162
+ # Handle different JSON formats
163
+ if isinstance(plugins_data, list):
164
+ # Direct array format
165
+ plugins_list = plugins_data
166
+ elif "plugins" in plugins_data:
167
+ # Standard plugins format
168
+ plugins_list = plugins_data.get("plugins", [])
169
+ elif "plugin" in plugins_data:
170
+ # Single plugin format (like from plugins.techsup.od.ua/)
171
+ plugins_list = [{
172
+ "name": plugins_data.get("plugin", "").replace(".py", ""),
173
+ "description": plugins_data.get("descr", ""),
174
+ "url": f"{plugins_server_url.rstrip('/')}/{plugins_data.get('plugin', '')}",
175
+ "version": "1.0.0",
176
+ "author": "Unknown",
177
+ "category": plugins_data.get("category", "")
178
+ }]
179
+ else:
180
+ # Unknown format, try to extract any plugin-like data
181
+ plugins_list = []
182
+ for key, value in plugins_data.items():
183
+ if isinstance(value, dict) and any(k in value for k in ["name", "plugin", "url"]):
184
+ plugins_list.append(value)
185
+
186
+ return PluginsResult(
187
+ success=True,
188
+ plugins_server=plugins_server_url,
189
+ plugins=plugins_list,
190
+ total_plugins=len(plugins_list)
191
+ )
192
+
193
+ except Exception as e:
194
+ return PluginsResult(
195
+ success=False,
196
+ plugins_server=plugins_server_url if 'plugins_server_url' in locals() else "",
197
+ plugins=[],
198
+ total_plugins=0,
199
+ error=str(e)
200
+ )
201
+
202
+ @classmethod
203
+ def get_metadata(cls) -> Dict[str, Any]:
204
+ """
205
+ Get command metadata.
206
+
207
+ Returns:
208
+ Dict[str, Any]: Command metadata
209
+ """
210
+ return {
211
+ "name": cls.name,
212
+ "summary": "Command that reads and displays available plugins from a plugins server",
213
+ "description": cls.__doc__,
214
+ "parameters": {},
215
+ "examples": cls._generate_examples({}),
216
+ "schema": cls.result_class.get_schema()
217
+ }
218
+
219
+ @classmethod
220
+ def _generate_examples(cls, params: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]:
221
+ """
222
+ Generate examples for the command.
223
+
224
+ Args:
225
+ params: Command parameters schema
226
+
227
+ Returns:
228
+ List[Dict[str, Any]]: List of examples
229
+ """
230
+ examples = [
231
+ {"command": cls.name, "description": "Get list of available plugins from configured server"},
232
+ {"command": cls.name, "description": "Discover plugins without parameters"},
233
+ {"command": cls.name, "description": "Check plugin availability and metadata"}
234
+ ]
235
+ return examples
@@ -0,0 +1,232 @@
1
+ """
2
+ Protocol management command module.
3
+
4
+ This module provides commands for managing and querying protocol configurations,
5
+ including HTTP, HTTPS, and MTLS protocols.
6
+ """
7
+
8
+ from typing import Dict, List, Optional, Any
9
+ from dataclasses import dataclass
10
+
11
+ from mcp_proxy_adapter.commands.base import Command
12
+ from mcp_proxy_adapter.commands.result import SuccessResult, ErrorResult
13
+ from mcp_proxy_adapter.core.protocol_manager import protocol_manager
14
+ from mcp_proxy_adapter.core.logging import logger
15
+
16
+
17
+ @dataclass
18
+ class ProtocolInfo:
19
+ """Protocol information data class."""
20
+ name: str
21
+ enabled: bool
22
+ allowed: bool
23
+ port: Optional[int]
24
+ requires_ssl: bool
25
+ ssl_context_available: bool
26
+
27
+
28
+ @dataclass
29
+ class ProtocolManagementResult:
30
+ """Result data for protocol management operations."""
31
+ protocols: Dict[str, Dict[str, Any]]
32
+ allowed_protocols: List[str]
33
+ validation_errors: List[str]
34
+ total_protocols: int
35
+ enabled_protocols: int
36
+
37
+
38
+ class ProtocolManagementCommand(Command):
39
+ """
40
+ Command for managing and querying protocol configurations.
41
+
42
+ This command provides functionality to:
43
+ - Get information about all configured protocols
44
+ - Check protocol validation status
45
+ - Get allowed protocols list
46
+ - Validate protocol configurations
47
+ """
48
+
49
+ name = "protocol_management"
50
+ descr = "Manage and query protocol configurations (HTTP, HTTPS, MTLS)"
51
+
52
+ @classmethod
53
+ def get_schema(cls) -> Dict[str, Any]:
54
+ """
55
+ Get command schema.
56
+
57
+ Returns:
58
+ Command schema dictionary
59
+ """
60
+ return {
61
+ "type": "object",
62
+ "properties": {
63
+ "action": {
64
+ "type": "string",
65
+ "enum": ["get_info", "validate_config", "get_allowed", "check_protocol"],
66
+ "description": "Action to perform"
67
+ },
68
+ "protocol": {
69
+ "type": "string",
70
+ "enum": ["http", "https", "mtls"],
71
+ "description": "Protocol to check (for check_protocol action)"
72
+ }
73
+ },
74
+ "required": ["action"]
75
+ }
76
+
77
+ async def execute(self, **kwargs) -> SuccessResult | ErrorResult:
78
+ """
79
+ Execute protocol management command.
80
+
81
+ Args:
82
+ action: Action to perform (get_info, validate_config, get_allowed, check_protocol)
83
+ protocol: Protocol name for check_protocol action
84
+
85
+ Returns:
86
+ Command execution result
87
+ """
88
+ try:
89
+ action = kwargs.get("action")
90
+
91
+ if action == "get_info":
92
+ return await self._get_protocol_info()
93
+ elif action == "validate_config":
94
+ return await self._validate_configuration()
95
+ elif action == "get_allowed":
96
+ return await self._get_allowed_protocols()
97
+ elif action == "check_protocol":
98
+ protocol = kwargs.get("protocol")
99
+ if not protocol:
100
+ return ErrorResult("Protocol parameter required for check_protocol action")
101
+ return await self._check_protocol(protocol)
102
+ else:
103
+ return ErrorResult(f"Unknown action: {action}")
104
+
105
+ except Exception as e:
106
+ logger.error(f"Protocol management command error: {e}")
107
+ return ErrorResult(f"Protocol management error: {str(e)}")
108
+
109
+ async def _get_protocol_info(self) -> SuccessResult:
110
+ """
111
+ Get information about all protocols.
112
+
113
+ Returns:
114
+ Success result with protocol information
115
+ """
116
+ try:
117
+ protocol_info = protocol_manager.get_protocol_info()
118
+ allowed_protocols = protocol_manager.get_allowed_protocols()
119
+ validation_errors = protocol_manager.validate_protocol_configuration()
120
+
121
+ enabled_count = sum(1 for info in protocol_info.values() if info["enabled"])
122
+
123
+ result_data = ProtocolManagementResult(
124
+ protocols=protocol_info,
125
+ allowed_protocols=allowed_protocols,
126
+ validation_errors=validation_errors,
127
+ total_protocols=len(protocol_info),
128
+ enabled_protocols=enabled_count
129
+ )
130
+
131
+ return SuccessResult(
132
+ data={
133
+ "protocol_info": result_data.protocols,
134
+ "allowed_protocols": result_data.allowed_protocols,
135
+ "validation_errors": result_data.validation_errors,
136
+ "total_protocols": result_data.total_protocols,
137
+ "enabled_protocols": result_data.enabled_protocols,
138
+ "protocols_enabled": protocol_manager.enabled
139
+ },
140
+ message="Protocol information retrieved successfully"
141
+ )
142
+
143
+ except Exception as e:
144
+ logger.error(f"Error getting protocol info: {e}")
145
+ return ErrorResult(f"Failed to get protocol info: {str(e)}")
146
+
147
+ async def _validate_configuration(self) -> SuccessResult:
148
+ """
149
+ Validate protocol configuration.
150
+
151
+ Returns:
152
+ Success result with validation results
153
+ """
154
+ try:
155
+ validation_errors = protocol_manager.validate_protocol_configuration()
156
+ is_valid = len(validation_errors) == 0
157
+
158
+ return SuccessResult(
159
+ data={
160
+ "is_valid": is_valid,
161
+ "validation_errors": validation_errors,
162
+ "error_count": len(validation_errors)
163
+ },
164
+ message=f"Configuration validation {'passed' if is_valid else 'failed'}"
165
+ )
166
+
167
+ except Exception as e:
168
+ logger.error(f"Error validating configuration: {e}")
169
+ return ErrorResult(f"Failed to validate configuration: {str(e)}")
170
+
171
+ async def _get_allowed_protocols(self) -> SuccessResult:
172
+ """
173
+ Get list of allowed protocols.
174
+
175
+ Returns:
176
+ Success result with allowed protocols
177
+ """
178
+ try:
179
+ allowed_protocols = protocol_manager.get_allowed_protocols()
180
+
181
+ return SuccessResult(
182
+ data={
183
+ "allowed_protocols": allowed_protocols,
184
+ "count": len(allowed_protocols)
185
+ },
186
+ message="Allowed protocols retrieved successfully"
187
+ )
188
+
189
+ except Exception as e:
190
+ logger.error(f"Error getting allowed protocols: {e}")
191
+ return ErrorResult(f"Failed to get allowed protocols: {str(e)}")
192
+
193
+ async def _check_protocol(self, protocol: str) -> SuccessResult:
194
+ """
195
+ Check specific protocol configuration.
196
+
197
+ Args:
198
+ protocol: Protocol name to check
199
+
200
+ Returns:
201
+ Success result with protocol check results
202
+ """
203
+ try:
204
+ protocol_lower = protocol.lower()
205
+
206
+ if protocol_lower not in ["http", "https", "mtls"]:
207
+ return ErrorResult(f"Unknown protocol: {protocol}")
208
+
209
+ is_allowed = protocol_manager.is_protocol_allowed(protocol_lower)
210
+ port = protocol_manager.get_protocol_port(protocol_lower)
211
+ config = protocol_manager.get_protocol_config(protocol_lower)
212
+
213
+ ssl_context_available = None
214
+ if protocol_lower in ["https", "mtls"]:
215
+ ssl_context_available = protocol_manager.get_ssl_context_for_protocol(protocol_lower) is not None
216
+
217
+ return SuccessResult(
218
+ data={
219
+ "protocol": protocol_lower,
220
+ "is_allowed": is_allowed,
221
+ "port": port,
222
+ "enabled": config.get("enabled", False),
223
+ "requires_ssl": protocol_lower in ["https", "mtls"],
224
+ "ssl_context_available": ssl_context_available,
225
+ "configuration": config
226
+ },
227
+ message=f"Protocol '{protocol}' check completed"
228
+ )
229
+
230
+ except Exception as e:
231
+ logger.error(f"Error checking protocol {protocol}: {e}")
232
+ return ErrorResult(f"Failed to check protocol {protocol}: {str(e)}")