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
@@ -1,18 +1,15 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Security Test Client for MCP Proxy Adapter
4
-
5
4
  This client tests various security configurations including:
6
5
  - Basic HTTP
7
6
  - HTTP + Token authentication
8
7
  - HTTPS
9
8
  - HTTPS + Token authentication
10
9
  - mTLS with certificate authentication
11
-
12
10
  Author: Vasiliy Zdanovskiy
13
11
  email: vasilyvz@gmail.com
14
12
  """
15
-
16
13
  import asyncio
17
14
  import json
18
15
  import os
@@ -22,20 +19,15 @@ import time
22
19
  from pathlib import Path
23
20
  from typing import Dict, List, Optional, Any
24
21
  from dataclasses import dataclass
25
-
26
22
  import aiohttp
27
23
  from aiohttp import ClientSession, ClientTimeout, TCPConnector
28
-
29
24
  # Add project root to path for imports
30
25
  project_root = Path(__file__).parent.parent.parent
31
26
  current_dir = Path(__file__).parent
32
27
  parent_dir = current_dir.parent
33
-
34
28
  sys.path.insert(0, str(project_root))
35
29
  sys.path.insert(0, str(current_dir))
36
30
  sys.path.insert(0, str(parent_dir))
37
-
38
-
39
31
  @dataclass
40
32
  class TestResult:
41
33
  """Test result data class."""
@@ -47,11 +39,8 @@ class TestResult:
47
39
  response_data: Optional[Dict] = None
48
40
  error_message: Optional[str] = None
49
41
  duration: float = 0.0
50
-
51
-
52
42
  class SecurityTestClient:
53
43
  """Security test client for comprehensive testing."""
54
-
55
44
  def __init__(self, base_url: str = "http://localhost:8000"):
56
45
  """Initialize security test client."""
57
46
  self.base_url = base_url
@@ -62,7 +51,6 @@ class SecurityTestClient:
62
51
  self.ssl_manager = None
63
52
  self.auth_manager = None
64
53
  self.test_results: List[TestResult] = []
65
-
66
54
  # Test tokens
67
55
  self.test_tokens = {
68
56
  "admin": "test-token-123",
@@ -72,7 +60,6 @@ class SecurityTestClient:
72
60
  "proxy": "proxy-token-123",
73
61
  "invalid": "invalid-token-999"
74
62
  }
75
-
76
63
  # Test certificates
77
64
  self.test_certificates = {
78
65
  "admin": {
@@ -88,68 +75,51 @@ class SecurityTestClient:
88
75
  "key": "mcp_proxy_adapter/examples/certs/readonly_key.pem"
89
76
  }
90
77
  }
91
-
92
78
  async def __aenter__(self):
93
79
  """Async context manager entry."""
94
80
  timeout = ClientTimeout(total=30)
95
-
96
81
  # Create SSL context for HTTPS connections
97
82
  ssl_context = self.create_ssl_context()
98
83
  connector = TCPConnector(ssl=ssl_context)
99
-
100
84
  self.session = ClientSession(timeout=timeout, connector=connector)
101
85
  return self
102
-
103
86
  def create_ssl_context_for_mtls(self) -> ssl.SSLContext:
104
87
  """Create SSL context for mTLS connections."""
105
88
  ssl_context = ssl.create_default_context()
106
-
107
89
  # For mTLS testing
108
90
  ssl_context.check_hostname = False
109
91
  ssl_context.verify_mode = ssl.CERT_NONE
110
-
111
92
  # Load client certificate and key
112
93
  cert_file = "./certs/user_cert.pem"
113
94
  key_file = "./certs/user_key.pem"
114
95
  ca_cert_file = "./certs/ca_cert.pem"
115
-
116
96
  if os.path.exists(cert_file) and os.path.exists(key_file):
117
97
  ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)
118
-
119
98
  if os.path.exists(ca_cert_file):
120
99
  ssl_context.load_verify_locations(cafile=ca_cert_file)
121
-
122
100
  return ssl_context
123
-
124
101
  async def __aexit__(self, exc_type, exc_val, exc_tb):
125
102
  """Async context manager exit."""
126
103
  if self.session:
127
104
  await self.session.close()
128
-
129
105
  def create_ssl_context(self, cert_file: Optional[str] = None,
130
106
  key_file: Optional[str] = None,
131
107
  ca_cert_file: Optional[str] = None) -> ssl.SSLContext:
132
108
  """Create SSL context for client."""
133
109
  ssl_context = ssl.create_default_context()
134
-
135
110
  # For testing with self-signed certificates
136
111
  ssl_context.check_hostname = False
137
112
  ssl_context.verify_mode = ssl.CERT_NONE
138
-
139
113
  if cert_file and key_file:
140
114
  ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)
141
-
142
115
  if ca_cert_file:
143
116
  ssl_context.load_verify_locations(cafile=ca_cert_file)
144
117
  # For testing, still don't verify
145
118
  ssl_context.verify_mode = ssl.CERT_NONE
146
-
147
119
  return ssl_context
148
-
149
120
  def create_auth_headers(self, auth_type: str, **kwargs) -> Dict[str, str]:
150
121
  """Create authentication headers."""
151
122
  headers = {"Content-Type": "application/json"}
152
-
153
123
  if auth_type == "api_key":
154
124
  token = kwargs.get("token", "test-token-123") # Use correct token
155
125
  headers["X-API-Key"] = token # Use X-API-Key header
@@ -163,20 +133,15 @@ class SecurityTestClient:
163
133
  # For mTLS, we need to use client certificates
164
134
  # This is handled by SSL context, not headers
165
135
  pass
166
-
167
136
  return headers
168
-
169
137
  async def test_health_check(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
170
138
  """Test health check endpoint."""
171
139
  start_time = time.time()
172
140
  test_name = f"Health Check ({auth_type})"
173
-
174
141
  try:
175
142
  headers = self.create_auth_headers(auth_type, **kwargs)
176
-
177
143
  async with self.session.get(f"{server_url}/health", headers=headers) as response:
178
144
  duration = time.time() - start_time
179
-
180
145
  if response.status == 200:
181
146
  data = await response.json()
182
147
  return TestResult(
@@ -199,7 +164,6 @@ class SecurityTestClient:
199
164
  error_message=f"Health check failed: {error_text}",
200
165
  duration=duration
201
166
  )
202
-
203
167
  except Exception as e:
204
168
  duration = time.time() - start_time
205
169
  return TestResult(
@@ -210,15 +174,12 @@ class SecurityTestClient:
210
174
  error_message=f"Health check error: {str(e)}",
211
175
  duration=duration
212
176
  )
213
-
214
177
  async def test_echo_command(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
215
178
  """Test echo command."""
216
179
  start_time = time.time()
217
180
  test_name = f"Echo Command ({auth_type})"
218
-
219
181
  try:
220
182
  headers = self.create_auth_headers(auth_type, **kwargs)
221
-
222
183
  data = {
223
184
  "jsonrpc": "2.0",
224
185
  "method": "echo",
@@ -227,12 +188,10 @@ class SecurityTestClient:
227
188
  },
228
189
  "id": 1
229
190
  }
230
-
231
- async with self.session.post(f"{server_url}/cmd",
232
- headers=headers,
191
+ async with self.session.post(f"{server_url}/cmd",
192
+ headers=headers,
233
193
  json=data) as response:
234
194
  duration = time.time() - start_time
235
-
236
195
  if response.status == 200:
237
196
  data = await response.json()
238
197
  return TestResult(
@@ -255,7 +214,6 @@ class SecurityTestClient:
255
214
  error_message=f"Echo command failed: {error_text}",
256
215
  duration=duration
257
216
  )
258
-
259
217
  except Exception as e:
260
218
  duration = time.time() - start_time
261
219
  return TestResult(
@@ -266,27 +224,22 @@ class SecurityTestClient:
266
224
  error_message=f"Echo command error: {str(e)}",
267
225
  duration=duration
268
226
  )
269
-
270
227
  async def test_security_command(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
271
228
  """Test security command."""
272
229
  start_time = time.time()
273
230
  test_name = f"Security Command ({auth_type})"
274
-
275
231
  try:
276
232
  headers = self.create_auth_headers(auth_type, **kwargs)
277
-
278
233
  data = {
279
234
  "jsonrpc": "2.0",
280
235
  "method": "health",
281
236
  "params": {},
282
237
  "id": 2
283
238
  }
284
-
285
- async with self.session.post(f"{server_url}/cmd",
286
- headers=headers,
239
+ async with self.session.post(f"{server_url}/cmd",
240
+ headers=headers,
287
241
  json=data) as response:
288
242
  duration = time.time() - start_time
289
-
290
243
  if response.status == 200:
291
244
  data = await response.json()
292
245
  return TestResult(
@@ -309,7 +262,6 @@ class SecurityTestClient:
309
262
  error_message=f"Security command failed: {error_text}",
310
263
  duration=duration
311
264
  )
312
-
313
265
  except Exception as e:
314
266
  duration = time.time() - start_time
315
267
  return TestResult(
@@ -320,15 +272,12 @@ class SecurityTestClient:
320
272
  error_message=f"Security command error: {str(e)}",
321
273
  duration=duration
322
274
  )
323
-
324
275
  async def test_health(self) -> TestResult:
325
276
  """Test health endpoint."""
326
277
  return await self.test_health_check(self.base_url, "none")
327
-
328
278
  async def test_command_execution(self) -> TestResult:
329
279
  """Test command execution."""
330
280
  return await self.test_echo_command(self.base_url, "none")
331
-
332
281
  async def test_authentication(self) -> TestResult:
333
282
  """Test authentication."""
334
283
  if "api_key" in self.auth_methods:
@@ -346,38 +295,29 @@ class SecurityTestClient:
346
295
  success=False,
347
296
  error_message="No authentication method available"
348
297
  )
349
-
350
298
  async def test_negative_authentication(self) -> TestResult:
351
299
  """Test negative authentication (should fail)."""
352
300
  return await self.test_echo_command(self.base_url, "api_key", token="invalid-token")
353
-
354
301
  async def test_no_auth_required(self) -> TestResult:
355
302
  """Test that no authentication is required."""
356
303
  return await self.test_echo_command(self.base_url, "none")
357
-
358
-
359
-
360
304
  async def test_negative_auth(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
361
305
  """Test negative authentication scenarios."""
362
306
  start_time = time.time()
363
307
  test_name = f"Negative Auth ({auth_type})"
364
-
365
308
  try:
366
309
  # Use invalid token
367
310
  headers = self.create_auth_headers("api_key", token="invalid-token-999")
368
-
369
311
  data = {
370
312
  "jsonrpc": "2.0",
371
313
  "method": "echo",
372
314
  "params": {"message": "Should fail"},
373
315
  "id": 3
374
316
  }
375
-
376
- async with self.session.post(f"{server_url}/cmd",
377
- headers=headers,
317
+ async with self.session.post(f"{server_url}/cmd",
318
+ headers=headers,
378
319
  json=data) as response:
379
320
  duration = time.time() - start_time
380
-
381
321
  # Expected to fail with 401
382
322
  if response.status == 401:
383
323
  return TestResult(
@@ -399,7 +339,6 @@ class SecurityTestClient:
399
339
  error_message=f"Expected 401, got {response.status}",
400
340
  duration=duration
401
341
  )
402
-
403
342
  except Exception as e:
404
343
  duration = time.time() - start_time
405
344
  return TestResult(
@@ -410,30 +349,25 @@ class SecurityTestClient:
410
349
  error_message=f"Negative auth error: {str(e)}",
411
350
  duration=duration
412
351
  )
413
-
414
352
  async def test_role_based_access(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
415
353
  """Test role-based access control."""
416
354
  start_time = time.time()
417
355
  test_name = f"Role-Based Access ({auth_type})"
418
-
419
356
  try:
420
357
  # Test with different roles
421
358
  role = kwargs.get("role", "user")
422
359
  token = self.test_tokens.get(role, self.test_tokens["user"])
423
360
  headers = self.create_auth_headers("api_key", token=token)
424
-
425
361
  data = {
426
362
  "jsonrpc": "2.0",
427
363
  "method": "echo",
428
364
  "params": {"message": f"Testing {role} role"},
429
365
  "id": 4
430
366
  }
431
-
432
- async with self.session.post(f"{server_url}/cmd",
433
- headers=headers,
367
+ async with self.session.post(f"{server_url}/cmd",
368
+ headers=headers,
434
369
  json=data) as response:
435
370
  duration = time.time() - start_time
436
-
437
371
  if response.status == 200:
438
372
  data = await response.json()
439
373
  return TestResult(
@@ -456,7 +390,6 @@ class SecurityTestClient:
456
390
  error_message=f"Role-based access failed: {error_text}",
457
391
  duration=duration
458
392
  )
459
-
460
393
  except Exception as e:
461
394
  duration = time.time() - start_time
462
395
  return TestResult(
@@ -467,31 +400,26 @@ class SecurityTestClient:
467
400
  error_message=f"Role-based access error: {str(e)}",
468
401
  duration=duration
469
402
  )
470
-
471
403
  async def test_role_permissions(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
472
404
  """Test role permissions with role_test command."""
473
405
  start_time = time.time()
474
406
  test_name = f"Role Permissions Test ({auth_type})"
475
-
476
407
  try:
477
408
  # Test with different roles and actions
478
409
  role = kwargs.get("role", "user")
479
410
  action = kwargs.get("action", "read")
480
411
  token = self.test_tokens.get(role, self.test_tokens["user"])
481
412
  headers = self.create_auth_headers("api_key", token=token)
482
-
483
413
  data = {
484
414
  "jsonrpc": "2.0",
485
415
  "method": "role_test",
486
416
  "params": {"action": action},
487
417
  "id": 5
488
418
  }
489
-
490
- async with self.session.post(f"{server_url}/cmd",
491
- headers=headers,
419
+ async with self.session.post(f"{server_url}/cmd",
420
+ headers=headers,
492
421
  json=data) as response:
493
422
  duration = time.time() - start_time
494
-
495
423
  if response.status == 200:
496
424
  data = await response.json()
497
425
  return TestResult(
@@ -514,7 +442,6 @@ class SecurityTestClient:
514
442
  error_message=f"Role permissions test failed: {error_text}",
515
443
  duration=duration
516
444
  )
517
-
518
445
  except Exception as e:
519
446
  duration = time.time() - start_time
520
447
  return TestResult(
@@ -525,26 +452,22 @@ class SecurityTestClient:
525
452
  error_message=f"Role permissions test error: {str(e)}",
526
453
  duration=duration
527
454
  )
528
-
529
455
  async def test_multiple_roles(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
530
456
  """Test multiple roles with different permissions."""
531
457
  start_time = time.time()
532
458
  test_name = f"Multiple Roles Test ({auth_type})"
533
-
534
459
  try:
535
460
  # Test admin role (should have all permissions)
536
461
  admin_token = self.test_tokens.get("admin", "admin-token-123")
537
462
  admin_headers = self.create_auth_headers("api_key", token=admin_token)
538
-
539
463
  admin_data = {
540
464
  "jsonrpc": "2.0",
541
465
  "method": "role_test",
542
466
  "params": {"action": "manage"},
543
467
  "id": 6
544
468
  }
545
-
546
- async with self.session.post(f"{server_url}/cmd",
547
- headers=admin_headers,
469
+ async with self.session.post(f"{server_url}/cmd",
470
+ headers=admin_headers,
548
471
  json=admin_data) as response:
549
472
  if response.status != 200:
550
473
  return TestResult(
@@ -556,22 +479,18 @@ class SecurityTestClient:
556
479
  error_message="Admin role test failed",
557
480
  duration=time.time() - start_time
558
481
  )
559
-
560
482
  # Test readonly role (should only have read permission)
561
483
  readonly_token = self.test_tokens.get("readonly", "readonly-token-123")
562
484
  readonly_headers = self.create_auth_headers("api_key", token=readonly_token)
563
-
564
485
  readonly_data = {
565
486
  "jsonrpc": "2.0",
566
487
  "method": "role_test",
567
488
  "params": {"action": "write"},
568
489
  }
569
-
570
- async with self.session.post(f"{server_url}/cmd",
571
- headers=readonly_headers,
490
+ async with self.session.post(f"{server_url}/cmd",
491
+ headers=readonly_headers,
572
492
  json=readonly_data) as response:
573
493
  duration = time.time() - start_time
574
-
575
494
  # Readonly should be denied write access
576
495
  if response.status == 403:
577
496
  return TestResult(
@@ -593,7 +512,6 @@ class SecurityTestClient:
593
512
  error_message="Readonly role incorrectly allowed write access",
594
513
  duration=duration
595
514
  )
596
-
597
515
  except Exception as e:
598
516
  duration = time.time() - start_time
599
517
  return TestResult(
@@ -604,12 +522,10 @@ class SecurityTestClient:
604
522
  error_message=f"Multiple roles test error: {str(e)}",
605
523
  duration=duration
606
524
  )
607
-
608
525
  async def run_security_tests(self, server_url: str, auth_type: str = "none", **kwargs) -> List[TestResult]:
609
526
  """Run comprehensive security tests."""
610
527
  print(f"\nšŸ”’ Running security tests for {server_url} ({auth_type})")
611
528
  print("=" * 60)
612
-
613
529
  tests = [
614
530
  self.test_health_check(server_url, auth_type, **kwargs),
615
531
  self.test_echo_command(server_url, auth_type, **kwargs),
@@ -617,13 +533,11 @@ class SecurityTestClient:
617
533
  self.test_negative_auth(server_url, auth_type, **kwargs),
618
534
  self.test_role_based_access(server_url, auth_type, **kwargs)
619
535
  ]
620
-
621
536
  results = []
622
537
  for test in tests:
623
538
  result = await test
624
539
  results.append(result)
625
540
  self.test_results.append(result)
626
-
627
541
  # Print result
628
542
  status = "āœ… PASS" if result.success else "āŒ FAIL"
629
543
  print(f"{status} {result.test_name}")
@@ -633,9 +547,7 @@ class SecurityTestClient:
633
547
  if result.error_message:
634
548
  print(f" Error: {result.error_message}")
635
549
  print()
636
-
637
550
  return results
638
-
639
551
  async def test_all_scenarios(self) -> Dict[str, List[TestResult]]:
640
552
  """Test all security scenarios."""
641
553
  scenarios = {
@@ -660,41 +572,32 @@ class SecurityTestClient:
660
572
  "auth": "certificate"
661
573
  }
662
574
  }
663
-
664
575
  all_results = {}
665
-
666
576
  for scenario_name, config in scenarios.items():
667
577
  print(f"\nšŸš€ Testing scenario: {scenario_name.upper()}")
668
578
  print("=" * 60)
669
-
670
579
  try:
671
580
  results = await self.run_security_tests(
672
- config["url"],
581
+ config["url"],
673
582
  config["auth"]
674
583
  )
675
584
  all_results[scenario_name] = results
676
-
677
585
  except Exception as e:
678
586
  print(f"āŒ Failed to test {scenario_name}: {e}")
679
587
  all_results[scenario_name] = []
680
-
681
588
  return all_results
682
-
683
589
  def print_summary(self):
684
590
  """Print test summary."""
685
591
  print("\n" + "=" * 80)
686
592
  print("šŸ“Š SECURITY TEST SUMMARY")
687
593
  print("=" * 80)
688
-
689
594
  total_tests = len(self.test_results)
690
595
  passed_tests = sum(1 for r in self.test_results if r.success)
691
596
  failed_tests = total_tests - passed_tests
692
-
693
597
  print(f"Total Tests: {total_tests}")
694
598
  print(f"Passed: {passed_tests} āœ…")
695
599
  print(f"Failed: {failed_tests} āŒ")
696
600
  print(f"Success Rate: {(passed_tests/total_tests*100):.1f}%")
697
-
698
601
  if failed_tests > 0:
699
602
  print("\nāŒ Failed Tests:")
700
603
  for result in self.test_results:
@@ -702,31 +605,25 @@ class SecurityTestClient:
702
605
  print(f" - {result.test_name} ({result.server_url})")
703
606
  if result.error_message:
704
607
  print(f" Error: {result.error_message}")
705
-
706
608
  print("\nāœ… Passed Tests:")
707
609
  for result in self.test_results:
708
610
  if result.success:
709
611
  print(f" - {result.test_name} ({result.server_url})")
710
-
711
-
712
612
  async def main():
713
613
  """Main function."""
714
614
  import argparse
715
-
716
615
  parser = argparse.ArgumentParser(description="Security Test Client for MCP Proxy Adapter")
717
- parser.add_argument("--server", default="http://localhost:8000",
616
+ parser.add_argument("--server", default="http://localhost:8000",
718
617
  help="Server URL to test")
719
- parser.add_argument("--auth", choices=["none", "api_key", "basic", "certificate"],
618
+ parser.add_argument("--auth", choices=["none", "api_key", "basic", "certificate"],
720
619
  default="none", help="Authentication type")
721
- parser.add_argument("--all-scenarios", action="store_true",
620
+ parser.add_argument("--all-scenarios", action="store_true",
722
621
  help="Test all security scenarios")
723
622
  parser.add_argument("--token", help="API token for authentication")
724
623
  parser.add_argument("--cert", help="Client certificate file")
725
624
  parser.add_argument("--key", help="Client private key file")
726
625
  parser.add_argument("--ca-cert", help="CA certificate file")
727
-
728
626
  args = parser.parse_args()
729
-
730
627
  if args.all_scenarios:
731
628
  # Test all scenarios
732
629
  async with SecurityTestClient() as client:
@@ -737,7 +634,5 @@ async def main():
737
634
  async with SecurityTestClient(args.server) as client:
738
635
  await client.run_security_tests(args.server, args.auth, token=args.token)
739
636
  client.print_summary()
740
-
741
-
742
637
  if __name__ == "__main__":
743
638
  asyncio.run(main())