mcp-proxy-adapter 6.2.21__py3-none-any.whl → 6.2.22__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/api/app.py +3 -0
- mcp_proxy_adapter/api/middleware/__init__.py +7 -7
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +59 -12
- mcp_proxy_adapter/api/middleware/unified_security.py +42 -6
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +99 -24
- mcp_proxy_adapter/core/protocol_manager.py +33 -5
- mcp_proxy_adapter/examples/generate_test_configs.py +30 -9
- mcp_proxy_adapter/examples/run_proxy_server.py +9 -7
- mcp_proxy_adapter/examples/run_security_tests.py +187 -36
- mcp_proxy_adapter/examples/security_test_client.py +182 -38
- mcp_proxy_adapter/main.py +12 -2
- mcp_proxy_adapter/utils/config_generator.py +740 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.2.21.dist-info → mcp_proxy_adapter-6.2.22.dist-info}/METADATA +9 -11
- {mcp_proxy_adapter-6.2.21.dist-info → mcp_proxy_adapter-6.2.22.dist-info}/RECORD +19 -17
- mcp_proxy_adapter-6.2.22.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-6.2.21.dist-info → mcp_proxy_adapter-6.2.22.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.2.21.dist-info → mcp_proxy_adapter-6.2.22.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.2.21.dist-info → mcp_proxy_adapter-6.2.22.dist-info}/top_level.txt +0 -0
mcp_proxy_adapter/api/app.py
CHANGED
@@ -350,6 +350,9 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
350
350
|
)
|
351
351
|
|
352
352
|
# Setup middleware using the new middleware package
|
353
|
+
print(f"DEBUG create_app: calling setup_middleware with config type: {type(current_config)}")
|
354
|
+
if hasattr(current_config, 'keys'):
|
355
|
+
print(f"DEBUG create_app: current_config keys: {list(current_config.keys())}")
|
353
356
|
setup_middleware(app, current_config)
|
354
357
|
|
355
358
|
# Use custom OpenAPI schema
|
@@ -23,18 +23,21 @@ def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None)
|
|
23
23
|
# Use provided configuration or fallback to global config
|
24
24
|
current_config = app_config if app_config is not None else config.get_all()
|
25
25
|
|
26
|
+
# Add protocol middleware FIRST (before other middleware)
|
27
|
+
setup_protocol_middleware(app, current_config)
|
28
|
+
|
26
29
|
# Create middleware factory
|
27
30
|
factory = MiddlewareFactory(app, current_config)
|
28
|
-
|
31
|
+
|
29
32
|
# Validate middleware configuration
|
30
33
|
if not factory.validate_middleware_config():
|
31
34
|
logger.error("Middleware configuration validation failed")
|
32
35
|
raise SystemExit(1)
|
33
|
-
|
36
|
+
|
34
37
|
logger.info("Using unified security middleware")
|
35
38
|
middleware_list = factory.create_all_middleware()
|
36
|
-
|
37
|
-
# Add middleware to application
|
39
|
+
|
40
|
+
# Add middleware to application AFTER protocol middleware
|
38
41
|
for middleware in middleware_list:
|
39
42
|
# For ASGI middleware, we need to wrap the application
|
40
43
|
if hasattr(middleware, 'dispatch'):
|
@@ -43,9 +46,6 @@ def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None)
|
|
43
46
|
else:
|
44
47
|
logger.warning(f"Middleware {middleware.__class__.__name__} doesn't have dispatch method")
|
45
48
|
|
46
|
-
# Add protocol middleware with current configuration
|
47
|
-
setup_protocol_middleware(app, current_config)
|
48
|
-
|
49
49
|
# Log middleware information
|
50
50
|
middleware_info = factory.get_middleware_info()
|
51
51
|
logger.info(f"Middleware setup completed:")
|
@@ -24,15 +24,37 @@ class ProtocolMiddleware(BaseHTTPMiddleware):
|
|
24
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
30
|
app_config: Application configuration dictionary (optional)
|
31
31
|
"""
|
32
32
|
super().__init__(app)
|
33
|
-
|
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
|
34
56
|
# Get protocol manager with current configuration
|
35
|
-
self.protocol_manager = get_protocol_manager(
|
57
|
+
self.protocol_manager = get_protocol_manager(normalized_config)
|
36
58
|
|
37
59
|
def update_config(self, new_config: Dict[str, Any]):
|
38
60
|
"""
|
@@ -41,21 +63,31 @@ class ProtocolMiddleware(BaseHTTPMiddleware):
|
|
41
63
|
Args:
|
42
64
|
new_config: New configuration dictionary
|
43
65
|
"""
|
44
|
-
|
45
|
-
|
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)
|
46
77
|
logger.info("Protocol middleware configuration updated")
|
47
78
|
|
48
79
|
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
49
80
|
"""
|
50
81
|
Process request through protocol middleware.
|
51
|
-
|
82
|
+
|
52
83
|
Args:
|
53
84
|
request: Incoming request
|
54
85
|
call_next: Next middleware/endpoint function
|
55
|
-
|
86
|
+
|
56
87
|
Returns:
|
57
88
|
Response object
|
58
89
|
"""
|
90
|
+
logger.info(f"ProtocolMiddleware.dispatch called for {request.method} {request.url.path}")
|
59
91
|
try:
|
60
92
|
# Get protocol from request
|
61
93
|
protocol = self._get_request_protocol(request)
|
@@ -132,22 +164,37 @@ class ProtocolMiddleware(BaseHTTPMiddleware):
|
|
132
164
|
def setup_protocol_middleware(app, app_config: Optional[Dict[str, Any]] = None):
|
133
165
|
"""
|
134
166
|
Setup protocol middleware for FastAPI application.
|
135
|
-
|
167
|
+
|
136
168
|
Args:
|
137
169
|
app: FastAPI application
|
138
170
|
app_config: Application configuration dictionary (optional)
|
139
171
|
"""
|
172
|
+
logger.info(f"setup_protocol_middleware - app_config type: {type(app_config)}")
|
173
|
+
|
140
174
|
# Check if protocol management is enabled
|
141
175
|
if app_config is None:
|
142
176
|
from mcp_proxy_adapter.config import config
|
143
177
|
app_config = config.get_all()
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
178
|
+
logger.info(f"setup_protocol_middleware - loaded from global config, type: {type(app_config)}")
|
179
|
+
|
180
|
+
logger.info(f"setup_protocol_middleware - final app_config type: {type(app_config)}")
|
181
|
+
|
182
|
+
if hasattr(app_config, 'get'):
|
183
|
+
logger.info(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.info(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.info(f"setup_protocol_middleware - app_config is not dict-like: {repr(app_config)}")
|
189
|
+
enabled = True
|
190
|
+
|
191
|
+
logger.info(f"setup_protocol_middleware - protocol management enabled: {enabled}")
|
192
|
+
|
148
193
|
if enabled:
|
149
194
|
# Create protocol middleware with current configuration
|
195
|
+
logger.info(f"setup_protocol_middleware - creating ProtocolMiddleware with config type: {type(app_config)}")
|
150
196
|
middleware = ProtocolMiddleware(app, app_config)
|
197
|
+
logger.info(f"setup_protocol_middleware - adding middleware to app")
|
151
198
|
app.add_middleware(ProtocolMiddleware, app_config=app_config)
|
152
199
|
logger.info("Protocol middleware added to application")
|
153
200
|
else:
|
@@ -65,12 +65,17 @@ class UnifiedSecurityMiddleware(BaseHTTPMiddleware):
|
|
65
65
|
# Use framework's FastAPI middleware
|
66
66
|
self.framework_middleware = self.security_integration.security_manager.create_fastapi_middleware()
|
67
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")
|
68
71
|
except Exception as e:
|
69
72
|
logger.error(f"Security framework integration failed: {e}")
|
70
73
|
# Instead of raising error, log warning and continue without security
|
71
74
|
logger.warning("Continuing without security framework - some security features will be disabled")
|
72
75
|
self.security_integration = None
|
73
76
|
self.framework_middleware = None
|
77
|
+
# Keep original app in place when framework middleware is unavailable
|
78
|
+
# BaseHTTPMiddleware initialized it via super().__init__(app)
|
74
79
|
|
75
80
|
logger.info("Unified security middleware initialized")
|
76
81
|
|
@@ -86,14 +91,45 @@ class UnifiedSecurityMiddleware(BaseHTTPMiddleware):
|
|
86
91
|
Response object
|
87
92
|
"""
|
88
93
|
try:
|
89
|
-
#
|
90
|
-
if self.
|
91
|
-
|
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)
|
92
129
|
else:
|
93
|
-
#
|
94
|
-
logger.debug("Security framework not available, continuing without security checks")
|
130
|
+
# No framework middleware, continue normally
|
95
131
|
return await call_next(request)
|
96
|
-
|
132
|
+
|
97
133
|
except SecurityValidationError as e:
|
98
134
|
# Handle security validation errors
|
99
135
|
return await self._handle_security_error(request, e)
|
@@ -15,6 +15,16 @@ from starlette.middleware.base import BaseHTTPMiddleware
|
|
15
15
|
|
16
16
|
from mcp_proxy_adapter.core.logging import logger
|
17
17
|
|
18
|
+
# Import mcp_security_framework components
|
19
|
+
try:
|
20
|
+
from mcp_security_framework import AuthManager
|
21
|
+
from mcp_security_framework.schemas.config import AuthConfig
|
22
|
+
_MCP_SECURITY_AVAILABLE = True
|
23
|
+
print("✅ mcp_security_framework available in middleware")
|
24
|
+
except ImportError:
|
25
|
+
_MCP_SECURITY_AVAILABLE = False
|
26
|
+
print("⚠️ mcp_security_framework not available in middleware, using basic auth")
|
27
|
+
|
18
28
|
|
19
29
|
class UserInfoMiddleware(BaseHTTPMiddleware):
|
20
30
|
"""
|
@@ -27,20 +37,43 @@ class UserInfoMiddleware(BaseHTTPMiddleware):
|
|
27
37
|
def __init__(self, app, config: Dict[str, Any]):
|
28
38
|
"""
|
29
39
|
Initialize user info middleware.
|
30
|
-
|
40
|
+
|
31
41
|
Args:
|
32
42
|
app: FastAPI application
|
33
43
|
config: Configuration dictionary
|
34
44
|
"""
|
35
45
|
super().__init__(app)
|
36
46
|
self.config = config
|
37
|
-
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
47
|
+
|
48
|
+
# Initialize AuthManager if available
|
49
|
+
self.auth_manager = None
|
50
|
+
self._security_available = _MCP_SECURITY_AVAILABLE
|
51
|
+
|
52
|
+
if self._security_available:
|
53
|
+
try:
|
54
|
+
# Get API keys configuration
|
55
|
+
security_config = config.get("security", {})
|
56
|
+
auth_config = security_config.get("auth", {})
|
57
|
+
|
58
|
+
# Create AuthConfig for mcp_security_framework
|
59
|
+
mcp_auth_config = AuthConfig(
|
60
|
+
enabled=True,
|
61
|
+
methods=["api_key"],
|
62
|
+
api_keys=auth_config.get("api_keys", {})
|
63
|
+
)
|
64
|
+
|
65
|
+
self.auth_manager = AuthManager(mcp_auth_config)
|
66
|
+
logger.info("✅ User info middleware initialized with mcp_security_framework")
|
67
|
+
except Exception as e:
|
68
|
+
logger.warning(f"⚠️ Failed to initialize AuthManager: {e}")
|
69
|
+
self._security_available = False
|
70
|
+
|
71
|
+
if not self._security_available:
|
72
|
+
# Fallback to basic API key handling
|
73
|
+
security_config = config.get("security", {})
|
74
|
+
auth_config = security_config.get("auth", {})
|
75
|
+
self.api_keys = auth_config.get("api_keys", {})
|
76
|
+
logger.info("ℹ️ User info middleware initialized with basic auth")
|
44
77
|
|
45
78
|
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
46
79
|
"""
|
@@ -56,28 +89,70 @@ class UserInfoMiddleware(BaseHTTPMiddleware):
|
|
56
89
|
# Extract API key from headers
|
57
90
|
api_key = request.headers.get("X-API-Key")
|
58
91
|
|
59
|
-
if api_key
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
92
|
+
if api_key:
|
93
|
+
if self.auth_manager and self._security_available:
|
94
|
+
try:
|
95
|
+
# Use mcp_security_framework AuthManager
|
96
|
+
auth_result = self.auth_manager.authenticate_api_key(api_key)
|
97
|
+
|
98
|
+
if auth_result.is_valid:
|
99
|
+
# Set user info from AuthManager result
|
100
|
+
request.state.user = {
|
101
|
+
"id": api_key,
|
102
|
+
"role": auth_result.roles[0] if auth_result.roles else "guest",
|
103
|
+
"roles": auth_result.roles or ["guest"],
|
104
|
+
"permissions": getattr(auth_result, 'permissions', ["read"])
|
105
|
+
}
|
106
|
+
logger.debug(f"✅ Authenticated user with mcp_security_framework: {request.state.user}")
|
107
|
+
else:
|
108
|
+
# Authentication failed
|
109
|
+
request.state.user = {
|
110
|
+
"id": None,
|
111
|
+
"role": "guest",
|
112
|
+
"roles": ["guest"],
|
113
|
+
"permissions": ["read"]
|
114
|
+
}
|
115
|
+
logger.debug(f"❌ Authentication failed for API key: {api_key[:8]}...")
|
116
|
+
except Exception as e:
|
117
|
+
logger.warning(f"⚠️ AuthManager error: {e}, falling back to basic auth")
|
118
|
+
self._security_available = False
|
119
|
+
|
120
|
+
if not self._security_available:
|
121
|
+
# Fallback to basic API key handling
|
122
|
+
if api_key in getattr(self, 'api_keys', {}):
|
123
|
+
user_role = self.api_keys[api_key]
|
124
|
+
|
125
|
+
# Get permissions for this role from roles file if available
|
126
|
+
role_permissions = ["read"] # default permissions
|
127
|
+
if hasattr(self, 'roles_config') and self.roles_config and user_role in self.roles_config:
|
128
|
+
role_permissions = self.roles_config[user_role].get("permissions", ["read"])
|
129
|
+
|
130
|
+
# Set user info in request.state
|
131
|
+
request.state.user = {
|
132
|
+
"id": api_key,
|
133
|
+
"role": user_role,
|
134
|
+
"roles": [user_role],
|
135
|
+
"permissions": role_permissions
|
136
|
+
}
|
137
|
+
|
138
|
+
logger.debug(f"✅ Authenticated user with basic auth: {request.state.user}")
|
139
|
+
else:
|
140
|
+
# API key not found
|
141
|
+
request.state.user = {
|
142
|
+
"id": None,
|
143
|
+
"role": "guest",
|
144
|
+
"roles": ["guest"],
|
145
|
+
"permissions": ["read"]
|
146
|
+
}
|
147
|
+
logger.debug(f"❌ API key not found: {api_key[:8]}...")
|
72
148
|
else:
|
73
|
-
#
|
149
|
+
# No API key provided - guest access
|
74
150
|
request.state.user = {
|
75
151
|
"id": None,
|
76
152
|
"role": "guest",
|
77
153
|
"roles": ["guest"],
|
78
154
|
"permissions": ["read"]
|
79
155
|
}
|
80
|
-
|
81
|
-
logger.debug("Set default guest user info")
|
156
|
+
logger.debug("ℹ️ No API key provided, using guest access")
|
82
157
|
|
83
158
|
return await call_next(request)
|
@@ -33,12 +33,33 @@ class ProtocolManager:
|
|
33
33
|
|
34
34
|
def _load_config(self):
|
35
35
|
"""Load protocol configuration from config."""
|
36
|
-
# Use provided config or fallback to global config
|
36
|
+
# Use provided config or fallback to global config; normalize types
|
37
37
|
current_config = self.app_config if self.app_config is not None else config.get_all()
|
38
|
-
|
38
|
+
logger.info(f"ProtocolManager._load_config - current_config type: {type(current_config)}")
|
39
|
+
|
40
|
+
if not hasattr(current_config, 'get'):
|
41
|
+
# Not a dict-like config, fallback to global
|
42
|
+
logger.info(f"ProtocolManager._load_config - current_config is not dict-like, falling back to global config")
|
43
|
+
current_config = config.get_all()
|
44
|
+
|
45
|
+
logger.info(f"ProtocolManager._load_config - final current_config type: {type(current_config)}")
|
46
|
+
if hasattr(current_config, 'get'):
|
47
|
+
logger.info(f"ProtocolManager._load_config - current_config keys: {list(current_config.keys()) if hasattr(current_config, 'keys') else 'no keys'}")
|
48
|
+
|
39
49
|
# Get protocols configuration
|
40
|
-
|
41
|
-
|
50
|
+
logger.info(f"ProtocolManager._load_config - before getting protocols")
|
51
|
+
try:
|
52
|
+
self.protocols_config = current_config.get("protocols", {})
|
53
|
+
logger.info(f"ProtocolManager._load_config - protocols_config type: {type(self.protocols_config)}")
|
54
|
+
if hasattr(self.protocols_config, 'get'):
|
55
|
+
logger.info(f"ProtocolManager._load_config - protocols_config is dict-like")
|
56
|
+
else:
|
57
|
+
logger.info(f"ProtocolManager._load_config - protocols_config is NOT dict-like: {repr(self.protocols_config)}")
|
58
|
+
except Exception as e:
|
59
|
+
logger.info(f"ProtocolManager._load_config - ERROR getting protocols: {e}")
|
60
|
+
self.protocols_config = {}
|
61
|
+
|
62
|
+
self.enabled = self.protocols_config.get("enabled", True) if hasattr(self.protocols_config, 'get') else True
|
42
63
|
|
43
64
|
# Get SSL configuration to determine allowed protocols
|
44
65
|
ssl_enabled = self._is_ssl_enabled(current_config)
|
@@ -162,7 +183,14 @@ class ProtocolManager:
|
|
162
183
|
Protocol configuration dictionary
|
163
184
|
"""
|
164
185
|
protocol_lower = protocol.lower()
|
165
|
-
|
186
|
+
cfg = self.protocols_config.get(protocol_lower, {})
|
187
|
+
# Ensure dict type
|
188
|
+
if isinstance(cfg, dict):
|
189
|
+
try:
|
190
|
+
return cfg.copy()
|
191
|
+
except Exception:
|
192
|
+
return {}
|
193
|
+
return {}
|
166
194
|
|
167
195
|
def validate_url_protocol(self, url: str) -> Tuple[bool, Optional[str]]:
|
168
196
|
"""
|
@@ -36,7 +36,18 @@ def generate_http_token_config(port: int = 8001) -> Dict[str, Any]:
|
|
36
36
|
"ssl": {"enabled": False},
|
37
37
|
"security": {
|
38
38
|
"enabled": True,
|
39
|
-
"auth": {
|
39
|
+
"auth": {
|
40
|
+
"enabled": True,
|
41
|
+
"methods": ["api_key"],
|
42
|
+
# Map API tokens to roles for testing
|
43
|
+
"api_keys": {
|
44
|
+
"test-token-123": "admin",
|
45
|
+
"user-token-456": "user",
|
46
|
+
"readonly-token-123": "readonly",
|
47
|
+
"guest-token-123": "guest",
|
48
|
+
"proxy-token-123": "proxy"
|
49
|
+
}
|
50
|
+
},
|
40
51
|
"permissions": {"enabled": True, "roles_file": "./roles.json"}
|
41
52
|
},
|
42
53
|
"registration": {
|
@@ -82,7 +93,17 @@ def generate_https_token_config(port: int = 8003) -> Dict[str, Any]:
|
|
82
93
|
},
|
83
94
|
"security": {
|
84
95
|
"enabled": True,
|
85
|
-
"auth": {
|
96
|
+
"auth": {
|
97
|
+
"enabled": True,
|
98
|
+
"methods": ["api_key"],
|
99
|
+
"api_keys": {
|
100
|
+
"test-token-123": "admin",
|
101
|
+
"user-token-456": "user",
|
102
|
+
"readonly-token-123": "readonly",
|
103
|
+
"guest-token-123": "guest",
|
104
|
+
"proxy-token-123": "proxy"
|
105
|
+
}
|
106
|
+
},
|
86
107
|
"permissions": {"enabled": True, "roles_file": "./roles.json"}
|
87
108
|
},
|
88
109
|
"registration": {
|
@@ -104,7 +125,7 @@ def generate_mtls_no_roles_config(port: int = 8004) -> Dict[str, Any]:
|
|
104
125
|
"enabled": True,
|
105
126
|
"cert_file": "./certs/localhost_server.crt",
|
106
127
|
"key_file": "./keys/localhost_server.key",
|
107
|
-
"ca_cert": "./certs/
|
128
|
+
"ca_cert": "./certs/mcp_proxy_adapter_ca_ca.crt",
|
108
129
|
"verify_client": True
|
109
130
|
},
|
110
131
|
"security": {
|
@@ -122,7 +143,7 @@ def generate_mtls_with_roles_config(port: int = 8005) -> Dict[str, Any]:
|
|
122
143
|
"enabled": True,
|
123
144
|
"cert_file": "./certs/localhost_server.crt",
|
124
145
|
"key_file": "./keys/localhost_server.key",
|
125
|
-
"ca_cert": "./certs/
|
146
|
+
"ca_cert": "./certs/mcp_proxy_adapter_ca_ca.crt",
|
126
147
|
"verify_client": True
|
127
148
|
},
|
128
149
|
"registration": {
|
@@ -160,7 +181,7 @@ def generate_roles_config() -> Dict[str, Any]:
|
|
160
181
|
"heartbeat",
|
161
182
|
"discover"
|
162
183
|
],
|
163
|
-
"tokens": []
|
184
|
+
"tokens": ["test-token-123"]
|
164
185
|
},
|
165
186
|
"user": {
|
166
187
|
"description": "User role with limited access",
|
@@ -172,7 +193,7 @@ def generate_roles_config() -> Dict[str, Any]:
|
|
172
193
|
"heartbeat",
|
173
194
|
"discover"
|
174
195
|
],
|
175
|
-
"tokens": []
|
196
|
+
"tokens": ["user-token-456"]
|
176
197
|
},
|
177
198
|
"readonly": {
|
178
199
|
"description": "Read-only role",
|
@@ -180,7 +201,7 @@ def generate_roles_config() -> Dict[str, Any]:
|
|
180
201
|
"read",
|
181
202
|
"discover"
|
182
203
|
],
|
183
|
-
"tokens": []
|
204
|
+
"tokens": ["readonly-token-123"]
|
184
205
|
},
|
185
206
|
"guest": {
|
186
207
|
"description": "Guest role with read-only access",
|
@@ -188,7 +209,7 @@ def generate_roles_config() -> Dict[str, Any]:
|
|
188
209
|
"read",
|
189
210
|
"discover"
|
190
211
|
],
|
191
|
-
"tokens": []
|
212
|
+
"tokens": ["guest-token-123"]
|
192
213
|
},
|
193
214
|
"proxy": {
|
194
215
|
"description": "Proxy role for registration",
|
@@ -198,7 +219,7 @@ def generate_roles_config() -> Dict[str, Any]:
|
|
198
219
|
"heartbeat",
|
199
220
|
"discover"
|
200
221
|
],
|
201
|
-
"tokens": []
|
222
|
+
"tokens": ["proxy-token-123"]
|
202
223
|
}
|
203
224
|
}
|
204
225
|
def generate_all_configs(output_dir: str) -> None:
|
@@ -19,7 +19,7 @@ from datetime import datetime, timedelta
|
|
19
19
|
|
20
20
|
from fastapi import FastAPI, HTTPException
|
21
21
|
from pydantic import BaseModel
|
22
|
-
import
|
22
|
+
from mcp_proxy_adapter.core.server_adapter import UnifiedServerRunner
|
23
23
|
|
24
24
|
|
25
25
|
# Simple in-memory storage for registered adapters
|
@@ -130,13 +130,15 @@ def main() -> None:
|
|
130
130
|
print(" POST /proxy/heartbeat - Heartbeat from adapter")
|
131
131
|
print("⚡ Press Ctrl+C to stop\n")
|
132
132
|
|
133
|
-
# Run server
|
134
|
-
|
133
|
+
# Run server via unified runner (hypercorn under the hood)
|
134
|
+
runner = UnifiedServerRunner()
|
135
|
+
runner.run_server(
|
135
136
|
app,
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
137
|
+
{
|
138
|
+
"host": args.host,
|
139
|
+
"port": args.port,
|
140
|
+
"log_level": args.log_level,
|
141
|
+
},
|
140
142
|
)
|
141
143
|
|
142
144
|
|