mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.5__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/__init__.py +9 -5
- mcp_proxy_adapter/__main__.py +1 -1
- mcp_proxy_adapter/api/app.py +227 -176
- mcp_proxy_adapter/api/handlers.py +68 -60
- mcp_proxy_adapter/api/middleware/__init__.py +7 -5
- mcp_proxy_adapter/api/middleware/base.py +19 -16
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
- mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
- mcp_proxy_adapter/api/middleware/factory.py +50 -52
- mcp_proxy_adapter/api/middleware/logging.py +46 -30
- mcp_proxy_adapter/api/middleware/performance.py +19 -16
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
- mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
- mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
- mcp_proxy_adapter/api/schemas.py +69 -43
- mcp_proxy_adapter/api/tool_integration.py +83 -63
- mcp_proxy_adapter/api/tools.py +60 -50
- mcp_proxy_adapter/commands/__init__.py +15 -6
- mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
- mcp_proxy_adapter/commands/base.py +108 -112
- mcp_proxy_adapter/commands/builtin_commands.py +28 -18
- mcp_proxy_adapter/commands/catalog_manager.py +394 -265
- mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
- mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
- mcp_proxy_adapter/commands/command_registry.py +275 -226
- mcp_proxy_adapter/commands/config_command.py +48 -33
- mcp_proxy_adapter/commands/dependency_container.py +22 -23
- mcp_proxy_adapter/commands/dependency_manager.py +65 -56
- mcp_proxy_adapter/commands/echo_command.py +15 -15
- mcp_proxy_adapter/commands/health_command.py +31 -29
- mcp_proxy_adapter/commands/help_command.py +97 -61
- mcp_proxy_adapter/commands/hooks.py +65 -49
- mcp_proxy_adapter/commands/key_management_command.py +148 -147
- mcp_proxy_adapter/commands/load_command.py +58 -40
- mcp_proxy_adapter/commands/plugins_command.py +80 -54
- mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
- mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
- mcp_proxy_adapter/commands/reload_command.py +43 -37
- mcp_proxy_adapter/commands/result.py +26 -33
- mcp_proxy_adapter/commands/role_test_command.py +26 -26
- mcp_proxy_adapter/commands/roles_management_command.py +176 -173
- mcp_proxy_adapter/commands/security_command.py +134 -122
- mcp_proxy_adapter/commands/settings_command.py +47 -56
- mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
- mcp_proxy_adapter/commands/token_management_command.py +129 -158
- mcp_proxy_adapter/commands/transport_management_command.py +41 -36
- mcp_proxy_adapter/commands/unload_command.py +42 -37
- mcp_proxy_adapter/config.py +36 -35
- mcp_proxy_adapter/core/__init__.py +19 -21
- mcp_proxy_adapter/core/app_factory.py +30 -9
- mcp_proxy_adapter/core/app_runner.py +81 -64
- mcp_proxy_adapter/core/auth_validator.py +176 -182
- mcp_proxy_adapter/core/certificate_utils.py +469 -426
- mcp_proxy_adapter/core/client.py +155 -126
- mcp_proxy_adapter/core/client_manager.py +60 -54
- mcp_proxy_adapter/core/client_security.py +108 -88
- mcp_proxy_adapter/core/config_converter.py +176 -143
- mcp_proxy_adapter/core/config_validator.py +12 -4
- mcp_proxy_adapter/core/crl_utils.py +21 -7
- mcp_proxy_adapter/core/errors.py +64 -20
- mcp_proxy_adapter/core/logging.py +34 -29
- mcp_proxy_adapter/core/mtls_asgi.py +29 -25
- mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
- mcp_proxy_adapter/core/protocol_manager.py +154 -104
- mcp_proxy_adapter/core/proxy_client.py +202 -144
- mcp_proxy_adapter/core/proxy_registration.py +7 -3
- mcp_proxy_adapter/core/role_utils.py +139 -125
- mcp_proxy_adapter/core/security_adapter.py +88 -77
- mcp_proxy_adapter/core/security_factory.py +50 -44
- mcp_proxy_adapter/core/security_integration.py +72 -24
- mcp_proxy_adapter/core/server_adapter.py +68 -64
- mcp_proxy_adapter/core/server_engine.py +71 -53
- mcp_proxy_adapter/core/settings.py +68 -58
- mcp_proxy_adapter/core/ssl_utils.py +69 -56
- mcp_proxy_adapter/core/transport_manager.py +72 -60
- mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
- mcp_proxy_adapter/core/utils.py +4 -2
- mcp_proxy_adapter/custom_openapi.py +107 -99
- mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
- mcp_proxy_adapter/examples/debug_request_state.py +38 -19
- mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
- mcp_proxy_adapter/examples/demo_client.py +48 -36
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
- mcp_proxy_adapter/examples/generate_certificates.py +31 -16
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
- mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
- mcp_proxy_adapter/examples/run_example.py +23 -5
- mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
- mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
- mcp_proxy_adapter/examples/run_security_tests.py +103 -41
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
- mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/security_test_client.py +196 -127
- mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
- mcp_proxy_adapter/examples/test_config.py +19 -4
- mcp_proxy_adapter/examples/test_config_generator.py +23 -7
- mcp_proxy_adapter/examples/test_examples.py +84 -56
- mcp_proxy_adapter/examples/universal_client.py +119 -62
- mcp_proxy_adapter/openapi.py +108 -115
- mcp_proxy_adapter/utils/config_generator.py +429 -274
- mcp_proxy_adapter/version.py +1 -2
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
- mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -118,7 +118,9 @@ class CRLManager:
|
|
118
118
|
|
119
119
|
# If CRL is enabled but no source is configured, this is an error
|
120
120
|
if self.crl_enabled:
|
121
|
-
raise ValueError(
|
121
|
+
raise ValueError(
|
122
|
+
"CRL is enabled but neither crl_path nor crl_url is configured"
|
123
|
+
)
|
122
124
|
|
123
125
|
return None
|
124
126
|
|
@@ -149,7 +151,9 @@ class CRLManager:
|
|
149
151
|
logger.warning(f"Unexpected content type for CRL: {content_type}")
|
150
152
|
|
151
153
|
# Save to temporary file
|
152
|
-
with tempfile.NamedTemporaryFile(
|
154
|
+
with tempfile.NamedTemporaryFile(
|
155
|
+
mode="wb", suffix=".crl", delete=False
|
156
|
+
) as temp_file:
|
153
157
|
temp_file.write(response.content)
|
154
158
|
temp_file_path = temp_file.name
|
155
159
|
|
@@ -157,12 +161,16 @@ class CRLManager:
|
|
157
161
|
if SECURITY_FRAMEWORK_AVAILABLE:
|
158
162
|
try:
|
159
163
|
is_crl_valid(temp_file_path)
|
160
|
-
logger.info(
|
164
|
+
logger.info(
|
165
|
+
f"CRL downloaded and validated successfully from {self.crl_url}"
|
166
|
+
)
|
161
167
|
except Exception as e:
|
162
168
|
os.unlink(temp_file_path)
|
163
169
|
raise ValueError(f"Downloaded CRL is not valid: {e}")
|
164
170
|
else:
|
165
|
-
logger.warning(
|
171
|
+
logger.warning(
|
172
|
+
"mcp_security_framework not available, skipping CRL validation"
|
173
|
+
)
|
166
174
|
|
167
175
|
# Cache the file path
|
168
176
|
self._crl_cache[self.crl_url] = temp_file_path
|
@@ -198,7 +206,9 @@ class CRLManager:
|
|
198
206
|
except Exception as e:
|
199
207
|
raise ValueError(f"CRL file is not valid: {e}")
|
200
208
|
else:
|
201
|
-
logger.warning(
|
209
|
+
logger.warning(
|
210
|
+
"mcp_security_framework not available, skipping CRL validation"
|
211
|
+
)
|
202
212
|
|
203
213
|
return self.crl_path
|
204
214
|
|
@@ -234,7 +244,9 @@ class CRLManager:
|
|
234
244
|
if is_revoked:
|
235
245
|
logger.warning(f"Certificate is revoked according to CRL: {cert_path}")
|
236
246
|
else:
|
237
|
-
logger.debug(
|
247
|
+
logger.debug(
|
248
|
+
f"Certificate is not revoked according to CRL: {cert_path}"
|
249
|
+
)
|
238
250
|
|
239
251
|
return is_revoked
|
240
252
|
|
@@ -266,7 +278,9 @@ class CRLManager:
|
|
266
278
|
}
|
267
279
|
|
268
280
|
if not SECURITY_FRAMEWORK_AVAILABLE:
|
269
|
-
logger.warning(
|
281
|
+
logger.warning(
|
282
|
+
"mcp_security_framework not available, skipping CRL validation"
|
283
|
+
)
|
270
284
|
return {
|
271
285
|
"is_revoked": False,
|
272
286
|
"crl_checked": False,
|
mcp_proxy_adapter/core/errors.py
CHANGED
@@ -8,13 +8,16 @@ from typing import Any, Dict, List, Optional, Union
|
|
8
8
|
class MicroserviceError(Exception):
|
9
9
|
"""
|
10
10
|
Base class for all microservice exceptions.
|
11
|
-
|
11
|
+
|
12
12
|
Attributes:
|
13
13
|
message: Error message.
|
14
14
|
code: Error code.
|
15
15
|
data: Additional error data.
|
16
16
|
"""
|
17
|
-
|
17
|
+
|
18
|
+
def __init__(
|
19
|
+
self, message: str, code: int = -32000, data: Optional[Dict[str, Any]] = None
|
20
|
+
):
|
18
21
|
"""
|
19
22
|
Initialize the error.
|
20
23
|
|
@@ -35,14 +38,11 @@ class MicroserviceError(Exception):
|
|
35
38
|
Returns:
|
36
39
|
Dictionary with error information.
|
37
40
|
"""
|
38
|
-
result = {
|
39
|
-
|
40
|
-
"message": self.message
|
41
|
-
}
|
42
|
-
|
41
|
+
result = {"code": self.code, "message": self.message}
|
42
|
+
|
43
43
|
if self.data:
|
44
44
|
result["data"] = self.data
|
45
|
-
|
45
|
+
|
46
46
|
return result
|
47
47
|
|
48
48
|
|
@@ -51,7 +51,10 @@ class ParseError(MicroserviceError):
|
|
51
51
|
Error while parsing JSON request.
|
52
52
|
JSON-RPC Error code: -32700
|
53
53
|
"""
|
54
|
-
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self, message: str = "Parse error", data: Optional[Dict[str, Any]] = None
|
57
|
+
):
|
55
58
|
super().__init__(message, code=-32700, data=data)
|
56
59
|
|
57
60
|
|
@@ -60,7 +63,10 @@ class InvalidRequestError(MicroserviceError):
|
|
60
63
|
Invalid JSON-RPC request format.
|
61
64
|
JSON-RPC Error code: -32600
|
62
65
|
"""
|
63
|
-
|
66
|
+
|
67
|
+
def __init__(
|
68
|
+
self, message: str = "Invalid Request", data: Optional[Dict[str, Any]] = None
|
69
|
+
):
|
64
70
|
super().__init__(message, code=-32600, data=data)
|
65
71
|
|
66
72
|
|
@@ -69,7 +75,10 @@ class MethodNotFoundError(MicroserviceError):
|
|
69
75
|
Method not found error.
|
70
76
|
JSON-RPC Error code: -32601
|
71
77
|
"""
|
72
|
-
|
78
|
+
|
79
|
+
def __init__(
|
80
|
+
self, message: str = "Method not found", data: Optional[Dict[str, Any]] = None
|
81
|
+
):
|
73
82
|
super().__init__(message, code=-32601, data=data)
|
74
83
|
|
75
84
|
|
@@ -78,7 +87,10 @@ class InvalidParamsError(MicroserviceError):
|
|
78
87
|
Invalid method parameters.
|
79
88
|
JSON-RPC Error code: -32602
|
80
89
|
"""
|
81
|
-
|
90
|
+
|
91
|
+
def __init__(
|
92
|
+
self, message: str = "Invalid params", data: Optional[Dict[str, Any]] = None
|
93
|
+
):
|
82
94
|
super().__init__(message, code=-32602, data=data)
|
83
95
|
|
84
96
|
|
@@ -87,7 +99,10 @@ class InternalError(MicroserviceError):
|
|
87
99
|
Internal server error.
|
88
100
|
JSON-RPC Error code: -32603
|
89
101
|
"""
|
90
|
-
|
102
|
+
|
103
|
+
def __init__(
|
104
|
+
self, message: str = "Internal error", data: Optional[Dict[str, Any]] = None
|
105
|
+
):
|
91
106
|
super().__init__(message, code=-32603, data=data)
|
92
107
|
|
93
108
|
|
@@ -96,7 +111,10 @@ class ValidationError(MicroserviceError):
|
|
96
111
|
Input data validation error.
|
97
112
|
JSON-RPC Error code: -32602 (using Invalid params code)
|
98
113
|
"""
|
99
|
-
|
114
|
+
|
115
|
+
def __init__(
|
116
|
+
self, message: str = "Validation error", data: Optional[Dict[str, Any]] = None
|
117
|
+
):
|
100
118
|
super().__init__(message, code=-32602, data=data)
|
101
119
|
|
102
120
|
|
@@ -105,7 +123,12 @@ class CommandError(MicroserviceError):
|
|
105
123
|
Command execution error.
|
106
124
|
JSON-RPC Error code: -32000 (server error)
|
107
125
|
"""
|
108
|
-
|
126
|
+
|
127
|
+
def __init__(
|
128
|
+
self,
|
129
|
+
message: str = "Command execution error",
|
130
|
+
data: Optional[Dict[str, Any]] = None,
|
131
|
+
):
|
109
132
|
super().__init__(message, code=-32000, data=data)
|
110
133
|
|
111
134
|
|
@@ -114,7 +137,10 @@ class NotFoundError(MicroserviceError):
|
|
114
137
|
"Not found" error.
|
115
138
|
JSON-RPC Error code: -32601 (using Method not found code)
|
116
139
|
"""
|
117
|
-
|
140
|
+
|
141
|
+
def __init__(
|
142
|
+
self, message: str = "Resource not found", data: Optional[Dict[str, Any]] = None
|
143
|
+
):
|
118
144
|
super().__init__(message, code=-32601, data=data)
|
119
145
|
|
120
146
|
|
@@ -123,7 +149,12 @@ class ConfigurationError(MicroserviceError):
|
|
123
149
|
Configuration error.
|
124
150
|
JSON-RPC Error code: -32603 (using Internal error code)
|
125
151
|
"""
|
126
|
-
|
152
|
+
|
153
|
+
def __init__(
|
154
|
+
self,
|
155
|
+
message: str = "Configuration error",
|
156
|
+
data: Optional[Dict[str, Any]] = None,
|
157
|
+
):
|
127
158
|
super().__init__(message, code=-32603, data=data)
|
128
159
|
|
129
160
|
|
@@ -132,7 +163,12 @@ class AuthenticationError(MicroserviceError):
|
|
132
163
|
Authentication error.
|
133
164
|
JSON-RPC Error code: -32001 (server error)
|
134
165
|
"""
|
135
|
-
|
166
|
+
|
167
|
+
def __init__(
|
168
|
+
self,
|
169
|
+
message: str = "Authentication error",
|
170
|
+
data: Optional[Dict[str, Any]] = None,
|
171
|
+
):
|
136
172
|
super().__init__(message, code=-32001, data=data)
|
137
173
|
|
138
174
|
|
@@ -141,7 +177,12 @@ class AuthorizationError(MicroserviceError):
|
|
141
177
|
Authorization error.
|
142
178
|
JSON-RPC Error code: -32002 (server error)
|
143
179
|
"""
|
144
|
-
|
180
|
+
|
181
|
+
def __init__(
|
182
|
+
self,
|
183
|
+
message: str = "Authorization error",
|
184
|
+
data: Optional[Dict[str, Any]] = None,
|
185
|
+
):
|
145
186
|
super().__init__(message, code=-32002, data=data)
|
146
187
|
|
147
188
|
|
@@ -150,7 +191,10 @@ class TimeoutError(MicroserviceError):
|
|
150
191
|
Timeout error.
|
151
192
|
JSON-RPC Error code: -32003 (server error)
|
152
193
|
"""
|
153
|
-
|
194
|
+
|
195
|
+
def __init__(
|
196
|
+
self, message: str = "Timeout error", data: Optional[Dict[str, Any]] = None
|
197
|
+
):
|
154
198
|
super().__init__(message, code=-32003, data=data)
|
155
199
|
|
156
200
|
|
@@ -16,6 +16,7 @@ class CustomFormatter(logging.Formatter):
|
|
16
16
|
"""
|
17
17
|
Custom formatter for logs with colored output in console.
|
18
18
|
"""
|
19
|
+
|
19
20
|
grey = "\x1b[38;20m"
|
20
21
|
yellow = "\x1b[33;20m"
|
21
22
|
red = "\x1b[31;20m"
|
@@ -28,7 +29,7 @@ class CustomFormatter(logging.Formatter):
|
|
28
29
|
logging.INFO: grey + format_str + reset,
|
29
30
|
logging.WARNING: yellow + format_str + reset,
|
30
31
|
logging.ERROR: red + format_str + reset,
|
31
|
-
logging.CRITICAL: bold_red + format_str + reset
|
32
|
+
logging.CRITICAL: bold_red + format_str + reset,
|
32
33
|
}
|
33
34
|
|
34
35
|
def format(self, record):
|
@@ -41,11 +42,11 @@ class RequestContextFilter(logging.Filter):
|
|
41
42
|
"""
|
42
43
|
Filter for adding request context to logs.
|
43
44
|
"""
|
44
|
-
|
45
|
+
|
45
46
|
def __init__(self, request_id: Optional[str] = None):
|
46
47
|
super().__init__()
|
47
48
|
self.request_id = request_id
|
48
|
-
|
49
|
+
|
49
50
|
def filter(self, record):
|
50
51
|
# Add request_id attribute to the record
|
51
52
|
record.request_id = self.request_id or "no-request-id"
|
@@ -56,11 +57,11 @@ class RequestLogger:
|
|
56
57
|
"""
|
57
58
|
Logger class for logging requests with context.
|
58
59
|
"""
|
59
|
-
|
60
|
+
|
60
61
|
def __init__(self, logger_name: str, request_id: Optional[str] = None):
|
61
62
|
"""
|
62
63
|
Initialize request logger.
|
63
|
-
|
64
|
+
|
64
65
|
Args:
|
65
66
|
logger_name: Logger name.
|
66
67
|
request_id: Request identifier.
|
@@ -69,27 +70,27 @@ class RequestLogger:
|
|
69
70
|
self.request_id = request_id or str(uuid.uuid4())
|
70
71
|
self.filter = RequestContextFilter(self.request_id)
|
71
72
|
self.logger.addFilter(self.filter)
|
72
|
-
|
73
|
+
|
73
74
|
def debug(self, msg: str, *args, **kwargs):
|
74
75
|
"""Log message with DEBUG level."""
|
75
76
|
self.logger.debug(f"[{self.request_id}] {msg}", *args, **kwargs)
|
76
|
-
|
77
|
+
|
77
78
|
def info(self, msg: str, *args, **kwargs):
|
78
79
|
"""Log message with INFO level."""
|
79
80
|
self.logger.info(f"[{self.request_id}] {msg}", *args, **kwargs)
|
80
|
-
|
81
|
+
|
81
82
|
def warning(self, msg: str, *args, **kwargs):
|
82
83
|
"""Log message with WARNING level."""
|
83
84
|
self.logger.warning(f"[{self.request_id}] {msg}", *args, **kwargs)
|
84
|
-
|
85
|
+
|
85
86
|
def error(self, msg: str, *args, **kwargs):
|
86
87
|
"""Log message with ERROR level."""
|
87
88
|
self.logger.error(f"[{self.request_id}] {msg}", *args, **kwargs)
|
88
|
-
|
89
|
+
|
89
90
|
def exception(self, msg: str, *args, **kwargs):
|
90
91
|
"""Log exception with traceback."""
|
91
92
|
self.logger.exception(f"[{self.request_id}] {msg}", *args, **kwargs)
|
92
|
-
|
93
|
+
|
93
94
|
def critical(self, msg: str, *args, **kwargs):
|
94
95
|
"""Log message with CRITICAL level."""
|
95
96
|
self.logger.critical(f"[{self.request_id}] {msg}", *args, **kwargs)
|
@@ -102,7 +103,7 @@ def setup_logging(
|
|
102
103
|
backup_count: Optional[int] = None,
|
103
104
|
rotation_type: Optional[str] = None,
|
104
105
|
rotation_when: Optional[str] = None,
|
105
|
-
rotation_interval: Optional[int] = None
|
106
|
+
rotation_interval: Optional[int] = None,
|
106
107
|
) -> logging.Logger:
|
107
108
|
"""
|
108
109
|
Configure logging for the microservice.
|
@@ -121,7 +122,7 @@ def setup_logging(
|
|
121
122
|
"""
|
122
123
|
# Get parameters from configuration if not explicitly specified
|
123
124
|
level = level or config.get("logging.level", "INFO")
|
124
|
-
|
125
|
+
|
125
126
|
# Check debug level from config if debug is enabled
|
126
127
|
if config.get("debug.enabled", False):
|
127
128
|
debug_level = config.get("debug.level", "INFO")
|
@@ -131,27 +132,31 @@ def setup_logging(
|
|
131
132
|
if debug_level_num < level_num:
|
132
133
|
level = debug_level
|
133
134
|
logger.debug(f"Using debug level from config: {debug_level}")
|
134
|
-
|
135
|
+
|
135
136
|
log_file = log_file or config.get("logging.file")
|
136
137
|
rotation_type = rotation_type or "size" # Default to size-based rotation
|
137
|
-
|
138
|
+
|
138
139
|
# Get log directory and file settings from config
|
139
140
|
log_dir = config.get("logging.log_dir", "./logs")
|
140
141
|
log_file_name = config.get("logging.log_file", "mcp_proxy_adapter.log")
|
141
142
|
error_log_file = config.get("logging.error_log_file", "mcp_proxy_adapter_error.log")
|
142
|
-
access_log_file = config.get(
|
143
|
-
|
143
|
+
access_log_file = config.get(
|
144
|
+
"logging.access_log_file", "mcp_proxy_adapter_access.log"
|
145
|
+
)
|
146
|
+
|
144
147
|
# Get rotation settings from config
|
145
148
|
max_file_size_str = config.get("logging.max_file_size", "10MB")
|
146
149
|
backup_count = backup_count or config.get("logging.backup_count", 5)
|
147
|
-
|
150
|
+
|
148
151
|
# Parse max file size (e.g., "10MB" -> 10 * 1024 * 1024)
|
149
152
|
max_bytes = max_bytes or _parse_file_size(max_file_size_str)
|
150
|
-
|
153
|
+
|
151
154
|
# Get format settings
|
152
|
-
log_format = config.get(
|
155
|
+
log_format = config.get(
|
156
|
+
"logging.format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
157
|
+
)
|
153
158
|
date_format = config.get("logging.date_format", "%Y-%m-%d %H:%M:%S")
|
154
|
-
|
159
|
+
|
155
160
|
# Get output settings
|
156
161
|
console_output = config.get("logging.console_output", True)
|
157
162
|
file_output = config.get("logging.file_output", True)
|
@@ -189,7 +194,7 @@ def setup_logging(
|
|
189
194
|
main_log_path,
|
190
195
|
maxBytes=max_bytes,
|
191
196
|
backupCount=backup_count,
|
192
|
-
encoding="utf-8"
|
197
|
+
encoding="utf-8",
|
193
198
|
)
|
194
199
|
main_handler.setLevel(numeric_level)
|
195
200
|
main_handler.setFormatter(formatter)
|
@@ -202,7 +207,7 @@ def setup_logging(
|
|
202
207
|
error_log_path,
|
203
208
|
maxBytes=max_bytes,
|
204
209
|
backupCount=backup_count,
|
205
|
-
encoding="utf-8"
|
210
|
+
encoding="utf-8",
|
206
211
|
)
|
207
212
|
error_handler.setLevel(logging.ERROR)
|
208
213
|
error_handler.setFormatter(formatter)
|
@@ -215,7 +220,7 @@ def setup_logging(
|
|
215
220
|
access_log_path,
|
216
221
|
maxBytes=max_bytes,
|
217
222
|
backupCount=backup_count,
|
218
|
-
encoding="utf-8"
|
223
|
+
encoding="utf-8",
|
219
224
|
)
|
220
225
|
access_handler.setLevel(logging.INFO)
|
221
226
|
access_handler.setFormatter(formatter)
|
@@ -233,17 +238,17 @@ def setup_logging(
|
|
233
238
|
def _parse_file_size(size_str) -> int:
|
234
239
|
"""
|
235
240
|
Parse file size string to bytes.
|
236
|
-
|
241
|
+
|
237
242
|
Args:
|
238
243
|
size_str: Size string (e.g., "10MB", "1GB", "100KB") or int
|
239
|
-
|
244
|
+
|
240
245
|
Returns:
|
241
246
|
Size in bytes
|
242
247
|
"""
|
243
248
|
# If it's already an int, return it
|
244
249
|
if isinstance(size_str, int):
|
245
250
|
return size_str
|
246
|
-
|
251
|
+
|
247
252
|
# Convert to string and parse
|
248
253
|
size_str = str(size_str).upper()
|
249
254
|
if size_str.endswith("KB"):
|
@@ -260,10 +265,10 @@ def _parse_file_size(size_str) -> int:
|
|
260
265
|
def get_logger(name: str) -> logging.Logger:
|
261
266
|
"""
|
262
267
|
Get a logger with the specified name.
|
263
|
-
|
268
|
+
|
264
269
|
Args:
|
265
270
|
name: Logger name.
|
266
|
-
|
271
|
+
|
267
272
|
Returns:
|
268
273
|
Configured logger instance.
|
269
274
|
"""
|
@@ -19,15 +19,15 @@ logger = logging.getLogger(__name__)
|
|
19
19
|
class MTLSASGIApp:
|
20
20
|
"""
|
21
21
|
Custom ASGI application that properly handles mTLS client certificates.
|
22
|
-
|
22
|
+
|
23
23
|
This wrapper ensures that client certificates are properly extracted
|
24
24
|
and made available to the FastAPI application.
|
25
25
|
"""
|
26
|
-
|
26
|
+
|
27
27
|
def __init__(self, app: ASGIApp, ssl_config: Dict[str, Any]):
|
28
28
|
"""
|
29
29
|
Initialize MTLS ASGI application.
|
30
|
-
|
30
|
+
|
31
31
|
Args:
|
32
32
|
app: The underlying ASGI application (FastAPI)
|
33
33
|
ssl_config: SSL configuration for mTLS
|
@@ -36,14 +36,16 @@ class MTLSASGIApp:
|
|
36
36
|
self.ssl_config = ssl_config
|
37
37
|
self.verify_client = ssl_config.get("verify_client", False)
|
38
38
|
self.client_cert_required = ssl_config.get("client_cert_required", False)
|
39
|
-
|
40
|
-
logger.info(
|
41
|
-
|
42
|
-
|
39
|
+
|
40
|
+
logger.info(
|
41
|
+
f"MTLS ASGI app initialized: verify_client={self.verify_client}, "
|
42
|
+
f"client_cert_required={self.client_cert_required}"
|
43
|
+
)
|
44
|
+
|
43
45
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
44
46
|
"""
|
45
47
|
Handle ASGI request with mTLS support.
|
46
|
-
|
48
|
+
|
47
49
|
Args:
|
48
50
|
scope: ASGI scope
|
49
51
|
receive: ASGI receive callable
|
@@ -56,27 +58,29 @@ class MTLSASGIApp:
|
|
56
58
|
if client_cert:
|
57
59
|
# Store certificate in scope for middleware access
|
58
60
|
scope["client_certificate"] = client_cert
|
59
|
-
logger.debug(
|
61
|
+
logger.debug(
|
62
|
+
f"Client certificate extracted: {client_cert.get('subject', {})}"
|
63
|
+
)
|
60
64
|
elif self.client_cert_required:
|
61
65
|
logger.warning("Client certificate required but not provided")
|
62
66
|
# Return 401 Unauthorized
|
63
67
|
await self._send_unauthorized_response(send)
|
64
68
|
return
|
65
|
-
|
69
|
+
|
66
70
|
# Call the underlying application
|
67
71
|
await self.app(scope, receive, send)
|
68
|
-
|
72
|
+
|
69
73
|
except Exception as e:
|
70
74
|
logger.error(f"Error in MTLS ASGI app: {e}")
|
71
75
|
await self._send_error_response(send, str(e))
|
72
|
-
|
76
|
+
|
73
77
|
def _extract_client_certificate(self, scope: Scope) -> Optional[Dict[str, Any]]:
|
74
78
|
"""
|
75
79
|
Extract client certificate from SSL context.
|
76
|
-
|
80
|
+
|
77
81
|
Args:
|
78
82
|
scope: ASGI scope
|
79
|
-
|
83
|
+
|
80
84
|
Returns:
|
81
85
|
Client certificate data or None
|
82
86
|
"""
|
@@ -84,22 +88,22 @@ class MTLSASGIApp:
|
|
84
88
|
ssl_context = scope.get("ssl")
|
85
89
|
if not ssl_context:
|
86
90
|
return None
|
87
|
-
|
91
|
+
|
88
92
|
# Get peer certificate
|
89
93
|
cert = ssl_context.getpeercert()
|
90
94
|
if cert:
|
91
95
|
return cert
|
92
|
-
|
96
|
+
|
93
97
|
return None
|
94
|
-
|
98
|
+
|
95
99
|
except Exception as e:
|
96
100
|
logger.error(f"Failed to extract client certificate: {e}")
|
97
101
|
return None
|
98
|
-
|
102
|
+
|
99
103
|
async def _send_unauthorized_response(self, send: Send) -> None:
|
100
104
|
"""
|
101
105
|
Send 401 Unauthorized response.
|
102
|
-
|
106
|
+
|
103
107
|
Args:
|
104
108
|
send: ASGI send callable
|
105
109
|
"""
|
@@ -112,14 +116,14 @@ class MTLSASGIApp:
|
|
112
116
|
],
|
113
117
|
}
|
114
118
|
await send(response)
|
115
|
-
|
119
|
+
|
116
120
|
body = b'{"jsonrpc": "2.0", "error": {"code": -32001, "message": "Unauthorized: Client certificate required"}, "id": null}'
|
117
121
|
await send({"type": "http.response.body", "body": body})
|
118
|
-
|
122
|
+
|
119
123
|
async def _send_error_response(self, send: Send, error_message: str) -> None:
|
120
124
|
"""
|
121
125
|
Send error response.
|
122
|
-
|
126
|
+
|
123
127
|
Args:
|
124
128
|
send: ASGI send callable
|
125
129
|
error_message: Error message
|
@@ -132,7 +136,7 @@ class MTLSASGIApp:
|
|
132
136
|
],
|
133
137
|
}
|
134
138
|
await send(response)
|
135
|
-
|
139
|
+
|
136
140
|
body = f'{{"jsonrpc": "2.0", "error": {{"code": -32603, "message": "Internal error: {error_message}"}}, "id": null}}'.encode()
|
137
141
|
await send({"type": "http.response.body", "body": body})
|
138
142
|
|
@@ -140,11 +144,11 @@ class MTLSASGIApp:
|
|
140
144
|
def create_mtls_asgi_app(app: ASGIApp, ssl_config: Dict[str, Any]) -> ASGIApp:
|
141
145
|
"""
|
142
146
|
Create MTLS-enabled ASGI application.
|
143
|
-
|
147
|
+
|
144
148
|
Args:
|
145
149
|
app: The underlying ASGI application (FastAPI)
|
146
150
|
ssl_config: SSL configuration for mTLS
|
147
|
-
|
151
|
+
|
148
152
|
Returns:
|
149
153
|
MTLS-enabled ASGI application
|
150
154
|
"""
|