mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.0.1__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 (212) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +209 -79
  3. mcp_proxy_adapter/api/handlers.py +16 -5
  4. mcp_proxy_adapter/api/middleware/__init__.py +14 -9
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  7. mcp_proxy_adapter/api/middleware/protocol_middleware.py +84 -18
  8. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  9. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  10. mcp_proxy_adapter/commands/__init__.py +7 -1
  11. mcp_proxy_adapter/commands/base.py +7 -4
  12. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  13. mcp_proxy_adapter/commands/command_registry.py +8 -0
  14. mcp_proxy_adapter/commands/echo_command.py +81 -0
  15. mcp_proxy_adapter/commands/health_command.py +1 -1
  16. mcp_proxy_adapter/commands/help_command.py +21 -14
  17. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  18. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  19. mcp_proxy_adapter/commands/security_command.py +488 -0
  20. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  21. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  22. mcp_proxy_adapter/config.py +323 -40
  23. mcp_proxy_adapter/core/app_factory.py +410 -0
  24. mcp_proxy_adapter/core/app_runner.py +272 -0
  25. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  26. mcp_proxy_adapter/core/client.py +574 -0
  27. mcp_proxy_adapter/core/client_manager.py +284 -0
  28. mcp_proxy_adapter/core/client_security.py +384 -0
  29. mcp_proxy_adapter/core/logging.py +8 -3
  30. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  31. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  32. mcp_proxy_adapter/core/protocol_manager.py +169 -10
  33. mcp_proxy_adapter/core/proxy_client.py +602 -0
  34. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  35. mcp_proxy_adapter/core/security_adapter.py +12 -15
  36. mcp_proxy_adapter/core/security_integration.py +286 -0
  37. mcp_proxy_adapter/core/server_adapter.py +282 -0
  38. mcp_proxy_adapter/core/server_engine.py +270 -0
  39. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  40. mcp_proxy_adapter/core/transport_manager.py +5 -5
  41. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  42. mcp_proxy_adapter/examples/__init__.py +13 -4
  43. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  44. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  45. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  46. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  47. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  48. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  49. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  50. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  51. mcp_proxy_adapter/examples/demo_client.py +275 -0
  52. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  53. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  54. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  55. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  56. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  57. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  58. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  59. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  60. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  61. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  62. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  63. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  64. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  65. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  66. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  67. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  68. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  69. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  70. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  71. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  72. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  73. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  74. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  75. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  76. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  77. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  78. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  79. mcp_proxy_adapter/examples/run_example.py +59 -0
  80. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  81. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  82. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  83. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  84. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  85. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  86. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  87. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  88. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  89. mcp_proxy_adapter/examples/test_config.py +148 -0
  90. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  91. mcp_proxy_adapter/examples/test_examples.py +281 -0
  92. mcp_proxy_adapter/examples/universal_client.py +620 -0
  93. mcp_proxy_adapter/main.py +66 -148
  94. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  95. mcp_proxy_adapter/version.py +5 -2
  96. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  97. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  98. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  99. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  100. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  101. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  102. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  103. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  104. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  105. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  106. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  107. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  108. mcp_proxy_adapter/api/middleware/security.py +0 -376
  109. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  110. mcp_proxy_adapter/examples/README.md +0 -124
  111. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  112. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  113. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  114. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  115. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  116. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  117. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  118. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  119. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  120. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  121. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  122. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  123. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  124. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  125. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  126. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  127. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  128. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  129. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  130. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  131. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  132. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  133. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  134. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  135. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  136. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  137. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  138. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  139. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  140. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  141. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  142. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  143. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  144. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  145. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  146. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  147. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  148. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  149. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  150. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  153. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  154. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  155. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  156. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  157. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  158. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  159. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  160. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  161. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  162. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  163. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  164. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  166. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  167. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  168. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  169. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  170. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  171. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  172. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  173. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  174. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  175. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  176. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  177. mcp_proxy_adapter/tests/__init__.py +0 -0
  178. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  180. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  181. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  182. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  183. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  184. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  185. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  186. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  187. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  188. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  189. mcp_proxy_adapter/tests/conftest.py +0 -131
  190. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  191. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  192. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  193. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  194. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  195. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  196. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  197. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  198. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  199. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  200. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  201. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  202. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  203. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  204. mcp_proxy_adapter/tests/test_config.py +0 -127
  205. mcp_proxy_adapter/tests/test_utils.py +0 -65
  206. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  207. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  208. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  209. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  210. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  211. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,148 @@
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 typing import Dict, Any, Optional, Callable, Awaitable
13
+ from fastapi import Request, Response
14
+ from starlette.middleware.base import BaseHTTPMiddleware
15
+
16
+ from mcp_proxy_adapter.core.logging import logger
17
+
18
+
19
+ class CommandPermissionMiddleware(BaseHTTPMiddleware):
20
+ """
21
+ Middleware for checking command permissions.
22
+
23
+ This middleware checks if the authenticated user has the required
24
+ permissions to execute specific commands.
25
+ """
26
+
27
+ def __init__(self, app, config: Dict[str, Any]):
28
+ """
29
+ Initialize command permission middleware.
30
+
31
+ Args:
32
+ app: FastAPI application
33
+ config: Configuration dictionary
34
+ """
35
+ super().__init__(app)
36
+ self.config = config
37
+
38
+ # Define command permissions
39
+ self.command_permissions = {
40
+ "echo": ["read"],
41
+ "health": ["read"],
42
+ "role_test": ["read"],
43
+ "config": ["read"],
44
+ "help": ["read"],
45
+ # Add more commands as needed
46
+ }
47
+
48
+ logger.info("Command permission middleware initialized")
49
+
50
+ async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
51
+ """
52
+ Process request and check command permissions.
53
+
54
+ Args:
55
+ request: Request object
56
+ call_next: Next handler
57
+
58
+ Returns:
59
+ Response object
60
+ """
61
+ # Only check permissions for /cmd endpoint
62
+ if request.url.path != "/cmd":
63
+ return await call_next(request)
64
+
65
+ try:
66
+ # Get request body
67
+ body = await request.body()
68
+ if not body:
69
+ return await call_next(request)
70
+
71
+ # Parse JSON-RPC request
72
+ try:
73
+ data = json.loads(body)
74
+ except json.JSONDecodeError:
75
+ return await call_next(request)
76
+
77
+ # Extract method (command name)
78
+ method = data.get("method")
79
+ if not method:
80
+ return await call_next(request)
81
+
82
+ # Check if method requires permissions
83
+ if method not in self.command_permissions:
84
+ return await call_next(request)
85
+
86
+ required_permissions = self.command_permissions[method]
87
+
88
+ # Get user info from request state
89
+ user_info = getattr(request.state, "user", None)
90
+ if not user_info:
91
+ logger.warning(f"No user info found for command {method}")
92
+ return await call_next(request)
93
+
94
+ user_roles = user_info.get("roles", [])
95
+ user_permissions = user_info.get("permissions", [])
96
+
97
+ logger.debug(f"Checking permissions for {method}: user_roles={user_roles}, required={required_permissions}")
98
+
99
+ # Check if user has required permissions
100
+ has_permission = self._check_permissions(user_roles, user_permissions, required_permissions)
101
+
102
+ if not has_permission:
103
+ logger.warning(f"Permission denied for {method}: user_roles={user_roles}, required={required_permissions}")
104
+
105
+ # Return permission denied response
106
+ error_response = {
107
+ "error": {
108
+ "code": 403,
109
+ "message": f"Permission denied: {method} requires {required_permissions}",
110
+ "type": "permission_denied"
111
+ }
112
+ }
113
+
114
+ return Response(
115
+ content=json.dumps(error_response),
116
+ status_code=403,
117
+ media_type="application/json"
118
+ )
119
+
120
+ logger.debug(f"Permission granted for {method}")
121
+ return await call_next(request)
122
+
123
+ except Exception as e:
124
+ logger.error(f"Error in command permission middleware: {e}")
125
+ return await call_next(request)
126
+
127
+ def _check_permissions(self, user_roles: list, user_permissions: list, required_permissions: list) -> bool:
128
+ """
129
+ Check if user has required permissions.
130
+
131
+ Args:
132
+ user_roles: User roles
133
+ user_permissions: User permissions
134
+ required_permissions: Required permissions
135
+
136
+ Returns:
137
+ True if user has required permissions
138
+ """
139
+ # Admin has all permissions
140
+ if "admin" in user_roles or "*" in user_permissions:
141
+ return True
142
+
143
+ # Check if user has all required permissions
144
+ for required in required_permissions:
145
+ if required not in user_permissions:
146
+ return False
147
+
148
+ return True
@@ -13,9 +13,10 @@ from fastapi import FastAPI
13
13
  from mcp_proxy_adapter.core.logging import logger
14
14
  from mcp_proxy_adapter.core.security_factory import SecurityFactory
15
15
  from .base import BaseMiddleware
16
- from .security import SecurityMiddleware
16
+ from .unified_security import UnifiedSecurityMiddleware
17
17
  from .error_handling import ErrorHandlingMiddleware
18
18
  from .logging import LoggingMiddleware
19
+ from .user_info_middleware import UserInfoMiddleware
19
20
 
20
21
 
21
22
  class MiddlewareFactory:
@@ -40,12 +41,12 @@ class MiddlewareFactory:
40
41
 
41
42
  logger.info("Middleware factory initialized")
42
43
 
43
- def create_security_middleware(self) -> Optional[SecurityMiddleware]:
44
+ def create_security_middleware(self) -> Optional[UnifiedSecurityMiddleware]:
44
45
  """
45
- Create security middleware.
46
+ Create unified security middleware.
46
47
 
47
48
  Returns:
48
- SecurityMiddleware instance or None if creation failed
49
+ UnifiedSecurityMiddleware instance or None if creation failed
49
50
  """
50
51
  try:
51
52
  security_config = self.config.get("security", {})
@@ -54,14 +55,14 @@ class MiddlewareFactory:
54
55
  logger.info("Security middleware disabled by configuration")
55
56
  return None
56
57
 
57
- middleware = SecurityMiddleware(self.app, self.config)
58
+ middleware = UnifiedSecurityMiddleware(self.app, self.config)
58
59
  self.middleware_stack.append(middleware)
59
60
 
60
- logger.info("Security middleware created successfully")
61
+ logger.info("Unified security middleware created successfully")
61
62
  return middleware
62
63
 
63
64
  except Exception as e:
64
- logger.error(f"Failed to create security middleware: {e}")
65
+ logger.error(f"Failed to create unified security middleware: {e}")
65
66
  return None
66
67
 
67
68
  def create_error_handling_middleware(self) -> Optional[ErrorHandlingMiddleware]:
@@ -106,6 +107,24 @@ class MiddlewareFactory:
106
107
  logger.error(f"Failed to create logging middleware: {e}")
107
108
  return None
108
109
 
110
+ def create_user_info_middleware(self) -> Optional[UserInfoMiddleware]:
111
+ """
112
+ Create user info middleware.
113
+
114
+ Returns:
115
+ UserInfoMiddleware instance or None if creation failed
116
+ """
117
+ try:
118
+ middleware = UserInfoMiddleware(self.app, self.config)
119
+ self.middleware_stack.append(middleware)
120
+
121
+ logger.info("User info middleware created successfully")
122
+ return middleware
123
+
124
+ except Exception as e:
125
+ logger.error(f"Failed to create user info middleware: {e}")
126
+ return None
127
+
109
128
 
110
129
 
111
130
  def create_all_middleware(self) -> List[BaseMiddleware]:
@@ -132,6 +151,11 @@ class MiddlewareFactory:
132
151
  if logging_middleware:
133
152
  middleware_list.append(logging_middleware)
134
153
 
154
+ # Create user info middleware
155
+ user_info_middleware = self.create_user_info_middleware()
156
+ if user_info_middleware:
157
+ middleware_list.append(user_info_middleware)
158
+
135
159
  logger.info(f"Created {len(middleware_list)} middleware components")
136
160
  return middleware_list
137
161
 
@@ -152,14 +176,14 @@ class MiddlewareFactory:
152
176
  return middleware
153
177
  return None
154
178
 
155
- def get_security_middleware(self) -> Optional[SecurityMiddleware]:
179
+ def get_security_middleware(self) -> Optional[UnifiedSecurityMiddleware]:
156
180
  """
157
- Get security middleware instance.
181
+ Get unified security middleware instance.
158
182
 
159
183
  Returns:
160
- SecurityMiddleware instance or None if not found
184
+ UnifiedSecurityMiddleware instance or None if not found
161
185
  """
162
- return self.get_middleware_by_type(SecurityMiddleware)
186
+ return self.get_middleware_by_type(UnifiedSecurityMiddleware)
163
187
 
164
188
  def validate_middleware_config(self) -> bool:
165
189
  """
@@ -213,7 +237,7 @@ class MiddlewareFactory:
213
237
  middleware_type = type(middleware).__name__
214
238
  info["middleware_types"].append(middleware_type)
215
239
 
216
- if isinstance(middleware, SecurityMiddleware):
240
+ if isinstance(middleware, UnifiedSecurityMiddleware):
217
241
  info["security_enabled"] = True
218
242
 
219
243
  return info
@@ -4,12 +4,12 @@ Protocol middleware module.
4
4
  This module provides middleware for validating protocol access based on configuration.
5
5
  """
6
6
 
7
- from typing import Callable
7
+ from typing import Callable, Dict, Any, Optional
8
8
  from fastapi import Request, Response
9
9
  from starlette.middleware.base import BaseHTTPMiddleware
10
10
  from starlette.responses import JSONResponse
11
11
 
12
- from mcp_proxy_adapter.core.protocol_manager import protocol_manager
12
+ from mcp_proxy_adapter.core.protocol_manager import get_protocol_manager
13
13
  from mcp_proxy_adapter.core.logging import logger
14
14
 
15
15
 
@@ -21,28 +21,73 @@ class ProtocolMiddleware(BaseHTTPMiddleware):
21
21
  based on the protocol configuration.
22
22
  """
23
23
 
24
- def __init__(self, app, protocol_manager_instance=None):
24
+ def __init__(self, app, app_config: Optional[Dict[str, Any]] = None):
25
25
  """
26
26
  Initialize protocol middleware.
27
-
27
+
28
28
  Args:
29
29
  app: FastAPI application
30
- protocol_manager_instance: Protocol manager instance (optional)
30
+ app_config: Application configuration dictionary (optional)
31
31
  """
32
32
  super().__init__(app)
33
- self.protocol_manager = protocol_manager_instance or protocol_manager
33
+ # Normalize config to dictionary
34
+ normalized_config: Optional[Dict[str, Any]]
35
+ if app_config is None:
36
+ normalized_config = None
37
+ elif hasattr(app_config, 'get_all'):
38
+ try:
39
+ normalized_config = app_config.get_all()
40
+ except Exception as e:
41
+ logger.debug(f"ProtocolMiddleware - Error calling get_all(): {e}, type: {type(app_config)}")
42
+ normalized_config = None
43
+ elif hasattr(app_config, 'keys'):
44
+ normalized_config = app_config # Already dict-like
45
+ else:
46
+ logger.debug(f"ProtocolMiddleware - app_config is not dict-like, type: {type(app_config)}, value: {repr(app_config)}")
47
+ normalized_config = None
48
+
49
+ logger.debug(f"ProtocolMiddleware - normalized_config type: {type(normalized_config)}")
50
+ if normalized_config:
51
+ logger.debug(f"ProtocolMiddleware - protocols in config: {'protocols' in normalized_config}")
52
+ if 'protocols' in normalized_config:
53
+ logger.debug(f"ProtocolMiddleware - protocols type: {type(normalized_config['protocols'])}")
54
+
55
+ self.app_config = normalized_config
56
+ # Get protocol manager with current configuration
57
+ self.protocol_manager = get_protocol_manager(normalized_config)
58
+
59
+ def update_config(self, new_config: Dict[str, Any]):
60
+ """
61
+ Update configuration and reload protocol manager.
62
+
63
+ Args:
64
+ new_config: New configuration dictionary
65
+ """
66
+ # Normalize new config
67
+ if hasattr(new_config, 'get_all'):
68
+ try:
69
+ self.app_config = new_config.get_all()
70
+ except Exception:
71
+ self.app_config = None
72
+ elif hasattr(new_config, 'keys'):
73
+ self.app_config = new_config
74
+ else:
75
+ self.app_config = None
76
+ self.protocol_manager = get_protocol_manager(self.app_config)
77
+ logger.info("Protocol middleware configuration updated")
34
78
 
35
79
  async def dispatch(self, request: Request, call_next: Callable) -> Response:
36
80
  """
37
81
  Process request through protocol middleware.
38
-
82
+
39
83
  Args:
40
84
  request: Incoming request
41
85
  call_next: Next middleware/endpoint function
42
-
86
+
43
87
  Returns:
44
88
  Response object
45
89
  """
90
+ logger.debug(f"ProtocolMiddleware.dispatch called for {request.method} {request.url.path}")
46
91
  try:
47
92
  # Get protocol from request
48
93
  protocol = self._get_request_protocol(request)
@@ -116,20 +161,41 @@ class ProtocolMiddleware(BaseHTTPMiddleware):
116
161
  return "http"
117
162
 
118
163
 
119
- def setup_protocol_middleware(app, protocol_manager_instance=None):
164
+ def setup_protocol_middleware(app, app_config: Optional[Dict[str, Any]] = None):
120
165
  """
121
166
  Setup protocol middleware for FastAPI application.
122
-
167
+
123
168
  Args:
124
169
  app: FastAPI application
125
- protocol_manager_instance: Protocol manager instance (optional)
170
+ app_config: Application configuration dictionary (optional)
126
171
  """
127
- if protocol_manager_instance is None:
128
- protocol_manager_instance = protocol_manager
129
-
130
- # Only add middleware if protocol management is enabled
131
- if protocol_manager_instance.enabled:
132
- app.add_middleware(ProtocolMiddleware, protocol_manager_instance=protocol_manager_instance)
172
+ logger.debug(f"setup_protocol_middleware - app_config type: {type(app_config)}")
173
+
174
+ # Check if protocol management is enabled
175
+ if app_config is None:
176
+ from mcp_proxy_adapter.config import config
177
+ app_config = config.get_all()
178
+ logger.debug(f"setup_protocol_middleware - loaded from global config, type: {type(app_config)}")
179
+
180
+ logger.debug(f"setup_protocol_middleware - final app_config type: {type(app_config)}")
181
+
182
+ if hasattr(app_config, 'get'):
183
+ logger.debug(f"setup_protocol_middleware - app_config keys: {list(app_config.keys()) if hasattr(app_config, 'keys') else 'no keys'}")
184
+ protocols_config = app_config.get("protocols", {})
185
+ logger.debug(f"setup_protocol_middleware - protocols_config type: {type(protocols_config)}")
186
+ enabled = protocols_config.get("enabled", True) if hasattr(protocols_config, 'get') else True
187
+ else:
188
+ logger.debug(f"setup_protocol_middleware - app_config is not dict-like: {repr(app_config)}")
189
+ enabled = True
190
+
191
+ logger.debug(f"setup_protocol_middleware - protocol management enabled: {enabled}")
192
+
193
+ if enabled:
194
+ # Create protocol middleware with current configuration
195
+ logger.debug(f"setup_protocol_middleware - creating ProtocolMiddleware with config type: {type(app_config)}")
196
+ middleware = ProtocolMiddleware(app, app_config)
197
+ logger.debug(f"setup_protocol_middleware - adding middleware to app")
198
+ app.add_middleware(ProtocolMiddleware, app_config=app_config)
133
199
  logger.info("Protocol middleware added to application")
134
200
  else:
135
- logger.debug("Protocol management is disabled, skipping protocol middleware")
201
+ logger.info("Protocol management is disabled, skipping protocol middleware")
@@ -0,0 +1,197 @@
1
+ """
2
+ Unified Security Middleware - Direct Framework Integration
3
+
4
+ This middleware now directly uses mcp_security_framework components
5
+ instead of custom implementations.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import time
12
+ import logging
13
+ from typing import Dict, Any, Optional, Callable, Awaitable
14
+ from fastapi import Request, Response
15
+ from starlette.middleware.base import BaseHTTPMiddleware
16
+
17
+ # Direct import from framework
18
+ try:
19
+ from mcp_security_framework.middleware.fastapi_middleware import FastAPISecurityMiddleware
20
+ from mcp_security_framework import SecurityManager
21
+ from mcp_security_framework.schemas.config import SecurityConfig
22
+ SECURITY_FRAMEWORK_AVAILABLE = True
23
+ except ImportError:
24
+ SECURITY_FRAMEWORK_AVAILABLE = False
25
+ FastAPISecurityMiddleware = None
26
+ SecurityManager = None
27
+ SecurityConfig = None
28
+
29
+ from mcp_proxy_adapter.core.logging import logger
30
+ from mcp_proxy_adapter.core.security_integration import create_security_integration
31
+
32
+
33
+ class SecurityValidationError(Exception):
34
+ """Security validation error."""
35
+
36
+ def __init__(self, message: str, error_code: int):
37
+ self.message = message
38
+ self.error_code = error_code
39
+ super().__init__(self.message)
40
+
41
+
42
+ class UnifiedSecurityMiddleware(BaseHTTPMiddleware):
43
+ """
44
+ Unified security middleware using mcp_security_framework.
45
+
46
+ This middleware now directly uses the security framework's FastAPI middleware
47
+ and components instead of custom implementations.
48
+ """
49
+
50
+ def __init__(self, app, config: Dict[str, Any]):
51
+ """
52
+ Initialize unified security middleware.
53
+
54
+ Args:
55
+ app: FastAPI application
56
+ config: mcp_proxy_adapter configuration dictionary
57
+ """
58
+ super().__init__(app)
59
+ self.config = config
60
+
61
+ # Create security integration
62
+ try:
63
+ security_config = config.get("security", {})
64
+ self.security_integration = create_security_integration(security_config)
65
+ # Use framework's FastAPI middleware
66
+ self.framework_middleware = self.security_integration.security_manager.create_fastapi_middleware()
67
+ logger.info("Using mcp_security_framework FastAPI middleware")
68
+ # IMPORTANT: Don't replace self.app! This breaks the middleware chain.
69
+ # Instead, store the framework middleware for use in dispatch method.
70
+ logger.info("Framework middleware will be used in dispatch method")
71
+ except Exception as e:
72
+ logger.error(f"Security framework integration failed: {e}")
73
+ # Instead of raising error, log warning and continue without security
74
+ logger.warning("Continuing without security framework - some security features will be disabled")
75
+ self.security_integration = None
76
+ self.framework_middleware = None
77
+ # Keep original app in place when framework middleware is unavailable
78
+ # BaseHTTPMiddleware initialized it via super().__init__(app)
79
+
80
+ logger.info("Unified security middleware initialized")
81
+
82
+ async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
83
+ """
84
+ Process request using framework middleware.
85
+
86
+ Args:
87
+ request: Request object
88
+ call_next: Next handler
89
+
90
+ Returns:
91
+ Response object
92
+ """
93
+ try:
94
+ # Simple built-in API key enforcement if configured
95
+ security_cfg = self.config.get("security", {}) if isinstance(self.config, dict) else {}
96
+ auth_cfg = security_cfg.get("auth", {})
97
+ permissions_cfg = security_cfg.get("permissions", {})
98
+ public_paths = set(auth_cfg.get("public_paths", ["/health", "/docs", "/openapi.json"]))
99
+ # JSON-RPC endpoint must not be public when API key is required
100
+ public_paths.discard("/api/jsonrpc")
101
+ path = request.url.path
102
+ methods = set(auth_cfg.get("methods", []))
103
+ api_keys: Dict[str, str] = auth_cfg.get("api_keys", {}) or {}
104
+
105
+ # Enforce only for non-public paths when api_key method configured
106
+ if security_cfg.get("enabled", False) and ("api_key" in methods) and (path not in public_paths):
107
+ # Accept either X-API-Key or Authorization: Bearer
108
+ token = request.headers.get("X-API-Key")
109
+ if not token:
110
+ authz = request.headers.get("Authorization", "")
111
+ if authz.startswith("Bearer "):
112
+ token = authz[7:]
113
+ if not token or (api_keys and token not in api_keys):
114
+ from fastapi.responses import JSONResponse
115
+ return JSONResponse(status_code=401, content={
116
+ "error": {
117
+ "code": 401,
118
+ "message": "Unauthorized: invalid or missing API key",
119
+ "type": "authentication_error"
120
+ }
121
+ })
122
+
123
+ # Continue with framework middleware or regular flow
124
+ if self.framework_middleware:
125
+ # If framework middleware exists, we need to call it manually
126
+ # This is a workaround since we can't chain ASGI apps in BaseHTTPMiddleware
127
+ logger.debug("Framework middleware exists, continuing with regular call_next")
128
+ return await call_next(request)
129
+ else:
130
+ # No framework middleware, continue normally
131
+ return await call_next(request)
132
+
133
+ except SecurityValidationError as e:
134
+ # Handle security validation errors
135
+ return await self._handle_security_error(request, e)
136
+ except Exception as e:
137
+ # Handle other errors
138
+ logger.error(f"Unexpected error in unified security middleware: {e}")
139
+ return await self._handle_general_error(request, e)
140
+
141
+
142
+
143
+ async def _handle_security_error(self, request: Request, error: SecurityValidationError) -> Response:
144
+ """
145
+ Handle security validation errors.
146
+
147
+ Args:
148
+ request: Request object
149
+ error: Security validation error
150
+
151
+ Returns:
152
+ Error response
153
+ """
154
+ from fastapi.responses import JSONResponse
155
+
156
+ error_response = {
157
+ "error": {
158
+ "code": error.error_code,
159
+ "message": error.message,
160
+ "type": "security_validation_error"
161
+ }
162
+ }
163
+
164
+ logger.warning(f"Security validation failed: {error.message}")
165
+
166
+ return JSONResponse(
167
+ status_code=error.error_code,
168
+ content=error_response
169
+ )
170
+
171
+ async def _handle_general_error(self, request: Request, error: Exception) -> Response:
172
+ """
173
+ Handle general errors.
174
+
175
+ Args:
176
+ request: Request object
177
+ error: General error
178
+
179
+ Returns:
180
+ Error response
181
+ """
182
+ from fastapi.responses import JSONResponse
183
+
184
+ error_response = {
185
+ "error": {
186
+ "code": 500,
187
+ "message": "Internal server error",
188
+ "type": "general_error"
189
+ }
190
+ }
191
+
192
+ logger.error(f"General error in security middleware: {error}")
193
+
194
+ return JSONResponse(
195
+ status_code=500,
196
+ content=error_response
197
+ )