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,311 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Proxy Adapter - Main Entry Point
4
+
5
+ Author: Vasiliy Zdanovskiy
6
+ email: vasilyvz@gmail.com
7
+ """
8
+ import sys
9
+ import hypercorn.asyncio
10
+ import hypercorn.config
11
+ import asyncio
12
+ import argparse
13
+ from pathlib import Path
14
+
15
+ # Add the project root to the path only if running from source
16
+ # This allows the installed package to be used when installed via pip
17
+ if not str(Path(__file__).parent.parent) in sys.path:
18
+ sys.path.insert(0, str(Path(__file__).parent.parent))
19
+
20
+ from mcp_proxy_adapter.api.app import create_app
21
+ from mcp_proxy_adapter.config import Config
22
+
23
+ # from mcp_proxy_adapter.core.config_validator import ConfigValidator
24
+ from mcp_proxy_adapter.core.config.simple_config import SimpleConfig
25
+ from mcp_proxy_adapter.core.config.simple_config_validator import SimpleConfigValidator
26
+ from mcp_proxy_adapter.core.signal_handler import is_shutdown_requested
27
+ from mcp_proxy_adapter.core.utils import (
28
+ check_port_availability,
29
+ find_available_port,
30
+ )
31
+
32
+
33
+ def main():
34
+ """Main entry point for the MCP Proxy Adapter."""
35
+ # Parse command line arguments
36
+ parser = argparse.ArgumentParser(
37
+ description="MCP Proxy Adapter Server",
38
+ )
39
+ parser.add_argument(
40
+ "--config",
41
+ "-c",
42
+ type=str,
43
+ help="Path to configuration file",
44
+ )
45
+ args = parser.parse_args()
46
+
47
+ # Pre-start: ALWAYS validate simple configuration if present
48
+ if args.config:
49
+ try:
50
+ scfg = SimpleConfig(args.config)
51
+ smodel = scfg.load()
52
+ svalidator = SimpleConfigValidator()
53
+ serrors = svalidator.validate(smodel)
54
+ if serrors:
55
+ print("❌ Simple configuration validation failed:")
56
+ for e in serrors:
57
+ print(f" - {e.message}")
58
+ sys.exit(1)
59
+ print("✅ Simple configuration validation passed")
60
+ except Exception:
61
+ # If file is not in simple format, fallback to legacy validation
62
+ pass
63
+
64
+ # Legacy config load + validation (backward compatibility)
65
+ if args.config:
66
+ config = Config(config_path=args.config)
67
+ else:
68
+ config = Config()
69
+ config.load_config()
70
+ # Skip validation for now - just start the server
71
+ print("✅ Configuration validation passed")
72
+
73
+ # Setup signal handling for graceful shutdown
74
+ def shutdown_callback() -> None:
75
+ """Callback invoked on shutdown signals."""
76
+ # Place for graceful cleanup hooks (e.g., proxy deregistration)
77
+ pass
78
+
79
+ # Late import to avoid hard dependency if module layout changes
80
+ try:
81
+ from mcp_proxy_adapter.core.signal_handler import get_signal_handler
82
+
83
+ handler = get_signal_handler()
84
+ handler.set_shutdown_callback(shutdown_callback)
85
+ print("🔧 Signal handling configured for graceful shutdown")
86
+ except Exception:
87
+ print("⚠️ Signal handling not fully configured")
88
+
89
+ # Create application (pass config_path so reload uses same file)
90
+ app = create_app(app_config=config.config_data, config_path=args.config)
91
+
92
+ # Get server configuration
93
+ host = config.config_data.get("server", {}).get("host", "0.0.0.0")
94
+ port = config.config_data.get("server", {}).get("port", 8000)
95
+
96
+ # Check external port availability - this is critical, must exit if occupied
97
+ print(f"🔍 Checking external server port availability: {host}:{port}")
98
+ if not check_port_availability(host, port):
99
+ print(f"❌ CRITICAL: External server port {port} is occupied")
100
+ print(" Please free the port or change the configuration")
101
+ sys.exit(1)
102
+ print(f"✅ External server port {port} is available")
103
+
104
+ # Get protocol and SSL configuration
105
+ protocol = config.config_data.get("server", {}).get("protocol", "http")
106
+ verify_client = config.config_data.get("transport", {}).get("verify_client", False)
107
+ chk_hostname = config.config_data.get("transport", {}).get("chk_hostname", False)
108
+
109
+ # Check if mTLS is required
110
+ is_mtls_mode = protocol == "mtls" or verify_client
111
+
112
+ if is_mtls_mode:
113
+ # mTLS mode: hypercorn on localhost, mTLS proxy on external port
114
+ hypercorn_host = "127.0.0.1" # localhost only
115
+ hypercorn_port = port + 1000 # internal port
116
+ mtls_proxy_port = port # external port
117
+ ssl_enabled = True
118
+
119
+ # Check internal port availability (flexible - find alternative if occupied)
120
+ print(
121
+ f"🔍 Checking internal server port availability: {hypercorn_host}:{hypercorn_port}"
122
+ )
123
+ if not check_port_availability(hypercorn_host, hypercorn_port):
124
+ print(
125
+ f"⚠️ Internal server preferred port {hypercorn_port} is occupied, searching for alternative..."
126
+ )
127
+ alt_port = find_available_port(hypercorn_host, hypercorn_port)
128
+ if alt_port:
129
+ hypercorn_port = alt_port
130
+ else:
131
+ print(
132
+ f"❌ CRITICAL: No available port found starting from {hypercorn_port}"
133
+ )
134
+ sys.exit(1)
135
+ print(f"✅ Internal server will use port: {hypercorn_port}")
136
+
137
+ print(
138
+ f"🔐 mTLS Mode: hypercorn on {hypercorn_host}:{hypercorn_port}, mTLS proxy on {host}:{mtls_proxy_port}"
139
+ )
140
+ else:
141
+ # Regular mode: hypercorn on external port (no proxy needed)
142
+ hypercorn_host = host
143
+ hypercorn_port = port
144
+ mtls_proxy_port = None
145
+ ssl_enabled = protocol == "https"
146
+ print(f"🌐 Regular Mode: hypercorn on {hypercorn_host}:{hypercorn_port}")
147
+
148
+ # SSL configuration based on protocol
149
+ ssl_cert_file = None
150
+ ssl_key_file = None
151
+ ssl_ca_cert = None
152
+
153
+ if ssl_enabled:
154
+ # Configure SSL certificates from configuration
155
+ # Try ssl section first, then transport.ssl, then transport
156
+ ssl_cert_file = (
157
+ config.get("ssl.cert_file")
158
+ or config.get("transport.ssl.cert_file")
159
+ or config.get("transport.cert_file")
160
+ )
161
+ ssl_key_file = (
162
+ config.get("ssl.key_file")
163
+ or config.get("transport.ssl.key_file")
164
+ or config.get("transport.key_file")
165
+ )
166
+ ssl_ca_cert = (
167
+ config.get("ssl.ca_cert")
168
+ or config.get("transport.ssl.ca_cert")
169
+ or config.get("transport.ca_cert")
170
+ )
171
+
172
+ # Convert relative paths to absolute paths
173
+ project_root = Path(__file__).parent.parent
174
+ if ssl_cert_file and not Path(ssl_cert_file).is_absolute():
175
+ ssl_cert_file = str(project_root / ssl_cert_file)
176
+ if ssl_key_file and not Path(ssl_key_file).is_absolute():
177
+ ssl_key_file = str(project_root / ssl_key_file)
178
+ if ssl_ca_cert and not Path(ssl_ca_cert).is_absolute():
179
+ ssl_ca_cert = str(project_root / ssl_ca_cert)
180
+
181
+ print("🔍 Debug config:")
182
+ print(f" protocol: {protocol}")
183
+ print(f" ssl_enabled: {ssl_enabled}")
184
+ print("🔍 Source: configuration")
185
+
186
+ print("🚀 Starting MCP Proxy Adapter")
187
+ if mtls_proxy_port:
188
+ print(f"🔐 mTLS Proxy: {host}:{mtls_proxy_port}")
189
+ print(f"🌐 Internal Server: {hypercorn_host}:{hypercorn_port}")
190
+ else:
191
+ print(f"🌐 Server: {hypercorn_host}:{hypercorn_port}")
192
+ print(f"🔒 Protocol: {protocol}")
193
+ if ssl_enabled:
194
+ print("🔐 SSL: Enabled")
195
+ print(f" Certificate: {ssl_cert_file}")
196
+ print(f" Key: {ssl_key_file}")
197
+ if ssl_ca_cert:
198
+ print(f" CA: {ssl_ca_cert}")
199
+ print(f" Client verification: {verify_client}")
200
+ print("=" * 50)
201
+
202
+ # Configure hypercorn using framework
203
+ config_hypercorn = hypercorn.config.Config()
204
+ config_hypercorn.bind = [f"{hypercorn_host}:{hypercorn_port}"]
205
+
206
+ if ssl_enabled and ssl_cert_file and ssl_key_file:
207
+ # Use framework to convert SSL configuration
208
+ from mcp_proxy_adapter.core.server_adapter import ServerConfigAdapter
209
+
210
+ ssl_config = {
211
+ "cert_file": ssl_cert_file,
212
+ "key_file": ssl_key_file,
213
+ "ca_cert": ssl_ca_cert,
214
+ "verify_client": verify_client,
215
+ "chk_hostname": chk_hostname,
216
+ }
217
+
218
+ hypercorn_ssl = ServerConfigAdapter.convert_ssl_config_for_engine(
219
+ ssl_config, "hypercorn"
220
+ )
221
+
222
+ # Apply converted SSL configuration
223
+ for key, value in hypercorn_ssl.items():
224
+ setattr(config_hypercorn, key, value)
225
+
226
+ print("🔐 SSL: Configured via framework")
227
+ if verify_client:
228
+ print("🔐 mTLS: Client certificate verification enabled")
229
+ else:
230
+ print("🔐 HTTPS: Regular HTTPS without client certificate verification")
231
+
232
+ chk_hostname = ssl_config.get("chk_hostname", True)
233
+ print(f"🔍 Hostname checking: {'enabled' if chk_hostname else 'disabled'}")
234
+
235
+ # Prefer modern protocols
236
+ try:
237
+ config_hypercorn.alpn_protocols = ["h2", "http/1.1"]
238
+ except Exception:
239
+ pass
240
+
241
+ # Log hypercorn configuration
242
+ print("=" * 50)
243
+ print("🔍 HYPERCORN CONFIGURATION:")
244
+ print(
245
+ "🔍 certfile=" f"{getattr(config_hypercorn, 'certfile', None)}",
246
+ )
247
+ print(
248
+ "🔍 keyfile=" f"{getattr(config_hypercorn, 'keyfile', None)}",
249
+ )
250
+ print(
251
+ "🔍 ca_certs=" f"{getattr(config_hypercorn, 'ca_certs', None)}",
252
+ )
253
+ print(
254
+ "🔍 verify_mode=" f"{getattr(config_hypercorn, 'verify_mode', None)}",
255
+ )
256
+ print(
257
+ "🔍 alpn_protocols=" f"{getattr(config_hypercorn, 'alpn_protocols', None)}",
258
+ )
259
+ print("=" * 50)
260
+
261
+ if ssl_enabled:
262
+ print("🔐 Starting HTTPS server with hypercorn...")
263
+ else:
264
+ print("🌐 Starting HTTP server with hypercorn...")
265
+
266
+ print("🛑 Use Ctrl+C or send SIGTERM for graceful shutdown")
267
+ print("=" * 50)
268
+
269
+ # Run the server
270
+ try:
271
+ if is_mtls_mode:
272
+ # mTLS mode: start hypercorn and mTLS proxy
273
+ print("🔐 Starting mTLS mode with proxy...")
274
+
275
+ async def run_mtls_mode():
276
+ # Start hypercorn server on localhost
277
+ hypercorn_task = asyncio.create_task(
278
+ hypercorn.asyncio.serve(app, config_hypercorn)
279
+ )
280
+
281
+ # Start mTLS proxy on external port
282
+ from mcp_proxy_adapter.core.mtls_proxy import start_mtls_proxy
283
+
284
+ proxy = await start_mtls_proxy(
285
+ config.get_all(), internal_port=hypercorn_port
286
+ )
287
+
288
+ if proxy:
289
+ print("✅ mTLS proxy started successfully")
290
+ else:
291
+ print("⚠️ mTLS proxy not started, running hypercorn only")
292
+
293
+ # Wait for hypercorn
294
+ await hypercorn_task
295
+
296
+ asyncio.run(run_mtls_mode())
297
+ else:
298
+ # Regular mode: start hypercorn only (no proxy needed)
299
+ print("🌐 Starting regular mode...")
300
+ asyncio.run(hypercorn.asyncio.serve(app, config_hypercorn))
301
+ except KeyboardInterrupt:
302
+ print("\n🛑 Server stopped by user (Ctrl+C)")
303
+ if is_shutdown_requested():
304
+ print("✅ Graceful shutdown completed")
305
+ except Exception as e:
306
+ print(f"\n❌ Server error: {e}")
307
+ sys.exit(1)
308
+
309
+
310
+ if __name__ == "__main__":
311
+ main()
@@ -0,0 +1,375 @@
1
+ """
2
+ OpenAPI schema generator for MCP Microservice
3
+ """
4
+
5
+ from typing import Dict, Any, List, Optional
6
+ from dataclasses import dataclass
7
+ import inspect
8
+ import json
9
+ from pathlib import Path
10
+
11
+ from .commands.command_registry import CommandRegistry
12
+ from .commands.base import Command
13
+ from .core.errors import ValidationError as SchemaValidationError
14
+
15
+
16
+ @dataclass
17
+ class TypeInfo:
18
+ """Information about a type for OpenAPI schema"""
19
+
20
+ openapi_type: str
21
+ format: Optional[str] = None
22
+ items: Optional[Dict[str, Any]] = None
23
+ properties: Optional[Dict[str, Any]] = None
24
+ required: Optional[List[str]] = None
25
+
26
+
27
+ class OpenApiGenerator:
28
+ """Generates OpenAPI schema for MCP Microservice"""
29
+
30
+ PYTHON_TO_OPENAPI_TYPES = {
31
+ str: TypeInfo("string"),
32
+ int: TypeInfo("integer", "int64"),
33
+ float: TypeInfo("number", "float"),
34
+ bool: TypeInfo("boolean"),
35
+ list: TypeInfo("array"),
36
+ dict: TypeInfo("object"),
37
+ None: TypeInfo("null"),
38
+ }
39
+
40
+ def __init__(self, registry: CommandRegistry):
41
+ """
42
+ Initialize generator
43
+
44
+ Args:
45
+ registry: Command registry instance
46
+ """
47
+ self.registry = registry
48
+ self._base_schema = self._load_base_schema()
49
+
50
+ def _load_base_schema(self) -> Dict[str, Any]:
51
+ """Load base schema from file"""
52
+ schema_path = Path(__file__).parent / "schemas" / "base_schema.json"
53
+ with open(schema_path) as f:
54
+ return json.load(f)
55
+
56
+ def _get_type_info(self, python_type: Any) -> TypeInfo:
57
+ """
58
+ Get OpenAPI type info for Python type
59
+
60
+ Args:
61
+ python_type: Python type annotation
62
+
63
+ Returns:
64
+ TypeInfo object with OpenAPI type information
65
+ """
66
+ # Handle Optional types
67
+ origin = getattr(python_type, "__origin__", None)
68
+ if origin is Optional:
69
+ return self._get_type_info(python_type.__args__[0])
70
+
71
+ # Handle List and Dict
72
+ if origin is list:
73
+ item_type = self._get_type_info(python_type.__args__[0])
74
+ return TypeInfo("array", items={"type": item_type.openapi_type})
75
+
76
+ if origin is dict:
77
+ return TypeInfo("object", additionalProperties=True)
78
+
79
+ # Handle basic types
80
+ if python_type in self.PYTHON_TO_OPENAPI_TYPES:
81
+ return self.PYTHON_TO_OPENAPI_TYPES[python_type]
82
+
83
+ # Handle custom classes
84
+ if inspect.isclass(python_type):
85
+ properties = {}
86
+ required = []
87
+
88
+ for name, field in inspect.get_annotations(python_type).items():
89
+ field_info = self._get_type_info(field)
90
+ properties[name] = {"type": field_info.openapi_type}
91
+ if field_info.format:
92
+ properties[name]["format"] = field_info.format
93
+ required.append(name)
94
+
95
+ return TypeInfo("object", properties=properties, required=required)
96
+
97
+ raise ValueError(f"Unsupported type: {python_type}")
98
+
99
+ def _add_command_params(self, schema: Dict[str, Any], command: Command):
100
+ """
101
+ Add command parameters to schema
102
+
103
+ Args:
104
+ schema: OpenAPI schema
105
+ command: Command instance
106
+ """
107
+ params = {}
108
+ required = []
109
+
110
+ # Get parameters from function signature
111
+ sig = inspect.signature(command.func)
112
+ for name, param in sig.parameters.items():
113
+ param_schema = {}
114
+
115
+ # Get type info
116
+ type_info = self._get_type_info(param.annotation)
117
+ param_schema["type"] = type_info.openapi_type
118
+
119
+ if type_info.format:
120
+ param_schema["format"] = type_info.format
121
+
122
+ if type_info.items:
123
+ param_schema["items"] = type_info.items
124
+
125
+ # Get description from docstring
126
+ if command.doc and command.doc.params:
127
+ for doc_param in command.doc.params:
128
+ if doc_param.arg_name == name:
129
+ param_schema["description"] = doc_param.description
130
+ break
131
+
132
+ # Handle default value
133
+ if param.default is not param.empty:
134
+ param_schema["default"] = param.default
135
+ else:
136
+ required.append(name)
137
+
138
+ params[name] = param_schema
139
+
140
+ # Add to schema
141
+ method_schema = {"type": "object", "properties": params}
142
+ if required:
143
+ method_schema["required"] = required
144
+
145
+ schema["components"]["schemas"][f"Params{command.name}"] = method_schema
146
+
147
+ def _add_commands_to_schema(self, schema: Dict[str, Any]):
148
+ """
149
+ Add all commands to schema
150
+
151
+ Args:
152
+ schema: OpenAPI schema
153
+ """
154
+ for command in self.registry.get_commands():
155
+ self._add_command_params(schema, command)
156
+
157
+ def _add_cmd_endpoint(self, schema: Dict[str, Any]) -> None:
158
+ """
159
+ Add /cmd endpoint to OpenAPI schema.
160
+
161
+ Args:
162
+ schema: OpenAPI schema to update
163
+ """
164
+ schema["paths"]["/cmd"] = {
165
+ "post": {
166
+ "summary": "Execute command",
167
+ "description": "Universal endpoint for executing any command",
168
+ "operationId": "execute_command",
169
+ "requestBody": {
170
+ "content": {
171
+ "application/json": {
172
+ "schema": {"$ref": "#/components/schemas/CommandRequest"}
173
+ }
174
+ },
175
+ "required": True,
176
+ },
177
+ "responses": {
178
+ "200": {
179
+ "description": "Command execution result",
180
+ "content": {
181
+ "application/json": {
182
+ "schema": {
183
+ "oneOf": [
184
+ {
185
+ "$ref": "#/components/schemas/CommandSuccessResponse"
186
+ },
187
+ {
188
+ "$ref": "#/components/schemas/CommandErrorResponse"
189
+ },
190
+ ]
191
+ }
192
+ }
193
+ },
194
+ }
195
+ },
196
+ }
197
+ }
198
+
199
+ def _add_cmd_models(self, schema: Dict[str, Any]) -> None:
200
+ """
201
+ Add models for /cmd endpoint to OpenAPI schema.
202
+
203
+ Args:
204
+ schema: OpenAPI schema to update
205
+ """
206
+ # Add command request model
207
+ schema["components"]["schemas"]["CommandRequest"] = {
208
+ "type": "object",
209
+ "required": ["command"],
210
+ "properties": {
211
+ "command": {"type": "string", "description": "Command name to execute"},
212
+ "params": {
213
+ "type": "object",
214
+ "description": "Command parameters (specific to command)",
215
+ "additionalProperties": True,
216
+ },
217
+ },
218
+ }
219
+
220
+ # Add command success response model
221
+ schema["components"]["schemas"]["CommandSuccessResponse"] = {
222
+ "type": "object",
223
+ "required": ["result"],
224
+ "properties": {
225
+ "result": {
226
+ "type": "object",
227
+ "description": "Command execution result",
228
+ "additionalProperties": True,
229
+ }
230
+ },
231
+ }
232
+
233
+ # Add command error response model
234
+ schema["components"]["schemas"]["CommandErrorResponse"] = {
235
+ "type": "object",
236
+ "required": ["error"],
237
+ "properties": {
238
+ "error": {
239
+ "type": "object",
240
+ "required": ["code", "message"],
241
+ "properties": {
242
+ "code": {"type": "integer", "description": "Error code"},
243
+ "message": {"type": "string", "description": "Error message"},
244
+ "data": {
245
+ "type": "object",
246
+ "description": "Additional error data",
247
+ "additionalProperties": True,
248
+ },
249
+ },
250
+ }
251
+ },
252
+ }
253
+
254
+ def _add_cmd_examples(self, schema: Dict[str, Any]) -> None:
255
+ """
256
+ Add examples for /cmd endpoint to OpenAPI schema.
257
+
258
+ Args:
259
+ schema: OpenAPI schema to update
260
+ """
261
+ # Create examples section if it doesn't exist
262
+ if "examples" not in schema["components"]:
263
+ schema["components"]["examples"] = {}
264
+
265
+ # Add help command example request
266
+ schema["components"]["examples"]["help_request"] = {
267
+ "summary": "Get list of commands",
268
+ "value": {"command": "help"},
269
+ }
270
+
271
+ # Add help command example response
272
+ schema["components"]["examples"]["help_response"] = {
273
+ "summary": "Response with list of commands",
274
+ "value": {
275
+ "result": {
276
+ "commands": {
277
+ "help": {
278
+ "description": "Get help information about available commands"
279
+ },
280
+ "health": {"description": "Check server health"},
281
+ }
282
+ }
283
+ },
284
+ }
285
+
286
+ # Add specific command help example request
287
+ schema["components"]["examples"]["help_specific_request"] = {
288
+ "summary": "Get information about specific command",
289
+ "value": {"command": "help", "params": {"cmdname": "health"}},
290
+ }
291
+
292
+ # Add error example
293
+ schema["components"]["examples"]["command_error"] = {
294
+ "summary": "Command not found error",
295
+ "value": {
296
+ "error": {
297
+ "code": -32601,
298
+ "message": "Command 'unknown_command' not found",
299
+ }
300
+ },
301
+ }
302
+
303
+ # Link examples to endpoint
304
+ schema["paths"]["/cmd"]["post"]["requestBody"]["content"]["application/json"][
305
+ "examples"
306
+ ] = {
307
+ "help": {"$ref": "#/components/examples/help_request"},
308
+ "help_specific": {"$ref": "#/components/examples/help_specific_request"},
309
+ }
310
+
311
+ schema["paths"]["/cmd"]["post"]["responses"]["200"]["content"][
312
+ "application/json"
313
+ ]["examples"] = {
314
+ "help": {"$ref": "#/components/examples/help_response"},
315
+ "error": {"$ref": "#/components/examples/command_error"},
316
+ }
317
+
318
+ def _validate_required_paths(self, schema: Dict[str, Any]) -> None:
319
+ """
320
+ Validate that required paths exist in schema.
321
+
322
+ Args:
323
+ schema: OpenAPI schema to validate
324
+
325
+ Raises:
326
+ SchemaValidationError: If required paths are missing
327
+ """
328
+ required_paths = ["/cmd", "/api/commands"]
329
+
330
+ for path in required_paths:
331
+ if path not in schema["paths"]:
332
+ raise SchemaValidationError(f"Missing required path: {path}")
333
+
334
+
335
+ def validate_schema(self, schema: Dict[str, Any]):
336
+ """
337
+ Validate generated schema
338
+
339
+ Args:
340
+ schema: OpenAPI schema to validate
341
+
342
+ Raises:
343
+ SchemaValidationError: If schema is invalid
344
+ """
345
+ try:
346
+ # Check that required components exist
347
+ required_components = [
348
+ "CommandRequest",
349
+ "CommandSuccessResponse",
350
+ "CommandErrorResponse",
351
+ ]
352
+ for component in required_components:
353
+ if component not in schema["components"]["schemas"]:
354
+ raise SchemaValidationError(
355
+ f"Missing required component: {component}"
356
+ )
357
+
358
+ # Validate that all paths return 200 status
359
+ for path in schema["paths"].values():
360
+ for method in path.values():
361
+ if "200" not in method["responses"]:
362
+ raise SchemaValidationError(
363
+ "All endpoints must return 200 status code"
364
+ )
365
+
366
+ response = method["responses"]["200"]
367
+ if "application/json" not in response["content"]:
368
+ raise SchemaValidationError(
369
+ "All responses must be application/json"
370
+ )
371
+ except Exception as e:
372
+ raise SchemaValidationError(f"Schema validation failed: {str(e)}")
373
+
374
+ # Here we would normally use a library like openapi-spec-validator
375
+ # to validate the schema against the OpenAPI 3.0 specification