mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.30__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 +10 -9
- 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.30.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.30.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.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.30.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/top_level.txt +0 -0
|
@@ -23,44 +23,8 @@ class RegistrationStatusCommandResult(SuccessResult):
|
|
|
23
23
|
status: Dict[str, Any]
|
|
24
24
|
message: str = "Registration status retrieved successfully"
|
|
25
25
|
|
|
26
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
27
|
-
"""Convert result to dictionary."""
|
|
28
|
-
return {
|
|
29
|
-
"success": True,
|
|
30
|
-
"status": self.status,
|
|
31
|
-
"message": self.message,
|
|
32
|
-
}
|
|
33
26
|
|
|
34
27
|
@classmethod
|
|
35
|
-
def get_schema(cls) -> Dict[str, Any]:
|
|
36
|
-
"""Get JSON schema for result."""
|
|
37
|
-
return {
|
|
38
|
-
"type": "object",
|
|
39
|
-
"properties": {
|
|
40
|
-
"success": {
|
|
41
|
-
"type": "boolean",
|
|
42
|
-
"description": "Whether the command was successful",
|
|
43
|
-
},
|
|
44
|
-
"status": {
|
|
45
|
-
"type": "object",
|
|
46
|
-
"description": "Registration status information",
|
|
47
|
-
"properties": {
|
|
48
|
-
"state": {"type": "string", "description": "Current registration state"},
|
|
49
|
-
"server_key": {"type": "string", "description": "Server key if registered"},
|
|
50
|
-
"last_attempt": {"type": "number", "description": "Timestamp of last registration attempt"},
|
|
51
|
-
"last_success": {"type": "number", "description": "Timestamp of last successful registration"},
|
|
52
|
-
"last_error": {"type": "string", "description": "Last error message"},
|
|
53
|
-
"attempt_count": {"type": "integer", "description": "Total registration attempts"},
|
|
54
|
-
"success_count": {"type": "integer", "description": "Total successful registrations"},
|
|
55
|
-
"heartbeat_enabled": {"type": "boolean", "description": "Whether heartbeat is enabled"},
|
|
56
|
-
"heartbeat_interval": {"type": "integer", "description": "Heartbeat interval in seconds"},
|
|
57
|
-
"thread_alive": {"type": "boolean", "description": "Whether registration thread is alive"},
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
"message": {"type": "string", "description": "Result message"},
|
|
61
|
-
},
|
|
62
|
-
"required": ["success", "status", "message"],
|
|
63
|
-
}
|
|
64
28
|
|
|
65
29
|
|
|
66
30
|
class RegistrationStatusCommand(Command):
|
|
@@ -110,10 +74,3 @@ class RegistrationStatusCommand(Command):
|
|
|
110
74
|
)
|
|
111
75
|
|
|
112
76
|
@classmethod
|
|
113
|
-
def get_schema(cls) -> Dict[str, Any]:
|
|
114
|
-
"""Get JSON schema for command parameters."""
|
|
115
|
-
return {
|
|
116
|
-
"type": "object",
|
|
117
|
-
"properties": {},
|
|
118
|
-
"additionalProperties": False,
|
|
119
|
-
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Command registry package for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .command_registry import CommandRegistry
|
|
9
|
+
from .command_loader import CommandLoader
|
|
10
|
+
from .command_manager import CommandManager
|
|
11
|
+
from .command_info import CommandInfo
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CommandRegistry",
|
|
15
|
+
"CommandLoader",
|
|
16
|
+
"CommandManager",
|
|
17
|
+
"CommandInfo",
|
|
18
|
+
]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Command information utilities for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List, Optional, Type
|
|
9
|
+
|
|
10
|
+
from mcp_proxy_adapter.commands.base import Command
|
|
11
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CommandInfo:
|
|
15
|
+
"""Utilities for getting command information."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initialize command info utilities."""
|
|
19
|
+
self.logger = get_global_logger()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_command_info(self, command_name: str, command_class: Type[Command], command_types: Dict[str, str]) -> Optional[Dict[str, Any]]:
|
|
23
|
+
"""
|
|
24
|
+
Get detailed information about a specific command.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
command_name: Name of the command
|
|
28
|
+
command_class: Command class
|
|
29
|
+
command_types: Dictionary mapping command names to types
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Dictionary with command information or None if not found
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
cmd_type = command_types.get(command_name, "unknown")
|
|
36
|
+
|
|
37
|
+
info = {
|
|
38
|
+
"name": command_name,
|
|
39
|
+
"type": cmd_type,
|
|
40
|
+
"class": command_class.__name__,
|
|
41
|
+
"module": command_class.__module__,
|
|
42
|
+
"file": getattr(command_class, "__file__", None),
|
|
43
|
+
"description": getattr(command_class, "__doc__", None),
|
|
44
|
+
"methods": self._get_command_methods(command_class),
|
|
45
|
+
"attributes": self._get_command_attributes(command_class),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Get command-specific information
|
|
49
|
+
if hasattr(command_class, "get_schema"):
|
|
50
|
+
try:
|
|
51
|
+
info["schema"] = command_class.get_schema()
|
|
52
|
+
except Exception as e:
|
|
53
|
+
self.logger.warning(f"Failed to get schema for {command_name}: {e}")
|
|
54
|
+
|
|
55
|
+
return info
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
self.logger.error(f"Failed to get info for command {command_name}: {e}")
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
def _get_command_methods(self, command_class: Type[Command]) -> List[Dict[str, Any]]:
|
|
62
|
+
"""
|
|
63
|
+
Get information about command methods.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
command_class: Command class
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of method information
|
|
70
|
+
"""
|
|
71
|
+
methods = []
|
|
72
|
+
|
|
73
|
+
for name, method in command_class.__dict__.items():
|
|
74
|
+
if callable(method) and not name.startswith("_"):
|
|
75
|
+
methods.append({
|
|
76
|
+
"name": name,
|
|
77
|
+
"description": getattr(method, "__doc__", None),
|
|
78
|
+
"is_async": hasattr(method, "__code__") and method.__code__.co_flags & 0x80, # CO_ITERABLE_COROUTINE
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return methods
|
|
82
|
+
|
|
83
|
+
def _get_command_attributes(self, command_class: Type[Command]) -> Dict[str, Any]:
|
|
84
|
+
"""
|
|
85
|
+
Get information about command attributes.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
command_class: Command class
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Dictionary of attribute information
|
|
92
|
+
"""
|
|
93
|
+
attributes = {}
|
|
94
|
+
|
|
95
|
+
for name, value in command_class.__dict__.items():
|
|
96
|
+
if not callable(value) and not name.startswith("_"):
|
|
97
|
+
attributes[name] = {
|
|
98
|
+
"value": value,
|
|
99
|
+
"type": type(value).__name__,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return attributes
|
|
103
|
+
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Command loading utilities for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
import importlib.util
|
|
10
|
+
import inspect
|
|
11
|
+
import os
|
|
12
|
+
import tempfile
|
|
13
|
+
import urllib.parse
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from mcp_proxy_adapter.commands.base import Command
|
|
17
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import requests
|
|
21
|
+
REQUESTS_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
REQUESTS_AVAILABLE = False
|
|
24
|
+
get_global_logger().warning("requests library not available, HTTP/HTTPS loading will not work")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CommandLoader:
|
|
28
|
+
"""Loader for commands from various sources."""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
"""Initialize command loader."""
|
|
32
|
+
self.logger = get_global_logger()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _load_command_with_registry_check(self, source: str) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Load command with remote registry check.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
source: Local path or command name
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Dictionary with loading result information
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
from mcp_proxy_adapter.commands.catalog_manager import CatalogManager
|
|
47
|
+
from mcp_proxy_adapter.config import get_config
|
|
48
|
+
|
|
49
|
+
# Get configuration
|
|
50
|
+
config_obj = get_config()
|
|
51
|
+
|
|
52
|
+
# Get remote registry
|
|
53
|
+
plugin_servers = config_obj.get("commands.plugin_servers", [])
|
|
54
|
+
catalog_dir = "./catalog"
|
|
55
|
+
|
|
56
|
+
if plugin_servers:
|
|
57
|
+
# Initialize catalog manager
|
|
58
|
+
catalog_manager = CatalogManager(catalog_dir)
|
|
59
|
+
|
|
60
|
+
# Check if source is a command name in registry
|
|
61
|
+
if not os.path.exists(source) and not source.endswith("_command.py"):
|
|
62
|
+
# Try to find in remote registry
|
|
63
|
+
for server_url in plugin_servers:
|
|
64
|
+
try:
|
|
65
|
+
server_catalog = catalog_manager.get_catalog_from_server(
|
|
66
|
+
server_url
|
|
67
|
+
)
|
|
68
|
+
if source in server_catalog:
|
|
69
|
+
server_cmd = server_catalog[source]
|
|
70
|
+
# Download from registry
|
|
71
|
+
if catalog_manager._download_command(
|
|
72
|
+
source, server_cmd
|
|
73
|
+
):
|
|
74
|
+
source = str(
|
|
75
|
+
catalog_manager.commands_dir
|
|
76
|
+
/ f"{source}_command.py"
|
|
77
|
+
)
|
|
78
|
+
break
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.logger.warning(
|
|
81
|
+
f"Failed to check registry {server_url}: {e}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Load from local file
|
|
85
|
+
return self._load_command_from_file(source)
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
self.logger.error(f"Failed to load command with registry check: {e}")
|
|
89
|
+
return {"success": False, "commands_loaded": 0, "error": str(e)}
|
|
90
|
+
|
|
91
|
+
def _load_command_from_url(self, url: str) -> Dict[str, Any]:
|
|
92
|
+
"""
|
|
93
|
+
Load command from HTTP/HTTPS URL.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
url: URL to load command from
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Dictionary with loading result information
|
|
100
|
+
"""
|
|
101
|
+
if not REQUESTS_AVAILABLE:
|
|
102
|
+
error_msg = "requests library not available, cannot load from URL"
|
|
103
|
+
self.logger.error(error_msg)
|
|
104
|
+
return {
|
|
105
|
+
"success": False,
|
|
106
|
+
"error": error_msg,
|
|
107
|
+
"commands_loaded": 0,
|
|
108
|
+
"source": url,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Download command file
|
|
113
|
+
response = requests.get(url, timeout=30)
|
|
114
|
+
response.raise_for_status()
|
|
115
|
+
|
|
116
|
+
# Create temporary file
|
|
117
|
+
with tempfile.NamedTemporaryFile(
|
|
118
|
+
mode="w", suffix="_command.py", delete=False
|
|
119
|
+
) as temp_file:
|
|
120
|
+
temp_file.write(response.text)
|
|
121
|
+
temp_file_path = temp_file.name
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
# Load from temporary file
|
|
125
|
+
result = self._load_command_from_file(temp_file_path)
|
|
126
|
+
result["source"] = url
|
|
127
|
+
return result
|
|
128
|
+
finally:
|
|
129
|
+
# Clean up temporary file
|
|
130
|
+
os.unlink(temp_file_path)
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
self.logger.error(f"Failed to load command from URL {url}: {e}")
|
|
134
|
+
return {
|
|
135
|
+
"success": False,
|
|
136
|
+
"error": str(e),
|
|
137
|
+
"commands_loaded": 0,
|
|
138
|
+
"source": url,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def _load_command_from_file(self, file_path: str) -> Dict[str, Any]:
|
|
142
|
+
"""
|
|
143
|
+
Load command from local file.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
file_path: Path to command file
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Dictionary with loading result information
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
if not os.path.exists(file_path):
|
|
153
|
+
return {
|
|
154
|
+
"success": False,
|
|
155
|
+
"error": f"File not found: {file_path}",
|
|
156
|
+
"commands_loaded": 0,
|
|
157
|
+
"source": file_path,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Load module from file
|
|
161
|
+
spec = importlib.util.spec_from_file_location(
|
|
162
|
+
f"command_{Path(file_path).stem}", file_path
|
|
163
|
+
)
|
|
164
|
+
if spec is None or spec.loader is None:
|
|
165
|
+
return {
|
|
166
|
+
"success": False,
|
|
167
|
+
"error": f"Could not load module from {file_path}",
|
|
168
|
+
"commands_loaded": 0,
|
|
169
|
+
"source": file_path,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module = importlib.util.module_from_spec(spec)
|
|
173
|
+
spec.loader.exec_module(module)
|
|
174
|
+
|
|
175
|
+
# Find command classes in module
|
|
176
|
+
commands = []
|
|
177
|
+
for name, obj in inspect.getmembers(module):
|
|
178
|
+
if (
|
|
179
|
+
inspect.isclass(obj)
|
|
180
|
+
and issubclass(obj, Command)
|
|
181
|
+
and obj != Command
|
|
182
|
+
):
|
|
183
|
+
commands.append(obj)
|
|
184
|
+
|
|
185
|
+
if not commands:
|
|
186
|
+
return {
|
|
187
|
+
"success": False,
|
|
188
|
+
"error": f"No command classes found in {file_path}",
|
|
189
|
+
"commands_loaded": 0,
|
|
190
|
+
"source": file_path,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
"success": True,
|
|
195
|
+
"commands": commands,
|
|
196
|
+
"commands_loaded": len(commands),
|
|
197
|
+
"source": file_path,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
self.logger.error(f"Failed to load command from file {file_path}: {e}")
|
|
202
|
+
return {
|
|
203
|
+
"success": False,
|
|
204
|
+
"error": str(e),
|
|
205
|
+
"commands_loaded": 0,
|
|
206
|
+
"source": file_path,
|
|
207
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Command management utilities for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from mcp_proxy_adapter.commands.base import Command
|
|
11
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CommandManager:
|
|
15
|
+
"""Manager for command operations."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initialize command manager."""
|
|
19
|
+
self.logger = get_global_logger()
|
|
20
|
+
|
|
21
|
+
def _get_command_name(self, command_class: Type[Command]) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Get command name from command class.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
command_class: Command class
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Command name
|
|
30
|
+
"""
|
|
31
|
+
# Try to get name from class attribute
|
|
32
|
+
if hasattr(command_class, "name") and command_class.name:
|
|
33
|
+
return command_class.name
|
|
34
|
+
|
|
35
|
+
# Fallback to class name
|
|
36
|
+
class_name = command_class.__name__
|
|
37
|
+
if class_name.endswith("Command"):
|
|
38
|
+
return class_name[:-7].lower() # Remove "Command" suffix
|
|
39
|
+
return class_name.lower()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_command(self, command_name: str, commands: Dict[str, Type[Command]]) -> Type[Command]:
|
|
43
|
+
"""
|
|
44
|
+
Get command class by name.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
command_name: Name of the command
|
|
48
|
+
commands: Dictionary of registered commands
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Command class
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
NotFoundError: If command not found
|
|
55
|
+
"""
|
|
56
|
+
if command_name not in commands:
|
|
57
|
+
from mcp_proxy_adapter.core.errors import NotFoundError
|
|
58
|
+
raise NotFoundError(f"Command '{command_name}' not found")
|
|
59
|
+
return commands[command_name]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def clear(self, commands: Dict[str, Type[Command]], instances: Dict[str, Command], command_types: Dict[str, str]) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Clear all commands and instances.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
commands: Dictionary of registered commands
|
|
72
|
+
instances: Dictionary of command instances
|
|
73
|
+
command_types: Dictionary mapping command names to types
|
|
74
|
+
"""
|
|
75
|
+
commands.clear()
|
|
76
|
+
instances.clear()
|
|
77
|
+
command_types.clear()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _load_commands_from_directory(self, directory: str, commands: Dict[str, Type[Command]], command_types: Dict[str, str], cmd_type: str) -> int:
|
|
81
|
+
"""
|
|
82
|
+
Load commands from directory.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
directory: Directory to load commands from
|
|
86
|
+
commands: Dictionary of registered commands
|
|
87
|
+
command_types: Dictionary mapping command names to types
|
|
88
|
+
cmd_type: Type of commands being loaded
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Number of commands loaded
|
|
92
|
+
"""
|
|
93
|
+
loaded_count = 0
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
for root, dirs, files in os.walk(directory):
|
|
97
|
+
for file in files:
|
|
98
|
+
if file.endswith("_command.py"):
|
|
99
|
+
file_path = os.path.join(root, file)
|
|
100
|
+
try:
|
|
101
|
+
# Load command from file
|
|
102
|
+
from .command_loader import CommandLoader
|
|
103
|
+
loader = CommandLoader()
|
|
104
|
+
result = loader._load_command_from_file(file_path)
|
|
105
|
+
|
|
106
|
+
if result["success"]:
|
|
107
|
+
for command_class in result["commands"]:
|
|
108
|
+
command_name = self._get_command_name(command_class)
|
|
109
|
+
commands[command_name] = command_class
|
|
110
|
+
command_types[command_name] = cmd_type
|
|
111
|
+
loaded_count += 1
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
self.logger.warning(f"Failed to load command from {file_path}: {e}")
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
self.logger.error(f"Failed to load commands from directory {directory}: {e}")
|
|
118
|
+
|
|
119
|
+
return loaded_count
|