mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.29__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.
Potentially problematic release.
This version of mcp-proxy-adapter might be problematic. Click here for more details.
- mcp_proxy_adapter/__init__.py +10 -0
- mcp_proxy_adapter/__main__.py +8 -21
- mcp_proxy_adapter/api/app.py +10 -913
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +243 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_manager.py +166 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +78 -199
- mcp_proxy_adapter/api/middleware/__init__.py +1 -44
- mcp_proxy_adapter/api/middleware/base.py +0 -42
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
- mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
- mcp_proxy_adapter/api/middleware/factory.py +0 -94
- mcp_proxy_adapter/api/middleware/logging.py +0 -112
- mcp_proxy_adapter/api/middleware/performance.py +0 -35
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
- mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
- mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +0 -61
- mcp_proxy_adapter/api/tool_integration.py +0 -117
- mcp_proxy_adapter/api/tools.py +0 -46
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +128 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +324 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
- mcp_proxy_adapter/client/proxy.py +45 -0
- mcp_proxy_adapter/commands/__init__.py +44 -28
- mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
- mcp_proxy_adapter/commands/base.py +19 -43
- mcp_proxy_adapter/commands/builtin_commands.py +0 -75
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +58 -928
- mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
- mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
- mcp_proxy_adapter/commands/command_registry.py +172 -904
- mcp_proxy_adapter/commands/config_command.py +0 -28
- mcp_proxy_adapter/commands/dependency_container.py +1 -70
- mcp_proxy_adapter/commands/dependency_manager.py +0 -128
- mcp_proxy_adapter/commands/echo_command.py +0 -34
- mcp_proxy_adapter/commands/health_command.py +0 -3
- mcp_proxy_adapter/commands/help_command.py +0 -159
- mcp_proxy_adapter/commands/hooks.py +0 -137
- mcp_proxy_adapter/commands/key_management_command.py +0 -25
- mcp_proxy_adapter/commands/load_command.py +7 -78
- mcp_proxy_adapter/commands/plugins_command.py +0 -16
- mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
- mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +0 -43
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +0 -80
- mcp_proxy_adapter/commands/result.py +25 -77
- mcp_proxy_adapter/commands/role_test_command.py +0 -44
- mcp_proxy_adapter/commands/roles_management_command.py +0 -199
- mcp_proxy_adapter/commands/security_command.py +0 -30
- mcp_proxy_adapter/commands/settings_command.py +0 -68
- mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
- mcp_proxy_adapter/commands/token_management_command.py +0 -1
- mcp_proxy_adapter/commands/transport_management_command.py +0 -20
- mcp_proxy_adapter/commands/unload_command.py +0 -71
- mcp_proxy_adapter/config.py +15 -626
- mcp_proxy_adapter/core/__init__.py +5 -39
- mcp_proxy_adapter/core/app_factory.py +14 -36
- mcp_proxy_adapter/core/app_runner.py +0 -27
- mcp_proxy_adapter/core/auth_validator.py +1 -93
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
- mcp_proxy_adapter/core/certificate_utils.py +64 -903
- mcp_proxy_adapter/core/client.py +0 -6
- mcp_proxy_adapter/core/client_manager.py +0 -19
- mcp_proxy_adapter/core/client_security.py +0 -2
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +195 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +112 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
- mcp_proxy_adapter/core/config_converter.py +0 -186
- mcp_proxy_adapter/core/config_validator.py +96 -1238
- mcp_proxy_adapter/core/errors.py +7 -42
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +2 -22
- mcp_proxy_adapter/core/mtls_asgi.py +0 -20
- mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
- mcp_proxy_adapter/core/mtls_proxy.py +0 -80
- mcp_proxy_adapter/core/mtls_server.py +3 -173
- mcp_proxy_adapter/core/protocol_manager.py +1 -191
- mcp_proxy_adapter/core/proxy/__init__.py +22 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +0 -1
- mcp_proxy_adapter/core/proxy_registration.py +36 -913
- mcp_proxy_adapter/core/role_utils.py +0 -308
- mcp_proxy_adapter/core/security_adapter.py +1 -36
- mcp_proxy_adapter/core/security_factory.py +1 -150
- mcp_proxy_adapter/core/security_integration.py +0 -33
- mcp_proxy_adapter/core/server_adapter.py +1 -40
- mcp_proxy_adapter/core/server_engine.py +2 -173
- mcp_proxy_adapter/core/settings.py +0 -127
- mcp_proxy_adapter/core/signal_handler.py +0 -65
- mcp_proxy_adapter/core/ssl_utils.py +19 -137
- mcp_proxy_adapter/core/transport_manager.py +0 -151
- mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
- mcp_proxy_adapter/core/utils.py +1 -182
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +211 -0
- mcp_proxy_adapter/core/validation/file_validator.py +73 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
- mcp_proxy_adapter/core/validation/security_validator.py +58 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +33 -652
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
- mcp_proxy_adapter/examples/check_config.py +0 -2
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/config_builder.py +13 -2
- mcp_proxy_adapter/examples/config_cli.py +0 -1
- mcp_proxy_adapter/examples/create_test_configs.py +0 -46
- mcp_proxy_adapter/examples/debug_request_state.py +0 -1
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
- mcp_proxy_adapter/examples/full_application/main.py +186 -150
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
- mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
- mcp_proxy_adapter/examples/generate_config.py +65 -11
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +0 -2
- mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
- mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +24 -1075
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
- mcp_proxy_adapter/examples/test_config.py +0 -3
- mcp_proxy_adapter/examples/test_config_builder.py +25 -405
- mcp_proxy_adapter/examples/test_examples.py +0 -1
- mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
- mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
- mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
- mcp_proxy_adapter/examples/universal_client.py +0 -6
- mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +70 -62
- mcp_proxy_adapter/openapi.py +0 -22
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Core API utilities for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .app_factory import AppFactory
|
|
9
|
+
from .ssl_context_factory import SSLContextFactory
|
|
10
|
+
from .registration_manager import RegistrationManager
|
|
11
|
+
from .lifespan_manager import LifespanManager
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AppFactory",
|
|
15
|
+
"SSLContextFactory",
|
|
16
|
+
"RegistrationManager",
|
|
17
|
+
"LifespanManager",
|
|
18
|
+
]
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Application factory for MCP Proxy Adapter API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from fastapi import FastAPI, Body
|
|
13
|
+
from typing import List, Union
|
|
14
|
+
from mcp_proxy_adapter.api.handlers import (
|
|
15
|
+
execute_command,
|
|
16
|
+
handle_json_rpc,
|
|
17
|
+
handle_batch_json_rpc,
|
|
18
|
+
get_server_health,
|
|
19
|
+
get_commands_list,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# from mcp_proxy_adapter.api.middleware import setup_middleware
|
|
23
|
+
try:
|
|
24
|
+
from mcp_proxy_adapter.api.schemas import (
|
|
25
|
+
JsonRpcRequest,
|
|
26
|
+
JsonRpcSuccessResponse,
|
|
27
|
+
JsonRpcErrorResponse,
|
|
28
|
+
HealthResponse,
|
|
29
|
+
CommandListResponse,
|
|
30
|
+
APIToolDescription,
|
|
31
|
+
)
|
|
32
|
+
except Exception:
|
|
33
|
+
# If schemas are unavailable, define minimal type aliases to satisfy annotations
|
|
34
|
+
JsonRpcRequest = Dict[str, Any] # type: ignore
|
|
35
|
+
JsonRpcSuccessResponse = Dict[str, Any] # type: ignore
|
|
36
|
+
JsonRpcErrorResponse = Dict[str, Any] # type: ignore
|
|
37
|
+
HealthResponse = Dict[str, Any] # type: ignore
|
|
38
|
+
CommandListResponse = Dict[str, Any] # type: ignore
|
|
39
|
+
APIToolDescription = Dict[str, Any] # type: ignore
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
from mcp_proxy_adapter.api.tools import get_tool_description, execute_tool
|
|
43
|
+
except Exception:
|
|
44
|
+
get_tool_description = None
|
|
45
|
+
execute_tool = None
|
|
46
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
47
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
|
48
|
+
from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
|
|
49
|
+
from .ssl_context_factory import SSLContextFactory
|
|
50
|
+
from .lifespan_manager import LifespanManager
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AppFactory:
|
|
54
|
+
"""Factory for creating FastAPI applications."""
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
"""Initialize app factory."""
|
|
58
|
+
self.logger = get_global_logger()
|
|
59
|
+
self.ssl_factory = SSLContextFactory()
|
|
60
|
+
self.lifespan_manager = LifespanManager()
|
|
61
|
+
|
|
62
|
+
def create_app(
|
|
63
|
+
self,
|
|
64
|
+
title: Optional[str] = None,
|
|
65
|
+
description: Optional[str] = None,
|
|
66
|
+
version: Optional[str] = None,
|
|
67
|
+
app_config: Optional[Dict[str, Any]] = None,
|
|
68
|
+
config_path: Optional[str] = None,
|
|
69
|
+
) -> FastAPI:
|
|
70
|
+
"""
|
|
71
|
+
Creates and configures FastAPI application.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
title: Application title (default: "MCP Proxy Adapter")
|
|
75
|
+
description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
|
|
76
|
+
version: Application version (default: "1.0.0")
|
|
77
|
+
app_config: Application configuration dictionary (optional)
|
|
78
|
+
config_path: Path to configuration file (optional)
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Configured FastAPI application.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
SystemExit: If authentication is enabled but required files are missing (security issue)
|
|
85
|
+
"""
|
|
86
|
+
# Use provided configuration or fallback to global config
|
|
87
|
+
if app_config is not None:
|
|
88
|
+
if hasattr(app_config, "get_all"):
|
|
89
|
+
current_config = app_config.get_all()
|
|
90
|
+
elif hasattr(app_config, "keys"):
|
|
91
|
+
current_config = app_config
|
|
92
|
+
else:
|
|
93
|
+
# If app_config is not a dict-like object, use it as is
|
|
94
|
+
current_config = app_config
|
|
95
|
+
else:
|
|
96
|
+
# If no app_config provided, try to get global config
|
|
97
|
+
try:
|
|
98
|
+
from mcp_proxy_adapter.config import get_config
|
|
99
|
+
current_config = get_config().get_all()
|
|
100
|
+
except Exception:
|
|
101
|
+
# If global config is not available, create empty config
|
|
102
|
+
current_config = {}
|
|
103
|
+
|
|
104
|
+
# Debug: Check what config is passed to create_app
|
|
105
|
+
if app_config:
|
|
106
|
+
if hasattr(app_config, "keys"):
|
|
107
|
+
print(
|
|
108
|
+
f"🔍 Debug: create_app received app_config keys: {list(app_config.keys())}"
|
|
109
|
+
)
|
|
110
|
+
# Debug SSL configuration
|
|
111
|
+
protocol = app_config.get("server", {}).get("protocol", "http")
|
|
112
|
+
verify_client = app_config.get("transport", {}).get("verify_client", False)
|
|
113
|
+
ssl_enabled = protocol in ["https", "mtls"] or verify_client
|
|
114
|
+
print(f"🔍 Debug: create_app SSL config: enabled={ssl_enabled}")
|
|
115
|
+
print(f"🔍 Debug: create_app protocol: {protocol}")
|
|
116
|
+
print(f"🔍 Debug: create_app verify_client: {verify_client}")
|
|
117
|
+
else:
|
|
118
|
+
print(f"🔍 Debug: create_app received app_config type: {type(app_config)}")
|
|
119
|
+
else:
|
|
120
|
+
print("🔍 Debug: create_app received no app_config, using global config")
|
|
121
|
+
|
|
122
|
+
# Security check: Validate configuration strictly at startup (fail-fast)
|
|
123
|
+
self._validate_configuration(current_config)
|
|
124
|
+
|
|
125
|
+
# Security check: Validate all authentication configurations before startup
|
|
126
|
+
self._validate_security_configuration(current_config)
|
|
127
|
+
|
|
128
|
+
# Set default values
|
|
129
|
+
title = title or "MCP Proxy Adapter"
|
|
130
|
+
description = description or "JSON-RPC API for interacting with MCP Proxy"
|
|
131
|
+
version = version or "1.0.0"
|
|
132
|
+
|
|
133
|
+
# Create lifespan manager
|
|
134
|
+
lifespan = self.lifespan_manager.create_lifespan(config_path, current_config)
|
|
135
|
+
|
|
136
|
+
# Create FastAPI application
|
|
137
|
+
app = FastAPI(
|
|
138
|
+
title=title,
|
|
139
|
+
description=description,
|
|
140
|
+
version=version,
|
|
141
|
+
lifespan=lifespan,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Setup middleware - disabled for now
|
|
145
|
+
# setup_middleware(app, current_config)
|
|
146
|
+
|
|
147
|
+
# Setup routes
|
|
148
|
+
self._setup_routes(app)
|
|
149
|
+
|
|
150
|
+
# Setup OpenAPI
|
|
151
|
+
app.openapi = lambda: custom_openapi_with_fallback(app)
|
|
152
|
+
|
|
153
|
+
return app
|
|
154
|
+
|
|
155
|
+
def _validate_configuration(self, current_config: Dict[str, Any]) -> None:
|
|
156
|
+
"""Validate configuration at startup."""
|
|
157
|
+
try:
|
|
158
|
+
from mcp_proxy_adapter.core.config_validator import ConfigValidator
|
|
159
|
+
|
|
160
|
+
validator = ConfigValidator()
|
|
161
|
+
validator.config_data = current_config
|
|
162
|
+
validation_results = validator.validate_config()
|
|
163
|
+
errors = [r for r in validation_results if r.level == "error"]
|
|
164
|
+
warnings = [r for r in validation_results if r.level == "warning"]
|
|
165
|
+
|
|
166
|
+
if errors:
|
|
167
|
+
self.logger.critical("CRITICAL CONFIG ERROR: Invalid configuration at startup:")
|
|
168
|
+
for error in errors:
|
|
169
|
+
self.logger.critical(f" - {error.message}")
|
|
170
|
+
raise SystemExit(1)
|
|
171
|
+
for warning in warnings:
|
|
172
|
+
self.logger.warning(f"Config warning: {warning.message}")
|
|
173
|
+
except Exception as ex:
|
|
174
|
+
self.logger.error(f"Failed to run startup configuration validation: {ex}")
|
|
175
|
+
|
|
176
|
+
def _validate_security_configuration(self, current_config: Dict[str, Any]) -> None:
|
|
177
|
+
"""Validate security configuration at startup."""
|
|
178
|
+
security_errors = []
|
|
179
|
+
|
|
180
|
+
print(f"🔍 Debug: current_config keys: {list(current_config.keys())}")
|
|
181
|
+
if "security" in current_config:
|
|
182
|
+
print(f"🔍 Debug: security config: {current_config['security']}")
|
|
183
|
+
if "roles" in current_config:
|
|
184
|
+
print(f"🔍 Debug: roles config: {current_config['roles']}")
|
|
185
|
+
|
|
186
|
+
# Check security framework configuration only if enabled
|
|
187
|
+
security_config = current_config.get("security", {})
|
|
188
|
+
if security_config.get("enabled", False):
|
|
189
|
+
# Validate security framework configuration
|
|
190
|
+
from mcp_proxy_adapter.core.unified_config_adapter import UnifiedConfigAdapter
|
|
191
|
+
|
|
192
|
+
adapter = UnifiedConfigAdapter()
|
|
193
|
+
validation_result = adapter.validate_configuration(current_config)
|
|
194
|
+
|
|
195
|
+
if not validation_result.is_valid:
|
|
196
|
+
security_errors.extend(validation_result.errors)
|
|
197
|
+
|
|
198
|
+
# Check roles configuration only if enabled
|
|
199
|
+
roles_config = current_config.get("roles", {})
|
|
200
|
+
if roles_config.get("enabled", False):
|
|
201
|
+
# Validate roles configuration
|
|
202
|
+
from mcp_proxy_adapter.core.role_utils import RoleUtils
|
|
203
|
+
|
|
204
|
+
role_utils = RoleUtils()
|
|
205
|
+
validation_result = role_utils.validate_configuration(current_config)
|
|
206
|
+
|
|
207
|
+
if not validation_result.is_valid:
|
|
208
|
+
security_errors.extend(validation_result.errors)
|
|
209
|
+
|
|
210
|
+
# Fail if there are security errors
|
|
211
|
+
if security_errors:
|
|
212
|
+
self.logger.critical("CRITICAL SECURITY ERROR: Invalid security configuration at startup:")
|
|
213
|
+
for error in security_errors:
|
|
214
|
+
self.logger.critical(f" - {error}")
|
|
215
|
+
raise SystemExit(1)
|
|
216
|
+
|
|
217
|
+
def _setup_routes(self, app: FastAPI) -> None:
|
|
218
|
+
"""Setup application routes."""
|
|
219
|
+
@app.get("/health", response_model=HealthResponse)
|
|
220
|
+
async def health(): # type: ignore
|
|
221
|
+
return await get_server_health() # type: ignore[misc]
|
|
222
|
+
|
|
223
|
+
@app.get("/commands", response_model=CommandListResponse)
|
|
224
|
+
async def commands(): # type: ignore
|
|
225
|
+
return await get_commands_list() # type: ignore[misc]
|
|
226
|
+
|
|
227
|
+
@app.post("/api/jsonrpc", response_model=Union[JsonRpcSuccessResponse, JsonRpcErrorResponse])
|
|
228
|
+
async def jsonrpc(request: JsonRpcRequest): # type: ignore
|
|
229
|
+
return await handle_json_rpc(request.dict()) # type: ignore[misc]
|
|
230
|
+
|
|
231
|
+
@app.post("/api/jsonrpc/batch", response_model=List[Union[JsonRpcSuccessResponse, JsonRpcErrorResponse]])
|
|
232
|
+
async def jsonrpc_batch(requests: List[JsonRpcRequest]): # type: ignore
|
|
233
|
+
return await handle_batch_json_rpc([req.dict() for req in requests]) # type: ignore[misc]
|
|
234
|
+
|
|
235
|
+
# Optional tool endpoints if tools module is available
|
|
236
|
+
if get_tool_description and execute_tool:
|
|
237
|
+
@app.get("/api/tools", response_model=List[APIToolDescription])
|
|
238
|
+
async def tools(): # type: ignore
|
|
239
|
+
return await get_tool_description() # type: ignore[misc]
|
|
240
|
+
|
|
241
|
+
@app.post("/api/tools/{tool_name}")
|
|
242
|
+
async def execute_tool_endpoint(tool_name: str, params: Dict[str, Any] = Body(...)):
|
|
243
|
+
return await execute_tool(tool_name, params) # type: ignore[misc]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Lifespan management utilities for MCP Proxy Adapter API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from fastapi import FastAPI
|
|
13
|
+
|
|
14
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
15
|
+
from .registration_manager import RegistrationManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LifespanManager:
|
|
19
|
+
"""Manager for application lifespan events."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""Initialize lifespan manager."""
|
|
23
|
+
self.logger = get_global_logger()
|
|
24
|
+
self.registration_manager = RegistrationManager()
|
|
25
|
+
|
|
26
|
+
def create_lifespan(self, config_path: Optional[str] = None, current_config: Optional[Dict[str, Any]] = None):
|
|
27
|
+
"""
|
|
28
|
+
Create lifespan manager for the FastAPI application.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config_path: Path to configuration file (optional)
|
|
32
|
+
current_config: Current configuration data (optional)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Lifespan context manager
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@asynccontextmanager
|
|
39
|
+
async def lifespan(app: FastAPI):
|
|
40
|
+
"""Lifespan context manager."""
|
|
41
|
+
# Startup
|
|
42
|
+
get_global_logger().info("Starting MCP Proxy Adapter")
|
|
43
|
+
|
|
44
|
+
# Register with proxy if configured
|
|
45
|
+
if current_config:
|
|
46
|
+
await self.registration_manager.register_with_proxy(current_config)
|
|
47
|
+
await self.registration_manager.start_heartbeat(current_config)
|
|
48
|
+
|
|
49
|
+
yield
|
|
50
|
+
|
|
51
|
+
# Shutdown
|
|
52
|
+
get_global_logger().info("Shutting down MCP Proxy Adapter")
|
|
53
|
+
await self.registration_manager.stop()
|
|
54
|
+
|
|
55
|
+
return lifespan
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Registration management utilities for MCP Proxy Adapter API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import socket
|
|
10
|
+
import asyncio
|
|
11
|
+
from typing import Dict, Any, Optional
|
|
12
|
+
|
|
13
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
14
|
+
from mcp_proxy_adapter.client.jsonrpc_client import JsonRpcClient
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RegistrationManager:
|
|
18
|
+
"""Manager for proxy registration functionality using JsonRpcClient."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize registration manager."""
|
|
22
|
+
self.logger = get_global_logger()
|
|
23
|
+
self.registered = False
|
|
24
|
+
self.registration_task: Optional[asyncio.Task] = None
|
|
25
|
+
self.server_name: Optional[str] = None
|
|
26
|
+
self.server_url: Optional[str] = None
|
|
27
|
+
self.proxy_url: Optional[str] = None
|
|
28
|
+
self.capabilities: list = []
|
|
29
|
+
self.metadata: Dict[str, Any] = {}
|
|
30
|
+
|
|
31
|
+
async def register_with_proxy(self, config: Dict[str, Any]) -> bool:
|
|
32
|
+
"""Register this server with the proxy using JsonRpcClient."""
|
|
33
|
+
try:
|
|
34
|
+
proxy_config = config.get("proxy_registration", {})
|
|
35
|
+
if not proxy_config.get("enabled", False):
|
|
36
|
+
self.logger.info("Proxy registration disabled")
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
proxy_url = proxy_config.get("proxy_url") or proxy_config.get("server_url")
|
|
40
|
+
if not proxy_url:
|
|
41
|
+
self.logger.warning("No proxy server URL configured")
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
# Get server info
|
|
45
|
+
server_config = config.get("server", {})
|
|
46
|
+
host = server_config.get("host", "127.0.0.1")
|
|
47
|
+
port = server_config.get("port", 8000)
|
|
48
|
+
protocol = server_config.get("protocol", "http")
|
|
49
|
+
|
|
50
|
+
# Use advertised host if available
|
|
51
|
+
advertised_host = server_config.get("advertised_host") or host
|
|
52
|
+
scheme = "https" if protocol in ("https", "mtls") else "http"
|
|
53
|
+
advertised_url = f"{scheme}://{advertised_host}:{port}"
|
|
54
|
+
|
|
55
|
+
# Get server name from config or generate default
|
|
56
|
+
server_name = (
|
|
57
|
+
proxy_config.get("server_id")
|
|
58
|
+
or proxy_config.get("server_name")
|
|
59
|
+
or f"mcp-adapter-{host}-{port}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
self.server_name = server_name
|
|
63
|
+
self.server_url = advertised_url
|
|
64
|
+
self.proxy_url = proxy_url
|
|
65
|
+
|
|
66
|
+
# Get capabilities and metadata
|
|
67
|
+
self.capabilities = proxy_config.get("capabilities", ["jsonrpc", "health"])
|
|
68
|
+
self.metadata = {
|
|
69
|
+
"uuid": config.get("uuid"),
|
|
70
|
+
"protocol": protocol,
|
|
71
|
+
"host": host,
|
|
72
|
+
"port": port,
|
|
73
|
+
**(proxy_config.get("metadata") or {}),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Use JsonRpcClient for registration (run in executor as it's synchronous)
|
|
77
|
+
loop = asyncio.get_event_loop()
|
|
78
|
+
client = JsonRpcClient(protocol="http", host="127.0.0.1", port=8080) # Dummy, just for methods
|
|
79
|
+
|
|
80
|
+
# Register synchronously in executor
|
|
81
|
+
def _register():
|
|
82
|
+
return client.register_with_proxy(
|
|
83
|
+
proxy_url=proxy_url,
|
|
84
|
+
server_name=server_name,
|
|
85
|
+
server_url=advertised_url,
|
|
86
|
+
capabilities=self.capabilities,
|
|
87
|
+
metadata=self.metadata,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
result = await loop.run_in_executor(None, _register)
|
|
92
|
+
self.logger.info(f"✅ Successfully registered with proxy as {server_name} -> {advertised_url}")
|
|
93
|
+
self.registered = True
|
|
94
|
+
return True
|
|
95
|
+
except Exception as exc:
|
|
96
|
+
self.logger.error(f"❌ Failed to register with proxy: {exc}")
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
self.logger.error(f"❌ Registration error: {e}")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
async def start_heartbeat(self, config: Dict[str, Any]) -> None:
|
|
104
|
+
"""Start heartbeat task using JsonRpcClient."""
|
|
105
|
+
if not self.registered or not self.proxy_url or not self.server_name or not self.server_url:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
proxy_config = config.get("proxy_registration", {})
|
|
109
|
+
heartbeat_config = proxy_config.get("heartbeat", {}) or {}
|
|
110
|
+
heartbeat_interval = heartbeat_config.get("interval", proxy_config.get("heartbeat_interval", 30))
|
|
111
|
+
|
|
112
|
+
self.logger.info(f"💓 Starting heartbeat task (interval: {heartbeat_interval}s)")
|
|
113
|
+
|
|
114
|
+
async def heartbeat_loop():
|
|
115
|
+
loop = asyncio.get_event_loop()
|
|
116
|
+
client = JsonRpcClient(protocol="http", host="127.0.0.1", port=8080) # Dummy, just for methods
|
|
117
|
+
|
|
118
|
+
def _heartbeat():
|
|
119
|
+
return client.heartbeat_to_proxy(
|
|
120
|
+
proxy_url=self.proxy_url,
|
|
121
|
+
server_name=self.server_name,
|
|
122
|
+
server_url=self.server_url,
|
|
123
|
+
capabilities=self.capabilities,
|
|
124
|
+
metadata=self.metadata,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
while True:
|
|
128
|
+
try:
|
|
129
|
+
await asyncio.sleep(max(2, heartbeat_interval))
|
|
130
|
+
await loop.run_in_executor(None, _heartbeat)
|
|
131
|
+
self.logger.debug("💓 Heartbeat sent successfully")
|
|
132
|
+
except asyncio.CancelledError:
|
|
133
|
+
break
|
|
134
|
+
except Exception as e:
|
|
135
|
+
self.logger.error(f"Heartbeat error: {e}")
|
|
136
|
+
|
|
137
|
+
self.registration_task = asyncio.create_task(heartbeat_loop())
|
|
138
|
+
|
|
139
|
+
async def stop(self) -> None:
|
|
140
|
+
"""Stop registration manager and unregister from proxy."""
|
|
141
|
+
# Cancel heartbeat task
|
|
142
|
+
if self.registration_task:
|
|
143
|
+
self.registration_task.cancel()
|
|
144
|
+
try:
|
|
145
|
+
await self.registration_task
|
|
146
|
+
except asyncio.CancelledError:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
# Unregister from proxy if registered
|
|
150
|
+
if self.registered and self.proxy_url and self.server_name:
|
|
151
|
+
try:
|
|
152
|
+
loop = asyncio.get_event_loop()
|
|
153
|
+
client = JsonRpcClient(protocol="http", host="127.0.0.1", port=8080) # Dummy, just for methods
|
|
154
|
+
|
|
155
|
+
def _unregister():
|
|
156
|
+
return client.unregister_from_proxy(
|
|
157
|
+
proxy_url=self.proxy_url,
|
|
158
|
+
server_name=self.server_name,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
await loop.run_in_executor(None, _unregister)
|
|
162
|
+
self.logger.info(f"🛑 Unregistered from proxy: {self.server_name}")
|
|
163
|
+
except Exception as e:
|
|
164
|
+
self.logger.error(f"Error unregistering from proxy: {e}")
|
|
165
|
+
|
|
166
|
+
self.registered = False
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
SSL context factory for MCP Proxy Adapter API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ssl
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
13
|
+
from mcp_proxy_adapter.core.ssl_utils import SSLUtils
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SSLContextFactory:
|
|
17
|
+
"""Factory for creating SSL contexts."""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
"""Initialize SSL context factory."""
|
|
21
|
+
self.logger = get_global_logger()
|
|
22
|
+
|
|
23
|
+
def create_ssl_context(
|
|
24
|
+
self,
|
|
25
|
+
app_config: Optional[Dict[str, Any]] = None
|
|
26
|
+
) -> Optional[ssl.SSLContext]:
|
|
27
|
+
"""
|
|
28
|
+
Create SSL context based on configuration.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
app_config: Application configuration dictionary (optional)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
SSL context if SSL is enabled and properly configured, None otherwise
|
|
35
|
+
"""
|
|
36
|
+
from mcp_proxy_adapter.config import config
|
|
37
|
+
|
|
38
|
+
current_config = app_config if app_config is not None else config.get_all()
|
|
39
|
+
|
|
40
|
+
# Check SSL configuration from new structure
|
|
41
|
+
protocol = current_config.get("server", {}).get("protocol", "http")
|
|
42
|
+
verify_client = current_config.get("transport", {}).get("verify_client", False)
|
|
43
|
+
ssl_enabled = protocol in ["https", "mtls"] or verify_client
|
|
44
|
+
|
|
45
|
+
if not ssl_enabled:
|
|
46
|
+
self.logger.info("SSL is disabled in configuration")
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
# Get certificate paths from configuration
|
|
50
|
+
cert_file = current_config.get("transport", {}).get("cert_file")
|
|
51
|
+
key_file = current_config.get("transport", {}).get("key_file")
|
|
52
|
+
ca_cert = current_config.get("transport", {}).get("ca_cert")
|
|
53
|
+
|
|
54
|
+
# Convert relative paths to absolute paths
|
|
55
|
+
if cert_file and not Path(cert_file).is_absolute():
|
|
56
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
57
|
+
cert_file = str(project_root / cert_file)
|
|
58
|
+
if key_file and not Path(key_file).is_absolute():
|
|
59
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
60
|
+
key_file = str(project_root / key_file)
|
|
61
|
+
if ca_cert and not Path(ca_cert).is_absolute():
|
|
62
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
63
|
+
ca_cert = str(project_root / ca_cert)
|
|
64
|
+
|
|
65
|
+
if not cert_file or not key_file:
|
|
66
|
+
self.logger.warning("SSL enabled but certificate or key file not specified")
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
# Create SSL context using SSLUtils
|
|
71
|
+
ssl_context = SSLUtils.create_ssl_context(
|
|
72
|
+
cert_file=cert_file,
|
|
73
|
+
key_file=key_file,
|
|
74
|
+
ca_cert=ca_cert,
|
|
75
|
+
verify_client=current_config.get("transport", {}).get("verify_client", False),
|
|
76
|
+
cipher_suites=[],
|
|
77
|
+
min_tls_version="1.2",
|
|
78
|
+
max_tls_version="1.3",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self.logger.info(
|
|
82
|
+
f"SSL context created successfully for mode: https_only"
|
|
83
|
+
)
|
|
84
|
+
return ssl_context
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
self.logger.error(f"Failed to create SSL context: {e}")
|
|
88
|
+
return None
|