mcp-proxy-adapter 4.1.0__py3-none-any.whl → 6.0.0__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/__main__.py +12 -0
- mcp_proxy_adapter/api/app.py +138 -11
- mcp_proxy_adapter/api/handlers.py +16 -1
- mcp_proxy_adapter/api/middleware/__init__.py +30 -29
- mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +219 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
- mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
- mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
- mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
- mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
- mcp_proxy_adapter/api/middleware/security.py +376 -0
- mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/commands/__init__.py +13 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +61 -30
- mcp_proxy_adapter/commands/builtin_commands.py +89 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +705 -345
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/health_command.py +7 -0
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +99 -2
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +827 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +11 -0
- mcp_proxy_adapter/core/protocol_manager.py +226 -0
- mcp_proxy_adapter/core/proxy_registration.py +270 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +373 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +233 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/basic_server/config.json +58 -23
- mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
- mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
- mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
- mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
- mcp_proxy_adapter/examples/basic_server/server.py +17 -1
- mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
- mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
- mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
- mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
- mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
- mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
- mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
- mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
- mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
- mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
- mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
- mcp_proxy_adapter/main.py +175 -0
- mcp_proxy_adapter/schemas/roles_schema.json +162 -0
- mcp_proxy_adapter/tests/unit/test_config.py +53 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,529 @@
|
|
1
|
+
"""
|
2
|
+
Token Management Commands
|
3
|
+
|
4
|
+
This module provides commands for managing JWT and API tokens:
|
5
|
+
- Token creation
|
6
|
+
- Token validation
|
7
|
+
- Token revocation
|
8
|
+
- Token listing
|
9
|
+
- Token refresh
|
10
|
+
|
11
|
+
Author: MCP Proxy Adapter Team
|
12
|
+
Version: 1.0.0
|
13
|
+
"""
|
14
|
+
|
15
|
+
import json
|
16
|
+
import logging
|
17
|
+
import time
|
18
|
+
import uuid
|
19
|
+
from datetime import datetime, timedelta
|
20
|
+
from pathlib import Path
|
21
|
+
from typing import Dict, List, Any, Optional, Union
|
22
|
+
|
23
|
+
from .base import Command
|
24
|
+
from .result import SuccessResult, ErrorResult, CommandResult
|
25
|
+
from ..core.auth_validator import AuthValidator
|
26
|
+
|
27
|
+
|
28
|
+
class TokenManagementCommand(Command):
|
29
|
+
"""
|
30
|
+
Token management commands.
|
31
|
+
|
32
|
+
Provides commands for creating, validating, revoking, listing, and refreshing tokens.
|
33
|
+
Supports both JWT and API tokens.
|
34
|
+
"""
|
35
|
+
|
36
|
+
def __init__(self):
|
37
|
+
"""Initialize token management command."""
|
38
|
+
super().__init__()
|
39
|
+
self.auth_validator = AuthValidator()
|
40
|
+
self.logger = logging.getLogger(__name__)
|
41
|
+
|
42
|
+
# Load configuration
|
43
|
+
from ..config import config
|
44
|
+
self.token_config = config.get("ssl", {}).get("token_auth", {})
|
45
|
+
self.tokens_file = self.token_config.get("tokens_file", "tokens.json")
|
46
|
+
self.token_expiry = self.token_config.get("token_expiry", 3600)
|
47
|
+
self.jwt_secret = self.token_config.get("jwt_secret", "")
|
48
|
+
self.jwt_algorithm = self.token_config.get("jwt_algorithm", "HS256")
|
49
|
+
|
50
|
+
async def execute(self, **kwargs) -> CommandResult:
|
51
|
+
"""
|
52
|
+
Execute token management command.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
**kwargs: Command parameters containing:
|
56
|
+
- method: Command method (token_create, token_validate, token_revoke, token_list, token_refresh)
|
57
|
+
- token_type: Type of token for creation (jwt/api)
|
58
|
+
- token_data: Token data for creation
|
59
|
+
- token: Token string for validation/revocation/refresh
|
60
|
+
- active_only: Boolean for token listing
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
CommandResult with operation result
|
64
|
+
"""
|
65
|
+
try:
|
66
|
+
method = kwargs.get("method")
|
67
|
+
|
68
|
+
if method == "token_create":
|
69
|
+
token_type = kwargs.get("token_type", "api")
|
70
|
+
token_data = kwargs.get("token_data", {})
|
71
|
+
return await self.token_create(token_type, token_data)
|
72
|
+
elif method == "token_validate":
|
73
|
+
token = kwargs.get("token")
|
74
|
+
token_type = kwargs.get("token_type", "auto")
|
75
|
+
return await self.token_validate(token, token_type)
|
76
|
+
elif method == "token_revoke":
|
77
|
+
token = kwargs.get("token")
|
78
|
+
return await self.token_revoke(token)
|
79
|
+
elif method == "token_list":
|
80
|
+
active_only = kwargs.get("active_only", True)
|
81
|
+
return await self.token_list(active_only)
|
82
|
+
elif method == "token_refresh":
|
83
|
+
token = kwargs.get("token")
|
84
|
+
return await self.token_refresh(token)
|
85
|
+
else:
|
86
|
+
return ErrorResult(
|
87
|
+
message=f"Unknown method: {method}",
|
88
|
+
code=-32601
|
89
|
+
)
|
90
|
+
|
91
|
+
except Exception as e:
|
92
|
+
self.logger.error(f"Token management command execution error: {e}")
|
93
|
+
return ErrorResult(
|
94
|
+
message=f"Token management command failed: {str(e)}",
|
95
|
+
code=-32603
|
96
|
+
)
|
97
|
+
|
98
|
+
async def token_create(self, token_type: str, token_data: Dict[str, Any]) -> Union[SuccessResult, ErrorResult]:
|
99
|
+
"""
|
100
|
+
Create a new token.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
token_type: Type of token (jwt/api)
|
104
|
+
token_data: Token data dictionary containing:
|
105
|
+
- roles: List of roles for the token
|
106
|
+
- expires_in: Token expiration time in seconds (optional)
|
107
|
+
- description: Token description (optional)
|
108
|
+
- user_id: User ID associated with token (optional)
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
CommandResult with created token information
|
112
|
+
"""
|
113
|
+
try:
|
114
|
+
if token_type not in ["jwt", "api"]:
|
115
|
+
return ErrorResult(
|
116
|
+
message=f"Unsupported token type: {token_type}",
|
117
|
+
code=-32602
|
118
|
+
)
|
119
|
+
|
120
|
+
if token_type == "jwt":
|
121
|
+
return await self._create_jwt_token(token_data)
|
122
|
+
else:
|
123
|
+
return await self._create_api_token(token_data)
|
124
|
+
|
125
|
+
except Exception as e:
|
126
|
+
self.logger.error(f"Token creation error: {e}")
|
127
|
+
return ErrorResult(
|
128
|
+
message=f"Token creation failed: {str(e)}",
|
129
|
+
code=-32603
|
130
|
+
)
|
131
|
+
|
132
|
+
async def token_validate(self, token: str, token_type: str = "auto") -> Union[SuccessResult, ErrorResult]:
|
133
|
+
"""
|
134
|
+
Validate a token.
|
135
|
+
|
136
|
+
Args:
|
137
|
+
token: Token string to validate
|
138
|
+
token_type: Type of token (auto/jwt/api)
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
CommandResult with validation status and token information
|
142
|
+
"""
|
143
|
+
try:
|
144
|
+
if not token:
|
145
|
+
return ErrorResult(
|
146
|
+
message="Token not provided",
|
147
|
+
code=-32602
|
148
|
+
)
|
149
|
+
|
150
|
+
# Auto-detect token type if not specified
|
151
|
+
if token_type == "auto":
|
152
|
+
token_type = "jwt" if self._is_jwt_token(token) else "api"
|
153
|
+
|
154
|
+
# Use AuthValidator for validation
|
155
|
+
result = self.auth_validator.validate_token(token, token_type)
|
156
|
+
|
157
|
+
if result.is_valid:
|
158
|
+
return SuccessResult(
|
159
|
+
data={
|
160
|
+
"valid": True,
|
161
|
+
"token_type": token_type,
|
162
|
+
"roles": result.roles,
|
163
|
+
"expires_at": self._get_token_expiry(token, token_type)
|
164
|
+
}
|
165
|
+
)
|
166
|
+
else:
|
167
|
+
error_data = result.to_json_rpc_error()
|
168
|
+
return ErrorResult(
|
169
|
+
message=error_data["message"],
|
170
|
+
code=error_data["code"]
|
171
|
+
)
|
172
|
+
|
173
|
+
except Exception as e:
|
174
|
+
self.logger.error(f"Token validation error: {e}")
|
175
|
+
return ErrorResult(
|
176
|
+
message=f"Token validation failed: {str(e)}",
|
177
|
+
code=-32603
|
178
|
+
)
|
179
|
+
|
180
|
+
async def token_revoke(self, token: str) -> Union[SuccessResult, ErrorResult]:
|
181
|
+
"""
|
182
|
+
Revoke a token.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
token: Token string to revoke
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
CommandResult with revocation status
|
189
|
+
"""
|
190
|
+
try:
|
191
|
+
if not token:
|
192
|
+
return ErrorResult(
|
193
|
+
message="Token not provided",
|
194
|
+
code=-32602
|
195
|
+
)
|
196
|
+
|
197
|
+
# Load current tokens
|
198
|
+
tokens = self._load_tokens()
|
199
|
+
|
200
|
+
# Check if token exists
|
201
|
+
if token not in tokens:
|
202
|
+
return ErrorResult(
|
203
|
+
message="Token not found",
|
204
|
+
code=-32011
|
205
|
+
)
|
206
|
+
|
207
|
+
# Mark token as revoked
|
208
|
+
tokens[token]["active"] = False
|
209
|
+
tokens[token]["revoked_at"] = time.time()
|
210
|
+
tokens[token]["revoked_by"] = "system"
|
211
|
+
|
212
|
+
# Save updated tokens
|
213
|
+
self._save_tokens(tokens)
|
214
|
+
|
215
|
+
return SuccessResult(
|
216
|
+
data={
|
217
|
+
"revoked": True,
|
218
|
+
"token": token,
|
219
|
+
"revoked_at": datetime.now().isoformat()
|
220
|
+
}
|
221
|
+
)
|
222
|
+
|
223
|
+
except Exception as e:
|
224
|
+
self.logger.error(f"Token revocation error: {e}")
|
225
|
+
return ErrorResult(
|
226
|
+
message=f"Token revocation failed: {str(e)}",
|
227
|
+
code=-32603
|
228
|
+
)
|
229
|
+
|
230
|
+
async def token_list(self, active_only: bool = True) -> Union[SuccessResult, ErrorResult]:
|
231
|
+
"""
|
232
|
+
List all tokens.
|
233
|
+
|
234
|
+
Args:
|
235
|
+
active_only: If True, return only active tokens
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
CommandResult with list of tokens
|
239
|
+
"""
|
240
|
+
try:
|
241
|
+
# Load tokens
|
242
|
+
tokens = self._load_tokens()
|
243
|
+
|
244
|
+
# Filter tokens if requested
|
245
|
+
if active_only:
|
246
|
+
tokens = {k: v for k, v in tokens.items() if v.get("active", True)}
|
247
|
+
|
248
|
+
# Prepare token list (without sensitive data)
|
249
|
+
token_list = []
|
250
|
+
for token_id, token_data in tokens.items():
|
251
|
+
token_info = {
|
252
|
+
"id": token_id,
|
253
|
+
"type": token_data.get("type", "api"),
|
254
|
+
"roles": token_data.get("roles", []),
|
255
|
+
"active": token_data.get("active", True),
|
256
|
+
"created_at": token_data.get("created_at"),
|
257
|
+
"expires_at": token_data.get("expires_at"),
|
258
|
+
"description": token_data.get("description", ""),
|
259
|
+
"user_id": token_data.get("user_id")
|
260
|
+
}
|
261
|
+
|
262
|
+
if "revoked_at" in token_data:
|
263
|
+
token_info["revoked_at"] = token_data["revoked_at"]
|
264
|
+
|
265
|
+
token_list.append(token_info)
|
266
|
+
|
267
|
+
return SuccessResult(
|
268
|
+
data={
|
269
|
+
"tokens": token_list,
|
270
|
+
"count": len(token_list),
|
271
|
+
"active_only": active_only
|
272
|
+
}
|
273
|
+
)
|
274
|
+
|
275
|
+
except Exception as e:
|
276
|
+
self.logger.error(f"Token listing error: {e}")
|
277
|
+
return ErrorResult(
|
278
|
+
message=f"Token listing failed: {str(e)}",
|
279
|
+
code=-32603
|
280
|
+
)
|
281
|
+
|
282
|
+
async def token_refresh(self, token: str) -> Union[SuccessResult, ErrorResult]:
|
283
|
+
"""
|
284
|
+
Refresh a token.
|
285
|
+
|
286
|
+
Args:
|
287
|
+
token: Token string to refresh
|
288
|
+
|
289
|
+
Returns:
|
290
|
+
CommandResult with refreshed token information
|
291
|
+
"""
|
292
|
+
try:
|
293
|
+
if not token:
|
294
|
+
return ErrorResult(
|
295
|
+
message="Token not provided",
|
296
|
+
code=-32602
|
297
|
+
)
|
298
|
+
|
299
|
+
# Load current tokens
|
300
|
+
tokens = self._load_tokens()
|
301
|
+
|
302
|
+
# Check if token exists and is active
|
303
|
+
if token not in tokens:
|
304
|
+
return ErrorResult(
|
305
|
+
message="Token not found",
|
306
|
+
code=-32011
|
307
|
+
)
|
308
|
+
|
309
|
+
token_data = tokens[token]
|
310
|
+
if not token_data.get("active", True):
|
311
|
+
return ErrorResult(
|
312
|
+
message="Token is revoked",
|
313
|
+
code=-32011
|
314
|
+
)
|
315
|
+
|
316
|
+
# Check if token has expired
|
317
|
+
if "expires_at" in token_data and time.time() > token_data["expires_at"]:
|
318
|
+
return ErrorResult(
|
319
|
+
message="Token has expired",
|
320
|
+
code=-32010
|
321
|
+
)
|
322
|
+
|
323
|
+
# Create new token with same data
|
324
|
+
new_token_data = {
|
325
|
+
"type": token_data.get("type", "api"),
|
326
|
+
"roles": token_data.get("roles", []),
|
327
|
+
"active": True,
|
328
|
+
"created_at": time.time(),
|
329
|
+
"expires_at": time.time() + self.token_expiry,
|
330
|
+
"description": token_data.get("description", ""),
|
331
|
+
"user_id": token_data.get("user_id"),
|
332
|
+
"refreshed_from": token
|
333
|
+
}
|
334
|
+
|
335
|
+
# Generate new token ID
|
336
|
+
new_token_id = str(uuid.uuid4())
|
337
|
+
tokens[new_token_id] = new_token_data
|
338
|
+
|
339
|
+
# Revoke old token
|
340
|
+
tokens[token]["active"] = False
|
341
|
+
tokens[token]["refreshed_to"] = new_token_id
|
342
|
+
tokens[token]["refreshed_at"] = time.time()
|
343
|
+
|
344
|
+
# Save updated tokens
|
345
|
+
self._save_tokens(tokens)
|
346
|
+
|
347
|
+
return SuccessResult(
|
348
|
+
data={
|
349
|
+
"refreshed": True,
|
350
|
+
"old_token": token,
|
351
|
+
"new_token": new_token_id,
|
352
|
+
"expires_at": new_token_data["expires_at"]
|
353
|
+
}
|
354
|
+
)
|
355
|
+
|
356
|
+
except Exception as e:
|
357
|
+
self.logger.error(f"Token refresh error: {e}")
|
358
|
+
return ErrorResult(
|
359
|
+
message=f"Token refresh failed: {str(e)}",
|
360
|
+
code=-32603
|
361
|
+
)
|
362
|
+
|
363
|
+
async def _create_jwt_token(self, token_data: Dict[str, Any]) -> Union[SuccessResult, ErrorResult]:
|
364
|
+
"""
|
365
|
+
Create JWT token.
|
366
|
+
|
367
|
+
Args:
|
368
|
+
token_data: Token data dictionary
|
369
|
+
|
370
|
+
Returns:
|
371
|
+
CommandResult with JWT token
|
372
|
+
"""
|
373
|
+
try:
|
374
|
+
# This is a placeholder for JWT creation
|
375
|
+
# In a real implementation, you would use a JWT library like PyJWT
|
376
|
+
|
377
|
+
# For now, create a simple token structure
|
378
|
+
token_id = str(uuid.uuid4())
|
379
|
+
expires_in = token_data.get("expires_in", self.token_expiry)
|
380
|
+
|
381
|
+
jwt_token_data = {
|
382
|
+
"jti": token_id,
|
383
|
+
"sub": token_data.get("user_id", "system"),
|
384
|
+
"roles": token_data.get("roles", []),
|
385
|
+
"exp": time.time() + expires_in,
|
386
|
+
"iat": time.time(),
|
387
|
+
"iss": "mcp_proxy_adapter"
|
388
|
+
}
|
389
|
+
|
390
|
+
# In a real implementation, you would encode this as JWT
|
391
|
+
# For now, return the token data
|
392
|
+
return SuccessResult(
|
393
|
+
data={
|
394
|
+
"token": token_id,
|
395
|
+
"token_type": "jwt",
|
396
|
+
"expires_at": jwt_token_data["exp"],
|
397
|
+
"roles": jwt_token_data["roles"],
|
398
|
+
"user_id": jwt_token_data["sub"]
|
399
|
+
}
|
400
|
+
)
|
401
|
+
|
402
|
+
except Exception as e:
|
403
|
+
self.logger.error(f"JWT token creation error: {e}")
|
404
|
+
return ErrorResult(
|
405
|
+
message=f"JWT token creation failed: {str(e)}",
|
406
|
+
code=-32603
|
407
|
+
)
|
408
|
+
|
409
|
+
async def _create_api_token(self, token_data: Dict[str, Any]) -> Union[SuccessResult, ErrorResult]:
|
410
|
+
"""
|
411
|
+
Create API token.
|
412
|
+
|
413
|
+
Args:
|
414
|
+
token_data: Token data dictionary
|
415
|
+
|
416
|
+
Returns:
|
417
|
+
CommandResult with API token
|
418
|
+
"""
|
419
|
+
try:
|
420
|
+
# Generate token ID
|
421
|
+
token_id = str(uuid.uuid4())
|
422
|
+
expires_in = token_data.get("expires_in", self.token_expiry)
|
423
|
+
|
424
|
+
# Create token data
|
425
|
+
api_token_data = {
|
426
|
+
"type": "api",
|
427
|
+
"roles": token_data.get("roles", []),
|
428
|
+
"active": True,
|
429
|
+
"created_at": time.time(),
|
430
|
+
"expires_at": time.time() + expires_in,
|
431
|
+
"description": token_data.get("description", ""),
|
432
|
+
"user_id": token_data.get("user_id")
|
433
|
+
}
|
434
|
+
|
435
|
+
# Load current tokens and add new token
|
436
|
+
tokens = self._load_tokens()
|
437
|
+
tokens[token_id] = api_token_data
|
438
|
+
self._save_tokens(tokens)
|
439
|
+
|
440
|
+
return SuccessResult(
|
441
|
+
data={
|
442
|
+
"token": token_id,
|
443
|
+
"token_type": "api",
|
444
|
+
"expires_at": api_token_data["expires_at"],
|
445
|
+
"roles": api_token_data["roles"],
|
446
|
+
"user_id": api_token_data["user_id"]
|
447
|
+
}
|
448
|
+
)
|
449
|
+
|
450
|
+
except Exception as e:
|
451
|
+
self.logger.error(f"API token creation error: {e}")
|
452
|
+
return ErrorResult(
|
453
|
+
message=f"API token creation failed: {str(e)}",
|
454
|
+
code=-32603
|
455
|
+
)
|
456
|
+
|
457
|
+
def _is_jwt_token(self, token: str) -> bool:
|
458
|
+
"""
|
459
|
+
Check if token is JWT format.
|
460
|
+
|
461
|
+
Args:
|
462
|
+
token: Token string
|
463
|
+
|
464
|
+
Returns:
|
465
|
+
True if token appears to be JWT, False otherwise
|
466
|
+
"""
|
467
|
+
parts = token.split('.')
|
468
|
+
return len(parts) == 3
|
469
|
+
|
470
|
+
def _get_token_expiry(self, token: str, token_type: str) -> Optional[float]:
|
471
|
+
"""
|
472
|
+
Get token expiry time.
|
473
|
+
|
474
|
+
Args:
|
475
|
+
token: Token string
|
476
|
+
token_type: Type of token
|
477
|
+
|
478
|
+
Returns:
|
479
|
+
Expiry timestamp or None
|
480
|
+
"""
|
481
|
+
try:
|
482
|
+
if token_type == "api":
|
483
|
+
tokens = self._load_tokens()
|
484
|
+
if token in tokens:
|
485
|
+
return tokens[token].get("expires_at")
|
486
|
+
|
487
|
+
# For JWT tokens, this would require decoding
|
488
|
+
# For now, return None
|
489
|
+
return None
|
490
|
+
|
491
|
+
except Exception as e:
|
492
|
+
self.logger.error(f"Failed to get token expiry: {e}")
|
493
|
+
return None
|
494
|
+
|
495
|
+
def _load_tokens(self) -> Dict[str, Any]:
|
496
|
+
"""
|
497
|
+
Load tokens from file.
|
498
|
+
|
499
|
+
Returns:
|
500
|
+
Dictionary of tokens
|
501
|
+
"""
|
502
|
+
try:
|
503
|
+
if not self.tokens_file or not Path(self.tokens_file).exists():
|
504
|
+
return {}
|
505
|
+
|
506
|
+
with open(self.tokens_file, 'r', encoding='utf-8') as f:
|
507
|
+
return json.load(f)
|
508
|
+
|
509
|
+
except Exception as e:
|
510
|
+
self.logger.error(f"Failed to load tokens: {e}")
|
511
|
+
return {}
|
512
|
+
|
513
|
+
def _save_tokens(self, tokens: Dict[str, Any]) -> None:
|
514
|
+
"""
|
515
|
+
Save tokens to file.
|
516
|
+
|
517
|
+
Args:
|
518
|
+
tokens: Dictionary of tokens to save
|
519
|
+
"""
|
520
|
+
try:
|
521
|
+
# Ensure directory exists
|
522
|
+
Path(self.tokens_file).parent.mkdir(parents=True, exist_ok=True)
|
523
|
+
|
524
|
+
with open(self.tokens_file, 'w', encoding='utf-8') as f:
|
525
|
+
json.dump(tokens, f, indent=2, ensure_ascii=False)
|
526
|
+
|
527
|
+
except Exception as e:
|
528
|
+
self.logger.error(f"Failed to save tokens: {e}")
|
529
|
+
raise
|
@@ -0,0 +1,144 @@
|
|
1
|
+
"""
|
2
|
+
Transport Management Command
|
3
|
+
|
4
|
+
This command provides transport management functionality for the MCP Proxy Adapter.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Any, ClassVar
|
8
|
+
from mcp_proxy_adapter.commands.base import Command
|
9
|
+
from mcp_proxy_adapter.commands.result import SuccessResult, ErrorResult
|
10
|
+
from mcp_proxy_adapter.core.transport_manager import transport_manager
|
11
|
+
from mcp_proxy_adapter.core.logging import logger
|
12
|
+
|
13
|
+
|
14
|
+
class TransportManagementResult(SuccessResult):
|
15
|
+
"""Result class for transport management operations."""
|
16
|
+
|
17
|
+
def __init__(self, transport_info: Dict[str, Any], message: str = "Transport management operation completed"):
|
18
|
+
"""
|
19
|
+
Initialize transport management result.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
transport_info: Transport information
|
23
|
+
message: Success message
|
24
|
+
"""
|
25
|
+
super().__init__(data={"transport_info": transport_info}, message=message)
|
26
|
+
|
27
|
+
|
28
|
+
class TransportManagementCommand(Command):
|
29
|
+
"""
|
30
|
+
Transport management command.
|
31
|
+
|
32
|
+
This command provides functionality to manage and query transport configurations.
|
33
|
+
"""
|
34
|
+
|
35
|
+
name = "transport_management"
|
36
|
+
descr = "Manage and query transport configurations (HTTP, HTTPS, MTLS)"
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def get_schema(cls) -> Dict[str, Any]:
|
40
|
+
"""
|
41
|
+
Get command schema.
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
Command schema dictionary
|
45
|
+
"""
|
46
|
+
return {
|
47
|
+
"type": "object",
|
48
|
+
"properties": {
|
49
|
+
"action": {
|
50
|
+
"type": "string",
|
51
|
+
"enum": ["get_info", "validate", "reload"],
|
52
|
+
"description": "Action to perform"
|
53
|
+
}
|
54
|
+
},
|
55
|
+
"required": ["action"]
|
56
|
+
}
|
57
|
+
|
58
|
+
async def execute(self, **params) -> TransportManagementResult:
|
59
|
+
"""
|
60
|
+
Execute transport management command.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
params: Command parameters
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
Transport management result
|
67
|
+
"""
|
68
|
+
try:
|
69
|
+
action = params.get("action", "get_info")
|
70
|
+
|
71
|
+
if action == "get_info":
|
72
|
+
return await self._get_transport_info()
|
73
|
+
elif action == "validate":
|
74
|
+
return await self._validate_transport()
|
75
|
+
elif action == "reload":
|
76
|
+
return await self._reload_transport()
|
77
|
+
else:
|
78
|
+
return TransportManagementResult(
|
79
|
+
transport_info={"error": f"Unknown action: {action}"},
|
80
|
+
message=f"Unknown action: {action}"
|
81
|
+
)
|
82
|
+
|
83
|
+
except Exception as e:
|
84
|
+
logger.error(f"Transport management command error: {e}")
|
85
|
+
return TransportManagementResult(
|
86
|
+
transport_info={"error": str(e)},
|
87
|
+
message=f"Transport management failed: {e}"
|
88
|
+
)
|
89
|
+
|
90
|
+
async def _get_transport_info(self) -> TransportManagementResult:
|
91
|
+
"""
|
92
|
+
Get transport information.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
Transport information result
|
96
|
+
"""
|
97
|
+
transport_info = transport_manager.get_transport_info()
|
98
|
+
|
99
|
+
return TransportManagementResult(
|
100
|
+
transport_info=transport_info,
|
101
|
+
message="Transport information retrieved successfully"
|
102
|
+
)
|
103
|
+
|
104
|
+
async def _validate_transport(self) -> TransportManagementResult:
|
105
|
+
"""
|
106
|
+
Validate transport configuration.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
Validation result
|
110
|
+
"""
|
111
|
+
is_valid = transport_manager.validate_config()
|
112
|
+
|
113
|
+
transport_info = transport_manager.get_transport_info()
|
114
|
+
transport_info["validation"] = {
|
115
|
+
"is_valid": is_valid,
|
116
|
+
"timestamp": "2025-08-15T12:00:00Z"
|
117
|
+
}
|
118
|
+
|
119
|
+
message = "Transport configuration validated successfully" if is_valid else "Transport configuration validation failed"
|
120
|
+
|
121
|
+
return TransportManagementResult(
|
122
|
+
transport_info=transport_info,
|
123
|
+
message=message
|
124
|
+
)
|
125
|
+
|
126
|
+
async def _reload_transport(self) -> TransportManagementResult:
|
127
|
+
"""
|
128
|
+
Reload transport configuration.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
Reload result
|
132
|
+
"""
|
133
|
+
# Note: In a real implementation, this would reload the config
|
134
|
+
# For now, we just return current info
|
135
|
+
transport_info = transport_manager.get_transport_info()
|
136
|
+
transport_info["reload"] = {
|
137
|
+
"status": "completed",
|
138
|
+
"timestamp": "2025-08-15T12:00:00Z"
|
139
|
+
}
|
140
|
+
|
141
|
+
return TransportManagementResult(
|
142
|
+
transport_info=transport_info,
|
143
|
+
message="Transport configuration reload completed"
|
144
|
+
)
|