mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.0.1__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 (200) hide show
  1. mcp_proxy_adapter/__main__.py +32 -0
  2. mcp_proxy_adapter/api/app.py +290 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +38 -32
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +243 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/protocol_middleware.py +201 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  13. mcp_proxy_adapter/commands/__init__.py +19 -4
  14. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  15. mcp_proxy_adapter/commands/base.py +66 -32
  16. mcp_proxy_adapter/commands/builtin_commands.py +95 -0
  17. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  18. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  19. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  20. mcp_proxy_adapter/commands/command_registry.py +711 -354
  21. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  22. mcp_proxy_adapter/commands/echo_command.py +81 -0
  23. mcp_proxy_adapter/commands/health_command.py +8 -1
  24. mcp_proxy_adapter/commands/help_command.py +21 -14
  25. mcp_proxy_adapter/commands/hooks.py +200 -167
  26. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  27. mcp_proxy_adapter/commands/load_command.py +176 -0
  28. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  29. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  30. mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
  31. mcp_proxy_adapter/commands/reload_command.py +48 -50
  32. mcp_proxy_adapter/commands/result.py +1 -0
  33. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  34. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  35. mcp_proxy_adapter/commands/security_command.py +488 -0
  36. mcp_proxy_adapter/commands/ssl_setup_command.py +366 -0
  37. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  38. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  39. mcp_proxy_adapter/commands/unload_command.py +158 -0
  40. mcp_proxy_adapter/config.py +394 -14
  41. mcp_proxy_adapter/core/app_factory.py +410 -0
  42. mcp_proxy_adapter/core/app_runner.py +272 -0
  43. mcp_proxy_adapter/core/auth_validator.py +606 -0
  44. mcp_proxy_adapter/core/certificate_utils.py +1045 -0
  45. mcp_proxy_adapter/core/client.py +574 -0
  46. mcp_proxy_adapter/core/client_manager.py +284 -0
  47. mcp_proxy_adapter/core/client_security.py +384 -0
  48. mcp_proxy_adapter/core/config_converter.py +405 -0
  49. mcp_proxy_adapter/core/config_validator.py +218 -0
  50. mcp_proxy_adapter/core/logging.py +19 -3
  51. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  52. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  53. mcp_proxy_adapter/core/protocol_manager.py +385 -0
  54. mcp_proxy_adapter/core/proxy_client.py +602 -0
  55. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  56. mcp_proxy_adapter/core/role_utils.py +426 -0
  57. mcp_proxy_adapter/core/security_adapter.py +370 -0
  58. mcp_proxy_adapter/core/security_factory.py +239 -0
  59. mcp_proxy_adapter/core/security_integration.py +286 -0
  60. mcp_proxy_adapter/core/server_adapter.py +282 -0
  61. mcp_proxy_adapter/core/server_engine.py +270 -0
  62. mcp_proxy_adapter/core/settings.py +1 -0
  63. mcp_proxy_adapter/core/ssl_utils.py +234 -0
  64. mcp_proxy_adapter/core/transport_manager.py +292 -0
  65. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  66. mcp_proxy_adapter/custom_openapi.py +22 -11
  67. mcp_proxy_adapter/examples/__init__.py +13 -4
  68. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  69. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  70. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  71. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  72. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  73. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  74. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  75. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  76. mcp_proxy_adapter/examples/demo_client.py +275 -0
  77. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  78. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  79. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  80. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  81. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  82. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  83. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  84. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  85. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  86. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  87. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  88. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  89. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  90. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  91. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  92. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  93. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  94. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  95. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  96. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  97. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  98. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  99. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  100. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  101. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  102. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  103. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  104. mcp_proxy_adapter/examples/run_example.py +59 -0
  105. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  106. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  107. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  108. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  109. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  110. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  111. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  112. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  113. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  114. mcp_proxy_adapter/examples/test_config.py +148 -0
  115. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  116. mcp_proxy_adapter/examples/test_examples.py +281 -0
  117. mcp_proxy_adapter/examples/universal_client.py +620 -0
  118. mcp_proxy_adapter/main.py +93 -0
  119. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  120. mcp_proxy_adapter/version.py +5 -2
  121. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  122. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  123. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  124. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  125. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  126. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  127. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  128. mcp_proxy_adapter/examples/README.md +0 -124
  129. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  130. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  131. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  132. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  133. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  134. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  135. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  136. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  137. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  138. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  139. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  140. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  141. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  142. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  143. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  144. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  145. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  146. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  147. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  148. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  149. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  150. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  153. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  154. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  155. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  156. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  157. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  158. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  159. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  160. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  161. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  162. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  163. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  164. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  165. mcp_proxy_adapter/tests/__init__.py +0 -0
  166. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  167. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  168. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  169. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  170. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  171. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  172. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  173. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  174. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  175. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  176. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  177. mcp_proxy_adapter/tests/conftest.py +0 -131
  178. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  180. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  181. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  182. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  183. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  184. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  185. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  186. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  187. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  188. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  189. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  190. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  191. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  192. mcp_proxy_adapter/tests/test_config.py +0 -127
  193. mcp_proxy_adapter/tests/test_utils.py +0 -65
  194. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  195. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  196. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  197. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  198. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  199. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  200. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,409 @@
1
+ """
2
+ Proxy Registration Command
3
+
4
+ This command handles proxy registration functionality with security framework integration.
5
+ It provides endpoints for registration, unregistration, heartbeat, and discovery.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import json
12
+ import time
13
+ import uuid
14
+ from typing import Dict, Any, List, Optional
15
+ from dataclasses import dataclass
16
+
17
+ from mcp_proxy_adapter.commands.base import Command
18
+ from mcp_proxy_adapter.commands.result import SuccessResult
19
+ from mcp_proxy_adapter.core.logging import logger
20
+
21
+
22
+ @dataclass
23
+ class ProxyRegistrationCommandResult(SuccessResult):
24
+ """Result of proxy registration command."""
25
+
26
+ operation: str
27
+ success: bool
28
+ server_key: Optional[str] = None
29
+ message: str = ""
30
+ details: Optional[Dict[str, Any]] = None
31
+
32
+ def to_dict(self) -> Dict[str, Any]:
33
+ """Convert result to dictionary."""
34
+ result = {
35
+ "operation": self.operation,
36
+ "success": self.success,
37
+ "message": self.message
38
+ }
39
+
40
+ if self.server_key:
41
+ result["server_key"] = self.server_key
42
+
43
+ if self.details:
44
+ result["details"] = self.details
45
+
46
+ return result
47
+
48
+ @classmethod
49
+ def get_schema(cls) -> Dict[str, Any]:
50
+ """Get JSON schema for result."""
51
+ return {
52
+ "type": "object",
53
+ "properties": {
54
+ "operation": {
55
+ "type": "string",
56
+ "description": "Operation performed"
57
+ },
58
+ "success": {
59
+ "type": "boolean",
60
+ "description": "Whether operation was successful"
61
+ },
62
+ "server_key": {
63
+ "type": "string",
64
+ "description": "Server key for registered server"
65
+ },
66
+ "message": {
67
+ "type": "string",
68
+ "description": "Result message"
69
+ },
70
+ "details": {
71
+ "type": "object",
72
+ "description": "Additional details"
73
+ }
74
+ },
75
+ "required": ["operation", "success", "message"]
76
+ }
77
+
78
+
79
+ class ProxyRegistrationCommand(Command):
80
+ """Proxy registration command with security framework integration."""
81
+
82
+ name = "proxy_registration"
83
+ descr = "Proxy registration operations (register, unregister, heartbeat, discover)"
84
+ category = "proxy"
85
+ author = "Vasiliy Zdanovskiy"
86
+ email = "vasilyvz@gmail.com"
87
+
88
+ # In-memory registry for testing
89
+ _registry: Dict[str, Dict[str, Any]] = {}
90
+ _server_counter = 1
91
+
92
+ async def execute(self, **kwargs) -> ProxyRegistrationCommandResult:
93
+ """
94
+ Execute proxy registration command.
95
+
96
+ Args:
97
+ operation: Operation to perform (register, unregister, heartbeat, discover)
98
+ server_id: Server ID for registration
99
+ server_url: Server URL for registration
100
+ server_name: Server name
101
+ description: Server description
102
+ version: Server version
103
+ capabilities: Server capabilities
104
+ endpoints: Server endpoints
105
+ auth_method: Authentication method
106
+ security_enabled: Whether security is enabled
107
+ server_key: Server key for unregistration/heartbeat
108
+ copy_number: Copy number for unregistration
109
+ timestamp: Timestamp for heartbeat
110
+ status: Status for heartbeat
111
+
112
+ Returns:
113
+ ProxyRegistrationCommandResult
114
+ """
115
+ operation = kwargs.get("operation", "register")
116
+
117
+ # Check user permissions
118
+ context = kwargs.get("context", {})
119
+ user_info = context.get("user", {})
120
+ user_permissions = user_info.get("permissions", [])
121
+
122
+ # Define required permissions for each operation
123
+ operation_permissions = {
124
+ "register": ["register"],
125
+ "unregister": ["unregister"],
126
+ "heartbeat": ["heartbeat"],
127
+ "discover": ["discover"]
128
+ }
129
+
130
+ required_permissions = operation_permissions.get(operation, ["read"])
131
+
132
+ # Check if user has required permissions
133
+ logger.info(f"Checking permissions: user_permissions={user_permissions}, required={required_permissions}")
134
+ if not self._check_permissions(user_permissions, required_permissions):
135
+ return ProxyRegistrationCommandResult(
136
+ operation=operation,
137
+ success=False,
138
+ message=f"Permission denied: {operation} requires {required_permissions}"
139
+ )
140
+
141
+ logger.info(f"Executing proxy registration operation: {operation}")
142
+ logger.debug(f"User permissions: {user_permissions}, required: {required_permissions}")
143
+
144
+ if operation == "register":
145
+ return await self._handle_register(kwargs)
146
+ elif operation == "unregister":
147
+ return await self._handle_unregister(kwargs)
148
+ elif operation == "heartbeat":
149
+ return await self._handle_heartbeat(kwargs)
150
+ elif operation == "discover":
151
+ return await self._handle_discover(kwargs)
152
+ else:
153
+ return ProxyRegistrationCommandResult(
154
+ operation=operation,
155
+ success=False,
156
+ message=f"Unknown operation: {operation}"
157
+ )
158
+
159
+ async def _handle_register(self, kwargs: Dict[str, Any]) -> ProxyRegistrationCommandResult:
160
+ """Handle registration operation."""
161
+ server_id = kwargs.get("server_id")
162
+ server_url = kwargs.get("server_url")
163
+ server_name = kwargs.get("server_name", "Unknown Server")
164
+ description = kwargs.get("description", "")
165
+ version = kwargs.get("version", "1.0.0")
166
+ capabilities = kwargs.get("capabilities", ["jsonrpc", "rest"])
167
+ endpoints = kwargs.get("endpoints", {})
168
+ auth_method = kwargs.get("auth_method", "none")
169
+ security_enabled = kwargs.get("security_enabled", False)
170
+
171
+ if not server_id or not server_url:
172
+ return ProxyRegistrationCommandResult(
173
+ operation="register",
174
+ success=False,
175
+ message="Missing required parameters: server_id and server_url"
176
+ )
177
+
178
+ # Check if server already exists
179
+ existing_servers = [key for key in self._registry.keys() if key.startswith(server_id)]
180
+ copy_number = len(existing_servers) + 1
181
+ server_key = f"{server_id}_{copy_number}"
182
+
183
+ # Create server record
184
+ server_record = {
185
+ "server_id": server_id,
186
+ "server_url": server_url,
187
+ "server_name": server_name,
188
+ "description": description,
189
+ "version": version,
190
+ "capabilities": capabilities,
191
+ "endpoints": endpoints,
192
+ "auth_method": auth_method,
193
+ "security_enabled": security_enabled,
194
+ "registered_at": int(time.time()),
195
+ "last_heartbeat": int(time.time()),
196
+ "status": "active"
197
+ }
198
+
199
+ self._registry[server_key] = server_record
200
+
201
+ logger.info(f"Registered server: {server_key} at {server_url}")
202
+
203
+ return ProxyRegistrationCommandResult(
204
+ operation="register",
205
+ success=True,
206
+ server_key=server_key,
207
+ message=f"Server registered successfully with key: {server_key}",
208
+ details={
209
+ "server_id": server_id,
210
+ "copy_number": copy_number,
211
+ "registered_at": server_record["registered_at"]
212
+ }
213
+ )
214
+
215
+ async def _handle_unregister(self, kwargs: Dict[str, Any]) -> ProxyRegistrationCommandResult:
216
+ """Handle unregistration operation."""
217
+ server_id = kwargs.get("server_id")
218
+ copy_number = kwargs.get("copy_number", 1)
219
+
220
+ if not server_id:
221
+ return ProxyRegistrationCommandResult(
222
+ operation="unregister",
223
+ success=False,
224
+ message="Missing required parameter: server_id"
225
+ )
226
+
227
+ server_key = f"{server_id}_{copy_number}"
228
+
229
+ if server_key in self._registry:
230
+ del self._registry[server_key]
231
+ logger.info(f"Unregistered server: {server_key}")
232
+
233
+ return ProxyRegistrationCommandResult(
234
+ operation="unregister",
235
+ success=True,
236
+ message=f"Server unregistered successfully: {server_key}",
237
+ details={"unregistered": True}
238
+ )
239
+ else:
240
+ return ProxyRegistrationCommandResult(
241
+ operation="unregister",
242
+ success=True,
243
+ message=f"Server not found in registry: {server_key}",
244
+ details={"unregistered": False}
245
+ )
246
+
247
+ async def _handle_heartbeat(self, kwargs: Dict[str, Any]) -> ProxyRegistrationCommandResult:
248
+ """Handle heartbeat operation."""
249
+ server_id = kwargs.get("server_id")
250
+ server_key = kwargs.get("server_key")
251
+ timestamp = kwargs.get("timestamp", int(time.time()))
252
+ status = kwargs.get("status", "healthy")
253
+
254
+ if not server_key:
255
+ return ProxyRegistrationCommandResult(
256
+ operation="heartbeat",
257
+ success=False,
258
+ message="Missing required parameter: server_key"
259
+ )
260
+
261
+ if server_key in self._registry:
262
+ self._registry[server_key]["last_heartbeat"] = timestamp
263
+ self._registry[server_key]["status"] = status
264
+
265
+ logger.debug(f"Heartbeat received for server: {server_key}")
266
+
267
+ return ProxyRegistrationCommandResult(
268
+ operation="heartbeat",
269
+ success=True,
270
+ message="Heartbeat processed successfully",
271
+ details={
272
+ "server_key": server_key,
273
+ "timestamp": timestamp,
274
+ "status": status
275
+ }
276
+ )
277
+ else:
278
+ return ProxyRegistrationCommandResult(
279
+ operation="heartbeat",
280
+ success=False,
281
+ message=f"Server not found: {server_key}"
282
+ )
283
+
284
+ async def _handle_discover(self, kwargs: Dict[str, Any]) -> ProxyRegistrationCommandResult:
285
+ """Handle discovery operation."""
286
+ # Return all registered servers
287
+ proxies = []
288
+
289
+ for server_key, server_record in self._registry.items():
290
+ # Check if server is active (heartbeat within last 5 minutes)
291
+ last_heartbeat = server_record.get("last_heartbeat", 0)
292
+ if time.time() - last_heartbeat < 300: # 5 minutes
293
+ proxy_info = {
294
+ "server_key": server_key,
295
+ "server_id": server_record["server_id"],
296
+ "server_url": server_record["server_url"],
297
+ "server_name": server_record["server_name"],
298
+ "description": server_record["description"],
299
+ "version": server_record["version"],
300
+ "capabilities": server_record["capabilities"],
301
+ "endpoints": server_record["endpoints"],
302
+ "auth_method": server_record["auth_method"],
303
+ "security_enabled": server_record["security_enabled"],
304
+ "registered_at": server_record["registered_at"],
305
+ "last_heartbeat": server_record["last_heartbeat"],
306
+ "status": server_record["status"]
307
+ }
308
+ proxies.append(proxy_info)
309
+
310
+ logger.info(f"Discovery request returned {len(proxies)} active servers")
311
+
312
+ return ProxyRegistrationCommandResult(
313
+ operation="discover",
314
+ success=True,
315
+ message=f"Found {len(proxies)} active proxy servers",
316
+ details={"proxies": proxies}
317
+ )
318
+
319
+ def _check_permissions(self, user_permissions: List[str], required_permissions: List[str]) -> bool:
320
+ """
321
+ Check if user has required permissions.
322
+
323
+ Args:
324
+ user_permissions: User's permissions
325
+ required_permissions: Required permissions
326
+
327
+ Returns:
328
+ True if user has required permissions
329
+ """
330
+ # Admin has all permissions
331
+ if "*" in user_permissions:
332
+ return True
333
+
334
+ # Check if user has all required permissions
335
+ for required in required_permissions:
336
+ if required not in user_permissions:
337
+ return False
338
+
339
+ return True
340
+
341
+ @classmethod
342
+ def get_schema(cls) -> Dict[str, Any]:
343
+ """Get JSON schema for command parameters."""
344
+ return {
345
+ "type": "object",
346
+ "properties": {
347
+ "operation": {
348
+ "type": "string",
349
+ "enum": ["register", "unregister", "heartbeat", "discover"],
350
+ "description": "Operation to perform",
351
+ "default": "register"
352
+ },
353
+ "server_id": {
354
+ "type": "string",
355
+ "description": "Server ID for registration"
356
+ },
357
+ "server_url": {
358
+ "type": "string",
359
+ "description": "Server URL for registration"
360
+ },
361
+ "server_name": {
362
+ "type": "string",
363
+ "description": "Server name"
364
+ },
365
+ "description": {
366
+ "type": "string",
367
+ "description": "Server description"
368
+ },
369
+ "version": {
370
+ "type": "string",
371
+ "description": "Server version"
372
+ },
373
+ "capabilities": {
374
+ "type": "array",
375
+ "items": {"type": "string"},
376
+ "description": "Server capabilities"
377
+ },
378
+ "endpoints": {
379
+ "type": "object",
380
+ "description": "Server endpoints"
381
+ },
382
+ "auth_method": {
383
+ "type": "string",
384
+ "description": "Authentication method"
385
+ },
386
+ "security_enabled": {
387
+ "type": "boolean",
388
+ "description": "Whether security is enabled"
389
+ },
390
+ "server_key": {
391
+ "type": "string",
392
+ "description": "Server key for unregistration/heartbeat"
393
+ },
394
+ "copy_number": {
395
+ "type": "integer",
396
+ "description": "Copy number for unregistration"
397
+ },
398
+ "timestamp": {
399
+ "type": "integer",
400
+ "description": "Timestamp for heartbeat"
401
+ },
402
+ "status": {
403
+ "type": "string",
404
+ "description": "Status for heartbeat"
405
+ }
406
+ },
407
+ "required": ["operation"],
408
+ "additionalProperties": False
409
+ }
@@ -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