mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.6__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.
Files changed (131) hide show
  1. mcp_proxy_adapter/__init__.py +9 -5
  2. mcp_proxy_adapter/__main__.py +1 -1
  3. mcp_proxy_adapter/api/app.py +227 -176
  4. mcp_proxy_adapter/api/handlers.py +68 -60
  5. mcp_proxy_adapter/api/middleware/__init__.py +7 -5
  6. mcp_proxy_adapter/api/middleware/base.py +19 -16
  7. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
  8. mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
  9. mcp_proxy_adapter/api/middleware/factory.py +50 -52
  10. mcp_proxy_adapter/api/middleware/logging.py +46 -30
  11. mcp_proxy_adapter/api/middleware/performance.py +19 -16
  12. mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
  13. mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
  14. mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
  15. mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
  16. mcp_proxy_adapter/api/schemas.py +69 -43
  17. mcp_proxy_adapter/api/tool_integration.py +83 -63
  18. mcp_proxy_adapter/api/tools.py +60 -50
  19. mcp_proxy_adapter/commands/__init__.py +15 -6
  20. mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
  21. mcp_proxy_adapter/commands/base.py +108 -112
  22. mcp_proxy_adapter/commands/builtin_commands.py +28 -18
  23. mcp_proxy_adapter/commands/catalog_manager.py +394 -265
  24. mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
  25. mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
  26. mcp_proxy_adapter/commands/command_registry.py +275 -226
  27. mcp_proxy_adapter/commands/config_command.py +48 -33
  28. mcp_proxy_adapter/commands/dependency_container.py +22 -23
  29. mcp_proxy_adapter/commands/dependency_manager.py +65 -56
  30. mcp_proxy_adapter/commands/echo_command.py +15 -15
  31. mcp_proxy_adapter/commands/health_command.py +31 -29
  32. mcp_proxy_adapter/commands/help_command.py +97 -61
  33. mcp_proxy_adapter/commands/hooks.py +65 -49
  34. mcp_proxy_adapter/commands/key_management_command.py +148 -147
  35. mcp_proxy_adapter/commands/load_command.py +58 -40
  36. mcp_proxy_adapter/commands/plugins_command.py +80 -54
  37. mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
  38. mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
  39. mcp_proxy_adapter/commands/reload_command.py +43 -37
  40. mcp_proxy_adapter/commands/result.py +26 -33
  41. mcp_proxy_adapter/commands/role_test_command.py +26 -26
  42. mcp_proxy_adapter/commands/roles_management_command.py +176 -173
  43. mcp_proxy_adapter/commands/security_command.py +134 -122
  44. mcp_proxy_adapter/commands/settings_command.py +47 -56
  45. mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
  46. mcp_proxy_adapter/commands/token_management_command.py +129 -158
  47. mcp_proxy_adapter/commands/transport_management_command.py +41 -36
  48. mcp_proxy_adapter/commands/unload_command.py +42 -37
  49. mcp_proxy_adapter/config.py +36 -35
  50. mcp_proxy_adapter/core/__init__.py +19 -21
  51. mcp_proxy_adapter/core/app_factory.py +30 -9
  52. mcp_proxy_adapter/core/app_runner.py +81 -64
  53. mcp_proxy_adapter/core/auth_validator.py +176 -182
  54. mcp_proxy_adapter/core/certificate_utils.py +469 -426
  55. mcp_proxy_adapter/core/client.py +155 -126
  56. mcp_proxy_adapter/core/client_manager.py +60 -54
  57. mcp_proxy_adapter/core/client_security.py +120 -91
  58. mcp_proxy_adapter/core/config_converter.py +176 -143
  59. mcp_proxy_adapter/core/config_validator.py +12 -4
  60. mcp_proxy_adapter/core/crl_utils.py +21 -7
  61. mcp_proxy_adapter/core/errors.py +64 -20
  62. mcp_proxy_adapter/core/logging.py +34 -29
  63. mcp_proxy_adapter/core/mtls_asgi.py +29 -25
  64. mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
  65. mcp_proxy_adapter/core/protocol_manager.py +154 -104
  66. mcp_proxy_adapter/core/proxy_client.py +202 -144
  67. mcp_proxy_adapter/core/proxy_registration.py +7 -3
  68. mcp_proxy_adapter/core/role_utils.py +139 -125
  69. mcp_proxy_adapter/core/security_adapter.py +88 -77
  70. mcp_proxy_adapter/core/security_factory.py +50 -44
  71. mcp_proxy_adapter/core/security_integration.py +72 -24
  72. mcp_proxy_adapter/core/server_adapter.py +68 -64
  73. mcp_proxy_adapter/core/server_engine.py +71 -53
  74. mcp_proxy_adapter/core/settings.py +68 -58
  75. mcp_proxy_adapter/core/ssl_utils.py +69 -56
  76. mcp_proxy_adapter/core/transport_manager.py +72 -60
  77. mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
  78. mcp_proxy_adapter/core/utils.py +4 -2
  79. mcp_proxy_adapter/custom_openapi.py +107 -99
  80. mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
  81. mcp_proxy_adapter/examples/commands/__init__.py +1 -1
  82. mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
  83. mcp_proxy_adapter/examples/debug_request_state.py +38 -19
  84. mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
  85. mcp_proxy_adapter/examples/demo_client.py +48 -36
  86. mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
  87. mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
  88. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
  89. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  90. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
  91. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  92. mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
  93. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
  94. mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
  95. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
  96. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  99. mcp_proxy_adapter/examples/full_application/main.py +27 -2
  100. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
  102. mcp_proxy_adapter/examples/generate_certificates.py +31 -16
  103. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
  104. mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
  105. mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
  106. mcp_proxy_adapter/examples/run_example.py +23 -5
  107. mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
  108. mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
  109. mcp_proxy_adapter/examples/run_security_tests.py +103 -41
  110. mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
  111. mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
  112. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
  113. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
  114. mcp_proxy_adapter/examples/security_test_client.py +196 -127
  115. mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
  116. mcp_proxy_adapter/examples/test_config.py +19 -4
  117. mcp_proxy_adapter/examples/test_config_generator.py +23 -7
  118. mcp_proxy_adapter/examples/test_examples.py +84 -56
  119. mcp_proxy_adapter/examples/universal_client.py +119 -62
  120. mcp_proxy_adapter/openapi.py +108 -115
  121. mcp_proxy_adapter/utils/config_generator.py +429 -274
  122. mcp_proxy_adapter/version.py +1 -2
  123. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/METADATA +1 -1
  124. mcp_proxy_adapter-6.3.6.dist-info/RECORD +144 -0
  125. mcp_proxy_adapter-6.3.6.dist-info/top_level.txt +2 -0
  126. mcp_proxy_adapter_issue_package/demonstrate_issue.py +178 -0
  127. mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
  128. mcp_proxy_adapter-6.3.4.dist-info/top_level.txt +0 -1
  129. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/WHEEL +0 -0
  130. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/entry_points.txt +0 -0
  131. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/licenses/LICENSE +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("CRL is enabled but neither crl_path nor crl_url is configured")
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(mode="wb", suffix=".crl", delete=False) as temp_file:
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(f"CRL downloaded and validated successfully from {self.crl_url}")
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("mcp_security_framework not available, skipping CRL validation")
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("mcp_security_framework not available, skipping CRL validation")
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(f"Certificate is not revoked according to CRL: {cert_path}")
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("mcp_security_framework not available, skipping CRL validation")
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,
@@ -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
- def __init__(self, message: str, code: int = -32000, data: Optional[Dict[str, Any]] = None):
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
- "code": self.code,
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
- def __init__(self, message: str = "Parse error", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Invalid Request", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Method not found", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Invalid params", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Internal error", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Validation error", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Command execution error", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Resource not found", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Configuration error", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Authentication error", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Authorization error", data: Optional[Dict[str, Any]] = None):
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
- def __init__(self, message: str = "Timeout error", data: Optional[Dict[str, Any]] = None):
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("logging.access_log_file", "mcp_proxy_adapter_access.log")
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("logging.format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
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(f"MTLS ASGI app initialized: verify_client={self.verify_client}, "
41
- f"client_cert_required={self.client_cert_required}")
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(f"Client certificate extracted: {client_cert.get('subject', {})}")
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
  """