mcp-proxy-adapter 2.0.1__py3-none-any.whl → 6.9.50__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 +47 -0
- mcp_proxy_adapter/__main__.py +13 -0
- mcp_proxy_adapter/api/__init__.py +0 -0
- mcp_proxy_adapter/api/app.py +66 -0
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +400 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_context.py +356 -0
- mcp_proxy_adapter/api/core/registration_manager.py +307 -0
- mcp_proxy_adapter/api/core/registration_tasks.py +84 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +181 -0
- mcp_proxy_adapter/api/middleware/__init__.py +21 -0
- mcp_proxy_adapter/api/middleware/base.py +54 -0
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +73 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +76 -0
- mcp_proxy_adapter/api/middleware/factory.py +147 -0
- mcp_proxy_adapter/api/middleware/logging.py +31 -0
- mcp_proxy_adapter/api/middleware/performance.py +51 -0
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +140 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +87 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +223 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +132 -0
- 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 +270 -0
- mcp_proxy_adapter/api/tool_integration.py +131 -0
- mcp_proxy_adapter/api/tools.py +163 -0
- 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 +105 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +94 -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 +132 -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 +338 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client/__init__.py +9 -0
- mcp_proxy_adapter/client/jsonrpc_client/client.py +42 -0
- mcp_proxy_adapter/client/jsonrpc_client/command_api.py +45 -0
- mcp_proxy_adapter/client/jsonrpc_client/proxy_api.py +224 -0
- mcp_proxy_adapter/client/jsonrpc_client/queue_api.py +60 -0
- mcp_proxy_adapter/client/jsonrpc_client/transport.py +108 -0
- mcp_proxy_adapter/client/proxy.py +123 -0
- mcp_proxy_adapter/commands/__init__.py +66 -0
- mcp_proxy_adapter/commands/auth_validation_command.py +69 -0
- mcp_proxy_adapter/commands/base.py +389 -0
- mcp_proxy_adapter/commands/builtin_commands.py +30 -0
- 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 +97 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +552 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +562 -0
- mcp_proxy_adapter/commands/command_registry.py +298 -0
- mcp_proxy_adapter/commands/config_command.py +102 -0
- mcp_proxy_adapter/commands/dependency_container.py +40 -0
- mcp_proxy_adapter/commands/dependency_manager.py +143 -0
- mcp_proxy_adapter/commands/echo_command.py +48 -0
- mcp_proxy_adapter/commands/health_command.py +142 -0
- mcp_proxy_adapter/commands/help_command.py +175 -0
- mcp_proxy_adapter/commands/hooks.py +172 -0
- mcp_proxy_adapter/commands/key_management_command.py +484 -0
- mcp_proxy_adapter/commands/load_command.py +123 -0
- mcp_proxy_adapter/commands/plugins_command.py +246 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +216 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +319 -0
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +76 -0
- 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 +136 -0
- mcp_proxy_adapter/commands/result.py +157 -0
- mcp_proxy_adapter/commands/role_test_command.py +99 -0
- mcp_proxy_adapter/commands/roles_management_command.py +502 -0
- mcp_proxy_adapter/commands/security_command.py +472 -0
- mcp_proxy_adapter/commands/settings_command.py +113 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +306 -0
- mcp_proxy_adapter/commands/token_management_command.py +500 -0
- mcp_proxy_adapter/commands/transport_management_command.py +129 -0
- mcp_proxy_adapter/commands/unload_command.py +92 -0
- mcp_proxy_adapter/config.py +32 -0
- mcp_proxy_adapter/core/__init__.py +8 -0
- mcp_proxy_adapter/core/app_factory.py +560 -0
- mcp_proxy_adapter/core/app_runner.py +318 -0
- mcp_proxy_adapter/core/auth_validator.py +508 -0
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +372 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +185 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +481 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +65 -0
- mcp_proxy_adapter/core/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/client.py +608 -0
- mcp_proxy_adapter/core/client_manager.py +271 -0
- mcp_proxy_adapter/core/client_security.py +411 -0
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +237 -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 +204 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +131 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +476 -0
- mcp_proxy_adapter/core/config_converter.py +252 -0
- mcp_proxy_adapter/core/config_validator.py +211 -0
- mcp_proxy_adapter/core/crl_utils.py +362 -0
- mcp_proxy_adapter/core/errors.py +276 -0
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +250 -0
- mcp_proxy_adapter/core/mtls_asgi.py +140 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/mtls_proxy.py +229 -0
- mcp_proxy_adapter/core/mtls_server.py +154 -0
- mcp_proxy_adapter/core/protocol_manager.py +232 -0
- mcp_proxy_adapter/core/proxy/__init__.py +19 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +26 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +160 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +186 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +184 -0
- mcp_proxy_adapter/core/proxy_registration.py +80 -0
- mcp_proxy_adapter/core/role_utils.py +103 -0
- mcp_proxy_adapter/core/security_adapter.py +343 -0
- mcp_proxy_adapter/core/security_factory.py +96 -0
- mcp_proxy_adapter/core/security_integration.py +342 -0
- mcp_proxy_adapter/core/server_adapter.py +251 -0
- mcp_proxy_adapter/core/server_engine.py +217 -0
- mcp_proxy_adapter/core/settings.py +260 -0
- mcp_proxy_adapter/core/signal_handler.py +107 -0
- mcp_proxy_adapter/core/ssl_utils.py +161 -0
- mcp_proxy_adapter/core/transport_manager.py +153 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +471 -0
- mcp_proxy_adapter/core/utils.py +101 -0
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +219 -0
- mcp_proxy_adapter/core/validation/file_validator.py +131 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +205 -0
- mcp_proxy_adapter/core/validation/security_validator.py +140 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +58 -0
- mcp_proxy_adapter/examples/__init__.py +16 -0
- 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 +52 -0
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +261 -0
- mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
- mcp_proxy_adapter/examples/check_config.py +413 -0
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/commands/__init__.py +5 -0
- mcp_proxy_adapter/examples/config_builder.py +234 -0
- mcp_proxy_adapter/examples/config_cli.py +282 -0
- mcp_proxy_adapter/examples/create_test_configs.py +174 -0
- mcp_proxy_adapter/examples/debug_request_state.py +130 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +191 -0
- mcp_proxy_adapter/examples/demo_client.py +287 -0
- mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +8 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +45 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +52 -0
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +32 -0
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +54 -0
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +57 -0
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +5 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +29 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +27 -0
- mcp_proxy_adapter/examples/full_application/main.py +311 -0
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +161 -0
- mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
- mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +45 -0
- mcp_proxy_adapter/examples/full_application/test_server.py +163 -0
- mcp_proxy_adapter/examples/full_application/test_simple_server.py +62 -0
- mcp_proxy_adapter/examples/generate_config.py +502 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +335 -0
- 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 +208 -0
- mcp_proxy_adapter/examples/run_example.py +77 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +619 -0
- mcp_proxy_adapter/examples/run_proxy_server.py +153 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +435 -0
- 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 +72 -0
- 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 +235 -0
- mcp_proxy_adapter/examples/simple_protocol_test.py +125 -0
- mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
- mcp_proxy_adapter/examples/test_config.py +205 -0
- mcp_proxy_adapter/examples/test_config_builder.py +110 -0
- mcp_proxy_adapter/examples/test_examples.py +308 -0
- mcp_proxy_adapter/examples/test_framework_complete.py +267 -0
- mcp_proxy_adapter/examples/test_mcp_server.py +187 -0
- mcp_proxy_adapter/examples/test_protocol_examples.py +337 -0
- mcp_proxy_adapter/examples/universal_client.py +674 -0
- mcp_proxy_adapter/examples/update_config_certificates.py +135 -0
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +385 -0
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +61 -0
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +311 -0
- mcp_proxy_adapter/openapi.py +375 -0
- mcp_proxy_adapter/schemas/base_schema.json +114 -0
- mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
- mcp_proxy_adapter/schemas/roles.json +37 -0
- mcp_proxy_adapter/schemas/roles_schema.json +162 -0
- mcp_proxy_adapter/version.py +5 -0
- mcp_proxy_adapter-6.9.50.dist-info/METADATA +1088 -0
- mcp_proxy_adapter-6.9.50.dist-info/RECORD +242 -0
- {mcp_proxy_adapter-2.0.1.dist-info → mcp_proxy_adapter-6.9.50.dist-info}/WHEEL +1 -1
- mcp_proxy_adapter-6.9.50.dist-info/entry_points.txt +14 -0
- mcp_proxy_adapter-6.9.50.dist-info/top_level.txt +1 -0
- adapters/__init__.py +0 -16
- analyzers/__init__.py +0 -14
- analyzers/docstring_analyzer.py +0 -199
- analyzers/type_analyzer.py +0 -151
- cli/__init__.py +0 -12
- cli/__main__.py +0 -79
- cli/command_runner.py +0 -233
- dispatchers/__init__.py +0 -14
- dispatchers/base_dispatcher.py +0 -85
- dispatchers/json_rpc_dispatcher.py +0 -198
- generators/__init__.py +0 -14
- generators/endpoint_generator.py +0 -172
- generators/openapi_generator.py +0 -254
- generators/rest_api_generator.py +0 -207
- mcp_proxy_adapter-2.0.1.dist-info/METADATA +0 -272
- mcp_proxy_adapter-2.0.1.dist-info/RECORD +0 -28
- mcp_proxy_adapter-2.0.1.dist-info/licenses/LICENSE +0 -21
- mcp_proxy_adapter-2.0.1.dist-info/top_level.txt +0 -7
- openapi_schema/__init__.py +0 -38
- openapi_schema/command_registry.py +0 -312
- openapi_schema/rest_schema.py +0 -510
- openapi_schema/rpc_generator.py +0 -307
- openapi_schema/rpc_schema.py +0 -416
- validators/__init__.py +0 -14
- validators/base_validator.py +0 -23
- validators/docstring_validator.py +0 -75
- validators/metadata_validator.py +0 -76
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for configuring logging in the microservice.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import uuid
|
|
9
|
+
from logging.handlers import RotatingFileHandler
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CustomFormatter(logging.Formatter):
|
|
14
|
+
"""
|
|
15
|
+
Custom formatter for logs with colored output in console.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
grey = "\x1b[38;20m"
|
|
19
|
+
yellow = "\x1b[33;20m"
|
|
20
|
+
red = "\x1b[31;20m"
|
|
21
|
+
bold_red = "\x1b[31;1m"
|
|
22
|
+
reset = "\x1b[0m"
|
|
23
|
+
format_str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
24
|
+
|
|
25
|
+
FORMATS = {
|
|
26
|
+
logging.DEBUG: grey + format_str + reset,
|
|
27
|
+
logging.INFO: grey + format_str + reset,
|
|
28
|
+
logging.WARNING: yellow + format_str + reset,
|
|
29
|
+
logging.ERROR: red + format_str + reset,
|
|
30
|
+
logging.CRITICAL: bold_red + format_str + reset,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def format(self, record):
|
|
34
|
+
log_fmt = self.FORMATS.get(record.levelno)
|
|
35
|
+
formatter = logging.Formatter(log_fmt)
|
|
36
|
+
return formatter.format(record)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RequestContextFilter(logging.Filter):
|
|
40
|
+
"""
|
|
41
|
+
Filter for adding request context to logs.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, request_id: Optional[str] = None):
|
|
45
|
+
super().__init__()
|
|
46
|
+
self.request_id = request_id
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RequestLogger:
|
|
50
|
+
"""
|
|
51
|
+
Logger class for logging requests with context.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, logger_name: str, request_id: Optional[str] = None):
|
|
55
|
+
"""
|
|
56
|
+
Initialize request get_global_logger().
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
logger_name: Logger name.
|
|
60
|
+
request_id: Request identifier.
|
|
61
|
+
"""
|
|
62
|
+
self.logger = logging.getLogger(logger_name)
|
|
63
|
+
self.request_id = request_id or str(uuid.uuid4())
|
|
64
|
+
self.filter = RequestContextFilter(self.request_id)
|
|
65
|
+
get_global_logger().addFilter(self.filter)
|
|
66
|
+
|
|
67
|
+
def debug(self, msg: str, *args, **kwargs):
|
|
68
|
+
"""Log message with DEBUG level."""
|
|
69
|
+
get_global_logger().debug(f"[{self.request_id}] {msg}", *args, **kwargs)
|
|
70
|
+
|
|
71
|
+
def info(self, msg: str, *args, **kwargs):
|
|
72
|
+
"""Log message with INFO level."""
|
|
73
|
+
get_global_logger().info(f"[{self.request_id}] {msg}", *args, **kwargs)
|
|
74
|
+
|
|
75
|
+
def warning(self, msg: str, *args, **kwargs):
|
|
76
|
+
"""Log message with WARNING level."""
|
|
77
|
+
get_global_logger().warning(f"[{self.request_id}] {msg}", *args, **kwargs)
|
|
78
|
+
|
|
79
|
+
def error(self, msg: str, *args, **kwargs):
|
|
80
|
+
"""Log message with ERROR level."""
|
|
81
|
+
get_global_logger().error(f"[{self.request_id}] {msg}", *args, **kwargs)
|
|
82
|
+
|
|
83
|
+
def exception(self, msg: str, *args, **kwargs):
|
|
84
|
+
"""Log exception with traceback."""
|
|
85
|
+
get_global_logger().exception(f"[{self.request_id}] {msg}", *args, **kwargs)
|
|
86
|
+
|
|
87
|
+
def critical(self, msg: str, *args, **kwargs):
|
|
88
|
+
"""Log message with CRITICAL level."""
|
|
89
|
+
get_global_logger().critical(f"[{self.request_id}] {msg}", *args, **kwargs)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def setup_logging(
|
|
93
|
+
level: Optional[str] = None,
|
|
94
|
+
log_file: Optional[str] = None,
|
|
95
|
+
max_bytes: Optional[int] = None,
|
|
96
|
+
backup_count: Optional[int] = None,
|
|
97
|
+
rotation_type: Optional[str] = None,
|
|
98
|
+
rotation_when: Optional[str] = None,
|
|
99
|
+
rotation_interval: Optional[int] = None,
|
|
100
|
+
) -> logging.Logger:
|
|
101
|
+
"""
|
|
102
|
+
Configure logging for the microservice.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
level: Logging level. By default, taken from configuration.
|
|
106
|
+
log_file: Path to log file. By default, taken from configuration.
|
|
107
|
+
max_bytes: Maximum log file size in bytes. By default, taken from configuration.
|
|
108
|
+
backup_count: Number of rotation files. By default, taken from configuration.
|
|
109
|
+
rotation_type: Type of log rotation ('size' or 'time'). By default, taken from configuration.
|
|
110
|
+
rotation_when: Time unit for rotation (D, H, M, S). By default, taken from configuration.
|
|
111
|
+
rotation_interval: Interval for rotation. By default, taken from configuration.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Configured get_global_logger().
|
|
115
|
+
"""
|
|
116
|
+
# Use provided parameters or defaults
|
|
117
|
+
level = level or "INFO"
|
|
118
|
+
log_file = log_file
|
|
119
|
+
rotation_type = rotation_type or "size"
|
|
120
|
+
log_dir = "./logs"
|
|
121
|
+
log_file_name = "mcp_proxy_adapter.log"
|
|
122
|
+
error_log_file = "mcp_proxy_adapter_error.log"
|
|
123
|
+
access_log_file = "mcp_proxy_adapter_access.log"
|
|
124
|
+
|
|
125
|
+
# Get rotation settings
|
|
126
|
+
max_file_size_str = "10MB"
|
|
127
|
+
backup_count = backup_count or 5
|
|
128
|
+
|
|
129
|
+
# Parse max file size (e.g., "10MB" -> 10 * 1024 * 1024)
|
|
130
|
+
max_bytes = max_bytes or _parse_file_size(max_file_size_str)
|
|
131
|
+
|
|
132
|
+
# Get format settings
|
|
133
|
+
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
134
|
+
date_format = "%Y-%m-%d %H:%M:%S"
|
|
135
|
+
|
|
136
|
+
# Get output settings
|
|
137
|
+
console_output = True
|
|
138
|
+
file_output = True
|
|
139
|
+
|
|
140
|
+
# Convert string logging level to constant
|
|
141
|
+
numeric_level = getattr(logging, level.upper(), None)
|
|
142
|
+
if not isinstance(numeric_level, int):
|
|
143
|
+
numeric_level = logging.INFO
|
|
144
|
+
|
|
145
|
+
# Create root logger
|
|
146
|
+
logger = logging.getLogger("mcp_proxy_adapter")
|
|
147
|
+
logger.setLevel(numeric_level)
|
|
148
|
+
logger.handlers = [] # Clear handlers in case of repeated call
|
|
149
|
+
|
|
150
|
+
# Create formatter
|
|
151
|
+
formatter = logging.Formatter(log_format, date_format)
|
|
152
|
+
|
|
153
|
+
# Create console handler if enabled
|
|
154
|
+
if console_output:
|
|
155
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
156
|
+
console_handler.setLevel(numeric_level)
|
|
157
|
+
console_handler.setFormatter(CustomFormatter())
|
|
158
|
+
logger.addHandler(console_handler)
|
|
159
|
+
|
|
160
|
+
# Create file handlers if file output is enabled
|
|
161
|
+
if file_output and log_dir:
|
|
162
|
+
# Create directory for log files if it doesn't exist
|
|
163
|
+
if not os.path.exists(log_dir):
|
|
164
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
165
|
+
|
|
166
|
+
# Main log file
|
|
167
|
+
if log_file_name:
|
|
168
|
+
main_log_path = os.path.join(log_dir, log_file_name)
|
|
169
|
+
main_handler = RotatingFileHandler(
|
|
170
|
+
main_log_path,
|
|
171
|
+
maxBytes=max_bytes,
|
|
172
|
+
backupCount=backup_count,
|
|
173
|
+
encoding="utf-8",
|
|
174
|
+
)
|
|
175
|
+
main_handler.setLevel(numeric_level)
|
|
176
|
+
main_handler.setFormatter(formatter)
|
|
177
|
+
logger.addHandler(main_handler)
|
|
178
|
+
|
|
179
|
+
# Error log file
|
|
180
|
+
if error_log_file:
|
|
181
|
+
error_log_path = os.path.join(log_dir, error_log_file)
|
|
182
|
+
error_handler = RotatingFileHandler(
|
|
183
|
+
error_log_path,
|
|
184
|
+
maxBytes=max_bytes,
|
|
185
|
+
backupCount=backup_count,
|
|
186
|
+
encoding="utf-8",
|
|
187
|
+
)
|
|
188
|
+
error_handler.setLevel(logging.ERROR)
|
|
189
|
+
error_handler.setFormatter(formatter)
|
|
190
|
+
logger.addHandler(error_handler)
|
|
191
|
+
|
|
192
|
+
# Access log file (for HTTP requests)
|
|
193
|
+
if access_log_file:
|
|
194
|
+
access_log_path = os.path.join(log_dir, access_log_file)
|
|
195
|
+
access_handler = RotatingFileHandler(
|
|
196
|
+
access_log_path,
|
|
197
|
+
maxBytes=max_bytes,
|
|
198
|
+
backupCount=backup_count,
|
|
199
|
+
encoding="utf-8",
|
|
200
|
+
)
|
|
201
|
+
access_handler.setLevel(logging.INFO)
|
|
202
|
+
access_handler.setFormatter(formatter)
|
|
203
|
+
logger.addHandler(access_handler)
|
|
204
|
+
|
|
205
|
+
# Configure loggers for external libraries
|
|
206
|
+
log_levels = {}
|
|
207
|
+
for logger_name, logger_level in log_levels.items():
|
|
208
|
+
lib_logger = logging.getLogger(logger_name)
|
|
209
|
+
lib_logger.setLevel(getattr(logging, logger_level.upper(), logging.INFO))
|
|
210
|
+
|
|
211
|
+
return logger
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _parse_file_size(size_str) -> int:
|
|
215
|
+
"""
|
|
216
|
+
Parse file size string to bytes.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
size_str: Size string (e.g., "10MB", "1GB", "100KB") or int
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Size in bytes
|
|
223
|
+
"""
|
|
224
|
+
# If it's already an int, return it
|
|
225
|
+
if isinstance(size_str, int):
|
|
226
|
+
return size_str
|
|
227
|
+
|
|
228
|
+
# Convert to string and parse
|
|
229
|
+
size_str = str(size_str).upper()
|
|
230
|
+
if size_str.endswith("KB"):
|
|
231
|
+
return int(size_str[:-2]) * 1024
|
|
232
|
+
elif size_str.endswith("MB"):
|
|
233
|
+
return int(size_str[:-2]) * 1024 * 1024
|
|
234
|
+
elif size_str.endswith("GB"):
|
|
235
|
+
return int(size_str[:-2]) * 1024 * 1024 * 1024
|
|
236
|
+
else:
|
|
237
|
+
# Assume bytes if no unit specified
|
|
238
|
+
return int(size_str)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Global get_global_logger() for use throughout the application
|
|
242
|
+
# Initialize lazily to avoid import-time errors
|
|
243
|
+
logger = None
|
|
244
|
+
|
|
245
|
+
def get_global_logger():
|
|
246
|
+
"""Get the global logger, initializing it if necessary."""
|
|
247
|
+
global logger
|
|
248
|
+
if logger is None:
|
|
249
|
+
logger = setup_logging()
|
|
250
|
+
return logger
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom ASGI application for mTLS support.
|
|
3
|
+
|
|
4
|
+
This module provides a custom ASGI application that properly handles
|
|
5
|
+
client certificates in mTLS connections.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ssl
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Dict, Any, Optional
|
|
11
|
+
from starlette.types import ASGIApp, Receive, Send, Scope
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MTLSASGIApp:
|
|
17
|
+
"""
|
|
18
|
+
Custom ASGI application that properly handles mTLS client certificates.
|
|
19
|
+
|
|
20
|
+
This wrapper ensures that client certificates are properly extracted
|
|
21
|
+
and made available to the FastAPI application.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, app: ASGIApp, ssl_config: Dict[str, Any]):
|
|
25
|
+
"""
|
|
26
|
+
Initialize MTLS ASGI application.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
app: The underlying ASGI application (FastAPI)
|
|
30
|
+
ssl_config: SSL configuration for mTLS
|
|
31
|
+
"""
|
|
32
|
+
self.app = app
|
|
33
|
+
self.ssl_config = ssl_config
|
|
34
|
+
self.verify_client = ssl_config.get("verify_client", False)
|
|
35
|
+
self.client_cert_required = ssl_config.get("client_cert_required", False)
|
|
36
|
+
|
|
37
|
+
get_global_logger().info(
|
|
38
|
+
f"MTLS ASGI app initialized: verify_client={self.verify_client}, "
|
|
39
|
+
f"client_cert_required={self.client_cert_required}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Handle ASGI request with mTLS support.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
scope: ASGI scope
|
|
48
|
+
receive: ASGI receive callable
|
|
49
|
+
send: ASGI send callable
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
# Extract client certificate from SSL context
|
|
53
|
+
if scope["type"] == "http" and "ssl" in scope:
|
|
54
|
+
client_cert = self._extract_client_certificate(scope)
|
|
55
|
+
if client_cert:
|
|
56
|
+
# Store certificate in scope for middleware access
|
|
57
|
+
scope["client_certificate"] = client_cert
|
|
58
|
+
get_global_logger().debug(
|
|
59
|
+
f"Client certificate extracted: {client_cert.get('subject', {})}"
|
|
60
|
+
)
|
|
61
|
+
elif self.client_cert_required:
|
|
62
|
+
get_global_logger().warning("Client certificate required but not provided")
|
|
63
|
+
# Return 401 Unauthorized
|
|
64
|
+
await self._send_unauthorized_response(send)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# Call the underlying application
|
|
68
|
+
await self.app(scope, receive, send)
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
get_global_logger().error(f"Error in MTLS ASGI app: {e}")
|
|
72
|
+
await self._send_error_response(send, str(e))
|
|
73
|
+
|
|
74
|
+
def _extract_client_certificate(self, scope: Scope) -> Optional[Dict[str, Any]]:
|
|
75
|
+
"""
|
|
76
|
+
Extract client certificate from SSL context.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
scope: ASGI scope
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Client certificate data or None
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
ssl_context = scope.get("ssl")
|
|
86
|
+
if not ssl_context:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
# Get peer certificate
|
|
90
|
+
cert = ssl_context.getpeercert()
|
|
91
|
+
if cert:
|
|
92
|
+
return cert
|
|
93
|
+
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
get_global_logger().error(f"Failed to extract client certificate: {e}")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
async def _send_unauthorized_response(self, send: Send) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Send 401 Unauthorized response.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
send: ASGI send callable
|
|
106
|
+
"""
|
|
107
|
+
response = {
|
|
108
|
+
"type": "http.response.start",
|
|
109
|
+
"status": 401,
|
|
110
|
+
"headers": [
|
|
111
|
+
(b"content-type", b"application/json"),
|
|
112
|
+
(b"content-length", b"163"),
|
|
113
|
+
],
|
|
114
|
+
}
|
|
115
|
+
await send(response)
|
|
116
|
+
|
|
117
|
+
body = b'{"jsonrpc": "2.0", "error": {"code": -32001, "message": "Unauthorized: Client certificate required"}, "id": null}'
|
|
118
|
+
await send({"type": "http.response.body", "body": body})
|
|
119
|
+
|
|
120
|
+
async def _send_error_response(self, send: Send, error_message: str) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Send error response.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
send: ASGI send callable
|
|
126
|
+
error_message: Error message
|
|
127
|
+
"""
|
|
128
|
+
response = {
|
|
129
|
+
"type": "http.response.start",
|
|
130
|
+
"status": 500,
|
|
131
|
+
"headers": [
|
|
132
|
+
(b"content-type", b"application/json"),
|
|
133
|
+
],
|
|
134
|
+
}
|
|
135
|
+
await send(response)
|
|
136
|
+
|
|
137
|
+
body = f'{{"jsonrpc": "2.0", "error": {{"code": -32603, "message": "Internal error: {error_message}"}}, "id": null}}'.encode()
|
|
138
|
+
await send({"type": "http.response.body", "body": body})
|
|
139
|
+
|
|
140
|
+
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MTLS ASGI Application Wrapper
|
|
3
|
+
|
|
4
|
+
This module provides an ASGI application wrapper that extracts client certificates
|
|
5
|
+
from the SSL context and makes them available to FastAPI middleware.
|
|
6
|
+
|
|
7
|
+
Author: Vasiliy Zdanovskiy
|
|
8
|
+
email: vasilyvz@gmail.com
|
|
9
|
+
Version: 1.0.0
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import ssl
|
|
14
|
+
from typing import Dict, Any, Optional
|
|
15
|
+
from cryptography import x509
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MTLSASGIApp:
|
|
21
|
+
"""
|
|
22
|
+
ASGI application wrapper for mTLS support.
|
|
23
|
+
|
|
24
|
+
Extracts client certificates from SSL context and stores them in ASGI scope
|
|
25
|
+
for access by FastAPI middleware.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, app, ssl_config: Dict[str, Any]):
|
|
29
|
+
"""
|
|
30
|
+
Initialize MTLS ASGI app.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
app: The underlying ASGI application
|
|
34
|
+
ssl_config: SSL configuration dictionary
|
|
35
|
+
"""
|
|
36
|
+
self.app = app
|
|
37
|
+
self.ssl_config = ssl_config
|
|
38
|
+
self.client_cert_required = ssl_config.get("client_cert_required", True)
|
|
39
|
+
|
|
40
|
+
get_global_logger().info(
|
|
41
|
+
f"MTLS ASGI app initialized: client_cert_required={self.client_cert_required}"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
async def __call__(self, scope: Dict[str, Any], receive, send):
|
|
45
|
+
"""
|
|
46
|
+
Handle ASGI request with mTLS support.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
scope: ASGI scope dictionary
|
|
50
|
+
receive: ASGI receive callable
|
|
51
|
+
send: ASGI send callable
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
# Extract client certificate from SSL context
|
|
55
|
+
if scope["type"] == "http" and "ssl" in scope:
|
|
56
|
+
client_cert = self._extract_client_certificate(scope)
|
|
57
|
+
if client_cert:
|
|
58
|
+
# Store certificate in scope for middleware access
|
|
59
|
+
scope["client_certificate"] = client_cert
|
|
60
|
+
get_global_logger().debug(
|
|
61
|
+
f"Client certificate extracted: {client_cert.get('subject', {})}"
|
|
62
|
+
)
|
|
63
|
+
elif self.client_cert_required:
|
|
64
|
+
get_global_logger().warning("Client certificate required but not provided")
|
|
65
|
+
# Return 401 Unauthorized
|
|
66
|
+
await self._send_unauthorized_response(send)
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Call the underlying application
|
|
70
|
+
await self.app(scope, receive, send)
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
get_global_logger().error(f"Error in MTLS ASGI app: {e}")
|
|
74
|
+
await self._send_error_response(send, str(e))
|
|
75
|
+
|
|
76
|
+
def _extract_client_certificate(
|
|
77
|
+
self, scope: Dict[str, Any]
|
|
78
|
+
) -> Optional[Dict[str, Any]]:
|
|
79
|
+
"""
|
|
80
|
+
Extract client certificate from SSL context.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
scope: ASGI scope dictionary
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Certificate dictionary or None if not found
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
ssl_context = scope.get("ssl")
|
|
90
|
+
if not ssl_context:
|
|
91
|
+
get_global_logger().debug("No SSL context found in scope")
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
# Try to get peer certificate
|
|
95
|
+
if hasattr(ssl_context, "getpeercert"):
|
|
96
|
+
cert_data = ssl_context.getpeercert(binary_form=True)
|
|
97
|
+
if cert_data:
|
|
98
|
+
# Parse certificate
|
|
99
|
+
cert = x509.load_der_x509_certificate(cert_data)
|
|
100
|
+
return self._cert_to_dict(cert)
|
|
101
|
+
else:
|
|
102
|
+
get_global_logger().debug("No certificate data in SSL context")
|
|
103
|
+
return None
|
|
104
|
+
else:
|
|
105
|
+
get_global_logger().debug("SSL context has no getpeercert method")
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
get_global_logger().error(f"Failed to extract client certificate: {e}")
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def _cert_to_dict(self, cert: x509.Certificate) -> Dict[str, Any]:
|
|
113
|
+
"""
|
|
114
|
+
Convert x509 certificate to dictionary.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
cert: x509 certificate object
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Certificate dictionary
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
# Extract subject
|
|
124
|
+
subject = {}
|
|
125
|
+
for name in cert.subject:
|
|
126
|
+
subject[name.oid._name] = name.value
|
|
127
|
+
|
|
128
|
+
# Extract issuer
|
|
129
|
+
issuer = {}
|
|
130
|
+
for name in cert.issuer:
|
|
131
|
+
issuer[name.oid._name] = name.value
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
"subject": subject,
|
|
135
|
+
"issuer": issuer,
|
|
136
|
+
"serial_number": str(cert.serial_number),
|
|
137
|
+
"not_valid_before": cert.not_valid_before.isoformat(),
|
|
138
|
+
"not_valid_after": cert.not_valid_after.isoformat(),
|
|
139
|
+
"version": cert.version.value,
|
|
140
|
+
"signature_algorithm_oid": cert.signature_algorithm_oid._name,
|
|
141
|
+
"public_key": {
|
|
142
|
+
"key_size": (
|
|
143
|
+
cert.public_key().key_size
|
|
144
|
+
if hasattr(cert.public_key(), "key_size")
|
|
145
|
+
else None
|
|
146
|
+
),
|
|
147
|
+
"public_numbers": (
|
|
148
|
+
str(cert.public_key().public_numbers())
|
|
149
|
+
if hasattr(cert.public_key(), "public_numbers")
|
|
150
|
+
else None
|
|
151
|
+
),
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
except Exception as e:
|
|
155
|
+
get_global_logger().error(f"Failed to convert certificate to dict: {e}")
|
|
156
|
+
return {"error": str(e)}
|
|
157
|
+
|
|
158
|
+
async def _send_unauthorized_response(self, send):
|
|
159
|
+
"""Send 401 Unauthorized response."""
|
|
160
|
+
await send(
|
|
161
|
+
{
|
|
162
|
+
"type": "http.response.start",
|
|
163
|
+
"status": 401,
|
|
164
|
+
"headers": [
|
|
165
|
+
(b"content-type", b"application/json"),
|
|
166
|
+
(b"content-length", b"0"),
|
|
167
|
+
],
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
await send({"type": "http.response.body", "body": b""})
|
|
171
|
+
|
|
172
|
+
async def _send_error_response(self, send, error_message: str):
|
|
173
|
+
"""Send error response."""
|
|
174
|
+
body = f'{{"error": "{error_message}"}}'.encode("utf-8")
|
|
175
|
+
await send(
|
|
176
|
+
{
|
|
177
|
+
"type": "http.response.start",
|
|
178
|
+
"status": 500,
|
|
179
|
+
"headers": [
|
|
180
|
+
(b"content-type", b"application/json"),
|
|
181
|
+
(b"content-length", str(len(body)).encode()),
|
|
182
|
+
],
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
await send({"type": "http.response.body", "body": body})
|
|
186
|
+
|
|
187
|
+
|