mcp-proxy-adapter 2.0.1__py3-none-any.whl โ†’ 6.9.50__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.

Potentially problematic release.


This version of mcp-proxy-adapter might be problematic. Click here for more details.

Files changed (269) hide show
  1. mcp_proxy_adapter/__init__.py +47 -0
  2. mcp_proxy_adapter/__main__.py +13 -0
  3. mcp_proxy_adapter/api/__init__.py +0 -0
  4. mcp_proxy_adapter/api/app.py +66 -0
  5. mcp_proxy_adapter/api/core/__init__.py +18 -0
  6. mcp_proxy_adapter/api/core/app_factory.py +400 -0
  7. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  8. mcp_proxy_adapter/api/core/registration_context.py +356 -0
  9. mcp_proxy_adapter/api/core/registration_manager.py +307 -0
  10. mcp_proxy_adapter/api/core/registration_tasks.py +84 -0
  11. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  12. mcp_proxy_adapter/api/handlers.py +181 -0
  13. mcp_proxy_adapter/api/middleware/__init__.py +21 -0
  14. mcp_proxy_adapter/api/middleware/base.py +54 -0
  15. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +73 -0
  16. mcp_proxy_adapter/api/middleware/error_handling.py +76 -0
  17. mcp_proxy_adapter/api/middleware/factory.py +147 -0
  18. mcp_proxy_adapter/api/middleware/logging.py +31 -0
  19. mcp_proxy_adapter/api/middleware/performance.py +51 -0
  20. mcp_proxy_adapter/api/middleware/protocol_middleware.py +140 -0
  21. mcp_proxy_adapter/api/middleware/transport_middleware.py +87 -0
  22. mcp_proxy_adapter/api/middleware/unified_security.py +223 -0
  23. mcp_proxy_adapter/api/middleware/user_info_middleware.py +132 -0
  24. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  25. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  26. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  27. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  28. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  29. mcp_proxy_adapter/api/schemas.py +270 -0
  30. mcp_proxy_adapter/api/tool_integration.py +131 -0
  31. mcp_proxy_adapter/api/tools.py +163 -0
  32. mcp_proxy_adapter/cli/__init__.py +12 -0
  33. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  34. mcp_proxy_adapter/cli/commands/client.py +100 -0
  35. mcp_proxy_adapter/cli/commands/config_generate.py +105 -0
  36. mcp_proxy_adapter/cli/commands/config_validate.py +94 -0
  37. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  38. mcp_proxy_adapter/cli/commands/server.py +174 -0
  39. mcp_proxy_adapter/cli/commands/sets.py +132 -0
  40. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  41. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  42. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  43. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  44. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  45. mcp_proxy_adapter/cli/main.py +63 -0
  46. mcp_proxy_adapter/cli/parser.py +338 -0
  47. mcp_proxy_adapter/cli/validators.py +231 -0
  48. mcp_proxy_adapter/client/jsonrpc_client/__init__.py +9 -0
  49. mcp_proxy_adapter/client/jsonrpc_client/client.py +42 -0
  50. mcp_proxy_adapter/client/jsonrpc_client/command_api.py +45 -0
  51. mcp_proxy_adapter/client/jsonrpc_client/proxy_api.py +224 -0
  52. mcp_proxy_adapter/client/jsonrpc_client/queue_api.py +60 -0
  53. mcp_proxy_adapter/client/jsonrpc_client/transport.py +108 -0
  54. mcp_proxy_adapter/client/proxy.py +123 -0
  55. mcp_proxy_adapter/commands/__init__.py +66 -0
  56. mcp_proxy_adapter/commands/auth_validation_command.py +69 -0
  57. mcp_proxy_adapter/commands/base.py +389 -0
  58. mcp_proxy_adapter/commands/builtin_commands.py +30 -0
  59. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  60. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  61. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  62. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  63. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  64. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  65. mcp_proxy_adapter/commands/catalog_manager.py +97 -0
  66. mcp_proxy_adapter/commands/cert_monitor_command.py +552 -0
  67. mcp_proxy_adapter/commands/certificate_management_command.py +562 -0
  68. mcp_proxy_adapter/commands/command_registry.py +298 -0
  69. mcp_proxy_adapter/commands/config_command.py +102 -0
  70. mcp_proxy_adapter/commands/dependency_container.py +40 -0
  71. mcp_proxy_adapter/commands/dependency_manager.py +143 -0
  72. mcp_proxy_adapter/commands/echo_command.py +48 -0
  73. mcp_proxy_adapter/commands/health_command.py +142 -0
  74. mcp_proxy_adapter/commands/help_command.py +175 -0
  75. mcp_proxy_adapter/commands/hooks.py +172 -0
  76. mcp_proxy_adapter/commands/key_management_command.py +484 -0
  77. mcp_proxy_adapter/commands/load_command.py +123 -0
  78. mcp_proxy_adapter/commands/plugins_command.py +246 -0
  79. mcp_proxy_adapter/commands/protocol_management_command.py +216 -0
  80. mcp_proxy_adapter/commands/proxy_registration_command.py +319 -0
  81. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  82. mcp_proxy_adapter/commands/registration_status_command.py +76 -0
  83. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  84. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  85. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  86. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  87. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  88. mcp_proxy_adapter/commands/reload_command.py +136 -0
  89. mcp_proxy_adapter/commands/result.py +157 -0
  90. mcp_proxy_adapter/commands/role_test_command.py +99 -0
  91. mcp_proxy_adapter/commands/roles_management_command.py +502 -0
  92. mcp_proxy_adapter/commands/security_command.py +472 -0
  93. mcp_proxy_adapter/commands/settings_command.py +113 -0
  94. mcp_proxy_adapter/commands/ssl_setup_command.py +306 -0
  95. mcp_proxy_adapter/commands/token_management_command.py +500 -0
  96. mcp_proxy_adapter/commands/transport_management_command.py +129 -0
  97. mcp_proxy_adapter/commands/unload_command.py +92 -0
  98. mcp_proxy_adapter/config.py +32 -0
  99. mcp_proxy_adapter/core/__init__.py +8 -0
  100. mcp_proxy_adapter/core/app_factory.py +560 -0
  101. mcp_proxy_adapter/core/app_runner.py +318 -0
  102. mcp_proxy_adapter/core/auth_validator.py +508 -0
  103. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  104. mcp_proxy_adapter/core/certificate/certificate_creator.py +372 -0
  105. mcp_proxy_adapter/core/certificate/certificate_extractor.py +185 -0
  106. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  107. mcp_proxy_adapter/core/certificate/certificate_validator.py +481 -0
  108. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +65 -0
  109. mcp_proxy_adapter/core/certificate_utils.py +249 -0
  110. mcp_proxy_adapter/core/client.py +608 -0
  111. mcp_proxy_adapter/core/client_manager.py +271 -0
  112. mcp_proxy_adapter/core/client_security.py +411 -0
  113. mcp_proxy_adapter/core/config/__init__.py +18 -0
  114. mcp_proxy_adapter/core/config/config.py +237 -0
  115. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  116. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  117. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  118. mcp_proxy_adapter/core/config/simple_config.py +204 -0
  119. mcp_proxy_adapter/core/config/simple_config_generator.py +131 -0
  120. mcp_proxy_adapter/core/config/simple_config_validator.py +476 -0
  121. mcp_proxy_adapter/core/config_converter.py +252 -0
  122. mcp_proxy_adapter/core/config_validator.py +211 -0
  123. mcp_proxy_adapter/core/crl_utils.py +362 -0
  124. mcp_proxy_adapter/core/errors.py +276 -0
  125. mcp_proxy_adapter/core/job_manager.py +54 -0
  126. mcp_proxy_adapter/core/logging.py +250 -0
  127. mcp_proxy_adapter/core/mtls_asgi.py +140 -0
  128. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  129. mcp_proxy_adapter/core/mtls_proxy.py +229 -0
  130. mcp_proxy_adapter/core/mtls_server.py +154 -0
  131. mcp_proxy_adapter/core/protocol_manager.py +232 -0
  132. mcp_proxy_adapter/core/proxy/__init__.py +19 -0
  133. mcp_proxy_adapter/core/proxy/auth_manager.py +26 -0
  134. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +160 -0
  135. mcp_proxy_adapter/core/proxy/registration_client.py +186 -0
  136. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  137. mcp_proxy_adapter/core/proxy_client.py +184 -0
  138. mcp_proxy_adapter/core/proxy_registration.py +80 -0
  139. mcp_proxy_adapter/core/role_utils.py +103 -0
  140. mcp_proxy_adapter/core/security_adapter.py +343 -0
  141. mcp_proxy_adapter/core/security_factory.py +96 -0
  142. mcp_proxy_adapter/core/security_integration.py +342 -0
  143. mcp_proxy_adapter/core/server_adapter.py +251 -0
  144. mcp_proxy_adapter/core/server_engine.py +217 -0
  145. mcp_proxy_adapter/core/settings.py +260 -0
  146. mcp_proxy_adapter/core/signal_handler.py +107 -0
  147. mcp_proxy_adapter/core/ssl_utils.py +161 -0
  148. mcp_proxy_adapter/core/transport_manager.py +153 -0
  149. mcp_proxy_adapter/core/unified_config_adapter.py +471 -0
  150. mcp_proxy_adapter/core/utils.py +101 -0
  151. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  152. mcp_proxy_adapter/core/validation/config_validator.py +219 -0
  153. mcp_proxy_adapter/core/validation/file_validator.py +131 -0
  154. mcp_proxy_adapter/core/validation/protocol_validator.py +205 -0
  155. mcp_proxy_adapter/core/validation/security_validator.py +140 -0
  156. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  157. mcp_proxy_adapter/custom_openapi.py +58 -0
  158. mcp_proxy_adapter/examples/__init__.py +16 -0
  159. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  160. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  161. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  162. mcp_proxy_adapter/examples/basic_framework/main.py +52 -0
  163. mcp_proxy_adapter/examples/bugfix_certificate_config.py +261 -0
  164. mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
  165. mcp_proxy_adapter/examples/check_config.py +413 -0
  166. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  167. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  168. mcp_proxy_adapter/examples/config_builder.py +234 -0
  169. mcp_proxy_adapter/examples/config_cli.py +282 -0
  170. mcp_proxy_adapter/examples/create_test_configs.py +174 -0
  171. mcp_proxy_adapter/examples/debug_request_state.py +130 -0
  172. mcp_proxy_adapter/examples/debug_role_chain.py +191 -0
  173. mcp_proxy_adapter/examples/demo_client.py +287 -0
  174. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  175. mcp_proxy_adapter/examples/full_application/commands/__init__.py +8 -0
  176. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +45 -0
  177. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +52 -0
  178. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +32 -0
  179. mcp_proxy_adapter/examples/full_application/commands/help_command.py +54 -0
  180. mcp_proxy_adapter/examples/full_application/commands/list_command.py +57 -0
  181. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +5 -0
  182. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +29 -0
  183. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +27 -0
  184. mcp_proxy_adapter/examples/full_application/main.py +311 -0
  185. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +161 -0
  186. mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
  187. mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
  188. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +45 -0
  189. mcp_proxy_adapter/examples/full_application/test_server.py +163 -0
  190. mcp_proxy_adapter/examples/full_application/test_simple_server.py +62 -0
  191. mcp_proxy_adapter/examples/generate_config.py +502 -0
  192. mcp_proxy_adapter/examples/proxy_registration_example.py +335 -0
  193. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  194. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  195. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  196. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  197. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  198. mcp_proxy_adapter/examples/required_certificates.py +208 -0
  199. mcp_proxy_adapter/examples/run_example.py +77 -0
  200. mcp_proxy_adapter/examples/run_full_test_suite.py +619 -0
  201. mcp_proxy_adapter/examples/run_proxy_server.py +153 -0
  202. mcp_proxy_adapter/examples/run_security_tests_fixed.py +435 -0
  203. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  204. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  205. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  206. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  207. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  208. mcp_proxy_adapter/examples/security_test_client.py +72 -0
  209. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  210. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  211. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  212. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  213. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  214. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  215. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  216. mcp_proxy_adapter/examples/setup_test_environment.py +235 -0
  217. mcp_proxy_adapter/examples/simple_protocol_test.py +125 -0
  218. mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
  219. mcp_proxy_adapter/examples/test_config.py +205 -0
  220. mcp_proxy_adapter/examples/test_config_builder.py +110 -0
  221. mcp_proxy_adapter/examples/test_examples.py +308 -0
  222. mcp_proxy_adapter/examples/test_framework_complete.py +267 -0
  223. mcp_proxy_adapter/examples/test_mcp_server.py +187 -0
  224. mcp_proxy_adapter/examples/test_protocol_examples.py +337 -0
  225. mcp_proxy_adapter/examples/universal_client.py +674 -0
  226. mcp_proxy_adapter/examples/update_config_certificates.py +135 -0
  227. mcp_proxy_adapter/examples/validate_generator_compatibility.py +385 -0
  228. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +61 -0
  229. mcp_proxy_adapter/integrations/__init__.py +25 -0
  230. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  231. mcp_proxy_adapter/main.py +311 -0
  232. mcp_proxy_adapter/openapi.py +375 -0
  233. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  234. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  235. mcp_proxy_adapter/schemas/roles.json +37 -0
  236. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  237. mcp_proxy_adapter/version.py +5 -0
  238. mcp_proxy_adapter-6.9.50.dist-info/METADATA +1088 -0
  239. mcp_proxy_adapter-6.9.50.dist-info/RECORD +242 -0
  240. {mcp_proxy_adapter-2.0.1.dist-info โ†’ mcp_proxy_adapter-6.9.50.dist-info}/WHEEL +1 -1
  241. mcp_proxy_adapter-6.9.50.dist-info/entry_points.txt +14 -0
  242. mcp_proxy_adapter-6.9.50.dist-info/top_level.txt +1 -0
  243. adapters/__init__.py +0 -16
  244. analyzers/__init__.py +0 -14
  245. analyzers/docstring_analyzer.py +0 -199
  246. analyzers/type_analyzer.py +0 -151
  247. cli/__init__.py +0 -12
  248. cli/__main__.py +0 -79
  249. cli/command_runner.py +0 -233
  250. dispatchers/__init__.py +0 -14
  251. dispatchers/base_dispatcher.py +0 -85
  252. dispatchers/json_rpc_dispatcher.py +0 -198
  253. generators/__init__.py +0 -14
  254. generators/endpoint_generator.py +0 -172
  255. generators/openapi_generator.py +0 -254
  256. generators/rest_api_generator.py +0 -207
  257. mcp_proxy_adapter-2.0.1.dist-info/METADATA +0 -272
  258. mcp_proxy_adapter-2.0.1.dist-info/RECORD +0 -28
  259. mcp_proxy_adapter-2.0.1.dist-info/licenses/LICENSE +0 -21
  260. mcp_proxy_adapter-2.0.1.dist-info/top_level.txt +0 -7
  261. openapi_schema/__init__.py +0 -38
  262. openapi_schema/command_registry.py +0 -312
  263. openapi_schema/rest_schema.py +0 -510
  264. openapi_schema/rpc_generator.py +0 -307
  265. openapi_schema/rpc_schema.py +0 -416
  266. validators/__init__.py +0 -14
  267. validators/base_validator.py +0 -23
  268. validators/docstring_validator.py +0 -75
  269. validators/metadata_validator.py +0 -76
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Author: Vasiliy Zdanovskiy
4
+ email: vasilyvz@gmail.com
5
+
6
+ Lightweight local proxy server for MCP Proxy Adapter examples.
7
+
8
+ This server provides proxy registration endpoints at /proxy for adapter instances
9
+ to register/unregister/heartbeat and for simple discovery.
10
+ """
11
+
12
+ import argparse
13
+ import asyncio
14
+ import signal
15
+ import sys
16
+ from typing import Dict, List, Optional
17
+
18
+ from fastapi import FastAPI, HTTPException
19
+ from pydantic import BaseModel
20
+
21
+ # Simple in-memory storage for registered adapters
22
+ registered_adapters: Dict[str, Dict] = {}
23
+
24
+
25
+ class AdapterRegistration(BaseModel):
26
+ server_id: Optional[str] = None # Preferred field name
27
+ server_url: Optional[str] = None # Preferred field name
28
+ name: Optional[str] = None # Legacy field name (for backward compatibility)
29
+ url: Optional[str] = None # Legacy field name (for backward compatibility)
30
+ capabilities: List[str] = []
31
+ metadata: Optional[Dict] = {}
32
+
33
+ def get_server_id(self) -> str:
34
+ """Get server ID from either server_id (preferred) or name (legacy)."""
35
+ return self.server_id or self.name or "unknown"
36
+
37
+ def get_server_url(self) -> str:
38
+ """Get server URL from either server_url (preferred) or url (legacy)."""
39
+ return self.server_url or self.url or ""
40
+
41
+
42
+ class ProxyRouter:
43
+ """Simple proxy router for MCP examples."""
44
+
45
+ def __init__(self):
46
+ self.app = FastAPI(title="MCP Local Proxy", version="1.0.0")
47
+ self._setup_routes()
48
+
49
+ def _setup_routes(self):
50
+ @self.app.post("/register")
51
+ def register(adapter: AdapterRegistration): # type: ignore[name-defined]
52
+ server_id = adapter.get_server_id()
53
+ server_url = adapter.get_server_url()
54
+ if not server_id or not server_url:
55
+ raise HTTPException(
56
+ status_code=400,
57
+ detail="server_id (or name) and server_url (or url) are required",
58
+ )
59
+ registered_adapters[server_id] = {
60
+ "server_id": server_id,
61
+ "server_url": server_url,
62
+ "capabilities": adapter.capabilities,
63
+ "metadata": adapter.metadata or {},
64
+ }
65
+ return {"status": "ok", "registered": server_id}
66
+
67
+ @self.app.post("/unregister")
68
+ def unregister(adapter: AdapterRegistration): # type: ignore[name-defined]
69
+ server_id = adapter.get_server_id()
70
+ registered_adapters.pop(server_id, None)
71
+ return {"status": "ok", "unregistered": server_id}
72
+
73
+ @self.app.post("/proxy/heartbeat")
74
+ def heartbeat(adapter: AdapterRegistration): # type: ignore[name-defined]
75
+ server_id = adapter.get_server_id()
76
+ if server_id in registered_adapters:
77
+ return {"status": "ok", "heartbeat": server_id}
78
+ raise HTTPException(status_code=404, detail="Adapter not registered")
79
+
80
+ @self.app.get("/proxy/list")
81
+ def list_registered():
82
+ return {"servers": list(registered_adapters.values())}
83
+
84
+ @self.app.get("/proxy/health")
85
+ def proxy_health():
86
+ return {"status": "ok", "model": "mcp-local-proxy", "version": "1.0.0"}
87
+
88
+ # Compatibility endpoint expected by test instructions
89
+ @self.app.get("/servers")
90
+ def servers_plain():
91
+ return list(registered_adapters.values())
92
+
93
+
94
+ def create_proxy_app() -> FastAPI:
95
+ """Create FastAPI app with proxy endpoints."""
96
+ router = ProxyRouter()
97
+ return router.app
98
+
99
+
100
+ def main() -> None:
101
+ parser = argparse.ArgumentParser(
102
+ description="Run local proxy server for MCP examples"
103
+ )
104
+ parser.add_argument(
105
+ "--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)"
106
+ )
107
+ parser.add_argument(
108
+ "--port", type=int, default=3004, help="Port to bind to (default: 3004)"
109
+ )
110
+ parser.add_argument(
111
+ "--log-level",
112
+ default="info",
113
+ choices=["debug", "info", "warning", "error"],
114
+ help="Log level",
115
+ )
116
+
117
+ args = parser.parse_args()
118
+
119
+ # Create FastAPI app
120
+ app = create_proxy_app()
121
+
122
+ # Setup graceful shutdown
123
+ def signal_handler(signum, frame): # type: ignore[no-redef]
124
+ print("\n๐Ÿ›‘ Proxy server stopping...")
125
+ sys.exit(0)
126
+
127
+ signal.signal(signal.SIGINT, signal_handler)
128
+ signal.signal(signal.SIGTERM, signal_handler)
129
+
130
+ print("๐Ÿš€ Starting MCP Local Proxy Server...")
131
+ print(f"๐Ÿ“ก Server URL: http://{args.host}:{args.port}")
132
+ print(f"๐Ÿ”— Proxy endpoints available at: http://{args.host}:{args.port}/proxy")
133
+ print("๐Ÿ“‹ Supported endpoints:")
134
+ print(" POST /proxy/register - Register adapter")
135
+ print(" POST /proxy/unregister - Unregister adapter")
136
+ print(" GET /proxy/list - List registered adapters")
137
+ print(" GET /proxy/health - Health check")
138
+ print(" POST /proxy/heartbeat - Heartbeat from adapter")
139
+ print("โšก Press Ctrl+C to stop\n")
140
+
141
+ # Run server with Hypercorn
142
+ from hypercorn.asyncio import serve
143
+ from hypercorn.config import Config
144
+
145
+ config = Config()
146
+ config.bind = [f"{args.host}:{args.port}"]
147
+ config.loglevel = args.log_level
148
+
149
+ asyncio.run(serve(app, config))
150
+
151
+
152
+ if __name__ == "__main__":
153
+ main()
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Security Testing Script - Fixed Version
4
+ This script runs comprehensive security tests without fallback mode
5
+ and with proper port management.
6
+ Author: Vasiliy Zdanovskiy
7
+ email: vasilyvz@gmail.com
8
+ """
9
+ import asyncio
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+ import time
15
+ from pathlib import Path
16
+
17
+ # Add project root to path
18
+ project_root = Path(__file__).parent.parent.parent
19
+ sys.path.insert(0, str(project_root))
20
+ from security_test_client import SecurityTestClient, TestResult
21
+
22
+
23
+ class SecurityTestRunner:
24
+ """Security test runner with proper port management."""
25
+
26
+ def __init__(self):
27
+ self.project_root = Path(__file__).parent.parent.parent
28
+ self.configs_dir = self.project_root / "configs"
29
+ self.server_processes = {}
30
+ self.test_results = []
31
+
32
+ def kill_process_on_port(self, port: int) -> bool:
33
+ """Kill process using specific port."""
34
+ try:
35
+ # Find process using the port
36
+ result = subprocess.run(
37
+ ["lsof", "-ti", f":{port}"], capture_output=True, text=True, timeout=5
38
+ )
39
+ if result.returncode == 0 and result.stdout.strip():
40
+ pid = result.stdout.strip()
41
+ # Kill the process
42
+ subprocess.run(["kill", "-9", pid], check=True)
43
+ print(f"โœ… Killed process {pid} on port {port}")
44
+ time.sleep(1) # Wait for port to be released
45
+ return True
46
+ else:
47
+ print(f"โ„น๏ธ No process found on port {port}")
48
+ return True
49
+ except subprocess.TimeoutExpired:
50
+ print(f"โš ๏ธ Timeout checking port {port}")
51
+ return False
52
+ except Exception as e:
53
+ print(f"โŒ Error killing process on port {port}: {e}")
54
+ return False
55
+
56
+ def start_server(
57
+ self, config_name: str, config_path: Path
58
+ ) -> Optional[subprocess.Popen]:
59
+ """Start server with proper error handling."""
60
+ try:
61
+ # Get port from config
62
+ with open(config_path) as f:
63
+ config = json.load(f)
64
+ port = config.get("server", {}).get("port", 8000)
65
+ # Kill any existing process on this port
66
+ self.kill_process_on_port(port)
67
+ # Start server
68
+ cmd = [
69
+ sys.executable,
70
+ "-m",
71
+ "mcp_proxy_adapter.main",
72
+ "--config",
73
+ str(config_path.absolute()), # Use absolute path to avoid path issues
74
+ ]
75
+ # Always start from examples directory where configs are located
76
+ cwd = self.project_root / "mcp_proxy_adapter" / "examples"
77
+ print(f"๐Ÿš€ Starting {config_name} on port {port}...")
78
+ process = subprocess.Popen(
79
+ cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
80
+ )
81
+ # Wait a bit for server to start
82
+ time.sleep(3)
83
+ # Check if process is still running
84
+ if process.poll() is None:
85
+ print(f"โœ… {config_name} started successfully on port {port}")
86
+ return process
87
+ else:
88
+ stdout, stderr = process.communicate()
89
+ print(f"โŒ {config_name} failed to start:")
90
+ print(f"STDOUT: {stdout}")
91
+ print(f"STDERR: {stderr}")
92
+ return None
93
+ except Exception as e:
94
+ print(f"โŒ Error starting {config_name}: {e}")
95
+ return None
96
+
97
+ def stop_server(self, config_name: str, process: subprocess.Popen):
98
+ """Stop server gracefully."""
99
+ try:
100
+ print(f"๐Ÿ›‘ Stopping {config_name}...")
101
+ process.terminate()
102
+ process.wait(timeout=5)
103
+ print(f"โœ… {config_name} stopped")
104
+ except subprocess.TimeoutExpired:
105
+ print(f"โš ๏ธ Force killing {config_name}...")
106
+ process.kill()
107
+ process.wait()
108
+ except Exception as e:
109
+ print(f"โŒ Error stopping {config_name}: {e}")
110
+
111
+ async def test_server(
112
+ self, config_name: str, config_path: Path
113
+ ) -> List[TestResult]:
114
+ """Test a single server configuration."""
115
+ results = []
116
+
117
+ # Get config for port number first
118
+ with open(config_path) as f:
119
+ config = json.load(f)
120
+ port = config.get("server", {}).get("port", 8000)
121
+
122
+ # Start server
123
+ process = self.start_server(config_name, config_path)
124
+ if not process:
125
+ return [
126
+ TestResult(
127
+ test_name=f"{config_name}_startup",
128
+ server_url=f"http://localhost:{port}",
129
+ auth_type="none",
130
+ success=False,
131
+ error_message="Server failed to start",
132
+ )
133
+ ]
134
+ try:
135
+ # Get remaining config for client setup
136
+ auth_enabled = config.get("security", {}).get("enabled", False)
137
+ # For new simplified structure, if security is enabled, we use token auth
138
+ auth_methods = ["api_key"] if auth_enabled else []
139
+ # Create test client with correct protocol
140
+ server_protocol = config.get("server", {}).get("protocol", "http")
141
+ protocol = "https" if server_protocol in ["https", "mtls"] else "http"
142
+ client = SecurityTestClient(base_url=f"{protocol}://localhost:{port}")
143
+ print(f"๐Ÿ” DEBUG: Created client with URL: {client.base_url}")
144
+ client.auth_enabled = auth_enabled
145
+ client.auth_methods = auth_methods
146
+ client.api_keys = config.get("security", {}).get("tokens", {})
147
+ client.roles_file = config.get("security", {}).get("roles_file")
148
+ client.roles = config.get("security", {}).get("roles", {})
149
+ # For mTLS, override SSL context creation and change working directory
150
+ if server_protocol == "mtls":
151
+ client.create_ssl_context = client.create_ssl_context_for_mtls
152
+ # Ensure mTLS uses certificate auth
153
+ client.auth_methods = ["certificate"]
154
+ # Change to examples directory for mTLS tests
155
+ import os
156
+
157
+ os.chdir(self.project_root / "mcp_proxy_adapter" / "examples")
158
+ # Run tests
159
+ async with client:
160
+ # Test 1: Health check
161
+ result = await client.test_health()
162
+ results.append(result)
163
+ # Test 2: Command execution
164
+ result = await client.test_command_execution()
165
+ results.append(result)
166
+ # Test 3: Authentication (if enabled)
167
+ if auth_enabled:
168
+ result = await client.test_authentication()
169
+ results.append(result)
170
+ # Test 4: Negative authentication
171
+ result = await client.test_negative_authentication()
172
+ results.append(result)
173
+ # Test 5: Role-based access
174
+ if "api_key" in auth_methods:
175
+ result = await client.test_role_based_access(
176
+ client.base_url, "api_key", role="admin"
177
+ )
178
+ else:
179
+ result = await client.test_role_based_access(
180
+ client.base_url, "certificate", role="admin"
181
+ )
182
+ results.append(result)
183
+ # Test 6: Role permissions
184
+ if "api_key" in auth_methods:
185
+ result = await client.test_role_permissions(
186
+ client.base_url, "api_key", role="admin", action="read"
187
+ )
188
+ else:
189
+ result = await client.test_role_permissions(
190
+ client.base_url, "certificate", role="admin", action="read"
191
+ )
192
+ results.append(result)
193
+ # Test 7: Multiple roles test
194
+ if "api_key" in auth_methods:
195
+ result = await client.test_multiple_roles(
196
+ client.base_url, "api_key"
197
+ )
198
+ else:
199
+ result = await client.test_multiple_roles(
200
+ client.base_url, "certificate"
201
+ )
202
+ results.append(result)
203
+ else:
204
+ # Test 3: No authentication required
205
+ result = await client.test_no_auth_required()
206
+ results.append(result)
207
+ # Test 4: Negative auth (should fail)
208
+ result = await client.test_negative_authentication()
209
+ results.append(result)
210
+ except Exception as e:
211
+ results.append(
212
+ TestResult(
213
+ test_name=f"{config_name}_client_error",
214
+ server_url=f"{protocol}://localhost:{port}",
215
+ auth_type="none",
216
+ success=False,
217
+ error_message=str(e),
218
+ )
219
+ )
220
+ finally:
221
+ # Stop server
222
+ self.stop_server(config_name, process)
223
+ return results
224
+
225
+ def create_variant_from_full_config(self, full_config_path: Path, protocol: str, auth: str, port: int) -> Path:
226
+ """
227
+ Create a variant configuration from full config.
228
+
229
+ Args:
230
+ full_config_path: Path to the full configuration file
231
+ protocol: Protocol type (http, https, mtls)
232
+ auth: Authentication type (none, token, token_roles)
233
+ port: Server port
234
+
235
+ Returns:
236
+ Path to the temporary configuration file
237
+ """
238
+ import tempfile
239
+ import json
240
+
241
+ # Load the full configuration
242
+ with open(full_config_path, 'r') as f:
243
+ full_config = json.load(f)
244
+
245
+ # Create a copy of the full config
246
+ variant_config = full_config.copy()
247
+
248
+ # Set server port and protocol
249
+ variant_config["server"]["port"] = port
250
+ variant_config["server"]["protocol"] = protocol
251
+
252
+ # Apply protocol configuration
253
+ if protocol in variant_config.get("protocol_variants", {}):
254
+ protocol_config = variant_config["protocol_variants"][protocol]
255
+ variant_config["server"].update(protocol_config["server"])
256
+
257
+ # Apply authentication configuration
258
+ if auth in variant_config.get("auth_variants", {}):
259
+ auth_config = variant_config["auth_variants"][auth]
260
+ variant_config["security"].update(auth_config["security"])
261
+
262
+ # Remove the helper sections
263
+ variant_config.pop("protocol_variants", None)
264
+ variant_config.pop("auth_variants", None)
265
+
266
+ # Create temporary config file
267
+ temp_dir = tempfile.mkdtemp(prefix="full_config_test_")
268
+ config_name = f"{protocol}_{auth}.json"
269
+ config_path = Path(temp_dir) / config_name
270
+
271
+ with open(config_path, 'w') as f:
272
+ json.dump(variant_config, f, indent=2, ensure_ascii=False)
273
+
274
+ return config_path
275
+
276
+ async def run_all_tests(self):
277
+ """Run all security tests."""
278
+ print("๐Ÿ”’ Starting Security Testing Suite")
279
+ print("=" * 50)
280
+ # Test configurations
281
+ configs = [
282
+ ("basic_http", "http.json"),
283
+ ("http_token", "http_token_roles.json"),
284
+ ("https", "https.json"),
285
+ ("https_token", "https_token_roles.json"),
286
+ ("mtls", "mtls.json"),
287
+ ]
288
+ total_tests = 0
289
+ passed_tests = 0
290
+ for config_name, config_file in configs:
291
+ config_path = self.configs_dir / config_file
292
+ if not config_path.exists():
293
+ print(f"โŒ Configuration not found: {config_path}")
294
+ continue
295
+ print(f"\n๐Ÿ“‹ Testing {config_name.upper()} configuration")
296
+ print("-" * 30)
297
+ results = await self.test_server(config_name, config_path)
298
+ for result in results:
299
+ total_tests += 1
300
+ if result.success:
301
+ passed_tests += 1
302
+ print(f"โœ… {result.test_name}: PASS")
303
+ else:
304
+ print(f"โŒ {result.test_name}: FAIL - {result.error_message}")
305
+ self.test_results.extend(results)
306
+ # Print summary
307
+ print("\n" + "=" * 50)
308
+ print("๐Ÿ“Š TEST SUMMARY")
309
+ print("=" * 50)
310
+ print(f"Total tests: {total_tests}")
311
+ print(f"Passed: {passed_tests}")
312
+ print(f"Failed: {total_tests - passed_tests}")
313
+ print(
314
+ f"Success rate: {(passed_tests/total_tests*100):.1f}%"
315
+ if total_tests > 0
316
+ else "N/A"
317
+ )
318
+ # Detailed results
319
+ print("\n๐Ÿ“‹ DETAILED RESULTS")
320
+ print("-" * 30)
321
+ for result in self.test_results:
322
+ status = "โœ… PASS" if result.success else "โŒ FAIL"
323
+ print(f"{status} {result.test_name}")
324
+ if not result.success and result.error_message:
325
+ print(f" Error: {result.error_message}")
326
+ return passed_tests == total_tests
327
+
328
+ async def run_full_config_tests(self, full_config_path: str):
329
+ """Run tests using full configuration with all variants."""
330
+ print("๐Ÿš€ Full Configuration Variants Testing")
331
+ print("=" * 60)
332
+ print(f"๐Ÿ“ Using full config: {full_config_path}")
333
+
334
+ full_config_file = Path(full_config_path)
335
+ if not full_config_file.exists():
336
+ print(f"โŒ Full configuration file not found: {full_config_path}")
337
+ return False
338
+
339
+ # Define all combinations to test
340
+ variants = [
341
+ # HTTP variants
342
+ ("http", "none", 20000),
343
+ ("http", "token", 20001),
344
+ ("http", "token_roles", 20002),
345
+
346
+ # HTTPS variants
347
+ ("https", "none", 20003),
348
+ ("https", "token", 20004),
349
+ ("https", "token_roles", 20005),
350
+
351
+ # mTLS variants
352
+ ("mtls", "none", 20006),
353
+ ("mtls", "token", 20007),
354
+ ("mtls", "token_roles", 20008),
355
+ ]
356
+
357
+ total_tests = 0
358
+ passed_tests = 0
359
+ all_results = []
360
+
361
+ for protocol, auth, port in variants:
362
+ print(f"\n{'='*60}")
363
+ print(f"๐Ÿงช Testing {protocol.upper()} with {auth.upper()} authentication")
364
+ print(f"{'='*60}")
365
+
366
+ # Create variant configuration
367
+ config_path = self.create_variant_from_full_config(full_config_file, protocol, auth, port)
368
+
369
+ # Test the variant
370
+ config_name = f"{protocol}_{auth}"
371
+ results = await self.test_server(config_name, config_path)
372
+
373
+ # Count results
374
+ for result in results:
375
+ total_tests += 1
376
+ if result.success:
377
+ passed_tests += 1
378
+ print(f"โœ… {result.test_name}: PASS")
379
+ else:
380
+ print(f"โŒ {result.test_name}: FAIL - {result.error_message}")
381
+
382
+ all_results.extend(results)
383
+
384
+ # Clean up temporary config
385
+ import shutil
386
+ shutil.rmtree(config_path.parent)
387
+
388
+ # Print final summary
389
+ print(f"\n{'='*60}")
390
+ print("๐Ÿ“Š FULL CONFIG TEST SUMMARY")
391
+ print(f"{'='*60}")
392
+ print(f"Total tests: {total_tests}")
393
+ print(f"Passed: {passed_tests}")
394
+ print(f"Failed: {total_tests - passed_tests}")
395
+ print(f"Success rate: {(passed_tests/total_tests)*100:.1f}%")
396
+
397
+ if total_tests - passed_tests > 0:
398
+ print(f"\nโŒ Failed tests:")
399
+ for result in all_results:
400
+ if not result.success:
401
+ print(f" โ€ข {result.test_name}: {result.error_message}")
402
+
403
+ return passed_tests == total_tests
404
+
405
+
406
+
407
+ async def main():
408
+ """Main function."""
409
+ import argparse
410
+
411
+ parser = argparse.ArgumentParser(description="Security Testing Suite for MCP Proxy Adapter")
412
+ parser.add_argument("--full-config", help="Path to full configuration file for variant testing")
413
+ parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
414
+
415
+ args = parser.parse_args()
416
+
417
+ runner = SecurityTestRunner()
418
+ try:
419
+ if args.full_config:
420
+ # Test full configuration variants
421
+ success = await runner.run_full_config_tests(args.full_config)
422
+ else:
423
+ # Run standard tests
424
+ success = await runner.run_all_tests()
425
+ sys.exit(0 if success else 1)
426
+ except KeyboardInterrupt:
427
+ print("\nโš ๏ธ Testing interrupted by user")
428
+ sys.exit(1)
429
+ except Exception as e:
430
+ print(f"\nโŒ Testing failed: {e}")
431
+ sys.exit(1)
432
+
433
+
434
+ if __name__ == "__main__":
435
+ asyncio.run(main())
@@ -0,0 +1,18 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Security test package for MCP Proxy Adapter.
6
+ """
7
+
8
+ from .test_result import TestResult
9
+ from .ssl_context_manager import SSLContextManager
10
+ from .auth_manager import AuthManager
11
+ from .test_client import SecurityTestClient
12
+
13
+ __all__ = [
14
+ "TestResult",
15
+ "SSLContextManager",
16
+ "AuthManager",
17
+ "SecurityTestClient",
18
+ ]
@@ -0,0 +1,14 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Authentication manager for security testing.
6
+ """
7
+
8
+ import base64
9
+ from typing import Dict
10
+
11
+
12
+ class AuthManager:
13
+ """Manager for authentication in security testing."""
14
+
@@ -0,0 +1,28 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ SSL context manager for security testing.
6
+ """
7
+
8
+ import os
9
+ import ssl
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+
14
+ class SSLContextManager:
15
+ """Manager for SSL contexts in security testing."""
16
+
17
+ def __init__(self, project_root: Optional[Path] = None):
18
+ """
19
+ Initialize SSL context manager.
20
+
21
+ Args:
22
+ project_root: Root directory of the project (optional)
23
+ """
24
+ if project_root is None:
25
+ project_root = Path(__file__).parent.parent.parent.parent
26
+ self.project_root = project_root
27
+
28
+