mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.5__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 (129) hide show
  1. mcp_proxy_adapter/__init__.py +9 -5
  2. mcp_proxy_adapter/__main__.py +1 -1
  3. mcp_proxy_adapter/api/app.py +227 -176
  4. mcp_proxy_adapter/api/handlers.py +68 -60
  5. mcp_proxy_adapter/api/middleware/__init__.py +7 -5
  6. mcp_proxy_adapter/api/middleware/base.py +19 -16
  7. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
  8. mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
  9. mcp_proxy_adapter/api/middleware/factory.py +50 -52
  10. mcp_proxy_adapter/api/middleware/logging.py +46 -30
  11. mcp_proxy_adapter/api/middleware/performance.py +19 -16
  12. mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
  13. mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
  14. mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
  15. mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
  16. mcp_proxy_adapter/api/schemas.py +69 -43
  17. mcp_proxy_adapter/api/tool_integration.py +83 -63
  18. mcp_proxy_adapter/api/tools.py +60 -50
  19. mcp_proxy_adapter/commands/__init__.py +15 -6
  20. mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
  21. mcp_proxy_adapter/commands/base.py +108 -112
  22. mcp_proxy_adapter/commands/builtin_commands.py +28 -18
  23. mcp_proxy_adapter/commands/catalog_manager.py +394 -265
  24. mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
  25. mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
  26. mcp_proxy_adapter/commands/command_registry.py +275 -226
  27. mcp_proxy_adapter/commands/config_command.py +48 -33
  28. mcp_proxy_adapter/commands/dependency_container.py +22 -23
  29. mcp_proxy_adapter/commands/dependency_manager.py +65 -56
  30. mcp_proxy_adapter/commands/echo_command.py +15 -15
  31. mcp_proxy_adapter/commands/health_command.py +31 -29
  32. mcp_proxy_adapter/commands/help_command.py +97 -61
  33. mcp_proxy_adapter/commands/hooks.py +65 -49
  34. mcp_proxy_adapter/commands/key_management_command.py +148 -147
  35. mcp_proxy_adapter/commands/load_command.py +58 -40
  36. mcp_proxy_adapter/commands/plugins_command.py +80 -54
  37. mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
  38. mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
  39. mcp_proxy_adapter/commands/reload_command.py +43 -37
  40. mcp_proxy_adapter/commands/result.py +26 -33
  41. mcp_proxy_adapter/commands/role_test_command.py +26 -26
  42. mcp_proxy_adapter/commands/roles_management_command.py +176 -173
  43. mcp_proxy_adapter/commands/security_command.py +134 -122
  44. mcp_proxy_adapter/commands/settings_command.py +47 -56
  45. mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
  46. mcp_proxy_adapter/commands/token_management_command.py +129 -158
  47. mcp_proxy_adapter/commands/transport_management_command.py +41 -36
  48. mcp_proxy_adapter/commands/unload_command.py +42 -37
  49. mcp_proxy_adapter/config.py +36 -35
  50. mcp_proxy_adapter/core/__init__.py +19 -21
  51. mcp_proxy_adapter/core/app_factory.py +30 -9
  52. mcp_proxy_adapter/core/app_runner.py +81 -64
  53. mcp_proxy_adapter/core/auth_validator.py +176 -182
  54. mcp_proxy_adapter/core/certificate_utils.py +469 -426
  55. mcp_proxy_adapter/core/client.py +155 -126
  56. mcp_proxy_adapter/core/client_manager.py +60 -54
  57. mcp_proxy_adapter/core/client_security.py +108 -88
  58. mcp_proxy_adapter/core/config_converter.py +176 -143
  59. mcp_proxy_adapter/core/config_validator.py +12 -4
  60. mcp_proxy_adapter/core/crl_utils.py +21 -7
  61. mcp_proxy_adapter/core/errors.py +64 -20
  62. mcp_proxy_adapter/core/logging.py +34 -29
  63. mcp_proxy_adapter/core/mtls_asgi.py +29 -25
  64. mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
  65. mcp_proxy_adapter/core/protocol_manager.py +154 -104
  66. mcp_proxy_adapter/core/proxy_client.py +202 -144
  67. mcp_proxy_adapter/core/proxy_registration.py +7 -3
  68. mcp_proxy_adapter/core/role_utils.py +139 -125
  69. mcp_proxy_adapter/core/security_adapter.py +88 -77
  70. mcp_proxy_adapter/core/security_factory.py +50 -44
  71. mcp_proxy_adapter/core/security_integration.py +72 -24
  72. mcp_proxy_adapter/core/server_adapter.py +68 -64
  73. mcp_proxy_adapter/core/server_engine.py +71 -53
  74. mcp_proxy_adapter/core/settings.py +68 -58
  75. mcp_proxy_adapter/core/ssl_utils.py +69 -56
  76. mcp_proxy_adapter/core/transport_manager.py +72 -60
  77. mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
  78. mcp_proxy_adapter/core/utils.py +4 -2
  79. mcp_proxy_adapter/custom_openapi.py +107 -99
  80. mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
  81. mcp_proxy_adapter/examples/commands/__init__.py +1 -1
  82. mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
  83. mcp_proxy_adapter/examples/debug_request_state.py +38 -19
  84. mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
  85. mcp_proxy_adapter/examples/demo_client.py +48 -36
  86. mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
  87. mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
  88. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
  89. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  90. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
  91. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  92. mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
  93. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
  94. mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
  95. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
  96. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  99. mcp_proxy_adapter/examples/full_application/main.py +27 -2
  100. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
  102. mcp_proxy_adapter/examples/generate_certificates.py +31 -16
  103. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
  104. mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
  105. mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
  106. mcp_proxy_adapter/examples/run_example.py +23 -5
  107. mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
  108. mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
  109. mcp_proxy_adapter/examples/run_security_tests.py +103 -41
  110. mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
  111. mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
  112. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
  113. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
  114. mcp_proxy_adapter/examples/security_test_client.py +196 -127
  115. mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
  116. mcp_proxy_adapter/examples/test_config.py +19 -4
  117. mcp_proxy_adapter/examples/test_config_generator.py +23 -7
  118. mcp_proxy_adapter/examples/test_examples.py +84 -56
  119. mcp_proxy_adapter/examples/universal_client.py +119 -62
  120. mcp_proxy_adapter/openapi.py +108 -115
  121. mcp_proxy_adapter/utils/config_generator.py +429 -274
  122. mcp_proxy_adapter/version.py +1 -2
  123. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
  124. mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
  125. mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
  126. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
  127. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
  128. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
  129. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -4,43 +4,57 @@ This module demonstrates a dynamically loaded command implementation for the ful
4
4
  Author: Vasiliy Zdanovskiy
5
5
  email: vasilyvz@gmail.com
6
6
  """
7
+
7
8
  from typing import Dict, Any, Optional
8
9
  from mcp_proxy_adapter.commands.base import BaseCommand
9
10
  from mcp_proxy_adapter.commands.result import CommandResult
11
+
12
+
10
13
  class CalculatorResult(CommandResult):
11
14
  """Result class for calculator command."""
15
+
12
16
  def __init__(self, operation: str, result: float, expression: str):
13
17
  self.operation = operation
14
18
  self.result = result
15
19
  self.expression = expression
20
+
16
21
  def to_dict(self) -> Dict[str, Any]:
17
22
  """Convert result to dictionary."""
18
23
  return {
19
24
  "operation": self.operation,
20
25
  "result": self.result,
21
26
  "expression": self.expression,
22
- "command_type": "dynamic_calculator"
27
+ "command_type": "dynamic_calculator",
23
28
  }
29
+
24
30
  def get_schema(self) -> Dict[str, Any]:
25
31
  """Get result schema."""
26
32
  return {
27
33
  "type": "object",
28
34
  "properties": {
29
- "operation": {"type": "string", "description": "Mathematical operation"},
35
+ "operation": {
36
+ "type": "string",
37
+ "description": "Mathematical operation",
38
+ },
30
39
  "result": {"type": "number", "description": "Calculation result"},
31
40
  "expression": {"type": "string", "description": "Full expression"},
32
- "command_type": {"type": "string", "description": "Command type"}
41
+ "command_type": {"type": "string", "description": "Command type"},
33
42
  },
34
- "required": ["operation", "result", "expression", "command_type"]
43
+ "required": ["operation", "result", "expression", "command_type"],
35
44
  }
45
+
46
+
36
47
  class DynamicCalculatorCommand(BaseCommand):
37
48
  """Dynamic calculator command implementation."""
49
+
38
50
  def get_name(self) -> str:
39
51
  """Get command name."""
40
52
  return "dynamic_calculator"
53
+
41
54
  def get_description(self) -> str:
42
55
  """Get command description."""
43
56
  return "Dynamic calculator with basic mathematical operations"
57
+
44
58
  def get_schema(self) -> Dict[str, Any]:
45
59
  """Get command schema."""
46
60
  return {
@@ -49,19 +63,14 @@ class DynamicCalculatorCommand(BaseCommand):
49
63
  "operation": {
50
64
  "type": "string",
51
65
  "description": "Mathematical operation (add, subtract, multiply, divide)",
52
- "enum": ["add", "subtract", "multiply", "divide"]
53
- },
54
- "a": {
55
- "type": "number",
56
- "description": "First number"
66
+ "enum": ["add", "subtract", "multiply", "divide"],
57
67
  },
58
- "b": {
59
- "type": "number",
60
- "description": "Second number"
61
- }
68
+ "a": {"type": "number", "description": "First number"},
69
+ "b": {"type": "number", "description": "Second number"},
62
70
  },
63
- "required": ["operation", "a", "b"]
71
+ "required": ["operation", "a", "b"],
64
72
  }
73
+
65
74
  async def execute(self, params: Dict[str, Any]) -> CalculatorResult:
66
75
  """Execute the calculator command."""
67
76
  operation = params.get("operation")
@@ -84,7 +93,5 @@ class DynamicCalculatorCommand(BaseCommand):
84
93
  else:
85
94
  raise ValueError(f"Unknown operation: {operation}")
86
95
  return CalculatorResult(
87
- operation=operation,
88
- result=result,
89
- expression=expression
96
+ operation=operation, result=result, expression=expression
90
97
  )
@@ -4,12 +4,17 @@ This module demonstrates application-level hooks in the full application example
4
4
  Author: Vasiliy Zdanovskiy
5
5
  email: vasilyvz@gmail.com
6
6
  """
7
+
7
8
  import logging
8
9
  from typing import Dict, Any, Optional
9
10
  from datetime import datetime
11
+
10
12
  logger = logging.getLogger(__name__)
13
+
14
+
11
15
  class ApplicationHooks:
12
16
  """Application-level hooks."""
17
+
13
18
  @staticmethod
14
19
  def on_startup():
15
20
  """Hook executed on application startup."""
@@ -18,6 +23,7 @@ class ApplicationHooks:
18
23
  logger.info("📊 Initializing application metrics")
19
24
  logger.info("🔐 Loading security configurations")
20
25
  logger.info("📝 Setting up logging")
26
+
21
27
  @staticmethod
22
28
  def on_shutdown():
23
29
  """Hook executed on application shutdown."""
@@ -26,6 +32,7 @@ class ApplicationHooks:
26
32
  logger.info("🧹 Cleaning up resources")
27
33
  logger.info("💾 Saving application state")
28
34
  logger.info("📊 Finalizing metrics")
35
+
29
36
  @staticmethod
30
37
  def before_request(request_data: Dict[str, Any]) -> Dict[str, Any]:
31
38
  """Hook executed before processing any request."""
@@ -34,9 +41,10 @@ class ApplicationHooks:
34
41
  request_data["app_metadata"] = {
35
42
  "request_id": f"req_{datetime.now().timestamp()}",
36
43
  "timestamp": datetime.now().isoformat(),
37
- "application": "full_application_example"
44
+ "application": "full_application_example",
38
45
  }
39
46
  return request_data
47
+
40
48
  @staticmethod
41
49
  def after_request(result: Dict[str, Any]) -> Dict[str, Any]:
42
50
  """Hook executed after processing any request."""
@@ -45,9 +53,10 @@ class ApplicationHooks:
45
53
  result["app_response_metadata"] = {
46
54
  "processed_at": datetime.now().isoformat(),
47
55
  "application": "full_application_example",
48
- "version": "1.0.0"
56
+ "version": "1.0.0",
49
57
  }
50
58
  return result
59
+
51
60
  @staticmethod
52
61
  def on_error(error: Exception, context: Dict[str, Any]):
53
62
  """Hook executed when an error occurs."""
@@ -56,6 +65,7 @@ class ApplicationHooks:
56
65
  logger.error(f"Error type: {type(error).__name__}")
57
66
  logger.error(f"Error message: {str(error)}")
58
67
  logger.error(f"Context: {context}")
68
+
59
69
  @staticmethod
60
70
  def on_command_registered(command_name: str, command_info: Dict[str, Any]):
61
71
  """Hook executed when a command is registered."""
@@ -63,6 +73,7 @@ class ApplicationHooks:
63
73
  logger.info(f"Command info: {command_info}")
64
74
  # Track registered commands
65
75
  logger.info(f"📝 Command '{command_name}' registered successfully")
76
+
66
77
  @staticmethod
67
78
  def on_command_executed(command_name: str, execution_time: float, success: bool):
68
79
  """Hook executed when a command is executed."""
@@ -70,6 +81,8 @@ class ApplicationHooks:
70
81
  logger.info(f"Execution time: {execution_time}s, Success: {success}")
71
82
  # Track command execution metrics
72
83
  if success:
73
- logger.info(f"✅ Command '{command_name}' executed successfully in {execution_time}s")
84
+ logger.info(
85
+ f"✅ Command '{command_name}' executed successfully in {execution_time}s"
86
+ )
74
87
  else:
75
88
  logger.warning(f"⚠️ Command '{command_name}' failed after {execution_time}s")
@@ -4,12 +4,17 @@ This module demonstrates hooks for built-in commands in the full application exa
4
4
  Author: Vasiliy Zdanovskiy
5
5
  email: vasilyvz@gmail.com
6
6
  """
7
+
7
8
  import logging
8
9
  from typing import Dict, Any, Optional
9
10
  from datetime import datetime
11
+
10
12
  logger = logging.getLogger(__name__)
13
+
14
+
11
15
  class BuiltinCommandHooks:
12
16
  """Hooks for built-in commands."""
17
+
13
18
  @staticmethod
14
19
  def before_echo_command(params: Dict[str, Any]) -> Dict[str, Any]:
15
20
  """Hook executed before echo command."""
@@ -19,6 +24,7 @@ class BuiltinCommandHooks:
19
24
  timestamp = datetime.now().isoformat()
20
25
  params["message"] = f"[{timestamp}] {params['message']}"
21
26
  return params
27
+
22
28
  @staticmethod
23
29
  def after_echo_command(result: Dict[str, Any]) -> Dict[str, Any]:
24
30
  """Hook executed after echo command."""
@@ -27,9 +33,10 @@ class BuiltinCommandHooks:
27
33
  result["hook_metadata"] = {
28
34
  "hook_type": "builtin_after_echo",
29
35
  "timestamp": datetime.now().isoformat(),
30
- "processed": True
36
+ "processed": True,
31
37
  }
32
38
  return result
39
+
33
40
  @staticmethod
34
41
  def before_health_command(params: Dict[str, Any]) -> Dict[str, Any]:
35
42
  """Hook executed before health command."""
@@ -38,6 +45,7 @@ class BuiltinCommandHooks:
38
45
  params["include_detailed_info"] = True
39
46
  params["check_dependencies"] = True
40
47
  return params
48
+
41
49
  @staticmethod
42
50
  def after_health_command(result: Dict[str, Any]) -> Dict[str, Any]:
43
51
  """Hook executed after health command."""
@@ -47,9 +55,10 @@ class BuiltinCommandHooks:
47
55
  result["custom_metrics"] = {
48
56
  "uptime": "24h",
49
57
  "memory_usage": "45%",
50
- "cpu_usage": "12%"
58
+ "cpu_usage": "12%",
51
59
  }
52
60
  return result
61
+
53
62
  @staticmethod
54
63
  def before_config_command(params: Dict[str, Any]) -> Dict[str, Any]:
55
64
  """Hook executed before config command."""
@@ -58,6 +67,7 @@ class BuiltinCommandHooks:
58
67
  params["validate_config"] = True
59
68
  params["include_secrets"] = False
60
69
  return params
70
+
61
71
  @staticmethod
62
72
  def after_config_command(result: Dict[str, Any]) -> Dict[str, Any]:
63
73
  """Hook executed after config command."""
@@ -66,6 +76,6 @@ class BuiltinCommandHooks:
66
76
  result["config_metadata"] = {
67
77
  "last_modified": datetime.now().isoformat(),
68
78
  "version": "1.0.0",
69
- "environment": "development"
79
+ "environment": "development",
70
80
  }
71
81
  return result
@@ -14,14 +14,18 @@ import sys
14
14
  import argparse
15
15
  import logging
16
16
  from pathlib import Path
17
+
17
18
  # Add the framework to the path
18
19
  sys.path.insert(0, str(Path(__file__).parent.parent.parent))
19
20
  from mcp_proxy_adapter.core.app_factory import create_and_run_server
20
21
  from mcp_proxy_adapter.api.app import create_app
21
22
  from mcp_proxy_adapter.config import Config
22
23
  from mcp_proxy_adapter.commands.command_registry import CommandRegistry
24
+
25
+
23
26
  class FullApplication:
24
27
  """Full application example with all framework features."""
28
+
25
29
  def __init__(self, config_path: str):
26
30
  self.config_path = config_path
27
31
  self.config = Config(config_path)
@@ -30,12 +34,14 @@ class FullApplication:
30
34
  # Setup logging
31
35
  logging.basicConfig(level=logging.INFO)
32
36
  self.logger = logging.getLogger(__name__)
37
+
33
38
  def setup_hooks(self):
34
39
  """Setup application hooks."""
35
40
  try:
36
41
  # Import hooks
37
42
  from hooks.application_hooks import ApplicationHooks
38
43
  from hooks.builtin_command_hooks import BuiltinCommandHooks
44
+
39
45
  # Register application hooks
40
46
  self.logger.info("🔧 Setting up application hooks...")
41
47
  # Register built-in command hooks
@@ -45,6 +51,7 @@ class FullApplication:
45
51
  self.logger.info("✅ Hooks setup completed")
46
52
  except ImportError as e:
47
53
  self.logger.warning(f"⚠️ Could not import hooks: {e}")
54
+
48
55
  def setup_custom_commands(self):
49
56
  """Setup custom commands."""
50
57
  try:
@@ -52,23 +59,27 @@ class FullApplication:
52
59
  # Import custom commands
53
60
  from commands.custom_echo_command import CustomEchoCommand
54
61
  from commands.dynamic_calculator_command import DynamicCalculatorCommand
62
+
55
63
  # Register custom commands
56
64
  # Note: In a real implementation, these would be registered
57
65
  # with the framework's command registry
58
66
  self.logger.info("✅ Custom commands setup completed")
59
67
  except ImportError as e:
60
68
  self.logger.warning(f"⚠️ Could not import custom commands: {e}")
69
+
61
70
  def setup_proxy_endpoints(self):
62
71
  """Setup proxy registration endpoints."""
63
72
  try:
64
73
  self.logger.info("🔧 Setting up proxy endpoints...")
65
74
  # Import proxy endpoints
66
75
  from proxy_endpoints import router as proxy_router
76
+
67
77
  # Add proxy router to the application
68
78
  self.app.include_router(proxy_router)
69
79
  self.logger.info("✅ Proxy endpoints setup completed")
70
80
  except ImportError as e:
71
81
  self.logger.warning(f"⚠️ Could not import proxy endpoints: {e}")
82
+
72
83
  def create_application(self):
73
84
  """Create the FastAPI application."""
74
85
  self.logger.info("🔧 Creating application...")
@@ -80,6 +91,7 @@ class FullApplication:
80
91
  # Setup proxy endpoints after app creation
81
92
  self.setup_proxy_endpoints()
82
93
  self.logger.info("✅ Application created successfully")
94
+
83
95
  def run(self, host: str = None, port: int = None, debug: bool = False):
84
96
  """Run the application using the factory method."""
85
97
  # Override configuration if specified
@@ -92,7 +104,9 @@ class FullApplication:
92
104
  config_overrides["debug"] = True
93
105
  print(f"🚀 Starting Full Application Example")
94
106
  print(f"📋 Configuration: {self.config_path}")
95
- print(f"🔧 Features: Built-in commands, Custom commands, Dynamic commands, Hooks, Proxy endpoints")
107
+ print(
108
+ f"🔧 Features: Built-in commands, Custom commands, Dynamic commands, Hooks, Proxy endpoints"
109
+ )
96
110
  print("=" * 60)
97
111
  # Create application with configuration
98
112
  self.create_application()
@@ -121,6 +135,7 @@ class FullApplication:
121
135
  import hypercorn.asyncio
122
136
  import hypercorn.config
123
137
  import asyncio
138
+
124
139
  # Configure hypercorn
125
140
  config_hypercorn = hypercorn.config.Config()
126
141
  config_hypercorn.bind = [f"{server_host}:{server_port}"]
@@ -132,6 +147,7 @@ class FullApplication:
132
147
  config_hypercorn.ca_certs = ssl_ca_cert
133
148
  if verify_client:
134
149
  import ssl
150
+
135
151
  config_hypercorn.verify_mode = ssl.CERT_REQUIRED
136
152
  print(f"🔐 Starting HTTPS server with hypercorn...")
137
153
  else:
@@ -141,13 +157,18 @@ class FullApplication:
141
157
  except ImportError:
142
158
  print("❌ hypercorn not installed. Installing...")
143
159
  import subprocess
160
+
144
161
  subprocess.run([sys.executable, "-m", "pip", "install", "hypercorn"])
145
162
  print("✅ hypercorn installed. Please restart the application.")
146
163
  return
164
+
165
+
147
166
  def main():
148
167
  """Main entry point for the full application example."""
149
168
  parser = argparse.ArgumentParser(description="Full Application Example")
150
- parser.add_argument("--config", "-c", required=True, help="Path to configuration file")
169
+ parser.add_argument(
170
+ "--config", "-c", required=True, help="Path to configuration file"
171
+ )
151
172
  parser.add_argument("--host", help="Server host")
152
173
  parser.add_argument("--port", type=int, help="Server port")
153
174
  parser.add_argument("--debug", action="store_true", help="Enable debug mode")
@@ -155,9 +176,12 @@ def main():
155
176
  # Create and run application
156
177
  app = FullApplication(args.config)
157
178
  app.run(host=args.host, port=args.port, debug=args.debug)
179
+
180
+
158
181
  # Create global app instance for import
159
182
  app = None
160
183
 
184
+
161
185
  def get_app():
162
186
  """Get the FastAPI application instance."""
163
187
  global app
@@ -169,5 +193,6 @@ def get_app():
169
193
  app = app_instance.app
170
194
  return app
171
195
 
196
+
172
197
  if __name__ == "__main__":
173
198
  main()
@@ -4,16 +4,21 @@ This module provides proxy registration endpoints for testing.
4
4
  Author: Vasiliy Zdanovskiy
5
5
  email: vasilyvz@gmail.com
6
6
  """
7
+
7
8
  from fastapi import APIRouter, HTTPException
8
9
  from pydantic import BaseModel
9
10
  from typing import Dict, List, Optional
10
11
  import time
11
12
  import uuid
13
+
12
14
  # In-memory registry for testing
13
15
  _registry: Dict[str, Dict] = {}
14
16
  router = APIRouter(prefix="/proxy", tags=["proxy"])
17
+
18
+
15
19
  class ServerRegistration(BaseModel):
16
20
  """Server registration request model."""
21
+
17
22
  server_id: str
18
23
  server_url: str
19
24
  server_name: str
@@ -23,27 +28,41 @@ class ServerRegistration(BaseModel):
23
28
  endpoints: Optional[Dict[str, str]] = None
24
29
  auth_method: Optional[str] = "none"
25
30
  security_enabled: Optional[bool] = False
31
+
32
+
26
33
  class ServerUnregistration(BaseModel):
27
34
  """Server unregistration request model."""
35
+
28
36
  server_key: str # Use server_key directly
37
+
38
+
29
39
  class HeartbeatData(BaseModel):
30
40
  """Heartbeat data model."""
41
+
31
42
  server_id: str
32
43
  server_key: str
33
44
  timestamp: Optional[int] = None
34
45
  status: Optional[str] = "healthy"
46
+
47
+
35
48
  class RegistrationResponse(BaseModel):
36
49
  """Registration response model."""
50
+
37
51
  success: bool
38
52
  server_key: str
39
53
  message: str
40
54
  copy_number: int
55
+
56
+
41
57
  class DiscoveryResponse(BaseModel):
42
58
  """Discovery response model."""
59
+
43
60
  success: bool
44
61
  servers: List[Dict]
45
62
  total: int
46
63
  active: int
64
+
65
+
47
66
  @router.post("/register", response_model=RegistrationResponse)
48
67
  async def register_server(registration: ServerRegistration):
49
68
  """Register a server with the proxy."""
@@ -64,16 +83,18 @@ async def register_server(registration: ServerRegistration):
64
83
  "security_enabled": registration.security_enabled,
65
84
  "registered_at": int(time.time()),
66
85
  "last_heartbeat": int(time.time()),
67
- "status": "active"
86
+ "status": "active",
68
87
  }
69
88
  return RegistrationResponse(
70
89
  success=True,
71
90
  server_key=server_key,
72
91
  message=f"Server {registration.server_name} registered successfully",
73
- copy_number=copy_number
92
+ copy_number=copy_number,
74
93
  )
75
94
  except Exception as e:
76
95
  raise HTTPException(status_code=500, detail=f"Registration failed: {str(e)}")
96
+
97
+
77
98
  @router.post("/unregister")
78
99
  async def unregister_server(unregistration: ServerUnregistration):
79
100
  """Unregister a server from the proxy."""
@@ -88,6 +109,8 @@ async def unregister_server(unregistration: ServerUnregistration):
88
109
  raise
89
110
  except Exception as e:
90
111
  raise HTTPException(status_code=500, detail=f"Unregistration failed: {str(e)}")
112
+
113
+
91
114
  @router.post("/heartbeat")
92
115
  async def send_heartbeat(heartbeat: HeartbeatData):
93
116
  """Send heartbeat for a registered server."""
@@ -95,13 +118,17 @@ async def send_heartbeat(heartbeat: HeartbeatData):
95
118
  if heartbeat.server_key not in _registry:
96
119
  raise HTTPException(status_code=404, detail="Server not found")
97
120
  # Update heartbeat information
98
- _registry[heartbeat.server_key]["last_heartbeat"] = heartbeat.timestamp or int(time.time())
121
+ _registry[heartbeat.server_key]["last_heartbeat"] = heartbeat.timestamp or int(
122
+ time.time()
123
+ )
99
124
  _registry[heartbeat.server_key]["status"] = heartbeat.status
100
125
  return {"success": True, "message": "Heartbeat received"}
101
126
  except HTTPException:
102
127
  raise
103
128
  except Exception as e:
104
129
  raise HTTPException(status_code=500, detail=f"Heartbeat failed: {str(e)}")
130
+
131
+
105
132
  @router.get("/discover", response_model=DiscoveryResponse)
106
133
  async def discover_servers():
107
134
  """Discover active servers."""
@@ -111,39 +138,46 @@ async def discover_servers():
111
138
  for server_key, server in _registry.items():
112
139
  # Consider server active if heartbeat was within last 5 minutes
113
140
  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
- })
141
+ active_servers.append(
142
+ {
143
+ "server_key": server_key,
144
+ "server_id": server["server_id"],
145
+ "server_name": server["server_name"],
146
+ "server_url": server["server_url"],
147
+ "status": server["status"],
148
+ "last_heartbeat": server["last_heartbeat"],
149
+ }
150
+ )
122
151
  return DiscoveryResponse(
123
152
  success=True,
124
153
  servers=active_servers,
125
154
  total=len(_registry),
126
- active=len(active_servers)
155
+ active=len(active_servers),
127
156
  )
128
157
  except Exception as e:
129
158
  raise HTTPException(status_code=500, detail=f"Discovery failed: {str(e)}")
159
+
160
+
130
161
  @router.get("/status")
131
162
  async def get_proxy_status():
132
163
  """Get proxy status."""
133
164
  try:
134
165
  current_time = int(time.time())
135
166
  active_count = sum(
136
- 1 for server in _registry.values()
167
+ 1
168
+ for server in _registry.values()
137
169
  if current_time - server["last_heartbeat"] < 300
138
170
  )
139
171
  return {
140
172
  "success": True,
141
173
  "total_registered": len(_registry),
142
174
  "active_servers": active_count,
143
- "inactive_servers": len(_registry) - active_count
175
+ "inactive_servers": len(_registry) - active_count,
144
176
  }
145
177
  except Exception as e:
146
178
  raise HTTPException(status_code=500, detail=f"Status check failed: {str(e)}")
179
+
180
+
147
181
  @router.delete("/clear")
148
182
  async def clear_registry():
149
183
  """Clear the registry (for testing)."""
@@ -8,5 +8,6 @@ This example demonstrates advanced usage of MCP Proxy Adapter including:
8
8
  """
9
9
 
10
10
  from .main import get_app
11
+
11
12
  app = get_app()
12
13
  from .proxy_endpoints import router as proxy_router
@@ -4,23 +4,29 @@ This module demonstrates a custom command implementation for the full applicatio
4
4
  Author: Vasiliy Zdanovskiy
5
5
  email: vasilyvz@gmail.com
6
6
  """
7
+
7
8
  from typing import Dict, Any, Optional
8
9
  from mcp_proxy_adapter.commands.base import BaseCommand
9
10
  from mcp_proxy_adapter.commands.result import CommandResult
11
+
12
+
10
13
  class CustomEchoResult(CommandResult):
11
14
  """Result class for custom echo command."""
15
+
12
16
  def __init__(self, message: str, timestamp: str, echo_count: int):
13
17
  self.message = message
14
18
  self.timestamp = timestamp
15
19
  self.echo_count = echo_count
20
+
16
21
  def to_dict(self) -> Dict[str, Any]:
17
22
  """Convert result to dictionary."""
18
23
  return {
19
24
  "message": self.message,
20
25
  "timestamp": self.timestamp,
21
26
  "echo_count": self.echo_count,
22
- "command_type": "custom_echo"
27
+ "command_type": "custom_echo",
23
28
  }
29
+
24
30
  def get_schema(self) -> Dict[str, Any]:
25
31
  """Get result schema."""
26
32
  return {
@@ -29,21 +35,27 @@ class CustomEchoResult(CommandResult):
29
35
  "message": {"type": "string", "description": "Echoed message"},
30
36
  "timestamp": {"type": "string", "description": "Timestamp of echo"},
31
37
  "echo_count": {"type": "integer", "description": "Number of echoes"},
32
- "command_type": {"type": "string", "description": "Command type"}
38
+ "command_type": {"type": "string", "description": "Command type"},
33
39
  },
34
- "required": ["message", "timestamp", "echo_count", "command_type"]
40
+ "required": ["message", "timestamp", "echo_count", "command_type"],
35
41
  }
42
+
43
+
36
44
  class CustomEchoCommand(BaseCommand):
37
45
  """Custom echo command implementation."""
46
+
38
47
  def __init__(self):
39
48
  super().__init__()
40
49
  self.echo_count = 0
50
+
41
51
  def get_name(self) -> str:
42
52
  """Get command name."""
43
53
  return "custom_echo"
54
+
44
55
  def get_description(self) -> str:
45
56
  """Get command description."""
46
57
  return "Custom echo command with enhanced features"
58
+
47
59
  def get_schema(self) -> Dict[str, Any]:
48
60
  """Get command schema."""
49
61
  return {
@@ -52,29 +64,29 @@ class CustomEchoCommand(BaseCommand):
52
64
  "message": {
53
65
  "type": "string",
54
66
  "description": "Message to echo",
55
- "default": "Hello from custom echo!"
67
+ "default": "Hello from custom echo!",
56
68
  },
57
69
  "repeat": {
58
70
  "type": "integer",
59
71
  "description": "Number of times to repeat",
60
72
  "default": 1,
61
73
  "minimum": 1,
62
- "maximum": 10
63
- }
74
+ "maximum": 10,
75
+ },
64
76
  },
65
- "required": ["message"]
77
+ "required": ["message"],
66
78
  }
79
+
67
80
  async def execute(self, params: Dict[str, Any]) -> CustomEchoResult:
68
81
  """Execute the custom echo command."""
69
82
  message = params.get("message", "Hello from custom echo!")
70
83
  repeat = min(max(params.get("repeat", 1), 1), 10)
71
84
  self.echo_count += 1
72
85
  from datetime import datetime
86
+
73
87
  timestamp = datetime.now().isoformat()
74
88
  # Repeat the message
75
89
  echoed_message = " ".join([message] * repeat)
76
90
  return CustomEchoResult(
77
- message=echoed_message,
78
- timestamp=timestamp,
79
- echo_count=self.echo_count
91
+ message=echoed_message, timestamp=timestamp, echo_count=self.echo_count
80
92
  )