mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.1.1__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 (264) hide show
  1. mcp_proxy_adapter/api/app.py +174 -80
  2. mcp_proxy_adapter/api/handlers.py +16 -5
  3. mcp_proxy_adapter/api/middleware/__init__.py +9 -4
  4. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  5. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  6. mcp_proxy_adapter/api/middleware/protocol_middleware.py +32 -13
  7. mcp_proxy_adapter/api/middleware/unified_security.py +160 -0
  8. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  9. mcp_proxy_adapter/commands/__init__.py +7 -1
  10. mcp_proxy_adapter/commands/base.py +7 -4
  11. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  12. mcp_proxy_adapter/commands/command_registry.py +8 -0
  13. mcp_proxy_adapter/commands/echo_command.py +81 -0
  14. mcp_proxy_adapter/commands/help_command.py +21 -14
  15. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  16. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  17. mcp_proxy_adapter/commands/security_command.py +488 -0
  18. mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
  19. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  20. mcp_proxy_adapter/config.py +81 -21
  21. mcp_proxy_adapter/core/app_factory.py +326 -0
  22. mcp_proxy_adapter/core/client_security.py +384 -0
  23. mcp_proxy_adapter/core/logging.py +8 -3
  24. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  25. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  26. mcp_proxy_adapter/core/protocol_manager.py +139 -8
  27. mcp_proxy_adapter/core/proxy_client.py +602 -0
  28. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  29. mcp_proxy_adapter/core/security_adapter.py +12 -15
  30. mcp_proxy_adapter/core/security_integration.py +285 -0
  31. mcp_proxy_adapter/core/server_adapter.py +345 -0
  32. mcp_proxy_adapter/core/server_engine.py +364 -0
  33. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  34. mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +285 -0
  35. mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +285 -0
  36. mcp_proxy_adapter/examples/README.md +230 -97
  37. mcp_proxy_adapter/examples/README_EN.md +258 -0
  38. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  39. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  40. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  41. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +43 -0
  42. mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +36 -0
  43. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +29 -0
  44. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +34 -0
  45. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  46. mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +35 -0
  47. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  48. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  49. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  50. mcp_proxy_adapter/examples/cert_config.json +9 -0
  51. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  52. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  53. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  54. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  55. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  56. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  57. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  58. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  59. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  60. mcp_proxy_adapter/examples/certs/client.key +52 -0
  61. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  62. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  63. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  64. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  65. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  66. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  67. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  68. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  69. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  70. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  71. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  72. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  73. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  74. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  75. mcp_proxy_adapter/examples/certs/server.key +52 -0
  76. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  77. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  78. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  79. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  80. mcp_proxy_adapter/examples/certs/user.key +52 -0
  81. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  82. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  83. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  84. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  85. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  86. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  87. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  88. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  89. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  90. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  91. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  92. mcp_proxy_adapter/examples/demo_client.py +341 -0
  93. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  94. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  95. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  96. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  97. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  98. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  99. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  100. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  101. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  102. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  103. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  104. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  105. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  106. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  107. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  108. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  109. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  110. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  111. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  112. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  124. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  125. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  126. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  127. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  128. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  129. mcp_proxy_adapter/examples/roles.json +38 -0
  130. mcp_proxy_adapter/examples/run_example.py +81 -0
  131. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  132. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  133. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  134. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  135. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  136. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  137. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  138. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  139. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  140. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  141. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  142. mcp_proxy_adapter/examples/test_config_generator.py +110 -0
  143. mcp_proxy_adapter/examples/test_examples.py +344 -0
  144. mcp_proxy_adapter/examples/universal_client.py +628 -0
  145. mcp_proxy_adapter/main.py +21 -10
  146. mcp_proxy_adapter/utils/config_generator.py +727 -0
  147. mcp_proxy_adapter/version.py +5 -2
  148. mcp_proxy_adapter-6.1.1.dist-info/METADATA +205 -0
  149. mcp_proxy_adapter-6.1.1.dist-info/RECORD +197 -0
  150. mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +2 -0
  151. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/licenses/LICENSE +2 -2
  152. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  153. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  154. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  155. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  156. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  157. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  158. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  159. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  160. mcp_proxy_adapter/api/middleware/security.py +0 -376
  161. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  162. mcp_proxy_adapter/examples/__init__.py +0 -7
  163. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  164. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  166. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  167. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  168. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  169. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  170. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  171. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  172. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  173. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  174. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  175. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  176. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  177. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  178. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  179. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  180. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  181. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  182. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  183. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  184. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  185. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  186. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  187. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  188. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  189. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  190. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  191. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  192. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  193. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  194. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  195. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  196. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  197. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  198. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  199. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  200. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  201. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  202. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  203. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  204. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  206. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  207. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  208. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  209. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  210. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  211. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  212. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  213. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  214. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  215. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  216. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  217. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  218. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  219. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  220. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  221. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  222. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  223. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  224. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  225. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  226. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  227. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  228. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  229. mcp_proxy_adapter/tests/__init__.py +0 -0
  230. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  231. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  232. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  233. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  234. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  235. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  236. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  237. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  238. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  239. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  240. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  241. mcp_proxy_adapter/tests/conftest.py +0 -131
  242. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  243. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  244. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  245. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  246. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  247. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  249. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  250. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  251. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  252. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  253. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  254. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  255. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  256. mcp_proxy_adapter/tests/test_config.py +0 -127
  257. mcp_proxy_adapter/tests/test_utils.py +0 -65
  258. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  259. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  260. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  261. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  262. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  263. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/WHEEL +0 -0
  264. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ Module for FastAPI application setup.
4
4
 
5
5
  import json
6
6
  import ssl
7
+ from pathlib import Path
7
8
  from typing import Any, Dict, List, Optional, Union
8
9
  from contextlib import asynccontextmanager
9
10
 
@@ -23,68 +24,102 @@ from mcp_proxy_adapter.commands.command_registry import registry
23
24
  from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
24
25
 
25
26
 
26
- @asynccontextmanager
27
- async def lifespan(app: FastAPI):
27
+ def create_lifespan(config_path: Optional[str] = None):
28
28
  """
29
- Lifespan manager for the FastAPI application. Handles startup and shutdown events.
30
- """
31
- # Startup events
32
- from mcp_proxy_adapter.commands.command_registry import registry
33
- from mcp_proxy_adapter.core.proxy_registration import register_with_proxy, unregister_from_proxy
34
-
35
- # Initialize system using unified logic
36
- # This will load config, register custom commands, and discover auto-commands
37
- init_result = await registry.reload_system()
38
-
39
- logger.info(f"Application started with {init_result['total_commands']} commands registered")
40
- logger.info(f"System initialization result: {init_result}")
41
-
42
- # Register with proxy if enabled
43
- server_config = config.get("server", {})
44
- server_host = server_config.get("host", "0.0.0.0")
45
- server_port = server_config.get("port", 8000)
46
-
47
- # Determine server URL based on SSL configuration
48
- ssl_config = config.get("ssl", {})
49
- if ssl_config.get("enabled", False):
50
- protocol = "https"
51
- else:
52
- protocol = "http"
53
-
54
- # Use localhost for external access if host is 0.0.0.0
55
- if server_host == "0.0.0.0":
56
- server_host = "localhost"
29
+ Create lifespan manager for the FastAPI application.
57
30
 
58
- server_url = f"{protocol}://{server_host}:{server_port}"
59
-
60
- # Attempt proxy registration
61
- registration_success = await register_with_proxy(server_url)
62
- if registration_success:
63
- logger.info("✅ Proxy registration completed successfully")
64
- else:
65
- logger.info("ℹ️ Proxy registration is disabled or failed")
66
-
67
- yield # Application is running
31
+ Args:
32
+ config_path: Path to configuration file (optional)
68
33
 
69
- # Shutdown events
70
- logger.info("Application shutting down")
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")
71
100
 
72
- # Unregister from proxy if enabled
73
- unregistration_success = await unregister_from_proxy()
74
- if unregistration_success:
75
- logger.info("✅ Proxy unregistration completed successfully")
76
- else:
77
- logger.warning("⚠️ Proxy unregistration failed or was disabled")
101
+ return lifespan
78
102
 
79
103
 
80
- def create_ssl_context() -> Optional[ssl.SSLContext]:
104
+ def create_ssl_context(app_config: Optional[Dict[str, Any]] = None) -> Optional[ssl.SSLContext]:
81
105
  """
82
106
  Create SSL context based on configuration.
83
107
 
108
+ Args:
109
+ app_config: Application configuration dictionary (optional)
110
+
84
111
  Returns:
85
112
  SSL context if SSL is enabled and properly configured, None otherwise
86
113
  """
87
- ssl_config = config.get("ssl", {})
114
+ current_config = app_config if app_config is not None else config.get_all()
115
+
116
+ # Try security framework SSL config first
117
+ security_config = current_config.get("security", {})
118
+ ssl_config = security_config.get("ssl", {})
119
+
120
+ # Fallback to legacy SSL config
121
+ if not ssl_config.get("enabled", False):
122
+ ssl_config = current_config.get("ssl", {})
88
123
 
89
124
  if not ssl_config.get("enabled", False):
90
125
  logger.info("SSL is disabled in configuration")
@@ -117,7 +152,7 @@ def create_ssl_context() -> Optional[ssl.SSLContext]:
117
152
  return None
118
153
 
119
154
 
120
- 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:
121
156
  """
122
157
  Creates and configures FastAPI application.
123
158
 
@@ -125,6 +160,8 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
125
160
  title: Application title (default: "MCP Proxy Adapter")
126
161
  description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
127
162
  version: Application version (default: "1.0.0")
163
+ app_config: Application configuration dictionary (optional)
164
+ config_path: Path to configuration file (optional)
128
165
 
129
166
  Returns:
130
167
  Configured FastAPI application.
@@ -132,47 +169,111 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
132
169
  Raises:
133
170
  SystemExit: If authentication is enabled but required files are missing (security issue)
134
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
+
135
186
  # Security check: Validate all authentication configurations before startup
136
187
  security_errors = []
137
188
 
138
- # Check roles configuration
139
- roles_config = config.get("roles", {})
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}")
140
230
  if roles_config.get("enabled", False):
141
231
  roles_config_path = roles_config.get("config_file", "schemas/roles_schema.json")
142
- from pathlib import Path
232
+ print(f"🔍 Debug: Checking roles file: {roles_config_path}")
143
233
  if not Path(roles_config_path).exists():
144
234
  security_errors.append(f"Roles are enabled but schema file not found: {roles_config_path}")
145
235
 
146
- # Check SSL configuration
147
- ssl_config = config.get("ssl", {})
148
- if ssl_config.get("enabled", False):
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):
149
246
  # Check SSL certificate files
150
- cert_file = ssl_config.get("cert_file")
151
- key_file = ssl_config.get("key_file")
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'}")
152
253
 
153
254
  if cert_file and not Path(cert_file).exists():
154
- security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
255
+ security_errors.append(f"Legacy SSL is enabled but certificate file not found: {cert_file}")
155
256
 
156
257
  if key_file and not Path(key_file).exists():
157
- security_errors.append(f"SSL is enabled but private key file not found: {key_file}")
258
+ security_errors.append(f"Legacy SSL is enabled but private key file not found: {key_file}")
158
259
 
159
260
  # Check mTLS configuration
160
- if ssl_config.get("mode") == "mtls":
161
- ca_cert = ssl_config.get("ca_cert")
261
+ if legacy_ssl_config.get("mode") == "mtls":
262
+ ca_cert = legacy_ssl_config.get("ca_cert")
162
263
  if ca_cert and not Path(ca_cert).exists():
163
- security_errors.append(f"mTLS is enabled but CA certificate file not found: {ca_cert}")
264
+ security_errors.append(f"Legacy mTLS is enabled but CA certificate file not found: {ca_cert}")
164
265
 
165
266
  # Check token authentication configuration
166
- token_auth_config = ssl_config.get("token_auth", {})
267
+ token_auth_config = legacy_ssl_config.get("token_auth", {})
167
268
  if token_auth_config.get("enabled", False):
168
269
  tokens_file = token_auth_config.get("tokens_file", "tokens.json")
169
270
  if not Path(tokens_file).exists():
170
271
  security_errors.append(f"Token authentication is enabled but tokens file not found: {tokens_file}")
171
272
 
172
273
  # Check general authentication
173
- if config.get("auth_enabled", False):
274
+ if current_config.get("auth_enabled", False):
174
275
  # If auth is enabled, check if any authentication method is properly configured
175
- ssl_enabled = ssl_config.get("enabled", False)
276
+ ssl_enabled = legacy_ssl_config.get("enabled", False)
176
277
  roles_enabled = roles_config.get("enabled", False)
177
278
  token_auth_enabled = token_auth_config.get("enabled", False)
178
279
 
@@ -200,7 +301,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
200
301
  version=app_version,
201
302
  docs_url="/docs",
202
303
  redoc_url="/redoc",
203
- lifespan=lifespan,
304
+ lifespan=create_lifespan(config_path),
204
305
  )
205
306
 
206
307
  # Configure CORS
@@ -213,7 +314,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
213
314
  )
214
315
 
215
316
  # Setup middleware using the new middleware package
216
- setup_middleware(app)
317
+ setup_middleware(app, current_config)
217
318
 
218
319
  # Use custom OpenAPI schema
219
320
  app.openapi = lambda: custom_openapi_with_fallback(app)
@@ -259,7 +360,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
259
360
  return await handle_batch_json_rpc(request_data, request)
260
361
  else:
261
362
  # Process single request
262
- return await handle_json_rpc(request_data, request_id)
363
+ return await handle_json_rpc(request_data, request_id, request)
263
364
 
264
365
  # Command execution endpoint (/cmd)
265
366
  @app.post("/cmd")
@@ -295,7 +396,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
295
396
  # Determine request format (CommandRequest or JSON-RPC)
296
397
  if "jsonrpc" in command_data and "method" in command_data:
297
398
  # JSON-RPC format
298
- return await handle_json_rpc(command_data, request_id)
399
+ return await handle_json_rpc(command_data, request_id, request)
299
400
 
300
401
  # CommandRequest format
301
402
  if "command" not in command_data:
@@ -330,7 +431,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
330
431
 
331
432
  # Execute command
332
433
  try:
333
- result = await execute_command(command_name, params, request_id)
434
+ result = await execute_command(command_name, params, request_id, request)
334
435
  return {"result": result}
335
436
  except MicroserviceError as e:
336
437
  # Handle command execution errors
@@ -397,7 +498,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
397
498
  request_id = getattr(request.state, "request_id", None)
398
499
 
399
500
  try:
400
- result = await execute_command(command_name, params, request_id)
501
+ result = await execute_command(command_name, params, request_id, request)
401
502
  return result
402
503
  except MicroserviceError as e:
403
504
  # Convert to proper HTTP status code
@@ -568,10 +669,3 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
568
669
  )
569
670
 
570
671
  return app
571
-
572
-
573
-
574
-
575
-
576
- # Create global application instance
577
- 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
 
@@ -52,7 +52,18 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
52
52
 
53
53
  # Use Command.run that handles instances with dependencies properly
54
54
  command_class = registry.get_command(command_name)
55
- result = await command_class.run(**params)
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)
56
67
 
57
68
  execution_time = time.time() - start_time
58
69
 
@@ -97,13 +108,13 @@ async def handle_batch_json_rpc(batch_requests: List[Dict[str, Any]], request: O
97
108
 
98
109
  for request_data in batch_requests:
99
110
  # Process each request in the batch
100
- response = await handle_json_rpc(request_data, request_id)
111
+ response = await handle_json_rpc(request_data, request_id, request)
101
112
  responses.append(response)
102
113
 
103
114
  return responses
104
115
 
105
116
 
106
- 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]:
107
118
  """
108
119
  Handles JSON-RPC request.
109
120
 
@@ -139,7 +150,7 @@ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str
139
150
 
140
151
  try:
141
152
  # Execute command
142
- result = await execute_command(method, params, request_id)
153
+ result = await execute_command(method, params, request_id, request)
143
154
 
144
155
  # Form successful response
145
156
  return {
@@ -3,6 +3,7 @@ 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
@@ -11,15 +12,19 @@ from .base import BaseMiddleware
11
12
  from .factory import MiddlewareFactory
12
13
  from .protocol_middleware import setup_protocol_middleware
13
14
 
14
- def setup_middleware(app: FastAPI) -> None:
15
+ def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None) -> None:
15
16
  """
16
17
  Sets up middleware for application using the new middleware factory.
17
18
 
18
19
  Args:
19
20
  app: FastAPI application instance.
21
+ app_config: Application configuration dictionary (optional)
20
22
  """
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
+
21
26
  # Create middleware factory
22
- factory = MiddlewareFactory(app, config.get_all())
27
+ factory = MiddlewareFactory(app, current_config)
23
28
 
24
29
  # Validate middleware configuration
25
30
  if not factory.validate_middleware_config():
@@ -38,8 +43,8 @@ def setup_middleware(app: FastAPI) -> None:
38
43
  else:
39
44
  logger.warning(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
40
45
 
41
- # Add protocol middleware (always needed)
42
- setup_protocol_middleware(app)
46
+ # Add protocol middleware with current configuration
47
+ setup_protocol_middleware(app, current_config)
43
48
 
44
49
  # Log middleware information
45
50
  middleware_info = factory.get_middleware_info()
@@ -0,0 +1,148 @@
1
+ """
2
+ Command Permission Middleware
3
+
4
+ This middleware checks permissions for specific commands based on user roles.
5
+
6
+ Author: Vasiliy Zdanovskiy
7
+ email: vasilyvz@gmail.com
8
+ """
9
+
10
+ import json
11
+ import logging
12
+ from typing import Dict, Any, Optional, Callable, Awaitable
13
+ from fastapi import Request, Response
14
+ from starlette.middleware.base import BaseHTTPMiddleware
15
+
16
+ from mcp_proxy_adapter.core.logging import logger
17
+
18
+
19
+ class CommandPermissionMiddleware(BaseHTTPMiddleware):
20
+ """
21
+ Middleware for checking command permissions.
22
+
23
+ This middleware checks if the authenticated user has the required
24
+ permissions to execute specific commands.
25
+ """
26
+
27
+ def __init__(self, app, config: Dict[str, Any]):
28
+ """
29
+ Initialize command permission middleware.
30
+
31
+ Args:
32
+ app: FastAPI application
33
+ config: Configuration dictionary
34
+ """
35
+ super().__init__(app)
36
+ self.config = config
37
+
38
+ # Define command permissions
39
+ self.command_permissions = {
40
+ "echo": ["read"],
41
+ "health": ["read"],
42
+ "role_test": ["read"],
43
+ "config": ["read"],
44
+ "help": ["read"],
45
+ # Add more commands as needed
46
+ }
47
+
48
+ logger.info("Command permission middleware initialized")
49
+
50
+ async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
51
+ """
52
+ Process request and check command permissions.
53
+
54
+ Args:
55
+ request: Request object
56
+ call_next: Next handler
57
+
58
+ Returns:
59
+ Response object
60
+ """
61
+ # Only check permissions for /cmd endpoint
62
+ if request.url.path != "/cmd":
63
+ return await call_next(request)
64
+
65
+ try:
66
+ # Get request body
67
+ body = await request.body()
68
+ if not body:
69
+ return await call_next(request)
70
+
71
+ # Parse JSON-RPC request
72
+ try:
73
+ data = json.loads(body)
74
+ except json.JSONDecodeError:
75
+ return await call_next(request)
76
+
77
+ # Extract method (command name)
78
+ method = data.get("method")
79
+ if not method:
80
+ return await call_next(request)
81
+
82
+ # Check if method requires permissions
83
+ if method not in self.command_permissions:
84
+ return await call_next(request)
85
+
86
+ required_permissions = self.command_permissions[method]
87
+
88
+ # Get user info from request state
89
+ user_info = getattr(request.state, "user", None)
90
+ if not user_info:
91
+ logger.warning(f"No user info found for command {method}")
92
+ return await call_next(request)
93
+
94
+ user_roles = user_info.get("roles", [])
95
+ user_permissions = user_info.get("permissions", [])
96
+
97
+ logger.debug(f"Checking permissions for {method}: user_roles={user_roles}, required={required_permissions}")
98
+
99
+ # Check if user has required permissions
100
+ has_permission = self._check_permissions(user_roles, user_permissions, required_permissions)
101
+
102
+ if not has_permission:
103
+ logger.warning(f"Permission denied for {method}: user_roles={user_roles}, required={required_permissions}")
104
+
105
+ # Return permission denied response
106
+ error_response = {
107
+ "error": {
108
+ "code": 403,
109
+ "message": f"Permission denied: {method} requires {required_permissions}",
110
+ "type": "permission_denied"
111
+ }
112
+ }
113
+
114
+ return Response(
115
+ content=json.dumps(error_response),
116
+ status_code=403,
117
+ media_type="application/json"
118
+ )
119
+
120
+ logger.debug(f"Permission granted for {method}")
121
+ return await call_next(request)
122
+
123
+ except Exception as e:
124
+ logger.error(f"Error in command permission middleware: {e}")
125
+ return await call_next(request)
126
+
127
+ def _check_permissions(self, user_roles: list, user_permissions: list, required_permissions: list) -> bool:
128
+ """
129
+ Check if user has required permissions.
130
+
131
+ Args:
132
+ user_roles: User roles
133
+ user_permissions: User permissions
134
+ required_permissions: Required permissions
135
+
136
+ Returns:
137
+ True if user has required permissions
138
+ """
139
+ # Admin has all permissions
140
+ if "admin" in user_roles or "*" in user_permissions:
141
+ return True
142
+
143
+ # Check if user has all required permissions
144
+ for required in required_permissions:
145
+ if required not in user_permissions:
146
+ return False
147
+
148
+ return True