mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.30__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 (212) hide show
  1. mcp_proxy_adapter/__init__.py +10 -0
  2. mcp_proxy_adapter/__main__.py +8 -21
  3. mcp_proxy_adapter/api/app.py +10 -913
  4. mcp_proxy_adapter/api/core/__init__.py +18 -0
  5. mcp_proxy_adapter/api/core/app_factory.py +243 -0
  6. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  7. mcp_proxy_adapter/api/core/registration_manager.py +166 -0
  8. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  9. mcp_proxy_adapter/api/handlers.py +78 -199
  10. mcp_proxy_adapter/api/middleware/__init__.py +1 -44
  11. mcp_proxy_adapter/api/middleware/base.py +0 -42
  12. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
  13. mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
  14. mcp_proxy_adapter/api/middleware/factory.py +0 -94
  15. mcp_proxy_adapter/api/middleware/logging.py +0 -112
  16. mcp_proxy_adapter/api/middleware/performance.py +0 -35
  17. mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
  18. mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
  19. mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
  20. mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
  21. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  22. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  23. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  24. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  25. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  26. mcp_proxy_adapter/api/schemas.py +0 -61
  27. mcp_proxy_adapter/api/tool_integration.py +0 -117
  28. mcp_proxy_adapter/api/tools.py +0 -46
  29. mcp_proxy_adapter/cli/__init__.py +12 -0
  30. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  31. mcp_proxy_adapter/cli/commands/client.py +100 -0
  32. mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
  33. mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
  34. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  35. mcp_proxy_adapter/cli/commands/server.py +174 -0
  36. mcp_proxy_adapter/cli/commands/sets.py +128 -0
  37. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  38. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  39. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  40. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  41. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  42. mcp_proxy_adapter/cli/main.py +63 -0
  43. mcp_proxy_adapter/cli/parser.py +324 -0
  44. mcp_proxy_adapter/cli/validators.py +231 -0
  45. mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
  46. mcp_proxy_adapter/client/proxy.py +45 -0
  47. mcp_proxy_adapter/commands/__init__.py +44 -28
  48. mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
  49. mcp_proxy_adapter/commands/base.py +19 -43
  50. mcp_proxy_adapter/commands/builtin_commands.py +0 -75
  51. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  52. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  53. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  54. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  55. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  56. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  57. mcp_proxy_adapter/commands/catalog_manager.py +58 -928
  58. mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
  59. mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
  60. mcp_proxy_adapter/commands/command_registry.py +172 -904
  61. mcp_proxy_adapter/commands/config_command.py +0 -28
  62. mcp_proxy_adapter/commands/dependency_container.py +1 -70
  63. mcp_proxy_adapter/commands/dependency_manager.py +0 -128
  64. mcp_proxy_adapter/commands/echo_command.py +0 -34
  65. mcp_proxy_adapter/commands/health_command.py +0 -3
  66. mcp_proxy_adapter/commands/help_command.py +0 -159
  67. mcp_proxy_adapter/commands/hooks.py +0 -137
  68. mcp_proxy_adapter/commands/key_management_command.py +0 -25
  69. mcp_proxy_adapter/commands/load_command.py +7 -78
  70. mcp_proxy_adapter/commands/plugins_command.py +0 -16
  71. mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
  72. mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
  73. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  74. mcp_proxy_adapter/commands/registration_status_command.py +0 -43
  75. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  76. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  77. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  78. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  79. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  80. mcp_proxy_adapter/commands/reload_command.py +0 -80
  81. mcp_proxy_adapter/commands/result.py +25 -77
  82. mcp_proxy_adapter/commands/role_test_command.py +0 -44
  83. mcp_proxy_adapter/commands/roles_management_command.py +0 -199
  84. mcp_proxy_adapter/commands/security_command.py +0 -30
  85. mcp_proxy_adapter/commands/settings_command.py +0 -68
  86. mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
  87. mcp_proxy_adapter/commands/token_management_command.py +0 -1
  88. mcp_proxy_adapter/commands/transport_management_command.py +0 -20
  89. mcp_proxy_adapter/commands/unload_command.py +0 -71
  90. mcp_proxy_adapter/config.py +15 -626
  91. mcp_proxy_adapter/core/__init__.py +5 -39
  92. mcp_proxy_adapter/core/app_factory.py +14 -36
  93. mcp_proxy_adapter/core/app_runner.py +0 -27
  94. mcp_proxy_adapter/core/auth_validator.py +1 -93
  95. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  96. mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
  97. mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
  98. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  99. mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
  100. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
  101. mcp_proxy_adapter/core/certificate_utils.py +64 -903
  102. mcp_proxy_adapter/core/client.py +10 -9
  103. mcp_proxy_adapter/core/client_manager.py +0 -19
  104. mcp_proxy_adapter/core/client_security.py +0 -2
  105. mcp_proxy_adapter/core/config/__init__.py +18 -0
  106. mcp_proxy_adapter/core/config/config.py +195 -0
  107. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  108. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  109. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  110. mcp_proxy_adapter/core/config/simple_config.py +112 -0
  111. mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
  112. mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
  113. mcp_proxy_adapter/core/config_converter.py +0 -186
  114. mcp_proxy_adapter/core/config_validator.py +96 -1238
  115. mcp_proxy_adapter/core/errors.py +7 -42
  116. mcp_proxy_adapter/core/job_manager.py +54 -0
  117. mcp_proxy_adapter/core/logging.py +2 -22
  118. mcp_proxy_adapter/core/mtls_asgi.py +0 -20
  119. mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
  120. mcp_proxy_adapter/core/mtls_proxy.py +0 -80
  121. mcp_proxy_adapter/core/mtls_server.py +3 -173
  122. mcp_proxy_adapter/core/protocol_manager.py +1 -191
  123. mcp_proxy_adapter/core/proxy/__init__.py +22 -0
  124. mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
  125. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
  126. mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
  127. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  128. mcp_proxy_adapter/core/proxy_client.py +0 -1
  129. mcp_proxy_adapter/core/proxy_registration.py +36 -913
  130. mcp_proxy_adapter/core/role_utils.py +0 -308
  131. mcp_proxy_adapter/core/security_adapter.py +1 -36
  132. mcp_proxy_adapter/core/security_factory.py +1 -150
  133. mcp_proxy_adapter/core/security_integration.py +0 -33
  134. mcp_proxy_adapter/core/server_adapter.py +1 -40
  135. mcp_proxy_adapter/core/server_engine.py +2 -173
  136. mcp_proxy_adapter/core/settings.py +0 -127
  137. mcp_proxy_adapter/core/signal_handler.py +0 -65
  138. mcp_proxy_adapter/core/ssl_utils.py +19 -137
  139. mcp_proxy_adapter/core/transport_manager.py +0 -151
  140. mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
  141. mcp_proxy_adapter/core/utils.py +1 -182
  142. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  143. mcp_proxy_adapter/core/validation/config_validator.py +211 -0
  144. mcp_proxy_adapter/core/validation/file_validator.py +73 -0
  145. mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
  146. mcp_proxy_adapter/core/validation/security_validator.py +58 -0
  147. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  148. mcp_proxy_adapter/custom_openapi.py +33 -652
  149. mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
  150. mcp_proxy_adapter/examples/check_config.py +0 -2
  151. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  152. mcp_proxy_adapter/examples/config_builder.py +13 -2
  153. mcp_proxy_adapter/examples/config_cli.py +0 -1
  154. mcp_proxy_adapter/examples/create_test_configs.py +0 -46
  155. mcp_proxy_adapter/examples/debug_request_state.py +0 -1
  156. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
  157. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
  158. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
  159. mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
  160. mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
  161. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
  162. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
  163. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
  164. mcp_proxy_adapter/examples/full_application/main.py +186 -150
  165. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
  166. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
  167. mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
  168. mcp_proxy_adapter/examples/generate_config.py +65 -11
  169. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  170. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  171. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  172. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  173. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  174. mcp_proxy_adapter/examples/required_certificates.py +0 -2
  175. mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
  176. mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
  177. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
  178. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  179. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  180. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  181. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  182. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  183. mcp_proxy_adapter/examples/security_test_client.py +24 -1075
  184. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  185. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  186. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  187. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  188. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  189. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  190. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  191. mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
  192. mcp_proxy_adapter/examples/test_config.py +0 -3
  193. mcp_proxy_adapter/examples/test_config_builder.py +25 -405
  194. mcp_proxy_adapter/examples/test_examples.py +0 -1
  195. mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
  196. mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
  197. mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
  198. mcp_proxy_adapter/examples/universal_client.py +0 -6
  199. mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
  200. mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
  201. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
  202. mcp_proxy_adapter/integrations/__init__.py +25 -0
  203. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  204. mcp_proxy_adapter/main.py +70 -62
  205. mcp_proxy_adapter/openapi.py +0 -22
  206. mcp_proxy_adapter/version.py +1 -1
  207. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/METADATA +2 -1
  208. mcp_proxy_adapter-6.9.30.dist-info/RECORD +235 -0
  209. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/entry_points.txt +1 -1
  210. mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
  211. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,18 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Core API utilities for MCP Proxy Adapter.
6
+ """
7
+
8
+ from .app_factory import AppFactory
9
+ from .ssl_context_factory import SSLContextFactory
10
+ from .registration_manager import RegistrationManager
11
+ from .lifespan_manager import LifespanManager
12
+
13
+ __all__ = [
14
+ "AppFactory",
15
+ "SSLContextFactory",
16
+ "RegistrationManager",
17
+ "LifespanManager",
18
+ ]
@@ -0,0 +1,243 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Application factory for MCP Proxy Adapter API.
6
+ """
7
+
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Any, Dict, Optional
11
+
12
+ from fastapi import FastAPI, Body
13
+ from typing import List, Union
14
+ from mcp_proxy_adapter.api.handlers import (
15
+ execute_command,
16
+ handle_json_rpc,
17
+ handle_batch_json_rpc,
18
+ get_server_health,
19
+ get_commands_list,
20
+ )
21
+
22
+ # from mcp_proxy_adapter.api.middleware import setup_middleware
23
+ try:
24
+ from mcp_proxy_adapter.api.schemas import (
25
+ JsonRpcRequest,
26
+ JsonRpcSuccessResponse,
27
+ JsonRpcErrorResponse,
28
+ HealthResponse,
29
+ CommandListResponse,
30
+ APIToolDescription,
31
+ )
32
+ except Exception:
33
+ # If schemas are unavailable, define minimal type aliases to satisfy annotations
34
+ JsonRpcRequest = Dict[str, Any] # type: ignore
35
+ JsonRpcSuccessResponse = Dict[str, Any] # type: ignore
36
+ JsonRpcErrorResponse = Dict[str, Any] # type: ignore
37
+ HealthResponse = Dict[str, Any] # type: ignore
38
+ CommandListResponse = Dict[str, Any] # type: ignore
39
+ APIToolDescription = Dict[str, Any] # type: ignore
40
+
41
+ try:
42
+ from mcp_proxy_adapter.api.tools import get_tool_description, execute_tool
43
+ except Exception:
44
+ get_tool_description = None
45
+ execute_tool = None
46
+ from mcp_proxy_adapter.core.logging import get_global_logger
47
+ from mcp_proxy_adapter.commands.command_registry import registry
48
+ from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
49
+ from .ssl_context_factory import SSLContextFactory
50
+ from .lifespan_manager import LifespanManager
51
+
52
+
53
+ class AppFactory:
54
+ """Factory for creating FastAPI applications."""
55
+
56
+ def __init__(self):
57
+ """Initialize app factory."""
58
+ self.logger = get_global_logger()
59
+ self.ssl_factory = SSLContextFactory()
60
+ self.lifespan_manager = LifespanManager()
61
+
62
+ def create_app(
63
+ self,
64
+ title: Optional[str] = None,
65
+ description: Optional[str] = None,
66
+ version: Optional[str] = None,
67
+ app_config: Optional[Dict[str, Any]] = None,
68
+ config_path: Optional[str] = None,
69
+ ) -> FastAPI:
70
+ """
71
+ Creates and configures FastAPI application.
72
+
73
+ Args:
74
+ title: Application title (default: "MCP Proxy Adapter")
75
+ description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
76
+ version: Application version (default: "1.0.0")
77
+ app_config: Application configuration dictionary (optional)
78
+ config_path: Path to configuration file (optional)
79
+
80
+ Returns:
81
+ Configured FastAPI application.
82
+
83
+ Raises:
84
+ SystemExit: If authentication is enabled but required files are missing (security issue)
85
+ """
86
+ # Use provided configuration or fallback to global config
87
+ if app_config is not None:
88
+ if hasattr(app_config, "get_all"):
89
+ current_config = app_config.get_all()
90
+ elif hasattr(app_config, "keys"):
91
+ current_config = app_config
92
+ else:
93
+ # If app_config is not a dict-like object, use it as is
94
+ current_config = app_config
95
+ else:
96
+ # If no app_config provided, try to get global config
97
+ try:
98
+ from mcp_proxy_adapter.config import get_config
99
+ current_config = get_config().get_all()
100
+ except Exception:
101
+ # If global config is not available, create empty config
102
+ current_config = {}
103
+
104
+ # Debug: Check what config is passed to create_app
105
+ if app_config:
106
+ if hasattr(app_config, "keys"):
107
+ print(
108
+ f"🔍 Debug: create_app received app_config keys: {list(app_config.keys())}"
109
+ )
110
+ # Debug SSL configuration
111
+ protocol = app_config.get("server", {}).get("protocol", "http")
112
+ verify_client = app_config.get("transport", {}).get("verify_client", False)
113
+ ssl_enabled = protocol in ["https", "mtls"] or verify_client
114
+ print(f"🔍 Debug: create_app SSL config: enabled={ssl_enabled}")
115
+ print(f"🔍 Debug: create_app protocol: {protocol}")
116
+ print(f"🔍 Debug: create_app verify_client: {verify_client}")
117
+ else:
118
+ print(f"🔍 Debug: create_app received app_config type: {type(app_config)}")
119
+ else:
120
+ print("🔍 Debug: create_app received no app_config, using global config")
121
+
122
+ # Security check: Validate configuration strictly at startup (fail-fast)
123
+ self._validate_configuration(current_config)
124
+
125
+ # Security check: Validate all authentication configurations before startup
126
+ self._validate_security_configuration(current_config)
127
+
128
+ # Set default values
129
+ title = title or "MCP Proxy Adapter"
130
+ description = description or "JSON-RPC API for interacting with MCP Proxy"
131
+ version = version or "1.0.0"
132
+
133
+ # Create lifespan manager
134
+ lifespan = self.lifespan_manager.create_lifespan(config_path, current_config)
135
+
136
+ # Create FastAPI application
137
+ app = FastAPI(
138
+ title=title,
139
+ description=description,
140
+ version=version,
141
+ lifespan=lifespan,
142
+ )
143
+
144
+ # Setup middleware - disabled for now
145
+ # setup_middleware(app, current_config)
146
+
147
+ # Setup routes
148
+ self._setup_routes(app)
149
+
150
+ # Setup OpenAPI
151
+ app.openapi = lambda: custom_openapi_with_fallback(app)
152
+
153
+ return app
154
+
155
+ def _validate_configuration(self, current_config: Dict[str, Any]) -> None:
156
+ """Validate configuration at startup."""
157
+ try:
158
+ from mcp_proxy_adapter.core.config_validator import ConfigValidator
159
+
160
+ validator = ConfigValidator()
161
+ validator.config_data = current_config
162
+ validation_results = validator.validate_config()
163
+ errors = [r for r in validation_results if r.level == "error"]
164
+ warnings = [r for r in validation_results if r.level == "warning"]
165
+
166
+ if errors:
167
+ self.logger.critical("CRITICAL CONFIG ERROR: Invalid configuration at startup:")
168
+ for error in errors:
169
+ self.logger.critical(f" - {error.message}")
170
+ raise SystemExit(1)
171
+ for warning in warnings:
172
+ self.logger.warning(f"Config warning: {warning.message}")
173
+ except Exception as ex:
174
+ self.logger.error(f"Failed to run startup configuration validation: {ex}")
175
+
176
+ def _validate_security_configuration(self, current_config: Dict[str, Any]) -> None:
177
+ """Validate security configuration at startup."""
178
+ security_errors = []
179
+
180
+ print(f"🔍 Debug: current_config keys: {list(current_config.keys())}")
181
+ if "security" in current_config:
182
+ print(f"🔍 Debug: security config: {current_config['security']}")
183
+ if "roles" in current_config:
184
+ print(f"🔍 Debug: roles config: {current_config['roles']}")
185
+
186
+ # Check security framework configuration only if enabled
187
+ security_config = current_config.get("security", {})
188
+ if security_config.get("enabled", False):
189
+ # Validate security framework configuration
190
+ from mcp_proxy_adapter.core.unified_config_adapter import UnifiedConfigAdapter
191
+
192
+ adapter = UnifiedConfigAdapter()
193
+ validation_result = adapter.validate_configuration(current_config)
194
+
195
+ if not validation_result.is_valid:
196
+ security_errors.extend(validation_result.errors)
197
+
198
+ # Check roles configuration only if enabled
199
+ roles_config = current_config.get("roles", {})
200
+ if roles_config.get("enabled", False):
201
+ # Validate roles configuration
202
+ from mcp_proxy_adapter.core.role_utils import RoleUtils
203
+
204
+ role_utils = RoleUtils()
205
+ validation_result = role_utils.validate_configuration(current_config)
206
+
207
+ if not validation_result.is_valid:
208
+ security_errors.extend(validation_result.errors)
209
+
210
+ # Fail if there are security errors
211
+ if security_errors:
212
+ self.logger.critical("CRITICAL SECURITY ERROR: Invalid security configuration at startup:")
213
+ for error in security_errors:
214
+ self.logger.critical(f" - {error}")
215
+ raise SystemExit(1)
216
+
217
+ def _setup_routes(self, app: FastAPI) -> None:
218
+ """Setup application routes."""
219
+ @app.get("/health", response_model=HealthResponse)
220
+ async def health(): # type: ignore
221
+ return await get_server_health() # type: ignore[misc]
222
+
223
+ @app.get("/commands", response_model=CommandListResponse)
224
+ async def commands(): # type: ignore
225
+ return await get_commands_list() # type: ignore[misc]
226
+
227
+ @app.post("/api/jsonrpc", response_model=Union[JsonRpcSuccessResponse, JsonRpcErrorResponse])
228
+ async def jsonrpc(request: JsonRpcRequest): # type: ignore
229
+ return await handle_json_rpc(request.dict()) # type: ignore[misc]
230
+
231
+ @app.post("/api/jsonrpc/batch", response_model=List[Union[JsonRpcSuccessResponse, JsonRpcErrorResponse]])
232
+ async def jsonrpc_batch(requests: List[JsonRpcRequest]): # type: ignore
233
+ return await handle_batch_json_rpc([req.dict() for req in requests]) # type: ignore[misc]
234
+
235
+ # Optional tool endpoints if tools module is available
236
+ if get_tool_description and execute_tool:
237
+ @app.get("/api/tools", response_model=List[APIToolDescription])
238
+ async def tools(): # type: ignore
239
+ return await get_tool_description() # type: ignore[misc]
240
+
241
+ @app.post("/api/tools/{tool_name}")
242
+ async def execute_tool_endpoint(tool_name: str, params: Dict[str, Any] = Body(...)):
243
+ return await execute_tool(tool_name, params) # type: ignore[misc]
@@ -0,0 +1,55 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Lifespan management utilities for MCP Proxy Adapter API.
6
+ """
7
+
8
+ import asyncio
9
+ from contextlib import asynccontextmanager
10
+ from typing import Any, Dict, Optional
11
+
12
+ from fastapi import FastAPI
13
+
14
+ from mcp_proxy_adapter.core.logging import get_global_logger
15
+ from .registration_manager import RegistrationManager
16
+
17
+
18
+ class LifespanManager:
19
+ """Manager for application lifespan events."""
20
+
21
+ def __init__(self):
22
+ """Initialize lifespan manager."""
23
+ self.logger = get_global_logger()
24
+ self.registration_manager = RegistrationManager()
25
+
26
+ def create_lifespan(self, config_path: Optional[str] = None, current_config: Optional[Dict[str, Any]] = None):
27
+ """
28
+ Create lifespan manager for the FastAPI application.
29
+
30
+ Args:
31
+ config_path: Path to configuration file (optional)
32
+ current_config: Current configuration data (optional)
33
+
34
+ Returns:
35
+ Lifespan context manager
36
+ """
37
+
38
+ @asynccontextmanager
39
+ async def lifespan(app: FastAPI):
40
+ """Lifespan context manager."""
41
+ # Startup
42
+ get_global_logger().info("Starting MCP Proxy Adapter")
43
+
44
+ # Register with proxy if configured
45
+ if current_config:
46
+ await self.registration_manager.register_with_proxy(current_config)
47
+ await self.registration_manager.start_heartbeat(current_config)
48
+
49
+ yield
50
+
51
+ # Shutdown
52
+ get_global_logger().info("Shutting down MCP Proxy Adapter")
53
+ await self.registration_manager.stop()
54
+
55
+ return lifespan
@@ -0,0 +1,166 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Registration management utilities for MCP Proxy Adapter API.
6
+ """
7
+
8
+ import os
9
+ import socket
10
+ import asyncio
11
+ from typing import Dict, Any, Optional
12
+
13
+ from mcp_proxy_adapter.core.logging import get_global_logger
14
+ from mcp_proxy_adapter.client.jsonrpc_client import JsonRpcClient
15
+
16
+
17
+ class RegistrationManager:
18
+ """Manager for proxy registration functionality using JsonRpcClient."""
19
+
20
+ def __init__(self):
21
+ """Initialize registration manager."""
22
+ self.logger = get_global_logger()
23
+ self.registered = False
24
+ self.registration_task: Optional[asyncio.Task] = None
25
+ self.server_name: Optional[str] = None
26
+ self.server_url: Optional[str] = None
27
+ self.proxy_url: Optional[str] = None
28
+ self.capabilities: list = []
29
+ self.metadata: Dict[str, Any] = {}
30
+
31
+ async def register_with_proxy(self, config: Dict[str, Any]) -> bool:
32
+ """Register this server with the proxy using JsonRpcClient."""
33
+ try:
34
+ proxy_config = config.get("proxy_registration", {})
35
+ if not proxy_config.get("enabled", False):
36
+ self.logger.info("Proxy registration disabled")
37
+ return True
38
+
39
+ proxy_url = proxy_config.get("proxy_url") or proxy_config.get("server_url")
40
+ if not proxy_url:
41
+ self.logger.warning("No proxy server URL configured")
42
+ return False
43
+
44
+ # Get server info
45
+ server_config = config.get("server", {})
46
+ host = server_config.get("host", "127.0.0.1")
47
+ port = server_config.get("port", 8000)
48
+ protocol = server_config.get("protocol", "http")
49
+
50
+ # Use advertised host if available
51
+ advertised_host = server_config.get("advertised_host") or host
52
+ scheme = "https" if protocol in ("https", "mtls") else "http"
53
+ advertised_url = f"{scheme}://{advertised_host}:{port}"
54
+
55
+ # Get server name from config or generate default
56
+ server_name = (
57
+ proxy_config.get("server_id")
58
+ or proxy_config.get("server_name")
59
+ or f"mcp-adapter-{host}-{port}"
60
+ )
61
+
62
+ self.server_name = server_name
63
+ self.server_url = advertised_url
64
+ self.proxy_url = proxy_url
65
+
66
+ # Get capabilities and metadata
67
+ self.capabilities = proxy_config.get("capabilities", ["jsonrpc", "health"])
68
+ self.metadata = {
69
+ "uuid": config.get("uuid"),
70
+ "protocol": protocol,
71
+ "host": host,
72
+ "port": port,
73
+ **(proxy_config.get("metadata") or {}),
74
+ }
75
+
76
+ # Use JsonRpcClient for registration (run in executor as it's synchronous)
77
+ loop = asyncio.get_event_loop()
78
+ client = JsonRpcClient(protocol="http", host="127.0.0.1", port=8080) # Dummy, just for methods
79
+
80
+ # Register synchronously in executor
81
+ def _register():
82
+ return client.register_with_proxy(
83
+ proxy_url=proxy_url,
84
+ server_name=server_name,
85
+ server_url=advertised_url,
86
+ capabilities=self.capabilities,
87
+ metadata=self.metadata,
88
+ )
89
+
90
+ try:
91
+ result = await loop.run_in_executor(None, _register)
92
+ self.logger.info(f"✅ Successfully registered with proxy as {server_name} -> {advertised_url}")
93
+ self.registered = True
94
+ return True
95
+ except Exception as exc:
96
+ self.logger.error(f"❌ Failed to register with proxy: {exc}")
97
+ return False
98
+
99
+ except Exception as e:
100
+ self.logger.error(f"❌ Registration error: {e}")
101
+ return False
102
+
103
+ async def start_heartbeat(self, config: Dict[str, Any]) -> None:
104
+ """Start heartbeat task using JsonRpcClient."""
105
+ if not self.registered or not self.proxy_url or not self.server_name or not self.server_url:
106
+ return
107
+
108
+ proxy_config = config.get("proxy_registration", {})
109
+ heartbeat_config = proxy_config.get("heartbeat", {}) or {}
110
+ heartbeat_interval = heartbeat_config.get("interval", proxy_config.get("heartbeat_interval", 30))
111
+
112
+ self.logger.info(f"💓 Starting heartbeat task (interval: {heartbeat_interval}s)")
113
+
114
+ async def heartbeat_loop():
115
+ loop = asyncio.get_event_loop()
116
+ client = JsonRpcClient(protocol="http", host="127.0.0.1", port=8080) # Dummy, just for methods
117
+
118
+ def _heartbeat():
119
+ return client.heartbeat_to_proxy(
120
+ proxy_url=self.proxy_url,
121
+ server_name=self.server_name,
122
+ server_url=self.server_url,
123
+ capabilities=self.capabilities,
124
+ metadata=self.metadata,
125
+ )
126
+
127
+ while True:
128
+ try:
129
+ await asyncio.sleep(max(2, heartbeat_interval))
130
+ await loop.run_in_executor(None, _heartbeat)
131
+ self.logger.debug("💓 Heartbeat sent successfully")
132
+ except asyncio.CancelledError:
133
+ break
134
+ except Exception as e:
135
+ self.logger.error(f"Heartbeat error: {e}")
136
+
137
+ self.registration_task = asyncio.create_task(heartbeat_loop())
138
+
139
+ async def stop(self) -> None:
140
+ """Stop registration manager and unregister from proxy."""
141
+ # Cancel heartbeat task
142
+ if self.registration_task:
143
+ self.registration_task.cancel()
144
+ try:
145
+ await self.registration_task
146
+ except asyncio.CancelledError:
147
+ pass
148
+
149
+ # Unregister from proxy if registered
150
+ if self.registered and self.proxy_url and self.server_name:
151
+ try:
152
+ loop = asyncio.get_event_loop()
153
+ client = JsonRpcClient(protocol="http", host="127.0.0.1", port=8080) # Dummy, just for methods
154
+
155
+ def _unregister():
156
+ return client.unregister_from_proxy(
157
+ proxy_url=self.proxy_url,
158
+ server_name=self.server_name,
159
+ )
160
+
161
+ await loop.run_in_executor(None, _unregister)
162
+ self.logger.info(f"🛑 Unregistered from proxy: {self.server_name}")
163
+ except Exception as e:
164
+ self.logger.error(f"Error unregistering from proxy: {e}")
165
+
166
+ self.registered = False
@@ -0,0 +1,88 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ SSL context factory for MCP Proxy Adapter API.
6
+ """
7
+
8
+ import ssl
9
+ from pathlib import Path
10
+ from typing import Any, Dict, Optional
11
+
12
+ from mcp_proxy_adapter.core.logging import get_global_logger
13
+ from mcp_proxy_adapter.core.ssl_utils import SSLUtils
14
+
15
+
16
+ class SSLContextFactory:
17
+ """Factory for creating SSL contexts."""
18
+
19
+ def __init__(self):
20
+ """Initialize SSL context factory."""
21
+ self.logger = get_global_logger()
22
+
23
+ def create_ssl_context(
24
+ self,
25
+ app_config: Optional[Dict[str, Any]] = None
26
+ ) -> Optional[ssl.SSLContext]:
27
+ """
28
+ Create SSL context based on configuration.
29
+
30
+ Args:
31
+ app_config: Application configuration dictionary (optional)
32
+
33
+ Returns:
34
+ SSL context if SSL is enabled and properly configured, None otherwise
35
+ """
36
+ from mcp_proxy_adapter.config import config
37
+
38
+ current_config = app_config if app_config is not None else config.get_all()
39
+
40
+ # Check SSL configuration from new structure
41
+ protocol = current_config.get("server", {}).get("protocol", "http")
42
+ verify_client = current_config.get("transport", {}).get("verify_client", False)
43
+ ssl_enabled = protocol in ["https", "mtls"] or verify_client
44
+
45
+ if not ssl_enabled:
46
+ self.logger.info("SSL is disabled in configuration")
47
+ return None
48
+
49
+ # Get certificate paths from configuration
50
+ cert_file = current_config.get("transport", {}).get("cert_file")
51
+ key_file = current_config.get("transport", {}).get("key_file")
52
+ ca_cert = current_config.get("transport", {}).get("ca_cert")
53
+
54
+ # Convert relative paths to absolute paths
55
+ if cert_file and not Path(cert_file).is_absolute():
56
+ project_root = Path(__file__).parent.parent.parent.parent
57
+ cert_file = str(project_root / cert_file)
58
+ if key_file and not Path(key_file).is_absolute():
59
+ project_root = Path(__file__).parent.parent.parent.parent
60
+ key_file = str(project_root / key_file)
61
+ if ca_cert and not Path(ca_cert).is_absolute():
62
+ project_root = Path(__file__).parent.parent.parent.parent
63
+ ca_cert = str(project_root / ca_cert)
64
+
65
+ if not cert_file or not key_file:
66
+ self.logger.warning("SSL enabled but certificate or key file not specified")
67
+ return None
68
+
69
+ try:
70
+ # Create SSL context using SSLUtils
71
+ ssl_context = SSLUtils.create_ssl_context(
72
+ cert_file=cert_file,
73
+ key_file=key_file,
74
+ ca_cert=ca_cert,
75
+ verify_client=current_config.get("transport", {}).get("verify_client", False),
76
+ cipher_suites=[],
77
+ min_tls_version="1.2",
78
+ max_tls_version="1.3",
79
+ )
80
+
81
+ self.logger.info(
82
+ f"SSL context created successfully for mode: https_only"
83
+ )
84
+ return ssl_context
85
+
86
+ except Exception as e:
87
+ self.logger.error(f"Failed to create SSL context: {e}")
88
+ return None