mcp-proxy-adapter 6.1.1__py3-none-any.whl → 6.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +18 -7
  3. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  4. mcp_proxy_adapter/core/app_factory.py +87 -3
  5. mcp_proxy_adapter/core/app_runner.py +272 -0
  6. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  7. mcp_proxy_adapter/core/client.py +574 -0
  8. mcp_proxy_adapter/core/client_manager.py +284 -0
  9. mcp_proxy_adapter/core/server_adapter.py +17 -80
  10. mcp_proxy_adapter/core/server_engine.py +5 -99
  11. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  12. mcp_proxy_adapter/core/transport_manager.py +5 -5
  13. mcp_proxy_adapter/examples/__init__.py +16 -0
  14. mcp_proxy_adapter/examples/basic_framework/__init__.py +7 -0
  15. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  16. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  17. mcp_proxy_adapter/examples/basic_framework/main.py +21 -40
  18. mcp_proxy_adapter/examples/commands/__init__.py +5 -1
  19. mcp_proxy_adapter/examples/create_certificates_simple.py +260 -75
  20. mcp_proxy_adapter/examples/debug_request_state.py +4 -36
  21. mcp_proxy_adapter/examples/debug_role_chain.py +2 -49
  22. mcp_proxy_adapter/examples/demo_client.py +0 -66
  23. mcp_proxy_adapter/examples/full_application/__init__.py +11 -0
  24. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  25. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -19
  26. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -16
  27. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  28. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -22
  29. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -24
  30. mcp_proxy_adapter/examples/full_application/main.py +65 -44
  31. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  32. mcp_proxy_adapter/examples/generate_all_certificates.py +0 -67
  33. mcp_proxy_adapter/examples/generate_certificates.py +0 -15
  34. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  35. mcp_proxy_adapter/examples/generate_test_configs.py +204 -0
  36. mcp_proxy_adapter/examples/proxy_registration_example.py +3 -70
  37. mcp_proxy_adapter/examples/run_example.py +1 -23
  38. mcp_proxy_adapter/examples/run_security_tests.py +2 -60
  39. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -53
  40. mcp_proxy_adapter/examples/security_test_client.py +18 -123
  41. mcp_proxy_adapter/examples/setup_test_environment.py +179 -0
  42. mcp_proxy_adapter/examples/test_config.py +148 -0
  43. mcp_proxy_adapter/examples/test_config_generator.py +1 -25
  44. mcp_proxy_adapter/examples/test_examples.py +4 -67
  45. mcp_proxy_adapter/examples/universal_client.py +154 -162
  46. mcp_proxy_adapter/main.py +51 -161
  47. mcp_proxy_adapter/version.py +1 -1
  48. mcp_proxy_adapter-6.2.0.dist-info/METADATA +687 -0
  49. mcp_proxy_adapter-6.2.0.dist-info/RECORD +122 -0
  50. mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +0 -285
  51. mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +0 -285
  52. mcp_proxy_adapter/examples/README.md +0 -257
  53. mcp_proxy_adapter/examples/README_EN.md +0 -258
  54. mcp_proxy_adapter/examples/SECURITY_TESTING.md +0 -455
  55. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +0 -37
  56. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +0 -23
  57. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +0 -43
  58. mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +0 -36
  59. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +0 -29
  60. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +0 -34
  61. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +0 -39
  62. mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +0 -35
  63. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +0 -45
  64. mcp_proxy_adapter/examples/basic_framework/roles.json +0 -21
  65. mcp_proxy_adapter/examples/cert_config.json +0 -9
  66. mcp_proxy_adapter/examples/certs/admin.crt +0 -32
  67. mcp_proxy_adapter/examples/certs/admin.key +0 -52
  68. mcp_proxy_adapter/examples/certs/admin_cert.pem +0 -21
  69. mcp_proxy_adapter/examples/certs/admin_key.pem +0 -28
  70. mcp_proxy_adapter/examples/certs/ca_cert.pem +0 -23
  71. mcp_proxy_adapter/examples/certs/ca_cert.srl +0 -1
  72. mcp_proxy_adapter/examples/certs/ca_key.pem +0 -28
  73. mcp_proxy_adapter/examples/certs/cert_config.json +0 -9
  74. mcp_proxy_adapter/examples/certs/client.crt +0 -32
  75. mcp_proxy_adapter/examples/certs/client.key +0 -52
  76. mcp_proxy_adapter/examples/certs/client_admin.crt +0 -32
  77. mcp_proxy_adapter/examples/certs/client_admin.key +0 -52
  78. mcp_proxy_adapter/examples/certs/client_user.crt +0 -32
  79. mcp_proxy_adapter/examples/certs/client_user.key +0 -52
  80. mcp_proxy_adapter/examples/certs/guest_cert.pem +0 -21
  81. mcp_proxy_adapter/examples/certs/guest_key.pem +0 -28
  82. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +0 -23
  83. mcp_proxy_adapter/examples/certs/proxy_cert.pem +0 -21
  84. mcp_proxy_adapter/examples/certs/proxy_key.pem +0 -28
  85. mcp_proxy_adapter/examples/certs/readonly.crt +0 -32
  86. mcp_proxy_adapter/examples/certs/readonly.key +0 -52
  87. mcp_proxy_adapter/examples/certs/readonly_cert.pem +0 -21
  88. mcp_proxy_adapter/examples/certs/readonly_key.pem +0 -28
  89. mcp_proxy_adapter/examples/certs/server.crt +0 -32
  90. mcp_proxy_adapter/examples/certs/server.key +0 -52
  91. mcp_proxy_adapter/examples/certs/server_cert.pem +0 -32
  92. mcp_proxy_adapter/examples/certs/server_key.pem +0 -52
  93. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +0 -20
  94. mcp_proxy_adapter/examples/certs/user.crt +0 -32
  95. mcp_proxy_adapter/examples/certs/user.key +0 -52
  96. mcp_proxy_adapter/examples/certs/user_cert.pem +0 -21
  97. mcp_proxy_adapter/examples/certs/user_key.pem +0 -28
  98. mcp_proxy_adapter/examples/client_configs/api_key_client.json +0 -13
  99. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +0 -13
  100. mcp_proxy_adapter/examples/client_configs/certificate_client.json +0 -22
  101. mcp_proxy_adapter/examples/client_configs/jwt_client.json +0 -15
  102. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +0 -9
  103. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +0 -37
  104. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +0 -23
  105. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +0 -39
  106. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +0 -25
  107. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +0 -39
  108. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +0 -45
  109. mcp_proxy_adapter/examples/full_application/roles.json +0 -21
  110. mcp_proxy_adapter/examples/keys/ca_key.pem +0 -28
  111. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +0 -28
  112. mcp_proxy_adapter/examples/keys/test_ca_ca.key +0 -28
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +0 -220
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +0 -1
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +0 -1
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +0 -1
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +0 -1
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +0 -1
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +0 -220
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +0 -1
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +0 -1
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +0 -1
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +0 -1
  124. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +0 -1
  125. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +0 -2
  126. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +0 -1
  127. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +0 -1
  128. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +0 -1
  129. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +0 -1
  130. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +0 -1
  131. mcp_proxy_adapter/examples/roles.json +0 -38
  132. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +0 -204
  133. mcp_proxy_adapter/examples/server_configs/config_http_token.json +0 -238
  134. mcp_proxy_adapter/examples/server_configs/config_https.json +0 -215
  135. mcp_proxy_adapter/examples/server_configs/config_https_token.json +0 -231
  136. mcp_proxy_adapter/examples/server_configs/config_mtls.json +0 -215
  137. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +0 -250
  138. mcp_proxy_adapter/examples/server_configs/config_simple.json +0 -46
  139. mcp_proxy_adapter/examples/server_configs/roles.json +0 -38
  140. mcp_proxy_adapter-6.1.1.dist-info/METADATA +0 -205
  141. mcp_proxy_adapter-6.1.1.dist-info/RECORD +0 -197
  142. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/WHEEL +0 -0
  143. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/entry_points.txt +0 -0
  144. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/licenses/LICENSE +0 -0
  145. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/top_level.txt +0 -0
@@ -13,9 +13,9 @@ import sys
13
13
  from pathlib import Path
14
14
  from typing import Optional, Dict, Any
15
15
 
16
+ from fastapi import FastAPI
16
17
  from mcp_proxy_adapter.api.app import create_app
17
18
  from mcp_proxy_adapter.core.logging import setup_logging, get_logger
18
- from mcp_proxy_adapter.core.server_adapter import UnifiedServerRunner
19
19
  from mcp_proxy_adapter.config import config
20
20
  from mcp_proxy_adapter.commands.builtin_commands import register_builtin_commands
21
21
 
@@ -267,8 +267,37 @@ def create_and_run_server(
267
267
  print(" Use Ctrl+C to stop the server")
268
268
  print("=" * 60)
269
269
 
270
- server_runner = UnifiedServerRunner()
271
- server_runner.run_server(app, server_config, "hypercorn")
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))
272
301
 
273
302
  except KeyboardInterrupt:
274
303
  print("\nšŸ›‘ Server stopped by user")
@@ -324,3 +353,58 @@ def validate_log_config_file(log_config_path: str) -> bool:
324
353
  except Exception as e:
325
354
  print(f"āŒ Log configuration file validation failed: {e}")
326
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)