mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.29__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.

Potentially problematic release.


This version of mcp-proxy-adapter might be problematic. Click here for more details.

Files changed (212) hide show
  1. mcp_proxy_adapter/__init__.py +10 -0
  2. mcp_proxy_adapter/__main__.py +8 -21
  3. mcp_proxy_adapter/api/app.py +10 -913
  4. mcp_proxy_adapter/api/core/__init__.py +18 -0
  5. mcp_proxy_adapter/api/core/app_factory.py +243 -0
  6. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  7. mcp_proxy_adapter/api/core/registration_manager.py +166 -0
  8. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  9. mcp_proxy_adapter/api/handlers.py +78 -199
  10. mcp_proxy_adapter/api/middleware/__init__.py +1 -44
  11. mcp_proxy_adapter/api/middleware/base.py +0 -42
  12. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
  13. mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
  14. mcp_proxy_adapter/api/middleware/factory.py +0 -94
  15. mcp_proxy_adapter/api/middleware/logging.py +0 -112
  16. mcp_proxy_adapter/api/middleware/performance.py +0 -35
  17. mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
  18. mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
  19. mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
  20. mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
  21. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  22. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  23. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  24. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  25. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  26. mcp_proxy_adapter/api/schemas.py +0 -61
  27. mcp_proxy_adapter/api/tool_integration.py +0 -117
  28. mcp_proxy_adapter/api/tools.py +0 -46
  29. mcp_proxy_adapter/cli/__init__.py +12 -0
  30. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  31. mcp_proxy_adapter/cli/commands/client.py +100 -0
  32. mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
  33. mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
  34. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  35. mcp_proxy_adapter/cli/commands/server.py +174 -0
  36. mcp_proxy_adapter/cli/commands/sets.py +128 -0
  37. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  38. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  39. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  40. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  41. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  42. mcp_proxy_adapter/cli/main.py +63 -0
  43. mcp_proxy_adapter/cli/parser.py +324 -0
  44. mcp_proxy_adapter/cli/validators.py +231 -0
  45. mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
  46. mcp_proxy_adapter/client/proxy.py +45 -0
  47. mcp_proxy_adapter/commands/__init__.py +44 -28
  48. mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
  49. mcp_proxy_adapter/commands/base.py +19 -43
  50. mcp_proxy_adapter/commands/builtin_commands.py +0 -75
  51. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  52. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  53. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  54. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  55. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  56. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  57. mcp_proxy_adapter/commands/catalog_manager.py +58 -928
  58. mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
  59. mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
  60. mcp_proxy_adapter/commands/command_registry.py +172 -904
  61. mcp_proxy_adapter/commands/config_command.py +0 -28
  62. mcp_proxy_adapter/commands/dependency_container.py +1 -70
  63. mcp_proxy_adapter/commands/dependency_manager.py +0 -128
  64. mcp_proxy_adapter/commands/echo_command.py +0 -34
  65. mcp_proxy_adapter/commands/health_command.py +0 -3
  66. mcp_proxy_adapter/commands/help_command.py +0 -159
  67. mcp_proxy_adapter/commands/hooks.py +0 -137
  68. mcp_proxy_adapter/commands/key_management_command.py +0 -25
  69. mcp_proxy_adapter/commands/load_command.py +7 -78
  70. mcp_proxy_adapter/commands/plugins_command.py +0 -16
  71. mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
  72. mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
  73. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  74. mcp_proxy_adapter/commands/registration_status_command.py +0 -43
  75. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  76. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  77. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  78. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  79. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  80. mcp_proxy_adapter/commands/reload_command.py +0 -80
  81. mcp_proxy_adapter/commands/result.py +25 -77
  82. mcp_proxy_adapter/commands/role_test_command.py +0 -44
  83. mcp_proxy_adapter/commands/roles_management_command.py +0 -199
  84. mcp_proxy_adapter/commands/security_command.py +0 -30
  85. mcp_proxy_adapter/commands/settings_command.py +0 -68
  86. mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
  87. mcp_proxy_adapter/commands/token_management_command.py +0 -1
  88. mcp_proxy_adapter/commands/transport_management_command.py +0 -20
  89. mcp_proxy_adapter/commands/unload_command.py +0 -71
  90. mcp_proxy_adapter/config.py +15 -626
  91. mcp_proxy_adapter/core/__init__.py +5 -39
  92. mcp_proxy_adapter/core/app_factory.py +14 -36
  93. mcp_proxy_adapter/core/app_runner.py +0 -27
  94. mcp_proxy_adapter/core/auth_validator.py +1 -93
  95. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  96. mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
  97. mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
  98. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  99. mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
  100. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
  101. mcp_proxy_adapter/core/certificate_utils.py +64 -903
  102. mcp_proxy_adapter/core/client.py +0 -6
  103. mcp_proxy_adapter/core/client_manager.py +0 -19
  104. mcp_proxy_adapter/core/client_security.py +0 -2
  105. mcp_proxy_adapter/core/config/__init__.py +18 -0
  106. mcp_proxy_adapter/core/config/config.py +195 -0
  107. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  108. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  109. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  110. mcp_proxy_adapter/core/config/simple_config.py +112 -0
  111. mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
  112. mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
  113. mcp_proxy_adapter/core/config_converter.py +0 -186
  114. mcp_proxy_adapter/core/config_validator.py +96 -1238
  115. mcp_proxy_adapter/core/errors.py +7 -42
  116. mcp_proxy_adapter/core/job_manager.py +54 -0
  117. mcp_proxy_adapter/core/logging.py +2 -22
  118. mcp_proxy_adapter/core/mtls_asgi.py +0 -20
  119. mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
  120. mcp_proxy_adapter/core/mtls_proxy.py +0 -80
  121. mcp_proxy_adapter/core/mtls_server.py +3 -173
  122. mcp_proxy_adapter/core/protocol_manager.py +1 -191
  123. mcp_proxy_adapter/core/proxy/__init__.py +22 -0
  124. mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
  125. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
  126. mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
  127. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  128. mcp_proxy_adapter/core/proxy_client.py +0 -1
  129. mcp_proxy_adapter/core/proxy_registration.py +36 -913
  130. mcp_proxy_adapter/core/role_utils.py +0 -308
  131. mcp_proxy_adapter/core/security_adapter.py +1 -36
  132. mcp_proxy_adapter/core/security_factory.py +1 -150
  133. mcp_proxy_adapter/core/security_integration.py +0 -33
  134. mcp_proxy_adapter/core/server_adapter.py +1 -40
  135. mcp_proxy_adapter/core/server_engine.py +2 -173
  136. mcp_proxy_adapter/core/settings.py +0 -127
  137. mcp_proxy_adapter/core/signal_handler.py +0 -65
  138. mcp_proxy_adapter/core/ssl_utils.py +19 -137
  139. mcp_proxy_adapter/core/transport_manager.py +0 -151
  140. mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
  141. mcp_proxy_adapter/core/utils.py +1 -182
  142. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  143. mcp_proxy_adapter/core/validation/config_validator.py +211 -0
  144. mcp_proxy_adapter/core/validation/file_validator.py +73 -0
  145. mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
  146. mcp_proxy_adapter/core/validation/security_validator.py +58 -0
  147. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  148. mcp_proxy_adapter/custom_openapi.py +33 -652
  149. mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
  150. mcp_proxy_adapter/examples/check_config.py +0 -2
  151. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  152. mcp_proxy_adapter/examples/config_builder.py +13 -2
  153. mcp_proxy_adapter/examples/config_cli.py +0 -1
  154. mcp_proxy_adapter/examples/create_test_configs.py +0 -46
  155. mcp_proxy_adapter/examples/debug_request_state.py +0 -1
  156. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
  157. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
  158. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
  159. mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
  160. mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
  161. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
  162. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
  163. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
  164. mcp_proxy_adapter/examples/full_application/main.py +186 -150
  165. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
  166. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
  167. mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
  168. mcp_proxy_adapter/examples/generate_config.py +65 -11
  169. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  170. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  171. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  172. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  173. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  174. mcp_proxy_adapter/examples/required_certificates.py +0 -2
  175. mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
  176. mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
  177. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
  178. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  179. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  180. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  181. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  182. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  183. mcp_proxy_adapter/examples/security_test_client.py +24 -1075
  184. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  185. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  186. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  187. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  188. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  189. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  190. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  191. mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
  192. mcp_proxy_adapter/examples/test_config.py +0 -3
  193. mcp_proxy_adapter/examples/test_config_builder.py +25 -405
  194. mcp_proxy_adapter/examples/test_examples.py +0 -1
  195. mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
  196. mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
  197. mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
  198. mcp_proxy_adapter/examples/universal_client.py +0 -6
  199. mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
  200. mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
  201. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
  202. mcp_proxy_adapter/integrations/__init__.py +25 -0
  203. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  204. mcp_proxy_adapter/main.py +70 -62
  205. mcp_proxy_adapter/openapi.py +0 -22
  206. mcp_proxy_adapter/version.py +1 -1
  207. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
  208. mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
  209. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
  210. mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
  211. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
@@ -1,233 +1,156 @@
1
1
  """
2
- Module for registering and managing commands.
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
3
4
 
4
- Example: Registering a command instance (for dependency injection)
5
- ---------------------------------------------------------------
6
-
7
- .. code-block:: python
8
-
9
- from mcp_proxy_adapter.commands.command_registry import registry
10
- from my_commands import MyCommand
11
-
12
- # Suppose MyCommand requires a service dependency
13
- service = MyService()
14
- my_command_instance = MyCommand(service=service)
15
- registry.register(my_command_instance)
16
-
17
- # Now, when the command is executed, the same instance (with dependencies) will be used
5
+ Main command registry for MCP Proxy Adapter.
18
6
  """
19
7
 
20
- import importlib
21
- import importlib.util
22
- import inspect
23
- import os
24
- import pkgutil
25
- import tempfile
26
- import urllib.parse
27
- from pathlib import Path
28
- from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast
8
+
9
+ from typing import Dict, List, Type, Union, Any, Optional
29
10
 
30
11
  from mcp_proxy_adapter.commands.base import Command
31
- from mcp_proxy_adapter.commands.hooks import hooks
32
- from mcp_proxy_adapter.commands.catalog_manager import CatalogManager
33
- from mcp_proxy_adapter.config import config
34
- from mcp_proxy_adapter.core.errors import NotFoundError
35
12
  from mcp_proxy_adapter.core.logging import get_global_logger
36
-
37
- try:
38
- import requests
39
-
40
- REQUESTS_AVAILABLE = True
41
- except ImportError:
42
- REQUESTS_AVAILABLE = False
43
- get_global_logger().warning("requests library not available, HTTP/HTTPS loading will not work")
44
-
45
- T = TypeVar("T", bound=Command)
13
+ # from .command_loader import CommandLoader
14
+ # from .command_manager import CommandManager
15
+ # from .command_info import CommandInfo
46
16
 
47
17
 
48
18
  class CommandRegistry:
49
19
  """
50
20
  Registry for registering and finding commands.
21
+
22
+ Supports three types of commands:
23
+ - Builtin: Core commands that come with the framework
24
+ - Custom: User-defined commands
25
+ - Loaded: Commands loaded from external sources
51
26
  """
52
27
 
53
28
  def __init__(self):
54
- """
55
- Initialize command registry.
56
- """
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
66
- self._instances: Dict[str, Command] = {} # Command instances
67
-
68
- def register_builtin(self, command: Union[Type[Command], Command]) -> None:
69
- """
70
- Register a built-in framework command.
71
-
72
- Args:
73
- command: Command class or instance to register.
74
-
75
- Raises:
76
- ValueError: If command with the same name is already registered.
77
- """
78
- command_name = self._get_command_name(command)
79
-
80
- # Check for conflicts with other built-in commands
81
- if command_name in self._builtin_commands:
82
- get_global_logger().error(
83
- f"Built-in command '{command_name}' is already registered, skipping"
84
- )
85
- raise ValueError(f"Built-in command '{command_name}' is already registered")
86
-
87
- # Built-in commands can override loaded commands
88
- # Remove any existing loaded commands with the same name
89
- if command_name in self._loaded_commands:
90
- get_global_logger().info(f"Built-in command '{command_name}' overrides loaded command")
91
- del self._loaded_commands[command_name]
92
-
93
- self._register_command(command, self._builtin_commands, "built-in")
94
-
95
- def register_custom(self, command: Union[Type[Command], Command]) -> None:
96
- """
97
- Register a custom command with highest priority.
98
-
99
- Args:
100
- command: Command class or instance to register.
29
+ """Initialize command registry."""
30
+ self.logger = get_global_logger()
31
+
32
+ # Command storage
33
+ self._commands: Dict[str, Type[Command]] = {}
34
+ self._instances: Dict[str, Command] = {}
35
+ self._command_types: Dict[str, str] = {} # "builtin", "custom", "loaded"
36
+
37
+ # Initialize components
38
+ # self._loader = CommandLoader()
39
+ self._loader = None
40
+ # self._manager = CommandManager()
41
+ # self._info = CommandInfo()
42
+ self._manager = None
43
+ self._info = None
44
+
45
+ # Register built-in echo command
46
+ self._register_echo_command()
47
+ self._register_long_task_commands()
48
+
49
+ def _register_echo_command(self) -> None:
50
+ """Register built-in echo command."""
51
+ from mcp_proxy_adapter.commands.base import Command, CommandResult
52
+
53
+ class EchoCommand(Command):
54
+ name = "echo"
55
+ descr = "Echo command for testing"
56
+
57
+ async def execute(self, message: str = "Hello", **kwargs) -> CommandResult:
58
+ return CommandResult(success=True, data={"message": message})
59
+
60
+ @classmethod
61
+ def get_schema(cls) -> Dict[str, Any]:
62
+ return {
63
+ "type": "object",
64
+ "properties": {
65
+ "message": {"type": "string", "default": "Hello"}
66
+ }
67
+ }
68
+
69
+ self._commands["echo"] = EchoCommand
70
+ self._command_types["echo"] = "builtin"
71
+
72
+ def _register_long_task_commands(self) -> None:
73
+ """Register demo long-running task commands (enqueue/status)."""
74
+ from mcp_proxy_adapter.commands.base import Command, CommandResult
75
+ from mcp_proxy_adapter.core.job_manager import enqueue_coroutine, get_job_status
76
+ import asyncio
77
+
78
+ class LongTaskCommand(Command):
79
+ name = "long_task"
80
+ descr = "Enqueue a long-running task that sleeps for given seconds"
81
+
82
+ async def execute(self, seconds: float = 5.0, **kwargs) -> CommandResult:
83
+ async def _work():
84
+ await asyncio.sleep(max(0.0, float(seconds)))
85
+ return {"slept": float(seconds)}
86
+
87
+ job_id = enqueue_coroutine(_work())
88
+ return CommandResult(success=True, data={"job_id": job_id, "status": "queued"})
89
+
90
+ @classmethod
91
+ def get_schema(cls) -> Dict[str, Any]:
92
+ return {
93
+ "type": "object",
94
+ "properties": {"seconds": {"type": "number", "default": 5.0}},
95
+ "description": "Start a demo long-running job"
96
+ }
101
97
 
102
- Raises:
103
- ValueError: If command with the same name is already registered.
104
- """
105
- command_name = self._get_command_name(command)
98
+ class JobStatusCommand(Command):
99
+ name = "job_status"
100
+ descr = "Get status of a previously enqueued job"
106
101
 
107
- # Check for conflicts with other custom commands
108
- if command_name in self._custom_commands:
109
- get_global_logger().error(
110
- f"Custom command '{command_name}' is already registered, skipping"
111
- )
112
- raise ValueError(f"Custom command '{command_name}' is already registered")
102
+ async def execute(self, job_id: str, **kwargs) -> CommandResult:
103
+ status = get_job_status(job_id)
104
+ return CommandResult(success=True, data=status)
113
105
 
114
- # Custom commands can override built-in and loaded commands
115
- # Remove any existing commands with the same name from other types
116
- if command_name in self._builtin_commands:
117
- get_global_logger().info(f"Custom command '{command_name}' overrides built-in command")
118
- del self._builtin_commands[command_name]
106
+ @classmethod
107
+ def get_schema(cls) -> Dict[str, Any]:
108
+ return {
109
+ "type": "object",
110
+ "properties": {"job_id": {"type": "string"}},
111
+ "required": ["job_id"],
112
+ "description": "Check job status"
113
+ }
119
114
 
120
- if command_name in self._loaded_commands:
121
- get_global_logger().info(f"Custom command '{command_name}' overrides loaded command")
122
- del self._loaded_commands[command_name]
115
+ self._commands["long_task"] = LongTaskCommand
116
+ self._command_types["long_task"] = "builtin"
117
+ self._commands["job_status"] = JobStatusCommand
118
+ self._command_types["job_status"] = "builtin"
123
119
 
124
- self._register_command(command, self._custom_commands, "custom")
125
120
 
126
121
  def register_loaded(self, command: Union[Type[Command], Command]) -> None:
127
122
  """
128
- Register a command loaded from directory.
123
+ Register a loaded command.
129
124
 
130
125
  Args:
131
- command: Command class or instance to register.
132
-
133
- Returns:
134
- bool: True if registered, False if skipped due to conflict.
126
+ command: Command class or instance to register
135
127
  """
136
- command_name = self._get_command_name(command)
137
-
138
- # Check for conflicts with custom and built-in commands
139
- if command_name in self._custom_commands:
140
- get_global_logger().warning(
141
- f"Loaded command '{command_name}' conflicts with custom command, skipping"
142
- )
143
- return False
144
-
145
- if command_name in self._builtin_commands:
146
- get_global_logger().warning(
147
- f"Loaded command '{command_name}' conflicts with built-in command, skipping"
148
- )
149
- return False
150
-
151
- # Check for conflicts within loaded commands
152
- if command_name in self._loaded_commands:
153
- get_global_logger().warning(
154
- f"Loaded command '{command_name}' already exists, skipping duplicate"
155
- )
156
- return False
128
+ self._register_command(command, "loaded")
157
129
 
158
- try:
159
- self._register_command(command, self._loaded_commands, "loaded")
160
- return True
161
- except ValueError:
162
- return False
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:
130
+ def _register_command(self, command: Union[Type[Command], Command], cmd_type: str) -> None:
170
131
  """
171
- Internal method to register a command in the specified dictionary.
132
+ Register a command.
172
133
 
173
134
  Args:
174
- command: Command class or instance to register.
175
- target_dict: Dictionary to register the command in.
176
- command_type: Type of command for logging.
177
-
178
- Raises:
179
- ValueError: If command with the same name is already registered.
180
- """
181
- # Determine if this is a class or an instance
182
- if isinstance(command, type) and issubclass(command, Command):
183
- command_class = command
184
- command_instance = None
185
- elif isinstance(command, Command):
186
- command_class = command.__class__
187
- command_instance = command
135
+ command: Command class or instance to register
136
+ cmd_type: Type of command ("builtin", "custom", "loaded")
137
+ """
138
+ if isinstance(command, Command):
139
+ # Register instance
140
+ command_name = self._manager._get_command_name(command.__class__)
141
+ self._instances[command_name] = command
142
+ self._commands[command_name] = command.__class__
188
143
  else:
189
- raise ValueError(
190
- f"Invalid command type: {type(command)}. Expected Command class or instance."
191
- )
192
-
193
- command_name = self._get_command_name(command_class)
194
-
195
- if command_name in target_dict:
196
- raise ValueError(
197
- f"{command_type.capitalize()} command '{command_name}' is already registered"
198
- )
199
-
200
- get_global_logger().debug(f"Registering {command_type} command: {command_name}")
201
- target_dict[command_name] = command_class
202
-
203
- # Store instance if provided
204
- if command_instance:
205
- get_global_logger().debug(f"Storing {command_type} instance for command: {command_name}")
206
- self._instances[command_name] = command_instance
207
-
208
- def _get_command_name(self, command_class: Type[Command]) -> str:
209
- """
210
- Get command name from command class.
211
-
212
- Args:
213
- command_class: Command class.
214
-
215
- Returns:
216
- Command name.
217
- """
218
- if not hasattr(command_class, "name") or not command_class.name:
219
- # Use class name if name attribute is not set
220
- command_name = command_class.__name__.lower()
221
- if command_name.endswith("command"):
222
- command_name = command_name[:-7] # Remove "command" suffix
223
- else:
224
- command_name = command_class.name
225
-
226
- return command_name
144
+ # Register class
145
+ command_name = self._manager._get_command_name(command)
146
+ self._commands[command_name] = command
147
+
148
+ self._command_types[command_name] = cmd_type
149
+ self.logger.info(f"Registered {cmd_type} command: {command_name}")
227
150
 
228
151
  def load_command_from_source(self, source: str) -> Dict[str, Any]:
229
152
  """
230
- Universal command loader - handles local files, URLs, and remote registry.
153
+ Load command from source.
231
154
 
232
155
  Args:
233
156
  source: Source string - local path, URL, or command name from registry
@@ -235,738 +158,116 @@ class CommandRegistry:
235
158
  Returns:
236
159
  Dictionary with loading result information
237
160
  """
238
- get_global_logger().info(f"Loading command from source: {source}")
239
-
240
- # Parse source to determine type
241
- parsed_url = urllib.parse.urlparse(source)
242
- is_url = parsed_url.scheme in ("http", "https")
243
-
244
- if is_url:
245
- # URL - always download and load
246
- return self._load_command_from_url(source)
247
- else:
248
- # Local path or command name - check remote registry first
249
- return self._load_command_with_registry_check(source)
250
-
251
- def _load_command_with_registry_check(self, source: str) -> Dict[str, Any]:
252
- """
253
- Load command with remote registry check.
254
-
255
- Args:
256
- source: Local path or command name
257
-
258
- Returns:
259
- Dictionary with loading result information
260
- """
261
- try:
262
- from mcp_proxy_adapter.commands.catalog_manager import CatalogManager
263
- from mcp_proxy_adapter.config import get_config
264
-
265
- # Get configuration
266
- config_obj = get_config()
267
-
268
- # Get remote registry
269
- plugin_servers = config_obj.get("commands.plugin_servers", [])
270
- catalog_dir = "./catalog"
271
-
272
- if plugin_servers:
273
- # Initialize catalog manager
274
- catalog_manager = CatalogManager(catalog_dir)
275
-
276
- # Check if source is a command name in registry
277
- if not os.path.exists(source) and not source.endswith("_command.py"):
278
- # Try to find in remote registry
279
- for server_url in plugin_servers:
280
- try:
281
- server_catalog = catalog_manager.get_catalog_from_server(
282
- server_url
283
- )
284
- if source in server_catalog:
285
- server_cmd = server_catalog[source]
286
- # Download from registry
287
- if catalog_manager._download_command(
288
- source, server_cmd
289
- ):
290
- source = str(
291
- catalog_manager.commands_dir
292
- / f"{source}_command.py"
293
- )
294
- break
295
- except Exception as e:
296
- get_global_logger().warning(
297
- f"Failed to check registry {server_url}: {e}"
298
- )
299
-
300
- # Load from local file
301
- return self._load_command_from_file(source)
302
-
303
- except Exception as e:
304
- get_global_logger().error(f"Failed to load command with registry check: {e}")
305
- return {"success": False, "commands_loaded": 0, "error": str(e)}
306
-
307
- def _load_command_from_url(self, url: str) -> Dict[str, Any]:
308
- """
309
- Load command from HTTP/HTTPS URL.
310
-
311
- Args:
312
- url: URL to load command from
313
-
314
- Returns:
315
- Dictionary with loading result information
316
- """
317
- if not REQUESTS_AVAILABLE:
318
- error_msg = "requests library not available, cannot load from URL"
319
- get_global_logger().error(error_msg)
320
- return {
321
- "success": False,
322
- "error": error_msg,
323
- "commands_loaded": 0,
324
- "source": url,
325
- }
326
-
327
- try:
328
- get_global_logger().debug(f"Downloading command from URL: {url}")
329
- response = requests.get(url, timeout=30)
330
- response.raise_for_status()
331
-
332
- # Get filename from URL or use default
333
- filename = os.path.basename(urllib.parse.urlparse(url).path)
334
- if not filename or not filename.endswith(".py"):
335
- filename = "remote_command.py"
336
-
337
- # Create temporary file
338
- with tempfile.NamedTemporaryFile(
339
- mode="w", suffix=".py", delete=False
340
- ) as temp_file:
341
- temp_file.write(response.text)
342
- temp_file_path = temp_file.name
343
-
344
- try:
345
- # Load command from temporary file
346
- result = self._load_command_from_file(temp_file_path, is_temporary=True)
347
- result["source"] = url
348
- return result
349
- finally:
350
- # Clean up temporary file
351
- try:
352
- os.unlink(temp_file_path)
353
- except Exception as e:
354
- get_global_logger().warning(
355
- f"Failed to clean up temporary file {temp_file_path}: {e}"
356
- )
357
-
358
- except Exception as e:
359
- error_msg = f"Failed to load command from URL {url}: {e}"
360
- get_global_logger().error(error_msg)
361
- return {
362
- "success": False,
363
- "error": error_msg,
364
- "commands_loaded": 0,
365
- "source": url,
366
- }
367
-
368
- def _load_command_from_file(
369
- self, file_path: str, is_temporary: bool = False
370
- ) -> Dict[str, Any]:
371
- """
372
- Load command from local file.
373
-
374
- Args:
375
- file_path: Path to command file
376
- is_temporary: Whether this is a temporary file (for cleanup)
377
-
378
- Returns:
379
- Dictionary with loading result information
380
- """
381
- if not os.path.exists(file_path):
382
- error_msg = f"Command file does not exist: {file_path}"
383
- get_global_logger().error(error_msg)
384
- return {
385
- "success": False,
386
- "error": error_msg,
387
- "commands_loaded": 0,
388
- "source": file_path,
389
- }
390
-
391
- # For temporary files (downloaded from URL), we don't enforce the _command.py naming
392
- # since the original filename is preserved in the URL
393
- if not is_temporary and not file_path.endswith("_command.py"):
394
- error_msg = f"Command file must end with '_command.py': {file_path}"
395
- get_global_logger().error(error_msg)
396
- return {
397
- "success": False,
398
- "error": error_msg,
399
- "commands_loaded": 0,
400
- "source": file_path,
401
- }
402
-
403
- try:
404
- module_name = os.path.basename(file_path)[:-3] # Remove .py extension
405
- get_global_logger().debug(f"Loading command from file: {file_path}")
406
-
407
- # Load module from file
408
- spec = importlib.util.spec_from_file_location(module_name, file_path)
409
- if spec and spec.loader:
410
- module = importlib.util.module_from_spec(spec)
411
- spec.loader.exec_module(module)
412
-
413
- commands_loaded = 0
414
- loaded_commands = []
415
-
416
- # Find command classes in the module
417
- for name, obj in inspect.getmembers(module):
418
- if (
419
- inspect.isclass(obj)
420
- and issubclass(obj, Command)
421
- and obj != Command
422
- and not inspect.isabstract(obj)
423
- ):
424
-
425
- command_name = self._get_command_name(obj)
426
- if self.register_loaded(cast(Type[Command], obj)):
427
- commands_loaded += 1
428
- loaded_commands.append(command_name)
429
- get_global_logger().debug(f"Loaded command: {command_name}")
430
- else:
431
- get_global_logger().debug(f"Skipped command: {command_name}")
432
-
433
- return {
434
- "success": True,
435
- "commands_loaded": commands_loaded,
436
- "loaded_commands": loaded_commands,
437
- "source": file_path,
438
- }
439
- else:
440
- error_msg = f"Failed to create module spec for: {file_path}"
441
- get_global_logger().error(error_msg)
442
- return {
443
- "success": False,
444
- "error": error_msg,
445
- "commands_loaded": 0,
446
- "source": file_path,
447
- }
448
-
449
- except Exception as e:
450
- error_msg = f"Error loading command from file {file_path}: {e}"
451
- get_global_logger().error(error_msg)
452
- return {
453
- "success": False,
454
- "error": error_msg,
455
- "commands_loaded": 0,
456
- "source": file_path,
457
- }
458
-
459
- def unload_command(self, command_name: str) -> Dict[str, Any]:
460
- """
461
- Unload a loaded command from registry.
462
-
463
- Args:
464
- command_name: Name of the command to unload
465
-
466
- Returns:
467
- Dictionary with unloading result information
468
- """
469
- get_global_logger().info(f"Unloading command: {command_name}")
470
-
471
- # Check if command exists in loaded commands
472
- if command_name not in self._loaded_commands:
473
- error_msg = (
474
- f"Command '{command_name}' is not a loaded command or does not exist"
475
- )
476
- get_global_logger().warning(error_msg)
477
- return {"success": False, "error": error_msg, "command_name": command_name}
478
-
479
- try:
480
- # Remove from loaded commands
481
- del self._loaded_commands[command_name]
482
-
483
- # Remove instance if exists
484
- if command_name in self._instances:
485
- del self._instances[command_name]
486
-
487
- get_global_logger().info(f"Successfully unloaded command: {command_name}")
488
- return {
489
- "success": True,
490
- "command_name": command_name,
491
- "message": f"Command '{command_name}' unloaded successfully",
492
- }
161
+ result = self._loader.load_command_from_source(source)
162
+
163
+ if result["success"]:
164
+ # Register loaded commands
165
+ for command_class in result["commands"]:
166
+ self.register_loaded(command_class)
167
+
168
+ return result
493
169
 
494
- except Exception as e:
495
- error_msg = f"Failed to unload command '{command_name}': {e}"
496
- get_global_logger().error(error_msg)
497
- return {"success": False, "error": error_msg, "command_name": command_name}
498
170
 
499
171
  def command_exists(self, command_name: str) -> bool:
500
172
  """
501
- Check if command exists with priority order.
173
+ Check if command exists.
502
174
 
503
175
  Args:
504
- command_name: Command name to check.
176
+ command_name: Name of the command
505
177
 
506
178
  Returns:
507
- True if command exists, False otherwise.
179
+ True if command exists, False otherwise
508
180
  """
509
- return (
510
- command_name in self._custom_commands
511
- or command_name in self._builtin_commands
512
- or command_name in self._loaded_commands
513
- )
181
+ return self._manager.command_exists(command_name, self._commands)
514
182
 
515
183
  def get_command(self, command_name: str) -> Type[Command]:
516
184
  """
517
- Get command class with priority order.
185
+ Get command class by name.
518
186
 
519
187
  Args:
520
- command_name: Command name.
188
+ command_name: Name of the command
521
189
 
522
190
  Returns:
523
- Command class.
191
+ Command class
524
192
 
525
193
  Raises:
526
- NotFoundError: If command is not found.
194
+ NotFoundError: If command not found
527
195
  """
528
- # Check in priority order: custom -> built-in -> loaded
529
- if command_name in self._custom_commands:
530
- return self._custom_commands[command_name]
531
- elif command_name in self._builtin_commands:
532
- return self._builtin_commands[command_name]
533
- elif command_name in self._loaded_commands:
534
- return self._loaded_commands[command_name]
535
- else:
536
- raise NotFoundError(f"Command '{command_name}' not found")
196
+ if command_name not in self._commands:
197
+ raise KeyError(f"Command '{command_name}' not found")
198
+ return self._commands[command_name]
537
199
 
538
200
  def get_command_instance(self, command_name: str) -> Command:
539
201
  """
540
- Get command instance by name. If instance doesn't exist, creates new one.
202
+ Get command instance by name.
541
203
 
542
204
  Args:
543
- command_name: Command name
205
+ command_name: Name of the command
544
206
 
545
207
  Returns:
546
208
  Command instance
547
209
 
548
210
  Raises:
549
- NotFoundError: If command is not found
211
+ NotFoundError: If command not found
550
212
  """
551
- if not self.command_exists(command_name):
552
- raise NotFoundError(f"Command '{command_name}' not found")
553
-
554
- # Return existing instance if available
555
- if command_name in self._instances:
556
- return self._instances[command_name]
557
-
558
- # Otherwise create new instance
559
- try:
560
- command_class = self.get_command(command_name)
561
- return command_class()
562
- except Exception as e:
563
- get_global_logger().error(f"Failed to create instance of '{command_name}': {e}")
564
- raise ValueError(
565
- f"Command '{command_name}' requires dependencies but was registered as class. Register an instance instead."
566
- ) from e
213
+ return self._manager.get_command_instance(command_name, self._commands, self._instances)
567
214
 
568
215
  def has_instance(self, command_name: str) -> bool:
569
216
  """
570
- Check if command has a registered instance.
217
+ Check if command has instance.
571
218
 
572
219
  Args:
573
- command_name: Command name
220
+ command_name: Name of the command
574
221
 
575
222
  Returns:
576
223
  True if command has instance, False otherwise
577
224
  """
578
- return command_name in self._instances
225
+ return self._manager.has_instance(command_name, self._instances)
579
226
 
580
227
  def get_all_commands(self) -> Dict[str, Type[Command]]:
581
228
  """
582
- Get all registered commands with priority order.
229
+ Get all registered commands.
583
230
 
584
231
  Returns:
585
- Dictionary with command names and their classes.
232
+ Dictionary of all commands
586
233
  """
587
- all_commands = {}
588
-
589
- # Add commands in priority order: custom -> built-in -> loaded
590
- # Custom commands override built-in and loaded
591
- all_commands.update(self._custom_commands)
592
-
593
- # Built-in commands (only if not overridden by custom)
594
- for name, command_class in self._builtin_commands.items():
595
- if name not in all_commands:
596
- all_commands[name] = command_class
597
-
598
- # Loaded commands (only if not overridden by custom or built-in)
599
- for name, command_class in self._loaded_commands.items():
600
- if name not in all_commands:
601
- all_commands[name] = command_class
602
-
603
- return all_commands
234
+ return self._commands
604
235
 
605
236
  def get_commands_by_type(self) -> Dict[str, Dict[str, Type[Command]]]:
606
237
  """
607
238
  Get commands grouped by type.
608
239
 
609
240
  Returns:
610
- Dictionary with commands grouped by type.
241
+ Dictionary of commands grouped by type
611
242
  """
612
- return {
613
- "custom": self._custom_commands,
614
- "builtin": self._builtin_commands,
615
- "loaded": self._loaded_commands,
616
- }
243
+ return self._manager.get_commands_by_type(self._commands, self._command_types)
617
244
 
618
245
  def get_all_metadata(self) -> Dict[str, Dict[str, Any]]:
619
246
  """
620
- Get metadata for all registered commands.
247
+ Get metadata for all commands.
621
248
 
622
249
  Returns:
623
- Dictionary with command names as keys and metadata as values.
250
+ Dictionary of command metadata
624
251
  """
625
- metadata = {}
626
-
627
- # Get all commands with priority order
628
- all_commands = self.get_all_commands()
629
-
630
- for command_name, command_class in all_commands.items():
631
- try:
632
- # Get command metadata
633
- if hasattr(command_class, "get_metadata"):
634
- metadata[command_name] = command_class.get_metadata()
635
- else:
636
- # Fallback metadata
637
- metadata[command_name] = {
638
- "name": command_name,
639
- "class": command_class.__name__,
640
- "module": command_class.__module__,
641
- "description": getattr(
642
- command_class, "__doc__", "No description available"
643
- ),
644
- }
645
- except Exception as e:
646
- get_global_logger().warning(
647
- f"Failed to get metadata for command '{command_name}': {e}"
648
- )
649
- metadata[command_name] = {
650
- "name": command_name,
651
- "error": f"Failed to get metadata: {str(e)}",
652
- }
653
-
654
- return metadata
252
+ return self._manager.get_all_metadata(self._commands, self._command_types)
655
253
 
656
254
  def clear(self) -> None:
657
- """
658
- Clear all registered commands.
659
- """
660
- get_global_logger().debug("Clearing all registered commands")
661
- self._builtin_commands.clear()
662
- self._custom_commands.clear()
663
- self._loaded_commands.clear()
664
- self._instances.clear()
665
-
666
- async def reload_system(self, config_path: Optional[str] = None, config_obj: Optional[Any] = None) -> Dict[str, Any]:
667
- """
668
- Universal method for system initialization and reload.
669
- This method should be used both at startup and during reload.
670
-
671
- Args:
672
- config_path: Path to configuration file. If None, uses default or existing path.
673
-
674
- Returns:
675
- Dictionary with initialization information.
676
- """
677
- get_global_logger().info(
678
- f"🔄 Starting system reload with config: {config_path or 'default'}"
679
- )
680
-
681
- # Step 1: Load configuration (preserve previous config for soft-fail)
682
- if config_obj is None:
683
- from mcp_proxy_adapter.config import get_config
684
- config_obj = get_config()
685
-
686
- previous_config = config_obj.get_all()
687
- try:
688
- if config_path:
689
- config_obj.load_from_file(config_path)
690
- get_global_logger().info(f"✅ Configuration loaded from: {config_path}")
691
- else:
692
- config_obj.load_config()
693
- get_global_logger().info("✅ Configuration loaded from default path")
694
-
695
- config_reloaded = True
696
- except Exception as e:
697
- get_global_logger().error(f"❌ Failed to load configuration: {e}")
698
- config_reloaded = False
699
-
700
- # Step 1.1: Validate configuration (soft-fail on reload)
701
- try:
702
- from mcp_proxy_adapter.core.config_validator import ConfigValidator
703
-
704
- validator = ConfigValidator()
705
- validator.config_data = config_obj.get_all()
706
- validation_results = validator.validate_config()
707
-
708
- # Check for errors
709
- errors = [r for r in validation_results if r.level == "error"]
710
- warnings = [r for r in validation_results if r.level == "warning"]
711
-
712
- if errors:
713
- get_global_logger().error("⚠️ Configuration validation failed during reload:")
714
- for err in errors:
715
- get_global_logger().error(f" - {err.message}")
716
- # Do NOT exit on reload; restore previous configuration
717
- try:
718
- config_obj.config_data = previous_config
719
- config_reloaded = False
720
- get_global_logger().error("ℹ️ Restored previous configuration due to validation errors")
721
- except Exception as restore_ex:
722
- get_global_logger().error(f"❌ Failed to restore previous configuration: {restore_ex}")
723
- for warn in warnings:
724
- get_global_logger().warning(f"Config warning: {warn.message}")
725
- except Exception as e:
726
- get_global_logger().error(f"❌ Failed to validate configuration: {e}")
727
-
728
- # Step 2: Initialize logging with configuration
729
- try:
730
- from mcp_proxy_adapter.core.logging import setup_logging
731
-
732
- setup_logging()
733
- get_global_logger().info("✅ Logging initialized with configuration")
734
- except Exception as e:
735
- get_global_logger().error(f"❌ Failed to initialize logging: {e}")
736
-
737
- # Step 2.5: Reload protocol manager configuration
738
- try:
739
- from mcp_proxy_adapter.core.protocol_manager import protocol_manager
740
-
741
- if protocol_manager is not None:
742
- protocol_manager.reload_config()
743
- get_global_logger().info("✅ Protocol manager configuration reloaded")
744
- else:
745
- get_global_logger().debug("ℹ️ Protocol manager is None, skipping reload")
746
- except Exception as e:
747
- get_global_logger().error(f"❌ Failed to reload protocol manager: {e}")
748
-
749
- # Step 3: Clear all commands (always clear for consistency)
750
- self.clear()
751
-
752
- # Step 4: Execute before init hooks
753
- try:
754
- hooks.execute_before_init_hooks()
755
- except Exception as e:
756
- get_global_logger().error(f"❌ Failed to execute before init hooks: {e}")
757
-
758
- # Step 5: Register built-in commands
759
- try:
760
- from mcp_proxy_adapter.commands.builtin_commands import (
761
- register_builtin_commands,
762
- )
763
-
764
- builtin_commands_count = register_builtin_commands()
765
- except Exception as e:
766
- get_global_logger().error(f"❌ Failed to register built-in commands: {e}")
767
- builtin_commands_count = 0
768
-
769
- # Step 6: Execute custom commands hooks
770
- try:
771
- custom_commands_count = hooks.execute_custom_commands_hooks(self)
772
- except Exception as e:
773
- get_global_logger().error(f"❌ Failed to execute custom commands hooks: {e}")
774
- custom_commands_count = 0
775
-
776
- # Step 7: Load all commands (built-in, custom, loadable)
777
- try:
778
- # TODO: Implement _load_all_commands method
779
- load_result = {"remote_commands": 0, "loaded_commands": 0}
780
- remote_commands_count = load_result.get("remote_commands", 0)
781
- loaded_commands_count = load_result.get("loaded_commands", 0)
782
- except Exception as e:
783
- get_global_logger().error(f"❌ Failed to load commands: {e}")
784
- remote_commands_count = 0
785
- loaded_commands_count = 0
786
-
787
- # Step 8: Execute after init hooks
788
- try:
789
- hooks.execute_after_init_hooks()
790
- except Exception as e:
791
- get_global_logger().error(f"❌ Failed to execute after init hooks: {e}")
792
-
793
- # Step 9: Register with proxy if enabled
794
- proxy_registration_success = False
795
- try:
796
- from mcp_proxy_adapter.core.proxy_registration import (
797
- register_with_proxy,
798
- initialize_proxy_registration,
799
- )
800
-
801
- # Initialize proxy registration manager with current config
802
- initialize_proxy_registration(config_obj.get_all())
803
-
804
- # Get server configuration with proper URL resolution logic
805
- server_config = config_obj.get("server", {})
806
- server_host = server_config.get("host", "0.0.0.0")
807
- server_port = server_config.get("port", 8000)
808
-
809
- # Get registration configuration for public host/port overrides
810
- # First check server config, then registration config
811
- public_host = config_obj.get("server.public_host")
812
- public_port = config_obj.get("server.public_port")
813
-
814
- # Fallback to registration config if not found in server
815
- if not public_host or not public_port:
816
- reg_cfg = config_obj.get("registration", config_obj.get("proxy_registration", {}))
817
- public_host = public_host or reg_cfg.get("public_host")
818
- public_port = public_port or reg_cfg.get("public_port")
819
-
820
- # Determine protocol based on new configuration structure
821
- protocol = config_obj.get("server.protocol", "http")
822
- verify_client = config_obj.get("transport.verify_client", False)
823
- ssl_enabled = protocol in ["https", "mtls"] or verify_client
824
- protocol = "https" if ssl_enabled else "http"
825
-
826
- # Resolve host and port (same logic as in app.py)
827
- import os
828
- docker_host_addr = os.getenv("DOCKER_HOST_ADDR", "172.17.0.1")
829
- resolved_host = public_host or (docker_host_addr if server_host == "0.0.0.0" else server_host)
830
- resolved_port = public_port or server_port
831
-
832
- server_url = f"{protocol}://{resolved_host}:{resolved_port}"
833
-
834
- get_global_logger().info(f"🔍 Proxy registration URL resolved: {server_url}")
835
-
836
- # Attempt proxy registration
837
- proxy_registration_success = await register_with_proxy(server_url)
838
- if proxy_registration_success:
839
- get_global_logger().info(
840
- "✅ Proxy registration completed successfully during system reload"
841
- )
842
- else:
843
- get_global_logger().info(
844
- "ℹ️ Proxy registration is disabled or failed during system reload"
845
- )
846
-
847
- except Exception as e:
848
- get_global_logger().error(f"❌ Failed to register with proxy during system reload: {e}")
849
-
850
- # Get final counts
851
- total_commands = len(self.get_all_commands())
852
-
853
- result = {
854
- "config_reloaded": config_reloaded,
855
- "builtin_commands": builtin_commands_count,
856
- "custom_commands": custom_commands_count,
857
- "loaded_commands": loaded_commands_count,
858
- "remote_commands": remote_commands_count,
859
- "total_commands": total_commands,
860
- "proxy_registration_success": proxy_registration_success,
861
- }
862
-
863
- get_global_logger().info(f"✅ System reload completed: {result}")
864
- return result
865
-
866
- def _load_all_commands(self) -> Dict[str, Any]:
867
- """
868
- Universal command loader - handles all command types.
869
-
870
- Returns:
871
- Dictionary with loading results
872
- """
873
- try:
874
- remote_commands = 0
875
- loaded_commands = 0
876
-
877
- # 1. Load commands from directory (if configured)
878
- commands_directory = config.get("commands.commands_directory")
879
- if commands_directory and os.path.exists(commands_directory):
880
- get_global_logger().info(f"Loading commands from directory: {commands_directory}")
881
- for file_path in Path(commands_directory).glob("*_command.py"):
882
- try:
883
- result = self.load_command_from_source(str(file_path))
884
- if result.get("success"):
885
- loaded_commands += result.get("commands_loaded", 0)
886
- except Exception as e:
887
- get_global_logger().error(f"Failed to load command from {file_path}: {e}")
888
-
889
- # 2. Load commands from plugin servers (if configured)
890
- plugin_servers = config.get("commands.plugin_servers", [])
891
- if plugin_servers:
892
- get_global_logger().info(
893
- f"Loading commands from {len(plugin_servers)} plugin servers"
894
- )
895
- for server_url in plugin_servers:
896
- try:
897
- # Load catalog from server
898
- from mcp_proxy_adapter.commands.catalog_manager import (
899
- CatalogManager,
900
- )
901
-
902
- catalog_manager = CatalogManager("./catalog")
903
- server_catalog = catalog_manager.get_catalog_from_server(
904
- server_url
905
- )
906
-
907
- # Load each command from catalog
908
- for command_name, server_cmd in server_catalog.items():
909
- try:
910
- result = self.load_command_from_source(command_name)
911
- if result.get("success"):
912
- remote_commands += result.get("commands_loaded", 0)
913
- except Exception as e:
914
- get_global_logger().error(
915
- f"Failed to load command {command_name}: {e}"
916
- )
917
-
918
- except Exception as e:
919
- get_global_logger().error(f"Failed to load from server {server_url}: {e}")
920
-
921
- return {
922
- "remote_commands": remote_commands,
923
- "loaded_commands": loaded_commands,
924
- }
925
-
926
- except Exception as e:
927
- get_global_logger().error(f"Failed to load all commands: {e}")
928
- return {"remote_commands": 0, "loaded_commands": 0, "error": str(e)}
255
+ """Clear all commands and instances."""
256
+ self._manager.clear(self._commands, self._instances, self._command_types)
257
+ self.logger.info("Cleared all commands")
929
258
 
930
259
  def get_all_commands_info(self) -> Dict[str, Any]:
931
260
  """
932
- Get information about all registered commands.
261
+ Get comprehensive information about all commands.
933
262
 
934
263
  Returns:
935
264
  Dictionary with command information
936
265
  """
937
- commands_info = {}
938
-
939
- # Get all commands
940
- all_commands = self.get_all_commands()
941
-
942
- for command_name, command_class in all_commands.items():
943
- try:
944
- # Get command metadata
945
- metadata = command_class.get_metadata()
946
-
947
- # Get command schema
948
- schema = command_class.get_schema()
949
-
950
- commands_info[command_name] = {
951
- "name": command_name,
952
- "metadata": metadata,
953
- "schema": schema,
954
- "type": self._get_command_type(command_name),
955
- }
956
-
957
- except Exception as e:
958
- get_global_logger().warning(f"Failed to get info for command {command_name}: {e}")
959
- commands_info[command_name] = {
960
- "name": command_name,
961
- "error": str(e),
962
- "type": self._get_command_type(command_name),
963
- }
964
-
965
- return {"commands": commands_info, "total": len(commands_info)}
266
+ return self._info.get_all_commands_info(self._commands, self._command_types)
966
267
 
967
268
  def get_command_info(self, command_name: str) -> Optional[Dict[str, Any]]:
968
269
  """
969
- Get information about a specific command.
270
+ Get detailed information about a specific command.
970
271
 
971
272
  Args:
972
273
  command_name: Name of the command
@@ -974,57 +275,24 @@ class CommandRegistry:
974
275
  Returns:
975
276
  Dictionary with command information or None if not found
976
277
  """
977
- try:
978
- # Check if command exists
979
- if not self.command_exists(command_name):
980
- return None
981
-
982
- # Get command class
983
- command_class = self.get_command(command_name)
984
-
985
- # Get command metadata
986
- metadata = command_class.get_metadata()
987
-
988
- # Get command schema
989
- schema = command_class.get_schema()
990
-
991
- return {
992
- "name": command_name,
993
- "metadata": metadata,
994
- "schema": schema,
995
- "type": self._get_command_type(command_name),
996
- }
997
-
998
- except Exception as e:
999
- get_global_logger().warning(f"Failed to get info for command {command_name}: {e}")
1000
- return {
1001
- "name": command_name,
1002
- "error": str(e),
1003
- "type": self._get_command_type(command_name),
1004
- }
278
+ if command_name not in self._commands:
279
+ return None
280
+
281
+ return self._info.get_command_info(
282
+ command_name,
283
+ self._commands[command_name],
284
+ self._command_types
285
+ )
1005
286
 
1006
- def _get_command_type(self, command_name: str) -> str:
287
+ def _load_all_commands(self) -> Dict[str, int]:
1007
288
  """
1008
- Get the type of a command (built-in, custom, or loaded).
1009
-
1010
- Args:
1011
- command_name: Name of the command
289
+ Load all commands from configured directories.
1012
290
 
1013
291
  Returns:
1014
- Command type string
292
+ Dictionary with loading statistics
1015
293
  """
1016
- if command_name in self._custom_commands:
1017
- return "custom"
1018
- elif command_name in self._builtin_commands:
1019
- return "built-in"
1020
- elif command_name in self._loaded_commands:
1021
- return "loaded"
1022
- else:
1023
- return "unknown"
294
+ return self._manager._load_all_commands(self._commands, self._command_types)
1024
295
 
1025
296
 
1026
- # Global command registry instance
297
+ # Global registry instance
1027
298
  registry = CommandRegistry()
1028
-
1029
- # Remove automatic command discovery - use reload_system instead
1030
- # This prevents duplication of loading logic