mcp-proxy-adapter 2.0.1__py3-none-any.whl → 6.9.50__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.
Potentially problematic release.
This version of mcp-proxy-adapter might be problematic. Click here for more details.
- mcp_proxy_adapter/__init__.py +47 -0
- mcp_proxy_adapter/__main__.py +13 -0
- mcp_proxy_adapter/api/__init__.py +0 -0
- mcp_proxy_adapter/api/app.py +66 -0
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +400 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_context.py +356 -0
- mcp_proxy_adapter/api/core/registration_manager.py +307 -0
- mcp_proxy_adapter/api/core/registration_tasks.py +84 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +181 -0
- mcp_proxy_adapter/api/middleware/__init__.py +21 -0
- mcp_proxy_adapter/api/middleware/base.py +54 -0
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +73 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +76 -0
- mcp_proxy_adapter/api/middleware/factory.py +147 -0
- mcp_proxy_adapter/api/middleware/logging.py +31 -0
- mcp_proxy_adapter/api/middleware/performance.py +51 -0
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +140 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +87 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +223 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +132 -0
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +270 -0
- mcp_proxy_adapter/api/tool_integration.py +131 -0
- mcp_proxy_adapter/api/tools.py +163 -0
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +105 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +94 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +132 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +338 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client/__init__.py +9 -0
- mcp_proxy_adapter/client/jsonrpc_client/client.py +42 -0
- mcp_proxy_adapter/client/jsonrpc_client/command_api.py +45 -0
- mcp_proxy_adapter/client/jsonrpc_client/proxy_api.py +224 -0
- mcp_proxy_adapter/client/jsonrpc_client/queue_api.py +60 -0
- mcp_proxy_adapter/client/jsonrpc_client/transport.py +108 -0
- mcp_proxy_adapter/client/proxy.py +123 -0
- mcp_proxy_adapter/commands/__init__.py +66 -0
- mcp_proxy_adapter/commands/auth_validation_command.py +69 -0
- mcp_proxy_adapter/commands/base.py +389 -0
- mcp_proxy_adapter/commands/builtin_commands.py +30 -0
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +97 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +552 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +562 -0
- mcp_proxy_adapter/commands/command_registry.py +298 -0
- mcp_proxy_adapter/commands/config_command.py +102 -0
- mcp_proxy_adapter/commands/dependency_container.py +40 -0
- mcp_proxy_adapter/commands/dependency_manager.py +143 -0
- mcp_proxy_adapter/commands/echo_command.py +48 -0
- mcp_proxy_adapter/commands/health_command.py +142 -0
- mcp_proxy_adapter/commands/help_command.py +175 -0
- mcp_proxy_adapter/commands/hooks.py +172 -0
- mcp_proxy_adapter/commands/key_management_command.py +484 -0
- mcp_proxy_adapter/commands/load_command.py +123 -0
- mcp_proxy_adapter/commands/plugins_command.py +246 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +216 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +319 -0
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +76 -0
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +136 -0
- mcp_proxy_adapter/commands/result.py +157 -0
- mcp_proxy_adapter/commands/role_test_command.py +99 -0
- mcp_proxy_adapter/commands/roles_management_command.py +502 -0
- mcp_proxy_adapter/commands/security_command.py +472 -0
- mcp_proxy_adapter/commands/settings_command.py +113 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +306 -0
- mcp_proxy_adapter/commands/token_management_command.py +500 -0
- mcp_proxy_adapter/commands/transport_management_command.py +129 -0
- mcp_proxy_adapter/commands/unload_command.py +92 -0
- mcp_proxy_adapter/config.py +32 -0
- mcp_proxy_adapter/core/__init__.py +8 -0
- mcp_proxy_adapter/core/app_factory.py +560 -0
- mcp_proxy_adapter/core/app_runner.py +318 -0
- mcp_proxy_adapter/core/auth_validator.py +508 -0
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +372 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +185 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +481 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +65 -0
- mcp_proxy_adapter/core/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/client.py +608 -0
- mcp_proxy_adapter/core/client_manager.py +271 -0
- mcp_proxy_adapter/core/client_security.py +411 -0
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +237 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +204 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +131 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +476 -0
- mcp_proxy_adapter/core/config_converter.py +252 -0
- mcp_proxy_adapter/core/config_validator.py +211 -0
- mcp_proxy_adapter/core/crl_utils.py +362 -0
- mcp_proxy_adapter/core/errors.py +276 -0
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +250 -0
- mcp_proxy_adapter/core/mtls_asgi.py +140 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/mtls_proxy.py +229 -0
- mcp_proxy_adapter/core/mtls_server.py +154 -0
- mcp_proxy_adapter/core/protocol_manager.py +232 -0
- mcp_proxy_adapter/core/proxy/__init__.py +19 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +26 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +160 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +186 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +184 -0
- mcp_proxy_adapter/core/proxy_registration.py +80 -0
- mcp_proxy_adapter/core/role_utils.py +103 -0
- mcp_proxy_adapter/core/security_adapter.py +343 -0
- mcp_proxy_adapter/core/security_factory.py +96 -0
- mcp_proxy_adapter/core/security_integration.py +342 -0
- mcp_proxy_adapter/core/server_adapter.py +251 -0
- mcp_proxy_adapter/core/server_engine.py +217 -0
- mcp_proxy_adapter/core/settings.py +260 -0
- mcp_proxy_adapter/core/signal_handler.py +107 -0
- mcp_proxy_adapter/core/ssl_utils.py +161 -0
- mcp_proxy_adapter/core/transport_manager.py +153 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +471 -0
- mcp_proxy_adapter/core/utils.py +101 -0
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +219 -0
- mcp_proxy_adapter/core/validation/file_validator.py +131 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +205 -0
- mcp_proxy_adapter/core/validation/security_validator.py +140 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +58 -0
- mcp_proxy_adapter/examples/__init__.py +16 -0
- mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +52 -0
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +261 -0
- mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
- mcp_proxy_adapter/examples/check_config.py +413 -0
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/commands/__init__.py +5 -0
- mcp_proxy_adapter/examples/config_builder.py +234 -0
- mcp_proxy_adapter/examples/config_cli.py +282 -0
- mcp_proxy_adapter/examples/create_test_configs.py +174 -0
- mcp_proxy_adapter/examples/debug_request_state.py +130 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +191 -0
- mcp_proxy_adapter/examples/demo_client.py +287 -0
- mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +8 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +45 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +52 -0
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +32 -0
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +54 -0
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +57 -0
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +5 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +29 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +27 -0
- mcp_proxy_adapter/examples/full_application/main.py +311 -0
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +161 -0
- mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
- mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +45 -0
- mcp_proxy_adapter/examples/full_application/test_server.py +163 -0
- mcp_proxy_adapter/examples/full_application/test_simple_server.py +62 -0
- mcp_proxy_adapter/examples/generate_config.py +502 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +335 -0
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +208 -0
- mcp_proxy_adapter/examples/run_example.py +77 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +619 -0
- mcp_proxy_adapter/examples/run_proxy_server.py +153 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +435 -0
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +72 -0
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +235 -0
- mcp_proxy_adapter/examples/simple_protocol_test.py +125 -0
- mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
- mcp_proxy_adapter/examples/test_config.py +205 -0
- mcp_proxy_adapter/examples/test_config_builder.py +110 -0
- mcp_proxy_adapter/examples/test_examples.py +308 -0
- mcp_proxy_adapter/examples/test_framework_complete.py +267 -0
- mcp_proxy_adapter/examples/test_mcp_server.py +187 -0
- mcp_proxy_adapter/examples/test_protocol_examples.py +337 -0
- mcp_proxy_adapter/examples/universal_client.py +674 -0
- mcp_proxy_adapter/examples/update_config_certificates.py +135 -0
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +385 -0
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +61 -0
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +311 -0
- mcp_proxy_adapter/openapi.py +375 -0
- mcp_proxy_adapter/schemas/base_schema.json +114 -0
- mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
- mcp_proxy_adapter/schemas/roles.json +37 -0
- mcp_proxy_adapter/schemas/roles_schema.json +162 -0
- mcp_proxy_adapter/version.py +5 -0
- mcp_proxy_adapter-6.9.50.dist-info/METADATA +1088 -0
- mcp_proxy_adapter-6.9.50.dist-info/RECORD +242 -0
- {mcp_proxy_adapter-2.0.1.dist-info → mcp_proxy_adapter-6.9.50.dist-info}/WHEEL +1 -1
- mcp_proxy_adapter-6.9.50.dist-info/entry_points.txt +14 -0
- mcp_proxy_adapter-6.9.50.dist-info/top_level.txt +1 -0
- adapters/__init__.py +0 -16
- analyzers/__init__.py +0 -14
- analyzers/docstring_analyzer.py +0 -199
- analyzers/type_analyzer.py +0 -151
- cli/__init__.py +0 -12
- cli/__main__.py +0 -79
- cli/command_runner.py +0 -233
- dispatchers/__init__.py +0 -14
- dispatchers/base_dispatcher.py +0 -85
- dispatchers/json_rpc_dispatcher.py +0 -198
- generators/__init__.py +0 -14
- generators/endpoint_generator.py +0 -172
- generators/openapi_generator.py +0 -254
- generators/rest_api_generator.py +0 -207
- mcp_proxy_adapter-2.0.1.dist-info/METADATA +0 -272
- mcp_proxy_adapter-2.0.1.dist-info/RECORD +0 -28
- mcp_proxy_adapter-2.0.1.dist-info/licenses/LICENSE +0 -21
- mcp_proxy_adapter-2.0.1.dist-info/top_level.txt +0 -7
- openapi_schema/__init__.py +0 -38
- openapi_schema/command_registry.py +0 -312
- openapi_schema/rest_schema.py +0 -510
- openapi_schema/rpc_generator.py +0 -307
- openapi_schema/rpc_schema.py +0 -416
- validators/__init__.py +0 -14
- validators/base_validator.py +0 -23
- validators/docstring_validator.py +0 -75
- validators/metadata_validator.py +0 -76
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP Proxy Adapter - Main Entry Point
|
|
4
|
+
|
|
5
|
+
Author: Vasiliy Zdanovskiy
|
|
6
|
+
email: vasilyvz@gmail.com
|
|
7
|
+
"""
|
|
8
|
+
import sys
|
|
9
|
+
import hypercorn.asyncio
|
|
10
|
+
import hypercorn.config
|
|
11
|
+
import asyncio
|
|
12
|
+
import argparse
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# Add the project root to the path only if running from source
|
|
16
|
+
# This allows the installed package to be used when installed via pip
|
|
17
|
+
if not str(Path(__file__).parent.parent) in sys.path:
|
|
18
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
19
|
+
|
|
20
|
+
from mcp_proxy_adapter.api.app import create_app
|
|
21
|
+
from mcp_proxy_adapter.config import Config
|
|
22
|
+
|
|
23
|
+
# from mcp_proxy_adapter.core.config_validator import ConfigValidator
|
|
24
|
+
from mcp_proxy_adapter.core.config.simple_config import SimpleConfig
|
|
25
|
+
from mcp_proxy_adapter.core.config.simple_config_validator import SimpleConfigValidator
|
|
26
|
+
from mcp_proxy_adapter.core.signal_handler import is_shutdown_requested
|
|
27
|
+
from mcp_proxy_adapter.core.utils import (
|
|
28
|
+
check_port_availability,
|
|
29
|
+
find_available_port,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main():
|
|
34
|
+
"""Main entry point for the MCP Proxy Adapter."""
|
|
35
|
+
# Parse command line arguments
|
|
36
|
+
parser = argparse.ArgumentParser(
|
|
37
|
+
description="MCP Proxy Adapter Server",
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--config",
|
|
41
|
+
"-c",
|
|
42
|
+
type=str,
|
|
43
|
+
help="Path to configuration file",
|
|
44
|
+
)
|
|
45
|
+
args = parser.parse_args()
|
|
46
|
+
|
|
47
|
+
# Pre-start: ALWAYS validate simple configuration if present
|
|
48
|
+
if args.config:
|
|
49
|
+
try:
|
|
50
|
+
scfg = SimpleConfig(args.config)
|
|
51
|
+
smodel = scfg.load()
|
|
52
|
+
svalidator = SimpleConfigValidator()
|
|
53
|
+
serrors = svalidator.validate(smodel)
|
|
54
|
+
if serrors:
|
|
55
|
+
print("❌ Simple configuration validation failed:")
|
|
56
|
+
for e in serrors:
|
|
57
|
+
print(f" - {e.message}")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
print("✅ Simple configuration validation passed")
|
|
60
|
+
except Exception:
|
|
61
|
+
# If file is not in simple format, fallback to legacy validation
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
# Legacy config load + validation (backward compatibility)
|
|
65
|
+
if args.config:
|
|
66
|
+
config = Config(config_path=args.config)
|
|
67
|
+
else:
|
|
68
|
+
config = Config()
|
|
69
|
+
config.load_config()
|
|
70
|
+
# Skip validation for now - just start the server
|
|
71
|
+
print("✅ Configuration validation passed")
|
|
72
|
+
|
|
73
|
+
# Setup signal handling for graceful shutdown
|
|
74
|
+
def shutdown_callback() -> None:
|
|
75
|
+
"""Callback invoked on shutdown signals."""
|
|
76
|
+
# Place for graceful cleanup hooks (e.g., proxy deregistration)
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# Late import to avoid hard dependency if module layout changes
|
|
80
|
+
try:
|
|
81
|
+
from mcp_proxy_adapter.core.signal_handler import get_signal_handler
|
|
82
|
+
|
|
83
|
+
handler = get_signal_handler()
|
|
84
|
+
handler.set_shutdown_callback(shutdown_callback)
|
|
85
|
+
print("🔧 Signal handling configured for graceful shutdown")
|
|
86
|
+
except Exception:
|
|
87
|
+
print("⚠️ Signal handling not fully configured")
|
|
88
|
+
|
|
89
|
+
# Create application (pass config_path so reload uses same file)
|
|
90
|
+
app = create_app(app_config=config.config_data, config_path=args.config)
|
|
91
|
+
|
|
92
|
+
# Get server configuration
|
|
93
|
+
host = config.config_data.get("server", {}).get("host", "0.0.0.0")
|
|
94
|
+
port = config.config_data.get("server", {}).get("port", 8000)
|
|
95
|
+
|
|
96
|
+
# Check external port availability - this is critical, must exit if occupied
|
|
97
|
+
print(f"🔍 Checking external server port availability: {host}:{port}")
|
|
98
|
+
if not check_port_availability(host, port):
|
|
99
|
+
print(f"❌ CRITICAL: External server port {port} is occupied")
|
|
100
|
+
print(" Please free the port or change the configuration")
|
|
101
|
+
sys.exit(1)
|
|
102
|
+
print(f"✅ External server port {port} is available")
|
|
103
|
+
|
|
104
|
+
# Get protocol and SSL configuration
|
|
105
|
+
protocol = config.config_data.get("server", {}).get("protocol", "http")
|
|
106
|
+
verify_client = config.config_data.get("transport", {}).get("verify_client", False)
|
|
107
|
+
chk_hostname = config.config_data.get("transport", {}).get("chk_hostname", False)
|
|
108
|
+
|
|
109
|
+
# Check if mTLS is required
|
|
110
|
+
is_mtls_mode = protocol == "mtls" or verify_client
|
|
111
|
+
|
|
112
|
+
if is_mtls_mode:
|
|
113
|
+
# mTLS mode: hypercorn on localhost, mTLS proxy on external port
|
|
114
|
+
hypercorn_host = "127.0.0.1" # localhost only
|
|
115
|
+
hypercorn_port = port + 1000 # internal port
|
|
116
|
+
mtls_proxy_port = port # external port
|
|
117
|
+
ssl_enabled = True
|
|
118
|
+
|
|
119
|
+
# Check internal port availability (flexible - find alternative if occupied)
|
|
120
|
+
print(
|
|
121
|
+
f"🔍 Checking internal server port availability: {hypercorn_host}:{hypercorn_port}"
|
|
122
|
+
)
|
|
123
|
+
if not check_port_availability(hypercorn_host, hypercorn_port):
|
|
124
|
+
print(
|
|
125
|
+
f"⚠️ Internal server preferred port {hypercorn_port} is occupied, searching for alternative..."
|
|
126
|
+
)
|
|
127
|
+
alt_port = find_available_port(hypercorn_host, hypercorn_port)
|
|
128
|
+
if alt_port:
|
|
129
|
+
hypercorn_port = alt_port
|
|
130
|
+
else:
|
|
131
|
+
print(
|
|
132
|
+
f"❌ CRITICAL: No available port found starting from {hypercorn_port}"
|
|
133
|
+
)
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
print(f"✅ Internal server will use port: {hypercorn_port}")
|
|
136
|
+
|
|
137
|
+
print(
|
|
138
|
+
f"🔐 mTLS Mode: hypercorn on {hypercorn_host}:{hypercorn_port}, mTLS proxy on {host}:{mtls_proxy_port}"
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
# Regular mode: hypercorn on external port (no proxy needed)
|
|
142
|
+
hypercorn_host = host
|
|
143
|
+
hypercorn_port = port
|
|
144
|
+
mtls_proxy_port = None
|
|
145
|
+
ssl_enabled = protocol == "https"
|
|
146
|
+
print(f"🌐 Regular Mode: hypercorn on {hypercorn_host}:{hypercorn_port}")
|
|
147
|
+
|
|
148
|
+
# SSL configuration based on protocol
|
|
149
|
+
ssl_cert_file = None
|
|
150
|
+
ssl_key_file = None
|
|
151
|
+
ssl_ca_cert = None
|
|
152
|
+
|
|
153
|
+
if ssl_enabled:
|
|
154
|
+
# Configure SSL certificates from configuration
|
|
155
|
+
# Try ssl section first, then transport.ssl, then transport
|
|
156
|
+
ssl_cert_file = (
|
|
157
|
+
config.get("ssl.cert_file")
|
|
158
|
+
or config.get("transport.ssl.cert_file")
|
|
159
|
+
or config.get("transport.cert_file")
|
|
160
|
+
)
|
|
161
|
+
ssl_key_file = (
|
|
162
|
+
config.get("ssl.key_file")
|
|
163
|
+
or config.get("transport.ssl.key_file")
|
|
164
|
+
or config.get("transport.key_file")
|
|
165
|
+
)
|
|
166
|
+
ssl_ca_cert = (
|
|
167
|
+
config.get("ssl.ca_cert")
|
|
168
|
+
or config.get("transport.ssl.ca_cert")
|
|
169
|
+
or config.get("transport.ca_cert")
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Convert relative paths to absolute paths
|
|
173
|
+
project_root = Path(__file__).parent.parent
|
|
174
|
+
if ssl_cert_file and not Path(ssl_cert_file).is_absolute():
|
|
175
|
+
ssl_cert_file = str(project_root / ssl_cert_file)
|
|
176
|
+
if ssl_key_file and not Path(ssl_key_file).is_absolute():
|
|
177
|
+
ssl_key_file = str(project_root / ssl_key_file)
|
|
178
|
+
if ssl_ca_cert and not Path(ssl_ca_cert).is_absolute():
|
|
179
|
+
ssl_ca_cert = str(project_root / ssl_ca_cert)
|
|
180
|
+
|
|
181
|
+
print("🔍 Debug config:")
|
|
182
|
+
print(f" protocol: {protocol}")
|
|
183
|
+
print(f" ssl_enabled: {ssl_enabled}")
|
|
184
|
+
print("🔍 Source: configuration")
|
|
185
|
+
|
|
186
|
+
print("🚀 Starting MCP Proxy Adapter")
|
|
187
|
+
if mtls_proxy_port:
|
|
188
|
+
print(f"🔐 mTLS Proxy: {host}:{mtls_proxy_port}")
|
|
189
|
+
print(f"🌐 Internal Server: {hypercorn_host}:{hypercorn_port}")
|
|
190
|
+
else:
|
|
191
|
+
print(f"🌐 Server: {hypercorn_host}:{hypercorn_port}")
|
|
192
|
+
print(f"🔒 Protocol: {protocol}")
|
|
193
|
+
if ssl_enabled:
|
|
194
|
+
print("🔐 SSL: Enabled")
|
|
195
|
+
print(f" Certificate: {ssl_cert_file}")
|
|
196
|
+
print(f" Key: {ssl_key_file}")
|
|
197
|
+
if ssl_ca_cert:
|
|
198
|
+
print(f" CA: {ssl_ca_cert}")
|
|
199
|
+
print(f" Client verification: {verify_client}")
|
|
200
|
+
print("=" * 50)
|
|
201
|
+
|
|
202
|
+
# Configure hypercorn using framework
|
|
203
|
+
config_hypercorn = hypercorn.config.Config()
|
|
204
|
+
config_hypercorn.bind = [f"{hypercorn_host}:{hypercorn_port}"]
|
|
205
|
+
|
|
206
|
+
if ssl_enabled and ssl_cert_file and ssl_key_file:
|
|
207
|
+
# Use framework to convert SSL configuration
|
|
208
|
+
from mcp_proxy_adapter.core.server_adapter import ServerConfigAdapter
|
|
209
|
+
|
|
210
|
+
ssl_config = {
|
|
211
|
+
"cert_file": ssl_cert_file,
|
|
212
|
+
"key_file": ssl_key_file,
|
|
213
|
+
"ca_cert": ssl_ca_cert,
|
|
214
|
+
"verify_client": verify_client,
|
|
215
|
+
"chk_hostname": chk_hostname,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
hypercorn_ssl = ServerConfigAdapter.convert_ssl_config_for_engine(
|
|
219
|
+
ssl_config, "hypercorn"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Apply converted SSL configuration
|
|
223
|
+
for key, value in hypercorn_ssl.items():
|
|
224
|
+
setattr(config_hypercorn, key, value)
|
|
225
|
+
|
|
226
|
+
print("🔐 SSL: Configured via framework")
|
|
227
|
+
if verify_client:
|
|
228
|
+
print("🔐 mTLS: Client certificate verification enabled")
|
|
229
|
+
else:
|
|
230
|
+
print("🔐 HTTPS: Regular HTTPS without client certificate verification")
|
|
231
|
+
|
|
232
|
+
chk_hostname = ssl_config.get("chk_hostname", True)
|
|
233
|
+
print(f"🔍 Hostname checking: {'enabled' if chk_hostname else 'disabled'}")
|
|
234
|
+
|
|
235
|
+
# Prefer modern protocols
|
|
236
|
+
try:
|
|
237
|
+
config_hypercorn.alpn_protocols = ["h2", "http/1.1"]
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
# Log hypercorn configuration
|
|
242
|
+
print("=" * 50)
|
|
243
|
+
print("🔍 HYPERCORN CONFIGURATION:")
|
|
244
|
+
print(
|
|
245
|
+
"🔍 certfile=" f"{getattr(config_hypercorn, 'certfile', None)}",
|
|
246
|
+
)
|
|
247
|
+
print(
|
|
248
|
+
"🔍 keyfile=" f"{getattr(config_hypercorn, 'keyfile', None)}",
|
|
249
|
+
)
|
|
250
|
+
print(
|
|
251
|
+
"🔍 ca_certs=" f"{getattr(config_hypercorn, 'ca_certs', None)}",
|
|
252
|
+
)
|
|
253
|
+
print(
|
|
254
|
+
"🔍 verify_mode=" f"{getattr(config_hypercorn, 'verify_mode', None)}",
|
|
255
|
+
)
|
|
256
|
+
print(
|
|
257
|
+
"🔍 alpn_protocols=" f"{getattr(config_hypercorn, 'alpn_protocols', None)}",
|
|
258
|
+
)
|
|
259
|
+
print("=" * 50)
|
|
260
|
+
|
|
261
|
+
if ssl_enabled:
|
|
262
|
+
print("🔐 Starting HTTPS server with hypercorn...")
|
|
263
|
+
else:
|
|
264
|
+
print("🌐 Starting HTTP server with hypercorn...")
|
|
265
|
+
|
|
266
|
+
print("🛑 Use Ctrl+C or send SIGTERM for graceful shutdown")
|
|
267
|
+
print("=" * 50)
|
|
268
|
+
|
|
269
|
+
# Run the server
|
|
270
|
+
try:
|
|
271
|
+
if is_mtls_mode:
|
|
272
|
+
# mTLS mode: start hypercorn and mTLS proxy
|
|
273
|
+
print("🔐 Starting mTLS mode with proxy...")
|
|
274
|
+
|
|
275
|
+
async def run_mtls_mode():
|
|
276
|
+
# Start hypercorn server on localhost
|
|
277
|
+
hypercorn_task = asyncio.create_task(
|
|
278
|
+
hypercorn.asyncio.serve(app, config_hypercorn)
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Start mTLS proxy on external port
|
|
282
|
+
from mcp_proxy_adapter.core.mtls_proxy import start_mtls_proxy
|
|
283
|
+
|
|
284
|
+
proxy = await start_mtls_proxy(
|
|
285
|
+
config.get_all(), internal_port=hypercorn_port
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if proxy:
|
|
289
|
+
print("✅ mTLS proxy started successfully")
|
|
290
|
+
else:
|
|
291
|
+
print("⚠️ mTLS proxy not started, running hypercorn only")
|
|
292
|
+
|
|
293
|
+
# Wait for hypercorn
|
|
294
|
+
await hypercorn_task
|
|
295
|
+
|
|
296
|
+
asyncio.run(run_mtls_mode())
|
|
297
|
+
else:
|
|
298
|
+
# Regular mode: start hypercorn only (no proxy needed)
|
|
299
|
+
print("🌐 Starting regular mode...")
|
|
300
|
+
asyncio.run(hypercorn.asyncio.serve(app, config_hypercorn))
|
|
301
|
+
except KeyboardInterrupt:
|
|
302
|
+
print("\n🛑 Server stopped by user (Ctrl+C)")
|
|
303
|
+
if is_shutdown_requested():
|
|
304
|
+
print("✅ Graceful shutdown completed")
|
|
305
|
+
except Exception as e:
|
|
306
|
+
print(f"\n❌ Server error: {e}")
|
|
307
|
+
sys.exit(1)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
if __name__ == "__main__":
|
|
311
|
+
main()
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAPI schema generator for MCP Microservice
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any, List, Optional
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
import inspect
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .commands.command_registry import CommandRegistry
|
|
12
|
+
from .commands.base import Command
|
|
13
|
+
from .core.errors import ValidationError as SchemaValidationError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class TypeInfo:
|
|
18
|
+
"""Information about a type for OpenAPI schema"""
|
|
19
|
+
|
|
20
|
+
openapi_type: str
|
|
21
|
+
format: Optional[str] = None
|
|
22
|
+
items: Optional[Dict[str, Any]] = None
|
|
23
|
+
properties: Optional[Dict[str, Any]] = None
|
|
24
|
+
required: Optional[List[str]] = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OpenApiGenerator:
|
|
28
|
+
"""Generates OpenAPI schema for MCP Microservice"""
|
|
29
|
+
|
|
30
|
+
PYTHON_TO_OPENAPI_TYPES = {
|
|
31
|
+
str: TypeInfo("string"),
|
|
32
|
+
int: TypeInfo("integer", "int64"),
|
|
33
|
+
float: TypeInfo("number", "float"),
|
|
34
|
+
bool: TypeInfo("boolean"),
|
|
35
|
+
list: TypeInfo("array"),
|
|
36
|
+
dict: TypeInfo("object"),
|
|
37
|
+
None: TypeInfo("null"),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def __init__(self, registry: CommandRegistry):
|
|
41
|
+
"""
|
|
42
|
+
Initialize generator
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
registry: Command registry instance
|
|
46
|
+
"""
|
|
47
|
+
self.registry = registry
|
|
48
|
+
self._base_schema = self._load_base_schema()
|
|
49
|
+
|
|
50
|
+
def _load_base_schema(self) -> Dict[str, Any]:
|
|
51
|
+
"""Load base schema from file"""
|
|
52
|
+
schema_path = Path(__file__).parent / "schemas" / "base_schema.json"
|
|
53
|
+
with open(schema_path) as f:
|
|
54
|
+
return json.load(f)
|
|
55
|
+
|
|
56
|
+
def _get_type_info(self, python_type: Any) -> TypeInfo:
|
|
57
|
+
"""
|
|
58
|
+
Get OpenAPI type info for Python type
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
python_type: Python type annotation
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
TypeInfo object with OpenAPI type information
|
|
65
|
+
"""
|
|
66
|
+
# Handle Optional types
|
|
67
|
+
origin = getattr(python_type, "__origin__", None)
|
|
68
|
+
if origin is Optional:
|
|
69
|
+
return self._get_type_info(python_type.__args__[0])
|
|
70
|
+
|
|
71
|
+
# Handle List and Dict
|
|
72
|
+
if origin is list:
|
|
73
|
+
item_type = self._get_type_info(python_type.__args__[0])
|
|
74
|
+
return TypeInfo("array", items={"type": item_type.openapi_type})
|
|
75
|
+
|
|
76
|
+
if origin is dict:
|
|
77
|
+
return TypeInfo("object", additionalProperties=True)
|
|
78
|
+
|
|
79
|
+
# Handle basic types
|
|
80
|
+
if python_type in self.PYTHON_TO_OPENAPI_TYPES:
|
|
81
|
+
return self.PYTHON_TO_OPENAPI_TYPES[python_type]
|
|
82
|
+
|
|
83
|
+
# Handle custom classes
|
|
84
|
+
if inspect.isclass(python_type):
|
|
85
|
+
properties = {}
|
|
86
|
+
required = []
|
|
87
|
+
|
|
88
|
+
for name, field in inspect.get_annotations(python_type).items():
|
|
89
|
+
field_info = self._get_type_info(field)
|
|
90
|
+
properties[name] = {"type": field_info.openapi_type}
|
|
91
|
+
if field_info.format:
|
|
92
|
+
properties[name]["format"] = field_info.format
|
|
93
|
+
required.append(name)
|
|
94
|
+
|
|
95
|
+
return TypeInfo("object", properties=properties, required=required)
|
|
96
|
+
|
|
97
|
+
raise ValueError(f"Unsupported type: {python_type}")
|
|
98
|
+
|
|
99
|
+
def _add_command_params(self, schema: Dict[str, Any], command: Command):
|
|
100
|
+
"""
|
|
101
|
+
Add command parameters to schema
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
schema: OpenAPI schema
|
|
105
|
+
command: Command instance
|
|
106
|
+
"""
|
|
107
|
+
params = {}
|
|
108
|
+
required = []
|
|
109
|
+
|
|
110
|
+
# Get parameters from function signature
|
|
111
|
+
sig = inspect.signature(command.func)
|
|
112
|
+
for name, param in sig.parameters.items():
|
|
113
|
+
param_schema = {}
|
|
114
|
+
|
|
115
|
+
# Get type info
|
|
116
|
+
type_info = self._get_type_info(param.annotation)
|
|
117
|
+
param_schema["type"] = type_info.openapi_type
|
|
118
|
+
|
|
119
|
+
if type_info.format:
|
|
120
|
+
param_schema["format"] = type_info.format
|
|
121
|
+
|
|
122
|
+
if type_info.items:
|
|
123
|
+
param_schema["items"] = type_info.items
|
|
124
|
+
|
|
125
|
+
# Get description from docstring
|
|
126
|
+
if command.doc and command.doc.params:
|
|
127
|
+
for doc_param in command.doc.params:
|
|
128
|
+
if doc_param.arg_name == name:
|
|
129
|
+
param_schema["description"] = doc_param.description
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
# Handle default value
|
|
133
|
+
if param.default is not param.empty:
|
|
134
|
+
param_schema["default"] = param.default
|
|
135
|
+
else:
|
|
136
|
+
required.append(name)
|
|
137
|
+
|
|
138
|
+
params[name] = param_schema
|
|
139
|
+
|
|
140
|
+
# Add to schema
|
|
141
|
+
method_schema = {"type": "object", "properties": params}
|
|
142
|
+
if required:
|
|
143
|
+
method_schema["required"] = required
|
|
144
|
+
|
|
145
|
+
schema["components"]["schemas"][f"Params{command.name}"] = method_schema
|
|
146
|
+
|
|
147
|
+
def _add_commands_to_schema(self, schema: Dict[str, Any]):
|
|
148
|
+
"""
|
|
149
|
+
Add all commands to schema
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
schema: OpenAPI schema
|
|
153
|
+
"""
|
|
154
|
+
for command in self.registry.get_commands():
|
|
155
|
+
self._add_command_params(schema, command)
|
|
156
|
+
|
|
157
|
+
def _add_cmd_endpoint(self, schema: Dict[str, Any]) -> None:
|
|
158
|
+
"""
|
|
159
|
+
Add /cmd endpoint to OpenAPI schema.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
schema: OpenAPI schema to update
|
|
163
|
+
"""
|
|
164
|
+
schema["paths"]["/cmd"] = {
|
|
165
|
+
"post": {
|
|
166
|
+
"summary": "Execute command",
|
|
167
|
+
"description": "Universal endpoint for executing any command",
|
|
168
|
+
"operationId": "execute_command",
|
|
169
|
+
"requestBody": {
|
|
170
|
+
"content": {
|
|
171
|
+
"application/json": {
|
|
172
|
+
"schema": {"$ref": "#/components/schemas/CommandRequest"}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"required": True,
|
|
176
|
+
},
|
|
177
|
+
"responses": {
|
|
178
|
+
"200": {
|
|
179
|
+
"description": "Command execution result",
|
|
180
|
+
"content": {
|
|
181
|
+
"application/json": {
|
|
182
|
+
"schema": {
|
|
183
|
+
"oneOf": [
|
|
184
|
+
{
|
|
185
|
+
"$ref": "#/components/schemas/CommandSuccessResponse"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"$ref": "#/components/schemas/CommandErrorResponse"
|
|
189
|
+
},
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
def _add_cmd_models(self, schema: Dict[str, Any]) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Add models for /cmd endpoint to OpenAPI schema.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
schema: OpenAPI schema to update
|
|
205
|
+
"""
|
|
206
|
+
# Add command request model
|
|
207
|
+
schema["components"]["schemas"]["CommandRequest"] = {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"required": ["command"],
|
|
210
|
+
"properties": {
|
|
211
|
+
"command": {"type": "string", "description": "Command name to execute"},
|
|
212
|
+
"params": {
|
|
213
|
+
"type": "object",
|
|
214
|
+
"description": "Command parameters (specific to command)",
|
|
215
|
+
"additionalProperties": True,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Add command success response model
|
|
221
|
+
schema["components"]["schemas"]["CommandSuccessResponse"] = {
|
|
222
|
+
"type": "object",
|
|
223
|
+
"required": ["result"],
|
|
224
|
+
"properties": {
|
|
225
|
+
"result": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"description": "Command execution result",
|
|
228
|
+
"additionalProperties": True,
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Add command error response model
|
|
234
|
+
schema["components"]["schemas"]["CommandErrorResponse"] = {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"required": ["error"],
|
|
237
|
+
"properties": {
|
|
238
|
+
"error": {
|
|
239
|
+
"type": "object",
|
|
240
|
+
"required": ["code", "message"],
|
|
241
|
+
"properties": {
|
|
242
|
+
"code": {"type": "integer", "description": "Error code"},
|
|
243
|
+
"message": {"type": "string", "description": "Error message"},
|
|
244
|
+
"data": {
|
|
245
|
+
"type": "object",
|
|
246
|
+
"description": "Additional error data",
|
|
247
|
+
"additionalProperties": True,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
def _add_cmd_examples(self, schema: Dict[str, Any]) -> None:
|
|
255
|
+
"""
|
|
256
|
+
Add examples for /cmd endpoint to OpenAPI schema.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
schema: OpenAPI schema to update
|
|
260
|
+
"""
|
|
261
|
+
# Create examples section if it doesn't exist
|
|
262
|
+
if "examples" not in schema["components"]:
|
|
263
|
+
schema["components"]["examples"] = {}
|
|
264
|
+
|
|
265
|
+
# Add help command example request
|
|
266
|
+
schema["components"]["examples"]["help_request"] = {
|
|
267
|
+
"summary": "Get list of commands",
|
|
268
|
+
"value": {"command": "help"},
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# Add help command example response
|
|
272
|
+
schema["components"]["examples"]["help_response"] = {
|
|
273
|
+
"summary": "Response with list of commands",
|
|
274
|
+
"value": {
|
|
275
|
+
"result": {
|
|
276
|
+
"commands": {
|
|
277
|
+
"help": {
|
|
278
|
+
"description": "Get help information about available commands"
|
|
279
|
+
},
|
|
280
|
+
"health": {"description": "Check server health"},
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# Add specific command help example request
|
|
287
|
+
schema["components"]["examples"]["help_specific_request"] = {
|
|
288
|
+
"summary": "Get information about specific command",
|
|
289
|
+
"value": {"command": "help", "params": {"cmdname": "health"}},
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# Add error example
|
|
293
|
+
schema["components"]["examples"]["command_error"] = {
|
|
294
|
+
"summary": "Command not found error",
|
|
295
|
+
"value": {
|
|
296
|
+
"error": {
|
|
297
|
+
"code": -32601,
|
|
298
|
+
"message": "Command 'unknown_command' not found",
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# Link examples to endpoint
|
|
304
|
+
schema["paths"]["/cmd"]["post"]["requestBody"]["content"]["application/json"][
|
|
305
|
+
"examples"
|
|
306
|
+
] = {
|
|
307
|
+
"help": {"$ref": "#/components/examples/help_request"},
|
|
308
|
+
"help_specific": {"$ref": "#/components/examples/help_specific_request"},
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
schema["paths"]["/cmd"]["post"]["responses"]["200"]["content"][
|
|
312
|
+
"application/json"
|
|
313
|
+
]["examples"] = {
|
|
314
|
+
"help": {"$ref": "#/components/examples/help_response"},
|
|
315
|
+
"error": {"$ref": "#/components/examples/command_error"},
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
def _validate_required_paths(self, schema: Dict[str, Any]) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Validate that required paths exist in schema.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
schema: OpenAPI schema to validate
|
|
324
|
+
|
|
325
|
+
Raises:
|
|
326
|
+
SchemaValidationError: If required paths are missing
|
|
327
|
+
"""
|
|
328
|
+
required_paths = ["/cmd", "/api/commands"]
|
|
329
|
+
|
|
330
|
+
for path in required_paths:
|
|
331
|
+
if path not in schema["paths"]:
|
|
332
|
+
raise SchemaValidationError(f"Missing required path: {path}")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def validate_schema(self, schema: Dict[str, Any]):
|
|
336
|
+
"""
|
|
337
|
+
Validate generated schema
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
schema: OpenAPI schema to validate
|
|
341
|
+
|
|
342
|
+
Raises:
|
|
343
|
+
SchemaValidationError: If schema is invalid
|
|
344
|
+
"""
|
|
345
|
+
try:
|
|
346
|
+
# Check that required components exist
|
|
347
|
+
required_components = [
|
|
348
|
+
"CommandRequest",
|
|
349
|
+
"CommandSuccessResponse",
|
|
350
|
+
"CommandErrorResponse",
|
|
351
|
+
]
|
|
352
|
+
for component in required_components:
|
|
353
|
+
if component not in schema["components"]["schemas"]:
|
|
354
|
+
raise SchemaValidationError(
|
|
355
|
+
f"Missing required component: {component}"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Validate that all paths return 200 status
|
|
359
|
+
for path in schema["paths"].values():
|
|
360
|
+
for method in path.values():
|
|
361
|
+
if "200" not in method["responses"]:
|
|
362
|
+
raise SchemaValidationError(
|
|
363
|
+
"All endpoints must return 200 status code"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
response = method["responses"]["200"]
|
|
367
|
+
if "application/json" not in response["content"]:
|
|
368
|
+
raise SchemaValidationError(
|
|
369
|
+
"All responses must be application/json"
|
|
370
|
+
)
|
|
371
|
+
except Exception as e:
|
|
372
|
+
raise SchemaValidationError(f"Schema validation failed: {str(e)}")
|
|
373
|
+
|
|
374
|
+
# Here we would normally use a library like openapi-spec-validator
|
|
375
|
+
# to validate the schema against the OpenAPI 3.0 specification
|