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.
- mcp_proxy_adapter/__init__.py +10 -0
- mcp_proxy_adapter/__main__.py +8 -21
- mcp_proxy_adapter/api/app.py +10 -913
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +243 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_manager.py +166 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +78 -199
- mcp_proxy_adapter/api/middleware/__init__.py +1 -44
- mcp_proxy_adapter/api/middleware/base.py +0 -42
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
- mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
- mcp_proxy_adapter/api/middleware/factory.py +0 -94
- mcp_proxy_adapter/api/middleware/logging.py +0 -112
- mcp_proxy_adapter/api/middleware/performance.py +0 -35
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
- mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
- mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +0 -61
- mcp_proxy_adapter/api/tool_integration.py +0 -117
- mcp_proxy_adapter/api/tools.py +0 -46
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +128 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +324 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
- mcp_proxy_adapter/client/proxy.py +45 -0
- mcp_proxy_adapter/commands/__init__.py +44 -28
- mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
- mcp_proxy_adapter/commands/base.py +19 -43
- mcp_proxy_adapter/commands/builtin_commands.py +0 -75
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +58 -928
- mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
- mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
- mcp_proxy_adapter/commands/command_registry.py +172 -904
- mcp_proxy_adapter/commands/config_command.py +0 -28
- mcp_proxy_adapter/commands/dependency_container.py +1 -70
- mcp_proxy_adapter/commands/dependency_manager.py +0 -128
- mcp_proxy_adapter/commands/echo_command.py +0 -34
- mcp_proxy_adapter/commands/health_command.py +0 -3
- mcp_proxy_adapter/commands/help_command.py +0 -159
- mcp_proxy_adapter/commands/hooks.py +0 -137
- mcp_proxy_adapter/commands/key_management_command.py +0 -25
- mcp_proxy_adapter/commands/load_command.py +7 -78
- mcp_proxy_adapter/commands/plugins_command.py +0 -16
- mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
- mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +0 -43
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +0 -80
- mcp_proxy_adapter/commands/result.py +25 -77
- mcp_proxy_adapter/commands/role_test_command.py +0 -44
- mcp_proxy_adapter/commands/roles_management_command.py +0 -199
- mcp_proxy_adapter/commands/security_command.py +0 -30
- mcp_proxy_adapter/commands/settings_command.py +0 -68
- mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
- mcp_proxy_adapter/commands/token_management_command.py +0 -1
- mcp_proxy_adapter/commands/transport_management_command.py +0 -20
- mcp_proxy_adapter/commands/unload_command.py +0 -71
- mcp_proxy_adapter/config.py +15 -626
- mcp_proxy_adapter/core/__init__.py +5 -39
- mcp_proxy_adapter/core/app_factory.py +14 -36
- mcp_proxy_adapter/core/app_runner.py +0 -27
- mcp_proxy_adapter/core/auth_validator.py +1 -93
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
- mcp_proxy_adapter/core/certificate_utils.py +64 -903
- mcp_proxy_adapter/core/client.py +0 -6
- mcp_proxy_adapter/core/client_manager.py +0 -19
- mcp_proxy_adapter/core/client_security.py +0 -2
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +195 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +112 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
- mcp_proxy_adapter/core/config_converter.py +0 -186
- mcp_proxy_adapter/core/config_validator.py +96 -1238
- mcp_proxy_adapter/core/errors.py +7 -42
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +2 -22
- mcp_proxy_adapter/core/mtls_asgi.py +0 -20
- mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
- mcp_proxy_adapter/core/mtls_proxy.py +0 -80
- mcp_proxy_adapter/core/mtls_server.py +3 -173
- mcp_proxy_adapter/core/protocol_manager.py +1 -191
- mcp_proxy_adapter/core/proxy/__init__.py +22 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +0 -1
- mcp_proxy_adapter/core/proxy_registration.py +36 -913
- mcp_proxy_adapter/core/role_utils.py +0 -308
- mcp_proxy_adapter/core/security_adapter.py +1 -36
- mcp_proxy_adapter/core/security_factory.py +1 -150
- mcp_proxy_adapter/core/security_integration.py +0 -33
- mcp_proxy_adapter/core/server_adapter.py +1 -40
- mcp_proxy_adapter/core/server_engine.py +2 -173
- mcp_proxy_adapter/core/settings.py +0 -127
- mcp_proxy_adapter/core/signal_handler.py +0 -65
- mcp_proxy_adapter/core/ssl_utils.py +19 -137
- mcp_proxy_adapter/core/transport_manager.py +0 -151
- mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
- mcp_proxy_adapter/core/utils.py +1 -182
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +211 -0
- mcp_proxy_adapter/core/validation/file_validator.py +73 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
- mcp_proxy_adapter/core/validation/security_validator.py +58 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +33 -652
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
- mcp_proxy_adapter/examples/check_config.py +0 -2
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/config_builder.py +13 -2
- mcp_proxy_adapter/examples/config_cli.py +0 -1
- mcp_proxy_adapter/examples/create_test_configs.py +0 -46
- mcp_proxy_adapter/examples/debug_request_state.py +0 -1
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
- mcp_proxy_adapter/examples/full_application/main.py +186 -150
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
- mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
- mcp_proxy_adapter/examples/generate_config.py +65 -11
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +0 -2
- mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
- mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +24 -1075
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
- mcp_proxy_adapter/examples/test_config.py +0 -3
- mcp_proxy_adapter/examples/test_config_builder.py +25 -405
- mcp_proxy_adapter/examples/test_examples.py +0 -1
- mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
- mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
- mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
- mcp_proxy_adapter/examples/universal_client.py +0 -6
- mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +70 -62
- mcp_proxy_adapter/openapi.py +0 -22
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
3
4
|
|
|
4
|
-
|
|
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
|
-
|
|
21
|
-
import
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
self.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
self.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
self.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Register
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
+
Register a command.
|
|
172
133
|
|
|
173
134
|
Args:
|
|
174
|
-
command: Command class or instance to register
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
command_name =
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
|
173
|
+
Check if command exists.
|
|
502
174
|
|
|
503
175
|
Args:
|
|
504
|
-
command_name:
|
|
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
|
|
185
|
+
Get command class by name.
|
|
518
186
|
|
|
519
187
|
Args:
|
|
520
|
-
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
|
|
194
|
+
NotFoundError: If command not found
|
|
527
195
|
"""
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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.
|
|
202
|
+
Get command instance by name.
|
|
541
203
|
|
|
542
204
|
Args:
|
|
543
|
-
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
|
|
211
|
+
NotFoundError: If command not found
|
|
550
212
|
"""
|
|
551
|
-
|
|
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
|
|
217
|
+
Check if command has instance.
|
|
571
218
|
|
|
572
219
|
Args:
|
|
573
|
-
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
|
|
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
|
|
229
|
+
Get all registered commands.
|
|
583
230
|
|
|
584
231
|
Returns:
|
|
585
|
-
Dictionary
|
|
232
|
+
Dictionary of all commands
|
|
586
233
|
"""
|
|
587
|
-
|
|
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
|
|
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
|
|
247
|
+
Get metadata for all commands.
|
|
621
248
|
|
|
622
249
|
Returns:
|
|
623
|
-
Dictionary
|
|
250
|
+
Dictionary of command metadata
|
|
624
251
|
"""
|
|
625
|
-
|
|
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
|
-
|
|
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
|
|
261
|
+
Get comprehensive information about all commands.
|
|
933
262
|
|
|
934
263
|
Returns:
|
|
935
264
|
Dictionary with command information
|
|
936
265
|
"""
|
|
937
|
-
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
|
287
|
+
def _load_all_commands(self) -> Dict[str, int]:
|
|
1007
288
|
"""
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
Args:
|
|
1011
|
-
command_name: Name of the command
|
|
289
|
+
Load all commands from configured directories.
|
|
1012
290
|
|
|
1013
291
|
Returns:
|
|
1014
|
-
|
|
292
|
+
Dictionary with loading statistics
|
|
1015
293
|
"""
|
|
1016
|
-
|
|
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
|
|
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
|