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