mcp-proxy-adapter 6.0.0__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 (212) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +209 -79
  3. mcp_proxy_adapter/api/handlers.py +16 -5
  4. mcp_proxy_adapter/api/middleware/__init__.py +14 -9
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  7. mcp_proxy_adapter/api/middleware/protocol_middleware.py +84 -18
  8. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  9. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  10. mcp_proxy_adapter/commands/__init__.py +7 -1
  11. mcp_proxy_adapter/commands/base.py +7 -4
  12. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  13. mcp_proxy_adapter/commands/command_registry.py +8 -0
  14. mcp_proxy_adapter/commands/echo_command.py +81 -0
  15. mcp_proxy_adapter/commands/health_command.py +1 -1
  16. mcp_proxy_adapter/commands/help_command.py +21 -14
  17. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  18. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  19. mcp_proxy_adapter/commands/security_command.py +488 -0
  20. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  21. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  22. mcp_proxy_adapter/config.py +323 -40
  23. mcp_proxy_adapter/core/app_factory.py +410 -0
  24. mcp_proxy_adapter/core/app_runner.py +272 -0
  25. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  26. mcp_proxy_adapter/core/client.py +574 -0
  27. mcp_proxy_adapter/core/client_manager.py +284 -0
  28. mcp_proxy_adapter/core/client_security.py +384 -0
  29. mcp_proxy_adapter/core/logging.py +8 -3
  30. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  31. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  32. mcp_proxy_adapter/core/protocol_manager.py +169 -10
  33. mcp_proxy_adapter/core/proxy_client.py +602 -0
  34. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  35. mcp_proxy_adapter/core/security_adapter.py +12 -15
  36. mcp_proxy_adapter/core/security_integration.py +286 -0
  37. mcp_proxy_adapter/core/server_adapter.py +282 -0
  38. mcp_proxy_adapter/core/server_engine.py +270 -0
  39. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  40. mcp_proxy_adapter/core/transport_manager.py +5 -5
  41. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  42. mcp_proxy_adapter/examples/__init__.py +13 -4
  43. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  44. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  45. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  46. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  47. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  48. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  49. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  50. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  51. mcp_proxy_adapter/examples/demo_client.py +275 -0
  52. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  53. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  54. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  55. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  56. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  57. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  58. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  59. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  60. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  61. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  62. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  63. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  64. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  65. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  66. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  67. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  68. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  69. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  70. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  71. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  72. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  73. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  74. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  75. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  76. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  77. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  78. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  79. mcp_proxy_adapter/examples/run_example.py +59 -0
  80. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  81. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  82. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  83. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  84. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  85. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  86. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  87. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  88. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  89. mcp_proxy_adapter/examples/test_config.py +148 -0
  90. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  91. mcp_proxy_adapter/examples/test_examples.py +281 -0
  92. mcp_proxy_adapter/examples/universal_client.py +620 -0
  93. mcp_proxy_adapter/main.py +66 -148
  94. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  95. mcp_proxy_adapter/version.py +5 -2
  96. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  97. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  98. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  99. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  100. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  101. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  102. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  103. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  104. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  105. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  106. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  107. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  108. mcp_proxy_adapter/api/middleware/security.py +0 -376
  109. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  110. mcp_proxy_adapter/examples/README.md +0 -124
  111. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  112. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  113. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  114. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  115. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  116. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  117. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  118. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  119. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  120. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  121. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  122. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  123. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  124. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  125. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  126. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  127. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  128. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  129. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  130. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  131. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  132. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  133. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  134. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  135. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  136. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  137. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  138. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  139. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  140. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  141. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  142. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  143. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  144. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  145. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  146. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  147. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  148. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  149. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  150. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  153. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  154. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  155. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  156. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  157. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  158. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  159. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  160. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  161. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  162. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  163. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  164. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  166. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  167. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  168. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  169. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  170. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  171. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  172. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  173. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  174. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  175. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  176. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  177. mcp_proxy_adapter/tests/__init__.py +0 -0
  178. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  180. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  181. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  182. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  183. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  184. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  185. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  186. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  187. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  188. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  189. mcp_proxy_adapter/tests/conftest.py +0 -131
  190. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  191. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  192. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  193. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  194. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  195. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  196. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  197. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  198. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  199. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  200. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  201. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  202. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  203. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  204. mcp_proxy_adapter/tests/test_config.py +0 -127
  205. mcp_proxy_adapter/tests/test_utils.py +0 -65
  206. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  207. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  208. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  209. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  210. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  211. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.0.0.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())