mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.1.0__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 (253) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +254 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +36 -30
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +243 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  13. mcp_proxy_adapter/commands/__init__.py +19 -4
  14. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  15. mcp_proxy_adapter/commands/base.py +66 -32
  16. mcp_proxy_adapter/commands/builtin_commands.py +95 -0
  17. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  18. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  19. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  20. mcp_proxy_adapter/commands/command_registry.py +711 -354
  21. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  22. mcp_proxy_adapter/commands/echo_command.py +81 -0
  23. mcp_proxy_adapter/commands/health_command.py +7 -0
  24. mcp_proxy_adapter/commands/help_command.py +21 -14
  25. mcp_proxy_adapter/commands/hooks.py +200 -167
  26. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  27. mcp_proxy_adapter/commands/load_command.py +176 -0
  28. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  29. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  30. mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
  31. mcp_proxy_adapter/commands/reload_command.py +48 -50
  32. mcp_proxy_adapter/commands/result.py +1 -0
  33. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  34. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  35. mcp_proxy_adapter/commands/security_command.py +488 -0
  36. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  37. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  38. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  39. mcp_proxy_adapter/commands/unload_command.py +158 -0
  40. mcp_proxy_adapter/config.py +159 -2
  41. mcp_proxy_adapter/core/app_factory.py +326 -0
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/client_security.py +384 -0
  45. mcp_proxy_adapter/core/config_converter.py +405 -0
  46. mcp_proxy_adapter/core/config_validator.py +218 -0
  47. mcp_proxy_adapter/core/logging.py +19 -3
  48. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  49. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  50. mcp_proxy_adapter/core/protocol_manager.py +235 -0
  51. mcp_proxy_adapter/core/proxy_client.py +602 -0
  52. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  53. mcp_proxy_adapter/core/role_utils.py +426 -0
  54. mcp_proxy_adapter/core/security_adapter.py +370 -0
  55. mcp_proxy_adapter/core/security_factory.py +239 -0
  56. mcp_proxy_adapter/core/security_integration.py +277 -0
  57. mcp_proxy_adapter/core/server_adapter.py +345 -0
  58. mcp_proxy_adapter/core/server_engine.py +364 -0
  59. mcp_proxy_adapter/core/settings.py +1 -0
  60. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  61. mcp_proxy_adapter/core/transport_manager.py +292 -0
  62. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  63. mcp_proxy_adapter/custom_openapi.py +22 -11
  64. mcp_proxy_adapter/examples/README.md +230 -97
  65. mcp_proxy_adapter/examples/README_EN.md +258 -0
  66. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  67. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  68. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  69. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  70. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  71. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  72. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  73. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  74. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  75. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  76. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  77. mcp_proxy_adapter/examples/cert_config.json +9 -0
  78. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  79. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  80. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  81. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  82. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  83. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  84. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  85. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  86. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  87. mcp_proxy_adapter/examples/certs/client.key +52 -0
  88. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  89. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  90. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  91. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  92. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  93. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  94. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  95. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  96. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  97. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  98. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  99. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  100. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  101. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  102. mcp_proxy_adapter/examples/certs/server.key +52 -0
  103. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  104. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  105. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  106. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  107. mcp_proxy_adapter/examples/certs/user.key +52 -0
  108. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  109. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  110. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  111. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  112. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  113. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  114. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  115. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  116. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  117. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  118. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  119. mcp_proxy_adapter/examples/demo_client.py +341 -0
  120. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  121. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  122. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  123. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  124. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  125. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  126. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  127. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  128. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  129. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  130. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  131. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  132. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  133. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  134. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  135. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  136. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  137. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  138. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  139. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  140. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  141. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  142. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  143. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  144. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  145. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  146. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  147. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  148. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  149. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  150. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  151. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  152. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  153. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  154. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  155. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  156. mcp_proxy_adapter/examples/roles.json +38 -0
  157. mcp_proxy_adapter/examples/run_example.py +81 -0
  158. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  159. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  160. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  161. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  162. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  163. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  164. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  165. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  166. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  167. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  168. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  169. mcp_proxy_adapter/examples/test_examples.py +344 -0
  170. mcp_proxy_adapter/examples/universal_client.py +628 -0
  171. mcp_proxy_adapter/main.py +186 -0
  172. mcp_proxy_adapter/utils/config_generator.py +639 -0
  173. mcp_proxy_adapter/version.py +2 -1
  174. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  175. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  176. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  177. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  178. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  179. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  180. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  181. mcp_proxy_adapter/examples/__init__.py +0 -7
  182. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  183. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  184. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  185. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  186. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  187. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  188. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  189. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  190. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  191. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  192. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  193. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  194. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  195. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  196. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  197. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  198. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  199. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  200. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  201. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  202. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  203. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  204. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  206. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  207. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  208. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  209. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  210. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  211. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  212. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  213. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  214. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  215. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  216. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  217. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  218. mcp_proxy_adapter/tests/__init__.py +0 -0
  219. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  220. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  221. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  222. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  223. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  224. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  225. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  226. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  227. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  228. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  229. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  230. mcp_proxy_adapter/tests/conftest.py +0 -131
  231. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  232. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  233. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  234. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  235. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  236. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  237. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  238. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  239. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  240. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  241. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  242. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  243. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  244. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  245. mcp_proxy_adapter/tests/test_config.py +0 -127
  246. mcp_proxy_adapter/tests/test_utils.py +0 -65
  247. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  249. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  250. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  251. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  252. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  253. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Main entry point for MCP Proxy Adapter.
4
+
5
+ This module allows running the MCP Proxy Adapter as a module:
6
+ python -m mcp_proxy_adapter
7
+ """
8
+
9
+ from mcp_proxy_adapter.main import main
10
+
11
+ if __name__ == "__main__":
12
+ main()
@@ -3,6 +3,8 @@ Module for FastAPI application setup.
3
3
  """
4
4
 
5
5
  import json
6
+ import ssl
7
+ from pathlib import Path
6
8
  from typing import Any, Dict, List, Optional, Union
7
9
  from contextlib import asynccontextmanager
8
10
 
@@ -17,39 +19,140 @@ from mcp_proxy_adapter.api.tools import get_tool_description, execute_tool
17
19
  from mcp_proxy_adapter.config import config
18
20
  from mcp_proxy_adapter.core.errors import MicroserviceError, NotFoundError
19
21
  from mcp_proxy_adapter.core.logging import logger, RequestLogger
22
+ from mcp_proxy_adapter.core.ssl_utils import SSLUtils
20
23
  from mcp_proxy_adapter.commands.command_registry import registry
21
24
  from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
22
25
 
23
26
 
24
- @asynccontextmanager
25
- async def lifespan(app: FastAPI):
27
+ def create_lifespan(config_path: Optional[str] = None):
26
28
  """
27
- Lifespan manager for the FastAPI application. Handles startup and shutdown events.
29
+ Create lifespan manager for the FastAPI application.
30
+
31
+ Args:
32
+ config_path: Path to configuration file (optional)
33
+
34
+ Returns:
35
+ Lifespan context manager
36
+ """
37
+ @asynccontextmanager
38
+ async def lifespan(app: FastAPI):
39
+ """
40
+ Lifespan manager for the FastAPI application. Handles startup and shutdown events.
41
+ """
42
+ # Startup events
43
+ from mcp_proxy_adapter.commands.command_registry import registry
44
+ from mcp_proxy_adapter.core.proxy_registration import register_with_proxy, unregister_from_proxy
45
+
46
+ # Initialize system using unified logic
47
+ # This will load config, register custom commands, and discover auto-commands
48
+ # Only reload config if not already loaded from the same path
49
+ if config_path:
50
+ init_result = await registry.reload_system(config_path=config_path)
51
+ else:
52
+ init_result = await registry.reload_system()
53
+
54
+ logger.info(f"Application started with {init_result['total_commands']} commands registered")
55
+ logger.info(f"System initialization result: {init_result}")
56
+
57
+ # Register with proxy if enabled
58
+ server_config = config.get("server", {})
59
+ server_host = server_config.get("host", "0.0.0.0")
60
+ server_port = server_config.get("port", 8000)
61
+
62
+ # Determine server URL based on SSL configuration
63
+ # Try security framework SSL config first
64
+ security_config = config.get("security", {})
65
+ ssl_config = security_config.get("ssl", {})
66
+
67
+ # Fallback to legacy SSL config
68
+ if not ssl_config.get("enabled", False):
69
+ ssl_config = config.get("ssl", {})
70
+
71
+ if ssl_config.get("enabled", False):
72
+ protocol = "https"
73
+ else:
74
+ protocol = "http"
75
+
76
+ # Use localhost for external access if host is 0.0.0.0
77
+ if server_host == "0.0.0.0":
78
+ server_host = "localhost"
79
+
80
+ server_url = f"{protocol}://{server_host}:{server_port}"
81
+
82
+ # Attempt proxy registration
83
+ registration_success = await register_with_proxy(server_url)
84
+ if registration_success:
85
+ logger.info("✅ Proxy registration completed successfully")
86
+ else:
87
+ logger.info("ℹ️ Proxy registration is disabled or failed")
88
+
89
+ yield # Application is running
90
+
91
+ # Shutdown events
92
+ logger.info("Application shutting down")
93
+
94
+ # Unregister from proxy if enabled
95
+ unregistration_success = await unregister_from_proxy()
96
+ if unregistration_success:
97
+ logger.info("✅ Proxy unregistration completed successfully")
98
+ else:
99
+ logger.warning("⚠️ Proxy unregistration failed or was disabled")
100
+
101
+ return lifespan
102
+
103
+
104
+ def create_ssl_context(app_config: Optional[Dict[str, Any]] = None) -> Optional[ssl.SSLContext]:
105
+ """
106
+ Create SSL context based on configuration.
107
+
108
+ Args:
109
+ app_config: Application configuration dictionary (optional)
110
+
111
+ Returns:
112
+ SSL context if SSL is enabled and properly configured, None otherwise
28
113
  """
29
- # Startup events
30
- from mcp_proxy_adapter.commands.command_registry import registry
31
- from mcp_proxy_adapter.commands.help_command import HelpCommand
32
- from mcp_proxy_adapter.commands.health_command import HealthCommand
114
+ current_config = app_config if app_config is not None else config.get_all()
33
115
 
34
- # Register built-in commands if they don't exist (user can override them)
35
- if not registry.command_exists("help"):
36
- registry.register(HelpCommand)
116
+ # Try security framework SSL config first
117
+ security_config = current_config.get("security", {})
118
+ ssl_config = security_config.get("ssl", {})
37
119
 
38
- if not registry.command_exists("health"):
39
- registry.register(HealthCommand)
120
+ # Fallback to legacy SSL config
121
+ if not ssl_config.get("enabled", False):
122
+ ssl_config = current_config.get("ssl", {})
40
123
 
41
- # Discover and register additional commands automatically
42
- registry.discover_commands()
124
+ if not ssl_config.get("enabled", False):
125
+ logger.info("SSL is disabled in configuration")
126
+ return None
43
127
 
44
- logger.info(f"Application started with {len(registry.get_all_commands())} commands registered")
128
+ cert_file = ssl_config.get("cert_file")
129
+ key_file = ssl_config.get("key_file")
45
130
 
46
- yield # Application is running
131
+ if not cert_file or not key_file:
132
+ logger.warning("SSL enabled but certificate or key file not specified")
133
+ return None
47
134
 
48
- # Shutdown events
49
- logger.info("Application shutting down")
135
+ try:
136
+ # Create SSL context using SSLUtils
137
+ ssl_context = SSLUtils.create_ssl_context(
138
+ cert_file=cert_file,
139
+ key_file=key_file,
140
+ ca_cert=ssl_config.get("ca_cert"),
141
+ verify_client=ssl_config.get("verify_client", False),
142
+ cipher_suites=ssl_config.get("cipher_suites", []),
143
+ min_tls_version=ssl_config.get("min_tls_version", "1.2"),
144
+ max_tls_version=ssl_config.get("max_tls_version", "1.3")
145
+ )
146
+
147
+ logger.info(f"SSL context created successfully for mode: {ssl_config.get('mode', 'https_only')}")
148
+ return ssl_context
149
+
150
+ except Exception as e:
151
+ logger.error(f"Failed to create SSL context: {e}")
152
+ return None
50
153
 
51
154
 
52
- def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None) -> FastAPI:
155
+ def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None, app_config: Optional[Dict[str, Any]] = None, config_path: Optional[str] = None) -> FastAPI:
53
156
  """
54
157
  Creates and configures FastAPI application.
55
158
 
@@ -57,10 +160,135 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
57
160
  title: Application title (default: "MCP Proxy Adapter")
58
161
  description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
59
162
  version: Application version (default: "1.0.0")
163
+ app_config: Application configuration dictionary (optional)
164
+ config_path: Path to configuration file (optional)
60
165
 
61
166
  Returns:
62
167
  Configured FastAPI application.
168
+
169
+ Raises:
170
+ SystemExit: If authentication is enabled but required files are missing (security issue)
63
171
  """
172
+ # Use provided configuration or fallback to global config
173
+ current_config = app_config if app_config is not None else config.get_all()
174
+
175
+ # Debug: Check what config is passed to create_app
176
+ if app_config:
177
+ print(f"🔍 Debug: create_app received app_config keys: {list(app_config.keys())}")
178
+ if "security" in app_config:
179
+ ssl_config = app_config["security"].get("ssl", {})
180
+ print(f"🔍 Debug: create_app SSL config: enabled={ssl_config.get('enabled', False)}")
181
+ print(f"🔍 Debug: create_app SSL config: cert_file={ssl_config.get('cert_file')}")
182
+ print(f"🔍 Debug: create_app SSL config: key_file={ssl_config.get('key_file')}")
183
+ else:
184
+ print("🔍 Debug: create_app received no app_config, using global config")
185
+
186
+ # Security check: Validate all authentication configurations before startup
187
+ security_errors = []
188
+
189
+ print(f"🔍 Debug: current_config keys: {list(current_config.keys())}")
190
+ if "security" in current_config:
191
+ print(f"🔍 Debug: security config: {current_config['security']}")
192
+ if "roles" in current_config:
193
+ print(f"🔍 Debug: roles config: {current_config['roles']}")
194
+
195
+ # Check security framework configuration only if enabled
196
+ security_config = current_config.get("security", {})
197
+ if security_config.get("enabled", False):
198
+ # Validate security framework configuration
199
+ from mcp_proxy_adapter.core.unified_config_adapter import UnifiedConfigAdapter
200
+ adapter = UnifiedConfigAdapter()
201
+ validation_result = adapter.validate_configuration(current_config)
202
+
203
+ if not validation_result.is_valid:
204
+ security_errors.extend(validation_result.errors)
205
+
206
+ # Check SSL configuration within security framework
207
+ ssl_config = security_config.get("ssl", {})
208
+ if ssl_config.get("enabled", False):
209
+ cert_file = ssl_config.get("cert_file")
210
+ key_file = ssl_config.get("key_file")
211
+
212
+ print(f"🔍 Debug: api/app.py security.ssl: cert_file={cert_file}, key_file={key_file}")
213
+ print(f"🔍 Debug: api/app.py security.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
214
+ print(f"🔍 Debug: api/app.py security.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
215
+
216
+ if cert_file and not Path(cert_file).exists():
217
+ security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
218
+
219
+ if key_file and not Path(key_file).exists():
220
+ security_errors.append(f"SSL is enabled but private key file not found: {key_file}")
221
+
222
+ # Check mTLS configuration
223
+ ca_cert_file = ssl_config.get("ca_cert_file")
224
+ if ca_cert_file and not Path(ca_cert_file).exists():
225
+ security_errors.append(f"mTLS is enabled but CA certificate file not found: {ca_cert_file}")
226
+
227
+ # Legacy configuration checks for backward compatibility
228
+ roles_config = current_config.get("roles", {})
229
+ print(f"🔍 Debug: roles_config = {roles_config}")
230
+ if roles_config.get("enabled", False):
231
+ roles_config_path = roles_config.get("config_file", "schemas/roles_schema.json")
232
+ print(f"🔍 Debug: Checking roles file: {roles_config_path}")
233
+ if not Path(roles_config_path).exists():
234
+ security_errors.append(f"Roles are enabled but schema file not found: {roles_config_path}")
235
+
236
+ # Check new security framework permissions configuration
237
+ security_config = current_config.get("security", {})
238
+ permissions_config = security_config.get("permissions", {})
239
+ if permissions_config.get("enabled", False):
240
+ roles_file = permissions_config.get("roles_file")
241
+ if roles_file and not Path(roles_file).exists():
242
+ security_errors.append(f"Permissions are enabled but roles file not found: {roles_file}")
243
+
244
+ legacy_ssl_config = current_config.get("ssl", {})
245
+ if legacy_ssl_config.get("enabled", False):
246
+ # Check SSL certificate files
247
+ cert_file = legacy_ssl_config.get("cert_file")
248
+ key_file = legacy_ssl_config.get("key_file")
249
+
250
+ print(f"🔍 Debug: api/app.py legacy.ssl: cert_file={cert_file}, key_file={key_file}")
251
+ print(f"🔍 Debug: api/app.py legacy.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
252
+ print(f"🔍 Debug: api/app.py legacy.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
253
+
254
+ if cert_file and not Path(cert_file).exists():
255
+ security_errors.append(f"Legacy SSL is enabled but certificate file not found: {cert_file}")
256
+
257
+ if key_file and not Path(key_file).exists():
258
+ security_errors.append(f"Legacy SSL is enabled but private key file not found: {key_file}")
259
+
260
+ # Check mTLS configuration
261
+ if legacy_ssl_config.get("mode") == "mtls":
262
+ ca_cert = legacy_ssl_config.get("ca_cert")
263
+ if ca_cert and not Path(ca_cert).exists():
264
+ security_errors.append(f"Legacy mTLS is enabled but CA certificate file not found: {ca_cert}")
265
+
266
+ # Check token authentication configuration
267
+ token_auth_config = legacy_ssl_config.get("token_auth", {})
268
+ if token_auth_config.get("enabled", False):
269
+ tokens_file = token_auth_config.get("tokens_file", "tokens.json")
270
+ if not Path(tokens_file).exists():
271
+ security_errors.append(f"Token authentication is enabled but tokens file not found: {tokens_file}")
272
+
273
+ # Check general authentication
274
+ if current_config.get("auth_enabled", False):
275
+ # If auth is enabled, check if any authentication method is properly configured
276
+ ssl_enabled = legacy_ssl_config.get("enabled", False)
277
+ roles_enabled = roles_config.get("enabled", False)
278
+ token_auth_enabled = token_auth_config.get("enabled", False)
279
+
280
+ if not (ssl_enabled or roles_enabled or token_auth_enabled):
281
+ security_errors.append("Authentication is enabled but no authentication method is properly configured")
282
+
283
+ # If there are security errors, block startup
284
+ if security_errors:
285
+ logger.critical("CRITICAL SECURITY ERROR: Authentication configuration issues detected:")
286
+ for error in security_errors:
287
+ logger.critical(f" - {error}")
288
+ logger.critical("Server startup blocked for security reasons.")
289
+ logger.critical("Please fix authentication configuration or disable authentication features.")
290
+ raise SystemExit(1)
291
+
64
292
  # Use provided parameters or defaults
65
293
  app_title = title or "MCP Proxy Adapter"
66
294
  app_description = description or "JSON-RPC API for interacting with MCP Proxy"
@@ -73,7 +301,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
73
301
  version=app_version,
74
302
  docs_url="/docs",
75
303
  redoc_url="/redoc",
76
- lifespan=lifespan,
304
+ lifespan=create_lifespan(config_path),
77
305
  )
78
306
 
79
307
  # Configure CORS
@@ -86,7 +314,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
86
314
  )
87
315
 
88
316
  # Setup middleware using the new middleware package
89
- setup_middleware(app)
317
+ setup_middleware(app, current_config)
90
318
 
91
319
  # Use custom OpenAPI schema
92
320
  app.openapi = lambda: custom_openapi_with_fallback(app)
@@ -132,7 +360,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
132
360
  return await handle_batch_json_rpc(request_data, request)
133
361
  else:
134
362
  # Process single request
135
- return await handle_json_rpc(request_data, request_id)
363
+ return await handle_json_rpc(request_data, request_id, request)
136
364
 
137
365
  # Command execution endpoint (/cmd)
138
366
  @app.post("/cmd")
@@ -168,7 +396,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
168
396
  # Determine request format (CommandRequest or JSON-RPC)
169
397
  if "jsonrpc" in command_data and "method" in command_data:
170
398
  # JSON-RPC format
171
- return await handle_json_rpc(command_data, request_id)
399
+ return await handle_json_rpc(command_data, request_id, request)
172
400
 
173
401
  # CommandRequest format
174
402
  if "command" not in command_data:
@@ -186,7 +414,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
186
414
  command_name = command_data["command"]
187
415
  params = command_data.get("params", {})
188
416
 
189
- req_logger.info(f"Executing command via /cmd: {command_name}, params: {params}")
417
+ req_logger.debug(f"Executing command via /cmd: {command_name}, params: {params}")
190
418
 
191
419
  # Check if command exists
192
420
  if not registry.command_exists(command_name):
@@ -203,7 +431,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
203
431
 
204
432
  # Execute command
205
433
  try:
206
- result = await execute_command(command_name, params, request_id)
434
+ result = await execute_command(command_name, params, request_id, request)
207
435
  return {"result": result}
208
436
  except MicroserviceError as e:
209
437
  # Handle command execution errors
@@ -270,7 +498,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
270
498
  request_id = getattr(request.state, "request_id", None)
271
499
 
272
500
  try:
273
- result = await execute_command(command_name, params, request_id)
501
+ result = await execute_command(command_name, params, request_id, request)
274
502
  return result
275
503
  except MicroserviceError as e:
276
504
  # Convert to proper HTTP status code
@@ -441,10 +669,3 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
441
669
  )
442
670
 
443
671
  return app
444
-
445
-
446
-
447
-
448
-
449
- # Create global application instance
450
- app = create_app()
@@ -17,7 +17,7 @@ from mcp_proxy_adapter.core.errors import (
17
17
  from mcp_proxy_adapter.core.logging import logger, RequestLogger, get_logger
18
18
 
19
19
 
20
- async def execute_command(command_name: str, params: Dict[str, Any], request_id: Optional[str] = None) -> Dict[str, Any]:
20
+ async def execute_command(command_name: str, params: Dict[str, Any], request_id: Optional[str] = None, request: Optional[Request] = None) -> Dict[str, Any]:
21
21
  """
22
22
  Executes a command with the specified name and parameters.
23
23
 
@@ -39,17 +39,43 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
39
39
  try:
40
40
  log.info(f"Executing command: {command_name}")
41
41
 
42
+ # Execute before command hooks
43
+ try:
44
+ from mcp_proxy_adapter.commands.hooks import hooks
45
+ hooks.execute_before_command_hooks(command_name, params)
46
+ log.debug(f"Executed before command hooks for: {command_name}")
47
+ except Exception as e:
48
+ log.warning(f"Failed to execute before command hooks: {e}")
49
+
42
50
  # Get command class from registry and execute with parameters
43
51
  start_time = time.time()
44
52
 
45
53
  # Use Command.run that handles instances with dependencies properly
46
- command_class = registry.get_command_with_priority(command_name)
47
- result = await command_class.run(**params)
54
+ command_class = registry.get_command(command_name)
55
+
56
+ # Create context with user info from request state
57
+ context = {}
58
+ if request and hasattr(request.state, 'user_id'):
59
+ context['user'] = {
60
+ 'id': getattr(request.state, 'user_id', None),
61
+ 'role': getattr(request.state, 'user_role', 'guest'),
62
+ 'roles': getattr(request.state, 'user_roles', ['guest']),
63
+ 'permissions': getattr(request.state, 'user_permissions', ['read'])
64
+ }
65
+
66
+ result = await command_class.run(**params, context=context)
48
67
 
49
68
  execution_time = time.time() - start_time
50
69
 
51
70
  log.info(f"Command '{command_name}' executed in {execution_time:.3f} sec")
52
71
 
72
+ # Execute after command hooks
73
+ try:
74
+ hooks.execute_after_command_hooks(command_name, params, result)
75
+ log.debug(f"Executed after command hooks for: {command_name}")
76
+ except Exception as e:
77
+ log.warning(f"Failed to execute after command hooks: {e}")
78
+
53
79
  # Return result
54
80
  return result.to_dict()
55
81
  except NotFoundError as e:
@@ -82,13 +108,13 @@ async def handle_batch_json_rpc(batch_requests: List[Dict[str, Any]], request: O
82
108
 
83
109
  for request_data in batch_requests:
84
110
  # Process each request in the batch
85
- response = await handle_json_rpc(request_data, request_id)
111
+ response = await handle_json_rpc(request_data, request_id, request)
86
112
  responses.append(response)
87
113
 
88
114
  return responses
89
115
 
90
116
 
91
- async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str] = None) -> Dict[str, Any]:
117
+ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str] = None, request: Optional[Request] = None) -> Dict[str, Any]:
92
118
  """
93
119
  Handles JSON-RPC request.
94
120
 
@@ -124,7 +150,7 @@ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str
124
150
 
125
151
  try:
126
152
  # Execute command
127
- result = await execute_command(method, params, request_id)
153
+ result = await execute_command(method, params, request_id, request)
128
154
 
129
155
  # Form successful response
130
156
  return {
@@ -3,47 +3,53 @@ Middleware package for API.
3
3
  This package contains middleware components for request processing.
4
4
  """
5
5
 
6
+ from typing import Dict, Any, Optional
6
7
  from fastapi import FastAPI
7
8
 
8
9
  from mcp_proxy_adapter.core.logging import logger
10
+ from mcp_proxy_adapter.config import config
9
11
  from .base import BaseMiddleware
10
- from .logging import LoggingMiddleware
11
- from .error_handling import ErrorHandlingMiddleware
12
- from .auth import AuthMiddleware
13
- from .rate_limit import RateLimitMiddleware
14
- from .performance import PerformanceMiddleware
12
+ from .factory import MiddlewareFactory
13
+ from .protocol_middleware import setup_protocol_middleware
15
14
 
16
- def setup_middleware(app: FastAPI) -> None:
15
+ def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None) -> None:
17
16
  """
18
- Sets up middleware for application.
17
+ Sets up middleware for application using the new middleware factory.
19
18
 
20
19
  Args:
21
20
  app: FastAPI application instance.
21
+ app_config: Application configuration dictionary (optional)
22
22
  """
23
- # Add error handling middleware first (last to execute)
24
- app.add_middleware(ErrorHandlingMiddleware)
23
+ # Use provided configuration or fallback to global config
24
+ current_config = app_config if app_config is not None else config.get_all()
25
25
 
26
- # Add logging middleware
27
- app.add_middleware(LoggingMiddleware)
26
+ # Create middleware factory
27
+ factory = MiddlewareFactory(app, current_config)
28
28
 
29
- # Add rate limiting middleware if configured
30
- from mcp_proxy_adapter.config import config
31
- if config.get("rate_limit_enabled", False):
32
- app.add_middleware(
33
- RateLimitMiddleware,
34
- rate_limit=config.get("rate_limit", 100),
35
- time_window=config.get("rate_limit_window", 60)
36
- )
29
+ # Validate middleware configuration
30
+ if not factory.validate_middleware_config():
31
+ logger.error("Middleware configuration validation failed")
32
+ raise SystemExit(1)
37
33
 
38
- # Добавляем authentication middleware с явным указанием auth_enabled
39
- auth_enabled = config.get("auth_enabled", False)
40
- app.add_middleware(
41
- AuthMiddleware,
42
- api_keys=config.get("api_keys", {}),
43
- auth_enabled=auth_enabled
44
- )
45
-
46
- # Add performance middleware
47
- app.add_middleware(PerformanceMiddleware)
34
+ logger.info("Using unified security middleware")
35
+ middleware_list = factory.create_all_middleware()
36
+
37
+ # Add middleware to application
38
+ for middleware in middleware_list:
39
+ # For ASGI middleware, we need to wrap the application
40
+ if hasattr(middleware, 'dispatch'):
41
+ # This is a proper ASGI middleware
42
+ app.middleware("http")(middleware.dispatch)
43
+ else:
44
+ logger.warning(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
45
+
46
+ # Add protocol middleware (always needed)
47
+ setup_protocol_middleware(app)
48
48
 
49
- logger.info(f"Middleware setup completed. Auth enabled: {auth_enabled}")
49
+ # Log middleware information
50
+ middleware_info = factory.get_middleware_info()
51
+ logger.info(f"Middleware setup completed:")
52
+ logger.info(f" - Total middleware: {middleware_info['total_middleware']}")
53
+ logger.info(f" - Types: {', '.join(middleware_info['middleware_types'])}")
54
+ logger.info(f" - Security enabled: {middleware_info['security_enabled']}")
55
+