mcp-proxy-adapter 4.1.1__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 (200) hide show
  1. mcp_proxy_adapter/__main__.py +32 -0
  2. mcp_proxy_adapter/api/app.py +290 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +38 -32
  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 +201 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -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 +8 -1
  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 +366 -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 +394 -14
  41. mcp_proxy_adapter/core/app_factory.py +410 -0
  42. mcp_proxy_adapter/core/app_runner.py +272 -0
  43. mcp_proxy_adapter/core/auth_validator.py +606 -0
  44. mcp_proxy_adapter/core/certificate_utils.py +1045 -0
  45. mcp_proxy_adapter/core/client.py +574 -0
  46. mcp_proxy_adapter/core/client_manager.py +284 -0
  47. mcp_proxy_adapter/core/client_security.py +384 -0
  48. mcp_proxy_adapter/core/config_converter.py +405 -0
  49. mcp_proxy_adapter/core/config_validator.py +218 -0
  50. mcp_proxy_adapter/core/logging.py +19 -3
  51. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  52. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  53. mcp_proxy_adapter/core/protocol_manager.py +385 -0
  54. mcp_proxy_adapter/core/proxy_client.py +602 -0
  55. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  56. mcp_proxy_adapter/core/role_utils.py +426 -0
  57. mcp_proxy_adapter/core/security_adapter.py +370 -0
  58. mcp_proxy_adapter/core/security_factory.py +239 -0
  59. mcp_proxy_adapter/core/security_integration.py +286 -0
  60. mcp_proxy_adapter/core/server_adapter.py +282 -0
  61. mcp_proxy_adapter/core/server_engine.py +270 -0
  62. mcp_proxy_adapter/core/settings.py +1 -0
  63. mcp_proxy_adapter/core/ssl_utils.py +234 -0
  64. mcp_proxy_adapter/core/transport_manager.py +292 -0
  65. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  66. mcp_proxy_adapter/custom_openapi.py +22 -11
  67. mcp_proxy_adapter/examples/__init__.py +13 -4
  68. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  69. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  70. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  71. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  72. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  73. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  74. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  75. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  76. mcp_proxy_adapter/examples/demo_client.py +275 -0
  77. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  78. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  79. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  80. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  81. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  82. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  83. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  84. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  85. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  86. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  87. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  88. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  89. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  90. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  91. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  92. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  93. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  94. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  95. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  96. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  97. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  98. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  99. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  100. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  101. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  102. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  103. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  104. mcp_proxy_adapter/examples/run_example.py +59 -0
  105. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  106. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  107. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  108. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  109. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  110. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  111. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  112. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  113. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  114. mcp_proxy_adapter/examples/test_config.py +148 -0
  115. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  116. mcp_proxy_adapter/examples/test_examples.py +281 -0
  117. mcp_proxy_adapter/examples/universal_client.py +620 -0
  118. mcp_proxy_adapter/main.py +93 -0
  119. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  120. mcp_proxy_adapter/version.py +5 -2
  121. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  122. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  123. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  124. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  125. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  126. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  127. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  128. mcp_proxy_adapter/examples/README.md +0 -124
  129. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  130. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  131. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  132. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  133. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  134. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  135. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  136. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  137. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  138. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  139. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  140. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  141. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  142. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  143. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  144. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  145. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  146. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  147. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  148. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  149. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  150. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  153. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  154. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  155. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  156. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  157. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  158. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  159. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  160. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  161. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  162. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  163. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  164. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  165. mcp_proxy_adapter/tests/__init__.py +0 -0
  166. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  167. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  168. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  169. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  170. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  171. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  172. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  173. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  174. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  175. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  176. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  177. mcp_proxy_adapter/tests/conftest.py +0 -131
  178. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  180. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  181. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  182. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  183. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  184. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  185. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  186. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  187. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  188. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  189. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  190. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  191. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  192. mcp_proxy_adapter/tests/test_config.py +0 -127
  193. mcp_proxy_adapter/tests/test_utils.py +0 -65
  194. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  195. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  196. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  197. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  198. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  199. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  200. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,32 @@
1
+ """Main entry point for MCP Proxy Adapter CLI.
2
+
3
+ This module provides a command-line interface for running
4
+ MCP Proxy Adapter applications.
5
+ """
6
+
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
+
30
+
31
+ if __name__ == "__main__":
32
+ main()
@@ -3,8 +3,11 @@ 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
10
+ import asyncio
8
11
 
9
12
  from fastapi import FastAPI, Body, Depends, HTTPException, Request
10
13
  from fastapi.responses import JSONResponse, Response
@@ -17,39 +20,164 @@ from mcp_proxy_adapter.api.tools import get_tool_description, execute_tool
17
20
  from mcp_proxy_adapter.config import config
18
21
  from mcp_proxy_adapter.core.errors import MicroserviceError, NotFoundError
19
22
  from mcp_proxy_adapter.core.logging import logger, RequestLogger
23
+ from mcp_proxy_adapter.core.ssl_utils import SSLUtils
20
24
  from mcp_proxy_adapter.commands.command_registry import registry
21
25
  from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
22
26
 
23
27
 
24
- @asynccontextmanager
25
- async def lifespan(app: FastAPI):
28
+ def create_lifespan(config_path: Optional[str] = None):
26
29
  """
27
- Lifespan manager for the FastAPI application. Handles startup and shutdown events.
30
+ Create lifespan manager for the FastAPI application.
31
+
32
+ Args:
33
+ config_path: Path to configuration file (optional)
34
+
35
+ Returns:
36
+ Lifespan context manager
28
37
  """
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
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:
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")
33
125
 
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)
126
+ return lifespan
127
+
128
+
129
+ def create_ssl_context(app_config: Optional[Dict[str, Any]] = None) -> Optional[ssl.SSLContext]:
130
+ """
131
+ Create SSL context based on configuration.
37
132
 
38
- if not registry.command_exists("health"):
39
- registry.register(HealthCommand)
133
+ Args:
134
+ app_config: Application configuration dictionary (optional)
40
135
 
41
- # Discover and register additional commands automatically
42
- registry.discover_commands()
136
+ Returns:
137
+ SSL context if SSL is enabled and properly configured, None otherwise
138
+ """
139
+ current_config = app_config if app_config is not None else config.get_all()
43
140
 
44
- logger.info(f"Application started with {len(registry.get_all_commands())} commands registered")
141
+ # Try security framework SSL config first
142
+ security_config = current_config.get("security", {})
143
+ ssl_config = security_config.get("ssl", {})
45
144
 
46
- yield # Application is running
145
+ # Fallback to legacy SSL config
146
+ if not ssl_config.get("enabled", False):
147
+ ssl_config = current_config.get("ssl", {})
47
148
 
48
- # Shutdown events
49
- logger.info("Application shutting down")
149
+ if not ssl_config.get("enabled", False):
150
+ logger.info("SSL is disabled in configuration")
151
+ return None
152
+
153
+ cert_file = ssl_config.get("cert_file")
154
+ key_file = ssl_config.get("key_file")
155
+
156
+ if not cert_file or not key_file:
157
+ logger.warning("SSL enabled but certificate or key file not specified")
158
+ return None
159
+
160
+ try:
161
+ # Create SSL context using SSLUtils
162
+ ssl_context = SSLUtils.create_ssl_context(
163
+ cert_file=cert_file,
164
+ key_file=key_file,
165
+ ca_cert=ssl_config.get("ca_cert"),
166
+ verify_client=ssl_config.get("verify_client", False),
167
+ cipher_suites=ssl_config.get("cipher_suites", []),
168
+ min_tls_version=ssl_config.get("min_tls_version", "1.2"),
169
+ max_tls_version=ssl_config.get("max_tls_version", "1.3")
170
+ )
171
+
172
+ logger.info(f"SSL context created successfully for mode: {ssl_config.get('mode', 'https_only')}")
173
+ return ssl_context
174
+
175
+ except Exception as e:
176
+ logger.error(f"Failed to create SSL context: {e}")
177
+ return None
50
178
 
51
179
 
52
- 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:
53
181
  """
54
182
  Creates and configures FastAPI application.
55
183
 
@@ -57,10 +185,146 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
57
185
  title: Application title (default: "MCP Proxy Adapter")
58
186
  description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
59
187
  version: Application version (default: "1.0.0")
188
+ app_config: Application configuration dictionary (optional)
189
+ config_path: Path to configuration file (optional)
60
190
 
61
191
  Returns:
62
192
  Configured FastAPI application.
193
+
194
+ Raises:
195
+ SystemExit: If authentication is enabled but required files are missing (security issue)
63
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
+
222
+ # Security check: Validate all authentication configurations before startup
223
+ security_errors = []
224
+
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}")
266
+ if roles_config.get("enabled", False):
267
+ roles_config_path = roles_config.get("config_file", "schemas/roles_schema.json")
268
+ print(f"🔍 Debug: Checking roles file: {roles_config_path}")
269
+ if not Path(roles_config_path).exists():
270
+ security_errors.append(f"Roles are enabled but schema file not found: {roles_config_path}")
271
+
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):
282
+ # Check SSL certificate files
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'}")
289
+
290
+ if cert_file and not Path(cert_file).exists():
291
+ security_errors.append(f"Legacy SSL is enabled but certificate file not found: {cert_file}")
292
+
293
+ if key_file and not Path(key_file).exists():
294
+ security_errors.append(f"Legacy SSL is enabled but private key file not found: {key_file}")
295
+
296
+ # Check mTLS configuration
297
+ if legacy_ssl_config.get("mode") == "mtls":
298
+ ca_cert = legacy_ssl_config.get("ca_cert")
299
+ if ca_cert and not Path(ca_cert).exists():
300
+ security_errors.append(f"Legacy mTLS is enabled but CA certificate file not found: {ca_cert}")
301
+
302
+ # Check token authentication configuration
303
+ token_auth_config = legacy_ssl_config.get("token_auth", {})
304
+ if token_auth_config.get("enabled", False):
305
+ tokens_file = token_auth_config.get("tokens_file", "tokens.json")
306
+ if not Path(tokens_file).exists():
307
+ security_errors.append(f"Token authentication is enabled but tokens file not found: {tokens_file}")
308
+
309
+ # Check general authentication
310
+ if current_config.get("auth_enabled", False):
311
+ # If auth is enabled, check if any authentication method is properly configured
312
+ ssl_enabled = legacy_ssl_config.get("enabled", False)
313
+ roles_enabled = roles_config.get("enabled", False)
314
+ token_auth_enabled = token_auth_config.get("enabled", False)
315
+
316
+ if not (ssl_enabled or roles_enabled or token_auth_enabled):
317
+ security_errors.append("Authentication is enabled but no authentication method is properly configured")
318
+
319
+ # If there are security errors, block startup
320
+ if security_errors:
321
+ logger.critical("CRITICAL SECURITY ERROR: Authentication configuration issues detected:")
322
+ for error in security_errors:
323
+ logger.critical(f" - {error}")
324
+ logger.critical("Server startup blocked for security reasons.")
325
+ logger.critical("Please fix authentication configuration or disable authentication features.")
326
+ raise SystemExit(1)
327
+
64
328
  # Use provided parameters or defaults
65
329
  app_title = title or "MCP Proxy Adapter"
66
330
  app_description = description or "JSON-RPC API for interacting with MCP Proxy"
@@ -73,7 +337,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
73
337
  version=app_version,
74
338
  docs_url="/docs",
75
339
  redoc_url="/redoc",
76
- lifespan=lifespan,
340
+ lifespan=create_lifespan(config_path),
77
341
  )
78
342
 
79
343
  # Configure CORS
@@ -86,7 +350,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
86
350
  )
87
351
 
88
352
  # Setup middleware using the new middleware package
89
- setup_middleware(app)
353
+ setup_middleware(app, current_config)
90
354
 
91
355
  # Use custom OpenAPI schema
92
356
  app.openapi = lambda: custom_openapi_with_fallback(app)
@@ -132,7 +396,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
132
396
  return await handle_batch_json_rpc(request_data, request)
133
397
  else:
134
398
  # Process single request
135
- return await handle_json_rpc(request_data, request_id)
399
+ return await handle_json_rpc(request_data, request_id, request)
136
400
 
137
401
  # Command execution endpoint (/cmd)
138
402
  @app.post("/cmd")
@@ -168,7 +432,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
168
432
  # Determine request format (CommandRequest or JSON-RPC)
169
433
  if "jsonrpc" in command_data and "method" in command_data:
170
434
  # JSON-RPC format
171
- return await handle_json_rpc(command_data, request_id)
435
+ return await handle_json_rpc(command_data, request_id, request)
172
436
 
173
437
  # CommandRequest format
174
438
  if "command" not in command_data:
@@ -186,7 +450,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
186
450
  command_name = command_data["command"]
187
451
  params = command_data.get("params", {})
188
452
 
189
- req_logger.info(f"Executing command via /cmd: {command_name}, params: {params}")
453
+ req_logger.debug(f"Executing command via /cmd: {command_name}, params: {params}")
190
454
 
191
455
  # Check if command exists
192
456
  if not registry.command_exists(command_name):
@@ -203,7 +467,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
203
467
 
204
468
  # Execute command
205
469
  try:
206
- result = await execute_command(command_name, params, request_id)
470
+ result = await execute_command(command_name, params, request_id, request)
207
471
  return {"result": result}
208
472
  except MicroserviceError as e:
209
473
  # Handle command execution errors
@@ -270,7 +534,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
270
534
  request_id = getattr(request.state, "request_id", None)
271
535
 
272
536
  try:
273
- result = await execute_command(command_name, params, request_id)
537
+ result = await execute_command(command_name, params, request_id, request)
274
538
  return result
275
539
  except MicroserviceError as e:
276
540
  # Convert to proper HTTP status code
@@ -441,10 +705,3 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
441
705
  )
442
706
 
443
707
  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)
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
- )
37
-
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)
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
+
32
+ # Validate middleware configuration
33
+ if not factory.validate_middleware_config():
34
+ logger.error("Middleware configuration validation failed")
35
+ raise SystemExit(1)
36
+
37
+ logger.info("Using unified security middleware")
38
+ middleware_list = factory.create_all_middleware()
39
+
40
+ # Add middleware to application AFTER protocol middleware
41
+ for middleware in middleware_list:
42
+ # For ASGI middleware, we need to wrap the application
43
+ if hasattr(middleware, 'dispatch'):
44
+ # This is a proper ASGI middleware
45
+ app.middleware("http")(middleware.dispatch)
46
+ else:
47
+ logger.warning(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
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
+