mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +209 -79
  3. mcp_proxy_adapter/api/handlers.py +16 -5
  4. mcp_proxy_adapter/api/middleware/__init__.py +14 -9
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  7. mcp_proxy_adapter/api/middleware/protocol_middleware.py +84 -18
  8. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  9. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  10. mcp_proxy_adapter/commands/__init__.py +7 -1
  11. mcp_proxy_adapter/commands/base.py +7 -4
  12. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  13. mcp_proxy_adapter/commands/command_registry.py +8 -0
  14. mcp_proxy_adapter/commands/echo_command.py +81 -0
  15. mcp_proxy_adapter/commands/health_command.py +1 -1
  16. mcp_proxy_adapter/commands/help_command.py +21 -14
  17. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  18. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  19. mcp_proxy_adapter/commands/security_command.py +488 -0
  20. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  21. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  22. mcp_proxy_adapter/config.py +323 -40
  23. mcp_proxy_adapter/core/app_factory.py +410 -0
  24. mcp_proxy_adapter/core/app_runner.py +272 -0
  25. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  26. mcp_proxy_adapter/core/client.py +574 -0
  27. mcp_proxy_adapter/core/client_manager.py +284 -0
  28. mcp_proxy_adapter/core/client_security.py +384 -0
  29. mcp_proxy_adapter/core/logging.py +8 -3
  30. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  31. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  32. mcp_proxy_adapter/core/protocol_manager.py +169 -10
  33. mcp_proxy_adapter/core/proxy_client.py +602 -0
  34. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  35. mcp_proxy_adapter/core/security_adapter.py +12 -15
  36. mcp_proxy_adapter/core/security_integration.py +286 -0
  37. mcp_proxy_adapter/core/server_adapter.py +282 -0
  38. mcp_proxy_adapter/core/server_engine.py +270 -0
  39. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  40. mcp_proxy_adapter/core/transport_manager.py +5 -5
  41. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  42. mcp_proxy_adapter/examples/__init__.py +13 -4
  43. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  44. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  45. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  46. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  47. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  48. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  49. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  50. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  51. mcp_proxy_adapter/examples/demo_client.py +275 -0
  52. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  53. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  54. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  55. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  56. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  57. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  58. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  59. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  60. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  61. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  62. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  63. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  64. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  65. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  66. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  67. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  68. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  69. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  70. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  71. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  72. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  73. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  74. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  75. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  76. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  77. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  78. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  79. mcp_proxy_adapter/examples/run_example.py +59 -0
  80. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  81. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  82. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  83. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  84. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  85. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  86. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  87. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  88. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  89. mcp_proxy_adapter/examples/test_config.py +148 -0
  90. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  91. mcp_proxy_adapter/examples/test_examples.py +281 -0
  92. mcp_proxy_adapter/examples/universal_client.py +620 -0
  93. mcp_proxy_adapter/main.py +66 -148
  94. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  95. mcp_proxy_adapter/version.py +5 -2
  96. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  97. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  98. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  99. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  100. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  101. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  102. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  103. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  104. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  105. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  106. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  107. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  108. mcp_proxy_adapter/api/middleware/security.py +0 -376
  109. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  110. mcp_proxy_adapter/examples/README.md +0 -124
  111. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  112. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  113. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  114. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  115. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  116. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  117. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  118. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  119. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  120. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  121. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  122. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  123. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  124. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  125. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  126. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  127. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  128. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  129. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  130. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  131. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  132. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  133. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  134. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  135. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  136. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  137. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  138. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  139. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  140. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  141. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  142. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  143. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  144. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  145. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  146. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  147. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  148. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  149. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  150. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  153. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  154. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  155. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  156. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  157. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  158. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  159. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  160. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  161. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  162. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  163. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  164. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  166. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  167. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  168. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  169. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  170. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  171. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  172. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  173. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  174. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  175. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  176. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  177. mcp_proxy_adapter/tests/__init__.py +0 -0
  178. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  180. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  181. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  182. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  183. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  184. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  185. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  186. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  187. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  188. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  189. mcp_proxy_adapter/tests/conftest.py +0 -131
  190. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  191. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  192. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  193. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  194. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  195. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  196. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  197. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  198. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  199. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  200. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  201. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  202. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  203. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  204. mcp_proxy_adapter/tests/test_config.py +0 -127
  205. mcp_proxy_adapter/tests/test_utils.py +0 -65
  206. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  207. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  208. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  209. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  210. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  211. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,90 @@
1
+ """
2
+ Dynamic Calculator Command
3
+ This module demonstrates a dynamically loaded command implementation for the full application example.
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+ """
7
+ from typing import Dict, Any, Optional
8
+ from mcp_proxy_adapter.commands.base import BaseCommand
9
+ from mcp_proxy_adapter.commands.result import CommandResult
10
+ class CalculatorResult(CommandResult):
11
+ """Result class for calculator command."""
12
+ def __init__(self, operation: str, result: float, expression: str):
13
+ self.operation = operation
14
+ self.result = result
15
+ self.expression = expression
16
+ def to_dict(self) -> Dict[str, Any]:
17
+ """Convert result to dictionary."""
18
+ return {
19
+ "operation": self.operation,
20
+ "result": self.result,
21
+ "expression": self.expression,
22
+ "command_type": "dynamic_calculator"
23
+ }
24
+ def get_schema(self) -> Dict[str, Any]:
25
+ """Get result schema."""
26
+ return {
27
+ "type": "object",
28
+ "properties": {
29
+ "operation": {"type": "string", "description": "Mathematical operation"},
30
+ "result": {"type": "number", "description": "Calculation result"},
31
+ "expression": {"type": "string", "description": "Full expression"},
32
+ "command_type": {"type": "string", "description": "Command type"}
33
+ },
34
+ "required": ["operation", "result", "expression", "command_type"]
35
+ }
36
+ class DynamicCalculatorCommand(BaseCommand):
37
+ """Dynamic calculator command implementation."""
38
+ def get_name(self) -> str:
39
+ """Get command name."""
40
+ return "dynamic_calculator"
41
+ def get_description(self) -> str:
42
+ """Get command description."""
43
+ return "Dynamic calculator with basic mathematical operations"
44
+ def get_schema(self) -> Dict[str, Any]:
45
+ """Get command schema."""
46
+ return {
47
+ "type": "object",
48
+ "properties": {
49
+ "operation": {
50
+ "type": "string",
51
+ "description": "Mathematical operation (add, subtract, multiply, divide)",
52
+ "enum": ["add", "subtract", "multiply", "divide"]
53
+ },
54
+ "a": {
55
+ "type": "number",
56
+ "description": "First number"
57
+ },
58
+ "b": {
59
+ "type": "number",
60
+ "description": "Second number"
61
+ }
62
+ },
63
+ "required": ["operation", "a", "b"]
64
+ }
65
+ async def execute(self, params: Dict[str, Any]) -> CalculatorResult:
66
+ """Execute the calculator command."""
67
+ operation = params.get("operation")
68
+ a = params.get("a")
69
+ b = params.get("b")
70
+ if operation == "add":
71
+ result = a + b
72
+ expression = f"{a} + {b}"
73
+ elif operation == "subtract":
74
+ result = a - b
75
+ expression = f"{a} - {b}"
76
+ elif operation == "multiply":
77
+ result = a * b
78
+ expression = f"{a} * {b}"
79
+ elif operation == "divide":
80
+ if b == 0:
81
+ raise ValueError("Division by zero is not allowed")
82
+ result = a / b
83
+ expression = f"{a} / {b}"
84
+ else:
85
+ raise ValueError(f"Unknown operation: {operation}")
86
+ return CalculatorResult(
87
+ operation=operation,
88
+ result=result,
89
+ expression=expression
90
+ )
@@ -0,0 +1,7 @@
1
+ """Full Application Hooks.
2
+
3
+ Application and command hooks for the full application example.
4
+ """
5
+
6
+ from .application_hooks import ApplicationHooks
7
+ from .builtin_command_hooks import BuiltinCommandHooks
@@ -0,0 +1,75 @@
1
+ """
2
+ Application Hooks
3
+ This module demonstrates application-level hooks in the full application example.
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+ """
7
+ import logging
8
+ from typing import Dict, Any, Optional
9
+ from datetime import datetime
10
+ logger = logging.getLogger(__name__)
11
+ class ApplicationHooks:
12
+ """Application-level hooks."""
13
+ @staticmethod
14
+ def on_startup():
15
+ """Hook executed on application startup."""
16
+ logger.info("🚀 Application startup hook executed")
17
+ # Initialize application-specific resources
18
+ logger.info("📊 Initializing application metrics")
19
+ logger.info("🔐 Loading security configurations")
20
+ logger.info("📝 Setting up logging")
21
+ @staticmethod
22
+ def on_shutdown():
23
+ """Hook executed on application shutdown."""
24
+ logger.info("🛑 Application shutdown hook executed")
25
+ # Cleanup application resources
26
+ logger.info("🧹 Cleaning up resources")
27
+ logger.info("💾 Saving application state")
28
+ logger.info("📊 Finalizing metrics")
29
+ @staticmethod
30
+ def before_request(request_data: Dict[str, Any]) -> Dict[str, Any]:
31
+ """Hook executed before processing any request."""
32
+ logger.info(f"🔧 Application hook: before_request with data: {request_data}")
33
+ # Add request metadata
34
+ request_data["app_metadata"] = {
35
+ "request_id": f"req_{datetime.now().timestamp()}",
36
+ "timestamp": datetime.now().isoformat(),
37
+ "application": "full_application_example"
38
+ }
39
+ return request_data
40
+ @staticmethod
41
+ def after_request(result: Dict[str, Any]) -> Dict[str, Any]:
42
+ """Hook executed after processing any request."""
43
+ logger.info(f"🔧 Application hook: after_request with result: {result}")
44
+ # Add response metadata
45
+ result["app_response_metadata"] = {
46
+ "processed_at": datetime.now().isoformat(),
47
+ "application": "full_application_example",
48
+ "version": "1.0.0"
49
+ }
50
+ return result
51
+ @staticmethod
52
+ def on_error(error: Exception, context: Dict[str, Any]):
53
+ """Hook executed when an error occurs."""
54
+ logger.error(f"🔧 Application hook: on_error - {error} in context: {context}")
55
+ # Log error details
56
+ logger.error(f"Error type: {type(error).__name__}")
57
+ logger.error(f"Error message: {str(error)}")
58
+ logger.error(f"Context: {context}")
59
+ @staticmethod
60
+ def on_command_registered(command_name: str, command_info: Dict[str, Any]):
61
+ """Hook executed when a command is registered."""
62
+ logger.info(f"🔧 Application hook: on_command_registered - {command_name}")
63
+ logger.info(f"Command info: {command_info}")
64
+ # Track registered commands
65
+ logger.info(f"📝 Command '{command_name}' registered successfully")
66
+ @staticmethod
67
+ def on_command_executed(command_name: str, execution_time: float, success: bool):
68
+ """Hook executed when a command is executed."""
69
+ logger.info(f"🔧 Application hook: on_command_executed - {command_name}")
70
+ logger.info(f"Execution time: {execution_time}s, Success: {success}")
71
+ # Track command execution metrics
72
+ if success:
73
+ logger.info(f"✅ Command '{command_name}' executed successfully in {execution_time}s")
74
+ else:
75
+ logger.warning(f"⚠️ Command '{command_name}' failed after {execution_time}s")
@@ -0,0 +1,71 @@
1
+ """
2
+ Built-in Command Hooks
3
+ This module demonstrates hooks for built-in commands in the full application example.
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+ """
7
+ import logging
8
+ from typing import Dict, Any, Optional
9
+ from datetime import datetime
10
+ logger = logging.getLogger(__name__)
11
+ class BuiltinCommandHooks:
12
+ """Hooks for built-in commands."""
13
+ @staticmethod
14
+ def before_echo_command(params: Dict[str, Any]) -> Dict[str, Any]:
15
+ """Hook executed before echo command."""
16
+ logger.info(f"🔧 Built-in hook: before_echo_command with params: {params}")
17
+ # Add timestamp to message
18
+ if "message" in params:
19
+ timestamp = datetime.now().isoformat()
20
+ params["message"] = f"[{timestamp}] {params['message']}"
21
+ return params
22
+ @staticmethod
23
+ def after_echo_command(result: Dict[str, Any]) -> Dict[str, Any]:
24
+ """Hook executed after echo command."""
25
+ logger.info(f"🔧 Built-in hook: after_echo_command with result: {result}")
26
+ # Add hook metadata
27
+ result["hook_metadata"] = {
28
+ "hook_type": "builtin_after_echo",
29
+ "timestamp": datetime.now().isoformat(),
30
+ "processed": True
31
+ }
32
+ return result
33
+ @staticmethod
34
+ def before_health_command(params: Dict[str, Any]) -> Dict[str, Any]:
35
+ """Hook executed before health command."""
36
+ logger.info(f"🔧 Built-in hook: before_health_command with params: {params}")
37
+ # Add custom health check parameters
38
+ params["include_detailed_info"] = True
39
+ params["check_dependencies"] = True
40
+ return params
41
+ @staticmethod
42
+ def after_health_command(result: Dict[str, Any]) -> Dict[str, Any]:
43
+ """Hook executed after health command."""
44
+ logger.info(f"🔧 Built-in hook: after_health_command with result: {result}")
45
+ # Add custom health metrics
46
+ if "status" in result and result["status"] == "healthy":
47
+ result["custom_metrics"] = {
48
+ "uptime": "24h",
49
+ "memory_usage": "45%",
50
+ "cpu_usage": "12%"
51
+ }
52
+ return result
53
+ @staticmethod
54
+ def before_config_command(params: Dict[str, Any]) -> Dict[str, Any]:
55
+ """Hook executed before config command."""
56
+ logger.info(f"🔧 Built-in hook: before_config_command with params: {params}")
57
+ # Add configuration validation
58
+ params["validate_config"] = True
59
+ params["include_secrets"] = False
60
+ return params
61
+ @staticmethod
62
+ def after_config_command(result: Dict[str, Any]) -> Dict[str, Any]:
63
+ """Hook executed after config command."""
64
+ logger.info(f"🔧 Built-in hook: after_config_command with result: {result}")
65
+ # Add configuration metadata
66
+ result["config_metadata"] = {
67
+ "last_modified": datetime.now().isoformat(),
68
+ "version": "1.0.0",
69
+ "environment": "development"
70
+ }
71
+ return result
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Full Application Example
4
+ This is a complete application that demonstrates all features of MCP Proxy Adapter framework:
5
+ - Built-in commands
6
+ - Custom commands
7
+ - Dynamically loaded commands
8
+ - Built-in command hooks
9
+ - Application hooks
10
+ Author: Vasiliy Zdanovskiy
11
+ email: vasilyvz@gmail.com
12
+ """
13
+ import sys
14
+ import argparse
15
+ import logging
16
+ from pathlib import Path
17
+ # Add the framework to the path
18
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
19
+ from mcp_proxy_adapter.core.app_factory import create_and_run_server
20
+ from mcp_proxy_adapter.api.app import create_app
21
+ from mcp_proxy_adapter.config import Config
22
+ from mcp_proxy_adapter.commands.command_registry import CommandRegistry
23
+ class FullApplication:
24
+ """Full application example with all framework features."""
25
+ def __init__(self, config_path: str):
26
+ self.config_path = config_path
27
+ self.config = Config(config_path)
28
+ self.app = None
29
+ self.command_registry = None
30
+ # Setup logging
31
+ logging.basicConfig(level=logging.INFO)
32
+ self.logger = logging.getLogger(__name__)
33
+ def setup_hooks(self):
34
+ """Setup application hooks."""
35
+ try:
36
+ # Import hooks
37
+ from hooks.application_hooks import ApplicationHooks
38
+ from hooks.builtin_command_hooks import BuiltinCommandHooks
39
+ # Register application hooks
40
+ self.logger.info("🔧 Setting up application hooks...")
41
+ # Register built-in command hooks
42
+ self.logger.info("🔧 Setting up built-in command hooks...")
43
+ # Note: In a real implementation, these hooks would be registered
44
+ # with the framework's hook system
45
+ self.logger.info("✅ Hooks setup completed")
46
+ except ImportError as e:
47
+ self.logger.warning(f"⚠️ Could not import hooks: {e}")
48
+ def setup_custom_commands(self):
49
+ """Setup custom commands."""
50
+ try:
51
+ self.logger.info("🔧 Setting up custom commands...")
52
+ # Import custom commands
53
+ from commands.custom_echo_command import CustomEchoCommand
54
+ from commands.dynamic_calculator_command import DynamicCalculatorCommand
55
+ # Register custom commands
56
+ # Note: In a real implementation, these would be registered
57
+ # with the framework's command registry
58
+ self.logger.info("✅ Custom commands setup completed")
59
+ except ImportError as e:
60
+ self.logger.warning(f"⚠️ Could not import custom commands: {e}")
61
+ def setup_proxy_endpoints(self):
62
+ """Setup proxy registration endpoints."""
63
+ try:
64
+ self.logger.info("🔧 Setting up proxy endpoints...")
65
+ # Import proxy endpoints
66
+ from proxy_endpoints import router as proxy_router
67
+ # Add proxy router to the application
68
+ self.app.include_router(proxy_router)
69
+ self.logger.info("✅ Proxy endpoints setup completed")
70
+ except ImportError as e:
71
+ self.logger.warning(f"⚠️ Could not import proxy endpoints: {e}")
72
+ def create_application(self):
73
+ """Create the FastAPI application."""
74
+ self.logger.info("🔧 Creating application...")
75
+ # Setup hooks and commands before creating app
76
+ self.setup_hooks()
77
+ self.setup_custom_commands()
78
+ # Create application with configuration
79
+ self.app = create_app(app_config=self.config)
80
+ # Setup proxy endpoints after app creation
81
+ self.setup_proxy_endpoints()
82
+ self.logger.info("✅ Application created successfully")
83
+ def run(self, host: str = None, port: int = None, debug: bool = False):
84
+ """Run the application using the factory method."""
85
+ # Override configuration if specified
86
+ config_overrides = {}
87
+ if host:
88
+ config_overrides["host"] = host
89
+ if port:
90
+ config_overrides["port"] = port
91
+ if debug:
92
+ config_overrides["debug"] = True
93
+ print(f"🚀 Starting Full Application Example")
94
+ print(f"📋 Configuration: {self.config_path}")
95
+ print(f"🔧 Features: Built-in commands, Custom commands, Dynamic commands, Hooks, Proxy endpoints")
96
+ print("=" * 60)
97
+ # Create application with configuration
98
+ self.create_application()
99
+ # Get server configuration
100
+ server_host = self.config.get("server.host", "0.0.0.0")
101
+ server_port = self.config.get("server.port", 8000)
102
+ server_debug = self.config.get("server.debug", False)
103
+ # Get SSL configuration
104
+ ssl_enabled = self.config.get("ssl.enabled", False)
105
+ ssl_cert_file = self.config.get("ssl.cert_file")
106
+ ssl_key_file = self.config.get("ssl.key_file")
107
+ ssl_ca_cert = self.config.get("ssl.ca_cert")
108
+ verify_client = self.config.get("ssl.verify_client", False)
109
+ print(f"🌐 Server: {server_host}:{server_port}")
110
+ print(f"🔧 Debug: {server_debug}")
111
+ if ssl_enabled:
112
+ print(f"🔐 SSL: Enabled")
113
+ print(f" Certificate: {ssl_cert_file}")
114
+ print(f" Key: {ssl_key_file}")
115
+ if ssl_ca_cert:
116
+ print(f" CA: {ssl_ca_cert}")
117
+ print(f" Client verification: {verify_client}")
118
+ print("=" * 60)
119
+ # Use hypercorn directly to run the application with proxy endpoints
120
+ try:
121
+ import hypercorn.asyncio
122
+ import hypercorn.config
123
+ import asyncio
124
+ # Configure hypercorn
125
+ config_hypercorn = hypercorn.config.Config()
126
+ config_hypercorn.bind = [f"{server_host}:{server_port}"]
127
+ config_hypercorn.loglevel = "debug" if server_debug else "info"
128
+ if ssl_enabled and ssl_cert_file and ssl_key_file:
129
+ config_hypercorn.certfile = ssl_cert_file
130
+ config_hypercorn.keyfile = ssl_key_file
131
+ if ssl_ca_cert:
132
+ config_hypercorn.ca_certs = ssl_ca_cert
133
+ if verify_client:
134
+ import ssl
135
+ config_hypercorn.verify_mode = ssl.CERT_REQUIRED
136
+ print(f"🔐 Starting HTTPS server with hypercorn...")
137
+ else:
138
+ print(f"🌐 Starting HTTP server with hypercorn...")
139
+ # Run the server
140
+ asyncio.run(hypercorn.asyncio.serve(self.app, config_hypercorn))
141
+ except ImportError:
142
+ print("❌ hypercorn not installed. Installing...")
143
+ import subprocess
144
+ subprocess.run([sys.executable, "-m", "pip", "install", "hypercorn"])
145
+ print("✅ hypercorn installed. Please restart the application.")
146
+ return
147
+ def main():
148
+ """Main entry point for the full application example."""
149
+ parser = argparse.ArgumentParser(description="Full Application Example")
150
+ parser.add_argument("--config", "-c", required=True, help="Path to configuration file")
151
+ parser.add_argument("--host", help="Server host")
152
+ parser.add_argument("--port", type=int, help="Server port")
153
+ parser.add_argument("--debug", action="store_true", help="Enable debug mode")
154
+ args = parser.parse_args()
155
+ # Create and run application
156
+ app = FullApplication(args.config)
157
+ app.run(host=args.host, port=args.port, debug=args.debug)
158
+ # Create global app instance for import
159
+ app = None
160
+
161
+ def get_app():
162
+ """Get the FastAPI application instance."""
163
+ global app
164
+ if app is None:
165
+ # Create a default configuration for import
166
+ config = Config("configs/mtls_with_roles.json") # Default config
167
+ app_instance = FullApplication("configs/mtls_with_roles.json")
168
+ app_instance.create_application()
169
+ app = app_instance.app
170
+ return app
171
+
172
+ if __name__ == "__main__":
173
+ main()
@@ -0,0 +1,154 @@
1
+ """
2
+ Proxy Registration Endpoints
3
+ This module provides proxy registration endpoints for testing.
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+ """
7
+ from fastapi import APIRouter, HTTPException
8
+ from pydantic import BaseModel
9
+ from typing import Dict, List, Optional
10
+ import time
11
+ import uuid
12
+ # In-memory registry for testing
13
+ _registry: Dict[str, Dict] = {}
14
+ router = APIRouter(prefix="/proxy", tags=["proxy"])
15
+ class ServerRegistration(BaseModel):
16
+ """Server registration request model."""
17
+ server_id: str
18
+ server_url: str
19
+ server_name: str
20
+ description: Optional[str] = None
21
+ version: Optional[str] = "1.0.0"
22
+ capabilities: Optional[List[str]] = None
23
+ endpoints: Optional[Dict[str, str]] = None
24
+ auth_method: Optional[str] = "none"
25
+ security_enabled: Optional[bool] = False
26
+ class ServerUnregistration(BaseModel):
27
+ """Server unregistration request model."""
28
+ server_key: str # Use server_key directly
29
+ class HeartbeatData(BaseModel):
30
+ """Heartbeat data model."""
31
+ server_id: str
32
+ server_key: str
33
+ timestamp: Optional[int] = None
34
+ status: Optional[str] = "healthy"
35
+ class RegistrationResponse(BaseModel):
36
+ """Registration response model."""
37
+ success: bool
38
+ server_key: str
39
+ message: str
40
+ copy_number: int
41
+ class DiscoveryResponse(BaseModel):
42
+ """Discovery response model."""
43
+ success: bool
44
+ servers: List[Dict]
45
+ total: int
46
+ active: int
47
+ @router.post("/register", response_model=RegistrationResponse)
48
+ async def register_server(registration: ServerRegistration):
49
+ """Register a server with the proxy."""
50
+ try:
51
+ # Generate unique server key
52
+ server_key = f"{registration.server_id}_{uuid.uuid4().hex[:8]}"
53
+ copy_number = 1
54
+ # Store server information
55
+ _registry[server_key] = {
56
+ "server_id": registration.server_id,
57
+ "server_url": registration.server_url,
58
+ "server_name": registration.server_name,
59
+ "description": registration.description,
60
+ "version": registration.version,
61
+ "capabilities": registration.capabilities or [],
62
+ "endpoints": registration.endpoints or {},
63
+ "auth_method": registration.auth_method,
64
+ "security_enabled": registration.security_enabled,
65
+ "registered_at": int(time.time()),
66
+ "last_heartbeat": int(time.time()),
67
+ "status": "active"
68
+ }
69
+ return RegistrationResponse(
70
+ success=True,
71
+ server_key=server_key,
72
+ message=f"Server {registration.server_name} registered successfully",
73
+ copy_number=copy_number
74
+ )
75
+ except Exception as e:
76
+ raise HTTPException(status_code=500, detail=f"Registration failed: {str(e)}")
77
+ @router.post("/unregister")
78
+ async def unregister_server(unregistration: ServerUnregistration):
79
+ """Unregister a server from the proxy."""
80
+ try:
81
+ # Check if server exists in registry
82
+ if unregistration.server_key not in _registry:
83
+ raise HTTPException(status_code=404, detail="Server not found")
84
+ # Remove from registry
85
+ del _registry[unregistration.server_key]
86
+ return {"success": True, "message": "Server unregistered successfully"}
87
+ except HTTPException:
88
+ raise
89
+ except Exception as e:
90
+ raise HTTPException(status_code=500, detail=f"Unregistration failed: {str(e)}")
91
+ @router.post("/heartbeat")
92
+ async def send_heartbeat(heartbeat: HeartbeatData):
93
+ """Send heartbeat for a registered server."""
94
+ try:
95
+ if heartbeat.server_key not in _registry:
96
+ raise HTTPException(status_code=404, detail="Server not found")
97
+ # Update heartbeat information
98
+ _registry[heartbeat.server_key]["last_heartbeat"] = heartbeat.timestamp or int(time.time())
99
+ _registry[heartbeat.server_key]["status"] = heartbeat.status
100
+ return {"success": True, "message": "Heartbeat received"}
101
+ except HTTPException:
102
+ raise
103
+ except Exception as e:
104
+ raise HTTPException(status_code=500, detail=f"Heartbeat failed: {str(e)}")
105
+ @router.get("/discover", response_model=DiscoveryResponse)
106
+ async def discover_servers():
107
+ """Discover active servers."""
108
+ try:
109
+ current_time = int(time.time())
110
+ active_servers = []
111
+ for server_key, server in _registry.items():
112
+ # Consider server active if heartbeat was within last 5 minutes
113
+ if current_time - server["last_heartbeat"] < 300:
114
+ active_servers.append({
115
+ "server_key": server_key,
116
+ "server_id": server["server_id"],
117
+ "server_name": server["server_name"],
118
+ "server_url": server["server_url"],
119
+ "status": server["status"],
120
+ "last_heartbeat": server["last_heartbeat"]
121
+ })
122
+ return DiscoveryResponse(
123
+ success=True,
124
+ servers=active_servers,
125
+ total=len(_registry),
126
+ active=len(active_servers)
127
+ )
128
+ except Exception as e:
129
+ raise HTTPException(status_code=500, detail=f"Discovery failed: {str(e)}")
130
+ @router.get("/status")
131
+ async def get_proxy_status():
132
+ """Get proxy status."""
133
+ try:
134
+ current_time = int(time.time())
135
+ active_count = sum(
136
+ 1 for server in _registry.values()
137
+ if current_time - server["last_heartbeat"] < 300
138
+ )
139
+ return {
140
+ "success": True,
141
+ "total_registered": len(_registry),
142
+ "active_servers": active_count,
143
+ "inactive_servers": len(_registry) - active_count
144
+ }
145
+ except Exception as e:
146
+ raise HTTPException(status_code=500, detail=f"Status check failed: {str(e)}")
147
+ @router.delete("/clear")
148
+ async def clear_registry():
149
+ """Clear the registry (for testing)."""
150
+ try:
151
+ _registry.clear()
152
+ return {"success": True, "message": "Registry cleared"}
153
+ except Exception as e:
154
+ raise HTTPException(status_code=500, detail=f"Clear failed: {str(e)}")