mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.5__py3-none-any.whl

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