mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.1.0__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 +12 -0
- mcp_proxy_adapter/api/app.py +254 -33
- mcp_proxy_adapter/api/handlers.py +32 -6
- mcp_proxy_adapter/api/middleware/__init__.py +36 -30
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +243 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
- mcp_proxy_adapter/commands/__init__.py +19 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +66 -32
- mcp_proxy_adapter/commands/builtin_commands.py +95 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +711 -354
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/echo_command.py +81 -0
- mcp_proxy_adapter/commands/health_command.py +7 -0
- mcp_proxy_adapter/commands/help_command.py +21 -14
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/role_test_command.py +141 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/security_command.py +488 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +159 -2
- mcp_proxy_adapter/core/app_factory.py +326 -0
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +827 -0
- mcp_proxy_adapter/core/client_security.py +384 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +19 -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 +235 -0
- mcp_proxy_adapter/core/proxy_client.py +602 -0
- mcp_proxy_adapter/core/proxy_registration.py +522 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +370 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/security_integration.py +277 -0
- mcp_proxy_adapter/core/server_adapter.py +345 -0
- mcp_proxy_adapter/core/server_engine.py +364 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +233 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/README.md +230 -97
- mcp_proxy_adapter/examples/README_EN.md +258 -0
- mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
- mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
- mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
- mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
- mcp_proxy_adapter/examples/cert_config.json +9 -0
- mcp_proxy_adapter/examples/certs/admin.crt +32 -0
- mcp_proxy_adapter/examples/certs/admin.key +52 -0
- mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
- mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
- mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
- mcp_proxy_adapter/examples/certs/client.crt +32 -0
- mcp_proxy_adapter/examples/certs/client.key +52 -0
- mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
- mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
- mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
- mcp_proxy_adapter/examples/certs/client_user.key +52 -0
- mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
- mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
- mcp_proxy_adapter/examples/certs/readonly.key +52 -0
- mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/server.crt +32 -0
- mcp_proxy_adapter/examples/certs/server.key +52 -0
- mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
- mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
- mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
- mcp_proxy_adapter/examples/certs/user.crt +32 -0
- mcp_proxy_adapter/examples/certs/user.key +52 -0
- mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
- mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
- mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
- mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
- mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
- mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
- mcp_proxy_adapter/examples/commands/__init__.py +1 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
- mcp_proxy_adapter/examples/debug_request_state.py +144 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
- mcp_proxy_adapter/examples/demo_client.py +341 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
- mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
- mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
- mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
- mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
- mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
- mcp_proxy_adapter/examples/full_application/main.py +138 -0
- mcp_proxy_adapter/examples/full_application/roles.json +21 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
- mcp_proxy_adapter/examples/generate_certificates.py +121 -0
- mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
- mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
- mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
- mcp_proxy_adapter/examples/roles.json +38 -0
- mcp_proxy_adapter/examples/run_example.py +81 -0
- mcp_proxy_adapter/examples/run_security_tests.py +326 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
- mcp_proxy_adapter/examples/security_test_client.py +743 -0
- mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
- mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
- mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
- mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
- mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
- mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
- mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
- mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
- mcp_proxy_adapter/examples/test_examples.py +344 -0
- mcp_proxy_adapter/examples/universal_client.py +628 -0
- mcp_proxy_adapter/main.py +186 -0
- mcp_proxy_adapter/utils/config_generator.py +639 -0
- mcp_proxy_adapter/version.py +2 -1
- mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
- mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
- mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
- mcp_proxy_adapter/api/middleware/auth.py +0 -146
- mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter/examples/__init__.py +0 -7
- 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 -35
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
- mcp_proxy_adapter/examples/basic_server/server.py +0 -103
- 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 -250
- 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/config.json +0 -35
- 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/hooks.py +0 -230
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
- 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/schemas/base_schema.json +0 -114
- mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
- 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 -217
- mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
- mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Main entry point for MCP Proxy Adapter.
|
4
|
+
|
5
|
+
This module allows running the MCP Proxy Adapter as a module:
|
6
|
+
python -m mcp_proxy_adapter
|
7
|
+
"""
|
8
|
+
|
9
|
+
from mcp_proxy_adapter.main import main
|
10
|
+
|
11
|
+
if __name__ == "__main__":
|
12
|
+
main()
|
mcp_proxy_adapter/api/app.py
CHANGED
@@ -3,6 +3,8 @@ Module for FastAPI application setup.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import json
|
6
|
+
import ssl
|
7
|
+
from pathlib import Path
|
6
8
|
from typing import Any, Dict, List, Optional, Union
|
7
9
|
from contextlib import asynccontextmanager
|
8
10
|
|
@@ -17,39 +19,140 @@ from mcp_proxy_adapter.api.tools import get_tool_description, execute_tool
|
|
17
19
|
from mcp_proxy_adapter.config import config
|
18
20
|
from mcp_proxy_adapter.core.errors import MicroserviceError, NotFoundError
|
19
21
|
from mcp_proxy_adapter.core.logging import logger, RequestLogger
|
22
|
+
from mcp_proxy_adapter.core.ssl_utils import SSLUtils
|
20
23
|
from mcp_proxy_adapter.commands.command_registry import registry
|
21
24
|
from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
|
22
25
|
|
23
26
|
|
24
|
-
|
25
|
-
async def lifespan(app: FastAPI):
|
27
|
+
def create_lifespan(config_path: Optional[str] = None):
|
26
28
|
"""
|
27
|
-
|
29
|
+
Create lifespan manager for the FastAPI application.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
config_path: Path to configuration file (optional)
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
Lifespan context manager
|
36
|
+
"""
|
37
|
+
@asynccontextmanager
|
38
|
+
async def lifespan(app: FastAPI):
|
39
|
+
"""
|
40
|
+
Lifespan manager for the FastAPI application. Handles startup and shutdown events.
|
41
|
+
"""
|
42
|
+
# Startup events
|
43
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
44
|
+
from mcp_proxy_adapter.core.proxy_registration import register_with_proxy, unregister_from_proxy
|
45
|
+
|
46
|
+
# Initialize system using unified logic
|
47
|
+
# This will load config, register custom commands, and discover auto-commands
|
48
|
+
# Only reload config if not already loaded from the same path
|
49
|
+
if config_path:
|
50
|
+
init_result = await registry.reload_system(config_path=config_path)
|
51
|
+
else:
|
52
|
+
init_result = await registry.reload_system()
|
53
|
+
|
54
|
+
logger.info(f"Application started with {init_result['total_commands']} commands registered")
|
55
|
+
logger.info(f"System initialization result: {init_result}")
|
56
|
+
|
57
|
+
# Register with proxy if enabled
|
58
|
+
server_config = config.get("server", {})
|
59
|
+
server_host = server_config.get("host", "0.0.0.0")
|
60
|
+
server_port = server_config.get("port", 8000)
|
61
|
+
|
62
|
+
# Determine server URL based on SSL configuration
|
63
|
+
# Try security framework SSL config first
|
64
|
+
security_config = config.get("security", {})
|
65
|
+
ssl_config = security_config.get("ssl", {})
|
66
|
+
|
67
|
+
# Fallback to legacy SSL config
|
68
|
+
if not ssl_config.get("enabled", False):
|
69
|
+
ssl_config = config.get("ssl", {})
|
70
|
+
|
71
|
+
if ssl_config.get("enabled", False):
|
72
|
+
protocol = "https"
|
73
|
+
else:
|
74
|
+
protocol = "http"
|
75
|
+
|
76
|
+
# Use localhost for external access if host is 0.0.0.0
|
77
|
+
if server_host == "0.0.0.0":
|
78
|
+
server_host = "localhost"
|
79
|
+
|
80
|
+
server_url = f"{protocol}://{server_host}:{server_port}"
|
81
|
+
|
82
|
+
# Attempt proxy registration
|
83
|
+
registration_success = await register_with_proxy(server_url)
|
84
|
+
if registration_success:
|
85
|
+
logger.info("✅ Proxy registration completed successfully")
|
86
|
+
else:
|
87
|
+
logger.info("ℹ️ Proxy registration is disabled or failed")
|
88
|
+
|
89
|
+
yield # Application is running
|
90
|
+
|
91
|
+
# Shutdown events
|
92
|
+
logger.info("Application shutting down")
|
93
|
+
|
94
|
+
# Unregister from proxy if enabled
|
95
|
+
unregistration_success = await unregister_from_proxy()
|
96
|
+
if unregistration_success:
|
97
|
+
logger.info("✅ Proxy unregistration completed successfully")
|
98
|
+
else:
|
99
|
+
logger.warning("⚠️ Proxy unregistration failed or was disabled")
|
100
|
+
|
101
|
+
return lifespan
|
102
|
+
|
103
|
+
|
104
|
+
def create_ssl_context(app_config: Optional[Dict[str, Any]] = None) -> Optional[ssl.SSLContext]:
|
105
|
+
"""
|
106
|
+
Create SSL context based on configuration.
|
107
|
+
|
108
|
+
Args:
|
109
|
+
app_config: Application configuration dictionary (optional)
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
SSL context if SSL is enabled and properly configured, None otherwise
|
28
113
|
"""
|
29
|
-
|
30
|
-
from mcp_proxy_adapter.commands.command_registry import registry
|
31
|
-
from mcp_proxy_adapter.commands.help_command import HelpCommand
|
32
|
-
from mcp_proxy_adapter.commands.health_command import HealthCommand
|
114
|
+
current_config = app_config if app_config is not None else config.get_all()
|
33
115
|
|
34
|
-
#
|
35
|
-
|
36
|
-
|
116
|
+
# Try security framework SSL config first
|
117
|
+
security_config = current_config.get("security", {})
|
118
|
+
ssl_config = security_config.get("ssl", {})
|
37
119
|
|
38
|
-
|
39
|
-
|
120
|
+
# Fallback to legacy SSL config
|
121
|
+
if not ssl_config.get("enabled", False):
|
122
|
+
ssl_config = current_config.get("ssl", {})
|
40
123
|
|
41
|
-
|
42
|
-
|
124
|
+
if not ssl_config.get("enabled", False):
|
125
|
+
logger.info("SSL is disabled in configuration")
|
126
|
+
return None
|
43
127
|
|
44
|
-
|
128
|
+
cert_file = ssl_config.get("cert_file")
|
129
|
+
key_file = ssl_config.get("key_file")
|
45
130
|
|
46
|
-
|
131
|
+
if not cert_file or not key_file:
|
132
|
+
logger.warning("SSL enabled but certificate or key file not specified")
|
133
|
+
return None
|
47
134
|
|
48
|
-
|
49
|
-
|
135
|
+
try:
|
136
|
+
# Create SSL context using SSLUtils
|
137
|
+
ssl_context = SSLUtils.create_ssl_context(
|
138
|
+
cert_file=cert_file,
|
139
|
+
key_file=key_file,
|
140
|
+
ca_cert=ssl_config.get("ca_cert"),
|
141
|
+
verify_client=ssl_config.get("verify_client", False),
|
142
|
+
cipher_suites=ssl_config.get("cipher_suites", []),
|
143
|
+
min_tls_version=ssl_config.get("min_tls_version", "1.2"),
|
144
|
+
max_tls_version=ssl_config.get("max_tls_version", "1.3")
|
145
|
+
)
|
146
|
+
|
147
|
+
logger.info(f"SSL context created successfully for mode: {ssl_config.get('mode', 'https_only')}")
|
148
|
+
return ssl_context
|
149
|
+
|
150
|
+
except Exception as e:
|
151
|
+
logger.error(f"Failed to create SSL context: {e}")
|
152
|
+
return None
|
50
153
|
|
51
154
|
|
52
|
-
def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None) -> FastAPI:
|
155
|
+
def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None, app_config: Optional[Dict[str, Any]] = None, config_path: Optional[str] = None) -> FastAPI:
|
53
156
|
"""
|
54
157
|
Creates and configures FastAPI application.
|
55
158
|
|
@@ -57,10 +160,135 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
57
160
|
title: Application title (default: "MCP Proxy Adapter")
|
58
161
|
description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
|
59
162
|
version: Application version (default: "1.0.0")
|
163
|
+
app_config: Application configuration dictionary (optional)
|
164
|
+
config_path: Path to configuration file (optional)
|
60
165
|
|
61
166
|
Returns:
|
62
167
|
Configured FastAPI application.
|
168
|
+
|
169
|
+
Raises:
|
170
|
+
SystemExit: If authentication is enabled but required files are missing (security issue)
|
63
171
|
"""
|
172
|
+
# Use provided configuration or fallback to global config
|
173
|
+
current_config = app_config if app_config is not None else config.get_all()
|
174
|
+
|
175
|
+
# Debug: Check what config is passed to create_app
|
176
|
+
if app_config:
|
177
|
+
print(f"🔍 Debug: create_app received app_config keys: {list(app_config.keys())}")
|
178
|
+
if "security" in app_config:
|
179
|
+
ssl_config = app_config["security"].get("ssl", {})
|
180
|
+
print(f"🔍 Debug: create_app SSL config: enabled={ssl_config.get('enabled', False)}")
|
181
|
+
print(f"🔍 Debug: create_app SSL config: cert_file={ssl_config.get('cert_file')}")
|
182
|
+
print(f"🔍 Debug: create_app SSL config: key_file={ssl_config.get('key_file')}")
|
183
|
+
else:
|
184
|
+
print("🔍 Debug: create_app received no app_config, using global config")
|
185
|
+
|
186
|
+
# Security check: Validate all authentication configurations before startup
|
187
|
+
security_errors = []
|
188
|
+
|
189
|
+
print(f"🔍 Debug: current_config keys: {list(current_config.keys())}")
|
190
|
+
if "security" in current_config:
|
191
|
+
print(f"🔍 Debug: security config: {current_config['security']}")
|
192
|
+
if "roles" in current_config:
|
193
|
+
print(f"🔍 Debug: roles config: {current_config['roles']}")
|
194
|
+
|
195
|
+
# Check security framework configuration only if enabled
|
196
|
+
security_config = current_config.get("security", {})
|
197
|
+
if security_config.get("enabled", False):
|
198
|
+
# Validate security framework configuration
|
199
|
+
from mcp_proxy_adapter.core.unified_config_adapter import UnifiedConfigAdapter
|
200
|
+
adapter = UnifiedConfigAdapter()
|
201
|
+
validation_result = adapter.validate_configuration(current_config)
|
202
|
+
|
203
|
+
if not validation_result.is_valid:
|
204
|
+
security_errors.extend(validation_result.errors)
|
205
|
+
|
206
|
+
# Check SSL configuration within security framework
|
207
|
+
ssl_config = security_config.get("ssl", {})
|
208
|
+
if ssl_config.get("enabled", False):
|
209
|
+
cert_file = ssl_config.get("cert_file")
|
210
|
+
key_file = ssl_config.get("key_file")
|
211
|
+
|
212
|
+
print(f"🔍 Debug: api/app.py security.ssl: cert_file={cert_file}, key_file={key_file}")
|
213
|
+
print(f"🔍 Debug: api/app.py security.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
|
214
|
+
print(f"🔍 Debug: api/app.py security.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
|
215
|
+
|
216
|
+
if cert_file and not Path(cert_file).exists():
|
217
|
+
security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
|
218
|
+
|
219
|
+
if key_file and not Path(key_file).exists():
|
220
|
+
security_errors.append(f"SSL is enabled but private key file not found: {key_file}")
|
221
|
+
|
222
|
+
# Check mTLS configuration
|
223
|
+
ca_cert_file = ssl_config.get("ca_cert_file")
|
224
|
+
if ca_cert_file and not Path(ca_cert_file).exists():
|
225
|
+
security_errors.append(f"mTLS is enabled but CA certificate file not found: {ca_cert_file}")
|
226
|
+
|
227
|
+
# Legacy configuration checks for backward compatibility
|
228
|
+
roles_config = current_config.get("roles", {})
|
229
|
+
print(f"🔍 Debug: roles_config = {roles_config}")
|
230
|
+
if roles_config.get("enabled", False):
|
231
|
+
roles_config_path = roles_config.get("config_file", "schemas/roles_schema.json")
|
232
|
+
print(f"🔍 Debug: Checking roles file: {roles_config_path}")
|
233
|
+
if not Path(roles_config_path).exists():
|
234
|
+
security_errors.append(f"Roles are enabled but schema file not found: {roles_config_path}")
|
235
|
+
|
236
|
+
# Check new security framework permissions configuration
|
237
|
+
security_config = current_config.get("security", {})
|
238
|
+
permissions_config = security_config.get("permissions", {})
|
239
|
+
if permissions_config.get("enabled", False):
|
240
|
+
roles_file = permissions_config.get("roles_file")
|
241
|
+
if roles_file and not Path(roles_file).exists():
|
242
|
+
security_errors.append(f"Permissions are enabled but roles file not found: {roles_file}")
|
243
|
+
|
244
|
+
legacy_ssl_config = current_config.get("ssl", {})
|
245
|
+
if legacy_ssl_config.get("enabled", False):
|
246
|
+
# Check SSL certificate files
|
247
|
+
cert_file = legacy_ssl_config.get("cert_file")
|
248
|
+
key_file = legacy_ssl_config.get("key_file")
|
249
|
+
|
250
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: cert_file={cert_file}, key_file={key_file}")
|
251
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
|
252
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
|
253
|
+
|
254
|
+
if cert_file and not Path(cert_file).exists():
|
255
|
+
security_errors.append(f"Legacy SSL is enabled but certificate file not found: {cert_file}")
|
256
|
+
|
257
|
+
if key_file and not Path(key_file).exists():
|
258
|
+
security_errors.append(f"Legacy SSL is enabled but private key file not found: {key_file}")
|
259
|
+
|
260
|
+
# Check mTLS configuration
|
261
|
+
if legacy_ssl_config.get("mode") == "mtls":
|
262
|
+
ca_cert = legacy_ssl_config.get("ca_cert")
|
263
|
+
if ca_cert and not Path(ca_cert).exists():
|
264
|
+
security_errors.append(f"Legacy mTLS is enabled but CA certificate file not found: {ca_cert}")
|
265
|
+
|
266
|
+
# Check token authentication configuration
|
267
|
+
token_auth_config = legacy_ssl_config.get("token_auth", {})
|
268
|
+
if token_auth_config.get("enabled", False):
|
269
|
+
tokens_file = token_auth_config.get("tokens_file", "tokens.json")
|
270
|
+
if not Path(tokens_file).exists():
|
271
|
+
security_errors.append(f"Token authentication is enabled but tokens file not found: {tokens_file}")
|
272
|
+
|
273
|
+
# Check general authentication
|
274
|
+
if current_config.get("auth_enabled", False):
|
275
|
+
# If auth is enabled, check if any authentication method is properly configured
|
276
|
+
ssl_enabled = legacy_ssl_config.get("enabled", False)
|
277
|
+
roles_enabled = roles_config.get("enabled", False)
|
278
|
+
token_auth_enabled = token_auth_config.get("enabled", False)
|
279
|
+
|
280
|
+
if not (ssl_enabled or roles_enabled or token_auth_enabled):
|
281
|
+
security_errors.append("Authentication is enabled but no authentication method is properly configured")
|
282
|
+
|
283
|
+
# If there are security errors, block startup
|
284
|
+
if security_errors:
|
285
|
+
logger.critical("CRITICAL SECURITY ERROR: Authentication configuration issues detected:")
|
286
|
+
for error in security_errors:
|
287
|
+
logger.critical(f" - {error}")
|
288
|
+
logger.critical("Server startup blocked for security reasons.")
|
289
|
+
logger.critical("Please fix authentication configuration or disable authentication features.")
|
290
|
+
raise SystemExit(1)
|
291
|
+
|
64
292
|
# Use provided parameters or defaults
|
65
293
|
app_title = title or "MCP Proxy Adapter"
|
66
294
|
app_description = description or "JSON-RPC API for interacting with MCP Proxy"
|
@@ -73,7 +301,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
73
301
|
version=app_version,
|
74
302
|
docs_url="/docs",
|
75
303
|
redoc_url="/redoc",
|
76
|
-
lifespan=
|
304
|
+
lifespan=create_lifespan(config_path),
|
77
305
|
)
|
78
306
|
|
79
307
|
# Configure CORS
|
@@ -86,7 +314,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
86
314
|
)
|
87
315
|
|
88
316
|
# Setup middleware using the new middleware package
|
89
|
-
setup_middleware(app)
|
317
|
+
setup_middleware(app, current_config)
|
90
318
|
|
91
319
|
# Use custom OpenAPI schema
|
92
320
|
app.openapi = lambda: custom_openapi_with_fallback(app)
|
@@ -132,7 +360,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
132
360
|
return await handle_batch_json_rpc(request_data, request)
|
133
361
|
else:
|
134
362
|
# Process single request
|
135
|
-
return await handle_json_rpc(request_data, request_id)
|
363
|
+
return await handle_json_rpc(request_data, request_id, request)
|
136
364
|
|
137
365
|
# Command execution endpoint (/cmd)
|
138
366
|
@app.post("/cmd")
|
@@ -168,7 +396,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
168
396
|
# Determine request format (CommandRequest or JSON-RPC)
|
169
397
|
if "jsonrpc" in command_data and "method" in command_data:
|
170
398
|
# JSON-RPC format
|
171
|
-
return await handle_json_rpc(command_data, request_id)
|
399
|
+
return await handle_json_rpc(command_data, request_id, request)
|
172
400
|
|
173
401
|
# CommandRequest format
|
174
402
|
if "command" not in command_data:
|
@@ -186,7 +414,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
186
414
|
command_name = command_data["command"]
|
187
415
|
params = command_data.get("params", {})
|
188
416
|
|
189
|
-
req_logger.
|
417
|
+
req_logger.debug(f"Executing command via /cmd: {command_name}, params: {params}")
|
190
418
|
|
191
419
|
# Check if command exists
|
192
420
|
if not registry.command_exists(command_name):
|
@@ -203,7 +431,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
203
431
|
|
204
432
|
# Execute command
|
205
433
|
try:
|
206
|
-
result = await execute_command(command_name, params, request_id)
|
434
|
+
result = await execute_command(command_name, params, request_id, request)
|
207
435
|
return {"result": result}
|
208
436
|
except MicroserviceError as e:
|
209
437
|
# Handle command execution errors
|
@@ -270,7 +498,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
270
498
|
request_id = getattr(request.state, "request_id", None)
|
271
499
|
|
272
500
|
try:
|
273
|
-
result = await execute_command(command_name, params, request_id)
|
501
|
+
result = await execute_command(command_name, params, request_id, request)
|
274
502
|
return result
|
275
503
|
except MicroserviceError as e:
|
276
504
|
# Convert to proper HTTP status code
|
@@ -441,10 +669,3 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
441
669
|
)
|
442
670
|
|
443
671
|
return app
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
# Create global application instance
|
450
|
-
app = create_app()
|
@@ -17,7 +17,7 @@ from mcp_proxy_adapter.core.errors import (
|
|
17
17
|
from mcp_proxy_adapter.core.logging import logger, RequestLogger, get_logger
|
18
18
|
|
19
19
|
|
20
|
-
async def execute_command(command_name: str, params: Dict[str, Any], request_id: Optional[str] = None) -> Dict[str, Any]:
|
20
|
+
async def execute_command(command_name: str, params: Dict[str, Any], request_id: Optional[str] = None, request: Optional[Request] = None) -> Dict[str, Any]:
|
21
21
|
"""
|
22
22
|
Executes a command with the specified name and parameters.
|
23
23
|
|
@@ -39,17 +39,43 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
|
|
39
39
|
try:
|
40
40
|
log.info(f"Executing command: {command_name}")
|
41
41
|
|
42
|
+
# Execute before command hooks
|
43
|
+
try:
|
44
|
+
from mcp_proxy_adapter.commands.hooks import hooks
|
45
|
+
hooks.execute_before_command_hooks(command_name, params)
|
46
|
+
log.debug(f"Executed before command hooks for: {command_name}")
|
47
|
+
except Exception as e:
|
48
|
+
log.warning(f"Failed to execute before command hooks: {e}")
|
49
|
+
|
42
50
|
# Get command class from registry and execute with parameters
|
43
51
|
start_time = time.time()
|
44
52
|
|
45
53
|
# Use Command.run that handles instances with dependencies properly
|
46
|
-
command_class = registry.
|
47
|
-
|
54
|
+
command_class = registry.get_command(command_name)
|
55
|
+
|
56
|
+
# Create context with user info from request state
|
57
|
+
context = {}
|
58
|
+
if request and hasattr(request.state, 'user_id'):
|
59
|
+
context['user'] = {
|
60
|
+
'id': getattr(request.state, 'user_id', None),
|
61
|
+
'role': getattr(request.state, 'user_role', 'guest'),
|
62
|
+
'roles': getattr(request.state, 'user_roles', ['guest']),
|
63
|
+
'permissions': getattr(request.state, 'user_permissions', ['read'])
|
64
|
+
}
|
65
|
+
|
66
|
+
result = await command_class.run(**params, context=context)
|
48
67
|
|
49
68
|
execution_time = time.time() - start_time
|
50
69
|
|
51
70
|
log.info(f"Command '{command_name}' executed in {execution_time:.3f} sec")
|
52
71
|
|
72
|
+
# Execute after command hooks
|
73
|
+
try:
|
74
|
+
hooks.execute_after_command_hooks(command_name, params, result)
|
75
|
+
log.debug(f"Executed after command hooks for: {command_name}")
|
76
|
+
except Exception as e:
|
77
|
+
log.warning(f"Failed to execute after command hooks: {e}")
|
78
|
+
|
53
79
|
# Return result
|
54
80
|
return result.to_dict()
|
55
81
|
except NotFoundError as e:
|
@@ -82,13 +108,13 @@ async def handle_batch_json_rpc(batch_requests: List[Dict[str, Any]], request: O
|
|
82
108
|
|
83
109
|
for request_data in batch_requests:
|
84
110
|
# Process each request in the batch
|
85
|
-
response = await handle_json_rpc(request_data, request_id)
|
111
|
+
response = await handle_json_rpc(request_data, request_id, request)
|
86
112
|
responses.append(response)
|
87
113
|
|
88
114
|
return responses
|
89
115
|
|
90
116
|
|
91
|
-
async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str] = None) -> Dict[str, Any]:
|
117
|
+
async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str] = None, request: Optional[Request] = None) -> Dict[str, Any]:
|
92
118
|
"""
|
93
119
|
Handles JSON-RPC request.
|
94
120
|
|
@@ -124,7 +150,7 @@ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str
|
|
124
150
|
|
125
151
|
try:
|
126
152
|
# Execute command
|
127
|
-
result = await execute_command(method, params, request_id)
|
153
|
+
result = await execute_command(method, params, request_id, request)
|
128
154
|
|
129
155
|
# Form successful response
|
130
156
|
return {
|
@@ -3,47 +3,53 @@ Middleware package for API.
|
|
3
3
|
This package contains middleware components for request processing.
|
4
4
|
"""
|
5
5
|
|
6
|
+
from typing import Dict, Any, Optional
|
6
7
|
from fastapi import FastAPI
|
7
8
|
|
8
9
|
from mcp_proxy_adapter.core.logging import logger
|
10
|
+
from mcp_proxy_adapter.config import config
|
9
11
|
from .base import BaseMiddleware
|
10
|
-
from .
|
11
|
-
from .
|
12
|
-
from .auth import AuthMiddleware
|
13
|
-
from .rate_limit import RateLimitMiddleware
|
14
|
-
from .performance import PerformanceMiddleware
|
12
|
+
from .factory import MiddlewareFactory
|
13
|
+
from .protocol_middleware import setup_protocol_middleware
|
15
14
|
|
16
|
-
def setup_middleware(app: FastAPI) -> None:
|
15
|
+
def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None) -> None:
|
17
16
|
"""
|
18
|
-
Sets up middleware for application.
|
17
|
+
Sets up middleware for application using the new middleware factory.
|
19
18
|
|
20
19
|
Args:
|
21
20
|
app: FastAPI application instance.
|
21
|
+
app_config: Application configuration dictionary (optional)
|
22
22
|
"""
|
23
|
-
#
|
24
|
-
|
23
|
+
# Use provided configuration or fallback to global config
|
24
|
+
current_config = app_config if app_config is not None else config.get_all()
|
25
25
|
|
26
|
-
#
|
27
|
-
app
|
26
|
+
# Create middleware factory
|
27
|
+
factory = MiddlewareFactory(app, current_config)
|
28
28
|
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
RateLimitMiddleware,
|
34
|
-
rate_limit=config.get("rate_limit", 100),
|
35
|
-
time_window=config.get("rate_limit_window", 60)
|
36
|
-
)
|
29
|
+
# Validate middleware configuration
|
30
|
+
if not factory.validate_middleware_config():
|
31
|
+
logger.error("Middleware configuration validation failed")
|
32
|
+
raise SystemExit(1)
|
37
33
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
34
|
+
logger.info("Using unified security middleware")
|
35
|
+
middleware_list = factory.create_all_middleware()
|
36
|
+
|
37
|
+
# Add middleware to application
|
38
|
+
for middleware in middleware_list:
|
39
|
+
# For ASGI middleware, we need to wrap the application
|
40
|
+
if hasattr(middleware, 'dispatch'):
|
41
|
+
# This is a proper ASGI middleware
|
42
|
+
app.middleware("http")(middleware.dispatch)
|
43
|
+
else:
|
44
|
+
logger.warning(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
|
45
|
+
|
46
|
+
# Add protocol middleware (always needed)
|
47
|
+
setup_protocol_middleware(app)
|
48
48
|
|
49
|
-
|
49
|
+
# Log middleware information
|
50
|
+
middleware_info = factory.get_middleware_info()
|
51
|
+
logger.info(f"Middleware setup completed:")
|
52
|
+
logger.info(f" - Total middleware: {middleware_info['total_middleware']}")
|
53
|
+
logger.info(f" - Types: {', '.join(middleware_info['middleware_types'])}")
|
54
|
+
logger.info(f" - Security enabled: {middleware_info['security_enabled']}")
|
55
|
+
|