mcp-proxy-adapter 6.3.3__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.
Files changed (129) hide show
  1. mcp_proxy_adapter/__init__.py +9 -5
  2. mcp_proxy_adapter/__main__.py +1 -1
  3. mcp_proxy_adapter/api/app.py +227 -176
  4. mcp_proxy_adapter/api/handlers.py +68 -60
  5. mcp_proxy_adapter/api/middleware/__init__.py +7 -5
  6. mcp_proxy_adapter/api/middleware/base.py +19 -16
  7. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
  8. mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
  9. mcp_proxy_adapter/api/middleware/factory.py +50 -52
  10. mcp_proxy_adapter/api/middleware/logging.py +46 -30
  11. mcp_proxy_adapter/api/middleware/performance.py +19 -16
  12. mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
  13. mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
  14. mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
  15. mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
  16. mcp_proxy_adapter/api/schemas.py +69 -43
  17. mcp_proxy_adapter/api/tool_integration.py +83 -63
  18. mcp_proxy_adapter/api/tools.py +60 -50
  19. mcp_proxy_adapter/commands/__init__.py +15 -6
  20. mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
  21. mcp_proxy_adapter/commands/base.py +108 -112
  22. mcp_proxy_adapter/commands/builtin_commands.py +28 -18
  23. mcp_proxy_adapter/commands/catalog_manager.py +394 -265
  24. mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
  25. mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
  26. mcp_proxy_adapter/commands/command_registry.py +275 -226
  27. mcp_proxy_adapter/commands/config_command.py +48 -33
  28. mcp_proxy_adapter/commands/dependency_container.py +22 -23
  29. mcp_proxy_adapter/commands/dependency_manager.py +65 -56
  30. mcp_proxy_adapter/commands/echo_command.py +15 -15
  31. mcp_proxy_adapter/commands/health_command.py +31 -29
  32. mcp_proxy_adapter/commands/help_command.py +97 -61
  33. mcp_proxy_adapter/commands/hooks.py +65 -49
  34. mcp_proxy_adapter/commands/key_management_command.py +148 -147
  35. mcp_proxy_adapter/commands/load_command.py +58 -40
  36. mcp_proxy_adapter/commands/plugins_command.py +80 -54
  37. mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
  38. mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
  39. mcp_proxy_adapter/commands/reload_command.py +43 -37
  40. mcp_proxy_adapter/commands/result.py +26 -33
  41. mcp_proxy_adapter/commands/role_test_command.py +26 -26
  42. mcp_proxy_adapter/commands/roles_management_command.py +176 -173
  43. mcp_proxy_adapter/commands/security_command.py +134 -122
  44. mcp_proxy_adapter/commands/settings_command.py +47 -56
  45. mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
  46. mcp_proxy_adapter/commands/token_management_command.py +129 -158
  47. mcp_proxy_adapter/commands/transport_management_command.py +41 -36
  48. mcp_proxy_adapter/commands/unload_command.py +42 -37
  49. mcp_proxy_adapter/config.py +36 -35
  50. mcp_proxy_adapter/core/__init__.py +19 -21
  51. mcp_proxy_adapter/core/app_factory.py +30 -9
  52. mcp_proxy_adapter/core/app_runner.py +81 -64
  53. mcp_proxy_adapter/core/auth_validator.py +176 -182
  54. mcp_proxy_adapter/core/certificate_utils.py +469 -426
  55. mcp_proxy_adapter/core/client.py +155 -126
  56. mcp_proxy_adapter/core/client_manager.py +60 -54
  57. mcp_proxy_adapter/core/client_security.py +108 -88
  58. mcp_proxy_adapter/core/config_converter.py +176 -143
  59. mcp_proxy_adapter/core/config_validator.py +12 -4
  60. mcp_proxy_adapter/core/crl_utils.py +21 -7
  61. mcp_proxy_adapter/core/errors.py +64 -20
  62. mcp_proxy_adapter/core/logging.py +34 -29
  63. mcp_proxy_adapter/core/mtls_asgi.py +29 -25
  64. mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
  65. mcp_proxy_adapter/core/protocol_manager.py +154 -104
  66. mcp_proxy_adapter/core/proxy_client.py +202 -144
  67. mcp_proxy_adapter/core/proxy_registration.py +12 -2
  68. mcp_proxy_adapter/core/role_utils.py +139 -125
  69. mcp_proxy_adapter/core/security_adapter.py +88 -77
  70. mcp_proxy_adapter/core/security_factory.py +50 -44
  71. mcp_proxy_adapter/core/security_integration.py +72 -24
  72. mcp_proxy_adapter/core/server_adapter.py +68 -64
  73. mcp_proxy_adapter/core/server_engine.py +71 -53
  74. mcp_proxy_adapter/core/settings.py +68 -58
  75. mcp_proxy_adapter/core/ssl_utils.py +69 -56
  76. mcp_proxy_adapter/core/transport_manager.py +72 -60
  77. mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
  78. mcp_proxy_adapter/core/utils.py +4 -2
  79. mcp_proxy_adapter/custom_openapi.py +107 -99
  80. mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
  81. mcp_proxy_adapter/examples/commands/__init__.py +1 -1
  82. mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
  83. mcp_proxy_adapter/examples/debug_request_state.py +38 -19
  84. mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
  85. mcp_proxy_adapter/examples/demo_client.py +48 -36
  86. mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
  87. mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
  88. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
  89. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  90. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
  91. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  92. mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
  93. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
  94. mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
  95. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
  96. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  99. mcp_proxy_adapter/examples/full_application/main.py +27 -2
  100. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
  102. mcp_proxy_adapter/examples/generate_certificates.py +31 -16
  103. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
  104. mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
  105. mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
  106. mcp_proxy_adapter/examples/run_example.py +23 -5
  107. mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
  108. mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
  109. mcp_proxy_adapter/examples/run_security_tests.py +103 -41
  110. mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
  111. mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
  112. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
  113. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
  114. mcp_proxy_adapter/examples/security_test_client.py +196 -127
  115. mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
  116. mcp_proxy_adapter/examples/test_config.py +19 -4
  117. mcp_proxy_adapter/examples/test_config_generator.py +23 -7
  118. mcp_proxy_adapter/examples/test_examples.py +84 -56
  119. mcp_proxy_adapter/examples/universal_client.py +119 -62
  120. mcp_proxy_adapter/openapi.py +108 -115
  121. mcp_proxy_adapter/utils/config_generator.py +429 -274
  122. mcp_proxy_adapter/version.py +1 -2
  123. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
  124. mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
  125. mcp_proxy_adapter-6.3.3.dist-info/RECORD +0 -143
  126. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
  127. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
  128. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
  129. {mcp_proxy_adapter-6.3.3.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, NotFoundError, ParseError, InvalidRequestError,
15
- MethodNotFoundError, InvalidParamsError, InternalError, CommandError
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(command_name: str, params: Dict[str, Any], request_id: Optional[str] = None, request: Optional[Request] = None) -> Dict[str, Any]:
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, 'user_id'):
59
- context['user'] = {
60
- 'id': getattr(request.state, 'user_id', None),
61
- 'role': getattr(request.state, 'user_role', 'guest'),
62
- 'roles': getattr(request.state, 'user_roles', ['guest']),
63
- 'permissions': getattr(request.state, 'user_permissions', ['read'])
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(f"Error executing command: {str(e)}", data={"original_error": str(e)})
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(batch_requests: List[Dict[str, Any]], request: Optional[Request] = None) -> List[Dict[str, Any]]:
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(request_data: Dict[str, Any], request_id: Optional[str] = None, request: Optional[Request] = None) -> Dict[str, Any]:
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
- "registered_count": len(registry.get_all_commands())
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, 'dispatch'):
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(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
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(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
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(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
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(f"Checking permissions for {method}: user_roles={user_roles}, required={required_permissions}")
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(user_roles, user_permissions, required_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(f"Permission denied for {method}: user_roles={user_roles}, required={required_permissions}")
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(self, user_roles: list, user_permissions: list, required_permissions: list) -> bool:
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