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
mcp_proxy_adapter/__main__.py
CHANGED
@@ -1,12 +1,32 @@
|
|
1
|
-
|
2
|
-
"""
|
3
|
-
Main entry point for MCP Proxy Adapter.
|
1
|
+
"""Main entry point for MCP Proxy Adapter CLI.
|
4
2
|
|
5
|
-
This module
|
6
|
-
|
3
|
+
This module provides a command-line interface for running
|
4
|
+
MCP Proxy Adapter applications.
|
7
5
|
"""
|
8
6
|
|
9
|
-
|
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
|
+
|
10
30
|
|
11
31
|
if __name__ == "__main__":
|
12
|
-
main()
|
32
|
+
main()
|
mcp_proxy_adapter/api/app.py
CHANGED
@@ -4,8 +4,10 @@ Module for FastAPI application setup.
|
|
4
4
|
|
5
5
|
import json
|
6
6
|
import ssl
|
7
|
+
from pathlib import Path
|
7
8
|
from typing import Any, Dict, List, Optional, Union
|
8
9
|
from contextlib import asynccontextmanager
|
10
|
+
import asyncio
|
9
11
|
|
10
12
|
from fastapi import FastAPI, Body, Depends, HTTPException, Request
|
11
13
|
from fastapi.responses import JSONResponse, Response
|
@@ -23,68 +25,126 @@ from mcp_proxy_adapter.commands.command_registry import registry
|
|
23
25
|
from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
|
24
26
|
|
25
27
|
|
26
|
-
|
27
|
-
async def lifespan(app: FastAPI):
|
28
|
+
def create_lifespan(config_path: Optional[str] = None):
|
28
29
|
"""
|
29
|
-
|
30
|
-
"""
|
31
|
-
# Startup events
|
32
|
-
from mcp_proxy_adapter.commands.command_registry import registry
|
33
|
-
from mcp_proxy_adapter.core.proxy_registration import register_with_proxy, unregister_from_proxy
|
34
|
-
|
35
|
-
# Initialize system using unified logic
|
36
|
-
# This will load config, register custom commands, and discover auto-commands
|
37
|
-
init_result = await registry.reload_system()
|
38
|
-
|
39
|
-
logger.info(f"Application started with {init_result['total_commands']} commands registered")
|
40
|
-
logger.info(f"System initialization result: {init_result}")
|
30
|
+
Create lifespan manager for the FastAPI application.
|
41
31
|
|
42
|
-
|
43
|
-
|
44
|
-
server_host = server_config.get("host", "0.0.0.0")
|
45
|
-
server_port = server_config.get("port", 8000)
|
46
|
-
|
47
|
-
# Determine server URL based on SSL configuration
|
48
|
-
ssl_config = config.get("ssl", {})
|
49
|
-
if ssl_config.get("enabled", False):
|
50
|
-
protocol = "https"
|
51
|
-
else:
|
52
|
-
protocol = "http"
|
53
|
-
|
54
|
-
# Use localhost for external access if host is 0.0.0.0
|
55
|
-
if server_host == "0.0.0.0":
|
56
|
-
server_host = "localhost"
|
57
|
-
|
58
|
-
server_url = f"{protocol}://{server_host}:{server_port}"
|
32
|
+
Args:
|
33
|
+
config_path: Path to configuration file (optional)
|
59
34
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
35
|
+
Returns:
|
36
|
+
Lifespan context manager
|
37
|
+
"""
|
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:
|
65
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")
|
66
125
|
|
67
|
-
|
68
|
-
|
69
|
-
# Shutdown events
|
70
|
-
logger.info("Application shutting down")
|
71
|
-
|
72
|
-
# Unregister from proxy if enabled
|
73
|
-
unregistration_success = await unregister_from_proxy()
|
74
|
-
if unregistration_success:
|
75
|
-
logger.info("✅ Proxy unregistration completed successfully")
|
76
|
-
else:
|
77
|
-
logger.warning("⚠️ Proxy unregistration failed or was disabled")
|
126
|
+
return lifespan
|
78
127
|
|
79
128
|
|
80
|
-
def create_ssl_context() -> Optional[ssl.SSLContext]:
|
129
|
+
def create_ssl_context(app_config: Optional[Dict[str, Any]] = None) -> Optional[ssl.SSLContext]:
|
81
130
|
"""
|
82
131
|
Create SSL context based on configuration.
|
83
132
|
|
133
|
+
Args:
|
134
|
+
app_config: Application configuration dictionary (optional)
|
135
|
+
|
84
136
|
Returns:
|
85
137
|
SSL context if SSL is enabled and properly configured, None otherwise
|
86
138
|
"""
|
87
|
-
|
139
|
+
current_config = app_config if app_config is not None else config.get_all()
|
140
|
+
|
141
|
+
# Try security framework SSL config first
|
142
|
+
security_config = current_config.get("security", {})
|
143
|
+
ssl_config = security_config.get("ssl", {})
|
144
|
+
|
145
|
+
# Fallback to legacy SSL config
|
146
|
+
if not ssl_config.get("enabled", False):
|
147
|
+
ssl_config = current_config.get("ssl", {})
|
88
148
|
|
89
149
|
if not ssl_config.get("enabled", False):
|
90
150
|
logger.info("SSL is disabled in configuration")
|
@@ -117,7 +177,7 @@ def create_ssl_context() -> Optional[ssl.SSLContext]:
|
|
117
177
|
return None
|
118
178
|
|
119
179
|
|
120
|
-
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:
|
121
181
|
"""
|
122
182
|
Creates and configures FastAPI application.
|
123
183
|
|
@@ -125,6 +185,8 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
125
185
|
title: Application title (default: "MCP Proxy Adapter")
|
126
186
|
description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
|
127
187
|
version: Application version (default: "1.0.0")
|
188
|
+
app_config: Application configuration dictionary (optional)
|
189
|
+
config_path: Path to configuration file (optional)
|
128
190
|
|
129
191
|
Returns:
|
130
192
|
Configured FastAPI application.
|
@@ -132,47 +194,122 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
132
194
|
Raises:
|
133
195
|
SystemExit: If authentication is enabled but required files are missing (security issue)
|
134
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
|
+
|
135
222
|
# Security check: Validate all authentication configurations before startup
|
136
223
|
security_errors = []
|
137
224
|
|
138
|
-
|
139
|
-
|
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}")
|
140
266
|
if roles_config.get("enabled", False):
|
141
267
|
roles_config_path = roles_config.get("config_file", "schemas/roles_schema.json")
|
142
|
-
|
268
|
+
print(f"🔍 Debug: Checking roles file: {roles_config_path}")
|
143
269
|
if not Path(roles_config_path).exists():
|
144
270
|
security_errors.append(f"Roles are enabled but schema file not found: {roles_config_path}")
|
145
271
|
|
146
|
-
# Check
|
147
|
-
|
148
|
-
|
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):
|
149
282
|
# Check SSL certificate files
|
150
|
-
cert_file =
|
151
|
-
key_file =
|
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'}")
|
152
289
|
|
153
290
|
if cert_file and not Path(cert_file).exists():
|
154
|
-
security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
|
291
|
+
security_errors.append(f"Legacy SSL is enabled but certificate file not found: {cert_file}")
|
155
292
|
|
156
293
|
if key_file and not Path(key_file).exists():
|
157
|
-
security_errors.append(f"SSL is enabled but private key file not found: {key_file}")
|
294
|
+
security_errors.append(f"Legacy SSL is enabled but private key file not found: {key_file}")
|
158
295
|
|
159
296
|
# Check mTLS configuration
|
160
|
-
if
|
161
|
-
ca_cert =
|
297
|
+
if legacy_ssl_config.get("mode") == "mtls":
|
298
|
+
ca_cert = legacy_ssl_config.get("ca_cert")
|
162
299
|
if ca_cert and not Path(ca_cert).exists():
|
163
|
-
security_errors.append(f"mTLS is enabled but CA certificate file not found: {ca_cert}")
|
300
|
+
security_errors.append(f"Legacy mTLS is enabled but CA certificate file not found: {ca_cert}")
|
164
301
|
|
165
302
|
# Check token authentication configuration
|
166
|
-
token_auth_config =
|
303
|
+
token_auth_config = legacy_ssl_config.get("token_auth", {})
|
167
304
|
if token_auth_config.get("enabled", False):
|
168
305
|
tokens_file = token_auth_config.get("tokens_file", "tokens.json")
|
169
306
|
if not Path(tokens_file).exists():
|
170
307
|
security_errors.append(f"Token authentication is enabled but tokens file not found: {tokens_file}")
|
171
308
|
|
172
309
|
# Check general authentication
|
173
|
-
if
|
310
|
+
if current_config.get("auth_enabled", False):
|
174
311
|
# If auth is enabled, check if any authentication method is properly configured
|
175
|
-
ssl_enabled =
|
312
|
+
ssl_enabled = legacy_ssl_config.get("enabled", False)
|
176
313
|
roles_enabled = roles_config.get("enabled", False)
|
177
314
|
token_auth_enabled = token_auth_config.get("enabled", False)
|
178
315
|
|
@@ -200,7 +337,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
200
337
|
version=app_version,
|
201
338
|
docs_url="/docs",
|
202
339
|
redoc_url="/redoc",
|
203
|
-
lifespan=
|
340
|
+
lifespan=create_lifespan(config_path),
|
204
341
|
)
|
205
342
|
|
206
343
|
# Configure CORS
|
@@ -213,7 +350,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
213
350
|
)
|
214
351
|
|
215
352
|
# Setup middleware using the new middleware package
|
216
|
-
setup_middleware(app)
|
353
|
+
setup_middleware(app, current_config)
|
217
354
|
|
218
355
|
# Use custom OpenAPI schema
|
219
356
|
app.openapi = lambda: custom_openapi_with_fallback(app)
|
@@ -259,7 +396,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
259
396
|
return await handle_batch_json_rpc(request_data, request)
|
260
397
|
else:
|
261
398
|
# Process single request
|
262
|
-
return await handle_json_rpc(request_data, request_id)
|
399
|
+
return await handle_json_rpc(request_data, request_id, request)
|
263
400
|
|
264
401
|
# Command execution endpoint (/cmd)
|
265
402
|
@app.post("/cmd")
|
@@ -295,7 +432,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
295
432
|
# Determine request format (CommandRequest or JSON-RPC)
|
296
433
|
if "jsonrpc" in command_data and "method" in command_data:
|
297
434
|
# JSON-RPC format
|
298
|
-
return await handle_json_rpc(command_data, request_id)
|
435
|
+
return await handle_json_rpc(command_data, request_id, request)
|
299
436
|
|
300
437
|
# CommandRequest format
|
301
438
|
if "command" not in command_data:
|
@@ -330,7 +467,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
330
467
|
|
331
468
|
# Execute command
|
332
469
|
try:
|
333
|
-
result = await execute_command(command_name, params, request_id)
|
470
|
+
result = await execute_command(command_name, params, request_id, request)
|
334
471
|
return {"result": result}
|
335
472
|
except MicroserviceError as e:
|
336
473
|
# Handle command execution errors
|
@@ -397,7 +534,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
397
534
|
request_id = getattr(request.state, "request_id", None)
|
398
535
|
|
399
536
|
try:
|
400
|
-
result = await execute_command(command_name, params, request_id)
|
537
|
+
result = await execute_command(command_name, params, request_id, request)
|
401
538
|
return result
|
402
539
|
except MicroserviceError as e:
|
403
540
|
# Convert to proper HTTP status code
|
@@ -568,10 +705,3 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
568
705
|
)
|
569
706
|
|
570
707
|
return app
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
# Create global application instance
|
577
|
-
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
|
|
@@ -52,7 +52,18 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
|
|
52
52
|
|
53
53
|
# Use Command.run that handles instances with dependencies properly
|
54
54
|
command_class = registry.get_command(command_name)
|
55
|
-
|
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)
|
56
67
|
|
57
68
|
execution_time = time.time() - start_time
|
58
69
|
|
@@ -97,13 +108,13 @@ async def handle_batch_json_rpc(batch_requests: List[Dict[str, Any]], request: O
|
|
97
108
|
|
98
109
|
for request_data in batch_requests:
|
99
110
|
# Process each request in the batch
|
100
|
-
response = await handle_json_rpc(request_data, request_id)
|
111
|
+
response = await handle_json_rpc(request_data, request_id, request)
|
101
112
|
responses.append(response)
|
102
113
|
|
103
114
|
return responses
|
104
115
|
|
105
116
|
|
106
|
-
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]:
|
107
118
|
"""
|
108
119
|
Handles JSON-RPC request.
|
109
120
|
|
@@ -139,7 +150,7 @@ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str
|
|
139
150
|
|
140
151
|
try:
|
141
152
|
# Execute command
|
142
|
-
result = await execute_command(method, params, request_id)
|
153
|
+
result = await execute_command(method, params, request_id, request)
|
143
154
|
|
144
155
|
# Form successful response
|
145
156
|
return {
|
@@ -3,6 +3,7 @@ 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
|
@@ -11,25 +12,32 @@ from .base import BaseMiddleware
|
|
11
12
|
from .factory import MiddlewareFactory
|
12
13
|
from .protocol_middleware import setup_protocol_middleware
|
13
14
|
|
14
|
-
def setup_middleware(app: FastAPI) -> None:
|
15
|
+
def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None) -> None:
|
15
16
|
"""
|
16
17
|
Sets up middleware for application using the new middleware factory.
|
17
18
|
|
18
19
|
Args:
|
19
20
|
app: FastAPI application instance.
|
21
|
+
app_config: Application configuration dictionary (optional)
|
20
22
|
"""
|
21
|
-
#
|
22
|
-
|
23
|
+
# Use provided configuration or fallback to global config
|
24
|
+
current_config = app_config if app_config is not None else config.get_all()
|
23
25
|
|
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
|
+
|
24
32
|
# Validate middleware configuration
|
25
33
|
if not factory.validate_middleware_config():
|
26
34
|
logger.error("Middleware configuration validation failed")
|
27
35
|
raise SystemExit(1)
|
28
|
-
|
36
|
+
|
29
37
|
logger.info("Using unified security middleware")
|
30
38
|
middleware_list = factory.create_all_middleware()
|
31
|
-
|
32
|
-
# Add middleware to application
|
39
|
+
|
40
|
+
# Add middleware to application AFTER protocol middleware
|
33
41
|
for middleware in middleware_list:
|
34
42
|
# For ASGI middleware, we need to wrap the application
|
35
43
|
if hasattr(middleware, 'dispatch'):
|
@@ -38,9 +46,6 @@ def setup_middleware(app: FastAPI) -> None:
|
|
38
46
|
else:
|
39
47
|
logger.warning(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
|
40
48
|
|
41
|
-
# Add protocol middleware (always needed)
|
42
|
-
setup_protocol_middleware(app)
|
43
|
-
|
44
49
|
# Log middleware information
|
45
50
|
middleware_info = factory.get_middleware_info()
|
46
51
|
logger.info(f"Middleware setup completed:")
|