mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/__init__.py +9 -5
- mcp_proxy_adapter/__main__.py +1 -1
- mcp_proxy_adapter/api/app.py +227 -176
- mcp_proxy_adapter/api/handlers.py +68 -60
- mcp_proxy_adapter/api/middleware/__init__.py +7 -5
- mcp_proxy_adapter/api/middleware/base.py +19 -16
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
- mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
- mcp_proxy_adapter/api/middleware/factory.py +50 -52
- mcp_proxy_adapter/api/middleware/logging.py +46 -30
- mcp_proxy_adapter/api/middleware/performance.py +19 -16
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
- mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
- mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
- mcp_proxy_adapter/api/schemas.py +69 -43
- mcp_proxy_adapter/api/tool_integration.py +83 -63
- mcp_proxy_adapter/api/tools.py +60 -50
- mcp_proxy_adapter/commands/__init__.py +15 -6
- mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
- mcp_proxy_adapter/commands/base.py +108 -112
- mcp_proxy_adapter/commands/builtin_commands.py +28 -18
- mcp_proxy_adapter/commands/catalog_manager.py +394 -265
- mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
- mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
- mcp_proxy_adapter/commands/command_registry.py +275 -226
- mcp_proxy_adapter/commands/config_command.py +48 -33
- mcp_proxy_adapter/commands/dependency_container.py +22 -23
- mcp_proxy_adapter/commands/dependency_manager.py +65 -56
- mcp_proxy_adapter/commands/echo_command.py +15 -15
- mcp_proxy_adapter/commands/health_command.py +31 -29
- mcp_proxy_adapter/commands/help_command.py +97 -61
- mcp_proxy_adapter/commands/hooks.py +65 -49
- mcp_proxy_adapter/commands/key_management_command.py +148 -147
- mcp_proxy_adapter/commands/load_command.py +58 -40
- mcp_proxy_adapter/commands/plugins_command.py +80 -54
- mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
- mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
- mcp_proxy_adapter/commands/reload_command.py +43 -37
- mcp_proxy_adapter/commands/result.py +26 -33
- mcp_proxy_adapter/commands/role_test_command.py +26 -26
- mcp_proxy_adapter/commands/roles_management_command.py +176 -173
- mcp_proxy_adapter/commands/security_command.py +134 -122
- mcp_proxy_adapter/commands/settings_command.py +47 -56
- mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
- mcp_proxy_adapter/commands/token_management_command.py +129 -158
- mcp_proxy_adapter/commands/transport_management_command.py +41 -36
- mcp_proxy_adapter/commands/unload_command.py +42 -37
- mcp_proxy_adapter/config.py +36 -35
- mcp_proxy_adapter/core/__init__.py +19 -21
- mcp_proxy_adapter/core/app_factory.py +30 -9
- mcp_proxy_adapter/core/app_runner.py +81 -64
- mcp_proxy_adapter/core/auth_validator.py +176 -182
- mcp_proxy_adapter/core/certificate_utils.py +469 -426
- mcp_proxy_adapter/core/client.py +155 -126
- mcp_proxy_adapter/core/client_manager.py +60 -54
- mcp_proxy_adapter/core/client_security.py +108 -88
- mcp_proxy_adapter/core/config_converter.py +176 -143
- mcp_proxy_adapter/core/config_validator.py +12 -4
- mcp_proxy_adapter/core/crl_utils.py +21 -7
- mcp_proxy_adapter/core/errors.py +64 -20
- mcp_proxy_adapter/core/logging.py +34 -29
- mcp_proxy_adapter/core/mtls_asgi.py +29 -25
- mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
- mcp_proxy_adapter/core/protocol_manager.py +154 -104
- mcp_proxy_adapter/core/proxy_client.py +202 -144
- mcp_proxy_adapter/core/proxy_registration.py +7 -3
- mcp_proxy_adapter/core/role_utils.py +139 -125
- mcp_proxy_adapter/core/security_adapter.py +88 -77
- mcp_proxy_adapter/core/security_factory.py +50 -44
- mcp_proxy_adapter/core/security_integration.py +72 -24
- mcp_proxy_adapter/core/server_adapter.py +68 -64
- mcp_proxy_adapter/core/server_engine.py +71 -53
- mcp_proxy_adapter/core/settings.py +68 -58
- mcp_proxy_adapter/core/ssl_utils.py +69 -56
- mcp_proxy_adapter/core/transport_manager.py +72 -60
- mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
- mcp_proxy_adapter/core/utils.py +4 -2
- mcp_proxy_adapter/custom_openapi.py +107 -99
- mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
- mcp_proxy_adapter/examples/debug_request_state.py +38 -19
- mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
- mcp_proxy_adapter/examples/demo_client.py +48 -36
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
- mcp_proxy_adapter/examples/generate_certificates.py +31 -16
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
- mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
- mcp_proxy_adapter/examples/run_example.py +23 -5
- mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
- mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
- mcp_proxy_adapter/examples/run_security_tests.py +103 -41
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
- mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/security_test_client.py +196 -127
- mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
- mcp_proxy_adapter/examples/test_config.py +19 -4
- mcp_proxy_adapter/examples/test_config_generator.py +23 -7
- mcp_proxy_adapter/examples/test_examples.py +84 -56
- mcp_proxy_adapter/examples/universal_client.py +119 -62
- mcp_proxy_adapter/openapi.py +108 -115
- mcp_proxy_adapter/utils/config_generator.py +429 -274
- mcp_proxy_adapter/version.py +1 -2
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
- mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -11,13 +11,24 @@ from fastapi.responses import JSONResponse
|
|
11
11
|
|
12
12
|
from mcp_proxy_adapter.commands.command_registry import registry
|
13
13
|
from mcp_proxy_adapter.core.errors import (
|
14
|
-
MicroserviceError,
|
15
|
-
|
14
|
+
MicroserviceError,
|
15
|
+
NotFoundError,
|
16
|
+
ParseError,
|
17
|
+
InvalidRequestError,
|
18
|
+
MethodNotFoundError,
|
19
|
+
InvalidParamsError,
|
20
|
+
InternalError,
|
21
|
+
CommandError,
|
16
22
|
)
|
17
23
|
from mcp_proxy_adapter.core.logging import logger, RequestLogger, get_logger
|
18
24
|
|
19
25
|
|
20
|
-
async def execute_command(
|
26
|
+
async def execute_command(
|
27
|
+
command_name: str,
|
28
|
+
params: Dict[str, Any],
|
29
|
+
request_id: Optional[str] = None,
|
30
|
+
request: Optional[Request] = None,
|
31
|
+
) -> Dict[str, Any]:
|
21
32
|
"""
|
22
33
|
Executes a command with the specified name and parameters.
|
23
34
|
|
@@ -35,47 +46,48 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
|
|
35
46
|
"""
|
36
47
|
# Create request logger if request_id is provided
|
37
48
|
log = RequestLogger(__name__, request_id) if request_id else logger
|
38
|
-
|
49
|
+
|
39
50
|
try:
|
40
51
|
log.info(f"Executing command: {command_name}")
|
41
|
-
|
52
|
+
|
42
53
|
# Execute before command hooks
|
43
54
|
try:
|
44
55
|
from mcp_proxy_adapter.commands.hooks import hooks
|
56
|
+
|
45
57
|
hooks.execute_before_command_hooks(command_name, params)
|
46
58
|
log.debug(f"Executed before command hooks for: {command_name}")
|
47
59
|
except Exception as e:
|
48
60
|
log.warning(f"Failed to execute before command hooks: {e}")
|
49
|
-
|
61
|
+
|
50
62
|
# Get command class from registry and execute with parameters
|
51
63
|
start_time = time.time()
|
52
|
-
|
64
|
+
|
53
65
|
# Use Command.run that handles instances with dependencies properly
|
54
66
|
command_class = registry.get_command(command_name)
|
55
|
-
|
67
|
+
|
56
68
|
# Create context with user info from request state
|
57
69
|
context = {}
|
58
|
-
if request and hasattr(request.state,
|
59
|
-
context[
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
70
|
+
if request and hasattr(request.state, "user_id"):
|
71
|
+
context["user"] = {
|
72
|
+
"id": getattr(request.state, "user_id", None),
|
73
|
+
"role": getattr(request.state, "user_role", "guest"),
|
74
|
+
"roles": getattr(request.state, "user_roles", ["guest"]),
|
75
|
+
"permissions": getattr(request.state, "user_permissions", ["read"]),
|
64
76
|
}
|
65
|
-
|
77
|
+
|
66
78
|
result = await command_class.run(**params, context=context)
|
67
|
-
|
79
|
+
|
68
80
|
execution_time = time.time() - start_time
|
69
|
-
|
81
|
+
|
70
82
|
log.info(f"Command '{command_name}' executed in {execution_time:.3f} sec")
|
71
|
-
|
83
|
+
|
72
84
|
# Execute after command hooks
|
73
85
|
try:
|
74
86
|
hooks.execute_after_command_hooks(command_name, params, result)
|
75
87
|
log.debug(f"Executed after command hooks for: {command_name}")
|
76
88
|
except Exception as e:
|
77
89
|
log.warning(f"Failed to execute after command hooks: {e}")
|
78
|
-
|
90
|
+
|
79
91
|
# Return result
|
80
92
|
return result.to_dict()
|
81
93
|
except NotFoundError as e:
|
@@ -87,10 +99,14 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
|
|
87
99
|
if isinstance(e, MicroserviceError):
|
88
100
|
raise e
|
89
101
|
# Все остальные ошибки оборачиваем в InternalError
|
90
|
-
raise InternalError(
|
102
|
+
raise InternalError(
|
103
|
+
f"Error executing command: {str(e)}", data={"original_error": str(e)}
|
104
|
+
)
|
91
105
|
|
92
106
|
|
93
|
-
async def handle_batch_json_rpc(
|
107
|
+
async def handle_batch_json_rpc(
|
108
|
+
batch_requests: List[Dict[str, Any]], request: Optional[Request] = None
|
109
|
+
) -> List[Dict[str, Any]]:
|
94
110
|
"""
|
95
111
|
Handles batch JSON-RPC requests.
|
96
112
|
|
@@ -102,19 +118,23 @@ async def handle_batch_json_rpc(batch_requests: List[Dict[str, Any]], request: O
|
|
102
118
|
List of JSON-RPC responses.
|
103
119
|
"""
|
104
120
|
responses = []
|
105
|
-
|
121
|
+
|
106
122
|
# Get request_id from request state if available
|
107
123
|
request_id = getattr(request.state, "request_id", None) if request else None
|
108
|
-
|
124
|
+
|
109
125
|
for request_data in batch_requests:
|
110
126
|
# Process each request in the batch
|
111
127
|
response = await handle_json_rpc(request_data, request_id, request)
|
112
128
|
responses.append(response)
|
113
|
-
|
129
|
+
|
114
130
|
return responses
|
115
131
|
|
116
132
|
|
117
|
-
async def handle_json_rpc(
|
133
|
+
async def handle_json_rpc(
|
134
|
+
request_data: Dict[str, Any],
|
135
|
+
request_id: Optional[str] = None,
|
136
|
+
request: Optional[Request] = None,
|
137
|
+
) -> Dict[str, Any]:
|
118
138
|
"""
|
119
139
|
Handles JSON-RPC request.
|
120
140
|
|
@@ -127,37 +147,32 @@ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str
|
|
127
147
|
"""
|
128
148
|
# Create request logger if request_id is provided
|
129
149
|
log = RequestLogger(__name__, request_id) if request_id else logger
|
130
|
-
|
150
|
+
|
131
151
|
# Check JSON-RPC version
|
132
152
|
if request_data.get("jsonrpc") != "2.0":
|
133
153
|
return _create_error_response(
|
134
154
|
InvalidRequestError("Invalid Request. Expected jsonrpc: 2.0"),
|
135
|
-
request_data.get("id")
|
155
|
+
request_data.get("id"),
|
136
156
|
)
|
137
|
-
|
157
|
+
|
138
158
|
# Get method and parameters
|
139
159
|
method = request_data.get("method")
|
140
160
|
params = request_data.get("params", {})
|
141
161
|
json_rpc_id = request_data.get("id")
|
142
|
-
|
162
|
+
|
143
163
|
if not method:
|
144
164
|
return _create_error_response(
|
145
|
-
InvalidRequestError("Invalid Request. Method is required"),
|
146
|
-
json_rpc_id
|
165
|
+
InvalidRequestError("Invalid Request. Method is required"), json_rpc_id
|
147
166
|
)
|
148
|
-
|
167
|
+
|
149
168
|
log.info(f"Executing JSON-RPC method: {method}")
|
150
|
-
|
169
|
+
|
151
170
|
try:
|
152
171
|
# Execute command
|
153
172
|
result = await execute_command(method, params, request_id, request)
|
154
|
-
|
173
|
+
|
155
174
|
# Form successful response
|
156
|
-
return {
|
157
|
-
"jsonrpc": "2.0",
|
158
|
-
"result": result,
|
159
|
-
"id": json_rpc_id
|
160
|
-
}
|
175
|
+
return {"jsonrpc": "2.0", "result": result, "id": json_rpc_id}
|
161
176
|
except MicroserviceError as e:
|
162
177
|
# Method execution error
|
163
178
|
log.error(f"Method execution error: {str(e)}")
|
@@ -166,8 +181,7 @@ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str
|
|
166
181
|
# Internal server error
|
167
182
|
log.exception(f"Unhandled error in JSON-RPC handler: {e}")
|
168
183
|
return _create_error_response(
|
169
|
-
InternalError("Internal error", data={"error": str(e)}),
|
170
|
-
json_rpc_id
|
184
|
+
InternalError("Internal error", data={"error": str(e)}), json_rpc_id
|
171
185
|
)
|
172
186
|
|
173
187
|
|
@@ -182,11 +196,7 @@ def _create_error_response(error: MicroserviceError, request_id: Any) -> Dict[st
|
|
182
196
|
Returns:
|
183
197
|
JSON-RPC error response dictionary.
|
184
198
|
"""
|
185
|
-
return {
|
186
|
-
"jsonrpc": "2.0",
|
187
|
-
"error": error.to_dict(),
|
188
|
-
"id": request_id
|
189
|
-
}
|
199
|
+
return {"jsonrpc": "2.0", "error": error.to_dict(), "id": request_id}
|
190
200
|
|
191
201
|
|
192
202
|
async def get_server_health() -> Dict[str, Any]:
|
@@ -201,15 +211,15 @@ async def get_server_health() -> Dict[str, Any]:
|
|
201
211
|
import sys
|
202
212
|
import psutil
|
203
213
|
from datetime import datetime
|
204
|
-
|
214
|
+
|
205
215
|
# Get process start time
|
206
216
|
process = psutil.Process(os.getpid())
|
207
217
|
start_time = datetime.fromtimestamp(process.create_time())
|
208
218
|
uptime_seconds = (datetime.now() - start_time).total_seconds()
|
209
|
-
|
219
|
+
|
210
220
|
# Get system information
|
211
221
|
memory_info = process.memory_info()
|
212
|
-
|
222
|
+
|
213
223
|
return {
|
214
224
|
"status": "ok",
|
215
225
|
"version": "1.0.0", # Should be replaced with actual version
|
@@ -218,17 +228,15 @@ async def get_server_health() -> Dict[str, Any]:
|
|
218
228
|
"system": {
|
219
229
|
"python_version": sys.version,
|
220
230
|
"platform": platform.platform(),
|
221
|
-
"cpu_count": os.cpu_count()
|
231
|
+
"cpu_count": os.cpu_count(),
|
222
232
|
},
|
223
233
|
"process": {
|
224
234
|
"pid": os.getpid(),
|
225
235
|
"memory_usage_mb": memory_info.rss / (1024 * 1024),
|
226
|
-
"start_time": start_time.isoformat()
|
236
|
+
"start_time": start_time.isoformat(),
|
227
237
|
},
|
228
|
-
"commands": {
|
229
|
-
|
230
|
-
}
|
231
|
-
}
|
238
|
+
"commands": {"registered_count": len(registry.get_all_commands())},
|
239
|
+
},
|
232
240
|
}
|
233
241
|
|
234
242
|
|
@@ -240,19 +248,19 @@ async def get_commands_list() -> Dict[str, Dict[str, Any]]:
|
|
240
248
|
Dictionary with information about available commands.
|
241
249
|
"""
|
242
250
|
result = {}
|
243
|
-
|
251
|
+
|
244
252
|
# Get all registered commands
|
245
253
|
all_commands = registry.get_all_commands()
|
246
|
-
|
254
|
+
|
247
255
|
for command_name, command_class in all_commands.items():
|
248
256
|
# Get schema information for the command
|
249
257
|
schema = command_class.get_schema()
|
250
|
-
|
258
|
+
|
251
259
|
# Add to result
|
252
260
|
result[command_name] = {
|
253
261
|
"name": command_name,
|
254
262
|
"schema": schema,
|
255
|
-
"description": schema.get("description", "")
|
263
|
+
"description": schema.get("description", ""),
|
256
264
|
}
|
257
|
-
|
265
|
+
|
258
266
|
return result
|
@@ -12,6 +12,7 @@ from .base import BaseMiddleware
|
|
12
12
|
from .factory import MiddlewareFactory
|
13
13
|
from .protocol_middleware import setup_protocol_middleware
|
14
14
|
|
15
|
+
|
15
16
|
def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None) -> None:
|
16
17
|
"""
|
17
18
|
Sets up middleware for application using the new middleware factory.
|
@@ -22,7 +23,7 @@ def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None)
|
|
22
23
|
"""
|
23
24
|
# Use provided configuration or fallback to global config
|
24
25
|
current_config = app_config if app_config is not None else config.get_all()
|
25
|
-
|
26
|
+
|
26
27
|
# Add protocol middleware FIRST (before other middleware)
|
27
28
|
setup_protocol_middleware(app, current_config)
|
28
29
|
|
@@ -40,16 +41,17 @@ def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None)
|
|
40
41
|
# Add middleware to application AFTER protocol middleware
|
41
42
|
for middleware in middleware_list:
|
42
43
|
# For ASGI middleware, we need to wrap the application
|
43
|
-
if hasattr(middleware,
|
44
|
+
if hasattr(middleware, "dispatch"):
|
44
45
|
# This is a proper ASGI middleware
|
45
46
|
app.middleware("http")(middleware.dispatch)
|
46
47
|
else:
|
47
|
-
logger.warning(
|
48
|
-
|
48
|
+
logger.warning(
|
49
|
+
f"Middleware {middleware.__class__.__name__} doesn't have dispatch method"
|
50
|
+
)
|
51
|
+
|
49
52
|
# Log middleware information
|
50
53
|
middleware_info = factory.get_middleware_info()
|
51
54
|
logger.info(f"Middleware setup completed:")
|
52
55
|
logger.info(f" - Total middleware: {middleware_info['total_middleware']}")
|
53
56
|
logger.info(f" - Types: {', '.join(middleware_info['middleware_types'])}")
|
54
57
|
logger.info(f" - Security enabled: {middleware_info['security_enabled']}")
|
55
|
-
|
@@ -10,70 +10,73 @@ from fastapi import Request, Response
|
|
10
10
|
|
11
11
|
from mcp_proxy_adapter.core.logging import logger
|
12
12
|
|
13
|
+
|
13
14
|
class BaseMiddleware(BaseHTTPMiddleware):
|
14
15
|
"""
|
15
16
|
Base class for all middleware.
|
16
17
|
"""
|
17
|
-
|
18
|
-
async def dispatch(
|
18
|
+
|
19
|
+
async def dispatch(
|
20
|
+
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
21
|
+
) -> Response:
|
19
22
|
"""
|
20
23
|
Method that will be overridden in child classes.
|
21
|
-
|
24
|
+
|
22
25
|
Args:
|
23
26
|
request: Request.
|
24
27
|
call_next: Next handler.
|
25
|
-
|
28
|
+
|
26
29
|
Returns:
|
27
30
|
Response.
|
28
31
|
"""
|
29
32
|
try:
|
30
33
|
# Process request before calling the main handler
|
31
34
|
await self.before_request(request)
|
32
|
-
|
35
|
+
|
33
36
|
# Call the next middleware or main handler
|
34
37
|
response = await call_next(request)
|
35
|
-
|
38
|
+
|
36
39
|
# Process response after calling the main handler
|
37
40
|
response = await self.after_response(request, response)
|
38
|
-
|
41
|
+
|
39
42
|
return response
|
40
43
|
except Exception as e:
|
41
44
|
logger.exception(f"Error in middleware: {str(e)}")
|
42
45
|
# If an error occurred, call the error handler
|
43
46
|
return await self.handle_error(request, e)
|
44
|
-
|
47
|
+
|
45
48
|
async def before_request(self, request: Request) -> None:
|
46
49
|
"""
|
47
50
|
Method for processing request before calling the main handler.
|
48
|
-
|
51
|
+
|
49
52
|
Args:
|
50
53
|
request: Request.
|
51
54
|
"""
|
52
55
|
pass
|
53
|
-
|
56
|
+
|
54
57
|
async def after_response(self, request: Request, response: Response) -> Response:
|
55
58
|
"""
|
56
59
|
Method for processing response after calling the main handler.
|
57
|
-
|
60
|
+
|
58
61
|
Args:
|
59
62
|
request: Request.
|
60
63
|
response: Response.
|
61
|
-
|
64
|
+
|
62
65
|
Returns:
|
63
66
|
Processed response.
|
64
67
|
"""
|
65
68
|
return response
|
66
|
-
|
69
|
+
|
67
70
|
async def handle_error(self, request: Request, exception: Exception) -> Response:
|
68
71
|
"""
|
69
72
|
Method for handling errors that occurred in middleware.
|
70
|
-
|
73
|
+
|
71
74
|
Args:
|
72
75
|
request: Request.
|
73
76
|
exception: Exception.
|
74
|
-
|
77
|
+
|
75
78
|
Returns:
|
76
79
|
Error response.
|
77
80
|
"""
|
78
81
|
# By default, just pass the error further
|
79
|
-
raise exception
|
82
|
+
raise exception
|
@@ -19,22 +19,22 @@ from mcp_proxy_adapter.core.logging import logger
|
|
19
19
|
class CommandPermissionMiddleware(BaseHTTPMiddleware):
|
20
20
|
"""
|
21
21
|
Middleware for checking command permissions.
|
22
|
-
|
22
|
+
|
23
23
|
This middleware checks if the authenticated user has the required
|
24
24
|
permissions to execute specific commands.
|
25
25
|
"""
|
26
|
-
|
26
|
+
|
27
27
|
def __init__(self, app, config: Dict[str, Any]):
|
28
28
|
"""
|
29
29
|
Initialize command permission middleware.
|
30
|
-
|
30
|
+
|
31
31
|
Args:
|
32
32
|
app: FastAPI application
|
33
33
|
config: Configuration dictionary
|
34
34
|
"""
|
35
35
|
super().__init__(app)
|
36
36
|
self.config = config
|
37
|
-
|
37
|
+
|
38
38
|
# Define command permissions
|
39
39
|
self.command_permissions = {
|
40
40
|
"echo": ["read"],
|
@@ -44,105 +44,115 @@ class CommandPermissionMiddleware(BaseHTTPMiddleware):
|
|
44
44
|
"help": ["read"],
|
45
45
|
# Add more commands as needed
|
46
46
|
}
|
47
|
-
|
47
|
+
|
48
48
|
logger.info("Command permission middleware initialized")
|
49
|
-
|
50
|
-
async def dispatch(
|
49
|
+
|
50
|
+
async def dispatch(
|
51
|
+
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
52
|
+
) -> Response:
|
51
53
|
"""
|
52
54
|
Process request and check command permissions.
|
53
|
-
|
55
|
+
|
54
56
|
Args:
|
55
57
|
request: Request object
|
56
58
|
call_next: Next handler
|
57
|
-
|
59
|
+
|
58
60
|
Returns:
|
59
61
|
Response object
|
60
62
|
"""
|
61
63
|
# Only check permissions for /cmd endpoint
|
62
64
|
if request.url.path != "/cmd":
|
63
65
|
return await call_next(request)
|
64
|
-
|
66
|
+
|
65
67
|
try:
|
66
68
|
# Get request body
|
67
69
|
body = await request.body()
|
68
70
|
if not body:
|
69
71
|
return await call_next(request)
|
70
|
-
|
72
|
+
|
71
73
|
# Parse JSON-RPC request
|
72
74
|
try:
|
73
75
|
data = json.loads(body)
|
74
76
|
except json.JSONDecodeError:
|
75
77
|
return await call_next(request)
|
76
|
-
|
78
|
+
|
77
79
|
# Extract method (command name)
|
78
80
|
method = data.get("method")
|
79
81
|
if not method:
|
80
82
|
return await call_next(request)
|
81
|
-
|
83
|
+
|
82
84
|
# Check if method requires permissions
|
83
85
|
if method not in self.command_permissions:
|
84
86
|
return await call_next(request)
|
85
|
-
|
87
|
+
|
86
88
|
required_permissions = self.command_permissions[method]
|
87
|
-
|
89
|
+
|
88
90
|
# Get user info from request state
|
89
91
|
user_info = getattr(request.state, "user", None)
|
90
92
|
if not user_info:
|
91
93
|
logger.warning(f"No user info found for command {method}")
|
92
94
|
return await call_next(request)
|
93
|
-
|
95
|
+
|
94
96
|
user_roles = user_info.get("roles", [])
|
95
97
|
user_permissions = user_info.get("permissions", [])
|
96
|
-
|
97
|
-
logger.debug(
|
98
|
-
|
98
|
+
|
99
|
+
logger.debug(
|
100
|
+
f"Checking permissions for {method}: user_roles={user_roles}, required={required_permissions}"
|
101
|
+
)
|
102
|
+
|
99
103
|
# Check if user has required permissions
|
100
|
-
has_permission = self._check_permissions(
|
101
|
-
|
104
|
+
has_permission = self._check_permissions(
|
105
|
+
user_roles, user_permissions, required_permissions
|
106
|
+
)
|
107
|
+
|
102
108
|
if not has_permission:
|
103
|
-
logger.warning(
|
104
|
-
|
109
|
+
logger.warning(
|
110
|
+
f"Permission denied for {method}: user_roles={user_roles}, required={required_permissions}"
|
111
|
+
)
|
112
|
+
|
105
113
|
# Return permission denied response
|
106
114
|
error_response = {
|
107
115
|
"error": {
|
108
116
|
"code": 403,
|
109
117
|
"message": f"Permission denied: {method} requires {required_permissions}",
|
110
|
-
"type": "permission_denied"
|
118
|
+
"type": "permission_denied",
|
111
119
|
}
|
112
120
|
}
|
113
|
-
|
121
|
+
|
114
122
|
return Response(
|
115
123
|
content=json.dumps(error_response),
|
116
124
|
status_code=403,
|
117
|
-
media_type="application/json"
|
125
|
+
media_type="application/json",
|
118
126
|
)
|
119
|
-
|
127
|
+
|
120
128
|
logger.debug(f"Permission granted for {method}")
|
121
129
|
return await call_next(request)
|
122
|
-
|
130
|
+
|
123
131
|
except Exception as e:
|
124
132
|
logger.error(f"Error in command permission middleware: {e}")
|
125
133
|
return await call_next(request)
|
126
|
-
|
127
|
-
def _check_permissions(
|
134
|
+
|
135
|
+
def _check_permissions(
|
136
|
+
self, user_roles: list, user_permissions: list, required_permissions: list
|
137
|
+
) -> bool:
|
128
138
|
"""
|
129
139
|
Check if user has required permissions.
|
130
|
-
|
140
|
+
|
131
141
|
Args:
|
132
142
|
user_roles: User roles
|
133
143
|
user_permissions: User permissions
|
134
144
|
required_permissions: Required permissions
|
135
|
-
|
145
|
+
|
136
146
|
Returns:
|
137
147
|
True if user has required permissions
|
138
148
|
"""
|
139
149
|
# Admin has all permissions
|
140
150
|
if "admin" in user_roles or "*" in user_permissions:
|
141
151
|
return True
|
142
|
-
|
152
|
+
|
143
153
|
# Check if user has all required permissions
|
144
154
|
for required in required_permissions:
|
145
155
|
if required not in user_permissions:
|
146
156
|
return False
|
147
|
-
|
157
|
+
|
148
158
|
return True
|