mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.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.
Files changed (253) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +254 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +36 -30
  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 +135 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -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 +7 -0
  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 +483 -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 +159 -2
  41. mcp_proxy_adapter/core/app_factory.py +326 -0
  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/client_security.py +384 -0
  45. mcp_proxy_adapter/core/config_converter.py +405 -0
  46. mcp_proxy_adapter/core/config_validator.py +218 -0
  47. mcp_proxy_adapter/core/logging.py +19 -3
  48. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  49. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  50. mcp_proxy_adapter/core/protocol_manager.py +235 -0
  51. mcp_proxy_adapter/core/proxy_client.py +602 -0
  52. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  53. mcp_proxy_adapter/core/role_utils.py +426 -0
  54. mcp_proxy_adapter/core/security_adapter.py +370 -0
  55. mcp_proxy_adapter/core/security_factory.py +239 -0
  56. mcp_proxy_adapter/core/security_integration.py +277 -0
  57. mcp_proxy_adapter/core/server_adapter.py +345 -0
  58. mcp_proxy_adapter/core/server_engine.py +364 -0
  59. mcp_proxy_adapter/core/settings.py +1 -0
  60. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  61. mcp_proxy_adapter/core/transport_manager.py +292 -0
  62. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  63. mcp_proxy_adapter/custom_openapi.py +22 -11
  64. mcp_proxy_adapter/examples/README.md +230 -97
  65. mcp_proxy_adapter/examples/README_EN.md +258 -0
  66. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  67. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  68. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  69. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  70. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  71. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  72. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  73. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  74. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  75. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  76. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  77. mcp_proxy_adapter/examples/cert_config.json +9 -0
  78. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  79. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  80. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  81. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  82. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  83. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  84. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  85. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  86. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  87. mcp_proxy_adapter/examples/certs/client.key +52 -0
  88. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  89. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  90. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  91. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  92. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  93. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  94. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  95. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  96. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  97. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  98. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  99. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  100. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  101. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  102. mcp_proxy_adapter/examples/certs/server.key +52 -0
  103. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  104. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  105. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  106. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  107. mcp_proxy_adapter/examples/certs/user.key +52 -0
  108. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  109. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  110. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  111. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  112. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  113. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  114. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  115. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  116. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  117. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  118. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  119. mcp_proxy_adapter/examples/demo_client.py +341 -0
  120. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  121. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  122. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  123. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  124. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  125. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  126. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  127. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  128. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  129. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  130. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  131. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  132. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  133. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  134. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  135. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  136. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  137. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  138. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  139. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  140. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  141. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  142. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  143. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  144. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  145. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  146. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  147. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  148. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  149. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  150. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  151. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  152. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  153. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  154. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  155. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  156. mcp_proxy_adapter/examples/roles.json +38 -0
  157. mcp_proxy_adapter/examples/run_example.py +81 -0
  158. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  159. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  160. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  161. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  162. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  163. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  164. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  165. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  166. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  167. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  168. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  169. mcp_proxy_adapter/examples/test_examples.py +344 -0
  170. mcp_proxy_adapter/examples/universal_client.py +628 -0
  171. mcp_proxy_adapter/main.py +186 -0
  172. mcp_proxy_adapter/utils/config_generator.py +639 -0
  173. mcp_proxy_adapter/version.py +2 -1
  174. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  175. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  176. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  177. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  178. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  179. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  180. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  181. mcp_proxy_adapter/examples/__init__.py +0 -7
  182. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  183. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  184. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  185. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  186. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  187. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  188. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  189. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  190. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  191. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  192. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  193. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  194. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  195. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  196. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  197. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  198. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  199. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  200. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  201. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  202. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  203. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  204. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  206. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  207. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  208. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  209. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  210. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  211. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  212. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  213. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  214. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  215. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  216. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  217. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  218. mcp_proxy_adapter/tests/__init__.py +0 -0
  219. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  220. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  221. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  222. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  223. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  224. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  225. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  226. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  227. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  228. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  229. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  230. mcp_proxy_adapter/tests/conftest.py +0 -131
  231. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  232. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  233. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  234. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  235. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  236. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  237. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  238. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  239. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  240. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  241. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  242. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  243. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  244. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  245. mcp_proxy_adapter/tests/test_config.py +0 -127
  246. mcp_proxy_adapter/tests/test_utils.py +0 -65
  247. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  249. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  250. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  251. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  252. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  253. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.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