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,410 @@
1
+ """
2
+ Application Factory for MCP Proxy Adapter
3
+
4
+ This module provides a factory function for creating and running MCP Proxy Adapter servers
5
+ with proper configuration validation and initialization.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import logging
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Optional, Dict, Any
15
+
16
+ from fastapi import FastAPI
17
+ from mcp_proxy_adapter.api.app import create_app
18
+ from mcp_proxy_adapter.core.logging import setup_logging, get_logger
19
+ from mcp_proxy_adapter.config import config
20
+ from mcp_proxy_adapter.commands.builtin_commands import register_builtin_commands
21
+
22
+ logger = get_logger("app_factory")
23
+
24
+
25
+ def create_and_run_server(
26
+ config_path: Optional[str] = None,
27
+ log_config_path: Optional[str] = None,
28
+ title: str = "MCP Proxy Adapter Server",
29
+ description: str = "Model Context Protocol Proxy Adapter with Security Framework",
30
+ version: str = "1.0.0",
31
+ host: str = "0.0.0.0",
32
+ log_level: str = "info",
33
+ engine: Optional[str] = None
34
+ ) -> None:
35
+ """
36
+ Create and run MCP Proxy Adapter server with proper validation.
37
+
38
+ This factory function validates all configuration files, sets up logging,
39
+ initializes the application, and starts the server with optimal settings.
40
+
41
+ Args:
42
+ config_path: Path to configuration file (JSON)
43
+ log_config_path: Path to logging configuration file (optional)
44
+ title: Application title for OpenAPI schema
45
+ description: Application description for OpenAPI schema
46
+ version: Application version
47
+ host: Server host address
48
+ port: Server port
49
+ log_level: Logging level
50
+ engine: Specific server engine to use (optional)
51
+
52
+ Raises:
53
+ SystemExit: If configuration validation fails or server cannot start
54
+ """
55
+ print("šŸš€ MCP Proxy Adapter Server Factory")
56
+ print("=" * 60)
57
+ print(f"šŸ“‹ Title: {title}")
58
+ print(f"šŸ“ Description: {description}")
59
+ print(f"šŸ”¢ Version: {version}")
60
+ print(f"🌐 Host: {host}")
61
+ print(f"šŸ“Š Log Level: {log_level}")
62
+ print("=" * 60)
63
+ print()
64
+
65
+ # 1. Validate and load configuration file
66
+ app_config = None
67
+ if config_path:
68
+ config_file = Path(config_path)
69
+ if not config_file.exists():
70
+ print(f"āŒ Configuration file not found: {config_path}")
71
+ print(" Please provide a valid path to config.json")
72
+ sys.exit(1)
73
+
74
+ try:
75
+ config.load_from_file(str(config_file))
76
+ app_config = config.get_all()
77
+ print(f"āœ… Configuration loaded from: {config_path}")
78
+
79
+ # Debug: Check what config.get_all() actually returns
80
+ print(f"šŸ” Debug: config.get_all() keys: {list(app_config.keys())}")
81
+ if "security" in app_config:
82
+ security_ssl = app_config["security"].get("ssl", {})
83
+ print(f"šŸ” Debug: config.get_all() security.ssl: {security_ssl}")
84
+
85
+ # Debug: Check if root ssl section exists after loading
86
+ if "ssl" in app_config:
87
+ print(f"šŸ” Debug: Root SSL section after loading: enabled={app_config['ssl'].get('enabled', False)}")
88
+ print(f"šŸ” Debug: Root SSL section after loading: cert_file={app_config['ssl'].get('cert_file')}")
89
+ print(f"šŸ” Debug: Root SSL section after loading: key_file={app_config['ssl'].get('key_file')}")
90
+ else:
91
+ print(f"šŸ” Debug: No root SSL section after loading")
92
+
93
+ # Debug: Check app_config immediately after get_all()
94
+ if app_config and "security" in app_config:
95
+ ssl_config = app_config["security"].get("ssl", {})
96
+ print(f"šŸ” Debug: app_config after get_all(): SSL enabled={ssl_config.get('enabled', False)}")
97
+ print(f"šŸ” Debug: app_config after get_all(): SSL cert_file={ssl_config.get('cert_file')}")
98
+ print(f"šŸ” Debug: app_config after get_all(): SSL key_file={ssl_config.get('key_file')}")
99
+
100
+ # Debug: Check SSL configuration after loading
101
+ if app_config and "security" in app_config:
102
+ ssl_config = app_config["security"].get("ssl", {})
103
+ print(f"šŸ” Debug: SSL config after loading: enabled={ssl_config.get('enabled', False)}")
104
+ print(f"šŸ” Debug: SSL config after loading: cert_file={ssl_config.get('cert_file')}")
105
+ print(f"šŸ” Debug: SSL config after loading: key_file={ssl_config.get('key_file')}")
106
+
107
+ # Debug: Check if SSL config is correct
108
+ if ssl_config.get('enabled', False):
109
+ print(f"šŸ” Debug: SSL config is enabled and correct")
110
+ else:
111
+ print(f"šŸ” Debug: SSL config is disabled or incorrect")
112
+ # Try to get SSL config from root level
113
+ root_ssl = app_config.get("ssl", {})
114
+ print(f"šŸ” Debug: Root SSL config: enabled={root_ssl.get('enabled', False)}")
115
+ print(f"šŸ” Debug: Root SSL config: cert_file={root_ssl.get('cert_file')}")
116
+ print(f"šŸ” Debug: Root SSL config: key_file={root_ssl.get('key_file')}")
117
+
118
+ # Validate security framework configuration only if enabled
119
+ security_config = app_config.get("security", {})
120
+ if security_config.get("enabled", False):
121
+ framework = security_config.get("framework", "mcp_security_framework")
122
+ print(f"šŸ”’ Security framework: {framework}")
123
+
124
+ # Debug: Check SSL config before validation
125
+ ssl_config = security_config.get("ssl", {})
126
+ print(f"šŸ” Debug: SSL config before validation: enabled={ssl_config.get('enabled', False)}")
127
+
128
+ # Validate security configuration
129
+ from mcp_proxy_adapter.core.unified_config_adapter import UnifiedConfigAdapter
130
+ adapter = UnifiedConfigAdapter()
131
+ validation_result = adapter.validate_configuration(app_config)
132
+
133
+ # Debug: Check SSL config after validation
134
+ ssl_config = app_config.get("security", {}).get("ssl", {})
135
+ print(f"šŸ” Debug: SSL config after validation: enabled={ssl_config.get('enabled', False)}")
136
+
137
+ if not validation_result.is_valid:
138
+ print("āŒ Security configuration validation failed:")
139
+ for error in validation_result.errors:
140
+ print(f" - {error}")
141
+ sys.exit(1)
142
+
143
+ if validation_result.warnings:
144
+ print("āš ļø Security configuration warnings:")
145
+ for warning in validation_result.warnings:
146
+ print(f" - {warning}")
147
+
148
+ print("āœ… Security configuration validated successfully")
149
+ else:
150
+ print("šŸ”“ Security framework disabled")
151
+
152
+ except Exception as e:
153
+ print(f"āŒ Failed to load configuration from {config_path}: {e}")
154
+ sys.exit(1)
155
+ else:
156
+ print("āš ļø No configuration file provided, using defaults")
157
+ app_config = config.get_all()
158
+
159
+ # 2. Setup logging
160
+ try:
161
+ if log_config_path:
162
+ log_config_file = Path(log_config_path)
163
+ if not log_config_file.exists():
164
+ print(f"āŒ Log configuration file not found: {log_config_path}")
165
+ sys.exit(1)
166
+ setup_logging(log_config_path=str(log_config_file))
167
+ print(f"āœ… Logging configured from: {log_config_path}")
168
+ else:
169
+ setup_logging()
170
+ print("āœ… Logging configured with defaults")
171
+ except Exception as e:
172
+ print(f"āŒ Failed to setup logging: {e}")
173
+ sys.exit(1)
174
+
175
+ # 3. Register built-in commands
176
+ try:
177
+ builtin_count = register_builtin_commands()
178
+ print(f"āœ… Registered {builtin_count} built-in commands")
179
+ except Exception as e:
180
+ print(f"āŒ Failed to register built-in commands: {e}")
181
+ sys.exit(1)
182
+
183
+ # 4. Create FastAPI application with configuration
184
+ try:
185
+ # Debug: Check app_config before passing to create_app
186
+ if app_config and "security" in app_config:
187
+ ssl_config = app_config["security"].get("ssl", {})
188
+ print(f"šŸ” Debug: app_config before create_app: SSL enabled={ssl_config.get('enabled', False)}")
189
+ print(f"šŸ” Debug: app_config before create_app: SSL cert_file={ssl_config.get('cert_file')}")
190
+ print(f"šŸ” Debug: app_config before create_app: SSL key_file={ssl_config.get('key_file')}")
191
+
192
+ app = create_app(
193
+ title=title,
194
+ description=description,
195
+ version=version,
196
+ app_config=app_config, # Pass configuration to create_app
197
+ config_path=config_path # Pass config path to preserve SSL settings
198
+ )
199
+ print("āœ… FastAPI application created successfully")
200
+ except Exception as e:
201
+ print(f"āŒ Failed to create FastAPI application: {e}")
202
+ sys.exit(1)
203
+
204
+ # 5. Create server configuration
205
+ # Get port from config if available, otherwise use default
206
+ server_port = app_config.get("server", {}).get("port", 8000) if app_config else 8000
207
+ print(f"šŸ”Œ Port: {server_port}")
208
+
209
+ server_config = {
210
+ "host": host,
211
+ "port": server_port,
212
+ "log_level": log_level,
213
+ "reload": False
214
+ }
215
+
216
+ # Add SSL configuration if present
217
+ print(f"šŸ” Debug: app_config keys: {list(app_config.keys()) if app_config else 'None'}")
218
+
219
+ # Check for SSL config in root section first (higher priority)
220
+ if app_config and "ssl" in app_config:
221
+ print(f"šŸ” Debug: SSL config found in root: {app_config['ssl']}")
222
+ print(f"šŸ” Debug: SSL enabled: {app_config['ssl'].get('enabled', False)}")
223
+ if app_config["ssl"].get("enabled", False):
224
+ ssl_config = app_config["ssl"]
225
+ # Add SSL config directly to server_config for Hypercorn
226
+ server_config["certfile"] = ssl_config.get("cert_file")
227
+ server_config["keyfile"] = ssl_config.get("key_file")
228
+ server_config["ca_certs"] = ssl_config.get("ca_cert_file")
229
+ server_config["verify_mode"] = ssl_config.get("verify_mode")
230
+ print(f"šŸ”’ SSL enabled: {ssl_config.get('cert_file', 'N/A')}")
231
+ print(f"šŸ”’ SSL enabled: cert={ssl_config.get('cert_file')}, key={ssl_config.get('key_file')}")
232
+ print(f"šŸ”’ Server config SSL: certfile={server_config.get('certfile')}, keyfile={server_config.get('keyfile')}, ca_certs={server_config.get('ca_certs')}, verify_mode={server_config.get('verify_mode')}")
233
+
234
+ # Check for SSL config in security section (fallback)
235
+ if app_config and "security" in app_config:
236
+ security_config = app_config["security"]
237
+ print(f"šŸ” Debug: security_config keys: {list(security_config.keys())}")
238
+ if "ssl" in security_config:
239
+ print(f"šŸ” Debug: SSL config found in security: {security_config['ssl']}")
240
+ print(f"šŸ” Debug: SSL enabled: {security_config['ssl'].get('enabled', False)}")
241
+ if security_config["ssl"].get("enabled", False):
242
+ ssl_config = security_config["ssl"]
243
+ # Add SSL config directly to server_config for Hypercorn
244
+ server_config["certfile"] = ssl_config.get("cert_file")
245
+ server_config["keyfile"] = ssl_config.get("key_file")
246
+ server_config["ca_certs"] = ssl_config.get("ca_cert_file")
247
+ server_config["verify_mode"] = ssl_config.get("verify_mode")
248
+ print(f"šŸ”’ SSL enabled: {ssl_config.get('cert_file', 'N/A')}")
249
+ print(f"šŸ”’ SSL enabled: cert={ssl_config.get('cert_file')}, key={ssl_config.get('key_file')}")
250
+ print(f"šŸ”’ Server config SSL: certfile={server_config.get('certfile')}, keyfile={server_config.get('keyfile')}, ca_certs={server_config.get('ca_certs')}, verify_mode={server_config.get('verify_mode')}")
251
+ print(f"šŸ” Debug: SSL config found in root: {app_config['ssl']}")
252
+ print(f"šŸ” Debug: SSL enabled: {app_config['ssl'].get('enabled', False)}")
253
+ if app_config["ssl"].get("enabled", False):
254
+ ssl_config = app_config["ssl"]
255
+ # Add SSL config directly to server_config for Hypercorn
256
+ server_config["certfile"] = ssl_config.get("cert_file")
257
+ server_config["keyfile"] = ssl_config.get("key_file")
258
+ server_config["ca_certs"] = ssl_config.get("ca_cert_file")
259
+ server_config["verify_mode"] = ssl_config.get("verify_mode")
260
+ print(f"šŸ”’ SSL enabled: {ssl_config.get('cert_file', 'N/A')}")
261
+ print(f"šŸ”’ SSL enabled: cert={ssl_config.get('cert_file')}, key={ssl_config.get('key_file')}")
262
+ print(f"šŸ”’ Server config SSL: certfile={server_config.get('certfile')}, keyfile={server_config.get('keyfile')}, ca_certs={server_config.get('ca_certs')}, verify_mode={server_config.get('verify_mode')}")
263
+
264
+ # 6. Start server
265
+ try:
266
+ print("šŸš€ Starting server...")
267
+ print(" Use Ctrl+C to stop the server")
268
+ print("=" * 60)
269
+
270
+ # Use hypercorn directly
271
+ import hypercorn.asyncio
272
+ import hypercorn.config
273
+ import asyncio
274
+
275
+ # Configure hypercorn
276
+ config_hypercorn = hypercorn.config.Config()
277
+ config_hypercorn.bind = [f"{server_config['host']}:{server_config['port']}"]
278
+ config_hypercorn.loglevel = server_config.get("log_level", "info")
279
+
280
+ # Add SSL configuration if present
281
+ if "certfile" in server_config:
282
+ config_hypercorn.certfile = server_config["certfile"]
283
+ if "keyfile" in server_config:
284
+ config_hypercorn.keyfile = server_config["keyfile"]
285
+ if "ca_certs" in server_config:
286
+ config_hypercorn.ca_certs = server_config["ca_certs"]
287
+ if "verify_mode" in server_config:
288
+ import ssl
289
+ config_hypercorn.verify_mode = ssl.CERT_REQUIRED
290
+
291
+ # Determine if SSL is enabled
292
+ ssl_enabled = any(key in server_config for key in ["certfile", "keyfile"])
293
+
294
+ if ssl_enabled:
295
+ print(f"šŸ” Starting HTTPS server with hypercorn...")
296
+ else:
297
+ print(f"🌐 Starting HTTP server with hypercorn...")
298
+
299
+ # Run the server
300
+ asyncio.run(hypercorn.asyncio.serve(app, config_hypercorn))
301
+
302
+ except KeyboardInterrupt:
303
+ print("\nšŸ›‘ Server stopped by user")
304
+ except Exception as e:
305
+ print(f"\nāŒ Failed to start server: {e}")
306
+ import traceback
307
+ traceback.print_exc()
308
+ sys.exit(1)
309
+
310
+
311
+ def validate_config_file(config_path: str) -> bool:
312
+ """
313
+ Validate configuration file exists and is readable.
314
+
315
+ Args:
316
+ config_path: Path to configuration file
317
+
318
+ Returns:
319
+ True if valid, False otherwise
320
+ """
321
+ try:
322
+ config_file = Path(config_path)
323
+ if not config_file.exists():
324
+ print(f"āŒ Configuration file not found: {config_path}")
325
+ return False
326
+
327
+ # Try to load configuration to validate JSON format
328
+ config.load_from_file(str(config_file))
329
+ return True
330
+
331
+ except Exception as e:
332
+ print(f"āŒ Configuration file validation failed: {e}")
333
+ return False
334
+
335
+
336
+ def validate_log_config_file(log_config_path: str) -> bool:
337
+ """
338
+ Validate logging configuration file exists and is readable.
339
+
340
+ Args:
341
+ log_config_path: Path to logging configuration file
342
+
343
+ Returns:
344
+ True if valid, False otherwise
345
+ """
346
+ try:
347
+ log_config_file = Path(log_config_path)
348
+ if not log_config_file.exists():
349
+ print(f"āŒ Log configuration file not found: {log_config_path}")
350
+ return False
351
+ return True
352
+
353
+ except Exception as e:
354
+ print(f"āŒ Log configuration file validation failed: {e}")
355
+ return False
356
+
357
+
358
+ def create_application(
359
+ config: Dict[str, Any],
360
+ title: str = "MCP Proxy Adapter",
361
+ description: str = "JSON-RPC API for interacting with MCP Proxy",
362
+ version: str = "1.0.0"
363
+ ) -> FastAPI:
364
+ """
365
+ Creates and configures FastAPI application.
366
+
367
+ Args:
368
+ config: Application configuration dictionary
369
+ title: Application title
370
+ description: Application description
371
+ version: Application version
372
+
373
+ Returns:
374
+ Configured FastAPI application
375
+ """
376
+ from fastapi.middleware.cors import CORSMiddleware
377
+ from mcp_proxy_adapter.api.app import create_app
378
+ from mcp_proxy_adapter.core.logging import setup_logging
379
+ from mcp_proxy_adapter.commands.builtin_commands import register_builtin_commands
380
+
381
+ # Setup logging
382
+ setup_logging()
383
+
384
+ # Register built-in commands
385
+ register_builtin_commands()
386
+
387
+ # Create FastAPI application using existing create_app function
388
+ app = create_app(
389
+ title=title,
390
+ description=description,
391
+ version=version,
392
+ app_config=config
393
+ )
394
+
395
+ # Add CORS middleware
396
+ app.add_middleware(
397
+ CORSMiddleware,
398
+ allow_origins=["*"],
399
+ allow_credentials=True,
400
+ allow_methods=["*"],
401
+ allow_headers=["*"],
402
+ )
403
+
404
+ # Add health endpoint
405
+ @app.get("/health")
406
+ async def health_check():
407
+ """Health check endpoint."""
408
+ return {"status": "healthy", "version": version}
409
+
410
+ return app
@@ -0,0 +1,272 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Application Runner for MCP Proxy Adapter
6
+
7
+ This module provides the ApplicationRunner class for running applications
8
+ with full configuration validation and error handling.
9
+ """
10
+
11
+ import socket
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Dict, Any, List, Optional
15
+
16
+ from fastapi import FastAPI
17
+
18
+ from mcp_proxy_adapter.core.logging import get_logger
19
+
20
+ logger = get_logger("app_runner")
21
+
22
+
23
+ class ApplicationRunner:
24
+ """
25
+ Class for running applications with configuration validation.
26
+ """
27
+
28
+ def __init__(self, app: FastAPI, config: Dict[str, Any]):
29
+ """
30
+ Initialize ApplicationRunner.
31
+
32
+ Args:
33
+ app: FastAPI application instance
34
+ config: Application configuration dictionary
35
+ """
36
+ self.app = app
37
+ self.config = config
38
+ self.errors: List[str] = []
39
+
40
+ def validate_configuration(self) -> List[str]:
41
+ """
42
+ Validates configuration and returns list of errors.
43
+
44
+ Returns:
45
+ List of validation error messages
46
+ """
47
+ self.errors = []
48
+
49
+ # Validate server configuration
50
+ self._validate_server_config()
51
+
52
+ # Validate SSL configuration
53
+ self._validate_ssl_config()
54
+
55
+ # Validate security configuration
56
+ self._validate_security_config()
57
+
58
+ # Validate file paths
59
+ self._validate_file_paths()
60
+
61
+ # Validate port availability
62
+ self._validate_port_availability()
63
+
64
+ # Validate configuration compatibility
65
+ self._validate_compatibility()
66
+
67
+ return self.errors
68
+
69
+ def _validate_server_config(self) -> None:
70
+ """Validate server configuration."""
71
+ server_config = self.config.get("server", {})
72
+
73
+ if not server_config:
74
+ self.errors.append("Server configuration is missing")
75
+ return
76
+
77
+ host = server_config.get("host")
78
+ port = server_config.get("port")
79
+
80
+ if not host:
81
+ self.errors.append("Server host is not specified")
82
+
83
+ if not port:
84
+ self.errors.append("Server port is not specified")
85
+ elif not isinstance(port, int) or port < 1 or port > 65535:
86
+ self.errors.append(f"Invalid server port: {port}")
87
+
88
+ def _validate_ssl_config(self) -> None:
89
+ """Validate SSL configuration."""
90
+ ssl_config = self.config.get("ssl", {})
91
+
92
+ if ssl_config.get("enabled", False):
93
+ cert_file = ssl_config.get("cert_file")
94
+ key_file = ssl_config.get("key_file")
95
+
96
+ if not cert_file:
97
+ self.errors.append("SSL enabled but certificate file not specified")
98
+ elif not Path(cert_file).exists():
99
+ self.errors.append(f"Certificate file not found: {cert_file}")
100
+
101
+ if not key_file:
102
+ self.errors.append("SSL enabled but private key file not specified")
103
+ elif not Path(key_file).exists():
104
+ self.errors.append(f"Private key file not found: {key_file}")
105
+
106
+ # Validate mTLS configuration
107
+ if ssl_config.get("verify_client", False):
108
+ ca_cert = ssl_config.get("ca_cert")
109
+ if not ca_cert:
110
+ self.errors.append("Client verification enabled but CA certificate not specified")
111
+ elif not Path(ca_cert).exists():
112
+ self.errors.append(f"CA certificate file not found: {ca_cert}")
113
+
114
+ def _validate_security_config(self) -> None:
115
+ """Validate security configuration."""
116
+ security_config = self.config.get("security", {})
117
+
118
+ if security_config.get("enabled", False):
119
+ auth_config = security_config.get("auth", {})
120
+ permissions_config = security_config.get("permissions", {})
121
+
122
+ # Validate authentication configuration
123
+ if auth_config.get("enabled", False):
124
+ methods = auth_config.get("methods", [])
125
+ if not methods:
126
+ self.errors.append("Authentication enabled but no methods specified")
127
+
128
+ # Validate API key configuration
129
+ if "api_key" in methods:
130
+ # Check if roles file exists for API key auth
131
+ if permissions_config.get("enabled", False):
132
+ roles_file = permissions_config.get("roles_file")
133
+ if not roles_file:
134
+ self.errors.append("Permissions enabled but roles file not specified")
135
+ elif not Path(roles_file).exists():
136
+ self.errors.append(f"Roles file not found: {roles_file}")
137
+
138
+ # Validate certificate configuration
139
+ if "certificate" in methods:
140
+ ssl_config = self.config.get("ssl", {})
141
+ if not ssl_config.get("enabled", False):
142
+ self.errors.append("Certificate authentication requires SSL to be enabled")
143
+ if not ssl_config.get("verify_client", False):
144
+ self.errors.append("Certificate authentication requires client verification to be enabled")
145
+
146
+ def _validate_file_paths(self) -> None:
147
+ """Validate all file paths in configuration."""
148
+ # Check SSL certificate files
149
+ ssl_config = self.config.get("ssl", {})
150
+ if ssl_config.get("enabled", False):
151
+ cert_file = ssl_config.get("cert_file")
152
+ key_file = ssl_config.get("key_file")
153
+ ca_cert = ssl_config.get("ca_cert")
154
+
155
+ if cert_file and not Path(cert_file).is_file():
156
+ self.errors.append(f"Certificate file is not a regular file: {cert_file}")
157
+
158
+ if key_file and not Path(key_file).is_file():
159
+ self.errors.append(f"Private key file is not a regular file: {key_file}")
160
+
161
+ if ca_cert and not Path(ca_cert).is_file():
162
+ self.errors.append(f"CA certificate file is not a regular file: {ca_cert}")
163
+
164
+ # Check roles file
165
+ security_config = self.config.get("security", {})
166
+ permissions_config = security_config.get("permissions", {})
167
+ if permissions_config.get("enabled", False):
168
+ roles_file = permissions_config.get("roles_file")
169
+ if roles_file and not Path(roles_file).is_file():
170
+ self.errors.append(f"Roles file is not a regular file: {roles_file}")
171
+
172
+ def _validate_port_availability(self) -> None:
173
+ """Validate that the configured port is available."""
174
+ server_config = self.config.get("server", {})
175
+ port = server_config.get("port")
176
+
177
+ if port:
178
+ try:
179
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
180
+ s.bind(("127.0.0.1", port))
181
+ except OSError:
182
+ self.errors.append(f"Port {port} is already in use")
183
+
184
+ def _validate_compatibility(self) -> None:
185
+ """Validate configuration compatibility."""
186
+ ssl_config = self.config.get("ssl", {})
187
+ security_config = self.config.get("security", {})
188
+ protocols_config = self.config.get("protocols", {})
189
+
190
+ # Check SSL and protocol compatibility
191
+ if ssl_config.get("enabled", False):
192
+ allowed_protocols = protocols_config.get("allowed_protocols", [])
193
+ if "http" in allowed_protocols and "https" not in allowed_protocols:
194
+ self.errors.append("SSL enabled but HTTPS not in allowed protocols")
195
+
196
+ # Check security and SSL compatibility
197
+ if security_config.get("enabled", False):
198
+ auth_config = security_config.get("auth", {})
199
+ if auth_config.get("enabled", False):
200
+ methods = auth_config.get("methods", [])
201
+ if "certificate" in methods and not ssl_config.get("enabled", False):
202
+ self.errors.append("Certificate authentication requires SSL to be enabled")
203
+
204
+ def setup_hooks(self) -> None:
205
+ """
206
+ Setup application hooks.
207
+ """
208
+ # Add startup event
209
+ @self.app.on_event("startup")
210
+ async def startup_event():
211
+ logger.info("Application starting up")
212
+ logger.info(f"Configuration validation passed with {len(self.errors)} errors")
213
+
214
+ # Add shutdown event
215
+ @self.app.on_event("shutdown")
216
+ async def shutdown_event():
217
+ logger.info("Application shutting down")
218
+
219
+ def run(self) -> None:
220
+ """
221
+ Run application with full validation.
222
+ """
223
+ # Validate configuration
224
+ errors = self.validate_configuration()
225
+
226
+ if errors:
227
+ print("ERROR: Configuration validation failed:", file=sys.stderr)
228
+ for error in errors:
229
+ print(f" - {error}", file=sys.stderr)
230
+ sys.exit(1)
231
+
232
+ # Setup hooks
233
+ self.setup_hooks()
234
+
235
+ # Get server configuration
236
+ server_config = self.config.get("server", {})
237
+ host = server_config.get("host", "127.0.0.1")
238
+ port = server_config.get("port", 8000)
239
+
240
+ # Prepare server configuration for hypercorn
241
+ server_kwargs = {
242
+ "host": host,
243
+ "port": port,
244
+ "log_level": "info"
245
+ }
246
+
247
+ # Add SSL configuration if enabled
248
+ ssl_config = self.config.get("ssl", {})
249
+ if ssl_config.get("enabled", False):
250
+ server_kwargs["certfile"] = ssl_config.get("cert_file")
251
+ server_kwargs["keyfile"] = ssl_config.get("key_file")
252
+
253
+ # Add mTLS configuration if enabled
254
+ if ssl_config.get("verify_client", False):
255
+ server_kwargs["ca_certs"] = ssl_config.get("ca_cert")
256
+
257
+ try:
258
+ import hypercorn.asyncio
259
+ import asyncio
260
+
261
+ print(f"šŸš€ Starting server on {host}:{port}")
262
+ print(" Use Ctrl+C to stop the server")
263
+ print("=" * 60)
264
+
265
+ # Run with hypercorn
266
+ asyncio.run(hypercorn.asyncio.serve(self.app, **server_kwargs))
267
+
268
+ except KeyboardInterrupt:
269
+ print("\nšŸ›‘ Server stopped by user")
270
+ except Exception as e:
271
+ print(f"\nāŒ Failed to start server: {e}", file=sys.stderr)
272
+ sys.exit(1)