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,84 @@
|
|
|
1
|
+
"""Async helper routines for proxy heartbeat and unregister flows.
|
|
2
|
+
|
|
3
|
+
Author: Vasiliy Zdanovskiy
|
|
4
|
+
email: vasilyvz@gmail.com
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from typing import Any, Dict, List
|
|
11
|
+
|
|
12
|
+
from mcp_proxy_adapter.client.jsonrpc_client import JsonRpcClient
|
|
13
|
+
|
|
14
|
+
from .registration_context import HeartbeatSettings, ProxyCredentials
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_heartbeat_task(
|
|
18
|
+
proxy_url: str,
|
|
19
|
+
server_name: str,
|
|
20
|
+
server_url: str,
|
|
21
|
+
capabilities: List[str],
|
|
22
|
+
metadata: Dict[str, Any],
|
|
23
|
+
settings: HeartbeatSettings,
|
|
24
|
+
credentials: ProxyCredentials,
|
|
25
|
+
logger: Any,
|
|
26
|
+
) -> asyncio.Task:
|
|
27
|
+
"""Create and return an asyncio Task that sends heartbeats."""
|
|
28
|
+
|
|
29
|
+
interval = max(2, settings.interval)
|
|
30
|
+
|
|
31
|
+
async def heartbeat_loop() -> None:
|
|
32
|
+
client = JsonRpcClient(protocol="http", host="127.0.0.1", port=8080)
|
|
33
|
+
try:
|
|
34
|
+
|
|
35
|
+
async def _send() -> None:
|
|
36
|
+
await client.heartbeat_to_proxy(
|
|
37
|
+
proxy_url=proxy_url,
|
|
38
|
+
server_name=server_name,
|
|
39
|
+
server_url=server_url,
|
|
40
|
+
capabilities=list(capabilities),
|
|
41
|
+
metadata=metadata,
|
|
42
|
+
cert=credentials.cert,
|
|
43
|
+
verify=credentials.verify,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
while True:
|
|
47
|
+
try:
|
|
48
|
+
await asyncio.sleep(interval)
|
|
49
|
+
await _send()
|
|
50
|
+
logger.debug("\ud83d\udc93 Heartbeat sent successfully")
|
|
51
|
+
except asyncio.CancelledError:
|
|
52
|
+
raise
|
|
53
|
+
except Exception as exc: # noqa: BLE001
|
|
54
|
+
logger.error(f"Heartbeat error: {exc}")
|
|
55
|
+
except asyncio.CancelledError:
|
|
56
|
+
logger.debug("Heartbeat loop cancelled")
|
|
57
|
+
raise
|
|
58
|
+
finally:
|
|
59
|
+
await client.close()
|
|
60
|
+
|
|
61
|
+
return asyncio.create_task(heartbeat_loop())
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def unregister_from_proxy(
|
|
65
|
+
proxy_url: str,
|
|
66
|
+
server_name: str,
|
|
67
|
+
endpoint: str,
|
|
68
|
+
credentials: ProxyCredentials,
|
|
69
|
+
logger: Any,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Unregister adapter from proxy using provided credentials."""
|
|
72
|
+
|
|
73
|
+
client = JsonRpcClient(protocol="http", host="127.0.0.1", port=8080)
|
|
74
|
+
try:
|
|
75
|
+
full_url = f"{proxy_url}{endpoint}"
|
|
76
|
+
await client.unregister_from_proxy(
|
|
77
|
+
proxy_url=full_url,
|
|
78
|
+
server_name=server_name,
|
|
79
|
+
cert=credentials.cert,
|
|
80
|
+
verify=credentials.verify,
|
|
81
|
+
)
|
|
82
|
+
logger.info(f"\ud83d\udd1a Unregistered from proxy: {server_name}")
|
|
83
|
+
finally:
|
|
84
|
+
await client.close()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
SSL context factory for MCP Proxy Adapter API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ssl
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
13
|
+
from mcp_proxy_adapter.core.ssl_utils import SSLUtils
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SSLContextFactory:
|
|
17
|
+
"""Factory for creating SSL contexts."""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
"""Initialize SSL context factory."""
|
|
21
|
+
self.logger = get_global_logger()
|
|
22
|
+
|
|
23
|
+
def create_ssl_context(
|
|
24
|
+
self,
|
|
25
|
+
app_config: Optional[Dict[str, Any]] = None
|
|
26
|
+
) -> Optional[ssl.SSLContext]:
|
|
27
|
+
"""
|
|
28
|
+
Create SSL context based on configuration.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
app_config: Application configuration dictionary (optional)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
SSL context if SSL is enabled and properly configured, None otherwise
|
|
35
|
+
"""
|
|
36
|
+
from mcp_proxy_adapter.config import config
|
|
37
|
+
|
|
38
|
+
current_config = app_config if app_config is not None else config.get_all()
|
|
39
|
+
|
|
40
|
+
# Check SSL configuration from new structure
|
|
41
|
+
protocol = current_config.get("server", {}).get("protocol", "http")
|
|
42
|
+
verify_client = current_config.get("transport", {}).get("verify_client", False)
|
|
43
|
+
ssl_enabled = protocol in ["https", "mtls"] or verify_client
|
|
44
|
+
|
|
45
|
+
if not ssl_enabled:
|
|
46
|
+
self.logger.info("SSL is disabled in configuration")
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
# Get certificate paths from configuration
|
|
50
|
+
cert_file = current_config.get("transport", {}).get("cert_file")
|
|
51
|
+
key_file = current_config.get("transport", {}).get("key_file")
|
|
52
|
+
ca_cert = current_config.get("transport", {}).get("ca_cert")
|
|
53
|
+
|
|
54
|
+
# Convert relative paths to absolute paths
|
|
55
|
+
if cert_file and not Path(cert_file).is_absolute():
|
|
56
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
57
|
+
cert_file = str(project_root / cert_file)
|
|
58
|
+
if key_file and not Path(key_file).is_absolute():
|
|
59
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
60
|
+
key_file = str(project_root / key_file)
|
|
61
|
+
if ca_cert and not Path(ca_cert).is_absolute():
|
|
62
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
63
|
+
ca_cert = str(project_root / ca_cert)
|
|
64
|
+
|
|
65
|
+
if not cert_file or not key_file:
|
|
66
|
+
self.logger.warning("SSL enabled but certificate or key file not specified")
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
# Create SSL context using SSLUtils
|
|
71
|
+
ssl_context = SSLUtils.create_ssl_context(
|
|
72
|
+
cert_file=cert_file,
|
|
73
|
+
key_file=key_file,
|
|
74
|
+
ca_cert=ca_cert,
|
|
75
|
+
verify_client=current_config.get("transport", {}).get("verify_client", False),
|
|
76
|
+
cipher_suites=[],
|
|
77
|
+
min_tls_version="1.2",
|
|
78
|
+
max_tls_version="1.3",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self.logger.info(
|
|
82
|
+
f"SSL context created successfully for mode: https_only"
|
|
83
|
+
)
|
|
84
|
+
return ssl_context
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
self.logger.error(f"Failed to create SSL context: {e}")
|
|
88
|
+
return None
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
HTTP handlers for the MCP Proxy Adapter API.
|
|
6
|
+
Provides JSON-RPC handling and health/commands endpoints.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import time
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
from fastapi import Request
|
|
16
|
+
|
|
17
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
|
18
|
+
from mcp_proxy_adapter.core.errors import (
|
|
19
|
+
MicroserviceError,
|
|
20
|
+
NotFoundError,
|
|
21
|
+
InvalidRequestError,
|
|
22
|
+
MethodNotFoundError,
|
|
23
|
+
InvalidParamsError,
|
|
24
|
+
InternalError,
|
|
25
|
+
)
|
|
26
|
+
from mcp_proxy_adapter.core.logging import get_global_logger, RequestLogger
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def execute_command(
|
|
30
|
+
command_name: str,
|
|
31
|
+
params: Dict[str, Any],
|
|
32
|
+
request_id: Optional[str] = None,
|
|
33
|
+
request: Optional[Request] = None,
|
|
34
|
+
) -> Dict[str, Any]:
|
|
35
|
+
"""Execute a registered command by name with parameters.
|
|
36
|
+
|
|
37
|
+
Raises MethodNotFoundError if command is not found.
|
|
38
|
+
Wraps unexpected exceptions into InternalError.
|
|
39
|
+
"""
|
|
40
|
+
logger = RequestLogger(__name__, request_id) if request_id else get_global_logger()
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
logger.info(f"Executing command: {command_name}")
|
|
44
|
+
|
|
45
|
+
# Resolve command
|
|
46
|
+
try:
|
|
47
|
+
command_class = registry.get_command(command_name)
|
|
48
|
+
except Exception:
|
|
49
|
+
raise MethodNotFoundError(f"Method not found: {command_name}")
|
|
50
|
+
|
|
51
|
+
# Build context (user info if middleware set state)
|
|
52
|
+
context: Dict[str, Any] = {}
|
|
53
|
+
if request is not None and hasattr(request, "state"):
|
|
54
|
+
user_id = getattr(request.state, "user_id", None)
|
|
55
|
+
user_role = getattr(request.state, "user_role", None)
|
|
56
|
+
user_roles = getattr(request.state, "user_roles", None)
|
|
57
|
+
if user_id or user_role or user_roles:
|
|
58
|
+
context["user"] = {
|
|
59
|
+
"id": user_id,
|
|
60
|
+
"role": user_role,
|
|
61
|
+
"roles": user_roles or [],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Execute with timeout
|
|
65
|
+
started_at = time.time()
|
|
66
|
+
try:
|
|
67
|
+
result_obj = await asyncio.wait_for(
|
|
68
|
+
command_class.run(**params, context=context), timeout=30.0
|
|
69
|
+
)
|
|
70
|
+
except asyncio.TimeoutError:
|
|
71
|
+
elapsed = time.time() - started_at
|
|
72
|
+
raise InternalError(f"Command timed out after {elapsed:.2f}s")
|
|
73
|
+
|
|
74
|
+
elapsed = time.time() - started_at
|
|
75
|
+
logger.info(f"Command '{command_name}' executed in {elapsed:.3f}s")
|
|
76
|
+
return result_obj.to_dict()
|
|
77
|
+
|
|
78
|
+
except MicroserviceError:
|
|
79
|
+
raise
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
logger.exception(f"Unhandled error in command '{command_name}': {exc}")
|
|
82
|
+
raise InternalError("Internal error", data={"error": str(exc)})
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def handle_batch_json_rpc(
|
|
86
|
+
batch_requests: List[Dict[str, Any]], request: Optional[Request] = None
|
|
87
|
+
) -> List[Dict[str, Any]]:
|
|
88
|
+
"""Handle batch JSON-RPC requests."""
|
|
89
|
+
responses: List[Dict[str, Any]] = []
|
|
90
|
+
request_id = getattr(request.state, "request_id", None) if request else None
|
|
91
|
+
for item in batch_requests:
|
|
92
|
+
responses.append(await handle_json_rpc(item, request_id, request))
|
|
93
|
+
return responses
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def handle_json_rpc(
|
|
97
|
+
request_data: Dict[str, Any],
|
|
98
|
+
request_id: Optional[str] = None,
|
|
99
|
+
request: Optional[Request] = None,
|
|
100
|
+
) -> Dict[str, Any]:
|
|
101
|
+
"""Handle a single JSON-RPC request with strict 2.0 compatibility.
|
|
102
|
+
|
|
103
|
+
Also supports simplified form: {"command": "echo", "params": {...}}.
|
|
104
|
+
"""
|
|
105
|
+
logger = RequestLogger(__name__, request_id) if request_id else get_global_logger()
|
|
106
|
+
|
|
107
|
+
method: Optional[str]
|
|
108
|
+
params: Dict[str, Any]
|
|
109
|
+
json_rpc_id: Any
|
|
110
|
+
|
|
111
|
+
if "jsonrpc" in request_data:
|
|
112
|
+
if request_data.get("jsonrpc") != "2.0":
|
|
113
|
+
return _error_response(InvalidRequestError("Invalid Request: jsonrpc must be '2.0'"), request_data.get("id"))
|
|
114
|
+
method = request_data.get("method")
|
|
115
|
+
params = request_data.get("params", {})
|
|
116
|
+
json_rpc_id = request_data.get("id")
|
|
117
|
+
if not method:
|
|
118
|
+
return _error_response(InvalidRequestError("Invalid Request: method is required"), json_rpc_id)
|
|
119
|
+
else:
|
|
120
|
+
# Simplified
|
|
121
|
+
method = request_data.get("command")
|
|
122
|
+
params = request_data.get("params", {})
|
|
123
|
+
json_rpc_id = request_data.get("id", 1)
|
|
124
|
+
if not method:
|
|
125
|
+
return _error_response(InvalidRequestError("Invalid Request: command is required"), json_rpc_id)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
result = await execute_command(method, params, request_id, request)
|
|
129
|
+
return {"jsonrpc": "2.0", "result": result, "id": json_rpc_id}
|
|
130
|
+
except MicroserviceError as err:
|
|
131
|
+
return _error_response(err, json_rpc_id)
|
|
132
|
+
except Exception as exc:
|
|
133
|
+
logger.exception(f"Unhandled error in JSON-RPC handler: {exc}")
|
|
134
|
+
return _error_response(InternalError("Internal error", data={"error": str(exc)}), json_rpc_id)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _error_response(error: MicroserviceError, request_id: Any) -> Dict[str, Any]:
|
|
138
|
+
return {"jsonrpc": "2.0", "error": error.to_dict(), "id": request_id}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def get_server_health() -> Dict[str, Any]:
|
|
142
|
+
"""Return server health info."""
|
|
143
|
+
import os
|
|
144
|
+
import platform
|
|
145
|
+
import sys
|
|
146
|
+
import psutil
|
|
147
|
+
from datetime import datetime
|
|
148
|
+
|
|
149
|
+
process = psutil.Process(os.getpid())
|
|
150
|
+
start_time = datetime.fromtimestamp(process.create_time())
|
|
151
|
+
uptime_seconds = (datetime.now() - start_time).total_seconds()
|
|
152
|
+
mem = process.memory_info().rss / (1024 * 1024)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"status": "ok",
|
|
156
|
+
"model": "mcp-proxy-adapter",
|
|
157
|
+
"version": "1.0.0",
|
|
158
|
+
"uptime": uptime_seconds,
|
|
159
|
+
"components": {
|
|
160
|
+
"system": {
|
|
161
|
+
"python_version": sys.version,
|
|
162
|
+
"platform": platform.platform(),
|
|
163
|
+
"cpu_count": os.cpu_count(),
|
|
164
|
+
},
|
|
165
|
+
"process": {
|
|
166
|
+
"pid": os.getpid(),
|
|
167
|
+
"memory_usage_mb": mem,
|
|
168
|
+
"start_time": start_time.isoformat(),
|
|
169
|
+
},
|
|
170
|
+
"commands": {"registered_count": len(registry.get_all_commands())},
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def get_commands_list() -> Dict[str, Dict[str, Any]]:
|
|
176
|
+
"""Return list of registered commands with schemas."""
|
|
177
|
+
result: Dict[str, Dict[str, Any]] = {}
|
|
178
|
+
for name, cls in registry.get_all_commands().items():
|
|
179
|
+
schema = cls.get_schema()
|
|
180
|
+
result[name] = {"name": name, "schema": schema, "description": schema.get("description", "")}
|
|
181
|
+
return result
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Middleware package for API.
|
|
3
|
+
This package contains middleware components for request processing.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
|
|
9
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
10
|
+
from mcp_proxy_adapter.config import config
|
|
11
|
+
from .factory import MiddlewareFactory
|
|
12
|
+
# from .protocol_middleware import setup_protocol_middleware
|
|
13
|
+
|
|
14
|
+
# Export mcp_security_framework availability
|
|
15
|
+
try:
|
|
16
|
+
from .user_info_middleware import _MCP_SECURITY_AVAILABLE
|
|
17
|
+
mcp_security_framework = _MCP_SECURITY_AVAILABLE
|
|
18
|
+
except ImportError:
|
|
19
|
+
mcp_security_framework = False
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base middleware module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Callable, Awaitable
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
9
|
+
from fastapi import Request, Response
|
|
10
|
+
|
|
11
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseMiddleware(BaseHTTPMiddleware):
|
|
15
|
+
"""
|
|
16
|
+
Base class for all middleware.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def before_request(self, request: Request) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Method for processing request before calling the main handler.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
request: Request.
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
async def after_response(self, request: Request, response: Response) -> Response:
|
|
30
|
+
"""
|
|
31
|
+
Method for processing response after calling the main handler.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
request: Request.
|
|
35
|
+
response: Response.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Processed response.
|
|
39
|
+
"""
|
|
40
|
+
return response
|
|
41
|
+
|
|
42
|
+
async def handle_error(self, request: Request, exception: Exception) -> Response:
|
|
43
|
+
"""
|
|
44
|
+
Method for handling errors that occurred in middleware.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
request: Request.
|
|
48
|
+
exception: Exception.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Error response.
|
|
52
|
+
"""
|
|
53
|
+
# By default, just pass the error further
|
|
54
|
+
raise exception
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command Permission Middleware
|
|
3
|
+
|
|
4
|
+
This middleware checks permissions for specific commands based on user roles.
|
|
5
|
+
|
|
6
|
+
Author: Vasiliy Zdanovskiy
|
|
7
|
+
email: vasilyvz@gmail.com
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from fastapi import Request, Response
|
|
13
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
14
|
+
|
|
15
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CommandPermissionMiddleware(BaseHTTPMiddleware):
|
|
19
|
+
"""
|
|
20
|
+
Middleware for checking command permissions.
|
|
21
|
+
|
|
22
|
+
This middleware checks if the authenticated user has the required
|
|
23
|
+
permissions to execute specific commands.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, app, config: Dict[str, Any]):
|
|
27
|
+
"""
|
|
28
|
+
Initialize command permission middleware.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
app: FastAPI application
|
|
32
|
+
config: Configuration dictionary
|
|
33
|
+
"""
|
|
34
|
+
super().__init__(app)
|
|
35
|
+
self.config = config
|
|
36
|
+
|
|
37
|
+
# Define command permissions
|
|
38
|
+
self.command_permissions = {
|
|
39
|
+
"echo": ["read"],
|
|
40
|
+
"health": ["read"],
|
|
41
|
+
"role_test": ["read"],
|
|
42
|
+
"config": ["read"],
|
|
43
|
+
"help": ["read"],
|
|
44
|
+
# Add more commands as needed
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get_global_logger().info("Command permission middleware initialized")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _check_permissions(
|
|
51
|
+
self, user_roles: list, user_permissions: list, required_permissions: list
|
|
52
|
+
) -> bool:
|
|
53
|
+
"""
|
|
54
|
+
Check if user has required permissions.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
user_roles: User roles
|
|
58
|
+
user_permissions: User permissions
|
|
59
|
+
required_permissions: Required permissions
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True if user has required permissions
|
|
63
|
+
"""
|
|
64
|
+
# Admin has all permissions
|
|
65
|
+
if "admin" in user_roles or "*" in user_permissions:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# Check if user has all required permissions
|
|
69
|
+
for required in required_permissions:
|
|
70
|
+
if required not in user_permissions:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
return True
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Middleware for error handling.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Optional, Any
|
|
7
|
+
|
|
8
|
+
from fastapi import Request, Response
|
|
9
|
+
from starlette.responses import JSONResponse
|
|
10
|
+
|
|
11
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
12
|
+
from mcp_proxy_adapter.core.errors import (
|
|
13
|
+
MicroserviceError,
|
|
14
|
+
CommandError,
|
|
15
|
+
ValidationError,
|
|
16
|
+
)
|
|
17
|
+
from .base import BaseMiddleware
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ErrorHandlingMiddleware(BaseMiddleware):
|
|
21
|
+
"""
|
|
22
|
+
Middleware for handling and formatting errors.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, app):
|
|
26
|
+
"""
|
|
27
|
+
Initialize error handling middleware.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
app: FastAPI application
|
|
31
|
+
"""
|
|
32
|
+
super().__init__(app)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _is_json_rpc_request(self, request: Request) -> bool:
|
|
36
|
+
"""
|
|
37
|
+
Checks if request is a JSON-RPC request.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
request: Request.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if request is JSON-RPC, False otherwise.
|
|
44
|
+
"""
|
|
45
|
+
# Only requests to /api/jsonrpc are JSON-RPC requests
|
|
46
|
+
return request.url.path == "/api/jsonrpc"
|
|
47
|
+
|
|
48
|
+
async def _get_json_rpc_id(self, request: Request) -> Optional[Any]:
|
|
49
|
+
"""
|
|
50
|
+
Gets JSON-RPC request ID.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
request: Request.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
JSON-RPC request ID if available, None otherwise.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
# Use request state to avoid body parsing if already done
|
|
60
|
+
if hasattr(request.state, "json_rpc_id"):
|
|
61
|
+
return request.state.json_rpc_id
|
|
62
|
+
|
|
63
|
+
# Parse request body
|
|
64
|
+
body = await request.body()
|
|
65
|
+
if body:
|
|
66
|
+
body_text = body.decode("utf-8")
|
|
67
|
+
body_json = json.loads(body_text)
|
|
68
|
+
request_id = body_json.get("id")
|
|
69
|
+
|
|
70
|
+
# Save ID in request state to avoid reparsing
|
|
71
|
+
request.state.json_rpc_id = request_id
|
|
72
|
+
return request_id
|
|
73
|
+
except Exception as e:
|
|
74
|
+
get_global_logger().warning(f"Error parsing JSON-RPC ID: {str(e)}")
|
|
75
|
+
|
|
76
|
+
return None
|