mcp-proxy-adapter 6.1.1__py3-none-any.whl → 6.2.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 (146) 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.1.dist-info/METADATA +676 -0
  49. mcp_proxy_adapter-6.2.1.dist-info/RECORD +119 -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/utils/config_generator.py +0 -727
  141. mcp_proxy_adapter-6.1.1.dist-info/METADATA +0 -205
  142. mcp_proxy_adapter-6.1.1.dist-info/RECORD +0 -197
  143. mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +0 -2
  144. mcp_proxy_adapter-6.1.1.dist-info/licenses/LICENSE +0 -21
  145. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.1.dist-info}/WHEEL +0 -0
  146. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,284 @@
1
+ """
2
+ Client Manager for MCP Proxy Adapter Framework
3
+
4
+ This module provides client management functionality for the MCP Proxy Adapter framework.
5
+ It handles client creation, connection management, and proxy registration.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import asyncio
12
+ import json
13
+ import logging
14
+ from typing import Dict, Any, Optional, List
15
+ from pathlib import Path
16
+
17
+ from .client import UniversalClient, create_client_from_config
18
+
19
+
20
+ class ClientManager:
21
+ """
22
+ Manages client connections and proxy registrations.
23
+
24
+ This class provides functionality for:
25
+ - Creating and managing client connections
26
+ - Proxy registration
27
+ - Connection pooling
28
+ - Authentication management
29
+ """
30
+
31
+ def __init__(self, config: Dict[str, Any]):
32
+ """
33
+ Initialize client manager.
34
+
35
+ Args:
36
+ config: Client manager configuration
37
+ """
38
+ self.config = config
39
+ self.logger = logging.getLogger(__name__)
40
+ self.clients: Dict[str, UniversalClient] = {}
41
+ self.connection_pool: Dict[str, UniversalClient] = {}
42
+
43
+ # Client manager settings
44
+ self.max_connections = config.get("max_connections", 10)
45
+ self.connection_timeout = config.get("connection_timeout", 30)
46
+ self.retry_attempts = config.get("retry_attempts", 3)
47
+ self.retry_delay = config.get("retry_delay", 1)
48
+
49
+ self.logger.info("Client manager initialized")
50
+
51
+ async def create_client(self, client_id: str, config_file: str) -> UniversalClient:
52
+ """
53
+ Create a new client instance.
54
+
55
+ Args:
56
+ client_id: Unique identifier for the client
57
+ config_file: Path to client configuration file
58
+
59
+ Returns:
60
+ UniversalClient instance
61
+ """
62
+ try:
63
+ if client_id in self.clients:
64
+ self.logger.warning(f"Client {client_id} already exists, reusing existing connection")
65
+ return self.clients[client_id]
66
+
67
+ client = create_client_from_config(config_file)
68
+ self.clients[client_id] = client
69
+
70
+ self.logger.info(f"Client {client_id} created successfully")
71
+ return client
72
+
73
+ except Exception as e:
74
+ self.logger.error(f"Failed to create client {client_id}: {e}")
75
+ raise
76
+
77
+ async def get_client(self, client_id: str) -> Optional[UniversalClient]:
78
+ """
79
+ Get an existing client instance.
80
+
81
+ Args:
82
+ client_id: Client identifier
83
+
84
+ Returns:
85
+ UniversalClient instance or None if not found
86
+ """
87
+ return self.clients.get(client_id)
88
+
89
+ async def remove_client(self, client_id: str) -> bool:
90
+ """
91
+ Remove a client instance.
92
+
93
+ Args:
94
+ client_id: Client identifier
95
+
96
+ Returns:
97
+ True if client was removed, False otherwise
98
+ """
99
+ if client_id in self.clients:
100
+ client = self.clients[client_id]
101
+ await client.disconnect()
102
+ del self.clients[client_id]
103
+ self.logger.info(f"Client {client_id} removed")
104
+ return True
105
+ return False
106
+
107
+ async def test_client_connection(self, client_id: str) -> bool:
108
+ """
109
+ Test connection for a specific client.
110
+
111
+ Args:
112
+ client_id: Client identifier
113
+
114
+ Returns:
115
+ True if connection is successful, False otherwise
116
+ """
117
+ client = await self.get_client(client_id)
118
+ if not client:
119
+ self.logger.error(f"Client {client_id} not found")
120
+ return False
121
+
122
+ try:
123
+ return await client.test_connection()
124
+ except Exception as e:
125
+ self.logger.error(f"Connection test failed for client {client_id}: {e}")
126
+ return False
127
+
128
+ async def register_proxy(self, client_id: str, proxy_config: Dict[str, Any]) -> Dict[str, Any]:
129
+ """
130
+ Register with proxy server using a specific client.
131
+
132
+ Args:
133
+ client_id: Client identifier
134
+ proxy_config: Proxy registration configuration
135
+
136
+ Returns:
137
+ Registration result
138
+ """
139
+ client = await self.get_client(client_id)
140
+ if not client:
141
+ return {"error": f"Client {client_id} not found"}
142
+
143
+ try:
144
+ result = await client.register_proxy(proxy_config)
145
+ self.logger.info(f"Proxy registration completed for client {client_id}")
146
+ return result
147
+ except Exception as e:
148
+ self.logger.error(f"Proxy registration failed for client {client_id}: {e}")
149
+ return {"error": str(e)}
150
+
151
+ async def execute_command(self, client_id: str, command: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
152
+ """
153
+ Execute a command using a specific client.
154
+
155
+ Args:
156
+ client_id: Client identifier
157
+ command: Command name
158
+ params: Command parameters
159
+
160
+ Returns:
161
+ Command result
162
+ """
163
+ client = await self.get_client(client_id)
164
+ if not client:
165
+ return {"error": f"Client {client_id} not found"}
166
+
167
+ try:
168
+ result = await client.execute_command(command, params or {})
169
+ self.logger.info(f"Command {command} executed for client {client_id}")
170
+ return result
171
+ except Exception as e:
172
+ self.logger.error(f"Command execution failed for client {client_id}: {e}")
173
+ return {"error": str(e)}
174
+
175
+ async def get_client_status(self, client_id: str) -> Dict[str, Any]:
176
+ """
177
+ Get status information for a specific client.
178
+
179
+ Args:
180
+ client_id: Client identifier
181
+
182
+ Returns:
183
+ Client status information
184
+ """
185
+ client = await self.get_client(client_id)
186
+ if not client:
187
+ return {"error": f"Client {client_id} not found"}
188
+
189
+ try:
190
+ # Test connection
191
+ connection_ok = await client.test_connection()
192
+
193
+ # Test security features
194
+ security_features = await client.test_security_features()
195
+
196
+ status = {
197
+ "client_id": client_id,
198
+ "base_url": client.base_url,
199
+ "auth_method": client.auth_method,
200
+ "connection_ok": connection_ok,
201
+ "security_features": security_features,
202
+ "session_active": client.session is not None
203
+ }
204
+
205
+ return status
206
+ except Exception as e:
207
+ self.logger.error(f"Failed to get status for client {client_id}: {e}")
208
+ return {"error": str(e)}
209
+
210
+ async def list_clients(self) -> List[str]:
211
+ """
212
+ Get list of all client identifiers.
213
+
214
+ Returns:
215
+ List of client identifiers
216
+ """
217
+ return list(self.clients.keys())
218
+
219
+ async def cleanup(self):
220
+ """Clean up all client connections."""
221
+ for client_id in list(self.clients.keys()):
222
+ await self.remove_client(client_id)
223
+ self.logger.info("All client connections cleaned up")
224
+
225
+ async def __aenter__(self):
226
+ """Async context manager entry."""
227
+ return self
228
+
229
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
230
+ """Async context manager exit."""
231
+ await self.cleanup()
232
+
233
+
234
+ def create_client_manager(config: Dict[str, Any]) -> ClientManager:
235
+ """
236
+ Create a ClientManager instance.
237
+
238
+ Args:
239
+ config: Client manager configuration
240
+
241
+ Returns:
242
+ ClientManager instance
243
+ """
244
+ return ClientManager(config)
245
+
246
+
247
+ # Example usage and testing functions
248
+ async def test_client_manager():
249
+ """Test client manager functionality."""
250
+ # Example configuration
251
+ config = {
252
+ "max_connections": 5,
253
+ "connection_timeout": 30,
254
+ "retry_attempts": 3,
255
+ "retry_delay": 1
256
+ }
257
+
258
+ async with ClientManager(config) as manager:
259
+ # Create a client
260
+ client_id = "test_client"
261
+ config_file = "configs/http_simple.json"
262
+
263
+ try:
264
+ client = await manager.create_client(client_id, config_file)
265
+ print(f"✅ Client {client_id} created successfully")
266
+
267
+ # Test connection
268
+ connection_ok = await manager.test_client_connection(client_id)
269
+ print(f"✅ Connection test: {connection_ok}")
270
+
271
+ # Get status
272
+ status = await manager.get_client_status(client_id)
273
+ print(f"✅ Client status: {json.dumps(status, indent=2)}")
274
+
275
+ # Execute command
276
+ result = await manager.execute_command(client_id, "help")
277
+ print(f"✅ Command result: {json.dumps(result, indent=2)}")
278
+
279
+ except Exception as e:
280
+ print(f"❌ Test failed: {e}")
281
+
282
+
283
+ if __name__ == "__main__":
284
+ asyncio.run(test_client_manager())
@@ -35,7 +35,7 @@ class ServerConfigAdapter:
35
35
 
36
36
  Args:
37
37
  ssl_config: Source SSL configuration
38
- target_engine: Target engine name (uvicorn, hypercorn, etc.)
38
+ target_engine: Target engine name (hypercorn)
39
39
 
40
40
  Returns:
41
41
  Converted SSL configuration for the target engine
@@ -45,35 +45,12 @@ class ServerConfigAdapter:
45
45
  logger.error(f"Unknown server engine: {target_engine}")
46
46
  return {}
47
47
 
48
- if target_engine == "uvicorn":
49
- return ServerConfigAdapter._convert_to_uvicorn_ssl(ssl_config)
50
- elif target_engine == "hypercorn":
48
+ if target_engine == "hypercorn":
51
49
  return ServerConfigAdapter._convert_to_hypercorn_ssl(ssl_config)
52
50
  else:
53
51
  logger.warning(f"No SSL conversion available for engine: {target_engine}")
54
52
  return {}
55
53
 
56
- @staticmethod
57
- def _convert_to_uvicorn_ssl(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
58
- """Convert SSL configuration to uvicorn format."""
59
- uvicorn_ssl = {}
60
-
61
- # Map SSL parameters
62
- if ssl_config.get("cert_file"):
63
- uvicorn_ssl["ssl_certfile"] = ssl_config["cert_file"]
64
- if ssl_config.get("key_file"):
65
- uvicorn_ssl["ssl_keyfile"] = ssl_config["key_file"]
66
- if ssl_config.get("ca_cert"):
67
- uvicorn_ssl["ssl_ca_certs"] = ssl_config["ca_cert"]
68
-
69
- # Map verification mode
70
- if ssl_config.get("verify_client", False):
71
- import ssl
72
- uvicorn_ssl["ssl_cert_reqs"] = ssl.CERT_REQUIRED
73
-
74
- logger.debug(f"Converted SSL config to uvicorn: {uvicorn_ssl}")
75
- return uvicorn_ssl
76
-
77
54
  @staticmethod
78
55
  def _convert_to_hypercorn_ssl(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
79
56
  """Convert SSL configuration to hypercorn format."""
@@ -99,49 +76,16 @@ class ServerConfigAdapter:
99
76
  """
100
77
  Determine the optimal server engine for a given configuration.
101
78
 
79
+ Currently only hypercorn is supported.
80
+
102
81
  Args:
103
82
  config: Server configuration
104
83
 
105
84
  Returns:
106
- Name of the optimal engine or None if no suitable engine found
85
+ Optimal engine name (currently always "hypercorn")
107
86
  """
108
- # Check if mTLS is required
109
- ssl_config = config.get("ssl", {})
110
- if not ssl_config:
111
- # Try to get SSL config from security section
112
- ssl_config = config.get("security", {}).get("ssl", {})
113
-
114
- # Prefer hypercorn for all SSL/TLS scenarios due to better mTLS support
115
- if ssl_config.get("enabled", False):
116
- engine = ServerEngineFactory.get_engine("hypercorn")
117
- if engine:
118
- logger.info("Selected hypercorn for SSL/TLS support (better mTLS capabilities)")
119
- return engine.get_name()
120
- else:
121
- logger.warning("SSL enabled but hypercorn not available")
122
-
123
- # For mTLS client verification, hypercorn is required
124
- if ssl_config.get("verify_client", False) or ssl_config.get("client_cert_required", False):
125
- engine = ServerEngineFactory.get_engine_with_feature("mtls_client_certs")
126
- if engine:
127
- logger.info(f"Selected {engine.get_name()} for mTLS support")
128
- return engine.get_name()
129
- else:
130
- logger.warning("mTLS required but no suitable engine available")
131
- return None
132
-
133
- # Default to hypercorn for better async support, fallback to uvicorn
134
- engine = ServerEngineFactory.get_engine("hypercorn")
135
- if engine:
136
- logger.info("Selected hypercorn as default engine (better async support)")
137
- return engine.get_name()
138
-
139
- engine = ServerEngineFactory.get_engine("uvicorn")
140
- if engine:
141
- logger.info("Selected uvicorn as fallback engine")
142
- return "uvicorn"
143
-
144
- return None
87
+ # Currently only hypercorn is supported
88
+ return "hypercorn"
145
89
 
146
90
  @staticmethod
147
91
  def validate_engine_compatibility(
@@ -201,19 +145,18 @@ class ServerConfigAdapter:
201
145
 
202
146
  class UnifiedServerRunner:
203
147
  """
204
- Unified server runner that abstracts the choice of server engine.
148
+ Unified server runner that uses hypercorn as the default engine.
205
149
 
206
- This class provides a unified interface for running servers regardless
207
- of the underlying engine, automatically selecting the best engine
208
- for the given configuration.
150
+ This class provides a unified interface for running servers using hypercorn
151
+ as the underlying engine.
209
152
  """
210
153
 
211
- def __init__(self, default_engine: str = "uvicorn"):
154
+ def __init__(self, default_engine: str = "hypercorn"):
212
155
  """
213
156
  Initialize the unified server runner.
214
157
 
215
158
  Args:
216
- default_engine: Default engine to use if no specific requirements
159
+ default_engine: Default engine to use (currently only hypercorn is supported)
217
160
  """
218
161
  self.default_engine = default_engine
219
162
  self.available_engines = ServerEngineFactory.get_available_engines()
@@ -228,22 +171,16 @@ class UnifiedServerRunner:
228
171
  engine_name: Optional[str] = None
229
172
  ) -> None:
230
173
  """
231
- Run server with the specified or optimal engine.
174
+ Run server with hypercorn engine.
232
175
 
233
176
  Args:
234
177
  app: ASGI application
235
178
  config: Server configuration
236
- engine_name: Specific engine to use (optional)
179
+ engine_name: Engine to use (currently only hypercorn is supported)
237
180
  """
238
- # Determine which engine to use
239
- if engine_name:
240
- selected_engine = engine_name
241
- logger.info(f"Using specified engine: {selected_engine}")
242
- else:
243
- selected_engine = ServerConfigAdapter.get_optimal_engine_for_config(config)
244
- if not selected_engine:
245
- selected_engine = self.default_engine
246
- logger.info(f"Using default engine: {selected_engine}")
181
+ # Use hypercorn as the only supported engine
182
+ selected_engine = "hypercorn"
183
+ logger.info(f"Using hypercorn engine")
247
184
 
248
185
  # Validate compatibility
249
186
  if not ServerConfigAdapter.validate_engine_compatibility(config, selected_engine):
@@ -1,8 +1,8 @@
1
1
  """
2
2
  Server Engine Abstraction
3
3
 
4
- This module provides an abstraction layer for different ASGI server engines,
5
- allowing easy switching between uvicorn, hypercorn, and other servers.
4
+ This module provides an abstraction layer for the hypercorn ASGI server engine,
5
+ providing full mTLS support and SSL capabilities.
6
6
 
7
7
  Author: Vasiliy Zdanovskiy
8
8
  email: vasilyvz@gmail.com
@@ -74,97 +74,6 @@ class ServerEngine(ABC):
74
74
  pass
75
75
 
76
76
 
77
- class UvicornEngine(ServerEngine):
78
- """
79
- Uvicorn server engine implementation.
80
-
81
- Provides basic SSL/TLS support but limited mTLS capabilities.
82
- """
83
-
84
- def get_name(self) -> str:
85
- return "uvicorn"
86
-
87
- def get_supported_features(self) -> Dict[str, bool]:
88
- return {
89
- "ssl_tls": True,
90
- "mtls_client_certs": False, # Limited support
91
- "ssl_scope_info": False, # No SSL info in request scope
92
- "client_cert_verification": False,
93
- "websockets": True,
94
- "http2": True,
95
- "reload": True
96
- }
97
-
98
- def get_config_schema(self) -> Dict[str, Any]:
99
- return {
100
- "host": {"type": "string", "default": "127.0.0.1"},
101
- "port": {"type": "integer", "default": 8000},
102
- "log_level": {"type": "string", "default": "info"},
103
- "ssl_certfile": {"type": "string", "optional": True},
104
- "ssl_keyfile": {"type": "string", "optional": True},
105
- "ssl_ca_certs": {"type": "string", "optional": True},
106
- "ssl_cert_reqs": {"type": "integer", "optional": True},
107
- "reload": {"type": "boolean", "default": False},
108
- "workers": {"type": "integer", "optional": True}
109
- }
110
-
111
- def validate_config(self, config: Dict[str, Any]) -> bool:
112
- """Validate uvicorn configuration."""
113
- required_fields = ["host", "port"]
114
-
115
- for field in required_fields:
116
- if field not in config:
117
- logger.error(f"Missing required field: {field}")
118
- return False
119
-
120
- # Validate SSL files exist if specified
121
- ssl_files = ["ssl_certfile", "ssl_keyfile", "ssl_ca_certs"]
122
- for ssl_file in ssl_files:
123
- if ssl_file in config and config[ssl_file]:
124
- if not Path(config[ssl_file]).exists():
125
- logger.error(f"SSL file not found: {config[ssl_file]}")
126
- return False
127
-
128
- return True
129
-
130
- def run_server(self, app: Any, config: Dict[str, Any]) -> None:
131
- """Run uvicorn server."""
132
- try:
133
- import uvicorn
134
-
135
- # Prepare uvicorn config
136
- uvicorn_config = {
137
- "host": config.get("host", "127.0.0.1"),
138
- "port": config.get("port", 8000),
139
- "log_level": config.get("log_level", "info").lower(),
140
- "reload": config.get("reload", False)
141
- }
142
-
143
- # Add SSL configuration if provided
144
- if "ssl_certfile" in config and config["ssl_certfile"]:
145
- uvicorn_config["ssl_certfile"] = config["ssl_certfile"]
146
- if "ssl_keyfile" in config and config["ssl_keyfile"]:
147
- uvicorn_config["ssl_keyfile"] = config["ssl_keyfile"]
148
- if "ssl_ca_certs" in config and config["ssl_ca_certs"]:
149
- uvicorn_config["ssl_ca_certs"] = config["ssl_ca_certs"]
150
- if "ssl_cert_reqs" in config and config["ssl_cert_reqs"]:
151
- uvicorn_config["ssl_cert_reqs"] = config["ssl_cert_reqs"]
152
-
153
- # Add workers if specified
154
- if "workers" in config and config["workers"]:
155
- uvicorn_config["workers"] = config["workers"]
156
-
157
- logger.info(f"Starting uvicorn server with config: {uvicorn_config}")
158
- uvicorn.run(app, **uvicorn_config)
159
-
160
- except ImportError:
161
- logger.error("uvicorn not installed. Install with: pip install uvicorn[standard]")
162
- raise
163
- except Exception as e:
164
- logger.error(f"Failed to start uvicorn server: {e}")
165
- raise
166
-
167
-
168
77
  class HypercornEngine(ServerEngine):
169
78
  """
170
79
  Hypercorn server engine implementation.
@@ -347,17 +256,14 @@ class ServerEngineFactory:
347
256
  @classmethod
348
257
  def initialize_default_engines(cls) -> None:
349
258
  """Initialize default server engines."""
350
- # Try to register hypercorn engine first (preferred for mTLS)
259
+ # Register hypercorn engine (only supported engine)
351
260
  try:
352
261
  import hypercorn
353
262
  cls.register_engine(HypercornEngine())
354
263
  logger.info("Hypercorn engine registered (full mTLS support available)")
355
264
  except ImportError:
356
- logger.info("Hypercorn not available")
357
-
358
- # Register uvicorn engine as fallback
359
- cls.register_engine(UvicornEngine())
360
- logger.info("Uvicorn engine registered (fallback)")
265
+ logger.error("Hypercorn not available - this is required for the framework")
266
+ raise
361
267
 
362
268
 
363
269
  # Initialize default engines
@@ -201,33 +201,34 @@ class SSLUtils:
201
201
  return min_tls <= max_tls
202
202
 
203
203
  @staticmethod
204
- def get_ssl_config_for_uvicorn(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
204
+ def get_ssl_config_for_hypercorn(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
205
205
  """
206
- Get SSL configuration for uvicorn from transport configuration.
206
+ Get SSL configuration for hypercorn from transport configuration.
207
207
 
208
208
  Args:
209
209
  ssl_config: SSL configuration from transport manager
210
210
 
211
211
  Returns:
212
- Configuration for uvicorn
212
+ Configuration for hypercorn
213
213
  """
214
- uvicorn_ssl = {}
214
+ hypercorn_ssl = {}
215
215
 
216
216
  if not ssl_config:
217
- return uvicorn_ssl
217
+ return hypercorn_ssl
218
218
 
219
219
  # Basic SSL parameters
220
220
  if ssl_config.get("cert_file"):
221
- uvicorn_ssl["ssl_certfile"] = ssl_config["cert_file"]
221
+ hypercorn_ssl["certfile"] = ssl_config["cert_file"]
222
222
 
223
223
  if ssl_config.get("key_file"):
224
- uvicorn_ssl["ssl_keyfile"] = ssl_config["key_file"]
224
+ hypercorn_ssl["keyfile"] = ssl_config["key_file"]
225
225
 
226
226
  if ssl_config.get("ca_cert"):
227
- uvicorn_ssl["ssl_ca_certs"] = ssl_config["ca_cert"]
227
+ hypercorn_ssl["ca_certs"] = ssl_config["ca_cert"]
228
228
 
229
- # Note: uvicorn doesn't support ssl_verify_mode parameter
230
- # Client verification is handled at the application level
229
+ # Client verification mode
230
+ if ssl_config.get("verify_client", False):
231
+ hypercorn_ssl["verify_mode"] = "CERT_REQUIRED"
231
232
 
232
- logger.info(f"Generated uvicorn SSL config: {uvicorn_ssl}")
233
- return uvicorn_ssl
233
+ logger.info(f"Generated hypercorn SSL config: {hypercorn_ssl}")
234
+ return hypercorn_ssl
@@ -265,12 +265,12 @@ class TransportManager:
265
265
  logger.info(f"All SSL files validated successfully: {files_to_check}")
266
266
  return True
267
267
 
268
- def get_uvicorn_config(self) -> Dict[str, Any]:
268
+ def get_hypercorn_config(self) -> Dict[str, Any]:
269
269
  """
270
- Get configuration for uvicorn.
270
+ Get configuration for hypercorn.
271
271
 
272
272
  Returns:
273
- Uvicorn configuration dictionary
273
+ Hypercorn configuration dictionary
274
274
  """
275
275
  config = {
276
276
  "host": "0.0.0.0", # Can be moved to settings
@@ -282,8 +282,8 @@ class TransportManager:
282
282
  ssl_config = self.get_ssl_config()
283
283
  if ssl_config:
284
284
  from mcp_proxy_adapter.core.ssl_utils import SSLUtils
285
- uvicorn_ssl = SSLUtils.get_ssl_config_for_uvicorn(ssl_config)
286
- config.update(uvicorn_ssl)
285
+ hypercorn_ssl = SSLUtils.get_ssl_config_for_hypercorn(ssl_config)
286
+ config.update(hypercorn_ssl)
287
287
 
288
288
  return config
289
289
 
@@ -0,0 +1,16 @@
1
+ """MCP Proxy Adapter Examples Package.
2
+
3
+ This package contains comprehensive examples demonstrating various
4
+ features and usage patterns of the MCP Proxy Adapter framework.
5
+
6
+ Examples include:
7
+ - Basic framework usage
8
+ - Full application with proxy registration
9
+ - Security configurations (HTTP, HTTPS, mTLS)
10
+ - Client implementations
11
+ - Command creation and registration
12
+
13
+ For detailed documentation, see the main README.md file.
14
+ """
15
+
16
+ __version__ = "6.2.1"
@@ -0,0 +1,7 @@
1
+ """Basic Framework Example.
2
+
3
+ This example demonstrates the fundamental usage of MCP Proxy Adapter
4
+ with minimal configuration and basic command registration.
5
+ """
6
+
7
+ from .main import app
@@ -0,0 +1,4 @@
1
+ """Basic Framework Commands.
2
+
3
+ Commands for the basic framework example.
4
+ """
@@ -0,0 +1,4 @@
1
+ """Basic Framework Hooks.
2
+
3
+ Hooks for the basic framework example.
4
+ """