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.

Files changed (269) hide show
  1. mcp_proxy_adapter/__init__.py +47 -0
  2. mcp_proxy_adapter/__main__.py +13 -0
  3. mcp_proxy_adapter/api/__init__.py +0 -0
  4. mcp_proxy_adapter/api/app.py +66 -0
  5. mcp_proxy_adapter/api/core/__init__.py +18 -0
  6. mcp_proxy_adapter/api/core/app_factory.py +400 -0
  7. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  8. mcp_proxy_adapter/api/core/registration_context.py +356 -0
  9. mcp_proxy_adapter/api/core/registration_manager.py +307 -0
  10. mcp_proxy_adapter/api/core/registration_tasks.py +84 -0
  11. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  12. mcp_proxy_adapter/api/handlers.py +181 -0
  13. mcp_proxy_adapter/api/middleware/__init__.py +21 -0
  14. mcp_proxy_adapter/api/middleware/base.py +54 -0
  15. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +73 -0
  16. mcp_proxy_adapter/api/middleware/error_handling.py +76 -0
  17. mcp_proxy_adapter/api/middleware/factory.py +147 -0
  18. mcp_proxy_adapter/api/middleware/logging.py +31 -0
  19. mcp_proxy_adapter/api/middleware/performance.py +51 -0
  20. mcp_proxy_adapter/api/middleware/protocol_middleware.py +140 -0
  21. mcp_proxy_adapter/api/middleware/transport_middleware.py +87 -0
  22. mcp_proxy_adapter/api/middleware/unified_security.py +223 -0
  23. mcp_proxy_adapter/api/middleware/user_info_middleware.py +132 -0
  24. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  25. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  26. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  27. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  28. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  29. mcp_proxy_adapter/api/schemas.py +270 -0
  30. mcp_proxy_adapter/api/tool_integration.py +131 -0
  31. mcp_proxy_adapter/api/tools.py +163 -0
  32. mcp_proxy_adapter/cli/__init__.py +12 -0
  33. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  34. mcp_proxy_adapter/cli/commands/client.py +100 -0
  35. mcp_proxy_adapter/cli/commands/config_generate.py +105 -0
  36. mcp_proxy_adapter/cli/commands/config_validate.py +94 -0
  37. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  38. mcp_proxy_adapter/cli/commands/server.py +174 -0
  39. mcp_proxy_adapter/cli/commands/sets.py +132 -0
  40. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  41. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  42. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  43. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  44. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  45. mcp_proxy_adapter/cli/main.py +63 -0
  46. mcp_proxy_adapter/cli/parser.py +338 -0
  47. mcp_proxy_adapter/cli/validators.py +231 -0
  48. mcp_proxy_adapter/client/jsonrpc_client/__init__.py +9 -0
  49. mcp_proxy_adapter/client/jsonrpc_client/client.py +42 -0
  50. mcp_proxy_adapter/client/jsonrpc_client/command_api.py +45 -0
  51. mcp_proxy_adapter/client/jsonrpc_client/proxy_api.py +224 -0
  52. mcp_proxy_adapter/client/jsonrpc_client/queue_api.py +60 -0
  53. mcp_proxy_adapter/client/jsonrpc_client/transport.py +108 -0
  54. mcp_proxy_adapter/client/proxy.py +123 -0
  55. mcp_proxy_adapter/commands/__init__.py +66 -0
  56. mcp_proxy_adapter/commands/auth_validation_command.py +69 -0
  57. mcp_proxy_adapter/commands/base.py +389 -0
  58. mcp_proxy_adapter/commands/builtin_commands.py +30 -0
  59. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  60. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  61. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  62. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  63. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  64. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  65. mcp_proxy_adapter/commands/catalog_manager.py +97 -0
  66. mcp_proxy_adapter/commands/cert_monitor_command.py +552 -0
  67. mcp_proxy_adapter/commands/certificate_management_command.py +562 -0
  68. mcp_proxy_adapter/commands/command_registry.py +298 -0
  69. mcp_proxy_adapter/commands/config_command.py +102 -0
  70. mcp_proxy_adapter/commands/dependency_container.py +40 -0
  71. mcp_proxy_adapter/commands/dependency_manager.py +143 -0
  72. mcp_proxy_adapter/commands/echo_command.py +48 -0
  73. mcp_proxy_adapter/commands/health_command.py +142 -0
  74. mcp_proxy_adapter/commands/help_command.py +175 -0
  75. mcp_proxy_adapter/commands/hooks.py +172 -0
  76. mcp_proxy_adapter/commands/key_management_command.py +484 -0
  77. mcp_proxy_adapter/commands/load_command.py +123 -0
  78. mcp_proxy_adapter/commands/plugins_command.py +246 -0
  79. mcp_proxy_adapter/commands/protocol_management_command.py +216 -0
  80. mcp_proxy_adapter/commands/proxy_registration_command.py +319 -0
  81. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  82. mcp_proxy_adapter/commands/registration_status_command.py +76 -0
  83. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  84. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  85. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  86. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  87. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  88. mcp_proxy_adapter/commands/reload_command.py +136 -0
  89. mcp_proxy_adapter/commands/result.py +157 -0
  90. mcp_proxy_adapter/commands/role_test_command.py +99 -0
  91. mcp_proxy_adapter/commands/roles_management_command.py +502 -0
  92. mcp_proxy_adapter/commands/security_command.py +472 -0
  93. mcp_proxy_adapter/commands/settings_command.py +113 -0
  94. mcp_proxy_adapter/commands/ssl_setup_command.py +306 -0
  95. mcp_proxy_adapter/commands/token_management_command.py +500 -0
  96. mcp_proxy_adapter/commands/transport_management_command.py +129 -0
  97. mcp_proxy_adapter/commands/unload_command.py +92 -0
  98. mcp_proxy_adapter/config.py +32 -0
  99. mcp_proxy_adapter/core/__init__.py +8 -0
  100. mcp_proxy_adapter/core/app_factory.py +560 -0
  101. mcp_proxy_adapter/core/app_runner.py +318 -0
  102. mcp_proxy_adapter/core/auth_validator.py +508 -0
  103. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  104. mcp_proxy_adapter/core/certificate/certificate_creator.py +372 -0
  105. mcp_proxy_adapter/core/certificate/certificate_extractor.py +185 -0
  106. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  107. mcp_proxy_adapter/core/certificate/certificate_validator.py +481 -0
  108. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +65 -0
  109. mcp_proxy_adapter/core/certificate_utils.py +249 -0
  110. mcp_proxy_adapter/core/client.py +608 -0
  111. mcp_proxy_adapter/core/client_manager.py +271 -0
  112. mcp_proxy_adapter/core/client_security.py +411 -0
  113. mcp_proxy_adapter/core/config/__init__.py +18 -0
  114. mcp_proxy_adapter/core/config/config.py +237 -0
  115. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  116. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  117. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  118. mcp_proxy_adapter/core/config/simple_config.py +204 -0
  119. mcp_proxy_adapter/core/config/simple_config_generator.py +131 -0
  120. mcp_proxy_adapter/core/config/simple_config_validator.py +476 -0
  121. mcp_proxy_adapter/core/config_converter.py +252 -0
  122. mcp_proxy_adapter/core/config_validator.py +211 -0
  123. mcp_proxy_adapter/core/crl_utils.py +362 -0
  124. mcp_proxy_adapter/core/errors.py +276 -0
  125. mcp_proxy_adapter/core/job_manager.py +54 -0
  126. mcp_proxy_adapter/core/logging.py +250 -0
  127. mcp_proxy_adapter/core/mtls_asgi.py +140 -0
  128. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  129. mcp_proxy_adapter/core/mtls_proxy.py +229 -0
  130. mcp_proxy_adapter/core/mtls_server.py +154 -0
  131. mcp_proxy_adapter/core/protocol_manager.py +232 -0
  132. mcp_proxy_adapter/core/proxy/__init__.py +19 -0
  133. mcp_proxy_adapter/core/proxy/auth_manager.py +26 -0
  134. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +160 -0
  135. mcp_proxy_adapter/core/proxy/registration_client.py +186 -0
  136. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  137. mcp_proxy_adapter/core/proxy_client.py +184 -0
  138. mcp_proxy_adapter/core/proxy_registration.py +80 -0
  139. mcp_proxy_adapter/core/role_utils.py +103 -0
  140. mcp_proxy_adapter/core/security_adapter.py +343 -0
  141. mcp_proxy_adapter/core/security_factory.py +96 -0
  142. mcp_proxy_adapter/core/security_integration.py +342 -0
  143. mcp_proxy_adapter/core/server_adapter.py +251 -0
  144. mcp_proxy_adapter/core/server_engine.py +217 -0
  145. mcp_proxy_adapter/core/settings.py +260 -0
  146. mcp_proxy_adapter/core/signal_handler.py +107 -0
  147. mcp_proxy_adapter/core/ssl_utils.py +161 -0
  148. mcp_proxy_adapter/core/transport_manager.py +153 -0
  149. mcp_proxy_adapter/core/unified_config_adapter.py +471 -0
  150. mcp_proxy_adapter/core/utils.py +101 -0
  151. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  152. mcp_proxy_adapter/core/validation/config_validator.py +219 -0
  153. mcp_proxy_adapter/core/validation/file_validator.py +131 -0
  154. mcp_proxy_adapter/core/validation/protocol_validator.py +205 -0
  155. mcp_proxy_adapter/core/validation/security_validator.py +140 -0
  156. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  157. mcp_proxy_adapter/custom_openapi.py +58 -0
  158. mcp_proxy_adapter/examples/__init__.py +16 -0
  159. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  160. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  161. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  162. mcp_proxy_adapter/examples/basic_framework/main.py +52 -0
  163. mcp_proxy_adapter/examples/bugfix_certificate_config.py +261 -0
  164. mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
  165. mcp_proxy_adapter/examples/check_config.py +413 -0
  166. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  167. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  168. mcp_proxy_adapter/examples/config_builder.py +234 -0
  169. mcp_proxy_adapter/examples/config_cli.py +282 -0
  170. mcp_proxy_adapter/examples/create_test_configs.py +174 -0
  171. mcp_proxy_adapter/examples/debug_request_state.py +130 -0
  172. mcp_proxy_adapter/examples/debug_role_chain.py +191 -0
  173. mcp_proxy_adapter/examples/demo_client.py +287 -0
  174. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  175. mcp_proxy_adapter/examples/full_application/commands/__init__.py +8 -0
  176. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +45 -0
  177. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +52 -0
  178. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +32 -0
  179. mcp_proxy_adapter/examples/full_application/commands/help_command.py +54 -0
  180. mcp_proxy_adapter/examples/full_application/commands/list_command.py +57 -0
  181. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +5 -0
  182. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +29 -0
  183. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +27 -0
  184. mcp_proxy_adapter/examples/full_application/main.py +311 -0
  185. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +161 -0
  186. mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
  187. mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
  188. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +45 -0
  189. mcp_proxy_adapter/examples/full_application/test_server.py +163 -0
  190. mcp_proxy_adapter/examples/full_application/test_simple_server.py +62 -0
  191. mcp_proxy_adapter/examples/generate_config.py +502 -0
  192. mcp_proxy_adapter/examples/proxy_registration_example.py +335 -0
  193. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  194. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  195. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  196. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  197. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  198. mcp_proxy_adapter/examples/required_certificates.py +208 -0
  199. mcp_proxy_adapter/examples/run_example.py +77 -0
  200. mcp_proxy_adapter/examples/run_full_test_suite.py +619 -0
  201. mcp_proxy_adapter/examples/run_proxy_server.py +153 -0
  202. mcp_proxy_adapter/examples/run_security_tests_fixed.py +435 -0
  203. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  204. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  205. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  206. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  207. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  208. mcp_proxy_adapter/examples/security_test_client.py +72 -0
  209. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  210. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  211. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  212. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  213. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  214. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  215. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  216. mcp_proxy_adapter/examples/setup_test_environment.py +235 -0
  217. mcp_proxy_adapter/examples/simple_protocol_test.py +125 -0
  218. mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
  219. mcp_proxy_adapter/examples/test_config.py +205 -0
  220. mcp_proxy_adapter/examples/test_config_builder.py +110 -0
  221. mcp_proxy_adapter/examples/test_examples.py +308 -0
  222. mcp_proxy_adapter/examples/test_framework_complete.py +267 -0
  223. mcp_proxy_adapter/examples/test_mcp_server.py +187 -0
  224. mcp_proxy_adapter/examples/test_protocol_examples.py +337 -0
  225. mcp_proxy_adapter/examples/universal_client.py +674 -0
  226. mcp_proxy_adapter/examples/update_config_certificates.py +135 -0
  227. mcp_proxy_adapter/examples/validate_generator_compatibility.py +385 -0
  228. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +61 -0
  229. mcp_proxy_adapter/integrations/__init__.py +25 -0
  230. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  231. mcp_proxy_adapter/main.py +311 -0
  232. mcp_proxy_adapter/openapi.py +375 -0
  233. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  234. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  235. mcp_proxy_adapter/schemas/roles.json +37 -0
  236. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  237. mcp_proxy_adapter/version.py +5 -0
  238. mcp_proxy_adapter-6.9.50.dist-info/METADATA +1088 -0
  239. mcp_proxy_adapter-6.9.50.dist-info/RECORD +242 -0
  240. {mcp_proxy_adapter-2.0.1.dist-info → mcp_proxy_adapter-6.9.50.dist-info}/WHEEL +1 -1
  241. mcp_proxy_adapter-6.9.50.dist-info/entry_points.txt +14 -0
  242. mcp_proxy_adapter-6.9.50.dist-info/top_level.txt +1 -0
  243. adapters/__init__.py +0 -16
  244. analyzers/__init__.py +0 -14
  245. analyzers/docstring_analyzer.py +0 -199
  246. analyzers/type_analyzer.py +0 -151
  247. cli/__init__.py +0 -12
  248. cli/__main__.py +0 -79
  249. cli/command_runner.py +0 -233
  250. dispatchers/__init__.py +0 -14
  251. dispatchers/base_dispatcher.py +0 -85
  252. dispatchers/json_rpc_dispatcher.py +0 -198
  253. generators/__init__.py +0 -14
  254. generators/endpoint_generator.py +0 -172
  255. generators/openapi_generator.py +0 -254
  256. generators/rest_api_generator.py +0 -207
  257. mcp_proxy_adapter-2.0.1.dist-info/METADATA +0 -272
  258. mcp_proxy_adapter-2.0.1.dist-info/RECORD +0 -28
  259. mcp_proxy_adapter-2.0.1.dist-info/licenses/LICENSE +0 -21
  260. mcp_proxy_adapter-2.0.1.dist-info/top_level.txt +0 -7
  261. openapi_schema/__init__.py +0 -38
  262. openapi_schema/command_registry.py +0 -312
  263. openapi_schema/rest_schema.py +0 -510
  264. openapi_schema/rpc_generator.py +0 -307
  265. openapi_schema/rpc_schema.py +0 -416
  266. validators/__init__.py +0 -14
  267. validators/base_validator.py +0 -23
  268. validators/docstring_validator.py +0 -75
  269. 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
+