mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.0.1__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/__main__.py +27 -7
- mcp_proxy_adapter/api/app.py +209 -79
- mcp_proxy_adapter/api/handlers.py +16 -5
- mcp_proxy_adapter/api/middleware/__init__.py +14 -9
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
- mcp_proxy_adapter/api/middleware/factory.py +36 -12
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +84 -18
- mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
- mcp_proxy_adapter/commands/__init__.py +7 -1
- mcp_proxy_adapter/commands/base.py +7 -4
- mcp_proxy_adapter/commands/builtin_commands.py +8 -2
- mcp_proxy_adapter/commands/command_registry.py +8 -0
- mcp_proxy_adapter/commands/echo_command.py +81 -0
- mcp_proxy_adapter/commands/health_command.py +1 -1
- mcp_proxy_adapter/commands/help_command.py +21 -14
- mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
- mcp_proxy_adapter/commands/role_test_command.py +141 -0
- mcp_proxy_adapter/commands/security_command.py +488 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
- mcp_proxy_adapter/commands/token_management_command.py +1 -1
- mcp_proxy_adapter/config.py +323 -40
- mcp_proxy_adapter/core/app_factory.py +410 -0
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/certificate_utils.py +291 -73
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -0
- mcp_proxy_adapter/core/client_security.py +384 -0
- mcp_proxy_adapter/core/logging.py +8 -3
- mcp_proxy_adapter/core/mtls_asgi.py +156 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/protocol_manager.py +169 -10
- mcp_proxy_adapter/core/proxy_client.py +602 -0
- mcp_proxy_adapter/core/proxy_registration.py +299 -47
- mcp_proxy_adapter/core/security_adapter.py +12 -15
- mcp_proxy_adapter/core/security_integration.py +286 -0
- mcp_proxy_adapter/core/server_adapter.py +282 -0
- mcp_proxy_adapter/core/server_engine.py +270 -0
- mcp_proxy_adapter/core/ssl_utils.py +13 -12
- mcp_proxy_adapter/core/transport_manager.py +5 -5
- mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
- mcp_proxy_adapter/examples/__init__.py +13 -4
- mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/commands/__init__.py +5 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
- mcp_proxy_adapter/examples/debug_request_state.py +112 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
- mcp_proxy_adapter/examples/demo_client.py +275 -0
- mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
- mcp_proxy_adapter/examples/generate_certificates.py +177 -0
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
- mcp_proxy_adapter/examples/run_example.py +59 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
- mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
- mcp_proxy_adapter/examples/run_security_tests.py +544 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
- mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/security_test_client.py +782 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
- mcp_proxy_adapter/examples/test_config.py +148 -0
- mcp_proxy_adapter/examples/test_config_generator.py +86 -0
- mcp_proxy_adapter/examples/test_examples.py +281 -0
- mcp_proxy_adapter/examples/universal_client.py +620 -0
- mcp_proxy_adapter/main.py +66 -148
- mcp_proxy_adapter/utils/config_generator.py +1008 -0
- mcp_proxy_adapter/version.py +5 -2
- mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
- mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
- mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
- mcp_proxy_adapter/api/middleware/auth.py +0 -146
- mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
- mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
- mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
- mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
- mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
- mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
- mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
- mcp_proxy_adapter/api/middleware/security.py +0 -376
- mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
- mcp_proxy_adapter/examples/README.md +0 -124
- mcp_proxy_adapter/examples/basic_server/README.md +0 -60
- mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
- mcp_proxy_adapter/examples/basic_server/config.json +0 -70
- mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
- mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
- mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
- mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
- mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
- mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
- mcp_proxy_adapter/examples/basic_server/server.py +0 -114
- mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
- mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
- mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
- mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
- mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
- mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
- mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
- mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
- mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
- mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
- mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
- mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
- mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
- mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
- mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
- mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
- mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
- mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
- mcp_proxy_adapter/examples/deployment/README.md +0 -49
- mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
- mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
- mcp_proxy_adapter/examples/deployment/config.json +0 -29
- mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
- mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
- mcp_proxy_adapter/examples/deployment/run.sh +0 -43
- mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
- mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
- mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
- mcp_proxy_adapter/schemas/base_schema.json +0 -114
- mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
- mcp_proxy_adapter/schemas/roles_schema.json +0 -162
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +0 -3
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
- mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
- mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
- mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
- mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
- mcp_proxy_adapter/tests/commands/__init__.py +0 -3
- mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
- mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
- mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
- mcp_proxy_adapter/tests/conftest.py +0 -131
- mcp_proxy_adapter/tests/functional/__init__.py +0 -3
- mcp_proxy_adapter/tests/functional/test_api.py +0 -253
- mcp_proxy_adapter/tests/integration/__init__.py +0 -3
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
- mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
- mcp_proxy_adapter/tests/performance/__init__.py +0 -3
- mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
- mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
- mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
- mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
- mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
- mcp_proxy_adapter/tests/test_base_command.py +0 -123
- mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
- mcp_proxy_adapter/tests/test_command_registry.py +0 -281
- mcp_proxy_adapter/tests/test_config.py +0 -127
- mcp_proxy_adapter/tests/test_utils.py +0 -65
- mcp_proxy_adapter/tests/unit/__init__.py +0 -3
- mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
- mcp_proxy_adapter/tests/unit/test_config.py +0 -270
- mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
- mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
- {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
"""
|
2
|
+
Role Test Command
|
3
|
+
|
4
|
+
This command tests role-based access control by checking user permissions.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import logging
|
11
|
+
from typing import Dict, Any, Optional
|
12
|
+
from mcp_proxy_adapter.commands.base import Command
|
13
|
+
from mcp_proxy_adapter.commands.result import SuccessResult
|
14
|
+
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
class RoleTestCommandResult(SuccessResult):
|
20
|
+
"""Result for role test command."""
|
21
|
+
|
22
|
+
def __init__(self, user_role: str, permissions: list, action: str, allowed: bool):
|
23
|
+
"""
|
24
|
+
Initialize role test result.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
user_role: User's role
|
28
|
+
permissions: User's permissions
|
29
|
+
action: Action being tested
|
30
|
+
allowed: Whether action is allowed
|
31
|
+
"""
|
32
|
+
super().__init__()
|
33
|
+
self.user_role = user_role
|
34
|
+
self.permissions = permissions
|
35
|
+
self.action = action
|
36
|
+
self.allowed = allowed
|
37
|
+
|
38
|
+
def to_dict(self) -> Dict[str, Any]:
|
39
|
+
"""Convert to dictionary."""
|
40
|
+
return {
|
41
|
+
"success": True,
|
42
|
+
"data": {
|
43
|
+
"user_role": self.user_role,
|
44
|
+
"permissions": self.permissions,
|
45
|
+
"action": self.action,
|
46
|
+
"allowed": self.allowed
|
47
|
+
},
|
48
|
+
"message": f"Action '{self.action}' {'allowed' if self.allowed else 'denied'} for role '{self.user_role}'"
|
49
|
+
}
|
50
|
+
|
51
|
+
def get_schema(self) -> Dict[str, Any]:
|
52
|
+
"""Get JSON schema."""
|
53
|
+
return {
|
54
|
+
"type": "object",
|
55
|
+
"properties": {
|
56
|
+
"success": {"type": "boolean"},
|
57
|
+
"data": {
|
58
|
+
"type": "object",
|
59
|
+
"properties": {
|
60
|
+
"user_role": {"type": "string"},
|
61
|
+
"permissions": {"type": "array", "items": {"type": "string"}},
|
62
|
+
"action": {"type": "string"},
|
63
|
+
"allowed": {"type": "boolean"}
|
64
|
+
}
|
65
|
+
},
|
66
|
+
"message": {"type": "string"}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
|
71
|
+
class RoleTestCommand(Command):
|
72
|
+
"""Test role-based access control."""
|
73
|
+
|
74
|
+
name = "roletest"
|
75
|
+
descr = "Test role-based access control"
|
76
|
+
category = "security"
|
77
|
+
author = "Vasiliy Zdanovskiy"
|
78
|
+
email = "vasilyvz@gmail.com"
|
79
|
+
|
80
|
+
async def execute(self, **kwargs) -> RoleTestCommandResult:
|
81
|
+
"""
|
82
|
+
Execute role test command.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
**kwargs: Command parameters including context
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
RoleTestCommandResult
|
89
|
+
"""
|
90
|
+
# Extract parameters
|
91
|
+
action = kwargs.get("action", "read")
|
92
|
+
context = kwargs.get("context", {})
|
93
|
+
|
94
|
+
# Get user info from context
|
95
|
+
user_role = "guest" # Default
|
96
|
+
permissions = ["read"] # Default
|
97
|
+
|
98
|
+
if context:
|
99
|
+
user_info = context.get("user", {})
|
100
|
+
user_role = user_info.get("role", "guest")
|
101
|
+
permissions = user_info.get("permissions", ["read"])
|
102
|
+
|
103
|
+
# Check if action is allowed
|
104
|
+
allowed = self._check_permission(action, permissions)
|
105
|
+
|
106
|
+
logger.info(f"Role test: user={user_role}, action={action}, allowed={allowed}")
|
107
|
+
|
108
|
+
return RoleTestCommandResult(user_role, permissions, action, allowed)
|
109
|
+
|
110
|
+
@classmethod
|
111
|
+
def get_schema(cls) -> Dict[str, Any]:
|
112
|
+
"""Get JSON schema for command parameters."""
|
113
|
+
return {
|
114
|
+
"type": "object",
|
115
|
+
"properties": {
|
116
|
+
"action": {
|
117
|
+
"type": "string",
|
118
|
+
"description": "Action to test",
|
119
|
+
"default": "read"
|
120
|
+
}
|
121
|
+
},
|
122
|
+
"additionalProperties": False
|
123
|
+
}
|
124
|
+
|
125
|
+
def _check_permission(self, action: str, permissions: list) -> bool:
|
126
|
+
"""
|
127
|
+
Check if action is allowed for given permissions.
|
128
|
+
|
129
|
+
Args:
|
130
|
+
action: Action to check
|
131
|
+
permissions: User permissions
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
True if allowed, False otherwise
|
135
|
+
"""
|
136
|
+
# Admin has all permissions
|
137
|
+
if "*" in permissions:
|
138
|
+
return True
|
139
|
+
|
140
|
+
# Check specific permission
|
141
|
+
return action in permissions
|
@@ -0,0 +1,488 @@
|
|
1
|
+
"""
|
2
|
+
Security Command - Direct Framework Integration
|
3
|
+
|
4
|
+
This command provides direct access to mcp_security_framework functionality
|
5
|
+
through JSON-RPC interface.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import logging
|
12
|
+
from typing import Dict, Any, List, Optional
|
13
|
+
from pathlib import Path
|
14
|
+
|
15
|
+
from .base import Command
|
16
|
+
from .result import CommandResult, SuccessResult, ErrorResult
|
17
|
+
from mcp_proxy_adapter.core.security_integration import create_security_integration
|
18
|
+
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
|
22
|
+
class SecurityResult(CommandResult):
|
23
|
+
"""Result class for security operations."""
|
24
|
+
|
25
|
+
def __init__(self, operation: str, success: bool, data: Dict[str, Any] = None, error: str = None):
|
26
|
+
"""
|
27
|
+
Initialize security result.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
operation: Security operation performed
|
31
|
+
success: Whether operation was successful
|
32
|
+
data: Operation data
|
33
|
+
error: Error message if any
|
34
|
+
"""
|
35
|
+
self.operation = operation
|
36
|
+
self.success = success
|
37
|
+
self.data = data or {}
|
38
|
+
self.error = error
|
39
|
+
|
40
|
+
def to_dict(self) -> Dict[str, Any]:
|
41
|
+
"""Convert to dictionary."""
|
42
|
+
return {
|
43
|
+
"operation": self.operation,
|
44
|
+
"success": self.success,
|
45
|
+
"data": self.data,
|
46
|
+
"error": self.error
|
47
|
+
}
|
48
|
+
|
49
|
+
def get_schema(self) -> Dict[str, Any]:
|
50
|
+
"""Get result schema."""
|
51
|
+
return {
|
52
|
+
"type": "object",
|
53
|
+
"properties": {
|
54
|
+
"operation": {"type": "string", "description": "Security operation performed"},
|
55
|
+
"success": {"type": "boolean", "description": "Whether operation was successful"},
|
56
|
+
"data": {"type": "object", "description": "Operation data"},
|
57
|
+
"error": {"type": "string", "description": "Error message if any"}
|
58
|
+
},
|
59
|
+
"required": ["operation", "success"]
|
60
|
+
}
|
61
|
+
|
62
|
+
|
63
|
+
class SecurityCommand(Command):
|
64
|
+
"""
|
65
|
+
Security command using mcp_security_framework.
|
66
|
+
|
67
|
+
Provides direct access to security framework functionality:
|
68
|
+
- Authentication (API key, JWT, certificate)
|
69
|
+
- Certificate management
|
70
|
+
- Permission management
|
71
|
+
- Rate limiting
|
72
|
+
"""
|
73
|
+
|
74
|
+
# Command metadata
|
75
|
+
name = "security"
|
76
|
+
version = "1.0.0"
|
77
|
+
descr = "Security operations using mcp_security_framework"
|
78
|
+
category = "security"
|
79
|
+
author = "MCP Proxy Adapter Team"
|
80
|
+
email = "team@mcp-proxy-adapter.com"
|
81
|
+
source_url = "https://github.com/mcp-proxy-adapter"
|
82
|
+
result_class = SecurityResult
|
83
|
+
|
84
|
+
def __init__(self, config: Dict[str, Any]):
|
85
|
+
"""Initialize security command."""
|
86
|
+
super().__init__()
|
87
|
+
self.config = config
|
88
|
+
self.security_integration = create_security_integration(config)
|
89
|
+
|
90
|
+
if not self.security_integration:
|
91
|
+
logger.warning("Security framework not available, security command will fail")
|
92
|
+
|
93
|
+
async def execute(self, **kwargs) -> CommandResult:
|
94
|
+
"""
|
95
|
+
Execute security command.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
**kwargs: Command parameters including:
|
99
|
+
- action: Action to perform (auth, cert, permission, rate_limit, status)
|
100
|
+
- method: Authentication method (api_key, jwt, certificate)
|
101
|
+
- api_key: API key for authentication
|
102
|
+
- token: JWT token for authentication
|
103
|
+
- cert_path: Certificate path for operations
|
104
|
+
- common_name: Common name for certificate creation
|
105
|
+
- user_id: User ID for permission operations
|
106
|
+
- permission: Permission to check
|
107
|
+
- role: Role for operations
|
108
|
+
- identifier: Identifier for rate limiting
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
CommandResult with security operation status
|
112
|
+
"""
|
113
|
+
if not self.security_integration:
|
114
|
+
return SecurityResult(
|
115
|
+
operation="security",
|
116
|
+
success=False,
|
117
|
+
error="Security framework not available"
|
118
|
+
)
|
119
|
+
|
120
|
+
action = kwargs.get("action", "status")
|
121
|
+
|
122
|
+
try:
|
123
|
+
if action == "auth":
|
124
|
+
return await self._handle_auth(kwargs)
|
125
|
+
elif action == "cert":
|
126
|
+
return await self._handle_certificate(kwargs)
|
127
|
+
elif action == "permission":
|
128
|
+
return await self._handle_permission(kwargs)
|
129
|
+
elif action == "rate_limit":
|
130
|
+
return await self._handle_rate_limit(kwargs)
|
131
|
+
elif action == "status":
|
132
|
+
return await self._handle_status(kwargs)
|
133
|
+
else:
|
134
|
+
return SecurityResult(
|
135
|
+
operation=action,
|
136
|
+
success=False,
|
137
|
+
error=f"Unknown action: {action}"
|
138
|
+
)
|
139
|
+
|
140
|
+
except Exception as e:
|
141
|
+
logger.error(f"Security command error: {e}")
|
142
|
+
return SecurityResult(
|
143
|
+
operation=action,
|
144
|
+
success=False,
|
145
|
+
error=f"Security operation failed: {str(e)}"
|
146
|
+
)
|
147
|
+
|
148
|
+
async def _handle_auth(self, kwargs: Dict[str, Any]) -> SecurityResult:
|
149
|
+
"""Handle authentication operations."""
|
150
|
+
method = kwargs.get("method", "api_key")
|
151
|
+
|
152
|
+
if method == "api_key":
|
153
|
+
api_key = kwargs.get("api_key")
|
154
|
+
if not api_key:
|
155
|
+
return SecurityResult(
|
156
|
+
operation="auth_api_key",
|
157
|
+
success=False,
|
158
|
+
error="API key required"
|
159
|
+
)
|
160
|
+
|
161
|
+
result = await self.security_integration.authenticate_api_key(api_key)
|
162
|
+
return SecurityResult(
|
163
|
+
operation="auth_api_key",
|
164
|
+
success=result.is_valid,
|
165
|
+
data={
|
166
|
+
"user_id": result.user_id,
|
167
|
+
"roles": result.roles,
|
168
|
+
"permissions": result.permissions
|
169
|
+
},
|
170
|
+
error=result.error_message if not result.is_valid else None
|
171
|
+
)
|
172
|
+
|
173
|
+
elif method == "jwt":
|
174
|
+
token = kwargs.get("token")
|
175
|
+
if not token:
|
176
|
+
return SecurityResult(
|
177
|
+
operation="auth_jwt",
|
178
|
+
success=False,
|
179
|
+
error="JWT token required"
|
180
|
+
)
|
181
|
+
|
182
|
+
result = await self.security_integration.authenticate_jwt(token)
|
183
|
+
return SecurityResult(
|
184
|
+
operation="auth_jwt",
|
185
|
+
success=result.is_valid,
|
186
|
+
data={
|
187
|
+
"user_id": result.user_id,
|
188
|
+
"roles": result.roles,
|
189
|
+
"permissions": result.permissions
|
190
|
+
},
|
191
|
+
error=result.error_message if not result.is_valid else None
|
192
|
+
)
|
193
|
+
|
194
|
+
elif method == "certificate":
|
195
|
+
cert_path = kwargs.get("cert_path")
|
196
|
+
if not cert_path:
|
197
|
+
return SecurityResult(
|
198
|
+
operation="auth_certificate",
|
199
|
+
success=False,
|
200
|
+
error="Certificate path required"
|
201
|
+
)
|
202
|
+
|
203
|
+
# Read certificate data
|
204
|
+
try:
|
205
|
+
with open(cert_path, 'rb') as f:
|
206
|
+
cert_data = f.read()
|
207
|
+
|
208
|
+
result = await self.security_integration.authenticate_certificate(cert_data)
|
209
|
+
return SecurityResult(
|
210
|
+
operation="auth_certificate",
|
211
|
+
success=result.is_valid,
|
212
|
+
data={
|
213
|
+
"user_id": result.user_id,
|
214
|
+
"roles": result.roles,
|
215
|
+
"permissions": result.permissions
|
216
|
+
},
|
217
|
+
error=result.error_message if not result.is_valid else None
|
218
|
+
)
|
219
|
+
except Exception as e:
|
220
|
+
return SecurityResult(
|
221
|
+
operation="auth_certificate",
|
222
|
+
success=False,
|
223
|
+
error=f"Failed to read certificate: {str(e)}"
|
224
|
+
)
|
225
|
+
|
226
|
+
else:
|
227
|
+
return SecurityResult(
|
228
|
+
operation="auth",
|
229
|
+
success=False,
|
230
|
+
error=f"Unknown authentication method: {method}"
|
231
|
+
)
|
232
|
+
|
233
|
+
async def _handle_certificate(self, kwargs: Dict[str, Any]) -> SecurityResult:
|
234
|
+
"""Handle certificate operations."""
|
235
|
+
cert_action = kwargs.get("cert_action", "validate")
|
236
|
+
|
237
|
+
if cert_action == "create_ca":
|
238
|
+
common_name = kwargs.get("common_name")
|
239
|
+
if not common_name:
|
240
|
+
return SecurityResult(
|
241
|
+
operation="cert_create_ca",
|
242
|
+
success=False,
|
243
|
+
error="Common name required"
|
244
|
+
)
|
245
|
+
|
246
|
+
try:
|
247
|
+
cert_pair = await self.security_integration.create_ca_certificate(common_name)
|
248
|
+
return SecurityResult(
|
249
|
+
operation="cert_create_ca",
|
250
|
+
success=True,
|
251
|
+
data={
|
252
|
+
"cert_path": str(cert_pair.cert_path),
|
253
|
+
"key_path": str(cert_pair.key_path),
|
254
|
+
"common_name": common_name
|
255
|
+
}
|
256
|
+
)
|
257
|
+
except Exception as e:
|
258
|
+
return SecurityResult(
|
259
|
+
operation="cert_create_ca",
|
260
|
+
success=False,
|
261
|
+
error=f"Failed to create CA certificate: {str(e)}"
|
262
|
+
)
|
263
|
+
|
264
|
+
elif cert_action == "create_client":
|
265
|
+
common_name = kwargs.get("common_name")
|
266
|
+
if not common_name:
|
267
|
+
return SecurityResult(
|
268
|
+
operation="cert_create_client",
|
269
|
+
success=False,
|
270
|
+
error="Common name required"
|
271
|
+
)
|
272
|
+
|
273
|
+
try:
|
274
|
+
cert_pair = await self.security_integration.create_client_certificate(common_name)
|
275
|
+
return SecurityResult(
|
276
|
+
operation="cert_create_client",
|
277
|
+
success=True,
|
278
|
+
data={
|
279
|
+
"cert_path": str(cert_pair.cert_path),
|
280
|
+
"key_path": str(cert_pair.key_path),
|
281
|
+
"common_name": common_name
|
282
|
+
}
|
283
|
+
)
|
284
|
+
except Exception as e:
|
285
|
+
return SecurityResult(
|
286
|
+
operation="cert_create_client",
|
287
|
+
success=False,
|
288
|
+
error=f"Failed to create client certificate: {str(e)}"
|
289
|
+
)
|
290
|
+
|
291
|
+
elif cert_action == "validate":
|
292
|
+
cert_path = kwargs.get("cert_path")
|
293
|
+
if not cert_path:
|
294
|
+
return SecurityResult(
|
295
|
+
operation="cert_validate",
|
296
|
+
success=False,
|
297
|
+
error="Certificate path required"
|
298
|
+
)
|
299
|
+
|
300
|
+
try:
|
301
|
+
is_valid = await self.security_integration.validate_certificate(cert_path)
|
302
|
+
return SecurityResult(
|
303
|
+
operation="cert_validate",
|
304
|
+
success=is_valid,
|
305
|
+
data={"cert_path": cert_path, "valid": is_valid}
|
306
|
+
)
|
307
|
+
except Exception as e:
|
308
|
+
return SecurityResult(
|
309
|
+
operation="cert_validate",
|
310
|
+
success=False,
|
311
|
+
error=f"Failed to validate certificate: {str(e)}"
|
312
|
+
)
|
313
|
+
|
314
|
+
elif cert_action == "extract_roles":
|
315
|
+
cert_path = kwargs.get("cert_path")
|
316
|
+
if not cert_path:
|
317
|
+
return SecurityResult(
|
318
|
+
operation="cert_extract_roles",
|
319
|
+
success=False,
|
320
|
+
error="Certificate path required"
|
321
|
+
)
|
322
|
+
|
323
|
+
try:
|
324
|
+
roles = await self.security_integration.extract_roles_from_certificate(cert_path)
|
325
|
+
return SecurityResult(
|
326
|
+
operation="cert_extract_roles",
|
327
|
+
success=True,
|
328
|
+
data={"cert_path": cert_path, "roles": roles}
|
329
|
+
)
|
330
|
+
except Exception as e:
|
331
|
+
return SecurityResult(
|
332
|
+
operation="cert_extract_roles",
|
333
|
+
success=False,
|
334
|
+
error=f"Failed to extract roles: {str(e)}"
|
335
|
+
)
|
336
|
+
|
337
|
+
else:
|
338
|
+
return SecurityResult(
|
339
|
+
operation="cert",
|
340
|
+
success=False,
|
341
|
+
error=f"Unknown certificate action: {cert_action}"
|
342
|
+
)
|
343
|
+
|
344
|
+
async def _handle_permission(self, kwargs: Dict[str, Any]) -> SecurityResult:
|
345
|
+
"""Handle permission operations."""
|
346
|
+
perm_action = kwargs.get("perm_action", "check")
|
347
|
+
user_id = kwargs.get("user_id")
|
348
|
+
|
349
|
+
if not user_id:
|
350
|
+
return SecurityResult(
|
351
|
+
operation="permission",
|
352
|
+
success=False,
|
353
|
+
error="User ID required"
|
354
|
+
)
|
355
|
+
|
356
|
+
if perm_action == "check":
|
357
|
+
permission = kwargs.get("permission")
|
358
|
+
if not permission:
|
359
|
+
return SecurityResult(
|
360
|
+
operation="permission_check",
|
361
|
+
success=False,
|
362
|
+
error="Permission required"
|
363
|
+
)
|
364
|
+
|
365
|
+
try:
|
366
|
+
has_permission = await self.security_integration.check_permission(user_id, permission)
|
367
|
+
return SecurityResult(
|
368
|
+
operation="permission_check",
|
369
|
+
success=True,
|
370
|
+
data={
|
371
|
+
"user_id": user_id,
|
372
|
+
"permission": permission,
|
373
|
+
"has_permission": has_permission
|
374
|
+
}
|
375
|
+
)
|
376
|
+
except Exception as e:
|
377
|
+
return SecurityResult(
|
378
|
+
operation="permission_check",
|
379
|
+
success=False,
|
380
|
+
error=f"Failed to check permission: {str(e)}"
|
381
|
+
)
|
382
|
+
|
383
|
+
elif perm_action == "get_roles":
|
384
|
+
try:
|
385
|
+
roles = await self.security_integration.get_user_roles(user_id)
|
386
|
+
return SecurityResult(
|
387
|
+
operation="permission_get_roles",
|
388
|
+
success=True,
|
389
|
+
data={"user_id": user_id, "roles": roles}
|
390
|
+
)
|
391
|
+
except Exception as e:
|
392
|
+
return SecurityResult(
|
393
|
+
operation="permission_get_roles",
|
394
|
+
success=False,
|
395
|
+
error=f"Failed to get user roles: {str(e)}"
|
396
|
+
)
|
397
|
+
|
398
|
+
elif perm_action == "add_role":
|
399
|
+
role = kwargs.get("role")
|
400
|
+
if not role:
|
401
|
+
return SecurityResult(
|
402
|
+
operation="permission_add_role",
|
403
|
+
success=False,
|
404
|
+
error="Role required"
|
405
|
+
)
|
406
|
+
|
407
|
+
try:
|
408
|
+
success = await self.security_integration.add_user_role(user_id, role)
|
409
|
+
return SecurityResult(
|
410
|
+
operation="permission_add_role",
|
411
|
+
success=success,
|
412
|
+
data={"user_id": user_id, "role": role, "added": success}
|
413
|
+
)
|
414
|
+
except Exception as e:
|
415
|
+
return SecurityResult(
|
416
|
+
operation="permission_add_role",
|
417
|
+
success=False,
|
418
|
+
error=f"Failed to add role: {str(e)}"
|
419
|
+
)
|
420
|
+
|
421
|
+
else:
|
422
|
+
return SecurityResult(
|
423
|
+
operation="permission",
|
424
|
+
success=False,
|
425
|
+
error=f"Unknown permission action: {perm_action}"
|
426
|
+
)
|
427
|
+
|
428
|
+
async def _handle_rate_limit(self, kwargs: Dict[str, Any]) -> SecurityResult:
|
429
|
+
"""Handle rate limiting operations."""
|
430
|
+
identifier = kwargs.get("identifier")
|
431
|
+
if not identifier:
|
432
|
+
return SecurityResult(
|
433
|
+
operation="rate_limit",
|
434
|
+
success=False,
|
435
|
+
error="Identifier required"
|
436
|
+
)
|
437
|
+
|
438
|
+
try:
|
439
|
+
# Check rate limit
|
440
|
+
is_allowed = await self.security_integration.check_rate_limit(identifier)
|
441
|
+
|
442
|
+
if is_allowed:
|
443
|
+
# Increment counter
|
444
|
+
await self.security_integration.increment_rate_limit(identifier)
|
445
|
+
|
446
|
+
# Get rate limit info
|
447
|
+
info = await self.security_integration.get_rate_limit_info(identifier)
|
448
|
+
|
449
|
+
return SecurityResult(
|
450
|
+
operation="rate_limit_check",
|
451
|
+
success=True,
|
452
|
+
data={
|
453
|
+
"identifier": identifier,
|
454
|
+
"allowed": is_allowed,
|
455
|
+
"info": info
|
456
|
+
}
|
457
|
+
)
|
458
|
+
except Exception as e:
|
459
|
+
return SecurityResult(
|
460
|
+
operation="rate_limit_check",
|
461
|
+
success=False,
|
462
|
+
error=f"Failed to check rate limit: {str(e)}"
|
463
|
+
)
|
464
|
+
|
465
|
+
async def _handle_status(self, kwargs: Dict[str, Any]) -> SecurityResult:
|
466
|
+
"""Handle status operations."""
|
467
|
+
try:
|
468
|
+
security_config = self.security_integration.get_security_config()
|
469
|
+
|
470
|
+
return SecurityResult(
|
471
|
+
operation="status",
|
472
|
+
success=True,
|
473
|
+
data={
|
474
|
+
"security_enabled": self.security_integration.is_security_enabled(),
|
475
|
+
"public_paths": self.security_integration.get_public_paths(),
|
476
|
+
"auth_enabled": security_config.auth.enabled,
|
477
|
+
"ssl_enabled": security_config.ssl.enabled,
|
478
|
+
"permissions_enabled": security_config.permissions.enabled,
|
479
|
+
"rate_limit_enabled": security_config.rate_limit.enabled,
|
480
|
+
"certificates_enabled": security_config.certificates.enabled
|
481
|
+
}
|
482
|
+
)
|
483
|
+
except Exception as e:
|
484
|
+
return SecurityResult(
|
485
|
+
operation="status",
|
486
|
+
success=False,
|
487
|
+
error=f"Failed to get status: {str(e)}"
|
488
|
+
)
|