mcp-proxy-adapter 6.9.27__py3-none-any.whl → 6.9.29__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 +10 -0
- mcp_proxy_adapter/__main__.py +8 -21
- mcp_proxy_adapter/api/app.py +10 -913
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +243 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_manager.py +166 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +78 -199
- mcp_proxy_adapter/api/middleware/__init__.py +1 -44
- mcp_proxy_adapter/api/middleware/base.py +0 -42
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
- mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
- mcp_proxy_adapter/api/middleware/factory.py +0 -94
- mcp_proxy_adapter/api/middleware/logging.py +0 -112
- mcp_proxy_adapter/api/middleware/performance.py +0 -35
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
- mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
- mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
- 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 +0 -61
- mcp_proxy_adapter/api/tool_integration.py +0 -117
- mcp_proxy_adapter/api/tools.py +0 -46
- 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 +21 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +36 -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 +128 -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 +324 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
- mcp_proxy_adapter/client/proxy.py +45 -0
- mcp_proxy_adapter/commands/__init__.py +44 -28
- mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
- mcp_proxy_adapter/commands/base.py +19 -43
- mcp_proxy_adapter/commands/builtin_commands.py +0 -75
- 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 +58 -928
- mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
- mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
- mcp_proxy_adapter/commands/command_registry.py +172 -904
- mcp_proxy_adapter/commands/config_command.py +0 -28
- mcp_proxy_adapter/commands/dependency_container.py +1 -70
- mcp_proxy_adapter/commands/dependency_manager.py +0 -128
- mcp_proxy_adapter/commands/echo_command.py +0 -34
- mcp_proxy_adapter/commands/health_command.py +0 -3
- mcp_proxy_adapter/commands/help_command.py +0 -159
- mcp_proxy_adapter/commands/hooks.py +0 -137
- mcp_proxy_adapter/commands/key_management_command.py +0 -25
- mcp_proxy_adapter/commands/load_command.py +7 -78
- mcp_proxy_adapter/commands/plugins_command.py +0 -16
- mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
- mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +0 -43
- 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 +0 -80
- mcp_proxy_adapter/commands/result.py +25 -77
- mcp_proxy_adapter/commands/role_test_command.py +0 -44
- mcp_proxy_adapter/commands/roles_management_command.py +0 -199
- mcp_proxy_adapter/commands/security_command.py +0 -30
- mcp_proxy_adapter/commands/settings_command.py +0 -68
- mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
- mcp_proxy_adapter/commands/token_management_command.py +0 -1
- mcp_proxy_adapter/commands/transport_management_command.py +0 -20
- mcp_proxy_adapter/commands/unload_command.py +0 -71
- mcp_proxy_adapter/config.py +15 -626
- mcp_proxy_adapter/core/__init__.py +5 -39
- mcp_proxy_adapter/core/app_factory.py +14 -36
- mcp_proxy_adapter/core/app_runner.py +0 -27
- mcp_proxy_adapter/core/auth_validator.py +1 -93
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
- mcp_proxy_adapter/core/certificate_utils.py +64 -903
- mcp_proxy_adapter/core/client.py +0 -6
- mcp_proxy_adapter/core/client_manager.py +0 -19
- mcp_proxy_adapter/core/client_security.py +0 -2
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +195 -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 +112 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
- mcp_proxy_adapter/core/config_converter.py +0 -186
- mcp_proxy_adapter/core/config_validator.py +96 -1238
- mcp_proxy_adapter/core/errors.py +7 -42
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +2 -22
- mcp_proxy_adapter/core/mtls_asgi.py +0 -20
- mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
- mcp_proxy_adapter/core/mtls_proxy.py +0 -80
- mcp_proxy_adapter/core/mtls_server.py +3 -173
- mcp_proxy_adapter/core/protocol_manager.py +1 -191
- mcp_proxy_adapter/core/proxy/__init__.py +22 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +0 -1
- mcp_proxy_adapter/core/proxy_registration.py +36 -912
- mcp_proxy_adapter/core/role_utils.py +0 -308
- mcp_proxy_adapter/core/security_adapter.py +1 -36
- mcp_proxy_adapter/core/security_factory.py +1 -150
- mcp_proxy_adapter/core/security_integration.py +0 -33
- mcp_proxy_adapter/core/server_adapter.py +1 -40
- mcp_proxy_adapter/core/server_engine.py +2 -173
- mcp_proxy_adapter/core/settings.py +0 -127
- mcp_proxy_adapter/core/signal_handler.py +0 -65
- mcp_proxy_adapter/core/ssl_utils.py +19 -137
- mcp_proxy_adapter/core/transport_manager.py +0 -151
- mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
- mcp_proxy_adapter/core/utils.py +1 -182
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +211 -0
- mcp_proxy_adapter/core/validation/file_validator.py +73 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
- mcp_proxy_adapter/core/validation/security_validator.py +58 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +33 -652
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
- mcp_proxy_adapter/examples/check_config.py +0 -2
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/config_builder.py +13 -2
- mcp_proxy_adapter/examples/config_cli.py +0 -1
- mcp_proxy_adapter/examples/create_test_configs.py +0 -46
- mcp_proxy_adapter/examples/debug_request_state.py +0 -1
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
- mcp_proxy_adapter/examples/full_application/main.py +186 -150
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
- mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
- mcp_proxy_adapter/examples/generate_config.py +65 -11
- 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 +0 -2
- mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
- mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
- 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 +24 -1075
- 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 +133 -1425
- mcp_proxy_adapter/examples/test_config.py +0 -3
- mcp_proxy_adapter/examples/test_config_builder.py +25 -405
- mcp_proxy_adapter/examples/test_examples.py +0 -1
- mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
- mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
- mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
- mcp_proxy_adapter/examples/universal_client.py +0 -6
- mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +70 -62
- mcp_proxy_adapter/openapi.py +0 -22
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.27.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
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.
|
|
3
7
|
"""
|
|
4
8
|
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
5
11
|
import asyncio
|
|
6
|
-
import json
|
|
7
12
|
import time
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
9
14
|
|
|
10
|
-
from fastapi import
|
|
11
|
-
from fastapi.responses import JSONResponse
|
|
15
|
+
from fastapi import Request
|
|
12
16
|
|
|
13
17
|
from mcp_proxy_adapter.commands.command_registry import registry
|
|
14
18
|
from mcp_proxy_adapter.core.errors import (
|
|
15
19
|
MicroserviceError,
|
|
16
20
|
NotFoundError,
|
|
17
|
-
ParseError,
|
|
18
21
|
InvalidRequestError,
|
|
19
22
|
MethodNotFoundError,
|
|
20
23
|
InvalidParamsError,
|
|
21
24
|
InternalError,
|
|
22
|
-
CommandError,
|
|
23
25
|
)
|
|
24
|
-
from mcp_proxy_adapter.core.logging import get_global_logger, RequestLogger
|
|
26
|
+
from mcp_proxy_adapter.core.logging import get_global_logger, RequestLogger
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
async def execute_command(
|
|
@@ -30,113 +32,64 @@ async def execute_command(
|
|
|
30
32
|
request_id: Optional[str] = None,
|
|
31
33
|
request: Optional[Request] = None,
|
|
32
34
|
) -> Dict[str, Any]:
|
|
33
|
-
"""
|
|
34
|
-
Executes a command with the specified name and parameters.
|
|
35
|
+
"""Execute a registered command by name with parameters.
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
params: Command parameters.
|
|
39
|
-
request_id: Optional request identifier for logging context.
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
Command execution result.
|
|
43
|
-
|
|
44
|
-
Raises:
|
|
45
|
-
MethodNotFoundError: If command is not found.
|
|
46
|
-
MicroserviceError: In case of command execution error.
|
|
37
|
+
Raises MethodNotFoundError if command is not found.
|
|
38
|
+
Wraps unexpected exceptions into InternalError.
|
|
47
39
|
"""
|
|
48
|
-
|
|
49
|
-
log = RequestLogger(__name__, request_id) if request_id else get_global_logger()
|
|
40
|
+
logger = RequestLogger(__name__, request_id) if request_id else get_global_logger()
|
|
50
41
|
|
|
51
42
|
try:
|
|
52
|
-
|
|
43
|
+
logger.info(f"Executing command: {command_name}")
|
|
53
44
|
|
|
54
|
-
#
|
|
45
|
+
# Resolve command
|
|
55
46
|
try:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"roles": getattr(request.state, "user_roles", ["guest"]),
|
|
76
|
-
"permissions": getattr(request.state, "user_permissions", ["read"]),
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
# Add timeout to prevent hanging commands
|
|
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()
|
|
80
66
|
try:
|
|
81
|
-
|
|
82
|
-
command_class.run(**params, context=context),
|
|
83
|
-
timeout=10.0 # 10 seconds timeout
|
|
67
|
+
result_obj = await asyncio.wait_for(
|
|
68
|
+
command_class.run(**params, context=context), timeout=30.0
|
|
84
69
|
)
|
|
85
70
|
except asyncio.TimeoutError:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
raise InternalError(f"Command execution timed out after 10 seconds")
|
|
71
|
+
elapsed = time.time() - started_at
|
|
72
|
+
raise InternalError(f"Command timed out after {elapsed:.2f}s")
|
|
89
73
|
|
|
90
|
-
|
|
74
|
+
elapsed = time.time() - started_at
|
|
75
|
+
logger.info(f"Command '{command_name}' executed in {elapsed:.3f}s")
|
|
76
|
+
return result_obj.to_dict()
|
|
91
77
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
log.debug(f"Executed after command hooks for: {command_name}")
|
|
98
|
-
except Exception as e:
|
|
99
|
-
log.warning(f"Failed to execute after command hooks: {e}")
|
|
100
|
-
|
|
101
|
-
# Return result
|
|
102
|
-
return result.to_dict()
|
|
103
|
-
except NotFoundError as e:
|
|
104
|
-
log.error(f"Command not found: {command_name}")
|
|
105
|
-
# Преобразуем в MethodNotFoundError для соответствия JSON-RPC
|
|
106
|
-
raise MethodNotFoundError(f"Method not found: {command_name}")
|
|
107
|
-
except Exception as e:
|
|
108
|
-
log.exception(f"Error executing command '{command_name}': {e}")
|
|
109
|
-
if isinstance(e, MicroserviceError):
|
|
110
|
-
raise e
|
|
111
|
-
# Все остальные ошибки оборачиваем в InternalError
|
|
112
|
-
raise InternalError(
|
|
113
|
-
f"Error executing command: {str(e)}", data={"original_error": str(e)}
|
|
114
|
-
)
|
|
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)})
|
|
115
83
|
|
|
116
84
|
|
|
117
85
|
async def handle_batch_json_rpc(
|
|
118
86
|
batch_requests: List[Dict[str, Any]], request: Optional[Request] = None
|
|
119
87
|
) -> List[Dict[str, Any]]:
|
|
120
|
-
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
batch_requests: List of JSON-RPC request data.
|
|
125
|
-
request: Original FastAPI request object.
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
List of JSON-RPC responses.
|
|
129
|
-
"""
|
|
130
|
-
responses = []
|
|
131
|
-
|
|
132
|
-
# Get request_id from request state if available
|
|
88
|
+
"""Handle batch JSON-RPC requests."""
|
|
89
|
+
responses: List[Dict[str, Any]] = []
|
|
133
90
|
request_id = getattr(request.state, "request_id", None) if request else None
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
# Process each request in the batch
|
|
137
|
-
response = await handle_json_rpc(request_data, request_id, request)
|
|
138
|
-
responses.append(response)
|
|
139
|
-
|
|
91
|
+
for item in batch_requests:
|
|
92
|
+
responses.append(await handle_json_rpc(item, request_id, request))
|
|
140
93
|
return responses
|
|
141
94
|
|
|
142
95
|
|
|
@@ -145,120 +98,63 @@ async def handle_json_rpc(
|
|
|
145
98
|
request_id: Optional[str] = None,
|
|
146
99
|
request: Optional[Request] = None,
|
|
147
100
|
) -> Dict[str, Any]:
|
|
148
|
-
"""
|
|
149
|
-
Handles JSON-RPC request with support for both standard JSON-RPC and simplified formats.
|
|
101
|
+
"""Handle a single JSON-RPC request with strict 2.0 compatibility.
|
|
150
102
|
|
|
151
|
-
|
|
152
|
-
request_data: JSON-RPC request data.
|
|
153
|
-
request_id: Optional request identifier for logging context.
|
|
154
|
-
|
|
155
|
-
Returns:
|
|
156
|
-
JSON-RPC response.
|
|
103
|
+
Also supports simplified form: {"command": "echo", "params": {...}}.
|
|
157
104
|
"""
|
|
158
|
-
|
|
159
|
-
log = RequestLogger(__name__, request_id) if request_id else get_global_logger()
|
|
105
|
+
logger = RequestLogger(__name__, request_id) if request_id else get_global_logger()
|
|
160
106
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
json_rpc_id = None
|
|
107
|
+
method: Optional[str]
|
|
108
|
+
params: Dict[str, Any]
|
|
109
|
+
json_rpc_id: Any
|
|
165
110
|
|
|
166
|
-
# Check if it's a standard JSON-RPC request
|
|
167
111
|
if "jsonrpc" in request_data:
|
|
168
|
-
# Standard JSON-RPC format
|
|
169
112
|
if request_data.get("jsonrpc") != "2.0":
|
|
170
|
-
return
|
|
171
|
-
InvalidRequestError("Invalid Request. Expected jsonrpc: 2.0"),
|
|
172
|
-
request_data.get("id"),
|
|
173
|
-
)
|
|
174
|
-
|
|
113
|
+
return _error_response(InvalidRequestError("Invalid Request: jsonrpc must be '2.0'"), request_data.get("id"))
|
|
175
114
|
method = request_data.get("method")
|
|
176
115
|
params = request_data.get("params", {})
|
|
177
116
|
json_rpc_id = request_data.get("id")
|
|
178
|
-
|
|
179
117
|
if not method:
|
|
180
|
-
return
|
|
181
|
-
InvalidRequestError("Invalid Request. Method is required"), json_rpc_id
|
|
182
|
-
)
|
|
118
|
+
return _error_response(InvalidRequestError("Invalid Request: method is required"), json_rpc_id)
|
|
183
119
|
else:
|
|
184
|
-
# Simplified
|
|
120
|
+
# Simplified
|
|
185
121
|
method = request_data.get("command")
|
|
186
122
|
params = request_data.get("params", {})
|
|
187
|
-
json_rpc_id = request_data.get("id", 1)
|
|
188
|
-
|
|
123
|
+
json_rpc_id = request_data.get("id", 1)
|
|
189
124
|
if not method:
|
|
190
|
-
return
|
|
191
|
-
InvalidRequestError("Invalid Request. Command is required"), json_rpc_id
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
log.info(f"Using simplified format for command: {method}")
|
|
195
|
-
|
|
196
|
-
log.info(f"Executing JSON-RPC method: {method}")
|
|
125
|
+
return _error_response(InvalidRequestError("Invalid Request: command is required"), json_rpc_id)
|
|
197
126
|
|
|
198
127
|
try:
|
|
199
|
-
# Execute command with detailed logging
|
|
200
|
-
log.info(f"🔍 Starting command execution: {method}")
|
|
201
|
-
log.debug(f"📋 Command params: {params}")
|
|
202
|
-
|
|
203
128
|
result = await execute_command(method, params, request_id, request)
|
|
204
|
-
|
|
205
|
-
log.info(f"✅ Command {method} completed successfully")
|
|
206
|
-
|
|
207
|
-
# Form successful response
|
|
208
129
|
return {"jsonrpc": "2.0", "result": result, "id": json_rpc_id}
|
|
209
|
-
except MicroserviceError as
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return
|
|
214
|
-
except Exception as e:
|
|
215
|
-
# Internal server error
|
|
216
|
-
log.exception(f"❌ Unhandled error in JSON-RPC handler: {e}")
|
|
217
|
-
log.error(f"📊 Exception type: {type(e).__name__}")
|
|
218
|
-
log.error(f"📊 Exception details: {repr(e)}")
|
|
219
|
-
return _create_error_response(
|
|
220
|
-
InternalError("Internal error", data={"error": str(e), "error_type": type(e).__name__}), json_rpc_id
|
|
221
|
-
)
|
|
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)
|
|
222
135
|
|
|
223
136
|
|
|
224
|
-
def
|
|
225
|
-
"""
|
|
226
|
-
Creates JSON-RPC error response.
|
|
227
|
-
|
|
228
|
-
Args:
|
|
229
|
-
error: Error object.
|
|
230
|
-
request_id: Request ID from client.
|
|
231
|
-
|
|
232
|
-
Returns:
|
|
233
|
-
JSON-RPC error response dictionary.
|
|
234
|
-
"""
|
|
137
|
+
def _error_response(error: MicroserviceError, request_id: Any) -> Dict[str, Any]:
|
|
235
138
|
return {"jsonrpc": "2.0", "error": error.to_dict(), "id": request_id}
|
|
236
139
|
|
|
237
140
|
|
|
238
141
|
async def get_server_health() -> Dict[str, Any]:
|
|
239
|
-
"""
|
|
240
|
-
Gets server health information.
|
|
241
|
-
|
|
242
|
-
Returns:
|
|
243
|
-
Dictionary with server health information.
|
|
244
|
-
"""
|
|
142
|
+
"""Return server health info."""
|
|
245
143
|
import os
|
|
246
144
|
import platform
|
|
247
145
|
import sys
|
|
248
146
|
import psutil
|
|
249
147
|
from datetime import datetime
|
|
250
148
|
|
|
251
|
-
# Get process start time
|
|
252
149
|
process = psutil.Process(os.getpid())
|
|
253
150
|
start_time = datetime.fromtimestamp(process.create_time())
|
|
254
151
|
uptime_seconds = (datetime.now() - start_time).total_seconds()
|
|
255
|
-
|
|
256
|
-
# Get system information
|
|
257
|
-
memory_info = process.memory_info()
|
|
152
|
+
mem = process.memory_info().rss / (1024 * 1024)
|
|
258
153
|
|
|
259
154
|
return {
|
|
260
155
|
"status": "ok",
|
|
261
|
-
"
|
|
156
|
+
"model": "mcp-proxy-adapter",
|
|
157
|
+
"version": "1.0.0",
|
|
262
158
|
"uptime": uptime_seconds,
|
|
263
159
|
"components": {
|
|
264
160
|
"system": {
|
|
@@ -268,7 +164,7 @@ async def get_server_health() -> Dict[str, Any]:
|
|
|
268
164
|
},
|
|
269
165
|
"process": {
|
|
270
166
|
"pid": os.getpid(),
|
|
271
|
-
"memory_usage_mb":
|
|
167
|
+
"memory_usage_mb": mem,
|
|
272
168
|
"start_time": start_time.isoformat(),
|
|
273
169
|
},
|
|
274
170
|
"commands": {"registered_count": len(registry.get_all_commands())},
|
|
@@ -277,26 +173,9 @@ async def get_server_health() -> Dict[str, Any]:
|
|
|
277
173
|
|
|
278
174
|
|
|
279
175
|
async def get_commands_list() -> Dict[str, Dict[str, Any]]:
|
|
280
|
-
"""
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
"""
|
|
286
|
-
result = {}
|
|
287
|
-
|
|
288
|
-
# Get all registered commands
|
|
289
|
-
all_commands = registry.get_all_commands()
|
|
290
|
-
|
|
291
|
-
for command_name, command_class in all_commands.items():
|
|
292
|
-
# Get schema information for the command
|
|
293
|
-
schema = command_class.get_schema()
|
|
294
|
-
|
|
295
|
-
# Add to result
|
|
296
|
-
result[command_name] = {
|
|
297
|
-
"name": command_name,
|
|
298
|
-
"schema": schema,
|
|
299
|
-
"description": schema.get("description", ""),
|
|
300
|
-
}
|
|
301
|
-
|
|
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", "")}
|
|
302
181
|
return result
|
|
@@ -8,9 +8,8 @@ from fastapi import FastAPI
|
|
|
8
8
|
|
|
9
9
|
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
10
10
|
from mcp_proxy_adapter.config import config
|
|
11
|
-
from .base import BaseMiddleware
|
|
12
11
|
from .factory import MiddlewareFactory
|
|
13
|
-
from .protocol_middleware import setup_protocol_middleware
|
|
12
|
+
# from .protocol_middleware import setup_protocol_middleware
|
|
14
13
|
|
|
15
14
|
# Export mcp_security_framework availability
|
|
16
15
|
try:
|
|
@@ -20,45 +19,3 @@ except ImportError:
|
|
|
20
19
|
mcp_security_framework = False
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None) -> None:
|
|
24
|
-
"""
|
|
25
|
-
Sets up middleware for application using the new middleware factory.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
app: FastAPI application instance.
|
|
29
|
-
app_config: Application configuration dictionary (optional)
|
|
30
|
-
"""
|
|
31
|
-
# Use provided configuration or fallback to global config
|
|
32
|
-
current_config = app_config if app_config is not None else config.get_all()
|
|
33
|
-
|
|
34
|
-
# Add protocol middleware FIRST (before other middleware)
|
|
35
|
-
setup_protocol_middleware(app, current_config)
|
|
36
|
-
|
|
37
|
-
# Create middleware factory
|
|
38
|
-
factory = MiddlewareFactory(app, current_config)
|
|
39
|
-
|
|
40
|
-
# Validate middleware configuration
|
|
41
|
-
if not factory.validate_middleware_config():
|
|
42
|
-
get_global_logger().error("Middleware configuration validation failed")
|
|
43
|
-
raise SystemExit(1)
|
|
44
|
-
|
|
45
|
-
get_global_logger().info("Using unified security middleware")
|
|
46
|
-
middleware_list = factory.create_all_middleware()
|
|
47
|
-
|
|
48
|
-
# Add middleware to application AFTER protocol middleware
|
|
49
|
-
for middleware in middleware_list:
|
|
50
|
-
# For ASGI middleware, we need to wrap the application
|
|
51
|
-
if hasattr(middleware, "dispatch"):
|
|
52
|
-
# This is a proper ASGI middleware
|
|
53
|
-
app.middleware("http")(middleware.dispatch)
|
|
54
|
-
else:
|
|
55
|
-
get_global_logger().warning(
|
|
56
|
-
f"Middleware {middleware.__class__.__name__} doesn't have dispatch method"
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# Log middleware information
|
|
60
|
-
middleware_info = factory.get_middleware_info()
|
|
61
|
-
get_global_logger().info(f"Middleware setup completed:")
|
|
62
|
-
get_global_logger().info(f" - Total middleware: {middleware_info['total_middleware']}")
|
|
63
|
-
get_global_logger().info(f" - Types: {', '.join(middleware_info['middleware_types'])}")
|
|
64
|
-
get_global_logger().info(f" - Security enabled: {middleware_info['security_enabled']}")
|
|
@@ -16,48 +16,6 @@ class BaseMiddleware(BaseHTTPMiddleware):
|
|
|
16
16
|
Base class for all middleware.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
async def dispatch(
|
|
20
|
-
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
21
|
-
) -> Response:
|
|
22
|
-
"""
|
|
23
|
-
Method that will be overridden in child classes.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
request: Request.
|
|
27
|
-
call_next: Next handler.
|
|
28
|
-
|
|
29
|
-
Returns:
|
|
30
|
-
Response.
|
|
31
|
-
"""
|
|
32
|
-
middleware_name = self.__class__.__name__
|
|
33
|
-
get_global_logger().info(f"🔍 STEP 1: {middleware_name}.dispatch START - {request.method} {request.url.path}")
|
|
34
|
-
get_global_logger().info(f"🔍 STEP 1.1: {middleware_name} - Request headers: {dict(request.headers)}")
|
|
35
|
-
get_global_logger().info(f"🔍 STEP 1.2: {middleware_name} - Request URL: {request.url}")
|
|
36
|
-
get_global_logger().info(f"🔍 STEP 1.3: {middleware_name} - Request scope: {request.scope}")
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
# Process request before calling the main handler
|
|
40
|
-
get_global_logger().info(f"🔍 STEP 2: {middleware_name}.before_request START")
|
|
41
|
-
await self.before_request(request)
|
|
42
|
-
get_global_logger().info(f"🔍 STEP 3: {middleware_name}.before_request COMPLETED")
|
|
43
|
-
|
|
44
|
-
# Call the next middleware or main handler
|
|
45
|
-
get_global_logger().info(f"🔍 STEP 4: {middleware_name}.call_next START - About to call next middleware/endpoint")
|
|
46
|
-
response = await call_next(request)
|
|
47
|
-
get_global_logger().info(f"🔍 STEP 5: {middleware_name}.call_next COMPLETED - Status: {response.status_code}")
|
|
48
|
-
get_global_logger().info(f"🔍 STEP 5.1: {middleware_name} - Response headers: {dict(response.headers)}")
|
|
49
|
-
|
|
50
|
-
# Process response after calling the main handler
|
|
51
|
-
get_global_logger().info(f"🔍 STEP 6: {middleware_name}.after_response START")
|
|
52
|
-
response = await self.after_response(request, response)
|
|
53
|
-
get_global_logger().info(f"🔍 STEP 7: {middleware_name}.after_response COMPLETED")
|
|
54
|
-
|
|
55
|
-
get_global_logger().info(f"🔍 STEP 8: {middleware_name}.dispatch COMPLETED SUCCESSFULLY")
|
|
56
|
-
return response
|
|
57
|
-
except Exception as e:
|
|
58
|
-
get_global_logger().error(f"❌ STEP ERROR: {middleware_name}.dispatch ERROR: {str(e)}", exc_info=True)
|
|
59
|
-
# If an error occurred, call the error handler
|
|
60
|
-
return await self.handle_error(request, e)
|
|
61
19
|
|
|
62
20
|
async def before_request(self, request: Request) -> None:
|
|
63
21
|
"""
|
|
@@ -9,7 +9,6 @@ email: vasilyvz@gmail.com
|
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
|
-
from typing import Dict, Any, Optional, Callable, Awaitable
|
|
13
12
|
from fastapi import Request, Response
|
|
14
13
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
15
14
|
|
|
@@ -47,90 +46,6 @@ class CommandPermissionMiddleware(BaseHTTPMiddleware):
|
|
|
47
46
|
|
|
48
47
|
get_global_logger().info("Command permission middleware initialized")
|
|
49
48
|
|
|
50
|
-
async def dispatch(
|
|
51
|
-
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
52
|
-
) -> Response:
|
|
53
|
-
"""
|
|
54
|
-
Process request and check command permissions.
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
request: Request object
|
|
58
|
-
call_next: Next handler
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
Response object
|
|
62
|
-
"""
|
|
63
|
-
# Only check permissions for /cmd endpoint
|
|
64
|
-
if request.url.path != "/cmd":
|
|
65
|
-
return await call_next(request)
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
# Get request body
|
|
69
|
-
body = await request.body()
|
|
70
|
-
if not body:
|
|
71
|
-
return await call_next(request)
|
|
72
|
-
|
|
73
|
-
# Parse JSON-RPC request
|
|
74
|
-
try:
|
|
75
|
-
data = json.loads(body)
|
|
76
|
-
except json.JSONDecodeError:
|
|
77
|
-
return await call_next(request)
|
|
78
|
-
|
|
79
|
-
# Extract method (command name)
|
|
80
|
-
method = data.get("method")
|
|
81
|
-
if not method:
|
|
82
|
-
return await call_next(request)
|
|
83
|
-
|
|
84
|
-
# Check if method requires permissions
|
|
85
|
-
if method not in self.command_permissions:
|
|
86
|
-
return await call_next(request)
|
|
87
|
-
|
|
88
|
-
required_permissions = self.command_permissions[method]
|
|
89
|
-
|
|
90
|
-
# Get user info from request state
|
|
91
|
-
user_info = getattr(request.state, "user", None)
|
|
92
|
-
if not user_info:
|
|
93
|
-
get_global_logger().warning(f"No user info found for command {method}")
|
|
94
|
-
return await call_next(request)
|
|
95
|
-
|
|
96
|
-
user_roles = user_info.get("roles", [])
|
|
97
|
-
user_permissions = user_info.get("permissions", [])
|
|
98
|
-
|
|
99
|
-
get_global_logger().debug(
|
|
100
|
-
f"Checking permissions for {method}: user_roles={user_roles}, required={required_permissions}"
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
# Check if user has required permissions
|
|
104
|
-
has_permission = self._check_permissions(
|
|
105
|
-
user_roles, user_permissions, required_permissions
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
if not has_permission:
|
|
109
|
-
get_global_logger().warning(
|
|
110
|
-
f"Permission denied for {method}: user_roles={user_roles}, required={required_permissions}"
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
# Return permission denied response
|
|
114
|
-
error_response = {
|
|
115
|
-
"error": {
|
|
116
|
-
"code": 403,
|
|
117
|
-
"message": f"Permission denied: {method} requires {required_permissions}",
|
|
118
|
-
"type": "permission_denied",
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return Response(
|
|
123
|
-
content=json.dumps(error_response),
|
|
124
|
-
status_code=403,
|
|
125
|
-
media_type="application/json",
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
get_global_logger().debug(f"Permission granted for {method}")
|
|
129
|
-
return await call_next(request)
|
|
130
|
-
|
|
131
|
-
except Exception as e:
|
|
132
|
-
get_global_logger().error(f"Error in command permission middleware: {e}")
|
|
133
|
-
return await call_next(request)
|
|
134
49
|
|
|
135
50
|
def _check_permissions(
|
|
136
51
|
self, user_roles: list, user_permissions: list, required_permissions: list
|