mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +254 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +36 -30
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +243 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  13. mcp_proxy_adapter/commands/__init__.py +19 -4
  14. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  15. mcp_proxy_adapter/commands/base.py +66 -32
  16. mcp_proxy_adapter/commands/builtin_commands.py +95 -0
  17. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  18. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  19. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  20. mcp_proxy_adapter/commands/command_registry.py +711 -354
  21. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  22. mcp_proxy_adapter/commands/echo_command.py +81 -0
  23. mcp_proxy_adapter/commands/health_command.py +7 -0
  24. mcp_proxy_adapter/commands/help_command.py +21 -14
  25. mcp_proxy_adapter/commands/hooks.py +200 -167
  26. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  27. mcp_proxy_adapter/commands/load_command.py +176 -0
  28. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  29. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  30. mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
  31. mcp_proxy_adapter/commands/reload_command.py +48 -50
  32. mcp_proxy_adapter/commands/result.py +1 -0
  33. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  34. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  35. mcp_proxy_adapter/commands/security_command.py +488 -0
  36. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  37. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  38. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  39. mcp_proxy_adapter/commands/unload_command.py +158 -0
  40. mcp_proxy_adapter/config.py +159 -2
  41. mcp_proxy_adapter/core/app_factory.py +326 -0
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/client_security.py +384 -0
  45. mcp_proxy_adapter/core/config_converter.py +405 -0
  46. mcp_proxy_adapter/core/config_validator.py +218 -0
  47. mcp_proxy_adapter/core/logging.py +19 -3
  48. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  49. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  50. mcp_proxy_adapter/core/protocol_manager.py +235 -0
  51. mcp_proxy_adapter/core/proxy_client.py +602 -0
  52. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  53. mcp_proxy_adapter/core/role_utils.py +426 -0
  54. mcp_proxy_adapter/core/security_adapter.py +370 -0
  55. mcp_proxy_adapter/core/security_factory.py +239 -0
  56. mcp_proxy_adapter/core/security_integration.py +277 -0
  57. mcp_proxy_adapter/core/server_adapter.py +345 -0
  58. mcp_proxy_adapter/core/server_engine.py +364 -0
  59. mcp_proxy_adapter/core/settings.py +1 -0
  60. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  61. mcp_proxy_adapter/core/transport_manager.py +292 -0
  62. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  63. mcp_proxy_adapter/custom_openapi.py +22 -11
  64. mcp_proxy_adapter/examples/README.md +230 -97
  65. mcp_proxy_adapter/examples/README_EN.md +258 -0
  66. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  67. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  68. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  69. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  70. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  71. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  72. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  73. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  74. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  75. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  76. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  77. mcp_proxy_adapter/examples/cert_config.json +9 -0
  78. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  79. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  80. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  81. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  82. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  83. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  84. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  85. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  86. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  87. mcp_proxy_adapter/examples/certs/client.key +52 -0
  88. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  89. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  90. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  91. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  92. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  93. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  94. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  95. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  96. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  97. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  98. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  99. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  100. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  101. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  102. mcp_proxy_adapter/examples/certs/server.key +52 -0
  103. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  104. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  105. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  106. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  107. mcp_proxy_adapter/examples/certs/user.key +52 -0
  108. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  109. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  110. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  111. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  112. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  113. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  114. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  115. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  116. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  117. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  118. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  119. mcp_proxy_adapter/examples/demo_client.py +341 -0
  120. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  121. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  122. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  123. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  124. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  125. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  126. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  127. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  128. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  129. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  130. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  131. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  132. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  133. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  134. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  135. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  136. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  137. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  138. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  139. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  140. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  141. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  142. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  143. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  144. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  145. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  146. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  147. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  148. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  149. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  150. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  151. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  152. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  153. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  154. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  155. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  156. mcp_proxy_adapter/examples/roles.json +38 -0
  157. mcp_proxy_adapter/examples/run_example.py +81 -0
  158. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  159. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  160. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  161. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  162. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  163. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  164. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  165. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  166. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  167. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  168. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  169. mcp_proxy_adapter/examples/test_examples.py +344 -0
  170. mcp_proxy_adapter/examples/universal_client.py +628 -0
  171. mcp_proxy_adapter/main.py +186 -0
  172. mcp_proxy_adapter/utils/config_generator.py +639 -0
  173. mcp_proxy_adapter/version.py +2 -1
  174. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  175. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  176. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  177. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  178. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  179. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  180. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  181. mcp_proxy_adapter/examples/__init__.py +0 -7
  182. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  183. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  184. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  185. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  186. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  187. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  188. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  189. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  190. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  191. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  192. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  193. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  194. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  195. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  196. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  197. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  198. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  199. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  200. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  201. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  202. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  203. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  204. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  206. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  207. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  208. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  209. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  210. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  211. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  212. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  213. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  214. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  215. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  216. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  217. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  218. mcp_proxy_adapter/tests/__init__.py +0 -0
  219. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  220. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  221. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  222. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  223. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  224. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  225. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  226. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  227. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  228. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  229. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  230. mcp_proxy_adapter/tests/conftest.py +0 -131
  231. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  232. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  233. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  234. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  235. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  236. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  237. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  238. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  239. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  240. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  241. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  242. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  243. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  244. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  245. mcp_proxy_adapter/tests/test_config.py +0 -127
  246. mcp_proxy_adapter/tests/test_utils.py +0 -65
  247. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  249. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  250. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  251. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  252. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  253. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,620 @@
1
+ """
2
+ Certificate Monitor Command
3
+
4
+ This module provides commands for certificate monitoring including expiry checks,
5
+ health monitoring, alert setup, and auto-renewal.
6
+
7
+ Author: MCP Proxy Adapter Team
8
+ Version: 1.0.0
9
+ """
10
+
11
+ import logging
12
+ import os
13
+ import json
14
+ from typing import Dict, List, Optional, Any
15
+ from pathlib import Path
16
+ from datetime import datetime, timedelta
17
+
18
+ from .base import Command
19
+ from .result import CommandResult, SuccessResult, ErrorResult
20
+ from ..core.certificate_utils import CertificateUtils
21
+ from ..core.auth_validator import AuthValidator
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class CertMonitorResult:
27
+ """
28
+ Result class for certificate monitoring operations.
29
+
30
+ Contains monitoring information and operation status.
31
+ """
32
+
33
+ def __init__(self, cert_path: str, check_type: str, status: str,
34
+ expiry_date: Optional[str] = None, days_until_expiry: Optional[int] = None,
35
+ health_score: Optional[int] = None, alerts: Optional[List[str]] = None,
36
+ auto_renewal_status: Optional[str] = None, error: Optional[str] = None):
37
+ """
38
+ Initialize certificate monitor result.
39
+
40
+ Args:
41
+ cert_path: Path to certificate file
42
+ check_type: Type of check performed (expiry, health, alert, auto_renewal)
43
+ status: Overall status (healthy, warning, critical, error)
44
+ expiry_date: Certificate expiry date
45
+ days_until_expiry: Days until certificate expires
46
+ health_score: Health score (0-100)
47
+ alerts: List of alert messages
48
+ auto_renewal_status: Auto-renewal status
49
+ error: Error message if any
50
+ """
51
+ self.cert_path = cert_path
52
+ self.check_type = check_type
53
+ self.status = status
54
+ self.expiry_date = expiry_date
55
+ self.days_until_expiry = days_until_expiry
56
+ self.health_score = health_score
57
+ self.alerts = alerts or []
58
+ self.auto_renewal_status = auto_renewal_status
59
+ self.error = error
60
+
61
+ def to_dict(self) -> Dict[str, Any]:
62
+ """
63
+ Convert to dictionary format.
64
+
65
+ Returns:
66
+ Dictionary representation
67
+ """
68
+ return {
69
+ "cert_path": self.cert_path,
70
+ "check_type": self.check_type,
71
+ "status": self.status,
72
+ "expiry_date": self.expiry_date,
73
+ "days_until_expiry": self.days_until_expiry,
74
+ "health_score": self.health_score,
75
+ "alerts": self.alerts,
76
+ "auto_renewal_status": self.auto_renewal_status,
77
+ "error": self.error
78
+ }
79
+
80
+ def get_schema(self) -> Dict[str, Any]:
81
+ """
82
+ Get JSON schema for this result.
83
+
84
+ Returns:
85
+ JSON schema dictionary
86
+ """
87
+ return {
88
+ "type": "object",
89
+ "properties": {
90
+ "cert_path": {"type": "string", "description": "Path to certificate file"},
91
+ "check_type": {"type": "string", "enum": ["expiry", "health", "alert", "auto_renewal"],
92
+ "description": "Type of check performed"},
93
+ "status": {"type": "string", "enum": ["healthy", "warning", "critical", "error"],
94
+ "description": "Overall status"},
95
+ "expiry_date": {"type": "string", "description": "Certificate expiry date"},
96
+ "days_until_expiry": {"type": "integer", "description": "Days until certificate expires"},
97
+ "health_score": {"type": "integer", "minimum": 0, "maximum": 100,
98
+ "description": "Health score (0-100)"},
99
+ "alerts": {"type": "array", "items": {"type": "string"},
100
+ "description": "List of alert messages"},
101
+ "auto_renewal_status": {"type": "string", "description": "Auto-renewal status"},
102
+ "error": {"type": "string", "description": "Error message if any"}
103
+ },
104
+ "required": ["cert_path", "check_type", "status"]
105
+ }
106
+
107
+
108
+ class CertMonitorCommand(Command):
109
+ """
110
+ Command for certificate monitoring.
111
+
112
+ Provides methods for monitoring certificate expiry, health, alerts, and auto-renewal.
113
+ """
114
+
115
+ # Command metadata
116
+ name = "cert_monitor"
117
+ version = "1.0.0"
118
+ descr = "Certificate expiry monitoring and health checks"
119
+ category = "security"
120
+ author = "MCP Proxy Adapter Team"
121
+ email = "team@mcp-proxy-adapter.com"
122
+ source_url = "https://github.com/mcp-proxy-adapter"
123
+ result_class = CertMonitorResult
124
+
125
+ def __init__(self):
126
+ """Initialize certificate monitor command."""
127
+ super().__init__()
128
+ self.certificate_utils = CertificateUtils()
129
+ self.auth_validator = AuthValidator()
130
+
131
+ async def execute(self, **kwargs) -> CommandResult:
132
+ """
133
+ Execute certificate monitor command.
134
+
135
+ Args:
136
+ **kwargs: Command parameters including:
137
+ - action: Action to perform (cert_expiry_check, cert_health_check, cert_alert_setup, cert_auto_renew)
138
+ - cert_path: Certificate file path for individual checks
139
+ - warning_days: Days before expiry to start warning
140
+ - critical_days: Days before expiry for critical status
141
+ - alert_config: Alert configuration for setup
142
+ - auto_renew_config: Auto-renewal configuration
143
+
144
+ Returns:
145
+ CommandResult with monitoring operation status
146
+ """
147
+ action = kwargs.get("action", "cert_expiry_check")
148
+
149
+ if action == "cert_expiry_check":
150
+ cert_path = kwargs.get("cert_path")
151
+ warning_days = kwargs.get("warning_days", 30)
152
+ critical_days = kwargs.get("critical_days", 7)
153
+ return await self.cert_expiry_check(cert_path, warning_days, critical_days)
154
+ elif action == "cert_health_check":
155
+ cert_path = kwargs.get("cert_path")
156
+ return await self.cert_health_check(cert_path)
157
+ elif action == "cert_alert_setup":
158
+ cert_path = kwargs.get("cert_path")
159
+ alert_config = kwargs.get("alert_config", {})
160
+ return await self.cert_alert_setup(cert_path, alert_config)
161
+ elif action == "cert_auto_renew":
162
+ cert_path = kwargs.get("cert_path")
163
+ auto_renew_config = kwargs.get("auto_renew_config", {})
164
+ return await self.cert_auto_renew(cert_path, auto_renew_config)
165
+ else:
166
+ return ErrorResult(
167
+ message=f"Unknown action: {action}. Supported actions: cert_expiry_check, cert_health_check, cert_alert_setup, cert_auto_renew"
168
+ )
169
+
170
+ async def cert_expiry_check(self, cert_path: str, warning_days: int = 30, critical_days: int = 7) -> CommandResult:
171
+ """
172
+ Check certificate expiry date.
173
+
174
+ Args:
175
+ cert_path: Path to certificate file
176
+ warning_days: Days before expiry to start warning
177
+ critical_days: Days before expiry for critical status
178
+
179
+ Returns:
180
+ CommandResult with expiry check results
181
+ """
182
+ try:
183
+ logger.info(f"Performing certificate expiry check for {cert_path}")
184
+
185
+ # Check if certificate file exists
186
+ if not os.path.exists(cert_path):
187
+ return ErrorResult(
188
+ message=f"Certificate file not found: {cert_path}"
189
+ )
190
+
191
+ # Get certificate info
192
+ cert_info = self.certificate_utils.get_certificate_info(cert_path)
193
+ if not cert_info:
194
+ return ErrorResult(
195
+ message="Could not read certificate information"
196
+ )
197
+
198
+ expiry_date = cert_info.get("expiry_date")
199
+ if not expiry_date:
200
+ return ErrorResult(
201
+ message="Could not determine certificate expiry date"
202
+ )
203
+
204
+ try:
205
+ # Calculate days until expiry
206
+ expiry_datetime = datetime.fromisoformat(expiry_date.replace('Z', '+00:00'))
207
+ days_until_expiry = (expiry_datetime - datetime.now(expiry_datetime.tzinfo)).days
208
+ except ValueError:
209
+ return ErrorResult(
210
+ message="Invalid expiry date format"
211
+ )
212
+
213
+ # Determine status
214
+ is_expired = days_until_expiry < 0
215
+ if is_expired:
216
+ health_status = "expired"
217
+ elif days_until_expiry <= critical_days:
218
+ health_status = "critical"
219
+ elif days_until_expiry <= warning_days:
220
+ health_status = "warning"
221
+ else:
222
+ health_status = "healthy"
223
+
224
+ logger.info(f"Certificate expiry check completed: {health_status} ({days_until_expiry} days)")
225
+
226
+ return SuccessResult(
227
+ data={
228
+ "monitor_result": {
229
+ "is_expired": is_expired,
230
+ "health_status": health_status,
231
+ "days_until_expiry": days_until_expiry,
232
+ "expiry_date": expiry_date,
233
+ "warning_days": warning_days,
234
+ "critical_days": critical_days
235
+ }
236
+ }
237
+ )
238
+
239
+ except Exception as e:
240
+ logger.error(f"Certificate expiry check failed: {e}")
241
+ return ErrorResult(
242
+ message=f"Certificate expiry check failed: {str(e)}"
243
+ )
244
+
245
+ async def cert_health_check(self, cert_path: str) -> CommandResult:
246
+ """
247
+ Perform comprehensive health check on certificate.
248
+
249
+ Args:
250
+ cert_path: Path to certificate file
251
+
252
+ Returns:
253
+ CommandResult with health check results
254
+ """
255
+ try:
256
+ logger.info(f"Performing certificate health check for {cert_path}")
257
+
258
+ # Check if certificate file exists
259
+ if not os.path.exists(cert_path):
260
+ return ErrorResult(
261
+ message=f"Certificate file not found: {cert_path}"
262
+ )
263
+
264
+ # Get certificate info
265
+ cert_info = self.certificate_utils.get_certificate_info(cert_path)
266
+ if not cert_info:
267
+ return ErrorResult(
268
+ message="Could not read certificate information"
269
+ )
270
+
271
+ # Validate certificate
272
+ validation = self.auth_validator.validate_certificate(cert_path)
273
+
274
+ # Calculate health score
275
+ health_score = 100
276
+ alerts = []
277
+
278
+ # Check if certificate is valid
279
+ if not validation.is_valid:
280
+ health_score -= 50
281
+ alerts.append(f"Certificate validation failed: {validation.error_message}")
282
+
283
+ # Check expiry
284
+ expiry_date = cert_info.get("expiry_date")
285
+ if expiry_date:
286
+ try:
287
+ expiry_datetime = datetime.fromisoformat(expiry_date.replace('Z', '+00:00'))
288
+ days_until_expiry = (expiry_datetime - datetime.now(expiry_datetime.tzinfo)).days
289
+
290
+ if days_until_expiry < 0:
291
+ health_score -= 30
292
+ alerts.append("Certificate has expired")
293
+ elif days_until_expiry <= 7:
294
+ health_score -= 20
295
+ alerts.append(f"Certificate expires in {days_until_expiry} days")
296
+ elif days_until_expiry <= 30:
297
+ health_score -= 10
298
+ alerts.append(f"Certificate expires in {days_until_expiry} days")
299
+ except ValueError:
300
+ health_score -= 10
301
+ alerts.append("Invalid expiry date format")
302
+
303
+ # Check key strength
304
+ key_size = cert_info.get("key_size", 0)
305
+ if key_size < 2048:
306
+ health_score -= 15
307
+ alerts.append(f"Key size {key_size} bits is below recommended 2048 bits")
308
+
309
+ # Determine overall status
310
+ if health_score >= 80:
311
+ overall_status = "healthy"
312
+ elif health_score >= 50:
313
+ overall_status = "warning"
314
+ else:
315
+ overall_status = "critical"
316
+
317
+ logger.info(f"Certificate health check completed: {overall_status} (score: {health_score})")
318
+
319
+ return SuccessResult(
320
+ data={
321
+ "monitor_result": {
322
+ "health_score": health_score,
323
+ "alerts": alerts,
324
+ "expiry_date": expiry_date
325
+ },
326
+ "health_checks": {
327
+ "validation": {
328
+ "passed": validation.is_valid
329
+ }
330
+ },
331
+ "overall_status": overall_status
332
+ }
333
+ )
334
+
335
+ except Exception as e:
336
+ logger.error(f"Certificate health check failed: {e}")
337
+ return ErrorResult(
338
+ message=f"Certificate health check failed: {str(e)}"
339
+ )
340
+
341
+ async def cert_alert_setup(self, cert_path: str, alert_config: Dict[str, Any]) -> CommandResult:
342
+ """
343
+ Set up certificate monitoring alerts.
344
+
345
+ Args:
346
+ cert_path: Path to certificate file
347
+ alert_config: Alert configuration dictionary
348
+
349
+ Returns:
350
+ CommandResult with alert setup status
351
+ """
352
+ try:
353
+ logger.info(f"Setting up certificate monitoring alerts for {cert_path}")
354
+
355
+ # Check if certificate file exists
356
+ if not os.path.exists(cert_path):
357
+ return ErrorResult(
358
+ message=f"Certificate file not found: {cert_path}"
359
+ )
360
+
361
+ # Validate alert configuration
362
+ if not isinstance(alert_config, dict):
363
+ return ErrorResult(
364
+ message="Alert configuration must be a dictionary"
365
+ )
366
+
367
+ # Check if alerts are disabled
368
+ if not alert_config.get("enabled", True):
369
+ return SuccessResult(
370
+ data={
371
+ "monitor_result": {
372
+ "alerts_enabled": False
373
+ },
374
+ "message": "Alerts disabled"
375
+ }
376
+ )
377
+
378
+ # Validate required fields
379
+ required_fields = ["warning_days", "critical_days"]
380
+ for field in required_fields:
381
+ if field not in alert_config:
382
+ return ErrorResult(
383
+ message=f"Missing required field in alert config: {field}"
384
+ )
385
+
386
+ if alert_config["warning_days"] <= 0 or alert_config["critical_days"] <= 0:
387
+ return ErrorResult(
388
+ message="Warning and critical days must be positive"
389
+ )
390
+
391
+ if alert_config["warning_days"] <= alert_config["critical_days"]:
392
+ return ErrorResult(
393
+ message="Warning days must be greater than critical days"
394
+ )
395
+
396
+ # Check notification channels
397
+ notification_channels = alert_config.get("notification_channels", [])
398
+ if not notification_channels:
399
+ return ErrorResult(
400
+ message="At least one notification channel must be specified"
401
+ )
402
+
403
+ # Test alert configuration
404
+ test_result = await self._test_alert_config(alert_config)
405
+ if not test_result["success"]:
406
+ return ErrorResult(
407
+ message=f"Alert configuration test failed: {test_result['error']}"
408
+ )
409
+
410
+ # Save alert configuration
411
+ config_path = "/tmp/cert_alert_config.json"
412
+ with open(config_path, 'w') as f:
413
+ json.dump(alert_config, f, indent=2)
414
+
415
+ logger.info(f"Alert configuration saved to {config_path}")
416
+
417
+ return SuccessResult(
418
+ data={
419
+ "monitor_result": {
420
+ "alerts_enabled": True
421
+ },
422
+ "alert_config": alert_config,
423
+ "config_path": config_path,
424
+ "setup_date": datetime.now().isoformat(),
425
+ "message": "Alerts configured successfully"
426
+ }
427
+ )
428
+
429
+ except Exception as e:
430
+ logger.error(f"Alert setup failed: {e}")
431
+ return ErrorResult(
432
+ message=f"Alert setup failed: {str(e)}"
433
+ )
434
+
435
+ async def cert_auto_renew(self, cert_path: str, auto_renew_config: Dict[str, Any]) -> CommandResult:
436
+ """
437
+ Set up certificate auto-renewal.
438
+
439
+ Args:
440
+ cert_path: Path to certificate file
441
+ auto_renew_config: Auto-renewal configuration dictionary
442
+
443
+ Returns:
444
+ CommandResult with auto-renewal setup status
445
+ """
446
+ try:
447
+ logger.info(f"Setting up certificate auto-renewal for {cert_path}")
448
+
449
+ # Check if certificate file exists
450
+ if not os.path.exists(cert_path):
451
+ return ErrorResult(
452
+ message=f"Certificate file not found: {cert_path}"
453
+ )
454
+
455
+ # Validate auto-renewal configuration
456
+ if not isinstance(auto_renew_config, dict):
457
+ return ErrorResult(
458
+ message="Auto-renewal configuration must be a dictionary"
459
+ )
460
+
461
+ # Check if auto-renewal is disabled
462
+ if not auto_renew_config.get("enabled", True):
463
+ return SuccessResult(
464
+ data={
465
+ "monitor_result": {
466
+ "auto_renewal_enabled": False
467
+ },
468
+ "message": "Auto-renewal disabled"
469
+ }
470
+ )
471
+
472
+ # Validate required fields
473
+ required_fields = ["renew_before_days", "ca_cert_path", "ca_key_path"]
474
+ for field in required_fields:
475
+ if field not in auto_renew_config:
476
+ return ErrorResult(
477
+ message=f"Missing required field in auto-renewal config: {field}"
478
+ )
479
+
480
+ if auto_renew_config["renew_before_days"] <= 0:
481
+ return ErrorResult(
482
+ message="Renew before days must be positive"
483
+ )
484
+
485
+ # Check CA files
486
+ ca_cert_path = auto_renew_config["ca_cert_path"]
487
+ ca_key_path = auto_renew_config["ca_key_path"]
488
+
489
+ if not os.path.exists(ca_cert_path):
490
+ return ErrorResult(
491
+ message=f"CA certificate not found: {ca_cert_path}"
492
+ )
493
+
494
+ if not os.path.exists(ca_key_path):
495
+ return ErrorResult(
496
+ message=f"CA private key not found: {ca_key_path}"
497
+ )
498
+
499
+ # Check output directory
500
+ output_dir = auto_renew_config.get("output_dir")
501
+ if not output_dir:
502
+ return ErrorResult(
503
+ message="Output directory must be specified"
504
+ )
505
+
506
+ # Test renewal configuration
507
+ test_result = await self._test_renewal_config(cert_path, auto_renew_config)
508
+ if not test_result["success"]:
509
+ return ErrorResult(
510
+ message=f"Renewal configuration test failed: {test_result['error']}"
511
+ )
512
+
513
+ # Save auto-renewal configuration
514
+ config_path = "/tmp/cert_auto_renew_config.json"
515
+ with open(config_path, 'w') as f:
516
+ json.dump(auto_renew_config, f, indent=2)
517
+
518
+ logger.info(f"Auto-renewal configuration saved to {config_path}")
519
+
520
+ return SuccessResult(
521
+ data={
522
+ "monitor_result": {
523
+ "auto_renewal_enabled": True
524
+ },
525
+ "auto_renew_config": auto_renew_config,
526
+ "config_path": config_path,
527
+ "setup_date": datetime.now().isoformat(),
528
+ "message": "Auto-renewal configured successfully"
529
+ }
530
+ )
531
+
532
+ except Exception as e:
533
+ logger.error(f"Auto-renewal setup failed: {e}")
534
+ return ErrorResult(
535
+ message=f"Auto-renewal setup failed: {str(e)}"
536
+ )
537
+
538
+ def _find_certificates(self, directory: str) -> List[str]:
539
+ """
540
+ Find all certificate files in a directory.
541
+
542
+ Args:
543
+ directory: Directory to scan
544
+
545
+ Returns:
546
+ List of certificate file paths
547
+ """
548
+ certificates = []
549
+ cert_extensions = ['.crt', '.pem', '.cer', '.der']
550
+
551
+ for file_path in Path(directory).rglob('*'):
552
+ if file_path.is_file() and file_path.suffix.lower() in cert_extensions:
553
+ certificates.append(str(file_path))
554
+
555
+ return certificates
556
+
557
+ async def _test_alert_config(self, alert_config: Dict[str, Any]) -> Dict[str, Any]:
558
+ """
559
+ Test alert configuration.
560
+
561
+ Args:
562
+ alert_config: Alert configuration to test
563
+
564
+ Returns:
565
+ Test result dictionary
566
+ """
567
+ try:
568
+ # Test email configuration if present
569
+ if "email_recipients" in alert_config:
570
+ recipients = alert_config["email_recipients"]
571
+ if not isinstance(recipients, list) or not recipients:
572
+ return {"success": False, "error": "Invalid email recipients"}
573
+
574
+ # Test webhook configuration if present
575
+ if "webhook_url" in alert_config:
576
+ webhook_url = alert_config["webhook_url"]
577
+ if not isinstance(webhook_url, str) or not webhook_url:
578
+ return {"success": False, "error": "Invalid webhook URL"}
579
+
580
+ return {"success": True}
581
+
582
+ except Exception as e:
583
+ return {"success": False, "error": str(e)}
584
+
585
+ async def _test_renewal_config(self, cert_path: str, renewal_config: Dict[str, Any]) -> Dict[str, Any]:
586
+ """
587
+ Test renewal configuration.
588
+
589
+ Args:
590
+ cert_path: Path to certificate file
591
+ renewal_config: Renewal configuration to test
592
+
593
+ Returns:
594
+ Test result dictionary
595
+ """
596
+ try:
597
+ # Get certificate info
598
+ cert_info = self.certificate_utils.get_certificate_info(cert_path)
599
+ if not cert_info:
600
+ return {"success": False, "error": "Could not read certificate information"}
601
+
602
+ # Check CA certificate
603
+ ca_cert_path = renewal_config.get("ca_cert_path")
604
+ if not ca_cert_path or not os.path.exists(ca_cert_path):
605
+ return {"success": False, "error": "CA certificate not found"}
606
+
607
+ # Check CA key
608
+ ca_key_path = renewal_config.get("ca_key_path")
609
+ if not ca_key_path or not os.path.exists(ca_key_path):
610
+ return {"success": False, "error": "CA private key not found"}
611
+
612
+ # Check output directory
613
+ output_dir = renewal_config.get("output_dir")
614
+ if not output_dir or not os.path.exists(output_dir):
615
+ return {"success": False, "error": "Output directory not found"}
616
+
617
+ return {"success": True}
618
+
619
+ except Exception as e:
620
+ return {"success": False, "error": str(e)}