mcp-proxy-adapter 4.1.1__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 +32 -0
- mcp_proxy_adapter/api/app.py +290 -33
- mcp_proxy_adapter/api/handlers.py +32 -6
- mcp_proxy_adapter/api/middleware/__init__.py +38 -32
- 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 +201 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- 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 +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 +8 -1
- 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 +366 -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 +394 -14
- mcp_proxy_adapter/core/app_factory.py +410 -0
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +1045 -0
- 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/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 +385 -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 +286 -0
- mcp_proxy_adapter/core/server_adapter.py +282 -0
- mcp_proxy_adapter/core/server_engine.py +270 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +234 -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/__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 +93 -0
- 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-4.1.1.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/rate_limit.py +0 -152
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- 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 -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.0.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
"""Main entry point for MCP Proxy Adapter CLI.
|
2
|
+
|
3
|
+
This module provides a command-line interface for running
|
4
|
+
MCP Proxy Adapter applications.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import sys
|
8
|
+
from pathlib import Path
|
9
|
+
|
10
|
+
# Add the current directory to Python path for imports
|
11
|
+
current_dir = Path(__file__).parent
|
12
|
+
sys.path.insert(0, str(current_dir))
|
13
|
+
|
14
|
+
from mcp_proxy_adapter.api.app import create_app
|
15
|
+
|
16
|
+
|
17
|
+
def main():
|
18
|
+
"""Main CLI entry point."""
|
19
|
+
print("MCP Proxy Adapter v6.2.21")
|
20
|
+
print("========================")
|
21
|
+
print()
|
22
|
+
print("Usage:")
|
23
|
+
print(" python -m mcp_proxy_adapter")
|
24
|
+
print(" # or")
|
25
|
+
print(" mcp-proxy-adapter")
|
26
|
+
print()
|
27
|
+
print("For more information, see:")
|
28
|
+
print(" https://github.com/maverikod/mcp-proxy-adapter#readme")
|
29
|
+
|
30
|
+
|
31
|
+
if __name__ == "__main__":
|
32
|
+
main()
|
mcp_proxy_adapter/api/app.py
CHANGED
@@ -3,8 +3,11 @@ 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
|
10
|
+
import asyncio
|
8
11
|
|
9
12
|
from fastapi import FastAPI, Body, Depends, HTTPException, Request
|
10
13
|
from fastapi.responses import JSONResponse, Response
|
@@ -17,39 +20,164 @@ from mcp_proxy_adapter.api.tools import get_tool_description, execute_tool
|
|
17
20
|
from mcp_proxy_adapter.config import config
|
18
21
|
from mcp_proxy_adapter.core.errors import MicroserviceError, NotFoundError
|
19
22
|
from mcp_proxy_adapter.core.logging import logger, RequestLogger
|
23
|
+
from mcp_proxy_adapter.core.ssl_utils import SSLUtils
|
20
24
|
from mcp_proxy_adapter.commands.command_registry import registry
|
21
25
|
from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
|
22
26
|
|
23
27
|
|
24
|
-
|
25
|
-
async def lifespan(app: FastAPI):
|
28
|
+
def create_lifespan(config_path: Optional[str] = None):
|
26
29
|
"""
|
27
|
-
|
30
|
+
Create lifespan manager for the FastAPI application.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
config_path: Path to configuration file (optional)
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
Lifespan context manager
|
28
37
|
"""
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
38
|
+
@asynccontextmanager
|
39
|
+
async def lifespan(app: FastAPI):
|
40
|
+
"""
|
41
|
+
Lifespan manager for the FastAPI application. Handles startup and shutdown events.
|
42
|
+
"""
|
43
|
+
# Startup events
|
44
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
45
|
+
from mcp_proxy_adapter.core.proxy_registration import (
|
46
|
+
register_with_proxy,
|
47
|
+
unregister_from_proxy,
|
48
|
+
initialize_proxy_registration,
|
49
|
+
)
|
50
|
+
|
51
|
+
# Initialize proxy registration manager WITH CURRENT CONFIG before reload_system
|
52
|
+
# so that registration inside reload_system can work
|
53
|
+
try:
|
54
|
+
initialize_proxy_registration(config.get_all())
|
55
|
+
except Exception as e:
|
56
|
+
logger.error(f"Failed to initialize proxy registration: {e}")
|
57
|
+
|
58
|
+
# Initialize system using unified logic
|
59
|
+
# This will load config, register custom commands, and discover auto-commands
|
60
|
+
# Only reload config if not already loaded from the same path
|
61
|
+
if config_path:
|
62
|
+
init_result = await registry.reload_system(config_path=config_path)
|
63
|
+
else:
|
64
|
+
init_result = await registry.reload_system()
|
65
|
+
|
66
|
+
logger.info(f"Application started with {init_result['total_commands']} commands registered")
|
67
|
+
logger.info(f"System initialization result: {init_result}")
|
68
|
+
|
69
|
+
# Initialize proxy registration manager with current config
|
70
|
+
try:
|
71
|
+
initialize_proxy_registration(config.get_all())
|
72
|
+
except Exception as e:
|
73
|
+
logger.error(f"Failed to initialize proxy registration: {e}")
|
74
|
+
|
75
|
+
# Register with proxy if enabled (run slightly delayed to ensure server is accepting connections)
|
76
|
+
server_config = config.get("server", {})
|
77
|
+
server_host = server_config.get("host", "0.0.0.0")
|
78
|
+
server_port = server_config.get("port", 8000)
|
79
|
+
|
80
|
+
# Determine server URL based on SSL configuration
|
81
|
+
# Try security framework SSL config first
|
82
|
+
security_config = config.get("security", {})
|
83
|
+
ssl_config = security_config.get("ssl", {})
|
84
|
+
|
85
|
+
# Fallback to legacy SSL config
|
86
|
+
if not ssl_config.get("enabled", False):
|
87
|
+
ssl_config = config.get("ssl", {})
|
88
|
+
|
89
|
+
if ssl_config.get("enabled", False):
|
90
|
+
protocol = "https"
|
91
|
+
else:
|
92
|
+
protocol = "http"
|
93
|
+
|
94
|
+
# Use localhost for external access if host is 0.0.0.0
|
95
|
+
if server_host == "0.0.0.0":
|
96
|
+
server_host = "localhost"
|
97
|
+
|
98
|
+
server_url = f"{protocol}://{server_host}:{server_port}"
|
99
|
+
|
100
|
+
# Attempt proxy registration in background with small delay
|
101
|
+
async def _delayed_register():
|
102
|
+
try:
|
103
|
+
await asyncio.sleep(0.5)
|
104
|
+
success = await register_with_proxy(server_url)
|
105
|
+
if success:
|
106
|
+
logger.info("✅ Proxy registration completed successfully")
|
107
|
+
else:
|
108
|
+
logger.info("ℹ️ Proxy registration is disabled or failed")
|
109
|
+
except Exception as e:
|
110
|
+
logger.error(f"Proxy registration failed: {e}")
|
111
|
+
|
112
|
+
asyncio.create_task(_delayed_register())
|
113
|
+
|
114
|
+
yield # Application is running
|
115
|
+
|
116
|
+
# Shutdown events
|
117
|
+
logger.info("Application shutting down")
|
118
|
+
|
119
|
+
# Unregister from proxy if enabled
|
120
|
+
unregistration_success = await unregister_from_proxy()
|
121
|
+
if unregistration_success:
|
122
|
+
logger.info("✅ Proxy unregistration completed successfully")
|
123
|
+
else:
|
124
|
+
logger.warning("⚠️ Proxy unregistration failed or was disabled")
|
33
125
|
|
34
|
-
|
35
|
-
|
36
|
-
|
126
|
+
return lifespan
|
127
|
+
|
128
|
+
|
129
|
+
def create_ssl_context(app_config: Optional[Dict[str, Any]] = None) -> Optional[ssl.SSLContext]:
|
130
|
+
"""
|
131
|
+
Create SSL context based on configuration.
|
37
132
|
|
38
|
-
|
39
|
-
|
133
|
+
Args:
|
134
|
+
app_config: Application configuration dictionary (optional)
|
40
135
|
|
41
|
-
|
42
|
-
|
136
|
+
Returns:
|
137
|
+
SSL context if SSL is enabled and properly configured, None otherwise
|
138
|
+
"""
|
139
|
+
current_config = app_config if app_config is not None else config.get_all()
|
43
140
|
|
44
|
-
|
141
|
+
# Try security framework SSL config first
|
142
|
+
security_config = current_config.get("security", {})
|
143
|
+
ssl_config = security_config.get("ssl", {})
|
45
144
|
|
46
|
-
|
145
|
+
# Fallback to legacy SSL config
|
146
|
+
if not ssl_config.get("enabled", False):
|
147
|
+
ssl_config = current_config.get("ssl", {})
|
47
148
|
|
48
|
-
|
49
|
-
|
149
|
+
if not ssl_config.get("enabled", False):
|
150
|
+
logger.info("SSL is disabled in configuration")
|
151
|
+
return None
|
152
|
+
|
153
|
+
cert_file = ssl_config.get("cert_file")
|
154
|
+
key_file = ssl_config.get("key_file")
|
155
|
+
|
156
|
+
if not cert_file or not key_file:
|
157
|
+
logger.warning("SSL enabled but certificate or key file not specified")
|
158
|
+
return None
|
159
|
+
|
160
|
+
try:
|
161
|
+
# Create SSL context using SSLUtils
|
162
|
+
ssl_context = SSLUtils.create_ssl_context(
|
163
|
+
cert_file=cert_file,
|
164
|
+
key_file=key_file,
|
165
|
+
ca_cert=ssl_config.get("ca_cert"),
|
166
|
+
verify_client=ssl_config.get("verify_client", False),
|
167
|
+
cipher_suites=ssl_config.get("cipher_suites", []),
|
168
|
+
min_tls_version=ssl_config.get("min_tls_version", "1.2"),
|
169
|
+
max_tls_version=ssl_config.get("max_tls_version", "1.3")
|
170
|
+
)
|
171
|
+
|
172
|
+
logger.info(f"SSL context created successfully for mode: {ssl_config.get('mode', 'https_only')}")
|
173
|
+
return ssl_context
|
174
|
+
|
175
|
+
except Exception as e:
|
176
|
+
logger.error(f"Failed to create SSL context: {e}")
|
177
|
+
return None
|
50
178
|
|
51
179
|
|
52
|
-
def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None) -> FastAPI:
|
180
|
+
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
181
|
"""
|
54
182
|
Creates and configures FastAPI application.
|
55
183
|
|
@@ -57,10 +185,146 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
57
185
|
title: Application title (default: "MCP Proxy Adapter")
|
58
186
|
description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
|
59
187
|
version: Application version (default: "1.0.0")
|
188
|
+
app_config: Application configuration dictionary (optional)
|
189
|
+
config_path: Path to configuration file (optional)
|
60
190
|
|
61
191
|
Returns:
|
62
192
|
Configured FastAPI application.
|
193
|
+
|
194
|
+
Raises:
|
195
|
+
SystemExit: If authentication is enabled but required files are missing (security issue)
|
63
196
|
"""
|
197
|
+
# Use provided configuration or fallback to global config
|
198
|
+
if app_config is not None:
|
199
|
+
if hasattr(app_config, 'get_all'):
|
200
|
+
current_config = app_config.get_all()
|
201
|
+
elif hasattr(app_config, 'keys'):
|
202
|
+
current_config = app_config
|
203
|
+
else:
|
204
|
+
current_config = config.get_all()
|
205
|
+
else:
|
206
|
+
current_config = config.get_all()
|
207
|
+
|
208
|
+
# Debug: Check what config is passed to create_app
|
209
|
+
if app_config:
|
210
|
+
if hasattr(app_config, 'keys'):
|
211
|
+
print(f"🔍 Debug: create_app received app_config keys: {list(app_config.keys())}")
|
212
|
+
if "security" in app_config:
|
213
|
+
ssl_config = app_config["security"].get("ssl", {})
|
214
|
+
print(f"🔍 Debug: create_app SSL config: enabled={ssl_config.get('enabled', False)}")
|
215
|
+
print(f"🔍 Debug: create_app SSL config: cert_file={ssl_config.get('cert_file')}")
|
216
|
+
print(f"🔍 Debug: create_app SSL config: key_file={ssl_config.get('key_file')}")
|
217
|
+
else:
|
218
|
+
print(f"🔍 Debug: create_app received app_config type: {type(app_config)}")
|
219
|
+
else:
|
220
|
+
print("🔍 Debug: create_app received no app_config, using global config")
|
221
|
+
|
222
|
+
# Security check: Validate all authentication configurations before startup
|
223
|
+
security_errors = []
|
224
|
+
|
225
|
+
print(f"🔍 Debug: current_config keys: {list(current_config.keys())}")
|
226
|
+
if "security" in current_config:
|
227
|
+
print(f"🔍 Debug: security config: {current_config['security']}")
|
228
|
+
if "roles" in current_config:
|
229
|
+
print(f"🔍 Debug: roles config: {current_config['roles']}")
|
230
|
+
|
231
|
+
# Check security framework configuration only if enabled
|
232
|
+
security_config = current_config.get("security", {})
|
233
|
+
if security_config.get("enabled", False):
|
234
|
+
# Validate security framework configuration
|
235
|
+
from mcp_proxy_adapter.core.unified_config_adapter import UnifiedConfigAdapter
|
236
|
+
adapter = UnifiedConfigAdapter()
|
237
|
+
validation_result = adapter.validate_configuration(current_config)
|
238
|
+
|
239
|
+
if not validation_result.is_valid:
|
240
|
+
security_errors.extend(validation_result.errors)
|
241
|
+
|
242
|
+
# Check SSL configuration within security framework
|
243
|
+
ssl_config = security_config.get("ssl", {})
|
244
|
+
if ssl_config.get("enabled", False):
|
245
|
+
cert_file = ssl_config.get("cert_file")
|
246
|
+
key_file = ssl_config.get("key_file")
|
247
|
+
|
248
|
+
print(f"🔍 Debug: api/app.py security.ssl: cert_file={cert_file}, key_file={key_file}")
|
249
|
+
print(f"🔍 Debug: api/app.py security.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
|
250
|
+
print(f"🔍 Debug: api/app.py security.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
|
251
|
+
|
252
|
+
if cert_file and not Path(cert_file).exists():
|
253
|
+
security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
|
254
|
+
|
255
|
+
if key_file and not Path(key_file).exists():
|
256
|
+
security_errors.append(f"SSL is enabled but private key file not found: {key_file}")
|
257
|
+
|
258
|
+
# Check mTLS configuration
|
259
|
+
ca_cert_file = ssl_config.get("ca_cert_file")
|
260
|
+
if ca_cert_file and not Path(ca_cert_file).exists():
|
261
|
+
security_errors.append(f"mTLS is enabled but CA certificate file not found: {ca_cert_file}")
|
262
|
+
|
263
|
+
# Legacy configuration checks for backward compatibility
|
264
|
+
roles_config = current_config.get("roles", {})
|
265
|
+
print(f"🔍 Debug: roles_config = {roles_config}")
|
266
|
+
if roles_config.get("enabled", False):
|
267
|
+
roles_config_path = roles_config.get("config_file", "schemas/roles_schema.json")
|
268
|
+
print(f"🔍 Debug: Checking roles file: {roles_config_path}")
|
269
|
+
if not Path(roles_config_path).exists():
|
270
|
+
security_errors.append(f"Roles are enabled but schema file not found: {roles_config_path}")
|
271
|
+
|
272
|
+
# Check new security framework permissions configuration
|
273
|
+
security_config = current_config.get("security", {})
|
274
|
+
permissions_config = security_config.get("permissions", {})
|
275
|
+
if permissions_config.get("enabled", False):
|
276
|
+
roles_file = permissions_config.get("roles_file")
|
277
|
+
if roles_file and not Path(roles_file).exists():
|
278
|
+
security_errors.append(f"Permissions are enabled but roles file not found: {roles_file}")
|
279
|
+
|
280
|
+
legacy_ssl_config = current_config.get("ssl", {})
|
281
|
+
if legacy_ssl_config.get("enabled", False):
|
282
|
+
# Check SSL certificate files
|
283
|
+
cert_file = legacy_ssl_config.get("cert_file")
|
284
|
+
key_file = legacy_ssl_config.get("key_file")
|
285
|
+
|
286
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: cert_file={cert_file}, key_file={key_file}")
|
287
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
|
288
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
|
289
|
+
|
290
|
+
if cert_file and not Path(cert_file).exists():
|
291
|
+
security_errors.append(f"Legacy SSL is enabled but certificate file not found: {cert_file}")
|
292
|
+
|
293
|
+
if key_file and not Path(key_file).exists():
|
294
|
+
security_errors.append(f"Legacy SSL is enabled but private key file not found: {key_file}")
|
295
|
+
|
296
|
+
# Check mTLS configuration
|
297
|
+
if legacy_ssl_config.get("mode") == "mtls":
|
298
|
+
ca_cert = legacy_ssl_config.get("ca_cert")
|
299
|
+
if ca_cert and not Path(ca_cert).exists():
|
300
|
+
security_errors.append(f"Legacy mTLS is enabled but CA certificate file not found: {ca_cert}")
|
301
|
+
|
302
|
+
# Check token authentication configuration
|
303
|
+
token_auth_config = legacy_ssl_config.get("token_auth", {})
|
304
|
+
if token_auth_config.get("enabled", False):
|
305
|
+
tokens_file = token_auth_config.get("tokens_file", "tokens.json")
|
306
|
+
if not Path(tokens_file).exists():
|
307
|
+
security_errors.append(f"Token authentication is enabled but tokens file not found: {tokens_file}")
|
308
|
+
|
309
|
+
# Check general authentication
|
310
|
+
if current_config.get("auth_enabled", False):
|
311
|
+
# If auth is enabled, check if any authentication method is properly configured
|
312
|
+
ssl_enabled = legacy_ssl_config.get("enabled", False)
|
313
|
+
roles_enabled = roles_config.get("enabled", False)
|
314
|
+
token_auth_enabled = token_auth_config.get("enabled", False)
|
315
|
+
|
316
|
+
if not (ssl_enabled or roles_enabled or token_auth_enabled):
|
317
|
+
security_errors.append("Authentication is enabled but no authentication method is properly configured")
|
318
|
+
|
319
|
+
# If there are security errors, block startup
|
320
|
+
if security_errors:
|
321
|
+
logger.critical("CRITICAL SECURITY ERROR: Authentication configuration issues detected:")
|
322
|
+
for error in security_errors:
|
323
|
+
logger.critical(f" - {error}")
|
324
|
+
logger.critical("Server startup blocked for security reasons.")
|
325
|
+
logger.critical("Please fix authentication configuration or disable authentication features.")
|
326
|
+
raise SystemExit(1)
|
327
|
+
|
64
328
|
# Use provided parameters or defaults
|
65
329
|
app_title = title or "MCP Proxy Adapter"
|
66
330
|
app_description = description or "JSON-RPC API for interacting with MCP Proxy"
|
@@ -73,7 +337,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
73
337
|
version=app_version,
|
74
338
|
docs_url="/docs",
|
75
339
|
redoc_url="/redoc",
|
76
|
-
lifespan=
|
340
|
+
lifespan=create_lifespan(config_path),
|
77
341
|
)
|
78
342
|
|
79
343
|
# Configure CORS
|
@@ -86,7 +350,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
86
350
|
)
|
87
351
|
|
88
352
|
# Setup middleware using the new middleware package
|
89
|
-
setup_middleware(app)
|
353
|
+
setup_middleware(app, current_config)
|
90
354
|
|
91
355
|
# Use custom OpenAPI schema
|
92
356
|
app.openapi = lambda: custom_openapi_with_fallback(app)
|
@@ -132,7 +396,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
132
396
|
return await handle_batch_json_rpc(request_data, request)
|
133
397
|
else:
|
134
398
|
# Process single request
|
135
|
-
return await handle_json_rpc(request_data, request_id)
|
399
|
+
return await handle_json_rpc(request_data, request_id, request)
|
136
400
|
|
137
401
|
# Command execution endpoint (/cmd)
|
138
402
|
@app.post("/cmd")
|
@@ -168,7 +432,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
168
432
|
# Determine request format (CommandRequest or JSON-RPC)
|
169
433
|
if "jsonrpc" in command_data and "method" in command_data:
|
170
434
|
# JSON-RPC format
|
171
|
-
return await handle_json_rpc(command_data, request_id)
|
435
|
+
return await handle_json_rpc(command_data, request_id, request)
|
172
436
|
|
173
437
|
# CommandRequest format
|
174
438
|
if "command" not in command_data:
|
@@ -186,7 +450,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
186
450
|
command_name = command_data["command"]
|
187
451
|
params = command_data.get("params", {})
|
188
452
|
|
189
|
-
req_logger.
|
453
|
+
req_logger.debug(f"Executing command via /cmd: {command_name}, params: {params}")
|
190
454
|
|
191
455
|
# Check if command exists
|
192
456
|
if not registry.command_exists(command_name):
|
@@ -203,7 +467,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
203
467
|
|
204
468
|
# Execute command
|
205
469
|
try:
|
206
|
-
result = await execute_command(command_name, params, request_id)
|
470
|
+
result = await execute_command(command_name, params, request_id, request)
|
207
471
|
return {"result": result}
|
208
472
|
except MicroserviceError as e:
|
209
473
|
# Handle command execution errors
|
@@ -270,7 +534,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
270
534
|
request_id = getattr(request.state, "request_id", None)
|
271
535
|
|
272
536
|
try:
|
273
|
-
result = await execute_command(command_name, params, request_id)
|
537
|
+
result = await execute_command(command_name, params, request_id, request)
|
274
538
|
return result
|
275
539
|
except MicroserviceError as e:
|
276
540
|
# Convert to proper HTTP status code
|
@@ -441,10 +705,3 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
441
705
|
)
|
442
706
|
|
443
707
|
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
|
-
# Add
|
27
|
-
app
|
28
|
-
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
26
|
+
# Add protocol middleware FIRST (before other middleware)
|
27
|
+
setup_protocol_middleware(app, current_config)
|
28
|
+
|
29
|
+
# Create middleware factory
|
30
|
+
factory = MiddlewareFactory(app, current_config)
|
31
|
+
|
32
|
+
# Validate middleware configuration
|
33
|
+
if not factory.validate_middleware_config():
|
34
|
+
logger.error("Middleware configuration validation failed")
|
35
|
+
raise SystemExit(1)
|
36
|
+
|
37
|
+
logger.info("Using unified security middleware")
|
38
|
+
middleware_list = factory.create_all_middleware()
|
39
|
+
|
40
|
+
# Add middleware to application AFTER protocol middleware
|
41
|
+
for middleware in middleware_list:
|
42
|
+
# For ASGI middleware, we need to wrap the application
|
43
|
+
if hasattr(middleware, 'dispatch'):
|
44
|
+
# This is a proper ASGI middleware
|
45
|
+
app.middleware("http")(middleware.dispatch)
|
46
|
+
else:
|
47
|
+
logger.warning(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
|
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
|
+
|