mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.0.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 (212) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +209 -79
  3. mcp_proxy_adapter/api/handlers.py +16 -5
  4. mcp_proxy_adapter/api/middleware/__init__.py +14 -9
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  7. mcp_proxy_adapter/api/middleware/protocol_middleware.py +84 -18
  8. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  9. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  10. mcp_proxy_adapter/commands/__init__.py +7 -1
  11. mcp_proxy_adapter/commands/base.py +7 -4
  12. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  13. mcp_proxy_adapter/commands/command_registry.py +8 -0
  14. mcp_proxy_adapter/commands/echo_command.py +81 -0
  15. mcp_proxy_adapter/commands/health_command.py +1 -1
  16. mcp_proxy_adapter/commands/help_command.py +21 -14
  17. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  18. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  19. mcp_proxy_adapter/commands/security_command.py +488 -0
  20. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  21. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  22. mcp_proxy_adapter/config.py +323 -40
  23. mcp_proxy_adapter/core/app_factory.py +410 -0
  24. mcp_proxy_adapter/core/app_runner.py +272 -0
  25. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  26. mcp_proxy_adapter/core/client.py +574 -0
  27. mcp_proxy_adapter/core/client_manager.py +284 -0
  28. mcp_proxy_adapter/core/client_security.py +384 -0
  29. mcp_proxy_adapter/core/logging.py +8 -3
  30. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  31. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  32. mcp_proxy_adapter/core/protocol_manager.py +169 -10
  33. mcp_proxy_adapter/core/proxy_client.py +602 -0
  34. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  35. mcp_proxy_adapter/core/security_adapter.py +12 -15
  36. mcp_proxy_adapter/core/security_integration.py +286 -0
  37. mcp_proxy_adapter/core/server_adapter.py +282 -0
  38. mcp_proxy_adapter/core/server_engine.py +270 -0
  39. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  40. mcp_proxy_adapter/core/transport_manager.py +5 -5
  41. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  42. mcp_proxy_adapter/examples/__init__.py +13 -4
  43. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  44. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  45. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  46. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  47. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  48. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  49. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  50. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  51. mcp_proxy_adapter/examples/demo_client.py +275 -0
  52. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  53. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  54. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  55. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  56. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  57. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  58. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  59. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  60. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  61. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  62. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  63. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  64. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  65. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  66. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  67. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  68. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  69. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  70. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  71. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  72. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  73. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  74. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  75. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  76. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  77. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  78. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  79. mcp_proxy_adapter/examples/run_example.py +59 -0
  80. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  81. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  82. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  83. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  84. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  85. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  86. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  87. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  88. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  89. mcp_proxy_adapter/examples/test_config.py +148 -0
  90. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  91. mcp_proxy_adapter/examples/test_examples.py +281 -0
  92. mcp_proxy_adapter/examples/universal_client.py +620 -0
  93. mcp_proxy_adapter/main.py +66 -148
  94. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  95. mcp_proxy_adapter/version.py +5 -2
  96. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  97. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  98. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  99. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  100. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  101. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  102. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  103. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  104. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  105. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  106. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  107. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  108. mcp_proxy_adapter/api/middleware/security.py +0 -376
  109. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  110. mcp_proxy_adapter/examples/README.md +0 -124
  111. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  112. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  113. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  114. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  115. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  116. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  117. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  118. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  119. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  120. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  121. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  122. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  123. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  124. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  125. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  126. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  127. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  128. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  129. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  130. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  131. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  132. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  133. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  134. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  135. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  136. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  137. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  138. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  139. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  140. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  141. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  142. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  143. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  144. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  145. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  146. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  147. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  148. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  149. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  150. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  153. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  154. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  155. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  156. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  157. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  158. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  159. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  160. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  161. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  162. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  163. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  164. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  166. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  167. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  168. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  169. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  170. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  171. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  172. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  173. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  174. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  175. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  176. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  177. mcp_proxy_adapter/tests/__init__.py +0 -0
  178. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  180. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  181. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  182. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  183. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  184. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  185. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  186. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  187. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  188. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  189. mcp_proxy_adapter/tests/conftest.py +0 -131
  190. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  191. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  192. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  193. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  194. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  195. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  196. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  197. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  198. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  199. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  200. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  201. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  202. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  203. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  204. mcp_proxy_adapter/tests/test_config.py +0 -127
  205. mcp_proxy_adapter/tests/test_utils.py +0 -65
  206. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  207. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  208. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  209. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  210. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  211. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,32 @@
1
- #!/usr/bin/env python3
2
- """
3
- Main entry point for MCP Proxy Adapter.
1
+ """Main entry point for MCP Proxy Adapter CLI.
4
2
 
5
- This module allows running the MCP Proxy Adapter as a module:
6
- python -m mcp_proxy_adapter
3
+ This module provides a command-line interface for running
4
+ MCP Proxy Adapter applications.
7
5
  """
8
6
 
9
- from mcp_proxy_adapter.main import main
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ # Add the current directory to Python path for imports
11
+ current_dir = Path(__file__).parent
12
+ sys.path.insert(0, str(current_dir))
13
+
14
+ from mcp_proxy_adapter.api.app import create_app
15
+
16
+
17
+ def main():
18
+ """Main CLI entry point."""
19
+ print("MCP Proxy Adapter v6.2.21")
20
+ print("========================")
21
+ print()
22
+ print("Usage:")
23
+ print(" python -m mcp_proxy_adapter")
24
+ print(" # or")
25
+ print(" mcp-proxy-adapter")
26
+ print()
27
+ print("For more information, see:")
28
+ print(" https://github.com/maverikod/mcp-proxy-adapter#readme")
29
+
10
30
 
11
31
  if __name__ == "__main__":
12
- main()
32
+ main()
@@ -4,8 +4,10 @@ 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
10
+ import asyncio
9
11
 
10
12
  from fastapi import FastAPI, Body, Depends, HTTPException, Request
11
13
  from fastapi.responses import JSONResponse, Response
@@ -23,68 +25,126 @@ from mcp_proxy_adapter.commands.command_registry import registry
23
25
  from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
24
26
 
25
27
 
26
- @asynccontextmanager
27
- async def lifespan(app: FastAPI):
28
+ def create_lifespan(config_path: Optional[str] = None):
28
29
  """
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}")
30
+ Create lifespan manager for the FastAPI application.
41
31
 
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"
57
-
58
- server_url = f"{protocol}://{server_host}:{server_port}"
32
+ Args:
33
+ config_path: Path to configuration file (optional)
59
34
 
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:
35
+ Returns:
36
+ Lifespan context manager
37
+ """
38
+ @asynccontextmanager
39
+ async def lifespan(app: FastAPI):
40
+ """
41
+ Lifespan manager for the FastAPI application. Handles startup and shutdown events.
42
+ """
43
+ # Startup events
44
+ from mcp_proxy_adapter.commands.command_registry import registry
45
+ from mcp_proxy_adapter.core.proxy_registration import (
46
+ register_with_proxy,
47
+ unregister_from_proxy,
48
+ initialize_proxy_registration,
49
+ )
50
+
51
+ # Initialize proxy registration manager WITH CURRENT CONFIG before reload_system
52
+ # so that registration inside reload_system can work
53
+ try:
54
+ initialize_proxy_registration(config.get_all())
55
+ except Exception as e:
56
+ logger.error(f"Failed to initialize proxy registration: {e}")
57
+
58
+ # Initialize system using unified logic
59
+ # This will load config, register custom commands, and discover auto-commands
60
+ # Only reload config if not already loaded from the same path
61
+ if config_path:
62
+ init_result = await registry.reload_system(config_path=config_path)
63
+ else:
64
+ init_result = await registry.reload_system()
65
+
66
+ logger.info(f"Application started with {init_result['total_commands']} commands registered")
67
+ logger.info(f"System initialization result: {init_result}")
68
+
69
+ # Initialize proxy registration manager with current config
70
+ try:
71
+ initialize_proxy_registration(config.get_all())
72
+ except Exception as e:
73
+ logger.error(f"Failed to initialize proxy registration: {e}")
74
+
75
+ # Register with proxy if enabled (run slightly delayed to ensure server is accepting connections)
76
+ server_config = config.get("server", {})
77
+ server_host = server_config.get("host", "0.0.0.0")
78
+ server_port = server_config.get("port", 8000)
79
+
80
+ # Determine server URL based on SSL configuration
81
+ # Try security framework SSL config first
82
+ security_config = config.get("security", {})
83
+ ssl_config = security_config.get("ssl", {})
84
+
85
+ # Fallback to legacy SSL config
86
+ if not ssl_config.get("enabled", False):
87
+ ssl_config = config.get("ssl", {})
88
+
89
+ if ssl_config.get("enabled", False):
90
+ protocol = "https"
91
+ else:
92
+ protocol = "http"
93
+
94
+ # Use localhost for external access if host is 0.0.0.0
95
+ if server_host == "0.0.0.0":
96
+ server_host = "localhost"
97
+
98
+ server_url = f"{protocol}://{server_host}:{server_port}"
99
+
100
+ # Attempt proxy registration in background with small delay
101
+ async def _delayed_register():
102
+ try:
103
+ await asyncio.sleep(0.5)
104
+ success = await register_with_proxy(server_url)
105
+ if success:
106
+ logger.info("✅ Proxy registration completed successfully")
107
+ else:
65
108
  logger.info("ℹ️ Proxy registration is disabled or failed")
109
+ except Exception as e:
110
+ logger.error(f"Proxy registration failed: {e}")
111
+
112
+ asyncio.create_task(_delayed_register())
113
+
114
+ yield # Application is running
115
+
116
+ # Shutdown events
117
+ logger.info("Application shutting down")
118
+
119
+ # Unregister from proxy if enabled
120
+ unregistration_success = await unregister_from_proxy()
121
+ if unregistration_success:
122
+ logger.info("✅ Proxy unregistration completed successfully")
123
+ else:
124
+ logger.warning("⚠️ Proxy unregistration failed or was disabled")
66
125
 
67
- yield # Application is running
68
-
69
- # Shutdown events
70
- logger.info("Application shutting down")
71
-
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")
126
+ return lifespan
78
127
 
79
128
 
80
- def create_ssl_context() -> Optional[ssl.SSLContext]:
129
+ def create_ssl_context(app_config: Optional[Dict[str, Any]] = None) -> Optional[ssl.SSLContext]:
81
130
  """
82
131
  Create SSL context based on configuration.
83
132
 
133
+ Args:
134
+ app_config: Application configuration dictionary (optional)
135
+
84
136
  Returns:
85
137
  SSL context if SSL is enabled and properly configured, None otherwise
86
138
  """
87
- ssl_config = config.get("ssl", {})
139
+ current_config = app_config if app_config is not None else config.get_all()
140
+
141
+ # Try security framework SSL config first
142
+ security_config = current_config.get("security", {})
143
+ ssl_config = security_config.get("ssl", {})
144
+
145
+ # Fallback to legacy SSL config
146
+ if not ssl_config.get("enabled", False):
147
+ ssl_config = current_config.get("ssl", {})
88
148
 
89
149
  if not ssl_config.get("enabled", False):
90
150
  logger.info("SSL is disabled in configuration")
@@ -117,7 +177,7 @@ def create_ssl_context() -> Optional[ssl.SSLContext]:
117
177
  return None
118
178
 
119
179
 
120
- def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None) -> FastAPI:
180
+ 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
181
  """
122
182
  Creates and configures FastAPI application.
123
183
 
@@ -125,6 +185,8 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
125
185
  title: Application title (default: "MCP Proxy Adapter")
126
186
  description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
127
187
  version: Application version (default: "1.0.0")
188
+ app_config: Application configuration dictionary (optional)
189
+ config_path: Path to configuration file (optional)
128
190
 
129
191
  Returns:
130
192
  Configured FastAPI application.
@@ -132,47 +194,122 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
132
194
  Raises:
133
195
  SystemExit: If authentication is enabled but required files are missing (security issue)
134
196
  """
197
+ # Use provided configuration or fallback to global config
198
+ if app_config is not None:
199
+ if hasattr(app_config, 'get_all'):
200
+ current_config = app_config.get_all()
201
+ elif hasattr(app_config, 'keys'):
202
+ current_config = app_config
203
+ else:
204
+ current_config = config.get_all()
205
+ else:
206
+ current_config = config.get_all()
207
+
208
+ # Debug: Check what config is passed to create_app
209
+ if app_config:
210
+ if hasattr(app_config, 'keys'):
211
+ print(f"🔍 Debug: create_app received app_config keys: {list(app_config.keys())}")
212
+ if "security" in app_config:
213
+ ssl_config = app_config["security"].get("ssl", {})
214
+ print(f"🔍 Debug: create_app SSL config: enabled={ssl_config.get('enabled', False)}")
215
+ print(f"🔍 Debug: create_app SSL config: cert_file={ssl_config.get('cert_file')}")
216
+ print(f"🔍 Debug: create_app SSL config: key_file={ssl_config.get('key_file')}")
217
+ else:
218
+ print(f"🔍 Debug: create_app received app_config type: {type(app_config)}")
219
+ else:
220
+ print("🔍 Debug: create_app received no app_config, using global config")
221
+
135
222
  # Security check: Validate all authentication configurations before startup
136
223
  security_errors = []
137
224
 
138
- # Check roles configuration
139
- roles_config = config.get("roles", {})
225
+ print(f"🔍 Debug: current_config keys: {list(current_config.keys())}")
226
+ if "security" in current_config:
227
+ print(f"🔍 Debug: security config: {current_config['security']}")
228
+ if "roles" in current_config:
229
+ print(f"🔍 Debug: roles config: {current_config['roles']}")
230
+
231
+ # Check security framework configuration only if enabled
232
+ security_config = current_config.get("security", {})
233
+ if security_config.get("enabled", False):
234
+ # Validate security framework configuration
235
+ from mcp_proxy_adapter.core.unified_config_adapter import UnifiedConfigAdapter
236
+ adapter = UnifiedConfigAdapter()
237
+ validation_result = adapter.validate_configuration(current_config)
238
+
239
+ if not validation_result.is_valid:
240
+ security_errors.extend(validation_result.errors)
241
+
242
+ # Check SSL configuration within security framework
243
+ ssl_config = security_config.get("ssl", {})
244
+ if ssl_config.get("enabled", False):
245
+ cert_file = ssl_config.get("cert_file")
246
+ key_file = ssl_config.get("key_file")
247
+
248
+ print(f"🔍 Debug: api/app.py security.ssl: cert_file={cert_file}, key_file={key_file}")
249
+ print(f"🔍 Debug: api/app.py security.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
250
+ print(f"🔍 Debug: api/app.py security.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
251
+
252
+ if cert_file and not Path(cert_file).exists():
253
+ security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
254
+
255
+ if key_file and not Path(key_file).exists():
256
+ security_errors.append(f"SSL is enabled but private key file not found: {key_file}")
257
+
258
+ # Check mTLS configuration
259
+ ca_cert_file = ssl_config.get("ca_cert_file")
260
+ if ca_cert_file and not Path(ca_cert_file).exists():
261
+ security_errors.append(f"mTLS is enabled but CA certificate file not found: {ca_cert_file}")
262
+
263
+ # Legacy configuration checks for backward compatibility
264
+ roles_config = current_config.get("roles", {})
265
+ print(f"🔍 Debug: roles_config = {roles_config}")
140
266
  if roles_config.get("enabled", False):
141
267
  roles_config_path = roles_config.get("config_file", "schemas/roles_schema.json")
142
- from pathlib import Path
268
+ print(f"🔍 Debug: Checking roles file: {roles_config_path}")
143
269
  if not Path(roles_config_path).exists():
144
270
  security_errors.append(f"Roles are enabled but schema file not found: {roles_config_path}")
145
271
 
146
- # Check SSL configuration
147
- ssl_config = config.get("ssl", {})
148
- if ssl_config.get("enabled", False):
272
+ # Check new security framework permissions configuration
273
+ security_config = current_config.get("security", {})
274
+ permissions_config = security_config.get("permissions", {})
275
+ if permissions_config.get("enabled", False):
276
+ roles_file = permissions_config.get("roles_file")
277
+ if roles_file and not Path(roles_file).exists():
278
+ security_errors.append(f"Permissions are enabled but roles file not found: {roles_file}")
279
+
280
+ legacy_ssl_config = current_config.get("ssl", {})
281
+ if legacy_ssl_config.get("enabled", False):
149
282
  # Check SSL certificate files
150
- cert_file = ssl_config.get("cert_file")
151
- key_file = ssl_config.get("key_file")
283
+ cert_file = legacy_ssl_config.get("cert_file")
284
+ key_file = legacy_ssl_config.get("key_file")
285
+
286
+ print(f"🔍 Debug: api/app.py legacy.ssl: cert_file={cert_file}, key_file={key_file}")
287
+ print(f"🔍 Debug: api/app.py legacy.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
288
+ print(f"🔍 Debug: api/app.py legacy.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
152
289
 
153
290
  if cert_file and not Path(cert_file).exists():
154
- security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
291
+ security_errors.append(f"Legacy SSL is enabled but certificate file not found: {cert_file}")
155
292
 
156
293
  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}")
294
+ security_errors.append(f"Legacy SSL is enabled but private key file not found: {key_file}")
158
295
 
159
296
  # Check mTLS configuration
160
- if ssl_config.get("mode") == "mtls":
161
- ca_cert = ssl_config.get("ca_cert")
297
+ if legacy_ssl_config.get("mode") == "mtls":
298
+ ca_cert = legacy_ssl_config.get("ca_cert")
162
299
  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}")
300
+ security_errors.append(f"Legacy mTLS is enabled but CA certificate file not found: {ca_cert}")
164
301
 
165
302
  # Check token authentication configuration
166
- token_auth_config = ssl_config.get("token_auth", {})
303
+ token_auth_config = legacy_ssl_config.get("token_auth", {})
167
304
  if token_auth_config.get("enabled", False):
168
305
  tokens_file = token_auth_config.get("tokens_file", "tokens.json")
169
306
  if not Path(tokens_file).exists():
170
307
  security_errors.append(f"Token authentication is enabled but tokens file not found: {tokens_file}")
171
308
 
172
309
  # Check general authentication
173
- if config.get("auth_enabled", False):
310
+ if current_config.get("auth_enabled", False):
174
311
  # If auth is enabled, check if any authentication method is properly configured
175
- ssl_enabled = ssl_config.get("enabled", False)
312
+ ssl_enabled = legacy_ssl_config.get("enabled", False)
176
313
  roles_enabled = roles_config.get("enabled", False)
177
314
  token_auth_enabled = token_auth_config.get("enabled", False)
178
315
 
@@ -200,7 +337,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
200
337
  version=app_version,
201
338
  docs_url="/docs",
202
339
  redoc_url="/redoc",
203
- lifespan=lifespan,
340
+ lifespan=create_lifespan(config_path),
204
341
  )
205
342
 
206
343
  # Configure CORS
@@ -213,7 +350,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
213
350
  )
214
351
 
215
352
  # Setup middleware using the new middleware package
216
- setup_middleware(app)
353
+ setup_middleware(app, current_config)
217
354
 
218
355
  # Use custom OpenAPI schema
219
356
  app.openapi = lambda: custom_openapi_with_fallback(app)
@@ -259,7 +396,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
259
396
  return await handle_batch_json_rpc(request_data, request)
260
397
  else:
261
398
  # Process single request
262
- return await handle_json_rpc(request_data, request_id)
399
+ return await handle_json_rpc(request_data, request_id, request)
263
400
 
264
401
  # Command execution endpoint (/cmd)
265
402
  @app.post("/cmd")
@@ -295,7 +432,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
295
432
  # Determine request format (CommandRequest or JSON-RPC)
296
433
  if "jsonrpc" in command_data and "method" in command_data:
297
434
  # JSON-RPC format
298
- return await handle_json_rpc(command_data, request_id)
435
+ return await handle_json_rpc(command_data, request_id, request)
299
436
 
300
437
  # CommandRequest format
301
438
  if "command" not in command_data:
@@ -330,7 +467,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
330
467
 
331
468
  # Execute command
332
469
  try:
333
- result = await execute_command(command_name, params, request_id)
470
+ result = await execute_command(command_name, params, request_id, request)
334
471
  return {"result": result}
335
472
  except MicroserviceError as e:
336
473
  # Handle command execution errors
@@ -397,7 +534,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
397
534
  request_id = getattr(request.state, "request_id", None)
398
535
 
399
536
  try:
400
- result = await execute_command(command_name, params, request_id)
537
+ result = await execute_command(command_name, params, request_id, request)
401
538
  return result
402
539
  except MicroserviceError as e:
403
540
  # Convert to proper HTTP status code
@@ -568,10 +705,3 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
568
705
  )
569
706
 
570
707
  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,25 +12,32 @@ 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
  """
21
- # Create middleware factory
22
- factory = MiddlewareFactory(app, config.get_all())
23
+ # Use provided configuration or fallback to global config
24
+ current_config = app_config if app_config is not None else config.get_all()
23
25
 
26
+ # Add protocol middleware FIRST (before other middleware)
27
+ setup_protocol_middleware(app, current_config)
28
+
29
+ # Create middleware factory
30
+ factory = MiddlewareFactory(app, current_config)
31
+
24
32
  # Validate middleware configuration
25
33
  if not factory.validate_middleware_config():
26
34
  logger.error("Middleware configuration validation failed")
27
35
  raise SystemExit(1)
28
-
36
+
29
37
  logger.info("Using unified security middleware")
30
38
  middleware_list = factory.create_all_middleware()
31
-
32
- # Add middleware to application
39
+
40
+ # Add middleware to application AFTER protocol middleware
33
41
  for middleware in middleware_list:
34
42
  # For ASGI middleware, we need to wrap the application
35
43
  if hasattr(middleware, 'dispatch'):
@@ -38,9 +46,6 @@ def setup_middleware(app: FastAPI) -> None:
38
46
  else:
39
47
  logger.warning(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
40
48
 
41
- # Add protocol middleware (always needed)
42
- setup_protocol_middleware(app)
43
-
44
49
  # Log middleware information
45
50
  middleware_info = factory.get_middleware_info()
46
51
  logger.info(f"Middleware setup completed:")