mcp-proxy-adapter 4.1.0__py3-none-any.whl → 6.0.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.
- mcp_proxy_adapter/__main__.py +12 -0
- mcp_proxy_adapter/api/app.py +138 -11
- mcp_proxy_adapter/api/handlers.py +16 -1
- mcp_proxy_adapter/api/middleware/__init__.py +30 -29
- mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +219 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
- mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
- mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
- mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
- mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
- mcp_proxy_adapter/api/middleware/security.py +376 -0
- mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/commands/__init__.py +13 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +61 -30
- mcp_proxy_adapter/commands/builtin_commands.py +89 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +705 -345
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/health_command.py +7 -0
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +99 -2
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +827 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +11 -0
- mcp_proxy_adapter/core/protocol_manager.py +226 -0
- mcp_proxy_adapter/core/proxy_registration.py +270 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +373 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +233 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/basic_server/config.json +58 -23
- mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
- mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
- mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
- mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
- mcp_proxy_adapter/examples/basic_server/server.py +17 -1
- mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
- mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
- mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
- mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
- mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
- mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
- mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
- mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
- mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
- mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
- mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
- mcp_proxy_adapter/main.py +175 -0
- mcp_proxy_adapter/schemas/roles_schema.json +162 -0
- mcp_proxy_adapter/tests/unit/test_config.py +53 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Script to get OpenAPI schema directly from the code.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
import asyncio
|
8
|
+
import sys
|
9
|
+
import os
|
10
|
+
from pathlib import Path
|
11
|
+
|
12
|
+
# Add current directory to path for imports
|
13
|
+
sys.path.insert(0, os.getcwd())
|
14
|
+
|
15
|
+
from mcp_proxy_adapter.custom_openapi import CustomOpenAPIGenerator
|
16
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
17
|
+
|
18
|
+
async def main():
|
19
|
+
"""Generate and save OpenAPI schema."""
|
20
|
+
try:
|
21
|
+
# Initialize commands first
|
22
|
+
print("🔄 Initializing commands...")
|
23
|
+
result = await registry.reload_system()
|
24
|
+
print(f"✅ Commands initialized: {result}")
|
25
|
+
|
26
|
+
# Load custom commands manually
|
27
|
+
print("🔄 Loading custom commands...")
|
28
|
+
custom_commands = [
|
29
|
+
"echo_command",
|
30
|
+
"custom_help_command",
|
31
|
+
"custom_health_command",
|
32
|
+
"manual_echo_command"
|
33
|
+
]
|
34
|
+
|
35
|
+
for cmd_name in custom_commands:
|
36
|
+
try:
|
37
|
+
# Import the command module
|
38
|
+
module = __import__(cmd_name)
|
39
|
+
print(f"✅ Loaded custom command: {cmd_name}")
|
40
|
+
|
41
|
+
# Try to register the command if it has a command class
|
42
|
+
if hasattr(module, 'EchoCommand'):
|
43
|
+
registry.register_custom(module.EchoCommand())
|
44
|
+
print(f"✅ Registered EchoCommand")
|
45
|
+
elif hasattr(module, 'CustomHelpCommand'):
|
46
|
+
registry.register_custom(module.CustomHelpCommand())
|
47
|
+
print(f"✅ Registered CustomHelpCommand")
|
48
|
+
elif hasattr(module, 'CustomHealthCommand'):
|
49
|
+
registry.register_custom(module.CustomHealthCommand())
|
50
|
+
print(f"✅ Registered CustomHealthCommand")
|
51
|
+
elif hasattr(module, 'ManualEchoCommand'):
|
52
|
+
registry.register_custom(module.ManualEchoCommand())
|
53
|
+
print(f"✅ Registered ManualEchoCommand")
|
54
|
+
|
55
|
+
except Exception as e:
|
56
|
+
print(f"⚠️ Failed to load {cmd_name}: {e}")
|
57
|
+
|
58
|
+
# Get commands after registration
|
59
|
+
print("📋 Getting commands after registration...")
|
60
|
+
commands = registry.get_all_commands()
|
61
|
+
print(f" Total commands: {len(commands)}")
|
62
|
+
print(f" Command names: {list(commands.keys())}")
|
63
|
+
|
64
|
+
# Create generator
|
65
|
+
generator = CustomOpenAPIGenerator()
|
66
|
+
|
67
|
+
# Generate schema
|
68
|
+
schema = generator.generate(
|
69
|
+
title="Extended MCP Proxy Server",
|
70
|
+
description="Advanced MCP Proxy Adapter server with custom commands and hooks",
|
71
|
+
version="2.1.0"
|
72
|
+
)
|
73
|
+
|
74
|
+
# Save to file
|
75
|
+
with open("generated_openapi.json", "w", encoding="utf-8") as f:
|
76
|
+
json.dump(schema, f, indent=2, ensure_ascii=False)
|
77
|
+
|
78
|
+
print("✅ OpenAPI schema generated successfully!")
|
79
|
+
print(f"📄 Saved to: generated_openapi.json")
|
80
|
+
print(f"📊 Schema size: {len(json.dumps(schema))} characters")
|
81
|
+
|
82
|
+
# Show basic info
|
83
|
+
print(f"\n📋 Schema info:")
|
84
|
+
print(f" Title: {schema['info']['title']}")
|
85
|
+
print(f" Version: {schema['info']['version']}")
|
86
|
+
|
87
|
+
# Get commands info
|
88
|
+
commands = registry.get_all_commands()
|
89
|
+
print(f" Commands: {len(commands)}")
|
90
|
+
print(f" Command names: {list(commands.keys())}")
|
91
|
+
|
92
|
+
# Check CommandRequest enum
|
93
|
+
command_enum = schema['components']['schemas'].get('CommandRequest', {}).get('properties', {}).get('command', {}).get('enum', [])
|
94
|
+
print(f" Commands in schema: {len(command_enum)}")
|
95
|
+
print(f" Schema commands: {command_enum}")
|
96
|
+
|
97
|
+
except Exception as e:
|
98
|
+
print(f"❌ Error generating schema: {e}")
|
99
|
+
import traceback
|
100
|
+
traceback.print_exc()
|
101
|
+
|
102
|
+
if __name__ == "__main__":
|
103
|
+
asyncio.run(main())
|
@@ -0,0 +1,129 @@
|
|
1
|
+
"""
|
2
|
+
Test command for loadable commands testing.
|
3
|
+
|
4
|
+
This command demonstrates the loadable commands functionality.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Any, Dict
|
8
|
+
|
9
|
+
from mcp_proxy_adapter.commands.base import Command
|
10
|
+
from mcp_proxy_adapter.commands.result import SuccessResult
|
11
|
+
|
12
|
+
|
13
|
+
class TestCommandResult:
|
14
|
+
"""
|
15
|
+
Result of test command execution.
|
16
|
+
"""
|
17
|
+
|
18
|
+
def __init__(self, message: str, test_data: Dict[str, Any]):
|
19
|
+
"""
|
20
|
+
Initialize test command result.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
message: Result message
|
24
|
+
test_data: Test data
|
25
|
+
"""
|
26
|
+
self.message = message
|
27
|
+
self.test_data = test_data
|
28
|
+
|
29
|
+
def to_dict(self) -> Dict[str, Any]:
|
30
|
+
"""
|
31
|
+
Convert result to dictionary.
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
Dictionary representation of the result.
|
35
|
+
"""
|
36
|
+
return {
|
37
|
+
"success": True,
|
38
|
+
"message": self.message,
|
39
|
+
"test_data": self.test_data
|
40
|
+
}
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
def get_schema(cls) -> Dict[str, Any]:
|
44
|
+
"""
|
45
|
+
Get JSON schema for the result.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
JSON schema dictionary.
|
49
|
+
"""
|
50
|
+
return {
|
51
|
+
"type": "object",
|
52
|
+
"properties": {
|
53
|
+
"success": {
|
54
|
+
"type": "boolean",
|
55
|
+
"description": "Whether command was successful"
|
56
|
+
},
|
57
|
+
"message": {
|
58
|
+
"type": "string",
|
59
|
+
"description": "Result message"
|
60
|
+
},
|
61
|
+
"test_data": {
|
62
|
+
"type": "object",
|
63
|
+
"description": "Test data"
|
64
|
+
}
|
65
|
+
},
|
66
|
+
"required": ["success", "message", "test_data"]
|
67
|
+
}
|
68
|
+
|
69
|
+
|
70
|
+
class TestCommand(Command):
|
71
|
+
"""
|
72
|
+
Test command for loadable commands testing.
|
73
|
+
"""
|
74
|
+
|
75
|
+
name = "test"
|
76
|
+
result_class = TestCommandResult
|
77
|
+
|
78
|
+
async def execute(self, **kwargs) -> TestCommandResult:
|
79
|
+
"""
|
80
|
+
Execute test command.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
**kwargs: Command parameters
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
TestCommandResult with test information
|
87
|
+
"""
|
88
|
+
# Get parameters
|
89
|
+
test_param = kwargs.get("test_param", "default_value")
|
90
|
+
echo_text = kwargs.get("echo_text", "Hello from loadable command!")
|
91
|
+
|
92
|
+
# Create test data
|
93
|
+
test_data = {
|
94
|
+
"command_type": "loadable",
|
95
|
+
"test_param": test_param,
|
96
|
+
"echo_text": echo_text,
|
97
|
+
"timestamp": "2025-08-12T09:45:00Z",
|
98
|
+
"status": "working"
|
99
|
+
}
|
100
|
+
|
101
|
+
return TestCommandResult(
|
102
|
+
message=f"Test command executed successfully with param: {test_param}",
|
103
|
+
test_data=test_data
|
104
|
+
)
|
105
|
+
|
106
|
+
@classmethod
|
107
|
+
def get_schema(cls) -> Dict[str, Any]:
|
108
|
+
"""
|
109
|
+
Get JSON schema for command parameters.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
JSON schema dictionary.
|
113
|
+
"""
|
114
|
+
return {
|
115
|
+
"type": "object",
|
116
|
+
"properties": {
|
117
|
+
"test_param": {
|
118
|
+
"type": "string",
|
119
|
+
"description": "Test parameter",
|
120
|
+
"default": "default_value"
|
121
|
+
},
|
122
|
+
"echo_text": {
|
123
|
+
"type": "string",
|
124
|
+
"description": "Text to echo",
|
125
|
+
"default": "Hello from loadable command!"
|
126
|
+
}
|
127
|
+
},
|
128
|
+
"additionalProperties": False
|
129
|
+
}
|
@@ -0,0 +1,278 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Proxy Connection Manager
|
4
|
+
|
5
|
+
Manages connection to the proxy server with regular health checks
|
6
|
+
and automatic re-registration when needed.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import asyncio
|
10
|
+
import time
|
11
|
+
import logging
|
12
|
+
from typing import Optional, Dict, Any
|
13
|
+
from datetime import datetime, timedelta
|
14
|
+
|
15
|
+
from mcp_proxy_adapter.core.proxy_registration import register_with_proxy, unregister_from_proxy
|
16
|
+
from mcp_proxy_adapter.config import config
|
17
|
+
from mcp_proxy_adapter.core.logging import get_logger
|
18
|
+
|
19
|
+
|
20
|
+
class ProxyConnectionManager:
|
21
|
+
"""
|
22
|
+
Manages connection to proxy server with health monitoring and auto-reconnection.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, check_interval: int = 30, max_retries: int = 3):
|
26
|
+
"""
|
27
|
+
Initialize proxy connection manager.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
check_interval: Interval between health checks in seconds
|
31
|
+
max_retries: Maximum number of retry attempts
|
32
|
+
"""
|
33
|
+
self.logger = get_logger("proxy_connection_manager")
|
34
|
+
self.check_interval = check_interval
|
35
|
+
self.max_retries = max_retries
|
36
|
+
self.is_running = False
|
37
|
+
self.last_registration = None
|
38
|
+
self.registration_count = 0
|
39
|
+
self.failed_attempts = 0
|
40
|
+
self.server_url = None
|
41
|
+
self.server_id = None
|
42
|
+
|
43
|
+
# Get configuration
|
44
|
+
proxy_config = config.get("proxy_registration", {})
|
45
|
+
self.proxy_url = proxy_config.get("proxy_url", "http://localhost:3004")
|
46
|
+
self.server_id = proxy_config.get("server_id", "mcp_proxy_adapter_custom")
|
47
|
+
|
48
|
+
# Get server configuration
|
49
|
+
server_config = config.get("server", {})
|
50
|
+
server_host = server_config.get("host", "0.0.0.0")
|
51
|
+
server_port = server_config.get("port", 8000)
|
52
|
+
|
53
|
+
# Use localhost for external access if host is 0.0.0.0
|
54
|
+
if server_host == "0.0.0.0":
|
55
|
+
server_host = "localhost"
|
56
|
+
|
57
|
+
self.server_url = f"http://{server_host}:{server_port}"
|
58
|
+
|
59
|
+
self.logger.info(f"Proxy Connection Manager initialized:")
|
60
|
+
self.logger.info(f" • Server URL: {self.server_url}")
|
61
|
+
self.logger.info(f" • Proxy URL: {self.proxy_url}")
|
62
|
+
self.logger.info(f" • Server ID: {self.server_id}")
|
63
|
+
self.logger.info(f" • Check interval: {check_interval}s")
|
64
|
+
self.logger.info(f" • Max retries: {max_retries}")
|
65
|
+
|
66
|
+
async def start(self) -> None:
|
67
|
+
"""
|
68
|
+
Start the proxy connection manager.
|
69
|
+
"""
|
70
|
+
if self.is_running:
|
71
|
+
self.logger.warning("Proxy Connection Manager is already running")
|
72
|
+
return
|
73
|
+
|
74
|
+
self.is_running = True
|
75
|
+
self.logger.info("🚀 Starting Proxy Connection Manager")
|
76
|
+
|
77
|
+
# Initial registration
|
78
|
+
await self.register_with_proxy()
|
79
|
+
|
80
|
+
# Start monitoring loop
|
81
|
+
asyncio.create_task(self._monitoring_loop())
|
82
|
+
|
83
|
+
async def stop(self) -> None:
|
84
|
+
"""
|
85
|
+
Stop the proxy connection manager.
|
86
|
+
"""
|
87
|
+
if not self.is_running:
|
88
|
+
return
|
89
|
+
|
90
|
+
self.is_running = False
|
91
|
+
self.logger.info("🛑 Stopping Proxy Connection Manager")
|
92
|
+
|
93
|
+
# Unregister from proxy
|
94
|
+
await self.unregister_from_proxy()
|
95
|
+
|
96
|
+
async def register_with_proxy(self) -> bool:
|
97
|
+
"""
|
98
|
+
Register server with proxy.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
True if registration was successful, False otherwise
|
102
|
+
"""
|
103
|
+
try:
|
104
|
+
self.logger.info(f"📡 Attempting to register server with proxy at {self.proxy_url}")
|
105
|
+
|
106
|
+
success = await register_with_proxy(self.server_url)
|
107
|
+
|
108
|
+
if success:
|
109
|
+
self.last_registration = datetime.now()
|
110
|
+
self.registration_count += 1
|
111
|
+
self.failed_attempts = 0
|
112
|
+
self.logger.info(f"✅ Successfully registered with proxy (attempt #{self.registration_count})")
|
113
|
+
return True
|
114
|
+
else:
|
115
|
+
self.failed_attempts += 1
|
116
|
+
self.logger.warning(f"⚠️ Failed to register with proxy (attempt #{self.failed_attempts})")
|
117
|
+
return False
|
118
|
+
|
119
|
+
except Exception as e:
|
120
|
+
self.failed_attempts += 1
|
121
|
+
self.logger.error(f"❌ Error registering with proxy: {e}")
|
122
|
+
return False
|
123
|
+
|
124
|
+
async def unregister_from_proxy(self) -> bool:
|
125
|
+
"""
|
126
|
+
Unregister server from proxy.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
True if unregistration was successful, False otherwise
|
130
|
+
"""
|
131
|
+
try:
|
132
|
+
self.logger.info("📡 Unregistering from proxy")
|
133
|
+
|
134
|
+
success = await unregister_from_proxy()
|
135
|
+
|
136
|
+
if success:
|
137
|
+
self.logger.info("✅ Successfully unregistered from proxy")
|
138
|
+
else:
|
139
|
+
self.logger.warning("⚠️ Failed to unregister from proxy")
|
140
|
+
|
141
|
+
return success
|
142
|
+
|
143
|
+
except Exception as e:
|
144
|
+
self.logger.error(f"❌ Error unregistering from proxy: {e}")
|
145
|
+
return False
|
146
|
+
|
147
|
+
async def check_proxy_health(self) -> bool:
|
148
|
+
"""
|
149
|
+
Check if proxy is accessible and server is registered.
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
True if proxy is healthy, False otherwise
|
153
|
+
"""
|
154
|
+
try:
|
155
|
+
import httpx
|
156
|
+
|
157
|
+
# Check if proxy is accessible
|
158
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
159
|
+
response = await client.get(f"{self.proxy_url}/health")
|
160
|
+
|
161
|
+
if response.status_code == 200:
|
162
|
+
self.logger.debug("✅ Proxy health check passed")
|
163
|
+
return True
|
164
|
+
else:
|
165
|
+
self.logger.warning(f"⚠️ Proxy health check failed: {response.status_code}")
|
166
|
+
return False
|
167
|
+
|
168
|
+
except Exception as e:
|
169
|
+
self.logger.warning(f"⚠️ Proxy health check error: {e}")
|
170
|
+
return False
|
171
|
+
|
172
|
+
async def _monitoring_loop(self) -> None:
|
173
|
+
"""
|
174
|
+
Main monitoring loop that checks proxy health and re-registers if needed.
|
175
|
+
"""
|
176
|
+
self.logger.info("🔄 Starting proxy monitoring loop")
|
177
|
+
|
178
|
+
while self.is_running:
|
179
|
+
try:
|
180
|
+
# Check if we need to re-register
|
181
|
+
should_reregister = False
|
182
|
+
|
183
|
+
# Check if last registration was too long ago (more than 5 minutes)
|
184
|
+
if self.last_registration:
|
185
|
+
time_since_registration = datetime.now() - self.last_registration
|
186
|
+
if time_since_registration > timedelta(minutes=5):
|
187
|
+
self.logger.info("⏰ Last registration was more than 5 minutes ago, re-registering")
|
188
|
+
should_reregister = True
|
189
|
+
|
190
|
+
# Check proxy health
|
191
|
+
proxy_healthy = await self.check_proxy_health()
|
192
|
+
|
193
|
+
if not proxy_healthy:
|
194
|
+
self.logger.warning("⚠️ Proxy health check failed, attempting re-registration")
|
195
|
+
should_reregister = True
|
196
|
+
|
197
|
+
# Re-register if needed
|
198
|
+
if should_reregister:
|
199
|
+
if self.failed_attempts >= self.max_retries:
|
200
|
+
self.logger.error(f"❌ Max retries ({self.max_retries}) reached, stopping re-registration attempts")
|
201
|
+
break
|
202
|
+
|
203
|
+
await self.register_with_proxy()
|
204
|
+
|
205
|
+
# Log status
|
206
|
+
if self.last_registration:
|
207
|
+
time_since = datetime.now() - self.last_registration
|
208
|
+
self.logger.info(f"📊 Status: Registered {time_since.total_seconds():.0f}s ago, "
|
209
|
+
f"attempts: {self.registration_count}, "
|
210
|
+
f"failed: {self.failed_attempts}")
|
211
|
+
|
212
|
+
# Wait for next check
|
213
|
+
await asyncio.sleep(self.check_interval)
|
214
|
+
|
215
|
+
except asyncio.CancelledError:
|
216
|
+
self.logger.info("🛑 Monitoring loop cancelled")
|
217
|
+
break
|
218
|
+
except Exception as e:
|
219
|
+
self.logger.error(f"❌ Error in monitoring loop: {e}")
|
220
|
+
await asyncio.sleep(self.check_interval)
|
221
|
+
|
222
|
+
self.logger.info("🔄 Proxy monitoring loop stopped")
|
223
|
+
|
224
|
+
def get_status(self) -> Dict[str, Any]:
|
225
|
+
"""
|
226
|
+
Get current status of the connection manager.
|
227
|
+
|
228
|
+
Returns:
|
229
|
+
Dictionary with status information
|
230
|
+
"""
|
231
|
+
status = {
|
232
|
+
"is_running": self.is_running,
|
233
|
+
"server_url": self.server_url,
|
234
|
+
"proxy_url": self.proxy_url,
|
235
|
+
"server_id": self.server_id,
|
236
|
+
"registration_count": self.registration_count,
|
237
|
+
"failed_attempts": self.failed_attempts,
|
238
|
+
"check_interval": self.check_interval,
|
239
|
+
"max_retries": self.max_retries
|
240
|
+
}
|
241
|
+
|
242
|
+
if self.last_registration:
|
243
|
+
time_since = datetime.now() - self.last_registration
|
244
|
+
status["last_registration"] = self.last_registration.isoformat()
|
245
|
+
status["time_since_registration"] = time_since.total_seconds()
|
246
|
+
else:
|
247
|
+
status["last_registration"] = None
|
248
|
+
status["time_since_registration"] = None
|
249
|
+
|
250
|
+
return status
|
251
|
+
|
252
|
+
|
253
|
+
# Global instance
|
254
|
+
proxy_manager = ProxyConnectionManager()
|
255
|
+
|
256
|
+
|
257
|
+
async def start_proxy_manager() -> None:
|
258
|
+
"""
|
259
|
+
Start the global proxy connection manager.
|
260
|
+
"""
|
261
|
+
await proxy_manager.start()
|
262
|
+
|
263
|
+
|
264
|
+
async def stop_proxy_manager() -> None:
|
265
|
+
"""
|
266
|
+
Stop the global proxy connection manager.
|
267
|
+
"""
|
268
|
+
await proxy_manager.stop()
|
269
|
+
|
270
|
+
|
271
|
+
def get_proxy_manager_status() -> Dict[str, Any]:
|
272
|
+
"""
|
273
|
+
Get status of the global proxy connection manager.
|
274
|
+
|
275
|
+
Returns:
|
276
|
+
Dictionary with status information
|
277
|
+
"""
|
278
|
+
return proxy_manager.get_status()
|