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
@@ -36,6 +36,7 @@ from mcp_proxy_adapter.core.logging import logger
36
36
 
37
37
  try:
38
38
  import requests
39
+
39
40
  REQUESTS_AVAILABLE = True
40
41
  except ImportError:
41
42
  REQUESTS_AVAILABLE = False
@@ -48,16 +49,22 @@ class CommandRegistry:
48
49
  """
49
50
  Registry for registering and finding commands.
50
51
  """
51
-
52
+
52
53
  def __init__(self):
53
54
  """
54
55
  Initialize command registry.
55
56
  """
56
- self._builtin_commands: Dict[str, Type[Command]] = {} # Built-in framework commands
57
- self._custom_commands: Dict[str, Type[Command]] = {} # Custom commands (highest priority)
58
- self._loaded_commands: Dict[str, Type[Command]] = {} # Commands loaded from directory
57
+ self._builtin_commands: Dict[str, Type[Command]] = (
58
+ {}
59
+ ) # Built-in framework commands
60
+ self._custom_commands: Dict[str, Type[Command]] = (
61
+ {}
62
+ ) # Custom commands (highest priority)
63
+ self._loaded_commands: Dict[str, Type[Command]] = (
64
+ {}
65
+ ) # Commands loaded from directory
59
66
  self._instances: Dict[str, Command] = {} # Command instances
60
-
67
+
61
68
  def register_builtin(self, command: Union[Type[Command], Command]) -> None:
62
69
  """
63
70
  Register a built-in framework command.
@@ -69,20 +76,22 @@ class CommandRegistry:
69
76
  ValueError: If command with the same name is already registered.
70
77
  """
71
78
  command_name = self._get_command_name(command)
72
-
79
+
73
80
  # Check for conflicts with other built-in commands
74
81
  if command_name in self._builtin_commands:
75
- logger.error(f"Built-in command '{command_name}' is already registered, skipping")
82
+ logger.error(
83
+ f"Built-in command '{command_name}' is already registered, skipping"
84
+ )
76
85
  raise ValueError(f"Built-in command '{command_name}' is already registered")
77
-
86
+
78
87
  # Built-in commands can override loaded commands
79
88
  # Remove any existing loaded commands with the same name
80
89
  if command_name in self._loaded_commands:
81
90
  logger.info(f"Built-in command '{command_name}' overrides loaded command")
82
91
  del self._loaded_commands[command_name]
83
-
92
+
84
93
  self._register_command(command, self._builtin_commands, "built-in")
85
-
94
+
86
95
  def register_custom(self, command: Union[Type[Command], Command]) -> None:
87
96
  """
88
97
  Register a custom command with highest priority.
@@ -94,24 +103,26 @@ class CommandRegistry:
94
103
  ValueError: If command with the same name is already registered.
95
104
  """
96
105
  command_name = self._get_command_name(command)
97
-
106
+
98
107
  # Check for conflicts with other custom commands
99
108
  if command_name in self._custom_commands:
100
- logger.error(f"Custom command '{command_name}' is already registered, skipping")
109
+ logger.error(
110
+ f"Custom command '{command_name}' is already registered, skipping"
111
+ )
101
112
  raise ValueError(f"Custom command '{command_name}' is already registered")
102
-
113
+
103
114
  # Custom commands can override built-in and loaded commands
104
115
  # Remove any existing commands with the same name from other types
105
116
  if command_name in self._builtin_commands:
106
117
  logger.info(f"Custom command '{command_name}' overrides built-in command")
107
118
  del self._builtin_commands[command_name]
108
-
119
+
109
120
  if command_name in self._loaded_commands:
110
121
  logger.info(f"Custom command '{command_name}' overrides loaded command")
111
122
  del self._loaded_commands[command_name]
112
-
123
+
113
124
  self._register_command(command, self._custom_commands, "custom")
114
-
125
+
115
126
  def register_loaded(self, command: Union[Type[Command], Command]) -> None:
116
127
  """
117
128
  Register a command loaded from directory.
@@ -123,30 +134,39 @@ class CommandRegistry:
123
134
  bool: True if registered, False if skipped due to conflict.
124
135
  """
125
136
  command_name = self._get_command_name(command)
126
-
137
+
127
138
  # Check for conflicts with custom and built-in commands
128
139
  if command_name in self._custom_commands:
129
- logger.warning(f"Loaded command '{command_name}' conflicts with custom command, skipping")
140
+ logger.warning(
141
+ f"Loaded command '{command_name}' conflicts with custom command, skipping"
142
+ )
130
143
  return False
131
-
144
+
132
145
  if command_name in self._builtin_commands:
133
- logger.warning(f"Loaded command '{command_name}' conflicts with built-in command, skipping")
146
+ logger.warning(
147
+ f"Loaded command '{command_name}' conflicts with built-in command, skipping"
148
+ )
134
149
  return False
135
-
150
+
136
151
  # Check for conflicts within loaded commands
137
152
  if command_name in self._loaded_commands:
138
- logger.warning(f"Loaded command '{command_name}' already exists, skipping duplicate")
153
+ logger.warning(
154
+ f"Loaded command '{command_name}' already exists, skipping duplicate"
155
+ )
139
156
  return False
140
-
157
+
141
158
  try:
142
159
  self._register_command(command, self._loaded_commands, "loaded")
143
160
  return True
144
161
  except ValueError:
145
162
  return False
146
-
147
- def _register_command(self, command: Union[Type[Command], Command],
148
- target_dict: Dict[str, Type[Command]],
149
- command_type: str) -> None:
163
+
164
+ def _register_command(
165
+ self,
166
+ command: Union[Type[Command], Command],
167
+ target_dict: Dict[str, Type[Command]],
168
+ command_type: str,
169
+ ) -> None:
150
170
  """
151
171
  Internal method to register a command in the specified dictionary.
152
172
 
@@ -166,21 +186,25 @@ class CommandRegistry:
166
186
  command_class = command.__class__
167
187
  command_instance = command
168
188
  else:
169
- raise ValueError(f"Invalid command type: {type(command)}. Expected Command class or instance.")
170
-
189
+ raise ValueError(
190
+ f"Invalid command type: {type(command)}. Expected Command class or instance."
191
+ )
192
+
171
193
  command_name = self._get_command_name(command_class)
172
-
194
+
173
195
  if command_name in target_dict:
174
- raise ValueError(f"{command_type.capitalize()} command '{command_name}' is already registered")
175
-
196
+ raise ValueError(
197
+ f"{command_type.capitalize()} command '{command_name}' is already registered"
198
+ )
199
+
176
200
  logger.debug(f"Registering {command_type} command: {command_name}")
177
201
  target_dict[command_name] = command_class
178
-
202
+
179
203
  # Store instance if provided
180
204
  if command_instance:
181
205
  logger.debug(f"Storing {command_type} instance for command: {command_name}")
182
206
  self._instances[command_name] = command_instance
183
-
207
+
184
208
  def _get_command_name(self, command_class: Type[Command]) -> str:
185
209
  """
186
210
  Get command name from command class.
@@ -198,90 +222,91 @@ class CommandRegistry:
198
222
  command_name = command_name[:-7] # Remove "command" suffix
199
223
  else:
200
224
  command_name = command_class.name
201
-
202
- return command_name
203
-
204
225
 
205
-
226
+ return command_name
206
227
 
207
-
208
228
  def load_command_from_source(self, source: str) -> Dict[str, Any]:
209
229
  """
210
230
  Universal command loader - handles local files, URLs, and remote registry.
211
-
231
+
212
232
  Args:
213
233
  source: Source string - local path, URL, or command name from registry
214
-
234
+
215
235
  Returns:
216
236
  Dictionary with loading result information
217
237
  """
218
238
  logger.info(f"Loading command from source: {source}")
219
-
239
+
220
240
  # Parse source to determine type
221
241
  parsed_url = urllib.parse.urlparse(source)
222
- is_url = parsed_url.scheme in ('http', 'https')
223
-
242
+ is_url = parsed_url.scheme in ("http", "https")
243
+
224
244
  if is_url:
225
245
  # URL - always download and load
226
246
  return self._load_command_from_url(source)
227
247
  else:
228
248
  # Local path or command name - check remote registry first
229
249
  return self._load_command_with_registry_check(source)
230
-
250
+
231
251
  def _load_command_with_registry_check(self, source: str) -> Dict[str, Any]:
232
252
  """
233
253
  Load command with remote registry check.
234
-
254
+
235
255
  Args:
236
256
  source: Local path or command name
237
-
257
+
238
258
  Returns:
239
259
  Dictionary with loading result information
240
260
  """
241
261
  try:
242
262
  from mcp_proxy_adapter.commands.catalog_manager import CatalogManager
243
-
263
+
244
264
  # Get remote registry
245
265
  plugin_servers = config.get("commands.plugin_servers", [])
246
266
  catalog_dir = "./catalog"
247
-
267
+
248
268
  if plugin_servers:
249
269
  # Initialize catalog manager
250
270
  catalog_manager = CatalogManager(catalog_dir)
251
-
271
+
252
272
  # Check if source is a command name in registry
253
- if not os.path.exists(source) and not source.endswith('_command.py'):
273
+ if not os.path.exists(source) and not source.endswith("_command.py"):
254
274
  # Try to find in remote registry
255
275
  for server_url in plugin_servers:
256
276
  try:
257
- server_catalog = catalog_manager.get_catalog_from_server(server_url)
277
+ server_catalog = catalog_manager.get_catalog_from_server(
278
+ server_url
279
+ )
258
280
  if source in server_catalog:
259
281
  server_cmd = server_catalog[source]
260
282
  # Download from registry
261
- if catalog_manager._download_command(source, server_cmd):
262
- source = str(catalog_manager.commands_dir / f"{source}_command.py")
283
+ if catalog_manager._download_command(
284
+ source, server_cmd
285
+ ):
286
+ source = str(
287
+ catalog_manager.commands_dir
288
+ / f"{source}_command.py"
289
+ )
263
290
  break
264
291
  except Exception as e:
265
- logger.warning(f"Failed to check registry {server_url}: {e}")
266
-
292
+ logger.warning(
293
+ f"Failed to check registry {server_url}: {e}"
294
+ )
295
+
267
296
  # Load from local file
268
297
  return self._load_command_from_file(source)
269
-
298
+
270
299
  except Exception as e:
271
300
  logger.error(f"Failed to load command with registry check: {e}")
272
- return {
273
- "success": False,
274
- "commands_loaded": 0,
275
- "error": str(e)
276
- }
277
-
301
+ return {"success": False, "commands_loaded": 0, "error": str(e)}
302
+
278
303
  def _load_command_from_url(self, url: str) -> Dict[str, Any]:
279
304
  """
280
305
  Load command from HTTP/HTTPS URL.
281
-
306
+
282
307
  Args:
283
308
  url: URL to load command from
284
-
309
+
285
310
  Returns:
286
311
  Dictionary with loading result information
287
312
  """
@@ -292,24 +317,26 @@ class CommandRegistry:
292
317
  "success": False,
293
318
  "error": error_msg,
294
319
  "commands_loaded": 0,
295
- "source": url
320
+ "source": url,
296
321
  }
297
-
322
+
298
323
  try:
299
324
  logger.debug(f"Downloading command from URL: {url}")
300
325
  response = requests.get(url, timeout=30)
301
326
  response.raise_for_status()
302
-
327
+
303
328
  # Get filename from URL or use default
304
329
  filename = os.path.basename(urllib.parse.urlparse(url).path)
305
- if not filename or not filename.endswith('.py'):
306
- filename = 'remote_command.py'
307
-
330
+ if not filename or not filename.endswith(".py"):
331
+ filename = "remote_command.py"
332
+
308
333
  # Create temporary file
309
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as temp_file:
334
+ with tempfile.NamedTemporaryFile(
335
+ mode="w", suffix=".py", delete=False
336
+ ) as temp_file:
310
337
  temp_file.write(response.text)
311
338
  temp_file_path = temp_file.name
312
-
339
+
313
340
  try:
314
341
  # Load command from temporary file
315
342
  result = self._load_command_from_file(temp_file_path, is_temporary=True)
@@ -320,8 +347,10 @@ class CommandRegistry:
320
347
  try:
321
348
  os.unlink(temp_file_path)
322
349
  except Exception as e:
323
- logger.warning(f"Failed to clean up temporary file {temp_file_path}: {e}")
324
-
350
+ logger.warning(
351
+ f"Failed to clean up temporary file {temp_file_path}: {e}"
352
+ )
353
+
325
354
  except Exception as e:
326
355
  error_msg = f"Failed to load command from URL {url}: {e}"
327
356
  logger.error(error_msg)
@@ -329,17 +358,19 @@ class CommandRegistry:
329
358
  "success": False,
330
359
  "error": error_msg,
331
360
  "commands_loaded": 0,
332
- "source": url
361
+ "source": url,
333
362
  }
334
-
335
- def _load_command_from_file(self, file_path: str, is_temporary: bool = False) -> Dict[str, Any]:
363
+
364
+ def _load_command_from_file(
365
+ self, file_path: str, is_temporary: bool = False
366
+ ) -> Dict[str, Any]:
336
367
  """
337
368
  Load command from local file.
338
-
369
+
339
370
  Args:
340
371
  file_path: Path to command file
341
372
  is_temporary: Whether this is a temporary file (for cleanup)
342
-
373
+
343
374
  Returns:
344
375
  Dictionary with loading result information
345
376
  """
@@ -350,41 +381,43 @@ class CommandRegistry:
350
381
  "success": False,
351
382
  "error": error_msg,
352
383
  "commands_loaded": 0,
353
- "source": file_path
384
+ "source": file_path,
354
385
  }
355
-
386
+
356
387
  # For temporary files (downloaded from URL), we don't enforce the _command.py naming
357
388
  # since the original filename is preserved in the URL
358
- if not is_temporary and not file_path.endswith('_command.py'):
389
+ if not is_temporary and not file_path.endswith("_command.py"):
359
390
  error_msg = f"Command file must end with '_command.py': {file_path}"
360
391
  logger.error(error_msg)
361
392
  return {
362
393
  "success": False,
363
394
  "error": error_msg,
364
395
  "commands_loaded": 0,
365
- "source": file_path
396
+ "source": file_path,
366
397
  }
367
-
398
+
368
399
  try:
369
400
  module_name = os.path.basename(file_path)[:-3] # Remove .py extension
370
401
  logger.debug(f"Loading command from file: {file_path}")
371
-
402
+
372
403
  # Load module from file
373
404
  spec = importlib.util.spec_from_file_location(module_name, file_path)
374
405
  if spec and spec.loader:
375
406
  module = importlib.util.module_from_spec(spec)
376
407
  spec.loader.exec_module(module)
377
-
408
+
378
409
  commands_loaded = 0
379
410
  loaded_commands = []
380
-
411
+
381
412
  # Find command classes in the module
382
413
  for name, obj in inspect.getmembers(module):
383
- if (inspect.isclass(obj) and
384
- issubclass(obj, Command) and
385
- obj != Command and
386
- not inspect.isabstract(obj)):
387
-
414
+ if (
415
+ inspect.isclass(obj)
416
+ and issubclass(obj, Command)
417
+ and obj != Command
418
+ and not inspect.isabstract(obj)
419
+ ):
420
+
388
421
  command_name = self._get_command_name(obj)
389
422
  if self.register_loaded(cast(Type[Command], obj)):
390
423
  commands_loaded += 1
@@ -392,12 +425,12 @@ class CommandRegistry:
392
425
  logger.debug(f"Loaded command: {command_name}")
393
426
  else:
394
427
  logger.debug(f"Skipped command: {command_name}")
395
-
428
+
396
429
  return {
397
430
  "success": True,
398
431
  "commands_loaded": commands_loaded,
399
432
  "loaded_commands": loaded_commands,
400
- "source": file_path
433
+ "source": file_path,
401
434
  }
402
435
  else:
403
436
  error_msg = f"Failed to create module spec for: {file_path}"
@@ -406,9 +439,9 @@ class CommandRegistry:
406
439
  "success": False,
407
440
  "error": error_msg,
408
441
  "commands_loaded": 0,
409
- "source": file_path
442
+ "source": file_path,
410
443
  }
411
-
444
+
412
445
  except Exception as e:
413
446
  error_msg = f"Error loading command from file {file_path}: {e}"
414
447
  logger.error(error_msg)
@@ -416,55 +449,49 @@ class CommandRegistry:
416
449
  "success": False,
417
450
  "error": error_msg,
418
451
  "commands_loaded": 0,
419
- "source": file_path
452
+ "source": file_path,
420
453
  }
421
-
454
+
422
455
  def unload_command(self, command_name: str) -> Dict[str, Any]:
423
456
  """
424
457
  Unload a loaded command from registry.
425
-
458
+
426
459
  Args:
427
460
  command_name: Name of the command to unload
428
-
461
+
429
462
  Returns:
430
463
  Dictionary with unloading result information
431
464
  """
432
465
  logger.info(f"Unloading command: {command_name}")
433
-
466
+
434
467
  # Check if command exists in loaded commands
435
468
  if command_name not in self._loaded_commands:
436
- error_msg = f"Command '{command_name}' is not a loaded command or does not exist"
469
+ error_msg = (
470
+ f"Command '{command_name}' is not a loaded command or does not exist"
471
+ )
437
472
  logger.warning(error_msg)
438
- return {
439
- "success": False,
440
- "error": error_msg,
441
- "command_name": command_name
442
- }
443
-
473
+ return {"success": False, "error": error_msg, "command_name": command_name}
474
+
444
475
  try:
445
476
  # Remove from loaded commands
446
477
  del self._loaded_commands[command_name]
447
-
478
+
448
479
  # Remove instance if exists
449
480
  if command_name in self._instances:
450
481
  del self._instances[command_name]
451
-
482
+
452
483
  logger.info(f"Successfully unloaded command: {command_name}")
453
484
  return {
454
485
  "success": True,
455
486
  "command_name": command_name,
456
- "message": f"Command '{command_name}' unloaded successfully"
487
+ "message": f"Command '{command_name}' unloaded successfully",
457
488
  }
458
-
489
+
459
490
  except Exception as e:
460
491
  error_msg = f"Failed to unload command '{command_name}': {e}"
461
492
  logger.error(error_msg)
462
- return {
463
- "success": False,
464
- "error": error_msg,
465
- "command_name": command_name
466
- }
467
-
493
+ return {"success": False, "error": error_msg, "command_name": command_name}
494
+
468
495
  def command_exists(self, command_name: str) -> bool:
469
496
  """
470
497
  Check if command exists with priority order.
@@ -475,10 +502,12 @@ class CommandRegistry:
475
502
  Returns:
476
503
  True if command exists, False otherwise.
477
504
  """
478
- return (command_name in self._custom_commands or
479
- command_name in self._builtin_commands or
480
- command_name in self._loaded_commands)
481
-
505
+ return (
506
+ command_name in self._custom_commands
507
+ or command_name in self._builtin_commands
508
+ or command_name in self._loaded_commands
509
+ )
510
+
482
511
  def get_command(self, command_name: str) -> Type[Command]:
483
512
  """
484
513
  Get command class with priority order.
@@ -501,47 +530,49 @@ class CommandRegistry:
501
530
  return self._loaded_commands[command_name]
502
531
  else:
503
532
  raise NotFoundError(f"Command '{command_name}' not found")
504
-
533
+
505
534
  def get_command_instance(self, command_name: str) -> Command:
506
535
  """
507
536
  Get command instance by name. If instance doesn't exist, creates new one.
508
-
537
+
509
538
  Args:
510
539
  command_name: Command name
511
-
540
+
512
541
  Returns:
513
542
  Command instance
514
-
543
+
515
544
  Raises:
516
545
  NotFoundError: If command is not found
517
546
  """
518
547
  if not self.command_exists(command_name):
519
548
  raise NotFoundError(f"Command '{command_name}' not found")
520
-
549
+
521
550
  # Return existing instance if available
522
551
  if command_name in self._instances:
523
552
  return self._instances[command_name]
524
-
553
+
525
554
  # Otherwise create new instance
526
555
  try:
527
556
  command_class = self.get_command(command_name)
528
557
  return command_class()
529
558
  except Exception as e:
530
559
  logger.error(f"Failed to create instance of '{command_name}': {e}")
531
- raise ValueError(f"Command '{command_name}' requires dependencies but was registered as class. Register an instance instead.") from e
532
-
560
+ raise ValueError(
561
+ f"Command '{command_name}' requires dependencies but was registered as class. Register an instance instead."
562
+ ) from e
563
+
533
564
  def has_instance(self, command_name: str) -> bool:
534
565
  """
535
566
  Check if command has a registered instance.
536
-
567
+
537
568
  Args:
538
569
  command_name: Command name
539
-
570
+
540
571
  Returns:
541
572
  True if command has instance, False otherwise
542
573
  """
543
574
  return command_name in self._instances
544
-
575
+
545
576
  def get_all_commands(self) -> Dict[str, Type[Command]]:
546
577
  """
547
578
  Get all registered commands with priority order.
@@ -550,23 +581,23 @@ class CommandRegistry:
550
581
  Dictionary with command names and their classes.
551
582
  """
552
583
  all_commands = {}
553
-
584
+
554
585
  # Add commands in priority order: custom -> built-in -> loaded
555
586
  # Custom commands override built-in and loaded
556
587
  all_commands.update(self._custom_commands)
557
-
588
+
558
589
  # Built-in commands (only if not overridden by custom)
559
590
  for name, command_class in self._builtin_commands.items():
560
591
  if name not in all_commands:
561
592
  all_commands[name] = command_class
562
-
593
+
563
594
  # Loaded commands (only if not overridden by custom or built-in)
564
595
  for name, command_class in self._loaded_commands.items():
565
596
  if name not in all_commands:
566
597
  all_commands[name] = command_class
567
-
598
+
568
599
  return all_commands
569
-
600
+
570
601
  def get_commands_by_type(self) -> Dict[str, Dict[str, Type[Command]]]:
571
602
  """
572
603
  Get commands grouped by type.
@@ -577,25 +608,25 @@ class CommandRegistry:
577
608
  return {
578
609
  "custom": self._custom_commands,
579
610
  "builtin": self._builtin_commands,
580
- "loaded": self._loaded_commands
611
+ "loaded": self._loaded_commands,
581
612
  }
582
-
613
+
583
614
  def get_all_metadata(self) -> Dict[str, Dict[str, Any]]:
584
615
  """
585
616
  Get metadata for all registered commands.
586
-
617
+
587
618
  Returns:
588
619
  Dictionary with command names as keys and metadata as values.
589
620
  """
590
621
  metadata = {}
591
-
622
+
592
623
  # Get all commands with priority order
593
624
  all_commands = self.get_all_commands()
594
-
625
+
595
626
  for command_name, command_class in all_commands.items():
596
627
  try:
597
628
  # Get command metadata
598
- if hasattr(command_class, 'get_metadata'):
629
+ if hasattr(command_class, "get_metadata"):
599
630
  metadata[command_name] = command_class.get_metadata()
600
631
  else:
601
632
  # Fallback metadata
@@ -603,17 +634,21 @@ class CommandRegistry:
603
634
  "name": command_name,
604
635
  "class": command_class.__name__,
605
636
  "module": command_class.__module__,
606
- "description": getattr(command_class, '__doc__', 'No description available')
637
+ "description": getattr(
638
+ command_class, "__doc__", "No description available"
639
+ ),
607
640
  }
608
641
  except Exception as e:
609
- logger.warning(f"Failed to get metadata for command '{command_name}': {e}")
642
+ logger.warning(
643
+ f"Failed to get metadata for command '{command_name}': {e}"
644
+ )
610
645
  metadata[command_name] = {
611
646
  "name": command_name,
612
- "error": f"Failed to get metadata: {str(e)}"
647
+ "error": f"Failed to get metadata: {str(e)}",
613
648
  }
614
-
649
+
615
650
  return metadata
616
-
651
+
617
652
  def clear(self) -> None:
618
653
  """
619
654
  Clear all registered commands.
@@ -623,21 +658,22 @@ class CommandRegistry:
623
658
  self._custom_commands.clear()
624
659
  self._loaded_commands.clear()
625
660
  self._instances.clear()
626
-
627
-
661
+
628
662
  async def reload_system(self, config_path: Optional[str] = None) -> Dict[str, Any]:
629
663
  """
630
664
  Universal method for system initialization and reload.
631
665
  This method should be used both at startup and during reload.
632
-
666
+
633
667
  Args:
634
668
  config_path: Path to configuration file. If None, uses default or existing path.
635
-
669
+
636
670
  Returns:
637
671
  Dictionary with initialization information.
638
672
  """
639
- logger.info(f"🔄 Starting system reload with config: {config_path or 'default'}")
640
-
673
+ logger.info(
674
+ f"🔄 Starting system reload with config: {config_path or 'default'}"
675
+ )
676
+
641
677
  # Step 1: Load configuration
642
678
  try:
643
679
  if config_path:
@@ -646,52 +682,57 @@ class CommandRegistry:
646
682
  else:
647
683
  config.load_config()
648
684
  logger.info("✅ Configuration loaded from default path")
649
-
685
+
650
686
  config_reloaded = True
651
687
  except Exception as e:
652
688
  logger.error(f"❌ Failed to load configuration: {e}")
653
689
  config_reloaded = False
654
-
690
+
655
691
  # Step 2: Initialize logging with configuration
656
692
  try:
657
693
  from mcp_proxy_adapter.core.logging import setup_logging
694
+
658
695
  setup_logging()
659
696
  logger.info("✅ Logging initialized with configuration")
660
697
  except Exception as e:
661
698
  logger.error(f"❌ Failed to initialize logging: {e}")
662
-
699
+
663
700
  # Step 2.5: Reload protocol manager configuration
664
701
  try:
665
702
  from mcp_proxy_adapter.core.protocol_manager import protocol_manager
703
+
666
704
  protocol_manager.reload_config()
667
705
  logger.info("✅ Protocol manager configuration reloaded")
668
706
  except Exception as e:
669
707
  logger.error(f"❌ Failed to reload protocol manager: {e}")
670
-
708
+
671
709
  # Step 3: Clear all commands (always clear for consistency)
672
710
  self.clear()
673
-
711
+
674
712
  # Step 4: Execute before init hooks
675
713
  try:
676
714
  hooks.execute_before_init_hooks()
677
715
  except Exception as e:
678
716
  logger.error(f"❌ Failed to execute before init hooks: {e}")
679
-
717
+
680
718
  # Step 5: Register built-in commands
681
719
  try:
682
- from mcp_proxy_adapter.commands.builtin_commands import register_builtin_commands
720
+ from mcp_proxy_adapter.commands.builtin_commands import (
721
+ register_builtin_commands,
722
+ )
723
+
683
724
  builtin_commands_count = register_builtin_commands()
684
725
  except Exception as e:
685
726
  logger.error(f"❌ Failed to register built-in commands: {e}")
686
727
  builtin_commands_count = 0
687
-
728
+
688
729
  # Step 6: Execute custom commands hooks
689
730
  try:
690
731
  custom_commands_count = hooks.execute_custom_commands_hooks(self)
691
732
  except Exception as e:
692
733
  logger.error(f"❌ Failed to execute custom commands hooks: {e}")
693
734
  custom_commands_count = 0
694
-
735
+
695
736
  # Step 7: Load all commands (built-in, custom, loadable)
696
737
  try:
697
738
  load_result = self._load_all_commands()
@@ -701,52 +742,59 @@ class CommandRegistry:
701
742
  logger.error(f"❌ Failed to load commands: {e}")
702
743
  remote_commands_count = 0
703
744
  loaded_commands_count = 0
704
-
745
+
705
746
  # Step 8: Execute after init hooks
706
747
  try:
707
748
  hooks.execute_after_init_hooks()
708
749
  except Exception as e:
709
750
  logger.error(f"❌ Failed to execute after init hooks: {e}")
710
-
751
+
711
752
  # Step 9: Register with proxy if enabled
712
753
  proxy_registration_success = False
713
754
  try:
714
- from mcp_proxy_adapter.core.proxy_registration import register_with_proxy, initialize_proxy_registration
715
-
755
+ from mcp_proxy_adapter.core.proxy_registration import (
756
+ register_with_proxy,
757
+ initialize_proxy_registration,
758
+ )
759
+
716
760
  # Initialize proxy registration manager with current config
717
761
  initialize_proxy_registration(config.get_all())
718
-
762
+
719
763
  # Get server configuration
720
764
  server_config = config.get("server", {})
721
765
  server_host = server_config.get("host", "0.0.0.0")
722
766
  server_port = server_config.get("port", 8000)
723
-
767
+
724
768
  # Determine server URL based on SSL configuration
725
769
  ssl_config = config.get("ssl", {})
726
770
  if ssl_config.get("enabled", False):
727
771
  protocol = "https"
728
772
  else:
729
773
  protocol = "http"
730
-
774
+
731
775
  # Use localhost for external access if host is 0.0.0.0
732
776
  if server_host == "0.0.0.0":
733
777
  server_host = "localhost"
734
-
778
+
735
779
  server_url = f"{protocol}://{server_host}:{server_port}"
736
-
780
+
737
781
  # Attempt proxy registration
738
782
  proxy_registration_success = await register_with_proxy(server_url)
739
783
  if proxy_registration_success:
740
- logger.info("✅ Proxy registration completed successfully during system reload")
784
+ logger.info(
785
+ "✅ Proxy registration completed successfully during system reload"
786
+ )
741
787
  else:
742
- logger.info("ℹ️ Proxy registration is disabled or failed during system reload")
743
-
788
+ logger.info(
789
+ "ℹ️ Proxy registration is disabled or failed during system reload"
790
+ )
791
+
744
792
  except Exception as e:
745
793
  logger.error(f"❌ Failed to register with proxy during system reload: {e}")
746
-
794
+
747
795
  # Get final counts
748
796
  total_commands = len(self.get_all_commands())
749
-
797
+
750
798
  result = {
751
799
  "config_reloaded": config_reloaded,
752
800
  "builtin_commands": builtin_commands_count,
@@ -754,23 +802,23 @@ class CommandRegistry:
754
802
  "loaded_commands": loaded_commands_count,
755
803
  "remote_commands": remote_commands_count,
756
804
  "total_commands": total_commands,
757
- "proxy_registration_success": proxy_registration_success
805
+ "proxy_registration_success": proxy_registration_success,
758
806
  }
759
-
807
+
760
808
  logger.info(f"✅ System reload completed: {result}")
761
809
  return result
762
-
810
+
763
811
  def _load_all_commands(self) -> Dict[str, Any]:
764
812
  """
765
813
  Universal command loader - handles all command types.
766
-
814
+
767
815
  Returns:
768
816
  Dictionary with loading results
769
817
  """
770
818
  try:
771
819
  remote_commands = 0
772
820
  loaded_commands = 0
773
-
821
+
774
822
  # 1. Load commands from directory (if configured)
775
823
  commands_directory = config.get("commands.commands_directory")
776
824
  if commands_directory and os.path.exists(commands_directory):
@@ -782,18 +830,25 @@ class CommandRegistry:
782
830
  loaded_commands += result.get("commands_loaded", 0)
783
831
  except Exception as e:
784
832
  logger.error(f"Failed to load command from {file_path}: {e}")
785
-
833
+
786
834
  # 2. Load commands from plugin servers (if configured)
787
835
  plugin_servers = config.get("commands.plugin_servers", [])
788
836
  if plugin_servers:
789
- logger.info(f"Loading commands from {len(plugin_servers)} plugin servers")
837
+ logger.info(
838
+ f"Loading commands from {len(plugin_servers)} plugin servers"
839
+ )
790
840
  for server_url in plugin_servers:
791
841
  try:
792
842
  # Load catalog from server
793
- from mcp_proxy_adapter.commands.catalog_manager import CatalogManager
843
+ from mcp_proxy_adapter.commands.catalog_manager import (
844
+ CatalogManager,
845
+ )
846
+
794
847
  catalog_manager = CatalogManager("./catalog")
795
- server_catalog = catalog_manager.get_catalog_from_server(server_url)
796
-
848
+ server_catalog = catalog_manager.get_catalog_from_server(
849
+ server_url
850
+ )
851
+
797
852
  # Load each command from catalog
798
853
  for command_name, server_cmd in server_catalog.items():
799
854
  try:
@@ -801,72 +856,66 @@ class CommandRegistry:
801
856
  if result.get("success"):
802
857
  remote_commands += result.get("commands_loaded", 0)
803
858
  except Exception as e:
804
- logger.error(f"Failed to load command {command_name}: {e}")
805
-
859
+ logger.error(
860
+ f"Failed to load command {command_name}: {e}"
861
+ )
862
+
806
863
  except Exception as e:
807
864
  logger.error(f"Failed to load from server {server_url}: {e}")
808
-
865
+
809
866
  return {
810
867
  "remote_commands": remote_commands,
811
- "loaded_commands": loaded_commands
868
+ "loaded_commands": loaded_commands,
812
869
  }
813
-
870
+
814
871
  except Exception as e:
815
872
  logger.error(f"Failed to load all commands: {e}")
816
- return {
817
- "remote_commands": 0,
818
- "loaded_commands": 0,
819
- "error": str(e)
820
- }
821
-
873
+ return {"remote_commands": 0, "loaded_commands": 0, "error": str(e)}
822
874
 
823
875
  def get_all_commands_info(self) -> Dict[str, Any]:
824
876
  """
825
877
  Get information about all registered commands.
826
-
878
+
827
879
  Returns:
828
880
  Dictionary with command information
829
881
  """
830
882
  commands_info = {}
831
-
883
+
832
884
  # Get all commands
833
885
  all_commands = self.get_all_commands()
834
-
886
+
835
887
  for command_name, command_class in all_commands.items():
836
888
  try:
837
889
  # Get command metadata
838
890
  metadata = command_class.get_metadata()
839
-
891
+
840
892
  # Get command schema
841
893
  schema = command_class.get_schema()
842
-
894
+
843
895
  commands_info[command_name] = {
844
896
  "name": command_name,
845
897
  "metadata": metadata,
846
898
  "schema": schema,
847
- "type": self._get_command_type(command_name)
899
+ "type": self._get_command_type(command_name),
848
900
  }
849
-
901
+
850
902
  except Exception as e:
851
903
  logger.warning(f"Failed to get info for command {command_name}: {e}")
852
904
  commands_info[command_name] = {
853
905
  "name": command_name,
854
906
  "error": str(e),
855
- "type": self._get_command_type(command_name)
907
+ "type": self._get_command_type(command_name),
856
908
  }
857
-
858
- return {
859
- "commands": commands_info,
860
- "total": len(commands_info)
861
- }
862
-
909
+
910
+ return {"commands": commands_info, "total": len(commands_info)}
911
+
863
912
  def get_command_info(self, command_name: str) -> Optional[Dict[str, Any]]:
864
913
  """
865
914
  Get information about a specific command.
866
-
915
+
867
916
  Args:
868
917
  command_name: Name of the command
869
-
918
+
870
919
  Returns:
871
920
  Dictionary with command information or None if not found
872
921
  """
@@ -874,38 +923,38 @@ class CommandRegistry:
874
923
  # Check if command exists
875
924
  if not self.command_exists(command_name):
876
925
  return None
877
-
926
+
878
927
  # Get command class
879
928
  command_class = self.get_command(command_name)
880
-
929
+
881
930
  # Get command metadata
882
931
  metadata = command_class.get_metadata()
883
-
932
+
884
933
  # Get command schema
885
934
  schema = command_class.get_schema()
886
-
935
+
887
936
  return {
888
937
  "name": command_name,
889
938
  "metadata": metadata,
890
939
  "schema": schema,
891
- "type": self._get_command_type(command_name)
940
+ "type": self._get_command_type(command_name),
892
941
  }
893
-
942
+
894
943
  except Exception as e:
895
944
  logger.warning(f"Failed to get info for command {command_name}: {e}")
896
945
  return {
897
946
  "name": command_name,
898
947
  "error": str(e),
899
- "type": self._get_command_type(command_name)
948
+ "type": self._get_command_type(command_name),
900
949
  }
901
-
950
+
902
951
  def _get_command_type(self, command_name: str) -> str:
903
952
  """
904
953
  Get the type of a command (built-in, custom, or loaded).
905
-
954
+
906
955
  Args:
907
956
  command_name: Name of the command
908
-
957
+
909
958
  Returns:
910
959
  Command type string
911
960
  """