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,620 @@
1
+ """
2
+ Universal Client Example
3
+ This module demonstrates all possible secure connection methods to the server
4
+ using the mcp_security_framework. The client supports all authentication methods
5
+ and connection types supported by the security framework.
6
+ Author: Vasiliy Zdanovskiy
7
+ email: vasilyvz@gmail.com
8
+ """
9
+ import asyncio
10
+ import json
11
+ import os
12
+ import ssl
13
+ import time
14
+ from typing import Dict, Any, Optional, List, Union
15
+ from urllib.parse import urljoin
16
+ from pathlib import Path
17
+ import aiohttp
18
+ import requests
19
+ from requests.exceptions import RequestException
20
+ # Import security framework components
21
+ try:
22
+ from mcp_security_framework import SecurityManager, AuthManager, CertificateManager
23
+ from mcp_security_framework.utils import generate_api_key, create_jwt_token, validate_jwt_token
24
+ from mcp_security_framework.utils import extract_roles_from_cert, validate_certificate_chain
25
+ from mcp_security_framework.utils import create_ssl_context, validate_server_certificate
26
+ SECURITY_FRAMEWORK_AVAILABLE = True
27
+ except ImportError:
28
+ SECURITY_FRAMEWORK_AVAILABLE = False
29
+ print("Warning: mcp_security_framework not available. Using basic HTTP client.")
30
+ class UniversalClient:
31
+ """
32
+ Universal client that demonstrates all possible secure connection methods.
33
+ Supports:
34
+ - HTTP/HTTPS connections
35
+ - API Key authentication
36
+ - JWT token authentication
37
+ - Certificate-based authentication
38
+ - SSL/TLS with custom certificates
39
+ - Role-based access control
40
+ - Rate limiting awareness
41
+ """
42
+ def __init__(self, config: Dict[str, Any]):
43
+ """
44
+ Initialize universal client with configuration.
45
+ Args:
46
+ config: Client configuration with security settings
47
+ """
48
+ self.config = config
49
+ self.base_url = config.get("server_url", "http://localhost:8000")
50
+ self.timeout = config.get("timeout", 30)
51
+ self.retry_attempts = config.get("retry_attempts", 3)
52
+ self.retry_delay = config.get("retry_delay", 1)
53
+ # Security configuration
54
+ self.security_config = config.get("security", {})
55
+ self.auth_method = self.security_config.get("auth_method", "none")
56
+ # Initialize security managers if framework is available
57
+ self.security_manager = None
58
+ self.auth_manager = None
59
+ self.cert_manager = None
60
+ if SECURITY_FRAMEWORK_AVAILABLE:
61
+ self._initialize_security_managers()
62
+ # Session management
63
+ self.session: Optional[aiohttp.ClientSession] = None
64
+ self.current_token: Optional[str] = None
65
+ self.token_expiry: Optional[float] = None
66
+ print(f"Universal client initialized with auth method: {self.auth_method}")
67
+ def _initialize_security_managers(self) -> None:
68
+ """Initialize security framework managers."""
69
+ try:
70
+ # Initialize security manager
71
+ self.security_manager = SecurityManager(self.security_config)
72
+ # Initialize auth manager
73
+ auth_config = self.security_config.get("auth", {})
74
+ self.auth_manager = AuthManager(auth_config)
75
+ # Initialize certificate manager
76
+ cert_config = self.security_config.get("certificates", {})
77
+ self.cert_manager = CertificateManager(cert_config)
78
+ print("Security framework managers initialized successfully")
79
+ except Exception as e:
80
+ print(f"Warning: Failed to initialize security managers: {e}")
81
+ async def __aenter__(self):
82
+ """Async context manager entry."""
83
+ await self.connect()
84
+ return self
85
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
86
+ """Async context manager exit."""
87
+ await self.disconnect()
88
+ async def connect(self) -> None:
89
+ """Establish connection with authentication."""
90
+ print(f"Connecting to {self.base_url} with {self.auth_method} authentication...")
91
+ # Create SSL context
92
+ ssl_context = self._create_ssl_context()
93
+ # Create connector with SSL context
94
+ connector = None
95
+ if ssl_context:
96
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
97
+ # Create session
98
+ self.session = aiohttp.ClientSession(connector=connector)
99
+ # Perform authentication based on method
100
+ if self.auth_method == "api_key":
101
+ await self._authenticate_api_key()
102
+ elif self.auth_method == "jwt":
103
+ await self._authenticate_jwt()
104
+ elif self.auth_method == "certificate":
105
+ await self._authenticate_certificate()
106
+ elif self.auth_method == "basic":
107
+ await self._authenticate_basic()
108
+ else:
109
+ print("No authentication required")
110
+ print("Connection established successfully")
111
+ async def disconnect(self) -> None:
112
+ """Close connection and cleanup."""
113
+ if self.session:
114
+ await self.session.close()
115
+ self.session = None
116
+ print("Connection closed")
117
+ async def _authenticate_api_key(self) -> None:
118
+ """Authenticate using API key."""
119
+ api_key_config = self.security_config.get("api_key", {})
120
+ api_key = api_key_config.get("key")
121
+ if not api_key:
122
+ raise ValueError("API key not provided in configuration")
123
+ # Store API key for requests
124
+ self.current_token = api_key
125
+ print(f"Authenticated with API key: {api_key[:8]}...")
126
+ async def _authenticate_jwt(self) -> None:
127
+ """Authenticate using JWT token."""
128
+ jwt_config = self.security_config.get("jwt", {})
129
+ # Check if we have a stored token that's still valid
130
+ if self.current_token and self.token_expiry and time.time() < self.token_expiry:
131
+ print("Using existing JWT token")
132
+ return
133
+ # Get credentials for JWT
134
+ username = jwt_config.get("username")
135
+ password = jwt_config.get("password")
136
+ secret = jwt_config.get("secret")
137
+ if not all([username, password, secret]):
138
+ raise ValueError("JWT credentials not provided in configuration")
139
+ # Create JWT token
140
+ if SECURITY_FRAMEWORK_AVAILABLE:
141
+ self.current_token = create_jwt_token(
142
+ username,
143
+ secret,
144
+ expiry_hours=jwt_config.get("expiry_hours", 24)
145
+ )
146
+ else:
147
+ # Simple JWT creation (for demonstration)
148
+ import jwt
149
+ payload = {
150
+ "username": username,
151
+ "exp": time.time() + (jwt_config.get("expiry_hours", 24) * 3600)
152
+ }
153
+ self.current_token = jwt.encode(payload, secret, algorithm="HS256")
154
+ self.token_expiry = time.time() + (jwt_config.get("expiry_hours", 24) * 3600)
155
+ print(f"Authenticated with JWT token: {self.current_token[:20]}...")
156
+ async def _authenticate_certificate(self) -> None:
157
+ """Authenticate using client certificate."""
158
+ cert_config = self.security_config.get("certificate", {})
159
+ cert_file = cert_config.get("cert_file")
160
+ key_file = cert_config.get("key_file")
161
+ if not cert_file or not key_file:
162
+ raise ValueError("Certificate files not provided in configuration")
163
+ # Validate certificate
164
+ if SECURITY_FRAMEWORK_AVAILABLE and self.cert_manager:
165
+ try:
166
+ cert_info = self.cert_manager.validate_certificate(cert_file, key_file)
167
+ print(f"Certificate validated: {cert_info.get('subject', 'Unknown')}")
168
+ # Extract roles from certificate
169
+ roles = extract_roles_from_cert(cert_file)
170
+ if roles:
171
+ print(f"Certificate roles: {roles}")
172
+ except Exception as e:
173
+ print(f"Warning: Certificate validation failed: {e}")
174
+ print("Certificate authentication prepared")
175
+ async def _authenticate_basic(self) -> None:
176
+ """Authenticate using basic authentication."""
177
+ basic_config = self.security_config.get("basic", {})
178
+ username = basic_config.get("username")
179
+ password = basic_config.get("password")
180
+ if not username or not password:
181
+ raise ValueError("Basic auth credentials not provided in configuration")
182
+ import base64
183
+ credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
184
+ self.current_token = f"Basic {credentials}"
185
+ print(f"Authenticated with basic auth: {username}")
186
+ def _get_auth_headers(self) -> Dict[str, str]:
187
+ """Get authentication headers for requests."""
188
+ headers = {"Content-Type": "application/json"}
189
+ if not self.current_token:
190
+ return headers
191
+ if self.auth_method == "api_key":
192
+ api_key_config = self.security_config.get("api_key", {})
193
+ header_name = api_key_config.get("header", "X-API-Key")
194
+ headers[header_name] = self.current_token
195
+ elif self.auth_method == "jwt":
196
+ headers["Authorization"] = f"Bearer {self.current_token}"
197
+ elif self.auth_method == "basic":
198
+ headers["Authorization"] = self.current_token
199
+ return headers
200
+ def _create_ssl_context(self) -> Optional[ssl.SSLContext]:
201
+ """Create SSL context for secure connections."""
202
+ ssl_config = self.security_config.get("ssl", {})
203
+ if not ssl_config.get("enabled", False):
204
+ return None
205
+ try:
206
+ ssl_config = self.security_config.get("ssl", {})
207
+ if not ssl_config.get("enabled", False):
208
+ return None
209
+ # Create SSL context using security framework
210
+ if self.security_manager:
211
+ return self.security_manager.create_client_ssl_context()
212
+ # Fallback SSL context creation
213
+ context = ssl.create_default_context()
214
+ # Add client certificate if provided
215
+ cert_config = self.security_config.get("certificate", {})
216
+ if cert_config.get("enabled", False):
217
+ cert_file = cert_config.get("cert_file")
218
+ key_file = cert_config.get("key_file")
219
+ if cert_file and key_file:
220
+ context.load_cert_chain(cert_file, key_file)
221
+ # Add CA certificate if provided
222
+ ca_cert_file = ssl_config.get("ca_cert_file") or ssl_config.get("ca_cert")
223
+ if ca_cert_file and os.path.exists(ca_cert_file):
224
+ context.load_verify_locations(ca_cert_file)
225
+ # Configure verification
226
+ if ssl_config.get("check_hostname", True):
227
+ context.check_hostname = True
228
+ context.verify_mode = ssl.CERT_REQUIRED
229
+ else:
230
+ context.check_hostname = False
231
+ context.verify_mode = ssl.CERT_NONE
232
+ return context
233
+ except Exception as e:
234
+ print(f"Warning: Failed to create SSL context: {e}")
235
+ return None
236
+ async def request(
237
+ self,
238
+ method: str,
239
+ endpoint: str,
240
+ data: Optional[Dict[str, Any]] = None,
241
+ headers: Optional[Dict[str, str]] = None
242
+ ) -> Dict[str, Any]:
243
+ """
244
+ Make authenticated request to server.
245
+ Args:
246
+ method: HTTP method (GET, POST, etc.)
247
+ endpoint: API endpoint
248
+ data: Request data
249
+ headers: Additional headers
250
+ Returns:
251
+ Response data
252
+ """
253
+ url = urljoin(self.base_url, endpoint)
254
+ # Prepare headers
255
+ request_headers = self._get_auth_headers()
256
+ if headers:
257
+ request_headers.update(headers)
258
+ try:
259
+ for attempt in range(self.retry_attempts):
260
+ try:
261
+ async with self.session.request(
262
+ method,
263
+ url,
264
+ json=data,
265
+ headers=request_headers,
266
+ timeout=aiohttp.ClientTimeout(total=self.timeout)
267
+ ) as response:
268
+ result = await response.json()
269
+ # Validate response if security framework available
270
+ if SECURITY_FRAMEWORK_AVAILABLE and self.security_manager:
271
+ self.security_manager.validate_server_response(dict(response.headers))
272
+ if response.status >= 400:
273
+ print(f"Request failed with status {response.status}: {result}")
274
+ return {"error": result, "status": response.status}
275
+ return result
276
+ except Exception as e:
277
+ print(f"Request attempt {attempt + 1} failed: {e}")
278
+ if attempt < self.retry_attempts - 1:
279
+ await asyncio.sleep(self.retry_delay)
280
+ else:
281
+ raise
282
+ except Exception as e:
283
+ print(f"Request failed: {e}")
284
+ raise
285
+ async def get(self, endpoint: str, **kwargs) -> Dict[str, Any]:
286
+ """Make GET request."""
287
+ return await self.request("GET", endpoint, **kwargs)
288
+ async def post(self, endpoint: str, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
289
+ """Make POST request."""
290
+ return await self.request("POST", endpoint, data=data, **kwargs)
291
+ async def put(self, endpoint: str, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
292
+ """Make PUT request."""
293
+ return await self.request("PUT", endpoint, data=data, **kwargs)
294
+ async def delete(self, endpoint: str, **kwargs) -> Dict[str, Any]:
295
+ """Make DELETE request."""
296
+ return await self.request("DELETE", endpoint, **kwargs)
297
+ async def test_connection(self) -> bool:
298
+ """Test connection to server."""
299
+ try:
300
+ result = await self.get("/health")
301
+ if "error" not in result:
302
+ print("✅ Connection test successful")
303
+ return True
304
+ else:
305
+ print(f"❌ Connection test failed: {result}")
306
+ return False
307
+ except Exception as e:
308
+ print(f"❌ Connection test failed: {e}")
309
+ return False
310
+ async def test_security_features(self) -> Dict[str, bool]:
311
+ """Test various security features."""
312
+ results = {}
313
+ # Test basic connectivity
314
+ results["connectivity"] = await self.test_connection()
315
+ # Test authentication
316
+ if self.auth_method != "none":
317
+ try:
318
+ result = await self.get("/api/auth/status")
319
+ results["authentication"] = "error" not in result
320
+ except:
321
+ results["authentication"] = False
322
+ # Test SSL/TLS
323
+ if self.base_url.startswith("https"):
324
+ results["ssl_tls"] = True
325
+ else:
326
+ results["ssl_tls"] = False
327
+ # Test certificate validation
328
+ if self.auth_method == "certificate" and SECURITY_FRAMEWORK_AVAILABLE:
329
+ results["certificate_validation"] = True
330
+ else:
331
+ results["certificate_validation"] = False
332
+ return results
333
+ def create_client_config(
334
+ server_url: str,
335
+ auth_method: str = "none",
336
+ **kwargs
337
+ ) -> Dict[str, Any]:
338
+ """
339
+ Create client configuration for different authentication methods.
340
+ Args:
341
+ server_url: Server URL
342
+ auth_method: Authentication method (none, api_key, jwt, certificate, basic)
343
+ **kwargs: Additional configuration parameters
344
+ Returns:
345
+ Client configuration dictionary
346
+ """
347
+ config = {
348
+ "server_url": server_url,
349
+ "timeout": 30,
350
+ "retry_attempts": 3,
351
+ "retry_delay": 1,
352
+ "security": {
353
+ "auth_method": auth_method
354
+ }
355
+ }
356
+ if auth_method == "api_key":
357
+ config["security"]["api_key"] = {
358
+ "key": kwargs.get("api_key", "your_api_key_here"),
359
+ "header": kwargs.get("header", "X-API-Key")
360
+ }
361
+ elif auth_method == "jwt":
362
+ config["security"]["jwt"] = {
363
+ "username": kwargs.get("username", "user"),
364
+ "password": kwargs.get("password", "password"),
365
+ "secret": kwargs.get("secret", "your_jwt_secret"),
366
+ "expiry_hours": kwargs.get("expiry_hours", 24)
367
+ }
368
+ elif auth_method == "certificate":
369
+ config["security"]["certificate"] = {
370
+ "enabled": True,
371
+ "cert_file": kwargs.get("cert_file", "./certs/client.crt"),
372
+ "key_file": kwargs.get("key_file", "./keys/client.key"),
373
+ "ca_cert_file": kwargs.get("ca_cert_file", "./certs/ca.crt")
374
+ }
375
+ config["security"]["ssl"] = {
376
+ "enabled": True,
377
+ "check_hostname": kwargs.get("check_hostname", True),
378
+ "ca_cert_file": kwargs.get("ca_cert_file", "./certs/ca.crt")
379
+ }
380
+ elif auth_method == "basic":
381
+ config["security"]["basic"] = {
382
+ "username": kwargs.get("username", "user"),
383
+ "password": kwargs.get("password", "password")
384
+ }
385
+ return config
386
+ async def demo_all_connection_methods():
387
+ """Demonstrate all possible connection methods."""
388
+ print("🚀 Universal Client Demo - All Connection Methods")
389
+ print("=" * 60)
390
+ # Test configurations for different auth methods
391
+ test_configs = [
392
+ {
393
+ "name": "No Authentication",
394
+ "config": create_client_config("http://localhost:8000", "none")
395
+ },
396
+ {
397
+ "name": "API Key Authentication",
398
+ "config": create_client_config(
399
+ "http://localhost:8000",
400
+ "api_key",
401
+ api_key="demo_api_key_123"
402
+ )
403
+ },
404
+ {
405
+ "name": "JWT Authentication",
406
+ "config": create_client_config(
407
+ "http://localhost:8000",
408
+ "jwt",
409
+ username="demo_user",
410
+ password="demo_password",
411
+ secret="demo_jwt_secret"
412
+ )
413
+ },
414
+ {
415
+ "name": "Basic Authentication",
416
+ "config": create_client_config(
417
+ "http://localhost:8000",
418
+ "basic",
419
+ username="demo_user",
420
+ password="demo_password"
421
+ )
422
+ },
423
+ {
424
+ "name": "Certificate Authentication (HTTPS)",
425
+ "config": create_client_config(
426
+ "https://localhost:8443",
427
+ "certificate",
428
+ cert_file="./certs/client.crt",
429
+ key_file="./keys/client.key",
430
+ ca_cert_file="./certs/ca.crt"
431
+ )
432
+ }
433
+ ]
434
+ for test_config in test_configs:
435
+ print(f"\n📋 Testing: {test_config['name']}")
436
+ print("-" * 40)
437
+ try:
438
+ async with UniversalClient(test_config["config"]) as client:
439
+ # Test connection
440
+ success = await client.test_connection()
441
+ if success:
442
+ # Test security features
443
+ security_results = await client.test_security_features()
444
+ print("Security Features:")
445
+ for feature, status in security_results.items():
446
+ status_icon = "✅" if status else "❌"
447
+ print(f" {status_icon} {feature}: {status}")
448
+ # Make a test API call
449
+ try:
450
+ result = await client.get("/api/status")
451
+ print(f"API Status: {result}")
452
+ except Exception as e:
453
+ print(f"API call failed: {e}")
454
+ else:
455
+ print("❌ Connection failed")
456
+ except Exception as e:
457
+ print(f"❌ Test failed: {e}")
458
+ print("\n🎉 Demo completed!")
459
+ async def demo_specific_connection(auth_method: str, **kwargs):
460
+ """
461
+ Demo specific connection method.
462
+ Args:
463
+ auth_method: Authentication method to test
464
+ **kwargs: Configuration parameters
465
+ """
466
+ print(f"🚀 Testing {auth_method} connection")
467
+ print("=" * 40)
468
+ config = create_client_config("http://localhost:8000", auth_method, **kwargs)
469
+ async with UniversalClient(config) as client:
470
+ # Test connection
471
+ success = await client.test_connection()
472
+ if success:
473
+ print("✅ Connection successful!")
474
+ # Make some API calls
475
+ try:
476
+ # Get server status
477
+ status = await client.get("/api/status")
478
+ print(f"Server Status: {status}")
479
+ # Test command execution
480
+ command_data = {
481
+ "jsonrpc": "2.0",
482
+ "method": "test_command",
483
+ "params": {"message": "Hello from universal client!"},
484
+ "id": 1
485
+ }
486
+ result = await client.post("/api/jsonrpc", command_data)
487
+ print(f"Command Result: {result}")
488
+ except Exception as e:
489
+ print(f"API calls failed: {e}")
490
+ else:
491
+ print("❌ Connection failed")
492
+ if __name__ == "__main__":
493
+ import sys
494
+ import argparse
495
+ import json
496
+ parser = argparse.ArgumentParser(description="Universal Client for MCP Proxy Adapter")
497
+ parser.add_argument("--config", help="Path to configuration file")
498
+ parser.add_argument("--method", help="JSON-RPC method to call")
499
+ parser.add_argument("--params", help="JSON-RPC parameters (JSON string)")
500
+ parser.add_argument("--auth-method", help="Authentication method")
501
+ parser.add_argument("--server-url", help="Server URL")
502
+ args = parser.parse_args()
503
+ if args.config:
504
+ # Load configuration from file
505
+ try:
506
+ with open(args.config, 'r') as f:
507
+ config_data = json.load(f)
508
+ # Extract server configuration
509
+ server_config = config_data.get("server", {})
510
+ host = server_config.get("host", "127.0.0.1")
511
+ port = server_config.get("port", 8000)
512
+ # Determine protocol
513
+ ssl_config = config_data.get("ssl", {})
514
+ ssl_enabled = ssl_config.get("enabled", False)
515
+ protocol = "https" if ssl_enabled else "http"
516
+ server_url = f"{protocol}://{host}:{port}"
517
+ print(f"🚀 Testing --config connection")
518
+ print("=" * 40)
519
+ print(f"Universal client initialized with auth method: --config")
520
+ print(f"Connecting to {server_url} with --config authentication...")
521
+ # Create client configuration
522
+ client_config = {
523
+ "server_url": server_url,
524
+ "timeout": 30,
525
+ "retry_attempts": 3,
526
+ "retry_delay": 1,
527
+ "security": {
528
+ "auth_method": "none"
529
+ }
530
+ }
531
+ # Add SSL configuration if needed
532
+ if ssl_enabled:
533
+ client_config["security"]["ssl"] = {
534
+ "enabled": True,
535
+ "check_hostname": False,
536
+ "verify": False
537
+ }
538
+ # Add CA certificate if available
539
+ ca_cert = ssl_config.get("ca_cert")
540
+ if ca_cert and os.path.exists(ca_cert):
541
+ client_config["security"]["ssl"]["ca_cert_file"] = ca_cert
542
+ async def test_config_connection():
543
+ async with UniversalClient(client_config) as client:
544
+ # Test connection
545
+ success = await client.test_connection()
546
+ if success:
547
+ print("No authentication required")
548
+ print("Connection established successfully")
549
+ if args.method:
550
+ # Execute JSON-RPC method
551
+ params = {}
552
+ if args.params:
553
+ try:
554
+ params = json.loads(args.params)
555
+ except json.JSONDecodeError:
556
+ print("❌ Invalid JSON parameters")
557
+ return
558
+ command_data = {
559
+ "jsonrpc": "2.0",
560
+ "method": args.method,
561
+ "params": params,
562
+ "id": 1
563
+ }
564
+ try:
565
+ result = await client.post("/api/jsonrpc", command_data)
566
+ print(f"✅ Method '{args.method}' executed successfully:")
567
+ print(json.dumps(result, indent=2))
568
+ except Exception as e:
569
+ print(f"❌ Method execution failed: {e}")
570
+ else:
571
+ # Default to help command
572
+ command_data = {
573
+ "jsonrpc": "2.0",
574
+ "method": "help",
575
+ "params": {},
576
+ "id": 1
577
+ }
578
+ try:
579
+ result = await client.post("/api/jsonrpc", command_data)
580
+ print("✅ Help command executed successfully:")
581
+ print(json.dumps(result, indent=2))
582
+ except Exception as e:
583
+ print(f"❌ Help command failed: {e}")
584
+ else:
585
+ print("❌ Connection failed")
586
+ print("Connection closed")
587
+ asyncio.run(test_config_connection())
588
+ except FileNotFoundError:
589
+ print(f"❌ Configuration file not found: {args.config}")
590
+ except json.JSONDecodeError:
591
+ print(f"❌ Invalid JSON in configuration file: {args.config}")
592
+ except Exception as e:
593
+ print(f"❌ Error loading configuration: {e}")
594
+ elif len(sys.argv) > 1:
595
+ # Test specific auth method (legacy mode)
596
+ auth_method = sys.argv[1]
597
+ kwargs = {}
598
+ if auth_method == "api_key":
599
+ kwargs["api_key"] = "demo_key_123"
600
+ elif auth_method == "jwt":
601
+ kwargs.update({
602
+ "username": "demo_user",
603
+ "password": "demo_password",
604
+ "secret": "demo_secret"
605
+ })
606
+ elif auth_method == "certificate":
607
+ kwargs.update({
608
+ "cert_file": "./certs/client.crt",
609
+ "key_file": "./keys/client.key",
610
+ "ca_cert_file": "./certs/ca.crt"
611
+ })
612
+ elif auth_method == "basic":
613
+ kwargs.update({
614
+ "username": "demo_user",
615
+ "password": "demo_password"
616
+ })
617
+ asyncio.run(demo_specific_connection(auth_method, **kwargs))
618
+ else:
619
+ # Demo all connection methods
620
+ asyncio.run(demo_all_connection_methods())