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,148 @@
|
|
1
|
+
"""
|
2
|
+
Command Permission Middleware
|
3
|
+
|
4
|
+
This middleware checks permissions for specific commands based on user roles.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import json
|
11
|
+
import logging
|
12
|
+
from typing import Dict, Any, Optional, Callable, Awaitable
|
13
|
+
from fastapi import Request, Response
|
14
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
15
|
+
|
16
|
+
from mcp_proxy_adapter.core.logging import logger
|
17
|
+
|
18
|
+
|
19
|
+
class CommandPermissionMiddleware(BaseHTTPMiddleware):
|
20
|
+
"""
|
21
|
+
Middleware for checking command permissions.
|
22
|
+
|
23
|
+
This middleware checks if the authenticated user has the required
|
24
|
+
permissions to execute specific commands.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self, app, config: Dict[str, Any]):
|
28
|
+
"""
|
29
|
+
Initialize command permission middleware.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
app: FastAPI application
|
33
|
+
config: Configuration dictionary
|
34
|
+
"""
|
35
|
+
super().__init__(app)
|
36
|
+
self.config = config
|
37
|
+
|
38
|
+
# Define command permissions
|
39
|
+
self.command_permissions = {
|
40
|
+
"echo": ["read"],
|
41
|
+
"health": ["read"],
|
42
|
+
"role_test": ["read"],
|
43
|
+
"config": ["read"],
|
44
|
+
"help": ["read"],
|
45
|
+
# Add more commands as needed
|
46
|
+
}
|
47
|
+
|
48
|
+
logger.info("Command permission middleware initialized")
|
49
|
+
|
50
|
+
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
51
|
+
"""
|
52
|
+
Process request and check command permissions.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
request: Request object
|
56
|
+
call_next: Next handler
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
Response object
|
60
|
+
"""
|
61
|
+
# Only check permissions for /cmd endpoint
|
62
|
+
if request.url.path != "/cmd":
|
63
|
+
return await call_next(request)
|
64
|
+
|
65
|
+
try:
|
66
|
+
# Get request body
|
67
|
+
body = await request.body()
|
68
|
+
if not body:
|
69
|
+
return await call_next(request)
|
70
|
+
|
71
|
+
# Parse JSON-RPC request
|
72
|
+
try:
|
73
|
+
data = json.loads(body)
|
74
|
+
except json.JSONDecodeError:
|
75
|
+
return await call_next(request)
|
76
|
+
|
77
|
+
# Extract method (command name)
|
78
|
+
method = data.get("method")
|
79
|
+
if not method:
|
80
|
+
return await call_next(request)
|
81
|
+
|
82
|
+
# Check if method requires permissions
|
83
|
+
if method not in self.command_permissions:
|
84
|
+
return await call_next(request)
|
85
|
+
|
86
|
+
required_permissions = self.command_permissions[method]
|
87
|
+
|
88
|
+
# Get user info from request state
|
89
|
+
user_info = getattr(request.state, "user", None)
|
90
|
+
if not user_info:
|
91
|
+
logger.warning(f"No user info found for command {method}")
|
92
|
+
return await call_next(request)
|
93
|
+
|
94
|
+
user_roles = user_info.get("roles", [])
|
95
|
+
user_permissions = user_info.get("permissions", [])
|
96
|
+
|
97
|
+
logger.debug(f"Checking permissions for {method}: user_roles={user_roles}, required={required_permissions}")
|
98
|
+
|
99
|
+
# Check if user has required permissions
|
100
|
+
has_permission = self._check_permissions(user_roles, user_permissions, required_permissions)
|
101
|
+
|
102
|
+
if not has_permission:
|
103
|
+
logger.warning(f"Permission denied for {method}: user_roles={user_roles}, required={required_permissions}")
|
104
|
+
|
105
|
+
# Return permission denied response
|
106
|
+
error_response = {
|
107
|
+
"error": {
|
108
|
+
"code": 403,
|
109
|
+
"message": f"Permission denied: {method} requires {required_permissions}",
|
110
|
+
"type": "permission_denied"
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
return Response(
|
115
|
+
content=json.dumps(error_response),
|
116
|
+
status_code=403,
|
117
|
+
media_type="application/json"
|
118
|
+
)
|
119
|
+
|
120
|
+
logger.debug(f"Permission granted for {method}")
|
121
|
+
return await call_next(request)
|
122
|
+
|
123
|
+
except Exception as e:
|
124
|
+
logger.error(f"Error in command permission middleware: {e}")
|
125
|
+
return await call_next(request)
|
126
|
+
|
127
|
+
def _check_permissions(self, user_roles: list, user_permissions: list, required_permissions: list) -> bool:
|
128
|
+
"""
|
129
|
+
Check if user has required permissions.
|
130
|
+
|
131
|
+
Args:
|
132
|
+
user_roles: User roles
|
133
|
+
user_permissions: User permissions
|
134
|
+
required_permissions: Required permissions
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
True if user has required permissions
|
138
|
+
"""
|
139
|
+
# Admin has all permissions
|
140
|
+
if "admin" in user_roles or "*" in user_permissions:
|
141
|
+
return True
|
142
|
+
|
143
|
+
# Check if user has all required permissions
|
144
|
+
for required in required_permissions:
|
145
|
+
if required not in user_permissions:
|
146
|
+
return False
|
147
|
+
|
148
|
+
return True
|
@@ -13,9 +13,10 @@ from fastapi import FastAPI
|
|
13
13
|
from mcp_proxy_adapter.core.logging import logger
|
14
14
|
from mcp_proxy_adapter.core.security_factory import SecurityFactory
|
15
15
|
from .base import BaseMiddleware
|
16
|
-
from .
|
16
|
+
from .unified_security import UnifiedSecurityMiddleware
|
17
17
|
from .error_handling import ErrorHandlingMiddleware
|
18
18
|
from .logging import LoggingMiddleware
|
19
|
+
from .user_info_middleware import UserInfoMiddleware
|
19
20
|
|
20
21
|
|
21
22
|
class MiddlewareFactory:
|
@@ -40,12 +41,12 @@ class MiddlewareFactory:
|
|
40
41
|
|
41
42
|
logger.info("Middleware factory initialized")
|
42
43
|
|
43
|
-
def create_security_middleware(self) -> Optional[
|
44
|
+
def create_security_middleware(self) -> Optional[UnifiedSecurityMiddleware]:
|
44
45
|
"""
|
45
|
-
Create security middleware.
|
46
|
+
Create unified security middleware.
|
46
47
|
|
47
48
|
Returns:
|
48
|
-
|
49
|
+
UnifiedSecurityMiddleware instance or None if creation failed
|
49
50
|
"""
|
50
51
|
try:
|
51
52
|
security_config = self.config.get("security", {})
|
@@ -54,14 +55,14 @@ class MiddlewareFactory:
|
|
54
55
|
logger.info("Security middleware disabled by configuration")
|
55
56
|
return None
|
56
57
|
|
57
|
-
middleware =
|
58
|
+
middleware = UnifiedSecurityMiddleware(self.app, self.config)
|
58
59
|
self.middleware_stack.append(middleware)
|
59
60
|
|
60
|
-
logger.info("
|
61
|
+
logger.info("Unified security middleware created successfully")
|
61
62
|
return middleware
|
62
63
|
|
63
64
|
except Exception as e:
|
64
|
-
logger.error(f"Failed to create security middleware: {e}")
|
65
|
+
logger.error(f"Failed to create unified security middleware: {e}")
|
65
66
|
return None
|
66
67
|
|
67
68
|
def create_error_handling_middleware(self) -> Optional[ErrorHandlingMiddleware]:
|
@@ -106,6 +107,24 @@ class MiddlewareFactory:
|
|
106
107
|
logger.error(f"Failed to create logging middleware: {e}")
|
107
108
|
return None
|
108
109
|
|
110
|
+
def create_user_info_middleware(self) -> Optional[UserInfoMiddleware]:
|
111
|
+
"""
|
112
|
+
Create user info middleware.
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
UserInfoMiddleware instance or None if creation failed
|
116
|
+
"""
|
117
|
+
try:
|
118
|
+
middleware = UserInfoMiddleware(self.app, self.config)
|
119
|
+
self.middleware_stack.append(middleware)
|
120
|
+
|
121
|
+
logger.info("User info middleware created successfully")
|
122
|
+
return middleware
|
123
|
+
|
124
|
+
except Exception as e:
|
125
|
+
logger.error(f"Failed to create user info middleware: {e}")
|
126
|
+
return None
|
127
|
+
|
109
128
|
|
110
129
|
|
111
130
|
def create_all_middleware(self) -> List[BaseMiddleware]:
|
@@ -132,6 +151,11 @@ class MiddlewareFactory:
|
|
132
151
|
if logging_middleware:
|
133
152
|
middleware_list.append(logging_middleware)
|
134
153
|
|
154
|
+
# Create user info middleware
|
155
|
+
user_info_middleware = self.create_user_info_middleware()
|
156
|
+
if user_info_middleware:
|
157
|
+
middleware_list.append(user_info_middleware)
|
158
|
+
|
135
159
|
logger.info(f"Created {len(middleware_list)} middleware components")
|
136
160
|
return middleware_list
|
137
161
|
|
@@ -152,14 +176,14 @@ class MiddlewareFactory:
|
|
152
176
|
return middleware
|
153
177
|
return None
|
154
178
|
|
155
|
-
def get_security_middleware(self) -> Optional[
|
179
|
+
def get_security_middleware(self) -> Optional[UnifiedSecurityMiddleware]:
|
156
180
|
"""
|
157
|
-
Get security middleware instance.
|
181
|
+
Get unified security middleware instance.
|
158
182
|
|
159
183
|
Returns:
|
160
|
-
|
184
|
+
UnifiedSecurityMiddleware instance or None if not found
|
161
185
|
"""
|
162
|
-
return self.get_middleware_by_type(
|
186
|
+
return self.get_middleware_by_type(UnifiedSecurityMiddleware)
|
163
187
|
|
164
188
|
def validate_middleware_config(self) -> bool:
|
165
189
|
"""
|
@@ -213,7 +237,7 @@ class MiddlewareFactory:
|
|
213
237
|
middleware_type = type(middleware).__name__
|
214
238
|
info["middleware_types"].append(middleware_type)
|
215
239
|
|
216
|
-
if isinstance(middleware,
|
240
|
+
if isinstance(middleware, UnifiedSecurityMiddleware):
|
217
241
|
info["security_enabled"] = True
|
218
242
|
|
219
243
|
return info
|
@@ -4,12 +4,12 @@ Protocol middleware module.
|
|
4
4
|
This module provides middleware for validating protocol access based on configuration.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from typing import Callable
|
7
|
+
from typing import Callable, Dict, Any, Optional
|
8
8
|
from fastapi import Request, Response
|
9
9
|
from starlette.middleware.base import BaseHTTPMiddleware
|
10
10
|
from starlette.responses import JSONResponse
|
11
11
|
|
12
|
-
from mcp_proxy_adapter.core.protocol_manager import
|
12
|
+
from mcp_proxy_adapter.core.protocol_manager import get_protocol_manager
|
13
13
|
from mcp_proxy_adapter.core.logging import logger
|
14
14
|
|
15
15
|
|
@@ -21,28 +21,73 @@ class ProtocolMiddleware(BaseHTTPMiddleware):
|
|
21
21
|
based on the protocol configuration.
|
22
22
|
"""
|
23
23
|
|
24
|
-
def __init__(self, app,
|
24
|
+
def __init__(self, app, app_config: Optional[Dict[str, Any]] = None):
|
25
25
|
"""
|
26
26
|
Initialize protocol middleware.
|
27
|
-
|
27
|
+
|
28
28
|
Args:
|
29
29
|
app: FastAPI application
|
30
|
-
|
30
|
+
app_config: Application configuration dictionary (optional)
|
31
31
|
"""
|
32
32
|
super().__init__(app)
|
33
|
-
|
33
|
+
# Normalize config to dictionary
|
34
|
+
normalized_config: Optional[Dict[str, Any]]
|
35
|
+
if app_config is None:
|
36
|
+
normalized_config = None
|
37
|
+
elif hasattr(app_config, 'get_all'):
|
38
|
+
try:
|
39
|
+
normalized_config = app_config.get_all()
|
40
|
+
except Exception as e:
|
41
|
+
logger.debug(f"ProtocolMiddleware - Error calling get_all(): {e}, type: {type(app_config)}")
|
42
|
+
normalized_config = None
|
43
|
+
elif hasattr(app_config, 'keys'):
|
44
|
+
normalized_config = app_config # Already dict-like
|
45
|
+
else:
|
46
|
+
logger.debug(f"ProtocolMiddleware - app_config is not dict-like, type: {type(app_config)}, value: {repr(app_config)}")
|
47
|
+
normalized_config = None
|
48
|
+
|
49
|
+
logger.debug(f"ProtocolMiddleware - normalized_config type: {type(normalized_config)}")
|
50
|
+
if normalized_config:
|
51
|
+
logger.debug(f"ProtocolMiddleware - protocols in config: {'protocols' in normalized_config}")
|
52
|
+
if 'protocols' in normalized_config:
|
53
|
+
logger.debug(f"ProtocolMiddleware - protocols type: {type(normalized_config['protocols'])}")
|
54
|
+
|
55
|
+
self.app_config = normalized_config
|
56
|
+
# Get protocol manager with current configuration
|
57
|
+
self.protocol_manager = get_protocol_manager(normalized_config)
|
58
|
+
|
59
|
+
def update_config(self, new_config: Dict[str, Any]):
|
60
|
+
"""
|
61
|
+
Update configuration and reload protocol manager.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
new_config: New configuration dictionary
|
65
|
+
"""
|
66
|
+
# Normalize new config
|
67
|
+
if hasattr(new_config, 'get_all'):
|
68
|
+
try:
|
69
|
+
self.app_config = new_config.get_all()
|
70
|
+
except Exception:
|
71
|
+
self.app_config = None
|
72
|
+
elif hasattr(new_config, 'keys'):
|
73
|
+
self.app_config = new_config
|
74
|
+
else:
|
75
|
+
self.app_config = None
|
76
|
+
self.protocol_manager = get_protocol_manager(self.app_config)
|
77
|
+
logger.info("Protocol middleware configuration updated")
|
34
78
|
|
35
79
|
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
36
80
|
"""
|
37
81
|
Process request through protocol middleware.
|
38
|
-
|
82
|
+
|
39
83
|
Args:
|
40
84
|
request: Incoming request
|
41
85
|
call_next: Next middleware/endpoint function
|
42
|
-
|
86
|
+
|
43
87
|
Returns:
|
44
88
|
Response object
|
45
89
|
"""
|
90
|
+
logger.debug(f"ProtocolMiddleware.dispatch called for {request.method} {request.url.path}")
|
46
91
|
try:
|
47
92
|
# Get protocol from request
|
48
93
|
protocol = self._get_request_protocol(request)
|
@@ -116,20 +161,41 @@ class ProtocolMiddleware(BaseHTTPMiddleware):
|
|
116
161
|
return "http"
|
117
162
|
|
118
163
|
|
119
|
-
def setup_protocol_middleware(app,
|
164
|
+
def setup_protocol_middleware(app, app_config: Optional[Dict[str, Any]] = None):
|
120
165
|
"""
|
121
166
|
Setup protocol middleware for FastAPI application.
|
122
|
-
|
167
|
+
|
123
168
|
Args:
|
124
169
|
app: FastAPI application
|
125
|
-
|
170
|
+
app_config: Application configuration dictionary (optional)
|
126
171
|
"""
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
172
|
+
logger.debug(f"setup_protocol_middleware - app_config type: {type(app_config)}")
|
173
|
+
|
174
|
+
# Check if protocol management is enabled
|
175
|
+
if app_config is None:
|
176
|
+
from mcp_proxy_adapter.config import config
|
177
|
+
app_config = config.get_all()
|
178
|
+
logger.debug(f"setup_protocol_middleware - loaded from global config, type: {type(app_config)}")
|
179
|
+
|
180
|
+
logger.debug(f"setup_protocol_middleware - final app_config type: {type(app_config)}")
|
181
|
+
|
182
|
+
if hasattr(app_config, 'get'):
|
183
|
+
logger.debug(f"setup_protocol_middleware - app_config keys: {list(app_config.keys()) if hasattr(app_config, 'keys') else 'no keys'}")
|
184
|
+
protocols_config = app_config.get("protocols", {})
|
185
|
+
logger.debug(f"setup_protocol_middleware - protocols_config type: {type(protocols_config)}")
|
186
|
+
enabled = protocols_config.get("enabled", True) if hasattr(protocols_config, 'get') else True
|
187
|
+
else:
|
188
|
+
logger.debug(f"setup_protocol_middleware - app_config is not dict-like: {repr(app_config)}")
|
189
|
+
enabled = True
|
190
|
+
|
191
|
+
logger.debug(f"setup_protocol_middleware - protocol management enabled: {enabled}")
|
192
|
+
|
193
|
+
if enabled:
|
194
|
+
# Create protocol middleware with current configuration
|
195
|
+
logger.debug(f"setup_protocol_middleware - creating ProtocolMiddleware with config type: {type(app_config)}")
|
196
|
+
middleware = ProtocolMiddleware(app, app_config)
|
197
|
+
logger.debug(f"setup_protocol_middleware - adding middleware to app")
|
198
|
+
app.add_middleware(ProtocolMiddleware, app_config=app_config)
|
133
199
|
logger.info("Protocol middleware added to application")
|
134
200
|
else:
|
135
|
-
logger.
|
201
|
+
logger.info("Protocol management is disabled, skipping protocol middleware")
|
@@ -0,0 +1,197 @@
|
|
1
|
+
"""
|
2
|
+
Unified Security Middleware - Direct Framework Integration
|
3
|
+
|
4
|
+
This middleware now directly uses mcp_security_framework components
|
5
|
+
instead of custom implementations.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import time
|
12
|
+
import logging
|
13
|
+
from typing import Dict, Any, Optional, Callable, Awaitable
|
14
|
+
from fastapi import Request, Response
|
15
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
16
|
+
|
17
|
+
# Direct import from framework
|
18
|
+
try:
|
19
|
+
from mcp_security_framework.middleware.fastapi_middleware import FastAPISecurityMiddleware
|
20
|
+
from mcp_security_framework import SecurityManager
|
21
|
+
from mcp_security_framework.schemas.config import SecurityConfig
|
22
|
+
SECURITY_FRAMEWORK_AVAILABLE = True
|
23
|
+
except ImportError:
|
24
|
+
SECURITY_FRAMEWORK_AVAILABLE = False
|
25
|
+
FastAPISecurityMiddleware = None
|
26
|
+
SecurityManager = None
|
27
|
+
SecurityConfig = None
|
28
|
+
|
29
|
+
from mcp_proxy_adapter.core.logging import logger
|
30
|
+
from mcp_proxy_adapter.core.security_integration import create_security_integration
|
31
|
+
|
32
|
+
|
33
|
+
class SecurityValidationError(Exception):
|
34
|
+
"""Security validation error."""
|
35
|
+
|
36
|
+
def __init__(self, message: str, error_code: int):
|
37
|
+
self.message = message
|
38
|
+
self.error_code = error_code
|
39
|
+
super().__init__(self.message)
|
40
|
+
|
41
|
+
|
42
|
+
class UnifiedSecurityMiddleware(BaseHTTPMiddleware):
|
43
|
+
"""
|
44
|
+
Unified security middleware using mcp_security_framework.
|
45
|
+
|
46
|
+
This middleware now directly uses the security framework's FastAPI middleware
|
47
|
+
and components instead of custom implementations.
|
48
|
+
"""
|
49
|
+
|
50
|
+
def __init__(self, app, config: Dict[str, Any]):
|
51
|
+
"""
|
52
|
+
Initialize unified security middleware.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
app: FastAPI application
|
56
|
+
config: mcp_proxy_adapter configuration dictionary
|
57
|
+
"""
|
58
|
+
super().__init__(app)
|
59
|
+
self.config = config
|
60
|
+
|
61
|
+
# Create security integration
|
62
|
+
try:
|
63
|
+
security_config = config.get("security", {})
|
64
|
+
self.security_integration = create_security_integration(security_config)
|
65
|
+
# Use framework's FastAPI middleware
|
66
|
+
self.framework_middleware = self.security_integration.security_manager.create_fastapi_middleware()
|
67
|
+
logger.info("Using mcp_security_framework FastAPI middleware")
|
68
|
+
# IMPORTANT: Don't replace self.app! This breaks the middleware chain.
|
69
|
+
# Instead, store the framework middleware for use in dispatch method.
|
70
|
+
logger.info("Framework middleware will be used in dispatch method")
|
71
|
+
except Exception as e:
|
72
|
+
logger.error(f"Security framework integration failed: {e}")
|
73
|
+
# Instead of raising error, log warning and continue without security
|
74
|
+
logger.warning("Continuing without security framework - some security features will be disabled")
|
75
|
+
self.security_integration = None
|
76
|
+
self.framework_middleware = None
|
77
|
+
# Keep original app in place when framework middleware is unavailable
|
78
|
+
# BaseHTTPMiddleware initialized it via super().__init__(app)
|
79
|
+
|
80
|
+
logger.info("Unified security middleware initialized")
|
81
|
+
|
82
|
+
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
83
|
+
"""
|
84
|
+
Process request using framework middleware.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
request: Request object
|
88
|
+
call_next: Next handler
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
Response object
|
92
|
+
"""
|
93
|
+
try:
|
94
|
+
# Simple built-in API key enforcement if configured
|
95
|
+
security_cfg = self.config.get("security", {}) if isinstance(self.config, dict) else {}
|
96
|
+
auth_cfg = security_cfg.get("auth", {})
|
97
|
+
permissions_cfg = security_cfg.get("permissions", {})
|
98
|
+
public_paths = set(auth_cfg.get("public_paths", ["/health", "/docs", "/openapi.json"]))
|
99
|
+
# JSON-RPC endpoint must not be public when API key is required
|
100
|
+
public_paths.discard("/api/jsonrpc")
|
101
|
+
path = request.url.path
|
102
|
+
methods = set(auth_cfg.get("methods", []))
|
103
|
+
api_keys: Dict[str, str] = auth_cfg.get("api_keys", {}) or {}
|
104
|
+
|
105
|
+
# Enforce only for non-public paths when api_key method configured
|
106
|
+
if security_cfg.get("enabled", False) and ("api_key" in methods) and (path not in public_paths):
|
107
|
+
# Accept either X-API-Key or Authorization: Bearer
|
108
|
+
token = request.headers.get("X-API-Key")
|
109
|
+
if not token:
|
110
|
+
authz = request.headers.get("Authorization", "")
|
111
|
+
if authz.startswith("Bearer "):
|
112
|
+
token = authz[7:]
|
113
|
+
if not token or (api_keys and token not in api_keys):
|
114
|
+
from fastapi.responses import JSONResponse
|
115
|
+
return JSONResponse(status_code=401, content={
|
116
|
+
"error": {
|
117
|
+
"code": 401,
|
118
|
+
"message": "Unauthorized: invalid or missing API key",
|
119
|
+
"type": "authentication_error"
|
120
|
+
}
|
121
|
+
})
|
122
|
+
|
123
|
+
# Continue with framework middleware or regular flow
|
124
|
+
if self.framework_middleware:
|
125
|
+
# If framework middleware exists, we need to call it manually
|
126
|
+
# This is a workaround since we can't chain ASGI apps in BaseHTTPMiddleware
|
127
|
+
logger.debug("Framework middleware exists, continuing with regular call_next")
|
128
|
+
return await call_next(request)
|
129
|
+
else:
|
130
|
+
# No framework middleware, continue normally
|
131
|
+
return await call_next(request)
|
132
|
+
|
133
|
+
except SecurityValidationError as e:
|
134
|
+
# Handle security validation errors
|
135
|
+
return await self._handle_security_error(request, e)
|
136
|
+
except Exception as e:
|
137
|
+
# Handle other errors
|
138
|
+
logger.error(f"Unexpected error in unified security middleware: {e}")
|
139
|
+
return await self._handle_general_error(request, e)
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
async def _handle_security_error(self, request: Request, error: SecurityValidationError) -> Response:
|
144
|
+
"""
|
145
|
+
Handle security validation errors.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
request: Request object
|
149
|
+
error: Security validation error
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
Error response
|
153
|
+
"""
|
154
|
+
from fastapi.responses import JSONResponse
|
155
|
+
|
156
|
+
error_response = {
|
157
|
+
"error": {
|
158
|
+
"code": error.error_code,
|
159
|
+
"message": error.message,
|
160
|
+
"type": "security_validation_error"
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
logger.warning(f"Security validation failed: {error.message}")
|
165
|
+
|
166
|
+
return JSONResponse(
|
167
|
+
status_code=error.error_code,
|
168
|
+
content=error_response
|
169
|
+
)
|
170
|
+
|
171
|
+
async def _handle_general_error(self, request: Request, error: Exception) -> Response:
|
172
|
+
"""
|
173
|
+
Handle general errors.
|
174
|
+
|
175
|
+
Args:
|
176
|
+
request: Request object
|
177
|
+
error: General error
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
Error response
|
181
|
+
"""
|
182
|
+
from fastapi.responses import JSONResponse
|
183
|
+
|
184
|
+
error_response = {
|
185
|
+
"error": {
|
186
|
+
"code": 500,
|
187
|
+
"message": "Internal server error",
|
188
|
+
"type": "general_error"
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
logger.error(f"General error in security middleware: {error}")
|
193
|
+
|
194
|
+
return JSONResponse(
|
195
|
+
status_code=500,
|
196
|
+
content=error_response
|
197
|
+
)
|