mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.6__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/__init__.py +9 -5
- mcp_proxy_adapter/__main__.py +1 -1
- mcp_proxy_adapter/api/app.py +227 -176
- mcp_proxy_adapter/api/handlers.py +68 -60
- mcp_proxy_adapter/api/middleware/__init__.py +7 -5
- mcp_proxy_adapter/api/middleware/base.py +19 -16
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
- mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
- mcp_proxy_adapter/api/middleware/factory.py +50 -52
- mcp_proxy_adapter/api/middleware/logging.py +46 -30
- mcp_proxy_adapter/api/middleware/performance.py +19 -16
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
- mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
- mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
- mcp_proxy_adapter/api/schemas.py +69 -43
- mcp_proxy_adapter/api/tool_integration.py +83 -63
- mcp_proxy_adapter/api/tools.py +60 -50
- mcp_proxy_adapter/commands/__init__.py +15 -6
- mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
- mcp_proxy_adapter/commands/base.py +108 -112
- mcp_proxy_adapter/commands/builtin_commands.py +28 -18
- mcp_proxy_adapter/commands/catalog_manager.py +394 -265
- mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
- mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
- mcp_proxy_adapter/commands/command_registry.py +275 -226
- mcp_proxy_adapter/commands/config_command.py +48 -33
- mcp_proxy_adapter/commands/dependency_container.py +22 -23
- mcp_proxy_adapter/commands/dependency_manager.py +65 -56
- mcp_proxy_adapter/commands/echo_command.py +15 -15
- mcp_proxy_adapter/commands/health_command.py +31 -29
- mcp_proxy_adapter/commands/help_command.py +97 -61
- mcp_proxy_adapter/commands/hooks.py +65 -49
- mcp_proxy_adapter/commands/key_management_command.py +148 -147
- mcp_proxy_adapter/commands/load_command.py +58 -40
- mcp_proxy_adapter/commands/plugins_command.py +80 -54
- mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
- mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
- mcp_proxy_adapter/commands/reload_command.py +43 -37
- mcp_proxy_adapter/commands/result.py +26 -33
- mcp_proxy_adapter/commands/role_test_command.py +26 -26
- mcp_proxy_adapter/commands/roles_management_command.py +176 -173
- mcp_proxy_adapter/commands/security_command.py +134 -122
- mcp_proxy_adapter/commands/settings_command.py +47 -56
- mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
- mcp_proxy_adapter/commands/token_management_command.py +129 -158
- mcp_proxy_adapter/commands/transport_management_command.py +41 -36
- mcp_proxy_adapter/commands/unload_command.py +42 -37
- mcp_proxy_adapter/config.py +36 -35
- mcp_proxy_adapter/core/__init__.py +19 -21
- mcp_proxy_adapter/core/app_factory.py +30 -9
- mcp_proxy_adapter/core/app_runner.py +81 -64
- mcp_proxy_adapter/core/auth_validator.py +176 -182
- mcp_proxy_adapter/core/certificate_utils.py +469 -426
- mcp_proxy_adapter/core/client.py +155 -126
- mcp_proxy_adapter/core/client_manager.py +60 -54
- mcp_proxy_adapter/core/client_security.py +120 -91
- mcp_proxy_adapter/core/config_converter.py +176 -143
- mcp_proxy_adapter/core/config_validator.py +12 -4
- mcp_proxy_adapter/core/crl_utils.py +21 -7
- mcp_proxy_adapter/core/errors.py +64 -20
- mcp_proxy_adapter/core/logging.py +34 -29
- mcp_proxy_adapter/core/mtls_asgi.py +29 -25
- mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
- mcp_proxy_adapter/core/protocol_manager.py +154 -104
- mcp_proxy_adapter/core/proxy_client.py +202 -144
- mcp_proxy_adapter/core/proxy_registration.py +7 -3
- mcp_proxy_adapter/core/role_utils.py +139 -125
- mcp_proxy_adapter/core/security_adapter.py +88 -77
- mcp_proxy_adapter/core/security_factory.py +50 -44
- mcp_proxy_adapter/core/security_integration.py +72 -24
- mcp_proxy_adapter/core/server_adapter.py +68 -64
- mcp_proxy_adapter/core/server_engine.py +71 -53
- mcp_proxy_adapter/core/settings.py +68 -58
- mcp_proxy_adapter/core/ssl_utils.py +69 -56
- mcp_proxy_adapter/core/transport_manager.py +72 -60
- mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
- mcp_proxy_adapter/core/utils.py +4 -2
- mcp_proxy_adapter/custom_openapi.py +107 -99
- mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
- mcp_proxy_adapter/examples/debug_request_state.py +38 -19
- mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
- mcp_proxy_adapter/examples/demo_client.py +48 -36
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
- mcp_proxy_adapter/examples/generate_certificates.py +31 -16
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
- mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
- mcp_proxy_adapter/examples/run_example.py +23 -5
- mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
- mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
- mcp_proxy_adapter/examples/run_security_tests.py +103 -41
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
- mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/security_test_client.py +196 -127
- mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
- mcp_proxy_adapter/examples/test_config.py +19 -4
- mcp_proxy_adapter/examples/test_config_generator.py +23 -7
- mcp_proxy_adapter/examples/test_examples.py +84 -56
- mcp_proxy_adapter/examples/universal_client.py +119 -62
- mcp_proxy_adapter/openapi.py +108 -115
- mcp_proxy_adapter/utils/config_generator.py +429 -274
- mcp_proxy_adapter/version.py +1 -2
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.6.dist-info/RECORD +144 -0
- mcp_proxy_adapter-6.3.6.dist-info/top_level.txt +2 -0
- mcp_proxy_adapter_issue_package/demonstrate_issue.py +178 -0
- mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
- mcp_proxy_adapter-6.3.4.dist-info/top_level.txt +0 -1
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/licenses/LICENSE +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": {
|
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
|
-
"
|
59
|
-
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
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)."""
|
@@ -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
|
)
|