mcp-proxy-adapter 4.1.1__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.
Files changed (101) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +138 -11
  3. mcp_proxy_adapter/api/handlers.py +16 -1
  4. mcp_proxy_adapter/api/middleware/__init__.py +30 -29
  5. mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +219 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
  10. mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
  11. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  12. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
  13. mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
  14. mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
  15. mcp_proxy_adapter/api/middleware/security.py +376 -0
  16. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
  17. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  18. mcp_proxy_adapter/commands/__init__.py +13 -4
  19. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  20. mcp_proxy_adapter/commands/base.py +61 -30
  21. mcp_proxy_adapter/commands/builtin_commands.py +89 -0
  22. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  23. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  24. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  25. mcp_proxy_adapter/commands/command_registry.py +703 -354
  26. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  27. mcp_proxy_adapter/commands/health_command.py +7 -0
  28. mcp_proxy_adapter/commands/hooks.py +200 -167
  29. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  30. mcp_proxy_adapter/commands/load_command.py +176 -0
  31. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  32. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  33. mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
  34. mcp_proxy_adapter/commands/reload_command.py +48 -50
  35. mcp_proxy_adapter/commands/result.py +1 -0
  36. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  37. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  38. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  39. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  40. mcp_proxy_adapter/commands/unload_command.py +158 -0
  41. mcp_proxy_adapter/config.py +99 -2
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/config_converter.py +405 -0
  45. mcp_proxy_adapter/core/config_validator.py +218 -0
  46. mcp_proxy_adapter/core/logging.py +11 -0
  47. mcp_proxy_adapter/core/protocol_manager.py +226 -0
  48. mcp_proxy_adapter/core/proxy_registration.py +270 -0
  49. mcp_proxy_adapter/core/role_utils.py +426 -0
  50. mcp_proxy_adapter/core/security_adapter.py +373 -0
  51. mcp_proxy_adapter/core/security_factory.py +239 -0
  52. mcp_proxy_adapter/core/settings.py +1 -0
  53. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  54. mcp_proxy_adapter/core/transport_manager.py +292 -0
  55. mcp_proxy_adapter/custom_openapi.py +22 -11
  56. mcp_proxy_adapter/examples/basic_server/config.json +58 -23
  57. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
  58. mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
  59. mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
  60. mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
  61. mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
  62. mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
  63. mcp_proxy_adapter/examples/basic_server/server.py +12 -1
  64. mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
  65. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
  66. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
  67. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
  68. mcp_proxy_adapter/examples/custom_commands/config.json +101 -18
  69. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
  70. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
  71. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
  72. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
  73. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
  74. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
  75. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
  76. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
  77. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
  78. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
  79. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
  80. mcp_proxy_adapter/examples/custom_commands/server.py +92 -68
  81. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
  82. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
  83. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
  84. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
  85. mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
  86. mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
  87. mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
  88. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
  89. mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
  90. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
  91. mcp_proxy_adapter/main.py +175 -0
  92. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  93. mcp_proxy_adapter/tests/unit/test_config.py +53 -0
  94. mcp_proxy_adapter/version.py +1 -1
  95. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
  96. mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
  97. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  98. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  99. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
  100. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
  101. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,697 @@
1
+ """
2
+ Roles Management Command
3
+
4
+ This module provides commands for managing roles in the role-based access control system.
5
+ Includes commands for listing, creating, updating, deleting, and validating roles.
6
+
7
+ Author: MCP Proxy Adapter Team
8
+ Version: 1.0.0
9
+ """
10
+
11
+ import json
12
+ import logging
13
+ from typing import Dict, List, Optional, Any
14
+ from pathlib import Path
15
+
16
+ from .base import Command
17
+ from .result import CommandResult, SuccessResult, ErrorResult
18
+ from ..core.role_utils import RoleUtils
19
+ from ..core.errors import ValidationError, NotFoundError, InternalError
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class RolesListResult(SuccessResult):
25
+ """
26
+ Result for roles list command.
27
+ """
28
+
29
+ def __init__(self, roles: List[Dict[str, Any]], total_count: int):
30
+ """
31
+ Initialize roles list result.
32
+
33
+ Args:
34
+ roles: List of role configurations
35
+ total_count: Total number of roles
36
+ """
37
+ super().__init__()
38
+ self.success = True
39
+ self.roles = roles
40
+ self.total_count = total_count
41
+
42
+ def to_dict(self) -> Dict[str, Any]:
43
+ """
44
+ Convert to dictionary format.
45
+
46
+ Returns:
47
+ Dictionary representation
48
+ """
49
+ return {
50
+ "success": self.success,
51
+ "roles": self.roles,
52
+ "total_count": self.total_count
53
+ }
54
+
55
+ @classmethod
56
+ def get_schema(cls) -> Dict[str, Any]:
57
+ """
58
+ Get JSON schema for result.
59
+
60
+ Returns:
61
+ JSON schema
62
+ """
63
+ return {
64
+ "type": "object",
65
+ "properties": {
66
+ "success": {"type": "boolean"},
67
+ "roles": {
68
+ "type": "array",
69
+ "items": {
70
+ "type": "object",
71
+ "properties": {
72
+ "name": {"type": "string"},
73
+ "description": {"type": "string"},
74
+ "allowed_servers": {"type": "array", "items": {"type": "string"}},
75
+ "allowed_clients": {"type": "array", "items": {"type": "string"}},
76
+ "permissions": {"type": "array", "items": {"type": "string"}},
77
+ "priority": {"type": "integer"}
78
+ }
79
+ }
80
+ },
81
+ "total_count": {"type": "integer"}
82
+ }
83
+ }
84
+
85
+
86
+ class RolesCreateResult(SuccessResult):
87
+ """
88
+ Result for roles create command.
89
+ """
90
+
91
+ def __init__(self, role_name: str, role_config: Dict[str, Any]):
92
+ """
93
+ Initialize roles create result.
94
+
95
+ Args:
96
+ role_name: Name of created role
97
+ role_config: Role configuration
98
+ """
99
+ super().__init__()
100
+ self.success = True
101
+ self.role_name = role_name
102
+ self.role_config = role_config
103
+
104
+ def to_dict(self) -> Dict[str, Any]:
105
+ """
106
+ Convert to dictionary format.
107
+
108
+ Returns:
109
+ Dictionary representation
110
+ """
111
+ return {
112
+ "success": self.success,
113
+ "role_name": self.role_name,
114
+ "role_config": self.role_config
115
+ }
116
+
117
+ @classmethod
118
+ def get_schema(cls) -> Dict[str, Any]:
119
+ """
120
+ Get JSON schema for result.
121
+
122
+ Returns:
123
+ JSON schema
124
+ """
125
+ return {
126
+ "type": "object",
127
+ "properties": {
128
+ "success": {"type": "boolean"},
129
+ "role_name": {"type": "string"},
130
+ "role_config": {"type": "object"}
131
+ }
132
+ }
133
+
134
+
135
+ class RolesUpdateResult(SuccessResult):
136
+ """
137
+ Result for roles update command.
138
+ """
139
+
140
+ def __init__(self, role_name: str, role_config: Dict[str, Any]):
141
+ """
142
+ Initialize roles update result.
143
+
144
+ Args:
145
+ role_name: Name of updated role
146
+ role_config: Updated role configuration
147
+ """
148
+ super().__init__()
149
+ self.success = True
150
+ self.role_name = role_name
151
+ self.role_config = role_config
152
+
153
+ def to_dict(self) -> Dict[str, Any]:
154
+ """
155
+ Convert to dictionary format.
156
+
157
+ Returns:
158
+ Dictionary representation
159
+ """
160
+ return {
161
+ "success": self.success,
162
+ "role_name": self.role_name,
163
+ "role_config": self.role_config
164
+ }
165
+
166
+ @classmethod
167
+ def get_schema(cls) -> Dict[str, Any]:
168
+ """
169
+ Get JSON schema for result.
170
+
171
+ Returns:
172
+ JSON schema
173
+ """
174
+ return {
175
+ "type": "object",
176
+ "properties": {
177
+ "success": {"type": "boolean"},
178
+ "role_name": {"type": "string"},
179
+ "role_config": {"type": "object"}
180
+ }
181
+ }
182
+
183
+
184
+ class RolesDeleteResult(SuccessResult):
185
+ """
186
+ Result for roles delete command.
187
+ """
188
+
189
+ def __init__(self, role_name: str):
190
+ """
191
+ Initialize roles delete result.
192
+
193
+ Args:
194
+ role_name: Name of deleted role
195
+ """
196
+ super().__init__()
197
+ self.success = True
198
+ self.role_name = role_name
199
+
200
+ def to_dict(self) -> Dict[str, Any]:
201
+ """
202
+ Convert to dictionary format.
203
+
204
+ Returns:
205
+ Dictionary representation
206
+ """
207
+ return {
208
+ "success": self.success,
209
+ "role_name": self.role_name
210
+ }
211
+
212
+ @classmethod
213
+ def get_schema(cls) -> Dict[str, Any]:
214
+ """
215
+ Get JSON schema for result.
216
+
217
+ Returns:
218
+ JSON schema
219
+ """
220
+ return {
221
+ "type": "object",
222
+ "properties": {
223
+ "success": {"type": "boolean"},
224
+ "role_name": {"type": "string"}
225
+ }
226
+ }
227
+
228
+
229
+ class RolesValidateResult(SuccessResult):
230
+ """
231
+ Result for roles validate command.
232
+ """
233
+
234
+ def __init__(self, role_name: str, is_valid: bool, validation_errors: List[str]):
235
+ """
236
+ Initialize roles validate result.
237
+
238
+ Args:
239
+ role_name: Name of validated role
240
+ is_valid: Whether role is valid
241
+ validation_errors: List of validation errors
242
+ """
243
+ super().__init__()
244
+ self.success = True
245
+ self.role_name = role_name
246
+ self.is_valid = is_valid
247
+ self.validation_errors = validation_errors
248
+
249
+ def to_dict(self) -> Dict[str, Any]:
250
+ """
251
+ Convert to dictionary format.
252
+
253
+ Returns:
254
+ Dictionary representation
255
+ """
256
+ return {
257
+ "success": self.success,
258
+ "role_name": self.role_name,
259
+ "is_valid": self.is_valid,
260
+ "validation_errors": self.validation_errors
261
+ }
262
+
263
+ @classmethod
264
+ def get_schema(cls) -> Dict[str, Any]:
265
+ """
266
+ Get JSON schema for result.
267
+
268
+ Returns:
269
+ JSON schema
270
+ """
271
+ return {
272
+ "type": "object",
273
+ "properties": {
274
+ "success": {"type": "boolean"},
275
+ "role_name": {"type": "string"},
276
+ "is_valid": {"type": "boolean"},
277
+ "validation_errors": {"type": "array", "items": {"type": "string"}}
278
+ }
279
+ }
280
+
281
+
282
+ class RolesManagementCommand(Command):
283
+ """
284
+ Command for managing roles in the role-based access control system.
285
+ """
286
+
287
+ name = "roles_management"
288
+ version = "1.0.0"
289
+ descr = "Manage roles in the role-based access control system"
290
+ category = "security"
291
+ author = "MCP Proxy Adapter Team"
292
+ email = "team@mcp-proxy-adapter.com"
293
+ source_url = "https://github.com/mcp-proxy-adapter"
294
+
295
+ def __init__(self, roles_config_path: str = "schemas/roles_schema.json"):
296
+ """
297
+ Initialize roles management command.
298
+
299
+ Args:
300
+ roles_config_path: Path to roles configuration file
301
+ """
302
+ self.roles_config_path = roles_config_path
303
+ self.role_utils = RoleUtils()
304
+ self.roles_config = self._load_roles_config()
305
+
306
+ def _load_roles_config(self) -> Dict[str, Any]:
307
+ """
308
+ Load roles configuration from file.
309
+
310
+ Returns:
311
+ Roles configuration dictionary
312
+ """
313
+ try:
314
+ config_path = Path(self.roles_config_path)
315
+ if not config_path.exists():
316
+ logger.warning(f"Roles config file not found: {self.roles_config_path}")
317
+ return {"roles": {}, "server_roles": {}, "role_hierarchy": {}}
318
+
319
+ with open(config_path, 'r', encoding='utf-8') as f:
320
+ config = json.load(f)
321
+
322
+ return config
323
+
324
+ except Exception as e:
325
+ logger.error(f"Failed to load roles configuration: {e}")
326
+ return {"roles": {}, "server_roles": {}, "role_hierarchy": {}}
327
+
328
+ def _save_roles_config(self) -> None:
329
+ """
330
+ Save roles configuration to file.
331
+ """
332
+ try:
333
+ config_path = Path(self.roles_config_path)
334
+ config_path.parent.mkdir(parents=True, exist_ok=True)
335
+
336
+ with open(config_path, 'w', encoding='utf-8') as f:
337
+ json.dump(self.roles_config, f, indent=2, ensure_ascii=False)
338
+
339
+ logger.info(f"Roles configuration saved to {self.roles_config_path}")
340
+
341
+ except Exception as e:
342
+ logger.error(f"Failed to save roles configuration: {e}")
343
+ raise InternalError(f"Failed to save roles configuration: {e}")
344
+
345
+ async def execute(self, **kwargs) -> CommandResult:
346
+ """
347
+ Execute roles management command.
348
+
349
+ Args:
350
+ **kwargs: Command parameters including 'action' and role-specific parameters
351
+
352
+ Returns:
353
+ Command result
354
+ """
355
+ try:
356
+ action = kwargs.get("action")
357
+
358
+ if action == "list":
359
+ return await self.roles_list(**kwargs)
360
+ elif action == "create":
361
+ return await self.roles_create(**kwargs)
362
+ elif action == "update":
363
+ return await self.roles_update(**kwargs)
364
+ elif action == "delete":
365
+ return await self.roles_delete(**kwargs)
366
+ elif action == "validate":
367
+ return await self.roles_validate(**kwargs)
368
+ else:
369
+ raise ValidationError(f"Invalid action: {action}. "
370
+ f"Valid actions: list, create, update, delete, validate")
371
+
372
+ except Exception as e:
373
+ logger.error(f"Roles management command failed: {e}")
374
+ return ErrorResult(str(e))
375
+
376
+ async def roles_list(self, **kwargs) -> RolesListResult:
377
+ """
378
+ List all roles.
379
+
380
+ Args:
381
+ **kwargs: Additional parameters (filter, limit, offset)
382
+
383
+ Returns:
384
+ Roles list result
385
+ """
386
+ roles = self.roles_config.get("roles", {})
387
+
388
+ # Apply filters if specified
389
+ filter_name = kwargs.get("filter")
390
+ if filter_name:
391
+ roles = {name: config for name, config in roles.items()
392
+ if filter_name.lower() in name.lower()}
393
+
394
+ # Convert to list format
395
+ roles_list = []
396
+ for name, config in roles.items():
397
+ role_info = {
398
+ "name": name,
399
+ "description": config.get("description", ""),
400
+ "allowed_servers": config.get("allowed_servers", []),
401
+ "allowed_clients": config.get("allowed_clients", []),
402
+ "permissions": config.get("permissions", []),
403
+ "priority": config.get("priority", 0)
404
+ }
405
+ roles_list.append(role_info)
406
+
407
+ # Apply pagination
408
+ limit = kwargs.get("limit")
409
+ offset = kwargs.get("offset", 0)
410
+
411
+ if limit:
412
+ roles_list = roles_list[offset:offset + limit]
413
+ elif offset:
414
+ roles_list = roles_list[offset:]
415
+
416
+ return RolesListResult(roles_list, len(roles))
417
+
418
+ async def roles_create(self, **kwargs) -> RolesCreateResult:
419
+ """
420
+ Create a new role.
421
+
422
+ Args:
423
+ **kwargs: Role parameters (role_name, description, allowed_servers, etc.)
424
+
425
+ Returns:
426
+ Roles create result
427
+ """
428
+ role_name = kwargs.get("role_name")
429
+ if not role_name:
430
+ raise ValidationError("role_name is required")
431
+
432
+ # Validate role name
433
+ if not self.role_utils.validate_single_role(role_name):
434
+ raise ValidationError(f"Invalid role name: {role_name}")
435
+
436
+ # Check if role already exists
437
+ if role_name in self.roles_config.get("roles", {}):
438
+ raise ValidationError(f"Role {role_name} already exists")
439
+
440
+ # Create role configuration
441
+ role_config = {
442
+ "description": kwargs.get("description", ""),
443
+ "allowed_servers": kwargs.get("allowed_servers", []),
444
+ "allowed_clients": kwargs.get("allowed_clients", []),
445
+ "permissions": kwargs.get("permissions", []),
446
+ "priority": kwargs.get("priority", 0)
447
+ }
448
+
449
+ # Validate role configuration
450
+ validation_errors = self._validate_role_config(role_config)
451
+ if validation_errors:
452
+ raise ValidationError(f"Invalid role configuration: {', '.join(validation_errors)}")
453
+
454
+ # Add role to configuration
455
+ if "roles" not in self.roles_config:
456
+ self.roles_config["roles"] = {}
457
+
458
+ self.roles_config["roles"][role_name] = role_config
459
+
460
+ # Save configuration
461
+ self._save_roles_config()
462
+
463
+ logger.info(f"Role {role_name} created successfully")
464
+ return RolesCreateResult(role_name, role_config)
465
+
466
+ async def roles_update(self, **kwargs) -> RolesUpdateResult:
467
+ """
468
+ Update an existing role.
469
+
470
+ Args:
471
+ **kwargs: Role parameters (role_name, description, allowed_servers, etc.)
472
+
473
+ Returns:
474
+ Roles update result
475
+ """
476
+ role_name = kwargs.get("role_name")
477
+ if not role_name:
478
+ raise ValidationError("role_name is required")
479
+
480
+ # Check if role exists
481
+ if role_name not in self.roles_config.get("roles", {}):
482
+ raise NotFoundError(f"Role {role_name} not found")
483
+
484
+ # Get existing configuration
485
+ existing_config = self.roles_config["roles"][role_name]
486
+
487
+ # Update configuration with new values
488
+ updated_config = existing_config.copy()
489
+ for key in ["description", "allowed_servers", "allowed_clients", "permissions", "priority"]:
490
+ if key in kwargs:
491
+ updated_config[key] = kwargs[key]
492
+
493
+ # Validate updated configuration
494
+ validation_errors = self._validate_role_config(updated_config)
495
+ if validation_errors:
496
+ raise ValidationError(f"Invalid role configuration: {', '.join(validation_errors)}")
497
+
498
+ # Update role configuration
499
+ self.roles_config["roles"][role_name] = updated_config
500
+
501
+ # Save configuration
502
+ self._save_roles_config()
503
+
504
+ logger.info(f"Role {role_name} updated successfully")
505
+ return RolesUpdateResult(role_name, updated_config)
506
+
507
+ async def roles_delete(self, **kwargs) -> RolesDeleteResult:
508
+ """
509
+ Delete a role.
510
+
511
+ Args:
512
+ **kwargs: Role parameters (role_name)
513
+
514
+ Returns:
515
+ Roles delete result
516
+ """
517
+ role_name = kwargs.get("role_name")
518
+ if not role_name:
519
+ raise ValidationError("role_name is required")
520
+
521
+ # Check if role exists
522
+ if role_name not in self.roles_config.get("roles", {}):
523
+ raise NotFoundError(f"Role {role_name} not found")
524
+
525
+ # Check if role is system role
526
+ if self.role_utils.is_system_role(role_name):
527
+ raise ValidationError(f"Cannot delete system role: {role_name}")
528
+
529
+ # Remove role from configuration
530
+ del self.roles_config["roles"][role_name]
531
+
532
+ # Remove from role hierarchy
533
+ if "role_hierarchy" in self.roles_config:
534
+ if role_name in self.roles_config["role_hierarchy"]:
535
+ del self.roles_config["role_hierarchy"][role_name]
536
+
537
+ # Remove from other roles' hierarchies
538
+ for other_role, hierarchy in self.roles_config["role_hierarchy"].items():
539
+ if role_name in hierarchy:
540
+ hierarchy.remove(role_name)
541
+
542
+ # Save configuration
543
+ self._save_roles_config()
544
+
545
+ logger.info(f"Role {role_name} deleted successfully")
546
+ return RolesDeleteResult(role_name)
547
+
548
+ async def roles_validate(self, **kwargs) -> RolesValidateResult:
549
+ """
550
+ Validate a role configuration.
551
+
552
+ Args:
553
+ **kwargs: Role parameters (role_name or role_config)
554
+
555
+ Returns:
556
+ Roles validate result
557
+ """
558
+ role_name = kwargs.get("role_name")
559
+ role_config = kwargs.get("role_config")
560
+
561
+ if not role_name and not role_config:
562
+ raise ValidationError("Either role_name or role_config is required")
563
+
564
+ validation_errors = []
565
+
566
+ if role_name:
567
+ # Validate existing role
568
+ if role_name not in self.roles_config.get("roles", {}):
569
+ validation_errors.append(f"Role {role_name} not found")
570
+ else:
571
+ role_config = self.roles_config["roles"][role_name]
572
+
573
+ if role_config:
574
+ # Validate role configuration
575
+ config_errors = self._validate_role_config(role_config)
576
+ validation_errors.extend(config_errors)
577
+
578
+ is_valid = len(validation_errors) == 0
579
+
580
+ return RolesValidateResult(role_name or "unknown", is_valid, validation_errors)
581
+
582
+ def _validate_role_config(self, role_config: Dict[str, Any]) -> List[str]:
583
+ """
584
+ Validate role configuration.
585
+
586
+ Args:
587
+ role_config: Role configuration to validate
588
+
589
+ Returns:
590
+ List of validation errors
591
+ """
592
+ errors = []
593
+
594
+ # Validate description
595
+ description = role_config.get("description", "")
596
+ if not isinstance(description, str):
597
+ errors.append("description must be a string")
598
+
599
+ # Validate allowed_servers
600
+ allowed_servers = role_config.get("allowed_servers", [])
601
+ if not isinstance(allowed_servers, list):
602
+ errors.append("allowed_servers must be a list")
603
+ else:
604
+ for server in allowed_servers:
605
+ if not isinstance(server, str):
606
+ errors.append("allowed_servers must contain only strings")
607
+
608
+ # Validate allowed_clients
609
+ allowed_clients = role_config.get("allowed_clients", [])
610
+ if not isinstance(allowed_clients, list):
611
+ errors.append("allowed_clients must be a list")
612
+ else:
613
+ for client in allowed_clients:
614
+ if not isinstance(client, str):
615
+ errors.append("allowed_clients must contain only strings")
616
+
617
+ # Validate permissions
618
+ permissions = role_config.get("permissions", [])
619
+ if not isinstance(permissions, list):
620
+ errors.append("permissions must be a list")
621
+ else:
622
+ for permission in permissions:
623
+ if not isinstance(permission, str):
624
+ errors.append("permissions must contain only strings")
625
+
626
+ # Validate priority
627
+ priority = role_config.get("priority", 0)
628
+ if not isinstance(priority, int):
629
+ errors.append("priority must be an integer")
630
+ elif priority < 0:
631
+ errors.append("priority must be non-negative")
632
+
633
+ return errors
634
+
635
+ @classmethod
636
+ def get_schema(cls) -> Dict[str, Any]:
637
+ """
638
+ Get JSON schema for command parameters.
639
+
640
+ Returns:
641
+ JSON schema
642
+ """
643
+ return {
644
+ "type": "object",
645
+ "properties": {
646
+ "action": {
647
+ "type": "string",
648
+ "enum": ["list", "create", "update", "delete", "validate"],
649
+ "description": "Action to perform"
650
+ },
651
+ "role_name": {
652
+ "type": "string",
653
+ "description": "Name of the role"
654
+ },
655
+ "description": {
656
+ "type": "string",
657
+ "description": "Role description"
658
+ },
659
+ "allowed_servers": {
660
+ "type": "array",
661
+ "items": {"type": "string"},
662
+ "description": "List of allowed servers"
663
+ },
664
+ "allowed_clients": {
665
+ "type": "array",
666
+ "items": {"type": "string"},
667
+ "description": "List of allowed clients"
668
+ },
669
+ "permissions": {
670
+ "type": "array",
671
+ "items": {"type": "string"},
672
+ "description": "List of permissions"
673
+ },
674
+ "priority": {
675
+ "type": "integer",
676
+ "description": "Role priority"
677
+ },
678
+ "role_config": {
679
+ "type": "object",
680
+ "description": "Complete role configuration"
681
+ },
682
+ "filter": {
683
+ "type": "string",
684
+ "description": "Filter for list action"
685
+ },
686
+ "limit": {
687
+ "type": "integer",
688
+ "description": "Limit for list action"
689
+ },
690
+ "offset": {
691
+ "type": "integer",
692
+ "description": "Offset for list action"
693
+ }
694
+ },
695
+ "required": ["action"],
696
+ "additionalProperties": False
697
+ }