mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/__init__.py +9 -5
- mcp_proxy_adapter/__main__.py +1 -1
- mcp_proxy_adapter/api/app.py +227 -176
- mcp_proxy_adapter/api/handlers.py +68 -60
- mcp_proxy_adapter/api/middleware/__init__.py +7 -5
- mcp_proxy_adapter/api/middleware/base.py +19 -16
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
- mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
- mcp_proxy_adapter/api/middleware/factory.py +50 -52
- mcp_proxy_adapter/api/middleware/logging.py +46 -30
- mcp_proxy_adapter/api/middleware/performance.py +19 -16
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
- mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
- mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
- mcp_proxy_adapter/api/schemas.py +69 -43
- mcp_proxy_adapter/api/tool_integration.py +83 -63
- mcp_proxy_adapter/api/tools.py +60 -50
- mcp_proxy_adapter/commands/__init__.py +15 -6
- mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
- mcp_proxy_adapter/commands/base.py +108 -112
- mcp_proxy_adapter/commands/builtin_commands.py +28 -18
- mcp_proxy_adapter/commands/catalog_manager.py +394 -265
- mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
- mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
- mcp_proxy_adapter/commands/command_registry.py +275 -226
- mcp_proxy_adapter/commands/config_command.py +48 -33
- mcp_proxy_adapter/commands/dependency_container.py +22 -23
- mcp_proxy_adapter/commands/dependency_manager.py +65 -56
- mcp_proxy_adapter/commands/echo_command.py +15 -15
- mcp_proxy_adapter/commands/health_command.py +31 -29
- mcp_proxy_adapter/commands/help_command.py +97 -61
- mcp_proxy_adapter/commands/hooks.py +65 -49
- mcp_proxy_adapter/commands/key_management_command.py +148 -147
- mcp_proxy_adapter/commands/load_command.py +58 -40
- mcp_proxy_adapter/commands/plugins_command.py +80 -54
- mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
- mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
- mcp_proxy_adapter/commands/reload_command.py +43 -37
- mcp_proxy_adapter/commands/result.py +26 -33
- mcp_proxy_adapter/commands/role_test_command.py +26 -26
- mcp_proxy_adapter/commands/roles_management_command.py +176 -173
- mcp_proxy_adapter/commands/security_command.py +134 -122
- mcp_proxy_adapter/commands/settings_command.py +47 -56
- mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
- mcp_proxy_adapter/commands/token_management_command.py +129 -158
- mcp_proxy_adapter/commands/transport_management_command.py +41 -36
- mcp_proxy_adapter/commands/unload_command.py +42 -37
- mcp_proxy_adapter/config.py +36 -35
- mcp_proxy_adapter/core/__init__.py +19 -21
- mcp_proxy_adapter/core/app_factory.py +30 -9
- mcp_proxy_adapter/core/app_runner.py +81 -64
- mcp_proxy_adapter/core/auth_validator.py +176 -182
- mcp_proxy_adapter/core/certificate_utils.py +469 -426
- mcp_proxy_adapter/core/client.py +155 -126
- mcp_proxy_adapter/core/client_manager.py +60 -54
- mcp_proxy_adapter/core/client_security.py +120 -91
- mcp_proxy_adapter/core/config_converter.py +176 -143
- mcp_proxy_adapter/core/config_validator.py +12 -4
- mcp_proxy_adapter/core/crl_utils.py +21 -7
- mcp_proxy_adapter/core/errors.py +64 -20
- mcp_proxy_adapter/core/logging.py +34 -29
- mcp_proxy_adapter/core/mtls_asgi.py +29 -25
- mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
- mcp_proxy_adapter/core/protocol_manager.py +154 -104
- mcp_proxy_adapter/core/proxy_client.py +202 -144
- mcp_proxy_adapter/core/proxy_registration.py +7 -3
- mcp_proxy_adapter/core/role_utils.py +139 -125
- mcp_proxy_adapter/core/security_adapter.py +88 -77
- mcp_proxy_adapter/core/security_factory.py +50 -44
- mcp_proxy_adapter/core/security_integration.py +72 -24
- mcp_proxy_adapter/core/server_adapter.py +68 -64
- mcp_proxy_adapter/core/server_engine.py +71 -53
- mcp_proxy_adapter/core/settings.py +68 -58
- mcp_proxy_adapter/core/ssl_utils.py +69 -56
- mcp_proxy_adapter/core/transport_manager.py +72 -60
- mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
- mcp_proxy_adapter/core/utils.py +4 -2
- mcp_proxy_adapter/custom_openapi.py +107 -99
- mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
- mcp_proxy_adapter/examples/debug_request_state.py +38 -19
- mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
- mcp_proxy_adapter/examples/demo_client.py +48 -36
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
- mcp_proxy_adapter/examples/generate_certificates.py +31 -16
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
- mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
- mcp_proxy_adapter/examples/run_example.py +23 -5
- mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
- mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
- mcp_proxy_adapter/examples/run_security_tests.py +103 -41
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
- mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/security_test_client.py +196 -127
- mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
- mcp_proxy_adapter/examples/test_config.py +19 -4
- mcp_proxy_adapter/examples/test_config_generator.py +23 -7
- mcp_proxy_adapter/examples/test_examples.py +84 -56
- mcp_proxy_adapter/examples/universal_client.py +119 -62
- mcp_proxy_adapter/openapi.py +108 -115
- mcp_proxy_adapter/utils/config_generator.py +429 -274
- mcp_proxy_adapter/version.py +1 -2
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.6.dist-info/RECORD +144 -0
- mcp_proxy_adapter-6.3.6.dist-info/top_level.txt +2 -0
- mcp_proxy_adapter_issue_package/demonstrate_issue.py +178 -0
- mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
- mcp_proxy_adapter-6.3.4.dist-info/top_level.txt +0 -1
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/licenses/LICENSE +0 -0
@@ -13,45 +13,59 @@ class ConfigResult(SuccessResult):
|
|
13
13
|
"""
|
14
14
|
Config operation result.
|
15
15
|
"""
|
16
|
-
|
17
|
-
def __init__(
|
16
|
+
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
config: Dict[str, Any],
|
20
|
+
operation: str,
|
21
|
+
message: Optional[str] = None,
|
22
|
+
):
|
18
23
|
"""
|
19
24
|
Initialize config result.
|
20
|
-
|
25
|
+
|
21
26
|
Args:
|
22
27
|
config: Configuration values
|
23
28
|
operation: Operation performed
|
24
29
|
message: Optional message
|
25
30
|
"""
|
26
|
-
super().__init__(
|
27
|
-
"config": config,
|
28
|
-
|
29
|
-
}, message=message)
|
31
|
+
super().__init__(
|
32
|
+
data={"config": config, "operation": operation}, message=message
|
33
|
+
)
|
30
34
|
|
31
35
|
|
32
36
|
class ConfigCommand(Command):
|
33
37
|
"""
|
34
38
|
Command for managing service configuration.
|
35
39
|
"""
|
40
|
+
|
36
41
|
name = "config"
|
37
42
|
description = "Get or set configuration values"
|
38
43
|
result_class = ConfigResult
|
39
|
-
|
40
|
-
async def execute(
|
44
|
+
|
45
|
+
async def execute(
|
46
|
+
self,
|
47
|
+
operation: str = "get",
|
48
|
+
path: Optional[str] = None,
|
49
|
+
value: Any = None,
|
50
|
+
context: Optional[Dict] = None,
|
51
|
+
**kwargs,
|
52
|
+
) -> ConfigResult:
|
41
53
|
"""
|
42
54
|
Execute the command.
|
43
|
-
|
55
|
+
|
44
56
|
Args:
|
45
57
|
operation: Operation to perform (get, set)
|
46
58
|
path: Configuration path (dot notation)
|
47
59
|
value: Value to set (for set operation)
|
48
|
-
|
60
|
+
context: Optional context parameter passed by framework
|
61
|
+
**kwargs: Additional parameters
|
62
|
+
|
49
63
|
Returns:
|
50
64
|
Config operation result
|
51
65
|
"""
|
52
66
|
message = None
|
53
67
|
result_config = {}
|
54
|
-
|
68
|
+
|
55
69
|
if operation == "get":
|
56
70
|
if path:
|
57
71
|
# Get specific config value
|
@@ -59,8 +73,8 @@ class ConfigCommand(Command):
|
|
59
73
|
else:
|
60
74
|
# Get all config
|
61
75
|
result_config = config_instance.get_all()
|
62
|
-
message =
|
63
|
-
|
76
|
+
message = "Configuration retrieved successfully"
|
77
|
+
|
64
78
|
elif operation == "set":
|
65
79
|
if path and value is not None:
|
66
80
|
# Set config value
|
@@ -68,26 +82,26 @@ class ConfigCommand(Command):
|
|
68
82
|
# Save config
|
69
83
|
config_instance.save()
|
70
84
|
result_config = {path: value}
|
71
|
-
message =
|
85
|
+
message = "Configuration updated successfully"
|
72
86
|
else:
|
73
87
|
# Error - missing required parameters
|
74
|
-
raise ValueError(
|
75
|
-
|
88
|
+
raise ValueError(
|
89
|
+
"Both 'path' and 'value' are required for 'set' operation"
|
90
|
+
)
|
91
|
+
|
76
92
|
else:
|
77
93
|
# Invalid operation
|
78
|
-
raise ValueError(
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
)
|
85
|
-
|
94
|
+
raise ValueError(
|
95
|
+
f"Invalid operation: {operation}. Valid operations: get, set"
|
96
|
+
)
|
97
|
+
|
98
|
+
return ConfigResult(config=result_config, operation=operation, message=message)
|
99
|
+
|
86
100
|
@classmethod
|
87
101
|
def get_schema(cls) -> Dict[str, Any]:
|
88
102
|
"""
|
89
103
|
Returns JSON schema for command parameters validation.
|
90
|
-
|
104
|
+
|
91
105
|
Returns:
|
92
106
|
Dictionary with JSON schema.
|
93
107
|
"""
|
@@ -95,19 +109,20 @@ class ConfigCommand(Command):
|
|
95
109
|
"type": "object",
|
96
110
|
"properties": {
|
97
111
|
"operation": {
|
98
|
-
"type": "string",
|
112
|
+
"type": "string",
|
99
113
|
"enum": ["get", "set"],
|
100
114
|
"default": "get",
|
101
|
-
"description": "Operation to perform (get or set)"
|
115
|
+
"description": "Operation to perform (get or set)",
|
102
116
|
},
|
103
117
|
"path": {
|
104
118
|
"type": "string",
|
105
|
-
"description": "Configuration path in dot notation
|
119
|
+
"description": "Configuration path in dot notation "
|
120
|
+
"(e.g. 'server.host')",
|
106
121
|
},
|
107
122
|
"value": {
|
108
|
-
"description": "Value to set (required for 'set' operation)"
|
109
|
-
}
|
123
|
+
"description": "Value to set (required for " "'set' operation)"
|
124
|
+
},
|
110
125
|
},
|
111
126
|
"required": ["operation"],
|
112
|
-
"additionalProperties": False
|
113
|
-
}
|
127
|
+
"additionalProperties": False,
|
128
|
+
}
|
@@ -7,47 +7,47 @@ for command instances in the microservice.
|
|
7
7
|
|
8
8
|
from typing import Any, Dict, Optional, Type, TypeVar, cast
|
9
9
|
|
10
|
-
T = TypeVar(
|
10
|
+
T = TypeVar("T")
|
11
11
|
|
12
12
|
|
13
13
|
class DependencyContainer:
|
14
14
|
"""
|
15
15
|
Container for managing dependencies.
|
16
|
-
|
16
|
+
|
17
17
|
This class provides functionality to register, resolve, and manage
|
18
18
|
dependencies that can be injected into command instances.
|
19
19
|
"""
|
20
|
-
|
20
|
+
|
21
21
|
def __init__(self):
|
22
22
|
"""Initialize dependency container."""
|
23
23
|
self._dependencies: Dict[str, Any] = {}
|
24
24
|
self._factories: Dict[str, callable] = {}
|
25
25
|
self._singletons: Dict[str, Any] = {}
|
26
|
-
|
26
|
+
|
27
27
|
def register(self, name: str, instance: Any) -> None:
|
28
28
|
"""
|
29
29
|
Register a dependency instance with a given name.
|
30
|
-
|
30
|
+
|
31
31
|
Args:
|
32
32
|
name: The name to register the dependency under
|
33
33
|
instance: The dependency instance
|
34
34
|
"""
|
35
35
|
self._dependencies[name] = instance
|
36
|
-
|
36
|
+
|
37
37
|
def register_factory(self, name: str, factory: callable) -> None:
|
38
38
|
"""
|
39
39
|
Register a factory function that will be called to create the dependency.
|
40
|
-
|
40
|
+
|
41
41
|
Args:
|
42
42
|
name: The name to register the factory under
|
43
43
|
factory: A callable that creates the dependency
|
44
44
|
"""
|
45
45
|
self._factories[name] = factory
|
46
|
-
|
46
|
+
|
47
47
|
def register_singleton(self, name: str, factory: callable) -> None:
|
48
48
|
"""
|
49
49
|
Register a singleton factory that will create the instance only once.
|
50
|
-
|
50
|
+
|
51
51
|
Args:
|
52
52
|
name: The name to register the singleton under
|
53
53
|
factory: A callable that creates the singleton instance
|
@@ -55,56 +55,55 @@ class DependencyContainer:
|
|
55
55
|
self._factories[name] = factory
|
56
56
|
# Mark as singleton but don't create until requested
|
57
57
|
self._singletons[name] = None
|
58
|
-
|
58
|
+
|
59
59
|
def get(self, name: str) -> Any:
|
60
60
|
"""
|
61
61
|
Get a dependency by name.
|
62
|
-
|
62
|
+
|
63
63
|
Args:
|
64
64
|
name: The name of the dependency to get
|
65
|
-
|
65
|
+
|
66
66
|
Returns:
|
67
67
|
The dependency instance
|
68
|
-
|
68
|
+
|
69
69
|
Raises:
|
70
70
|
KeyError: If the dependency is not registered
|
71
71
|
"""
|
72
72
|
# Check for direct instance
|
73
73
|
if name in self._dependencies:
|
74
74
|
return self._dependencies[name]
|
75
|
-
|
75
|
+
|
76
76
|
# Check for singleton
|
77
77
|
if name in self._singletons:
|
78
78
|
# Create singleton if doesn't exist
|
79
79
|
if self._singletons[name] is None:
|
80
80
|
self._singletons[name] = self._factories[name]()
|
81
81
|
return self._singletons[name]
|
82
|
-
|
82
|
+
|
83
83
|
# Check for factory
|
84
84
|
if name in self._factories:
|
85
85
|
return self._factories[name]()
|
86
|
-
|
86
|
+
|
87
87
|
raise KeyError(f"Dependency '{name}' not registered")
|
88
|
-
|
88
|
+
|
89
89
|
def clear(self) -> None:
|
90
90
|
"""Clear all registered dependencies."""
|
91
91
|
self._dependencies.clear()
|
92
92
|
self._factories.clear()
|
93
93
|
self._singletons.clear()
|
94
|
-
|
94
|
+
|
95
95
|
def has(self, name: str) -> bool:
|
96
96
|
"""
|
97
97
|
Check if a dependency is registered.
|
98
|
-
|
98
|
+
|
99
99
|
Args:
|
100
100
|
name: The name of the dependency
|
101
|
-
|
101
|
+
|
102
102
|
Returns:
|
103
103
|
True if the dependency is registered, False otherwise
|
104
104
|
"""
|
105
|
-
return
|
106
|
-
name in self._factories)
|
105
|
+
return name in self._dependencies or name in self._factories
|
107
106
|
|
108
107
|
|
109
108
|
# Global dependency container instance
|
110
|
-
container = DependencyContainer()
|
109
|
+
container = DependencyContainer()
|
@@ -19,12 +19,12 @@ class DependencyManager:
|
|
19
19
|
"""
|
20
20
|
Manages plugin dependencies installation and verification.
|
21
21
|
"""
|
22
|
-
|
22
|
+
|
23
23
|
def __init__(self):
|
24
24
|
"""Initialize dependency manager."""
|
25
25
|
self._installed_packages: Dict[str, str] = {}
|
26
26
|
self._load_installed_packages()
|
27
|
-
|
27
|
+
|
28
28
|
def _load_installed_packages(self) -> None:
|
29
29
|
"""Load list of currently installed packages."""
|
30
30
|
try:
|
@@ -33,39 +33,41 @@ class DependencyManager:
|
|
33
33
|
self._installed_packages[dist.project_name.lower()] = dist.version
|
34
34
|
except Exception as e:
|
35
35
|
logger.warning(f"Failed to load installed packages: {e}")
|
36
|
-
|
37
|
-
def check_dependencies(
|
36
|
+
|
37
|
+
def check_dependencies(
|
38
|
+
self, dependencies: List[str]
|
39
|
+
) -> Tuple[bool, List[str], List[str]]:
|
38
40
|
"""
|
39
41
|
Check if dependencies are satisfied.
|
40
|
-
|
42
|
+
|
41
43
|
Args:
|
42
44
|
dependencies: List of dependency names
|
43
|
-
|
45
|
+
|
44
46
|
Returns:
|
45
47
|
Tuple of (all_satisfied, missing_deps, installed_deps)
|
46
48
|
"""
|
47
49
|
if not dependencies:
|
48
50
|
return True, [], []
|
49
|
-
|
51
|
+
|
50
52
|
missing_deps = []
|
51
53
|
installed_deps = []
|
52
|
-
|
54
|
+
|
53
55
|
for dep in dependencies:
|
54
56
|
if self._is_dependency_satisfied(dep):
|
55
57
|
installed_deps.append(dep)
|
56
58
|
else:
|
57
59
|
missing_deps.append(dep)
|
58
|
-
|
60
|
+
|
59
61
|
all_satisfied = len(missing_deps) == 0
|
60
62
|
return all_satisfied, missing_deps, installed_deps
|
61
|
-
|
63
|
+
|
62
64
|
def _is_dependency_satisfied(self, dependency: str) -> bool:
|
63
65
|
"""
|
64
66
|
Check if a single dependency is satisfied.
|
65
|
-
|
67
|
+
|
66
68
|
Args:
|
67
69
|
dependency: Dependency name or spec
|
68
|
-
|
70
|
+
|
69
71
|
Returns:
|
70
72
|
True if dependency is satisfied, False otherwise
|
71
73
|
"""
|
@@ -75,38 +77,46 @@ class DependencyManager:
|
|
75
77
|
return True
|
76
78
|
except ImportError:
|
77
79
|
pass
|
78
|
-
|
80
|
+
|
79
81
|
# Check if it's installed as a package
|
80
82
|
try:
|
81
83
|
pkg_resources.require(dependency)
|
82
84
|
return True
|
83
85
|
except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict):
|
84
86
|
pass
|
85
|
-
|
87
|
+
|
86
88
|
# Check installed packages cache
|
87
|
-
dep_name =
|
89
|
+
dep_name = (
|
90
|
+
dependency.split("==")[0]
|
91
|
+
.split(">=")[0]
|
92
|
+
.split("<=")[0]
|
93
|
+
.split("!=")[0]
|
94
|
+
.split("~=")[0]
|
95
|
+
)
|
88
96
|
if dep_name.lower() in self._installed_packages:
|
89
97
|
return True
|
90
|
-
|
98
|
+
|
91
99
|
return False
|
92
|
-
|
93
|
-
def install_dependencies(
|
100
|
+
|
101
|
+
def install_dependencies(
|
102
|
+
self, dependencies: List[str], user_install: bool = False
|
103
|
+
) -> Tuple[bool, List[str], List[str]]:
|
94
104
|
"""
|
95
105
|
Install dependencies using pip.
|
96
|
-
|
106
|
+
|
97
107
|
Args:
|
98
108
|
dependencies: List of dependency names to install
|
99
109
|
user_install: Whether to install for current user only
|
100
|
-
|
110
|
+
|
101
111
|
Returns:
|
102
112
|
Tuple of (success, installed_deps, failed_deps)
|
103
113
|
"""
|
104
114
|
if not dependencies:
|
105
115
|
return True, [], []
|
106
|
-
|
116
|
+
|
107
117
|
installed_deps = []
|
108
118
|
failed_deps = []
|
109
|
-
|
119
|
+
|
110
120
|
for dep in dependencies:
|
111
121
|
try:
|
112
122
|
if self._install_single_dependency(dep, user_install):
|
@@ -118,92 +128,91 @@ class DependencyManager:
|
|
118
128
|
except Exception as e:
|
119
129
|
failed_deps.append(dep)
|
120
130
|
logger.error(f"Error installing dependency {dep}: {e}")
|
121
|
-
|
131
|
+
|
122
132
|
success = len(failed_deps) == 0
|
123
|
-
|
133
|
+
|
124
134
|
# Reload installed packages cache
|
125
135
|
if success:
|
126
136
|
self._load_installed_packages()
|
127
|
-
|
137
|
+
|
128
138
|
return success, installed_deps, failed_deps
|
129
|
-
|
130
|
-
def _install_single_dependency(
|
139
|
+
|
140
|
+
def _install_single_dependency(
|
141
|
+
self, dependency: str, user_install: bool = False
|
142
|
+
) -> bool:
|
131
143
|
"""
|
132
144
|
Install a single dependency using pip.
|
133
|
-
|
145
|
+
|
134
146
|
Args:
|
135
147
|
dependency: Dependency name or spec
|
136
148
|
user_install: Whether to install for current user only
|
137
|
-
|
149
|
+
|
138
150
|
Returns:
|
139
151
|
True if installation successful, False otherwise
|
140
152
|
"""
|
141
153
|
try:
|
142
154
|
# Build pip command
|
143
155
|
cmd = [sys.executable, "-m", "pip", "install"]
|
144
|
-
|
156
|
+
|
145
157
|
if user_install:
|
146
158
|
cmd.append("--user")
|
147
|
-
|
159
|
+
|
148
160
|
# Add quiet flag to reduce output
|
149
161
|
cmd.append("--quiet")
|
150
|
-
|
162
|
+
|
151
163
|
# Add dependency
|
152
164
|
cmd.append(dependency)
|
153
|
-
|
165
|
+
|
154
166
|
logger.debug(f"Installing dependency: {' '.join(cmd)}")
|
155
|
-
|
167
|
+
|
156
168
|
# Run pip install
|
157
169
|
result = subprocess.run(
|
158
|
-
cmd,
|
159
|
-
capture_output=True,
|
160
|
-
text=True,
|
161
|
-
timeout=300 # 5 minutes timeout
|
170
|
+
cmd, capture_output=True, text=True, timeout=300 # 5 minutes timeout
|
162
171
|
)
|
163
|
-
|
172
|
+
|
164
173
|
if result.returncode == 0:
|
165
174
|
logger.debug(f"Successfully installed {dependency}")
|
166
175
|
return True
|
167
176
|
else:
|
168
177
|
logger.error(f"Failed to install {dependency}: {result.stderr}")
|
169
178
|
return False
|
170
|
-
|
179
|
+
|
171
180
|
except subprocess.TimeoutExpired:
|
172
181
|
logger.error(f"Timeout while installing {dependency}")
|
173
182
|
return False
|
174
183
|
except Exception as e:
|
175
184
|
logger.error(f"Error installing {dependency}: {e}")
|
176
185
|
return False
|
177
|
-
|
186
|
+
|
178
187
|
def verify_installation(self, dependencies: List[str]) -> Tuple[bool, List[str]]:
|
179
188
|
"""
|
180
189
|
Verify that dependencies are properly installed.
|
181
|
-
|
190
|
+
|
182
191
|
Args:
|
183
192
|
dependencies: List of dependencies to verify
|
184
|
-
|
193
|
+
|
185
194
|
Returns:
|
186
195
|
Tuple of (all_verified, failed_verifications)
|
187
196
|
"""
|
188
197
|
if not dependencies:
|
189
198
|
return True, []
|
190
|
-
|
199
|
+
|
191
200
|
failed_verifications = []
|
192
|
-
|
201
|
+
|
193
202
|
for dep in dependencies:
|
194
203
|
if not self._is_dependency_satisfied(dep):
|
195
204
|
failed_verifications.append(dep)
|
196
|
-
|
205
|
+
|
197
206
|
all_verified = len(failed_verifications) == 0
|
198
207
|
return all_verified, failed_verifications
|
199
|
-
|
208
|
+
|
200
209
|
def get_dependency_info(self, dependency: str) -> Dict[str, Any]:
|
201
210
|
"""
|
202
211
|
Get information about a dependency.
|
203
|
-
|
212
|
+
|
204
213
|
Args:
|
205
214
|
dependency: Dependency name
|
206
|
-
|
215
|
+
|
207
216
|
Returns:
|
208
217
|
Dictionary with dependency information
|
209
218
|
"""
|
@@ -211,9 +220,9 @@ class DependencyManager:
|
|
211
220
|
"name": dependency,
|
212
221
|
"installed": False,
|
213
222
|
"version": None,
|
214
|
-
"importable": False
|
223
|
+
"importable": False,
|
215
224
|
}
|
216
|
-
|
225
|
+
|
217
226
|
# Check if it's installed
|
218
227
|
try:
|
219
228
|
dist = pkg_resources.get_distribution(dependency)
|
@@ -221,20 +230,20 @@ class DependencyManager:
|
|
221
230
|
info["version"] = dist.version
|
222
231
|
except pkg_resources.DistributionNotFound:
|
223
232
|
pass
|
224
|
-
|
233
|
+
|
225
234
|
# Check if it's importable
|
226
235
|
try:
|
227
236
|
importlib.import_module(dependency)
|
228
237
|
info["importable"] = True
|
229
238
|
except ImportError:
|
230
239
|
pass
|
231
|
-
|
240
|
+
|
232
241
|
return info
|
233
|
-
|
242
|
+
|
234
243
|
def list_installed_dependencies(self) -> Dict[str, str]:
|
235
244
|
"""
|
236
245
|
Get list of all installed packages.
|
237
|
-
|
246
|
+
|
238
247
|
Returns:
|
239
248
|
Dictionary mapping package names to versions
|
240
249
|
"""
|
@@ -242,4 +251,4 @@ class DependencyManager:
|
|
242
251
|
|
243
252
|
|
244
253
|
# Global instance
|
245
|
-
dependency_manager = DependencyManager()
|
254
|
+
dependency_manager = DependencyManager()
|
@@ -14,13 +14,13 @@ from mcp_proxy_adapter.commands.result import SuccessResult
|
|
14
14
|
|
15
15
|
class EchoCommandResult(SuccessResult):
|
16
16
|
"""Result for echo command."""
|
17
|
-
|
17
|
+
|
18
18
|
def __init__(self, message: str, timestamp: Optional[str] = None):
|
19
19
|
data = {"message": message}
|
20
20
|
if timestamp:
|
21
21
|
data["timestamp"] = timestamp
|
22
22
|
super().__init__(data=data, message=message)
|
23
|
-
|
23
|
+
|
24
24
|
@classmethod
|
25
25
|
def get_schema(cls) -> Dict[str, Any]:
|
26
26
|
"""Get JSON schema for result."""
|
@@ -32,18 +32,18 @@ class EchoCommandResult(SuccessResult):
|
|
32
32
|
"type": "object",
|
33
33
|
"properties": {
|
34
34
|
"message": {"type": "string"},
|
35
|
-
"timestamp": {"type": "string", "nullable": True}
|
36
|
-
}
|
35
|
+
"timestamp": {"type": "string", "nullable": True},
|
36
|
+
},
|
37
37
|
},
|
38
|
-
"message": {"type": "string"}
|
38
|
+
"message": {"type": "string"},
|
39
39
|
},
|
40
|
-
"required": ["success", "data"]
|
40
|
+
"required": ["success", "data"],
|
41
41
|
}
|
42
42
|
|
43
43
|
|
44
44
|
class EchoCommand(Command):
|
45
45
|
"""Echo command for testing purposes."""
|
46
|
-
|
46
|
+
|
47
47
|
name = "echo"
|
48
48
|
version = "1.0.0"
|
49
49
|
descr = "Echo command for testing"
|
@@ -51,17 +51,17 @@ class EchoCommand(Command):
|
|
51
51
|
author = "Vasiliy Zdanovskiy"
|
52
52
|
email = "vasilyvz@gmail.com"
|
53
53
|
result_class = EchoCommandResult
|
54
|
-
|
54
|
+
|
55
55
|
async def execute(self, **kwargs) -> EchoCommandResult:
|
56
56
|
"""Execute echo command."""
|
57
57
|
message = kwargs.get("message", "Hello, World!")
|
58
58
|
timestamp = kwargs.get("timestamp")
|
59
|
-
|
59
|
+
|
60
60
|
# Simulate some processing time
|
61
61
|
await asyncio.sleep(0.001)
|
62
|
-
|
62
|
+
|
63
63
|
return EchoCommandResult(message=message, timestamp=timestamp)
|
64
|
-
|
64
|
+
|
65
65
|
@classmethod
|
66
66
|
def get_schema(cls) -> Dict[str, Any]:
|
67
67
|
"""Get command schema."""
|
@@ -71,12 +71,12 @@ class EchoCommand(Command):
|
|
71
71
|
"message": {
|
72
72
|
"type": "string",
|
73
73
|
"description": "Message to echo",
|
74
|
-
"default": "Hello, World!"
|
74
|
+
"default": "Hello, World!",
|
75
75
|
},
|
76
76
|
"timestamp": {
|
77
77
|
"type": "string",
|
78
78
|
"description": "Optional timestamp",
|
79
|
-
"nullable": True
|
80
|
-
}
|
81
|
-
}
|
79
|
+
"nullable": True,
|
80
|
+
},
|
81
|
+
},
|
82
82
|
}
|