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,268 @@
1
+ """
2
+ Command for managing proxy registration.
3
+
4
+ This command allows manual registration, unregistration, and status checking
5
+ for the proxy registration functionality.
6
+ """
7
+
8
+ import asyncio
9
+ from typing import Dict, Any, Optional
10
+
11
+ from mcp_proxy_adapter.commands.base import Command
12
+ from mcp_proxy_adapter.commands.result import CommandResult
13
+ from mcp_proxy_adapter.core.proxy_registration import (
14
+ register_with_proxy,
15
+ unregister_from_proxy,
16
+ get_proxy_registration_status,
17
+ proxy_registration_manager
18
+ )
19
+ from mcp_proxy_adapter.core.logging import logger
20
+
21
+
22
+ class ProxyRegistrationResult(CommandResult):
23
+ """Result class for proxy registration commands."""
24
+
25
+ def __init__(self, success: bool, message: str, data: Optional[Dict[str, Any]] = None):
26
+ """
27
+ Initialize proxy registration result.
28
+
29
+ Args:
30
+ success: Whether the operation was successful.
31
+ message: Result message.
32
+ data: Additional data.
33
+ """
34
+ self.success = success
35
+ self.message = message
36
+ self.data = data
37
+
38
+ @classmethod
39
+ def get_schema(cls) -> Dict[str, Any]:
40
+ """
41
+ Get JSON schema for this result.
42
+
43
+ Returns:
44
+ JSON schema dictionary.
45
+ """
46
+ return {
47
+ "type": "object",
48
+ "properties": {
49
+ "success": {
50
+ "type": "boolean",
51
+ "description": "Whether the operation was successful"
52
+ },
53
+ "message": {
54
+ "type": "string",
55
+ "description": "Result message"
56
+ },
57
+ "data": {
58
+ "type": "object",
59
+ "description": "Additional data",
60
+ "additionalProperties": True
61
+ }
62
+ },
63
+ "required": ["success", "message"]
64
+ }
65
+
66
+ def to_dict(self) -> Dict[str, Any]:
67
+ """
68
+ Convert result to dictionary.
69
+
70
+ Returns:
71
+ Dictionary representation of the result.
72
+ """
73
+ result = {
74
+ "success": self.success,
75
+ "message": self.message
76
+ }
77
+ if self.data is not None:
78
+ result["data"] = self.data
79
+ return result
80
+
81
+
82
+ class ProxyRegistrationCommand(Command):
83
+ """
84
+ Command for managing proxy registration.
85
+
86
+ Supports registration, unregistration, and status checking.
87
+ """
88
+
89
+ name = "proxy_registration"
90
+
91
+ def __init__(self):
92
+ """Initialize the proxy registration command."""
93
+ super().__init__()
94
+
95
+ async def execute(self, **kwargs) -> ProxyRegistrationResult:
96
+ """
97
+ Execute proxy registration command.
98
+
99
+ Args:
100
+ **kwargs: Command parameters containing:
101
+ - action: "register", "unregister", or "status"
102
+ - server_url: (optional) Server URL for registration
103
+
104
+ Returns:
105
+ ProxyRegistrationResult with operation result.
106
+ """
107
+ action = kwargs.get("action", "status")
108
+
109
+ try:
110
+ if action == "register":
111
+ return await self._handle_register(kwargs)
112
+ elif action == "unregister":
113
+ return await self._handle_unregister(kwargs)
114
+ elif action == "status":
115
+ return await self._handle_status(kwargs)
116
+ else:
117
+ return ProxyRegistrationResult(
118
+ success=False,
119
+ message=f"Unknown action: {action}. Supported actions: register, unregister, status"
120
+ )
121
+
122
+ except Exception as e:
123
+ logger.error(f"Proxy registration command failed: {e}")
124
+ return ProxyRegistrationResult(
125
+ success=False,
126
+ message=f"Command execution failed: {str(e)}"
127
+ )
128
+
129
+ async def _handle_register(self, params: Dict[str, Any]) -> ProxyRegistrationResult:
130
+ """
131
+ Handle registration action.
132
+
133
+ Args:
134
+ params: Command parameters.
135
+
136
+ Returns:
137
+ ProxyRegistrationResult.
138
+ """
139
+ server_url = params.get("server_url")
140
+
141
+ if not server_url:
142
+ # Use current server configuration
143
+ from mcp_proxy_adapter.config import config
144
+
145
+ server_config = config.get("server", {})
146
+ server_host = server_config.get("host", "0.0.0.0")
147
+ server_port = server_config.get("port", 8000)
148
+
149
+ # Determine server URL based on SSL configuration
150
+ ssl_config = config.get("ssl", {})
151
+ if ssl_config.get("enabled", False):
152
+ protocol = "https"
153
+ else:
154
+ protocol = "http"
155
+
156
+ # Use localhost for external access if host is 0.0.0.0
157
+ if server_host == "0.0.0.0":
158
+ server_host = "localhost"
159
+
160
+ server_url = f"{protocol}://{server_host}:{server_port}"
161
+
162
+ logger.info(f"Attempting to register with proxy using URL: {server_url}")
163
+
164
+ success = await register_with_proxy(server_url)
165
+
166
+ if success:
167
+ status = get_proxy_registration_status()
168
+ return ProxyRegistrationResult(
169
+ success=True,
170
+ message="✅ Successfully registered with proxy",
171
+ data={
172
+ "server_url": server_url,
173
+ "server_key": status.get("server_key"),
174
+ "registration_status": status
175
+ }
176
+ )
177
+ else:
178
+ return ProxyRegistrationResult(
179
+ success=False,
180
+ message="❌ Failed to register with proxy"
181
+ )
182
+
183
+ async def _handle_unregister(self, params: Dict[str, Any]) -> ProxyRegistrationResult:
184
+ """
185
+ Handle unregistration action.
186
+
187
+ Args:
188
+ params: Command parameters.
189
+
190
+ Returns:
191
+ ProxyRegistrationResult.
192
+ """
193
+ logger.info("Attempting to unregister from proxy")
194
+
195
+ success = await unregister_from_proxy()
196
+
197
+ if success:
198
+ return ProxyRegistrationResult(
199
+ success=True,
200
+ message="✅ Successfully unregistered from proxy"
201
+ )
202
+ else:
203
+ return ProxyRegistrationResult(
204
+ success=False,
205
+ message="❌ Failed to unregister from proxy"
206
+ )
207
+
208
+ async def _handle_status(self, params: Dict[str, Any]) -> ProxyRegistrationResult:
209
+ """
210
+ Handle status action.
211
+
212
+ Args:
213
+ params: Command parameters.
214
+
215
+ Returns:
216
+ ProxyRegistrationResult.
217
+ """
218
+ status = get_proxy_registration_status()
219
+
220
+ return ProxyRegistrationResult(
221
+ success=True,
222
+ message="Proxy registration status retrieved successfully",
223
+ data={
224
+ "registration_status": status,
225
+ "enabled": status.get("enabled", False),
226
+ "registered": status.get("registered", False),
227
+ "server_key": status.get("server_key"),
228
+ "server_url": status.get("server_url"),
229
+ "proxy_url": status.get("proxy_url")
230
+ }
231
+ )
232
+
233
+ @classmethod
234
+ def get_schema(cls) -> Dict[str, Any]:
235
+ """
236
+ Get command schema.
237
+
238
+ Returns:
239
+ Command schema.
240
+ """
241
+ return {
242
+ "type": "object",
243
+ "properties": {
244
+ "action": {
245
+ "type": "string",
246
+ "enum": ["register", "unregister", "status"],
247
+ "description": "Action to perform: register, unregister, or status"
248
+ },
249
+ "server_url": {
250
+ "type": "string",
251
+ "description": "Server URL for registration (optional, uses current config if not provided)"
252
+ }
253
+ },
254
+ "required": ["action"]
255
+ }
256
+
257
+ def to_dict(self) -> Dict[str, Any]:
258
+ """
259
+ Convert command to dictionary.
260
+
261
+ Returns:
262
+ Command dictionary representation.
263
+ """
264
+ return {
265
+ "name": self.name,
266
+ "description": "Manage proxy registration (register, unregister, status)",
267
+ "schema": self.__class__.get_schema()
268
+ }
@@ -20,11 +20,11 @@ class ReloadResult:
20
20
  def __init__(
21
21
  self,
22
22
  config_reloaded: bool,
23
- commands_discovered: int,
24
- custom_commands_preserved: int,
25
- total_commands: int,
26
- built_in_commands: int,
23
+ builtin_commands: int,
27
24
  custom_commands: int,
25
+ loaded_commands: int,
26
+ remote_commands: int = 0,
27
+ total_commands: int = 0,
28
28
  server_restart_required: bool = True,
29
29
  success: bool = True,
30
30
  error_message: Optional[str] = None
@@ -34,21 +34,20 @@ class ReloadResult:
34
34
 
35
35
  Args:
36
36
  config_reloaded: Whether configuration was reloaded successfully
37
- commands_discovered: Number of commands discovered
38
- custom_commands_preserved: Number of custom commands preserved
37
+ builtin_commands: Number of built-in commands registered
38
+ custom_commands: Number of custom commands registered
39
+ loaded_commands: Number of commands loaded from directory
39
40
  total_commands: Total number of commands after reload
40
- built_in_commands: Number of built-in commands
41
- custom_commands: Number of custom commands
42
41
  server_restart_required: Whether server restart is required
43
42
  success: Whether reload was successful
44
43
  error_message: Error message if reload failed
45
44
  """
46
45
  self.config_reloaded = config_reloaded
47
- self.commands_discovered = commands_discovered
48
- self.custom_commands_preserved = custom_commands_preserved
49
- self.total_commands = total_commands
50
- self.built_in_commands = built_in_commands
46
+ self.builtin_commands = builtin_commands
51
47
  self.custom_commands = custom_commands
48
+ self.loaded_commands = loaded_commands
49
+ self.remote_commands = remote_commands
50
+ self.total_commands = total_commands
52
51
  self.server_restart_required = server_restart_required
53
52
  self.success = success
54
53
  self.error_message = error_message
@@ -63,11 +62,11 @@ class ReloadResult:
63
62
  return {
64
63
  "success": self.success,
65
64
  "config_reloaded": self.config_reloaded,
66
- "commands_discovered": self.commands_discovered,
67
- "custom_commands_preserved": self.custom_commands_preserved,
68
- "total_commands": self.total_commands,
69
- "built_in_commands": self.built_in_commands,
65
+ "builtin_commands": self.builtin_commands,
70
66
  "custom_commands": self.custom_commands,
67
+ "loaded_commands": self.loaded_commands,
68
+ "remote_commands": self.remote_commands,
69
+ "total_commands": self.total_commands,
71
70
  "server_restart_required": self.server_restart_required,
72
71
  "message": "Server restart required to apply configuration changes",
73
72
  "error_message": self.error_message
@@ -91,25 +90,25 @@ class ReloadResult:
91
90
  "type": "boolean",
92
91
  "description": "Whether configuration was reloaded successfully"
93
92
  },
94
- "commands_discovered": {
93
+ "builtin_commands": {
95
94
  "type": "integer",
96
- "description": "Number of commands discovered"
95
+ "description": "Number of built-in commands registered"
97
96
  },
98
- "custom_commands_preserved": {
97
+ "custom_commands": {
99
98
  "type": "integer",
100
- "description": "Number of custom commands preserved"
99
+ "description": "Number of custom commands registered"
101
100
  },
102
- "total_commands": {
101
+ "loaded_commands": {
103
102
  "type": "integer",
104
- "description": "Total number of commands after reload"
103
+ "description": "Number of commands loaded from directory"
105
104
  },
106
- "built_in_commands": {
105
+ "remote_commands": {
107
106
  "type": "integer",
108
- "description": "Number of built-in commands"
107
+ "description": "Number of commands loaded from remote plugins"
109
108
  },
110
- "custom_commands": {
109
+ "total_commands": {
111
110
  "type": "integer",
112
- "description": "Number of custom commands"
111
+ "description": "Total number of commands after reload"
113
112
  },
114
113
  "server_restart_required": {
115
114
  "type": "boolean",
@@ -125,9 +124,8 @@ class ReloadResult:
125
124
  }
126
125
  },
127
126
  "required": [
128
- "success", "config_reloaded", "commands_discovered",
129
- "custom_commands_preserved", "total_commands",
130
- "built_in_commands", "custom_commands", "server_restart_required"
127
+ "success", "config_reloaded", "builtin_commands", "custom_commands",
128
+ "loaded_commands", "remote_commands", "total_commands", "server_restart_required"
131
129
  ]
132
130
  }
133
131
 
@@ -135,7 +133,7 @@ class ReloadResult:
135
133
  class ReloadCommand(Command):
136
134
  """
137
135
  Command for reloading configuration and rediscovering commands.
138
- Note: This command will trigger a server restart to apply configuration changes.
136
+ Uses the unified initialization logic.
139
137
  """
140
138
 
141
139
  name = "reload"
@@ -145,7 +143,7 @@ class ReloadCommand(Command):
145
143
  Execute reload command.
146
144
 
147
145
  Args:
148
- **params: Command parameters (currently unused)
146
+ **params: Command parameters (config_path)
149
147
 
150
148
  Returns:
151
149
  ReloadResult with reload information
@@ -153,18 +151,23 @@ class ReloadCommand(Command):
153
151
  try:
154
152
  logger.info("🔄 Starting configuration and commands reload...")
155
153
 
156
- # Perform reload
157
- reload_info = registry.reload_config_and_commands()
154
+ # Get config path from parameters
155
+ config_path = params.get("config_path")
156
+ if not config_path:
157
+ logger.warning("No config_path provided, using default configuration")
158
+
159
+ # Perform reload using unified initialization
160
+ reload_info = registry.reload_system(config_path=config_path)
158
161
 
159
162
  # Create result
160
163
  result = ReloadResult(
161
164
  config_reloaded=reload_info.get("config_reloaded", False),
162
- commands_discovered=reload_info.get("commands_discovered", 0),
163
- custom_commands_preserved=reload_info.get("custom_commands_preserved", 0),
164
- total_commands=reload_info.get("total_commands", 0),
165
- built_in_commands=reload_info.get("built_in_commands", 0),
165
+ builtin_commands=reload_info.get("builtin_commands", 0),
166
166
  custom_commands=reload_info.get("custom_commands", 0),
167
- server_restart_required=True,
167
+ loaded_commands=reload_info.get("loaded_commands", 0),
168
+ remote_commands=reload_info.get("remote_commands", 0),
169
+ total_commands=reload_info.get("total_commands", 0),
170
+ server_restart_required=True, # Default to True as per tests
168
171
  success=True
169
172
  )
170
173
 
@@ -175,11 +178,11 @@ class ReloadCommand(Command):
175
178
  logger.error(f"❌ Reload failed: {str(e)}")
176
179
  return ReloadResult(
177
180
  config_reloaded=False,
178
- commands_discovered=0,
179
- custom_commands_preserved=0,
180
- total_commands=0,
181
- built_in_commands=0,
181
+ builtin_commands=0,
182
182
  custom_commands=0,
183
+ loaded_commands=0,
184
+ remote_commands=0,
185
+ total_commands=0,
183
186
  server_restart_required=False,
184
187
  success=False,
185
188
  error_message=str(e)
@@ -196,15 +199,10 @@ class ReloadCommand(Command):
196
199
  return {
197
200
  "type": "object",
198
201
  "properties": {
199
- "package_path": {
202
+ "config_path": {
200
203
  "type": "string",
201
- "description": "Path to package with commands to discover",
202
- "default": "mcp_proxy_adapter.commands"
203
- },
204
- "force_restart": {
205
- "type": "boolean",
206
- "description": "Force server restart to apply configuration changes",
207
- "default": True
204
+ "description": "Path to configuration file to reload",
205
+ "default": None
208
206
  }
209
207
  },
210
208
  "additionalProperties": False
@@ -150,6 +150,7 @@ class ErrorResult(CommandResult):
150
150
  details: Additional error details.
151
151
  """
152
152
  self.message = message
153
+ self.error = message # For backward compatibility with tests
153
154
  self.code = code
154
155
  self.details = details or {}
155
156