python-rac 0.2.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.
pyrac/mcp_server.py ADDED
@@ -0,0 +1,3845 @@
1
+ """Local MCP stdio interface; read-only unless mutations are explicitly enabled."""
2
+ import argparse
3
+ import asyncio
4
+ from functools import partial
5
+ from uuid import UUID
6
+ from pydantic import StrictBool, StrictInt, StrictStr
7
+ from .client import RacClient, RacError
8
+ from .read_service import CLUSTER_LISTS, SERVER_LISTS, PROFILE_ACL_LISTS, ReadService
9
+ from .web.workspace import startup_endpoints
10
+
11
+ READ_TIMEOUT = 30.0
12
+
13
+
14
+ def create_server(service, *, allow_process_turn_off=False, allow_cluster_insert=False,
15
+ allow_agent_admin_register=False, allow_agent_admin_remove=False,
16
+ allow_cluster_admin_register=False, allow_cluster_admin_remove=False,
17
+ allow_rule_insert=False, allow_rule_remove=False, allow_rule_apply=False,
18
+ allow_service_settings_apply=False, allow_service_setting_remove=False,
19
+ allow_service_setting_update=False, allow_service_setting_insert=False,
20
+ allow_profile_remove=False, allow_profile_description_update=False,
21
+ allow_profile_config_update=False, allow_profile_access_flags_update=False,
22
+ allow_profile_role_fields_update=False, allow_profile_directory_remove=False,
23
+ allow_profile_directory_update=False, allow_profile_com_remove=False,
24
+ allow_profile_com_update=False, allow_profile_addin_remove=False,
25
+ allow_profile_addin_update=False, allow_profile_module_remove=False,
26
+ allow_profile_module_update=False, allow_profile_app_remove=False,
27
+ allow_profile_app_update=False, allow_profile_inet_remove=False,
28
+ allow_profile_inet_update=False,
29
+ allow_rule_priority_update=False, allow_rule_fields_update=False,
30
+ allow_rule_position_update=False,
31
+ allow_infobase_security_profile_update=False,
32
+ allow_infobase_safe_mode_security_profile_update=False,
33
+ allow_infobase_maximum_scheduled_jobs_shift_update=False,
34
+ allow_infobase_minimum_scheduled_jobs_period_update=False,
35
+ allow_infobase_configuration_unload_delay_update=False,
36
+ allow_infobase_local_speech_update=False,
37
+ allow_infobase_external_session_manager_update=False,
38
+ allow_infobase_reserve_working_processes_update=False,
39
+ allow_infobase_denied_message_update=False,
40
+ allow_infobase_license_distribution_update=False,
41
+ allow_infobase_sessions_deny_update=False,
42
+ allow_infobase_scheduled_jobs_deny_update=False,
43
+ allow_infobase_description_update=False,
44
+ allow_infobase_summary_description_update=False,
45
+ allow_infobase_registration_drop=False,
46
+ allow_infobase_existing_registration=False,
47
+ allow_infobase_database_create=False,
48
+ allow_limit_remove=False,
49
+ allow_limit_update=False, allow_counter_remove=False,
50
+ allow_counter_description_update=False,
51
+ allow_counter_collection_time_update=False,
52
+ allow_counter_duration_update=False, allow_counter_group_update=False,
53
+ allow_counter_filter_type_update=False, allow_counter_filter_update=False,
54
+ allow_counter_analysis_flags_update=False, allow_counter_clear=False,
55
+ allow_cluster_remove=False,
56
+ allow_cluster_name_update=False,
57
+ allow_cluster_expiration_timeout_update=False,
58
+ allow_cluster_security_level_update=False,
59
+ allow_cluster_session_fault_tolerance_level_update=False,
60
+ allow_cluster_load_balancing_mode_update=False,
61
+ allow_cluster_kill_problem_processes_update=False,
62
+ allow_cluster_kill_by_memory_with_dump_update=False,
63
+ allow_cluster_audit_update=False,
64
+ allow_cluster_ping_update=False,
65
+ allow_cluster_restart_schedule_update=False,
66
+ allow_server_restart_schedule_update=False,
67
+ allow_server_infobases_limit_update=False,
68
+ allow_server_connections_limit_update=False,
69
+ allow_server_safe_call_memory_limit_update=False,
70
+ allow_server_critical_total_memory_update=False,
71
+ allow_server_temporary_total_memory_update=False,
72
+ allow_server_temporary_memory_time_limit_update=False,
73
+ allow_server_spn_update=False,
74
+ allow_server_dedicate_managers_update=False,
75
+ allow_server_port_ranges_update=False, workspace_endpoints=None):
76
+ from mcp.server.mcpserver import MCPServer
77
+
78
+ server = MCPServer('pyrac', description=('1C RAC views' if
79
+ (allow_process_turn_off or allow_cluster_insert or allow_cluster_remove or
80
+ allow_agent_admin_register or allow_agent_admin_remove or
81
+ allow_cluster_admin_register or allow_cluster_admin_remove or
82
+ allow_rule_insert or allow_rule_remove or allow_rule_priority_update or
83
+ allow_rule_fields_update or allow_rule_position_update or
84
+ allow_infobase_security_profile_update or
85
+ allow_infobase_safe_mode_security_profile_update or
86
+ allow_infobase_maximum_scheduled_jobs_shift_update or
87
+ allow_infobase_minimum_scheduled_jobs_period_update or
88
+ allow_infobase_configuration_unload_delay_update or
89
+ allow_infobase_local_speech_update or
90
+ allow_infobase_external_session_manager_update or
91
+ allow_infobase_reserve_working_processes_update or
92
+ allow_infobase_denied_message_update or
93
+ allow_infobase_license_distribution_update or
94
+ allow_infobase_sessions_deny_update or
95
+ allow_infobase_scheduled_jobs_deny_update or
96
+ allow_infobase_description_update or
97
+ allow_infobase_summary_description_update or
98
+ allow_infobase_registration_drop or
99
+ allow_infobase_existing_registration or
100
+ allow_infobase_database_create or
101
+ allow_rule_apply or allow_service_settings_apply or
102
+ allow_service_setting_remove or allow_service_setting_update or
103
+ allow_service_setting_insert or allow_profile_remove or
104
+ allow_profile_description_update or allow_profile_config_update or
105
+ allow_profile_access_flags_update or allow_profile_role_fields_update or
106
+ allow_profile_directory_remove or allow_profile_directory_update or
107
+ allow_profile_com_remove or allow_profile_com_update or
108
+ allow_profile_addin_remove or allow_profile_addin_update or
109
+ allow_profile_module_remove or allow_profile_module_update or
110
+ allow_profile_app_remove or allow_profile_app_update or
111
+ allow_profile_inet_remove or allow_profile_inet_update or
112
+ allow_limit_remove or allow_limit_update or allow_counter_remove or
113
+ allow_counter_description_update or allow_counter_collection_time_update or
114
+ allow_counter_duration_update or allow_counter_group_update or
115
+ allow_counter_filter_type_update or allow_counter_filter_update or
116
+ allow_counter_analysis_flags_update or allow_counter_clear or
117
+ allow_cluster_name_update or
118
+ allow_cluster_expiration_timeout_update or
119
+ allow_cluster_security_level_update or
120
+ allow_cluster_session_fault_tolerance_level_update or
121
+ allow_cluster_load_balancing_mode_update or
122
+ allow_cluster_kill_problem_processes_update or
123
+ allow_cluster_kill_by_memory_with_dump_update or
124
+ allow_cluster_audit_update or
125
+ allow_cluster_ping_update or
126
+ allow_cluster_restart_schedule_update or
127
+ allow_server_restart_schedule_update or
128
+ allow_server_infobases_limit_update or
129
+ allow_server_connections_limit_update or
130
+ allow_server_safe_call_memory_limit_update or
131
+ allow_server_critical_total_memory_update or
132
+ allow_server_temporary_total_memory_update or
133
+ allow_server_temporary_memory_time_limit_update or
134
+ allow_server_spn_update or
135
+ allow_server_dedicate_managers_update or
136
+ allow_server_port_ranges_update) else 'Read-only 1C RAC views'))
137
+
138
+ async def safe_read(operation, *args):
139
+ try:
140
+ async with asyncio.timeout(READ_TIMEOUT):
141
+ return ReadService.public_data(await operation(*args))
142
+ except Exception as exc:
143
+ # Do not let SDK logging include credentials or raw server frames,
144
+ # including errors raised by an unexpected decoder or backend.
145
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
146
+
147
+ if workspace_endpoints is not None:
148
+ @server.tool(description='List cluster navigation entries on every configured RAS '
149
+ 'without requiring a search term; failed endpoints are isolated')
150
+ async def rac_workspace_list_clusters() -> list[dict]:
151
+ semaphore = asyncio.Semaphore(4)
152
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
153
+
154
+ async def list_one(index, endpoint):
155
+ host, port, protocol = endpoint
156
+ try:
157
+ async with asyncio.timeout_at(deadline), semaphore:
158
+ rows = await ReadService(partial(RacClient, host, port, protocol)).clusters()
159
+ result = []
160
+ for row in rows:
161
+ if not isinstance(row, dict):
162
+ continue
163
+ try:
164
+ cluster = str(UUID(str(row['cluster'])))
165
+ except (KeyError, TypeError, ValueError):
166
+ continue
167
+ name = row.get('name')
168
+ object_host = row.get('host')
169
+ valid_host = (isinstance(object_host, str) and object_host.strip()
170
+ and ReadService._valid_search_text(object_host))
171
+ valid_name = (isinstance(name, str) and name.strip()
172
+ and ReadService._valid_search_text(name))
173
+ entry = {'ras': index, 'host': host, 'port': port,
174
+ 'protocol': protocol, 'cluster': cluster,
175
+ 'name': name if valid_name else
176
+ object_host if valid_host else 'Unnamed cluster'}
177
+ if valid_host:
178
+ entry['object_host'] = object_host
179
+ result.append(entry)
180
+ return result
181
+ except Exception:
182
+ return []
183
+
184
+ batches = await asyncio.gather(*(list_one(index, endpoint)
185
+ for index, endpoint in enumerate(workspace_endpoints)))
186
+ return [row for batch in batches for row in batch]
187
+
188
+ @server.tool(description='List infobase navigation entries across all startup RAS '
189
+ 'without requiring a search term; isolate failed clusters')
190
+ async def rac_workspace_list_infobases() -> list[dict]:
191
+ semaphore = asyncio.Semaphore(4)
192
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
193
+
194
+ async def list_one(index, endpoint):
195
+ host, port, protocol = endpoint
196
+ result = []
197
+ try:
198
+ async with asyncio.timeout_at(deadline), semaphore:
199
+ async with RacClient(host, port, protocol) as client:
200
+ for cluster_row in await client.list_clusters():
201
+ if not isinstance(cluster_row, dict):
202
+ continue
203
+ try:
204
+ cluster = str(UUID(str(cluster_row['cluster'])))
205
+ except (KeyError, TypeError, ValueError):
206
+ continue
207
+ try:
208
+ bases = await client.list_infobases(UUID(cluster))
209
+ for row in bases:
210
+ if not isinstance(row, dict):
211
+ continue
212
+ try:
213
+ infobase = str(UUID(str(row['infobase'])))
214
+ except (KeyError, TypeError, ValueError):
215
+ continue
216
+ name = row.get('name')
217
+ valid_name = (isinstance(name, str) and name.strip()
218
+ and ReadService._valid_search_text(name))
219
+ result.append({'ras': index, 'host': host, 'port': port,
220
+ 'protocol': protocol, 'cluster': cluster,
221
+ 'infobase': infobase,
222
+ 'name': name if valid_name else 'Unnamed infobase'})
223
+ except Exception:
224
+ continue
225
+ return result
226
+ except Exception:
227
+ return result
228
+
229
+ batches = await asyncio.gather(*(list_one(index, endpoint)
230
+ for index, endpoint in enumerate(workspace_endpoints)))
231
+ return [row for batch in batches for row in batch]
232
+
233
+ @server.tool(description='List working-server navigation entries across all startup RAS '
234
+ 'without a search term; isolate failed clusters')
235
+ async def rac_workspace_list_servers() -> list[dict]:
236
+ semaphore = asyncio.Semaphore(4)
237
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
238
+
239
+ async def list_one(index, endpoint):
240
+ host, port, protocol = endpoint
241
+ result = []
242
+ try:
243
+ async with asyncio.timeout_at(deadline), semaphore:
244
+ async with RacClient(host, port, protocol) as client:
245
+ for cluster_row in await client.list_clusters():
246
+ if not isinstance(cluster_row, dict):
247
+ continue
248
+ try:
249
+ cluster = str(UUID(str(cluster_row['cluster'])))
250
+ except (KeyError, TypeError, ValueError):
251
+ continue
252
+ try:
253
+ for row in await client.list_servers(UUID(cluster)):
254
+ if not isinstance(row, dict):
255
+ continue
256
+ try:
257
+ server_id = str(UUID(str(row['server'])))
258
+ except (KeyError, TypeError, ValueError):
259
+ continue
260
+ name, object_host = row.get('name'), row.get('host_name')
261
+ valid_name = (isinstance(name, str) and name.strip()
262
+ and ReadService._valid_search_text(name))
263
+ valid_host = (isinstance(object_host, str) and object_host.strip()
264
+ and ReadService._valid_search_text(object_host))
265
+ entry = {'ras': index, 'host': host, 'port': port,
266
+ 'protocol': protocol, 'cluster': cluster,
267
+ 'server': server_id, 'name': name if valid_name else
268
+ object_host if valid_host else 'Unnamed server'}
269
+ if valid_host:
270
+ entry['object_host'] = object_host
271
+ result.append(entry)
272
+ except Exception:
273
+ continue
274
+ except Exception:
275
+ pass # Keep completed clusters; never return server exception text.
276
+ return result
277
+
278
+ batches = await asyncio.gather(*(list_one(index, endpoint)
279
+ for index, endpoint in enumerate(workspace_endpoints)))
280
+ return [row for batch in batches for row in batch]
281
+
282
+ @server.tool(description='List working-process navigation entries across all startup RAS '
283
+ 'without a search term; isolate failed clusters')
284
+ async def rac_workspace_list_processes() -> list[dict]:
285
+ semaphore = asyncio.Semaphore(4)
286
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
287
+
288
+ async def list_one(index, endpoint):
289
+ host, port, protocol = endpoint
290
+ result = []
291
+ try:
292
+ async with asyncio.timeout_at(deadline), semaphore:
293
+ async with RacClient(host, port, protocol) as client:
294
+ for cluster_row in await client.list_clusters():
295
+ if not isinstance(cluster_row, dict):
296
+ continue
297
+ try:
298
+ cluster = str(UUID(str(cluster_row['cluster'])))
299
+ except (KeyError, TypeError, ValueError):
300
+ continue
301
+ try:
302
+ for row in await client.list_processes(UUID(cluster)):
303
+ if not isinstance(row, dict):
304
+ continue
305
+ try:
306
+ process = str(UUID(str(row['process'])))
307
+ except (KeyError, TypeError, ValueError):
308
+ continue
309
+ pid, object_host = row.get('pid'), row.get('host')
310
+ valid_pid = (type(pid) is str and pid.isascii()
311
+ and pid.isdecimal() and len(pid) <= 20)
312
+ valid_host = (isinstance(object_host, str) and object_host.strip()
313
+ and ReadService._valid_search_text(object_host))
314
+ entry = {'ras': index, 'host': host, 'port': port,
315
+ 'protocol': protocol, 'cluster': cluster,
316
+ 'process': process,
317
+ 'name': f'PID {pid}' if valid_pid else 'Working process'}
318
+ if valid_pid:
319
+ entry['pid'] = pid
320
+ if valid_host:
321
+ entry['object_host'] = object_host
322
+ result.append(entry)
323
+ except Exception:
324
+ continue
325
+ except Exception:
326
+ pass # Keep completed clusters; never return server exception text.
327
+ return result
328
+
329
+ batches = await asyncio.gather(*(list_one(index, endpoint)
330
+ for index, endpoint in enumerate(workspace_endpoints)))
331
+ return [row for batch in batches for row in batch]
332
+
333
+ @server.tool(description='List session navigation entries across all startup RAS '
334
+ 'without a search term; isolate failed clusters')
335
+ async def rac_workspace_list_sessions() -> list[dict]:
336
+ semaphore = asyncio.Semaphore(4)
337
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
338
+
339
+ async def list_one(index, endpoint):
340
+ host, port, protocol = endpoint
341
+ result = []
342
+ try:
343
+ async with asyncio.timeout_at(deadline), semaphore:
344
+ async with RacClient(host, port, protocol) as client:
345
+ for cluster_row in await client.list_clusters():
346
+ if not isinstance(cluster_row, dict):
347
+ continue
348
+ try:
349
+ cluster = str(UUID(str(cluster_row['cluster'])))
350
+ except (KeyError, TypeError, ValueError):
351
+ continue
352
+ try:
353
+ for row in await client.list_sessions(UUID(cluster)):
354
+ if not isinstance(row, dict):
355
+ continue
356
+ try:
357
+ session = str(UUID(str(row['session_id'])))
358
+ except (KeyError, TypeError, ValueError):
359
+ continue
360
+ name = row.get('user_name')
361
+ if (not isinstance(name, str) or not name.strip()
362
+ or not ReadService._valid_search_text(name)):
363
+ name = 'Unnamed session'
364
+ result.append({'ras': index, 'host': host, 'port': port,
365
+ 'protocol': protocol, 'cluster': cluster,
366
+ 'session': session, 'name': name})
367
+ except Exception:
368
+ continue
369
+ except Exception:
370
+ pass # Keep completed clusters; never return server exception text.
371
+ return result
372
+
373
+ batches = await asyncio.gather(*(list_one(index, endpoint)
374
+ for index, endpoint in enumerate(workspace_endpoints)))
375
+ return [row for batch in batches for row in batch]
376
+
377
+ @server.tool(description='List connection navigation entries across all startup RAS '
378
+ 'without a search term; isolate failed clusters')
379
+ async def rac_workspace_list_connections() -> list[dict]:
380
+ semaphore = asyncio.Semaphore(4)
381
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
382
+
383
+ async def list_one(index, endpoint):
384
+ host, port, protocol = endpoint
385
+ result = []
386
+ try:
387
+ async with asyncio.timeout_at(deadline), semaphore:
388
+ async with RacClient(host, port, protocol) as client:
389
+ for cluster_row in await client.list_clusters():
390
+ if not isinstance(cluster_row, dict):
391
+ continue
392
+ try:
393
+ cluster = str(UUID(str(cluster_row['cluster'])))
394
+ except (KeyError, TypeError, ValueError):
395
+ continue
396
+ try:
397
+ for row in await client.list_connections(UUID(cluster)):
398
+ if not isinstance(row, dict):
399
+ continue
400
+ try:
401
+ connection = str(UUID(str(row['connection'])))
402
+ except (KeyError, TypeError, ValueError):
403
+ continue
404
+ object_host = row.get('host')
405
+ valid_host = (isinstance(object_host, str) and object_host.strip()
406
+ and ReadService._valid_search_text(object_host))
407
+ entry = {'ras': index, 'host': host, 'port': port,
408
+ 'protocol': protocol, 'cluster': cluster,
409
+ 'connection': connection,
410
+ 'name': object_host if valid_host else 'Unnamed connection'}
411
+ if valid_host:
412
+ entry['object_host'] = object_host
413
+ result.append(entry)
414
+ except Exception:
415
+ continue
416
+ except Exception:
417
+ pass # Keep completed clusters; never return server exception text.
418
+ return result
419
+
420
+ batches = await asyncio.gather(*(list_one(index, endpoint)
421
+ for index, endpoint in enumerate(workspace_endpoints)))
422
+ return [row for batch in batches for row in batch]
423
+
424
+ @server.tool(description='List safe lock navigation entries across startup RAS '
425
+ 'without a search term; isolate failed clusters')
426
+ async def rac_workspace_list_locks() -> list[dict]:
427
+ semaphore = asyncio.Semaphore(4)
428
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
429
+
430
+ async def list_one(index, endpoint):
431
+ host, port, protocol = endpoint
432
+ result = []
433
+ try:
434
+ async with asyncio.timeout_at(deadline), semaphore:
435
+ async with RacClient(host, port, protocol) as client:
436
+ for cluster_row in await client.list_clusters():
437
+ if not isinstance(cluster_row, dict):
438
+ continue
439
+ try:
440
+ cluster = str(UUID(str(cluster_row['cluster'])))
441
+ except (KeyError, TypeError, ValueError):
442
+ continue
443
+ try:
444
+ for row in await client.list_locks(UUID(cluster)):
445
+ if not isinstance(row, dict):
446
+ continue
447
+ try:
448
+ connection = str(UUID(str(row['connection'])))
449
+ obj = str(UUID(str(row['object'])))
450
+ except (KeyError, TypeError, ValueError):
451
+ continue
452
+ label = row.get('lock-descr')
453
+ name = (label if isinstance(label, str) and label.strip()
454
+ and ReadService._valid_search_text(label)
455
+ else 'Lock')
456
+ result.append({'ras': index, 'host': host, 'port': port,
457
+ 'protocol': protocol, 'cluster': cluster,
458
+ 'connection': connection, 'object': obj,
459
+ 'name': name})
460
+ except Exception:
461
+ continue
462
+ except Exception:
463
+ pass # Keep completed clusters without private exception text.
464
+ return result
465
+
466
+ batches = await asyncio.gather(*(list_one(index, endpoint)
467
+ for index, endpoint in enumerate(workspace_endpoints)))
468
+ return [row for batch in batches for row in batch]
469
+
470
+ @server.tool(description='List cluster-service navigation entries across all startup RAS '
471
+ 'without a search term; isolate failed clusters')
472
+ async def rac_workspace_list_services() -> list[dict]:
473
+ semaphore = asyncio.Semaphore(4)
474
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
475
+
476
+ async def list_one(index, endpoint):
477
+ host, port, protocol = endpoint
478
+ result = []
479
+ try:
480
+ async with asyncio.timeout_at(deadline), semaphore:
481
+ async with RacClient(host, port, protocol) as client:
482
+ for cluster_row in await client.list_clusters():
483
+ if not isinstance(cluster_row, dict):
484
+ continue
485
+ try:
486
+ cluster = str(UUID(str(cluster_row['cluster'])))
487
+ except (KeyError, TypeError, ValueError):
488
+ continue
489
+ try:
490
+ for row in await client.list_services(UUID(cluster)):
491
+ if not isinstance(row, dict):
492
+ continue
493
+ name = row.get('name')
494
+ if (not isinstance(name, str) or not name.strip()
495
+ or not ReadService._valid_search_text(name)):
496
+ continue
497
+ result.append({'ras': index, 'host': host, 'port': port,
498
+ 'protocol': protocol, 'cluster': cluster,
499
+ 'name': name})
500
+ except Exception:
501
+ continue
502
+ except Exception:
503
+ pass # Keep completed clusters; never return server exception text.
504
+ return result
505
+
506
+ batches = await asyncio.gather(*(list_one(index, endpoint)
507
+ for index, endpoint in enumerate(workspace_endpoints)))
508
+ return [row for batch in batches for row in batch]
509
+
510
+ @server.tool(description='List security-profile navigation entries across all startup RAS '
511
+ 'without a search term; isolate failed clusters')
512
+ async def rac_workspace_list_profiles() -> list[dict]:
513
+ semaphore = asyncio.Semaphore(4)
514
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
515
+
516
+ async def list_one(index, endpoint):
517
+ host, port, protocol = endpoint
518
+ result = []
519
+ try:
520
+ async with asyncio.timeout_at(deadline), semaphore:
521
+ async with RacClient(host, port, protocol) as client:
522
+ for cluster_row in await client.list_clusters():
523
+ if not isinstance(cluster_row, dict):
524
+ continue
525
+ try:
526
+ cluster = str(UUID(str(cluster_row['cluster'])))
527
+ except (KeyError, TypeError, ValueError):
528
+ continue
529
+ try:
530
+ for row in await client.list_profiles(UUID(cluster)):
531
+ if not isinstance(row, dict):
532
+ continue
533
+ name = row.get('name')
534
+ if (not isinstance(name, str) or not name.strip()
535
+ or not ReadService._valid_search_text(name)):
536
+ continue
537
+ result.append({'ras': index, 'host': host, 'port': port,
538
+ 'protocol': protocol, 'cluster': cluster,
539
+ 'name': name})
540
+ except Exception:
541
+ continue
542
+ except Exception:
543
+ pass # Keep completed clusters; never return server exception text.
544
+ return result
545
+
546
+ batches = await asyncio.gather(*(list_one(index, endpoint)
547
+ for index, endpoint in enumerate(workspace_endpoints)))
548
+ return [row for batch in batches for row in batch]
549
+
550
+ @server.tool(description='List managers, counters or limits across startup RAS without '
551
+ 'a search term; return navigation fields only')
552
+ async def rac_workspace_list_cluster_inventory(kind: StrictStr) -> list[dict]:
553
+ options = {'managers': ('list_managers', 'manager', 'manager'),
554
+ 'counters': ('list_counters', 'counter', 'name'),
555
+ 'limits': ('list_limits', 'limit', 'name')}
556
+ if kind not in options:
557
+ raise ValueError('Unsupported cluster inventory')
558
+ method, row_kind, selector_key = options[kind]
559
+ semaphore = asyncio.Semaphore(4)
560
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
561
+
562
+ async def list_one(index, endpoint):
563
+ host, port, protocol = endpoint
564
+ result = []
565
+ try:
566
+ async with asyncio.timeout_at(deadline), semaphore:
567
+ async with RacClient(host, port, protocol) as client:
568
+ for cluster_row in await client.list_clusters():
569
+ if not isinstance(cluster_row, dict):
570
+ continue
571
+ try:
572
+ cluster = str(UUID(str(cluster_row['cluster'])))
573
+ except (KeyError, TypeError, ValueError):
574
+ continue
575
+ try:
576
+ for row in await getattr(client, method)(UUID(cluster)):
577
+ if not isinstance(row, dict):
578
+ continue
579
+ if row_kind == 'manager':
580
+ try:
581
+ selector = str(UUID(str(row[selector_key])))
582
+ except (KeyError, TypeError, ValueError):
583
+ continue
584
+ label = row.get('host')
585
+ name = (label if isinstance(label, str) and label.strip()
586
+ and ReadService._valid_search_text(label)
587
+ else 'Cluster manager')
588
+ else:
589
+ selector = row.get(selector_key)
590
+ if (not isinstance(selector, str) or not selector.strip()
591
+ or not ReadService._valid_search_text(selector)):
592
+ continue
593
+ name = selector
594
+ result.append({'ras': index, 'host': host, 'port': port,
595
+ 'protocol': protocol, 'cluster': cluster,
596
+ 'kind': row_kind, 'selector': selector,
597
+ 'name': name})
598
+ except Exception:
599
+ continue
600
+ except Exception:
601
+ pass # Keep completed clusters; never return server exception text.
602
+ return result
603
+
604
+ batches = await asyncio.gather(*(list_one(index, endpoint)
605
+ for index, endpoint in enumerate(workspace_endpoints)))
606
+ return [row for batch in batches for row in batch]
607
+
608
+ @server.tool(description='List rule, service-setting or transfer-directory navigation '
609
+ 'across startup RAS without a search term; return safe fields only')
610
+ async def rac_workspace_list_server_inventory(kind: StrictStr) -> list[dict]:
611
+ options = {'rules': ('list_rules', 'rule', 'object-type', 'Assignment rule'),
612
+ 'service-settings': ('list_service_settings', 'setting',
613
+ 'service-name', 'Service setting'),
614
+ 'transfer-directories': ('list_service_data_dirs_for_transfer',
615
+ 'service-name', 'service-name', '')}
616
+ if kind not in options:
617
+ raise ValueError('Unsupported server inventory')
618
+ method, selector_key, label_key, fallback = options[kind]
619
+ semaphore = asyncio.Semaphore(4)
620
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
621
+
622
+ async def list_one(index, endpoint):
623
+ host, port, protocol = endpoint
624
+ result = []
625
+ try:
626
+ async with asyncio.timeout_at(deadline), semaphore:
627
+ async with RacClient(host, port, protocol) as client:
628
+ for cluster_row in await client.list_clusters():
629
+ if not isinstance(cluster_row, dict):
630
+ continue
631
+ try:
632
+ cluster = str(UUID(str(cluster_row['cluster'])))
633
+ except (KeyError, TypeError, ValueError):
634
+ continue
635
+ try:
636
+ servers = await client.list_servers(UUID(cluster))
637
+ except Exception:
638
+ continue
639
+ for server_row in servers:
640
+ if not isinstance(server_row, dict):
641
+ continue
642
+ try:
643
+ server_id = str(UUID(str(server_row['server'])))
644
+ except (KeyError, TypeError, ValueError):
645
+ continue
646
+ try:
647
+ for row in await getattr(client, method)(
648
+ UUID(cluster), UUID(server_id)):
649
+ if not isinstance(row, dict):
650
+ continue
651
+ entry = {'ras': index, 'host': host, 'port': port,
652
+ 'protocol': protocol, 'cluster': cluster,
653
+ 'server': server_id, 'kind': kind}
654
+ if kind == 'transfer-directories':
655
+ service_name = row.get(selector_key)
656
+ if (not isinstance(service_name, str)
657
+ or not service_name.strip()
658
+ or not ReadService._valid_search_text(service_name)):
659
+ continue
660
+ entry.update({'service-name': service_name,
661
+ 'name': service_name})
662
+ infobase = row.get('infobase-name')
663
+ if (isinstance(infobase, str)
664
+ and ReadService._valid_search_text(infobase)):
665
+ entry['infobase-name'] = infobase
666
+ else:
667
+ try:
668
+ selector = str(UUID(str(row[selector_key])))
669
+ except (KeyError, TypeError, ValueError):
670
+ continue
671
+ label = row.get(label_key)
672
+ name = (label if isinstance(label, str) and label.strip()
673
+ and ReadService._valid_search_text(label)
674
+ else fallback)
675
+ entry.update({selector_key: selector, 'name': name})
676
+ result.append(entry)
677
+ except Exception:
678
+ continue
679
+ except Exception:
680
+ pass # Keep completed servers without exposing connection errors.
681
+ return result
682
+
683
+ batches = await asyncio.gather(*(list_one(index, endpoint)
684
+ for index, endpoint in enumerate(workspace_endpoints)))
685
+ return [row for batch in batches for row in batch]
686
+
687
+ @server.tool(description='List safe agent or cluster administrator names across '
688
+ 'startup RAS without a search term')
689
+ async def rac_workspace_list_administrators(kind: StrictStr) -> list[dict]:
690
+ if kind not in ('agent-admins', 'cluster-admins'):
691
+ raise ValueError('Unsupported administrator inventory')
692
+ semaphore = asyncio.Semaphore(4)
693
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
694
+
695
+ async def list_one(index, endpoint):
696
+ host, port, protocol = endpoint
697
+ result = []
698
+ try:
699
+ async with asyncio.timeout_at(deadline), semaphore:
700
+ async with RacClient(host, port, protocol) as client:
701
+ if kind == 'agent-admins':
702
+ for row in await client.list_agent_admins():
703
+ if not isinstance(row, dict):
704
+ continue
705
+ name = row.get('name')
706
+ if (isinstance(name, str) and name.strip()
707
+ and ReadService._valid_search_text(name)):
708
+ result.append({'ras': index, 'host': host, 'port': port,
709
+ 'protocol': protocol, 'kind': kind, 'name': name})
710
+ else:
711
+ for cluster_row in await client.list_clusters():
712
+ if not isinstance(cluster_row, dict):
713
+ continue
714
+ try:
715
+ cluster = str(UUID(str(cluster_row['cluster'])))
716
+ except (KeyError, TypeError, ValueError):
717
+ continue
718
+ try:
719
+ for row in await client.list_cluster_admins(UUID(cluster)):
720
+ if not isinstance(row, dict):
721
+ continue
722
+ name = row.get('name')
723
+ if (isinstance(name, str) and name.strip()
724
+ and ReadService._valid_search_text(name)):
725
+ result.append({'ras': index, 'host': host,
726
+ 'port': port, 'protocol': protocol,
727
+ 'kind': kind, 'cluster': cluster,
728
+ 'name': name})
729
+ except Exception:
730
+ continue
731
+ except Exception:
732
+ pass # Keep completed clusters, not server exception text.
733
+ return result
734
+
735
+ batches = await asyncio.gather(*(list_one(index, endpoint)
736
+ for index, endpoint in enumerate(workspace_endpoints)))
737
+ return [row for batch in batches for row in batch]
738
+
739
+ @server.tool(description='Read-only cross-RAS search of verified inventories; '
740
+ 'returns endpoint and object navigation fields only')
741
+ async def rac_workspace_search(query: StrictStr, scope: StrictStr) -> list[dict]:
742
+ methods = {'clusters': 'search_clusters', 'infobases': 'search_infobases',
743
+ 'profiles': 'search_profiles', 'servers': 'search_working_servers',
744
+ 'processes': 'search_working_processes', 'sessions': 'search_sessions',
745
+ 'connections': 'search_connections', 'managers': 'search_managers',
746
+ 'counters': 'search_counters', 'limits': 'search_limits',
747
+ 'services': 'search_services', 'locks': 'search_locks',
748
+ 'agent-admins': 'search_agent_admins',
749
+ 'cluster-admins': 'search_cluster_admins',
750
+ 'rules': 'search_rules',
751
+ 'service-settings': 'search_service_settings',
752
+ 'transfer-directories': 'search_transfer_directories',
753
+ **{f'profile-{kind if kind != "directory" else "directories"}':
754
+ kind for kind in PROFILE_ACL_LISTS}}
755
+ if scope not in methods:
756
+ raise ValueError('Invalid workspace search scope')
757
+ needle = ReadService._search_needle(query)
758
+ semaphore = asyncio.Semaphore(4)
759
+ deadline = asyncio.get_running_loop().time() + READ_TIMEOUT
760
+
761
+ async def search_one(index, endpoint):
762
+ host, port, protocol = endpoint
763
+ try:
764
+ async with asyncio.timeout_at(deadline), semaphore:
765
+ reader = ReadService(partial(RacClient, host, port, protocol))
766
+ if scope.startswith('profile-'):
767
+ rows = await reader.search_profile_acl_all(methods[scope], needle)
768
+ else:
769
+ rows = await getattr(reader, methods[scope])(needle)
770
+ results = []
771
+ for row in rows:
772
+ address = ReadService.public_data(row)
773
+ if 'host' in address:
774
+ address['object_host'] = address.pop('host')
775
+ results.append({**address, 'ras': index, 'host': host,
776
+ 'port': port, 'protocol': protocol})
777
+ return results
778
+ except Exception:
779
+ # A failed RAS must not hide the healthy endpoints or leak its error.
780
+ return []
781
+
782
+ batches = await asyncio.gather(*(search_one(i, endpoint)
783
+ for i, endpoint in enumerate(workspace_endpoints)))
784
+ return [row for batch in batches for row in batch]
785
+
786
+ @server.tool(description='Read-only cluster overview from one configured RAS; '
787
+ 'ras is the index returned by rac_workspace_search')
788
+ async def rac_workspace_cluster_overview(ras: StrictInt, cluster_id: StrictStr) -> dict:
789
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
790
+ raise ValueError('Invalid RAS index')
791
+ cluster = str(UUID(cluster_id))
792
+ host, port, protocol = workspace_endpoints[ras]
793
+ try:
794
+ async with asyncio.timeout(READ_TIMEOUT):
795
+ reader = ReadService(partial(RacClient, host, port, protocol))
796
+ record = ReadService.public_data(await reader.cluster(cluster))
797
+ return {'ras': ras, 'host': host, 'port': port,
798
+ 'protocol': protocol, **record}
799
+ except Exception as exc:
800
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
801
+
802
+ @server.tool(description='List a verified cluster inventory from a selected startup RAS')
803
+ async def rac_workspace_list_cluster_group(ras: StrictInt, cluster_id: StrictStr,
804
+ group: StrictStr) -> dict:
805
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
806
+ raise ValueError('Invalid RAS index')
807
+ if group not in CLUSTER_LISTS:
808
+ raise ValueError('Invalid cluster group')
809
+ cluster = str(UUID(cluster_id))
810
+ host, port, protocol = workspace_endpoints[ras]
811
+ try:
812
+ async with asyncio.timeout(READ_TIMEOUT):
813
+ reader = ReadService(partial(RacClient, host, port, protocol))
814
+ rows = ReadService.public_data(await reader.list_group(cluster, group))
815
+ for row in rows:
816
+ if 'host' in row:
817
+ row['object_host'] = row.pop('host')
818
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
819
+ 'cluster': cluster, 'group': group, 'rows': rows}
820
+ except Exception as exc:
821
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
822
+
823
+ @server.tool(description='Read one server, process, session, infobase or connection '
824
+ 'from a selected startup RAS')
825
+ async def rac_workspace_object_detail(ras: StrictInt, cluster_id: StrictStr,
826
+ kind: StrictStr, object_id: StrictStr) -> dict:
827
+ methods = {'server': 'server', 'process': 'process', 'session': 'session',
828
+ 'infobase': 'infobase_info', 'connection': 'connection'}
829
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
830
+ raise ValueError('Invalid RAS index')
831
+ if kind not in methods:
832
+ raise ValueError('Invalid object kind')
833
+ cluster, object_id = str(UUID(cluster_id)), str(UUID(object_id))
834
+ host, port, protocol = workspace_endpoints[ras]
835
+ try:
836
+ async with asyncio.timeout(READ_TIMEOUT):
837
+ reader = ReadService(partial(RacClient, host, port, protocol))
838
+ record = ReadService.public_data(
839
+ await getattr(reader, methods[kind])(cluster, object_id))
840
+ if 'host' in record:
841
+ record['object_host'] = record.pop('host')
842
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
843
+ 'cluster': cluster, 'kind': kind, 'object': record}
844
+ except Exception as exc:
845
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
846
+
847
+ @server.tool(description='List rules, service settings or transfer directories '
848
+ 'of a working server on the selected startup RAS')
849
+ async def rac_workspace_list_server_group(ras: StrictInt, cluster_id: StrictStr,
850
+ server_id: StrictStr, group: StrictStr) -> dict:
851
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
852
+ raise ValueError('Invalid RAS index')
853
+ if group not in SERVER_LISTS:
854
+ raise ValueError('Invalid server group')
855
+ cluster, server_id = str(UUID(cluster_id)), str(UUID(server_id))
856
+ host, port, protocol = workspace_endpoints[ras]
857
+ try:
858
+ async with asyncio.timeout(READ_TIMEOUT):
859
+ reader = ReadService(partial(RacClient, host, port, protocol))
860
+ rows = ReadService.public_data(
861
+ await reader.list_server_group(cluster, server_id, group))
862
+ for row in rows:
863
+ if 'host' in row:
864
+ row['object_host'] = row.pop('host')
865
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
866
+ 'cluster': cluster, 'server': server_id, 'group': group,
867
+ 'rows': rows}
868
+ except Exception as exc:
869
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
870
+
871
+ @server.tool(description='List one verified security-profile ACL subtype on '
872
+ 'the selected startup RAS')
873
+ async def rac_workspace_list_profile_acl(ras: StrictInt, cluster_id: StrictStr,
874
+ profile_name: StrictStr,
875
+ acl_type: StrictStr) -> dict:
876
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
877
+ raise ValueError('Invalid RAS index')
878
+ if acl_type not in PROFILE_ACL_LISTS:
879
+ raise ValueError('Invalid profile ACL type')
880
+ cluster = str(UUID(cluster_id))
881
+ ReadService._validated_profile_name(profile_name)
882
+ host, port, protocol = workspace_endpoints[ras]
883
+ try:
884
+ async with asyncio.timeout(READ_TIMEOUT):
885
+ reader = ReadService(partial(RacClient, host, port, protocol))
886
+ rows = ReadService.public_data(
887
+ await reader.list_profile_acl(cluster, profile_name, acl_type))
888
+ for row in rows:
889
+ if 'host' in row:
890
+ row['object_host'] = row.pop('host')
891
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
892
+ 'cluster': cluster, 'profile': profile_name,
893
+ 'acl_type': acl_type, 'rows': rows}
894
+ except Exception as exc:
895
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
896
+
897
+ @server.tool(description='List sessions, connections or binary storages of an '
898
+ 'infobase on the selected startup RAS')
899
+ async def rac_workspace_list_infobase_group(ras: StrictInt, cluster_id: StrictStr,
900
+ infobase_id: StrictStr,
901
+ group: StrictStr) -> dict:
902
+ methods = {'sessions': 'sessions_in_infobase',
903
+ 'connections': 'connections_in_infobase',
904
+ 'binary-storages': 'binary_storages'}
905
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
906
+ raise ValueError('Invalid RAS index')
907
+ if group not in methods:
908
+ raise ValueError('Invalid infobase group')
909
+ cluster, infobase = str(UUID(cluster_id)), str(UUID(infobase_id))
910
+ host, port, protocol = workspace_endpoints[ras]
911
+ try:
912
+ async with asyncio.timeout(READ_TIMEOUT):
913
+ reader = ReadService(partial(RacClient, host, port, protocol))
914
+ rows = ReadService.public_data(
915
+ await getattr(reader, methods[group])(cluster, infobase))
916
+ for row in rows:
917
+ if 'host' in row:
918
+ row['object_host'] = row.pop('host')
919
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
920
+ 'cluster': cluster, 'infobase': infobase, 'group': group,
921
+ 'rows': rows}
922
+ except Exception as exc:
923
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
924
+
925
+ @server.tool(description='Read a manager, rule, service setting, counter or limit '
926
+ 'from the selected startup RAS')
927
+ async def rac_workspace_record_detail(ras: StrictInt, cluster_id: StrictStr,
928
+ kind: StrictStr, selector: StrictStr,
929
+ server_id: StrictStr = '') -> dict:
930
+ methods = {'manager': 'manager', 'rule': 'rule',
931
+ 'service-setting': 'service_setting',
932
+ 'counter': 'counter', 'limit': 'limit'}
933
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
934
+ raise ValueError('Invalid RAS index')
935
+ if kind not in methods:
936
+ raise ValueError('Invalid record kind')
937
+ cluster = str(UUID(cluster_id))
938
+ if kind in ('rule', 'service-setting'):
939
+ server_id = str(UUID(server_id))
940
+ elif server_id:
941
+ raise ValueError('Unexpected working server')
942
+ if kind in ('counter', 'limit'):
943
+ if (not selector or '\x00' in selector
944
+ or len(selector.encode('utf-8')) > 1024):
945
+ raise ValueError('Invalid record name')
946
+ else:
947
+ selector = str(UUID(selector))
948
+ host, port, protocol = workspace_endpoints[ras]
949
+ try:
950
+ async with asyncio.timeout(READ_TIMEOUT):
951
+ reader = ReadService(partial(RacClient, host, port, protocol))
952
+ arguments = ((cluster, server_id, selector) if kind in ('rule', 'service-setting')
953
+ else (cluster, selector))
954
+ record = ReadService.public_data(
955
+ await getattr(reader, methods[kind])(*arguments))
956
+ if 'host' in record:
957
+ record['object_host'] = record.pop('host')
958
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
959
+ 'cluster': cluster, 'server': server_id, 'kind': kind,
960
+ 'selector': selector, 'object': record}
961
+ except Exception as exc:
962
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
963
+
964
+ @server.tool(description='Read related processes, connections or locks for a '
965
+ 'selected server, process, infobase or connection on a startup RAS')
966
+ async def rac_workspace_related_inventory(ras: StrictInt, cluster_id: StrictStr,
967
+ scope: StrictStr, record_id: StrictStr) -> dict:
968
+ methods = {'server-processes': 'processes_on_server',
969
+ 'process-connections': 'connections_in_process',
970
+ 'infobase-locks': 'filtered_locks',
971
+ 'connection-locks': 'filtered_locks'}
972
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
973
+ raise ValueError('Invalid RAS index')
974
+ if scope not in methods:
975
+ raise ValueError('Invalid related inventory scope')
976
+ cluster, record = str(UUID(cluster_id)), str(UUID(record_id))
977
+ host, port, protocol = workspace_endpoints[ras]
978
+ try:
979
+ async with asyncio.timeout(READ_TIMEOUT):
980
+ reader = ReadService(partial(RacClient, host, port, protocol))
981
+ if scope.endswith('-locks'):
982
+ rows = await reader.filtered_locks(cluster, scope.split('-')[0], record)
983
+ else:
984
+ rows = await getattr(reader, methods[scope])(cluster, record)
985
+ rows = ReadService.public_data(rows)
986
+ for row in rows:
987
+ if 'host' in row:
988
+ row['object_host'] = row.pop('host')
989
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
990
+ 'cluster': cluster, 'scope': scope, 'record': record,
991
+ 'rows': rows}
992
+ except Exception as exc:
993
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
994
+
995
+ @server.tool(description='Read regular or accumulated values of a named counter '
996
+ 'on the selected startup RAS')
997
+ async def rac_workspace_counter_values(ras: StrictInt, cluster_id: StrictStr,
998
+ name: StrictStr, object_filter: StrictStr = '',
999
+ accumulated: StrictBool = False) -> dict:
1000
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
1001
+ raise ValueError('Invalid RAS index')
1002
+ if (not name.strip() or '\x00' in name or len(name.encode('utf-8')) > 1024
1003
+ or '\x00' in object_filter
1004
+ or len(object_filter.encode('utf-8')) > 4096):
1005
+ raise ValueError('Invalid counter selector')
1006
+ cluster = str(UUID(cluster_id))
1007
+ host, port, protocol = workspace_endpoints[ras]
1008
+ try:
1009
+ async with asyncio.timeout(READ_TIMEOUT):
1010
+ reader = ReadService(partial(RacClient, host, port, protocol))
1011
+ rows = ReadService.public_data(await reader.counter_values(
1012
+ cluster, name, object_filter, accumulated=accumulated))
1013
+ for row in rows:
1014
+ if 'host' in row:
1015
+ row['object_host'] = row.pop('host')
1016
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
1017
+ 'cluster': cluster, 'name': name, 'object_filter': object_filter,
1018
+ 'accumulated': accumulated, 'rows': rows}
1019
+ except Exception as exc:
1020
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
1021
+
1022
+ @server.tool(description='Read an infobase summary or a security-profile overview '
1023
+ 'with its verified ACL lists from the selected startup RAS')
1024
+ async def rac_workspace_named_overview(ras: StrictInt, cluster_id: StrictStr,
1025
+ kind: StrictStr, selector: StrictStr) -> dict:
1026
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
1027
+ raise ValueError('Invalid RAS index')
1028
+ if kind not in ('infobase-summary', 'profile-overview'):
1029
+ raise ValueError('Invalid overview kind')
1030
+ cluster = str(UUID(cluster_id))
1031
+ if kind == 'infobase-summary':
1032
+ selector = str(UUID(selector))
1033
+ else:
1034
+ ReadService._validated_profile_name(selector)
1035
+ host, port, protocol = workspace_endpoints[ras]
1036
+ try:
1037
+ async with asyncio.timeout(READ_TIMEOUT):
1038
+ reader = ReadService(partial(RacClient, host, port, protocol))
1039
+ if kind == 'infobase-summary':
1040
+ record = await reader.infobase_summary(cluster, selector)
1041
+ else:
1042
+ record = await reader.profile_overview(cluster, selector)
1043
+ record = ReadService.public_data(record)
1044
+ if 'host' in record:
1045
+ record['object_host'] = record.pop('host')
1046
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
1047
+ 'cluster': cluster, 'kind': kind, 'selector': selector,
1048
+ 'object': record}
1049
+ except Exception as exc:
1050
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
1051
+
1052
+ @server.tool(description='Read an infobase info or summary by name on the '
1053
+ 'selected startup RAS, without requiring an infobase UUID')
1054
+ async def rac_workspace_infobase_by_name(ras: StrictInt, cluster_id: StrictStr,
1055
+ kind: StrictStr, name: StrictStr) -> dict:
1056
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
1057
+ raise ValueError('Invalid RAS index')
1058
+ if kind not in ('info', 'summary'):
1059
+ raise ValueError('Invalid infobase view')
1060
+ cluster = str(UUID(cluster_id))
1061
+ ReadService._validated_infobase_name(name)
1062
+ host, port, protocol = workspace_endpoints[ras]
1063
+ if protocol == '10.0':
1064
+ raise ValueError('Infobase info and summary by name require protocol 16.0')
1065
+ try:
1066
+ async with asyncio.timeout(READ_TIMEOUT):
1067
+ reader = ReadService(partial(RacClient, host, port, protocol))
1068
+ method = (reader.infobase_info_by_name if kind == 'info'
1069
+ else reader.infobase_summary_by_name)
1070
+ record = ReadService.public_data(await method(cluster, name))
1071
+ if 'host' in record:
1072
+ record['object_host'] = record.pop('host')
1073
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
1074
+ 'cluster': cluster, 'kind': kind, 'name': name, 'object': record}
1075
+ except Exception as exc:
1076
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
1077
+
1078
+ @server.tool(description='Read agent version, agent administrators or cluster '
1079
+ 'administrators from the selected startup RAS')
1080
+ async def rac_workspace_agent_view(ras: StrictInt, scope: StrictStr,
1081
+ cluster_id: StrictStr = '') -> dict:
1082
+ methods = {'agent-version': 'agent_version', 'agent-admins': 'agent_admins',
1083
+ 'cluster-admins': 'cluster_admins'}
1084
+ if type(ras) is not int or not 0 <= ras < len(workspace_endpoints):
1085
+ raise ValueError('Invalid RAS index')
1086
+ if scope not in methods:
1087
+ raise ValueError('Invalid agent view')
1088
+ if scope == 'cluster-admins':
1089
+ cluster_id = str(UUID(cluster_id))
1090
+ elif cluster_id:
1091
+ raise ValueError('Unexpected cluster')
1092
+ host, port, protocol = workspace_endpoints[ras]
1093
+ try:
1094
+ async with asyncio.timeout(READ_TIMEOUT):
1095
+ reader = ReadService(partial(RacClient, host, port, protocol))
1096
+ args = (cluster_id,) if cluster_id else ()
1097
+ data = ReadService.public_data(await getattr(reader, methods[scope])(*args))
1098
+ return {'ras': ras, 'host': host, 'port': port, 'protocol': protocol,
1099
+ 'scope': scope, 'cluster': cluster_id, 'data': data}
1100
+ except Exception as exc:
1101
+ raise ValueError('RAC read failed (' + type(exc).__name__ + ')') from None
1102
+
1103
+ @server.tool(description='List clusters accessible on the configured RAS')
1104
+ async def rac_list_clusters() -> list[dict]:
1105
+ return await safe_read(service.clusters)
1106
+
1107
+ @server.tool(description='Search clusters by name or UUID on the configured RAS; '
1108
+ 'returns only cluster UUID and name')
1109
+ async def rac_search_clusters(query: StrictStr) -> list[dict]:
1110
+ return await safe_read(service.search_clusters, query)
1111
+
1112
+ @server.tool(description='Search infobases by name or UUID across clusters on the configured RAS; '
1113
+ 'returns only cluster UUID, infobase UUID and name')
1114
+ async def rac_search_infobases(query: StrictStr) -> list[dict]:
1115
+ return await safe_read(service.search_infobases, query)
1116
+
1117
+ @server.tool(description='Search security profiles by name across clusters on the configured RAS; '
1118
+ 'returns only cluster UUID and profile name')
1119
+ async def rac_search_profiles(query: StrictStr) -> list[dict]:
1120
+ return await safe_read(service.search_profiles, query)
1121
+
1122
+ @server.tool(description='Search working servers by name or UUID across clusters on the '
1123
+ 'configured RAS; returns only cluster UUID, server UUID and name')
1124
+ async def rac_search_working_servers(query: StrictStr) -> list[dict]:
1125
+ return await safe_read(service.search_working_servers, query)
1126
+
1127
+ @server.tool(description='Search working processes by PID, host or UUID across clusters '
1128
+ 'on the configured RAS; returns only navigation fields')
1129
+ async def rac_search_working_processes(query: StrictStr) -> list[dict]:
1130
+ return await safe_read(service.search_working_processes, query)
1131
+
1132
+ @server.tool(description='Search sessions by user, host, application, UUID or numeric ID '
1133
+ 'on the configured RAS; returns only cluster, session UUID and user')
1134
+ async def rac_search_sessions(query: StrictStr) -> list[dict]:
1135
+ return await safe_read(service.search_sessions, query)
1136
+
1137
+ @server.tool(description='Search connections by host, application, UUID or numeric ID '
1138
+ 'on the configured RAS; returns only cluster, connection UUID and host')
1139
+ async def rac_search_connections(query: StrictStr) -> list[dict]:
1140
+ return await safe_read(service.search_connections, query)
1141
+
1142
+ @server.tool(description='Search agent administrators on the configured RAS by name or '
1143
+ 'OS user; returns only administrator names')
1144
+ async def rac_search_agent_admins(query: StrictStr) -> list[dict]:
1145
+ return await safe_read(service.search_agent_admins, query)
1146
+
1147
+ @server.tool(description='Search cluster administrators on the configured RAS by name or '
1148
+ 'OS user; returns only cluster UUID and administrator name')
1149
+ async def rac_search_cluster_admins(query: StrictStr) -> list[dict]:
1150
+ return await safe_read(service.search_cluster_admins, query)
1151
+
1152
+ @server.tool(description='Search cluster managers by UUID, host, description or PID '
1153
+ 'on the configured RAS; returns only cluster UUID, manager UUID and host')
1154
+ async def rac_search_managers(query: StrictStr) -> list[dict]:
1155
+ return await safe_read(service.search_managers, query)
1156
+
1157
+ @server.tool(description='Search cluster services by name, description or manager UUID '
1158
+ 'on the configured RAS; returns only cluster UUID and service name')
1159
+ async def rac_search_services(query: StrictStr) -> list[dict]:
1160
+ return await safe_read(service.search_services, query)
1161
+
1162
+ @server.tool(description='Search counters by name or description across clusters on '
1163
+ 'the configured RAS; returns only navigation fields')
1164
+ async def rac_search_counters(query: StrictStr) -> list[dict]:
1165
+ return await safe_read(service.search_counters, query)
1166
+
1167
+ @server.tool(description='Search limits by name or description across clusters on '
1168
+ 'the configured RAS; returns only navigation fields')
1169
+ async def rac_search_limits(query: StrictStr) -> list[dict]:
1170
+ return await safe_read(service.search_limits, query)
1171
+
1172
+ @server.tool(description='Search locks by description, connection UUID or object UUID '
1173
+ 'across clusters on the configured RAS; returns only navigation fields')
1174
+ async def rac_search_locks(query: StrictStr) -> list[dict]:
1175
+ return await safe_read(service.search_locks, query)
1176
+
1177
+ @server.tool(description='Search assignment rules across working servers on the '
1178
+ 'configured RAS; returns exact cluster, server and rule UUIDs')
1179
+ async def rac_search_rules(query: StrictStr) -> list[dict]:
1180
+ return await safe_read(service.search_rules, query)
1181
+
1182
+ @server.tool(description='Search service settings across working servers on the '
1183
+ 'configured RAS; returns exact cluster, server and setting UUIDs')
1184
+ async def rac_search_service_settings(query: StrictStr) -> list[dict]:
1185
+ return await safe_read(service.search_service_settings, query)
1186
+
1187
+ @server.tool(description='Search transfer directories by service or infobase name '
1188
+ 'on the configured RAS; excludes filesystem paths')
1189
+ async def rac_search_transfer_directories(query: StrictStr) -> list[dict]:
1190
+ return await safe_read(service.search_transfer_directories, query)
1191
+
1192
+ @server.tool(description='Get the RAS agent version (protocol 16.0)')
1193
+ async def rac_agent_version() -> str:
1194
+ return await safe_read(service.agent_version)
1195
+
1196
+ @server.tool(description='List RAS agent administrators')
1197
+ async def rac_list_agent_admins() -> list[dict]:
1198
+ return await safe_read(service.agent_admins)
1199
+
1200
+ @server.tool(description='List cluster administrators by cluster UUID')
1201
+ async def rac_list_cluster_admins(cluster_id: StrictStr) -> list[dict]:
1202
+ return await safe_read(service.cluster_admins, cluster_id)
1203
+
1204
+ @server.tool(description='Get a cluster record by UUID')
1205
+ async def rac_get_cluster(cluster_id: StrictStr) -> dict:
1206
+ return await safe_read(service.one, cluster_id, 'get_cluster')
1207
+
1208
+ @server.tool(description='Read cluster info, working servers, processes and infobase summaries')
1209
+ async def rac_cluster_overview(cluster_id: StrictStr) -> dict:
1210
+ return await safe_read(service.cluster, cluster_id)
1211
+
1212
+ @server.tool(description='List working servers of a cluster')
1213
+ async def rac_list_working_servers(cluster_id: StrictStr) -> list[dict]:
1214
+ return await safe_read(service.one, cluster_id, 'list_servers')
1215
+
1216
+ @server.tool(description='List working processes of a cluster')
1217
+ async def rac_list_working_processes(cluster_id: StrictStr) -> list[dict]:
1218
+ return await safe_read(service.one, cluster_id, 'list_processes')
1219
+
1220
+ @server.tool(description='List working processes filtered by working server UUID')
1221
+ async def rac_list_processes_on_server(cluster_id: StrictStr, server_id: StrictStr) -> list[dict]:
1222
+ return await safe_read(service.processes_on_server, cluster_id, server_id)
1223
+
1224
+ @server.tool(description='List infobase summaries of a cluster')
1225
+ async def rac_list_infobase_summaries(cluster_id: StrictStr) -> list[dict]:
1226
+ return await safe_read(service.one, cluster_id, 'list_infobases')
1227
+
1228
+ @server.tool(description='List sessions filtered by infobase UUID (populated 16.0 decoder unverified)')
1229
+ async def rac_list_sessions_in_infobase(cluster_id: StrictStr, infobase_id: StrictStr) -> list[dict]:
1230
+ return await safe_read(service.sessions_in_infobase, cluster_id, infobase_id)
1231
+
1232
+ @server.tool(description='List connections filtered by infobase UUID')
1233
+ async def rac_list_connections_in_infobase(cluster_id: StrictStr, infobase_id: StrictStr) -> list[dict]:
1234
+ return await safe_read(service.connections_in_infobase, cluster_id, infobase_id)
1235
+
1236
+ @server.tool(description='List connections filtered locally by process UUID after decoding the cluster list')
1237
+ async def rac_list_connections_in_process(cluster_id: StrictStr, process_id: StrictStr) -> list[dict]:
1238
+ return await safe_read(service.connections_in_process, cluster_id, process_id)
1239
+
1240
+ @server.tool(description='List locks filtered by infobase or connection UUID (not session)')
1241
+ async def rac_list_filtered_locks(cluster_id: StrictStr, scope: StrictStr, record_id: StrictStr) -> list[dict]:
1242
+ return await safe_read(service.filtered_locks, cluster_id, scope, record_id)
1243
+
1244
+ @server.tool(description='List a verified read-only cluster group: ' + ', '.join(CLUSTER_LISTS))
1245
+ async def rac_list_cluster_group(cluster_id: StrictStr, group: StrictStr) -> list[dict]:
1246
+ return await safe_read(service.list_group, cluster_id, group)
1247
+
1248
+ @server.tool(description='Get a working server record by cluster and server UUID')
1249
+ async def rac_get_server(cluster_id: StrictStr, server_id: StrictStr) -> dict:
1250
+ return await safe_read(service.server, cluster_id, server_id)
1251
+
1252
+ @server.tool(description='Get a working process record by cluster and process UUID')
1253
+ async def rac_get_process(cluster_id: StrictStr, process_id: StrictStr) -> dict:
1254
+ return await safe_read(service.process, cluster_id, process_id)
1255
+
1256
+ @server.tool(description='Get a session record by cluster and session UUID')
1257
+ async def rac_get_session(cluster_id: StrictStr, session_id: StrictStr) -> dict:
1258
+ return await safe_read(service.session, cluster_id, session_id)
1259
+
1260
+ @server.tool(description='Get an infobase summary by cluster and infobase UUID')
1261
+ async def rac_get_infobase_summary(cluster_id: StrictStr, infobase_id: StrictStr) -> dict:
1262
+ return await safe_read(service.infobase_summary, cluster_id, infobase_id)
1263
+
1264
+ @server.tool(description='Get an infobase summary by its unique name within a cluster')
1265
+ async def rac_get_infobase_summary_by_name(cluster_id: StrictStr, name: StrictStr) -> dict:
1266
+ return await safe_read(service.infobase_summary_by_name, cluster_id, name)
1267
+
1268
+ @server.tool(description='Get infobase full info by cluster and infobase UUID (protocol 16.0)')
1269
+ async def rac_get_infobase_info(cluster_id: StrictStr, infobase_id: StrictStr) -> dict:
1270
+ return await safe_read(service.infobase_info, cluster_id, infobase_id)
1271
+
1272
+ @server.tool(description='Get filtered infobase full info by its unique name (protocol 16.0)')
1273
+ async def rac_get_infobase_info_by_name(cluster_id: StrictStr, name: StrictStr) -> dict:
1274
+ return await safe_read(service.infobase_info_by_name, cluster_id, name)
1275
+
1276
+ @server.tool(description='List binary data storages of an infobase (populated results unverified)')
1277
+ async def rac_list_binary_storages(cluster_id: StrictStr, infobase_id: StrictStr) -> list[dict]:
1278
+ return await safe_read(service.binary_storages, cluster_id, infobase_id)
1279
+
1280
+ @server.tool(description='Get a connection record by cluster and connection UUID')
1281
+ async def rac_get_connection(cluster_id: StrictStr, connection_id: StrictStr) -> dict:
1282
+ return await safe_read(service.connection, cluster_id, connection_id)
1283
+
1284
+ @server.tool(description='Get a service setting by cluster, server and setting UUID')
1285
+ async def rac_get_service_setting(cluster_id: StrictStr, server_id: StrictStr, setting_id: StrictStr) -> dict:
1286
+ return await safe_read(service.service_setting, cluster_id, server_id, setting_id)
1287
+
1288
+ @server.tool(description='Get an assignment rule by cluster, server and rule UUID')
1289
+ async def rac_get_rule(cluster_id: StrictStr, server_id: StrictStr, rule_id: StrictStr) -> dict:
1290
+ return await safe_read(service.rule, cluster_id, server_id, rule_id)
1291
+
1292
+ @server.tool(description='Get a cluster manager by cluster and manager UUID')
1293
+ async def rac_get_manager(cluster_id: StrictStr, manager_id: StrictStr) -> dict:
1294
+ return await safe_read(service.manager, cluster_id, manager_id)
1295
+
1296
+ @server.tool(description='Get a cluster limit by cluster UUID and limit name')
1297
+ async def rac_get_limit(cluster_id: StrictStr, name: StrictStr) -> dict:
1298
+ return await safe_read(service.limit, cluster_id, name)
1299
+
1300
+ @server.tool(description='Get a cluster counter by cluster UUID and counter name')
1301
+ async def rac_get_counter(cluster_id: StrictStr, name: StrictStr) -> dict:
1302
+ return await safe_read(service.counter, cluster_id, name)
1303
+
1304
+ @server.tool(description='List counter readings (populated 16.0 decoder not live-verified)')
1305
+ async def rac_list_counter_values(cluster_id: StrictStr, name: StrictStr, object_filter: StrictStr = '') -> list[dict]:
1306
+ return await safe_read(service.counter_values, cluster_id, name, object_filter)
1307
+
1308
+ @server.tool(description='List accumulated counter readings (populated 16.0 decoder not live-verified)')
1309
+ async def rac_list_accumulated_counter_values(cluster_id: StrictStr, name: StrictStr,
1310
+ object_filter: StrictStr = '') -> list[dict]:
1311
+ return await safe_read(partial(service.counter_values, accumulated=True),
1312
+ cluster_id, name, object_filter)
1313
+
1314
+ @server.tool(description='List a verified read-only server group: ' + ', '.join(SERVER_LISTS))
1315
+ async def rac_list_server_group(cluster_id: StrictStr, server_id: StrictStr, group: StrictStr) -> list[dict]:
1316
+ return await safe_read(service.list_server_group, cluster_id, server_id, group)
1317
+
1318
+ @server.tool(description='List service transfer directories filtered by service name on a working server')
1319
+ async def rac_list_transfer_directories(cluster_id: StrictStr, server_id: StrictStr,
1320
+ service_name: StrictStr = '') -> list[dict]:
1321
+ return await safe_read(service.transfer_directories, cluster_id, server_id, service_name)
1322
+
1323
+ @server.tool(description='Get a security profile by its unique name within a cluster')
1324
+ async def rac_get_profile_by_name(cluster_id: StrictStr, name: StrictStr) -> dict:
1325
+ return await safe_read(service.profile_by_name, cluster_id, name)
1326
+
1327
+ @server.tool(description='Read a security profile and all six verified ACL lists by exact name')
1328
+ async def rac_profile_overview(cluster_id: StrictStr, name: StrictStr) -> dict:
1329
+ return await safe_read(service.profile_overview, cluster_id, name)
1330
+
1331
+ @server.tool(description='List profile ACL entries: ' + ', '.join(PROFILE_ACL_LISTS) +
1332
+ '; optionally filter by exact alias (directory) or name (other ACLs)')
1333
+ async def rac_list_profile_acl(cluster_id: StrictStr, profile_name: StrictStr,
1334
+ acl_type: StrictStr, name: StrictStr = '') -> list[dict]:
1335
+ return await safe_read(service.list_profile_acl, cluster_id, profile_name, acl_type, name)
1336
+
1337
+ @server.tool(description='Search one of six verified profile ACL categories by alias or name '
1338
+ 'on the configured RAS; returns only navigation fields')
1339
+ async def rac_search_profile_acl(cluster_id: StrictStr, acl_type: StrictStr,
1340
+ query: StrictStr) -> list[dict]:
1341
+ return await safe_read(service.search_profile_acl, cluster_id, acl_type, query)
1342
+
1343
+ if allow_agent_admin_register:
1344
+ @server.tool(description='MUTATION: register a password-free OS-capable agent administrator. '
1345
+ 'Confirm the exact name:os_user; guarded client verifies the complete admin list.')
1346
+ async def rac_register_agent_admin(name: StrictStr, os_user: StrictStr, descr: StrictStr,
1347
+ confirm_name_os_user: StrictStr) -> dict:
1348
+ if (type(name) is not str or not name or '\x00' in name
1349
+ or len(name.encode('utf-8')) > 1024
1350
+ or type(os_user) is not str or not os_user or '\x00' in os_user
1351
+ or len(os_user.encode('utf-8')) > 1024
1352
+ or type(descr) is not str or '\x00' in descr
1353
+ or len(descr.encode('utf-8')) > 1024):
1354
+ raise ValueError('Invalid agent administrator parameters')
1355
+ if confirm_name_os_user != f'{name}:{os_user}':
1356
+ raise ValueError('Confirmation must match name:os_user exactly')
1357
+ try:
1358
+ async with service.factory() as client:
1359
+ return await client.register_agent_admin(name, os_user, descr=descr)
1360
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1361
+ raise ValueError('RAC agent admin register failed (' + type(exc).__name__ + ')') from None
1362
+
1363
+ if allow_agent_admin_remove:
1364
+ @server.tool(description='DESTRUCTIVE: remove an agent administrator; requires an authenticated '
1365
+ 'agent-user and exact name confirmation. Guarded client verifies full list.')
1366
+ async def rac_remove_agent_admin(name: StrictStr, confirm_name: StrictStr) -> list[dict]:
1367
+ if (type(name) is not str or not name or '\x00' in name
1368
+ or len(name.encode('utf-8')) > 1024):
1369
+ raise ValueError('Invalid agent administrator name')
1370
+ if confirm_name != name:
1371
+ raise ValueError('Confirmation must match agent administrator name exactly')
1372
+ try:
1373
+ async with service.factory() as client:
1374
+ return await client.remove_agent_admin(name)
1375
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1376
+ raise ValueError('RAC agent admin remove failed (' + type(exc).__name__ + ')') from None
1377
+
1378
+ if allow_cluster_admin_register:
1379
+ @server.tool(description='MUTATION: register a password-free OS-capable cluster administrator. '
1380
+ 'Confirm exact cluster UUID:name:os_user; guarded client checks full admin list.')
1381
+ async def rac_register_cluster_admin(cluster_id: StrictStr, name: StrictStr,
1382
+ os_user: StrictStr, descr: StrictStr,
1383
+ confirm_cluster_name_os_user: StrictStr) -> dict:
1384
+ try:
1385
+ cluster = UUID(cluster_id)
1386
+ except (TypeError, ValueError, AttributeError):
1387
+ raise ValueError('Invalid cluster UUID') from None
1388
+ if (str(cluster) != cluster_id or type(name) is not str or not name
1389
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
1390
+ or type(os_user) is not str or not os_user or '\x00' in os_user
1391
+ or len(os_user.encode('utf-8')) > 1024
1392
+ or type(descr) is not str or '\x00' in descr
1393
+ or len(descr.encode('utf-8')) > 1024):
1394
+ raise ValueError('Invalid cluster administrator parameters')
1395
+ if confirm_cluster_name_os_user != f'{cluster}:{name}:{os_user}':
1396
+ raise ValueError('Confirmation must match cluster UUID:name:os_user exactly')
1397
+ try:
1398
+ async with service.factory() as client:
1399
+ return await client.register_cluster_admin(cluster, name, os_user, descr=descr)
1400
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1401
+ raise ValueError('RAC cluster admin register failed (' + type(exc).__name__ + ')') from None
1402
+
1403
+ if allow_cluster_admin_remove:
1404
+ @server.tool(description='DESTRUCTIVE: remove a cluster administrator. Requires an authenticated '
1405
+ 'cluster-user and exact cluster UUID:name confirmation; guarded client checks '
1406
+ 'the complete administrator list after removal.')
1407
+ async def rac_remove_cluster_admin(cluster_id: StrictStr, name: StrictStr,
1408
+ confirm_cluster_name: StrictStr) -> list[dict]:
1409
+ try:
1410
+ cluster = UUID(cluster_id)
1411
+ except (TypeError, ValueError, AttributeError):
1412
+ raise ValueError('Invalid cluster UUID') from None
1413
+ if (str(cluster) != cluster_id or type(name) is not str or not name
1414
+ or '\x00' in name or len(name.encode('utf-8')) > 1024):
1415
+ raise ValueError('Invalid cluster administrator parameters')
1416
+ if confirm_cluster_name != f'{cluster}:{name}':
1417
+ raise ValueError('Confirmation must match cluster UUID:name exactly')
1418
+ try:
1419
+ async with service.factory() as client:
1420
+ return await client.remove_cluster_admin(cluster, name)
1421
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1422
+ raise ValueError('RAC cluster admin remove failed (' + type(exc).__name__ + ')') from None
1423
+
1424
+ if allow_infobase_database_create:
1425
+ @server.tool(description='DESTRUCTIVE: create and register a new PostgreSQL database. '
1426
+ 'Confirm cluster UUID:name:database name exactly; guarded client '
1427
+ 'requires full list and info read-back. No password input.')
1428
+ async def rac_create_infobase_database(
1429
+ cluster_id: StrictStr, name: StrictStr, db_name: StrictStr, db_server: StrictStr,
1430
+ db_user: StrictStr, locale: StrictStr, confirm_cluster_name_db: StrictStr) -> dict:
1431
+ try:
1432
+ cluster = UUID(cluster_id)
1433
+ except (TypeError, ValueError, AttributeError):
1434
+ raise ValueError('Invalid cluster UUID') from None
1435
+ if str(cluster) != cluster_id:
1436
+ raise ValueError('Invalid cluster UUID')
1437
+ for field in (name, db_name, db_server, db_user, locale):
1438
+ if (type(field) is not str or not field or '\x00' in field
1439
+ or len(field.encode('utf-8')) > 255):
1440
+ raise ValueError('Invalid infobase creation parameters')
1441
+ if confirm_cluster_name_db != f'{cluster}:{name}:{db_name}':
1442
+ raise ValueError('Confirmation must match cluster UUID:name:database name exactly')
1443
+ try:
1444
+ async with service.factory() as client:
1445
+ return ReadService._public_infobase_info(await client.create_infobase(
1446
+ cluster, name=name, db_name=db_name, db_server=db_server,
1447
+ db_user=db_user, locale=locale, create_database=True))
1448
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1449
+ raise ValueError('RAC infobase database creation failed ('
1450
+ + type(exc).__name__ + ')') from None
1451
+
1452
+ if allow_infobase_existing_registration:
1453
+ @server.tool(description='MUTATION: register an already existing PostgreSQL infobase; '
1454
+ 'never create the database. Confirm cluster UUID:name:database name '
1455
+ 'exactly; guarded client verifies full list and info read-back.')
1456
+ async def rac_register_existing_infobase(
1457
+ cluster_id: StrictStr, name: StrictStr, db_name: StrictStr, db_server: StrictStr,
1458
+ db_user: StrictStr, locale: StrictStr, confirm_cluster_name_db: StrictStr) -> dict:
1459
+ try:
1460
+ cluster = UUID(cluster_id)
1461
+ except (TypeError, ValueError, AttributeError):
1462
+ raise ValueError('Invalid cluster UUID') from None
1463
+ if str(cluster) != cluster_id:
1464
+ raise ValueError('Invalid cluster UUID')
1465
+ for field in (name, db_name, db_server, db_user, locale):
1466
+ if (type(field) is not str or not field or '\x00' in field
1467
+ or len(field.encode('utf-8')) > 255):
1468
+ raise ValueError('Invalid infobase registration parameters')
1469
+ if confirm_cluster_name_db != f'{cluster}:{name}:{db_name}':
1470
+ raise ValueError('Confirmation must match cluster UUID:name:database name exactly')
1471
+ try:
1472
+ async with service.factory() as client:
1473
+ return ReadService._public_infobase_info(await client.create_infobase(
1474
+ cluster, name=name, db_name=db_name, db_server=db_server,
1475
+ db_user=db_user, locale=locale, create_database=False))
1476
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1477
+ raise ValueError('RAC existing infobase registration failed ('
1478
+ + type(exc).__name__ + ')') from None
1479
+
1480
+ if allow_infobase_registration_drop:
1481
+ @server.tool(description='DESTRUCTIVE: remove an infobase registration only; never drop or '
1482
+ 'clear the underlying database. Confirm cluster UUID:infobase UUID '
1483
+ 'exactly; guarded client verifies the complete registration list.')
1484
+ async def rac_drop_infobase_registration(
1485
+ cluster_id: StrictStr, infobase_id: StrictStr,
1486
+ confirm_cluster_infobase: StrictStr) -> list[dict]:
1487
+ try:
1488
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1489
+ except (TypeError, ValueError, AttributeError):
1490
+ raise ValueError('Invalid cluster or infobase UUID') from None
1491
+ if str(cluster) != cluster_id or str(infobase) != infobase_id:
1492
+ raise ValueError('Invalid cluster or infobase UUID')
1493
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1494
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1495
+ try:
1496
+ async with service.factory() as client:
1497
+ return await client.drop_infobase(cluster, infobase, drop_database=False,
1498
+ clear_database=False)
1499
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1500
+ raise ValueError('RAC infobase registration drop failed ('
1501
+ + type(exc).__name__ + ')') from None
1502
+
1503
+ if allow_infobase_summary_description_update:
1504
+ @server.tool(description='MUTATION: set infobase summary description (empty string '
1505
+ 'clears). Confirm cluster UUID:infobase UUID exactly; guarded '
1506
+ 'client checks the complete infobase list.')
1507
+ async def rac_update_infobase_summary_description(
1508
+ cluster_id: StrictStr, infobase_id: StrictStr, description: StrictStr,
1509
+ confirm_cluster_infobase: StrictStr) -> dict:
1510
+ try:
1511
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1512
+ except (TypeError, ValueError, AttributeError):
1513
+ raise ValueError('Invalid cluster or infobase UUID') from None
1514
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1515
+ or type(description) is not str or '\x00' in description
1516
+ or len(description.encode('utf-8')) > 1024):
1517
+ raise ValueError('Invalid infobase summary description')
1518
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1519
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1520
+ try:
1521
+ async with service.factory() as client:
1522
+ return ReadService._public_infobase_info(
1523
+ await client.update_infobase_summary_description(cluster, infobase, description))
1524
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1525
+ raise ValueError('RAC infobase summary description update failed ('
1526
+ + type(exc).__name__ + ')') from None
1527
+
1528
+ if allow_infobase_description_update:
1529
+ @server.tool(description='MUTATION: set infobase description (empty string clears). '
1530
+ 'Confirm cluster UUID:infobase UUID exactly; guarded client '
1531
+ 'checks complete info and infobase list.')
1532
+ async def rac_update_infobase_description(
1533
+ cluster_id: StrictStr, infobase_id: StrictStr, description: StrictStr,
1534
+ confirm_cluster_infobase: StrictStr) -> dict:
1535
+ try:
1536
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1537
+ except (TypeError, ValueError, AttributeError):
1538
+ raise ValueError('Invalid cluster or infobase UUID') from None
1539
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1540
+ or type(description) is not str or '\x00' in description
1541
+ or len(description.encode('utf-8')) > 1024):
1542
+ raise ValueError('Invalid infobase description')
1543
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1544
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1545
+ try:
1546
+ async with service.factory() as client:
1547
+ return ReadService._public_infobase_info(
1548
+ await client.update_infobase_description(cluster, infobase, description))
1549
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1550
+ raise ValueError('RAC infobase description update failed ('
1551
+ + type(exc).__name__ + ')') from None
1552
+
1553
+ if allow_infobase_scheduled_jobs_deny_update:
1554
+ @server.tool(description='MUTATION: deny or allow scheduled jobs for an infobase. '
1555
+ 'Confirm cluster UUID:infobase UUID exactly; guarded client '
1556
+ 'checks complete info and registration list.')
1557
+ async def rac_update_infobase_scheduled_jobs_deny(
1558
+ cluster_id: StrictStr, infobase_id: StrictStr, denied: StrictBool,
1559
+ confirm_cluster_infobase: StrictStr) -> dict:
1560
+ try:
1561
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1562
+ except (TypeError, ValueError, AttributeError):
1563
+ raise ValueError('Invalid cluster or infobase UUID') from None
1564
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1565
+ or type(denied) is not bool):
1566
+ raise ValueError('Invalid infobase scheduled jobs deny flag')
1567
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1568
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1569
+ try:
1570
+ async with service.factory() as client:
1571
+ return ReadService._public_infobase_info(
1572
+ await client.update_infobase_scheduled_jobs_deny(cluster, infobase, denied))
1573
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1574
+ raise ValueError('RAC infobase scheduled jobs deny update failed ('
1575
+ + type(exc).__name__ + ')') from None
1576
+
1577
+ if allow_infobase_sessions_deny_update:
1578
+ @server.tool(description='MUTATION: deny or allow sessions for an infobase. '
1579
+ 'Confirm cluster UUID:infobase UUID exactly; guarded client '
1580
+ 'checks complete info and registration list.')
1581
+ async def rac_update_infobase_sessions_deny(
1582
+ cluster_id: StrictStr, infobase_id: StrictStr, denied: StrictBool,
1583
+ confirm_cluster_infobase: StrictStr) -> dict:
1584
+ try:
1585
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1586
+ except (TypeError, ValueError, AttributeError):
1587
+ raise ValueError('Invalid cluster or infobase UUID') from None
1588
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1589
+ or type(denied) is not bool):
1590
+ raise ValueError('Invalid infobase sessions deny flag')
1591
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1592
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1593
+ try:
1594
+ async with service.factory() as client:
1595
+ return ReadService._public_infobase_info(
1596
+ await client.update_infobase_sessions_deny(cluster, infobase, denied))
1597
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1598
+ raise ValueError('RAC infobase sessions deny update failed ('
1599
+ + type(exc).__name__ + ')') from None
1600
+
1601
+ if allow_infobase_license_distribution_update:
1602
+ @server.tool(description='MUTATION: allow or deny infobase license distribution. '
1603
+ 'Confirm cluster UUID:infobase UUID exactly; guarded client '
1604
+ 'checks complete info and registration list.')
1605
+ async def rac_update_infobase_license_distribution(
1606
+ cluster_id: StrictStr, infobase_id: StrictStr, distribution: StrictStr,
1607
+ confirm_cluster_infobase: StrictStr) -> dict:
1608
+ try:
1609
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1610
+ except (TypeError, ValueError, AttributeError):
1611
+ raise ValueError('Invalid cluster or infobase UUID') from None
1612
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1613
+ or type(distribution) is not str or distribution not in ('allow', 'deny')):
1614
+ raise ValueError('Invalid infobase license distribution')
1615
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1616
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1617
+ try:
1618
+ async with service.factory() as client:
1619
+ return ReadService._public_infobase_info(
1620
+ await client.update_infobase_license_distribution(cluster, infobase, distribution))
1621
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1622
+ raise ValueError('RAC infobase license distribution update failed ('
1623
+ + type(exc).__name__ + ')') from None
1624
+
1625
+ if allow_infobase_denied_message_update:
1626
+ @server.tool(description='MUTATION: set infobase denied message (empty string clears). '
1627
+ 'Confirm cluster UUID:infobase UUID exactly; guarded client '
1628
+ 'checks complete info and registration list.')
1629
+ async def rac_update_infobase_denied_message(
1630
+ cluster_id: StrictStr, infobase_id: StrictStr, message: StrictStr,
1631
+ confirm_cluster_infobase: StrictStr) -> dict:
1632
+ try:
1633
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1634
+ except (TypeError, ValueError, AttributeError):
1635
+ raise ValueError('Invalid cluster or infobase UUID') from None
1636
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1637
+ or type(message) is not str or '\x00' in message):
1638
+ raise ValueError('Invalid infobase denied message')
1639
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1640
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1641
+ try:
1642
+ async with service.factory() as client:
1643
+ return ReadService._public_infobase_info(
1644
+ await client.update_infobase_denied_message(cluster, infobase, message))
1645
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1646
+ raise ValueError('RAC infobase denied message update failed ('
1647
+ + type(exc).__name__ + ')') from None
1648
+
1649
+ if allow_infobase_reserve_working_processes_update:
1650
+ @server.tool(description='MUTATION: toggle infobase reserve working processes. '
1651
+ 'Confirm cluster UUID:infobase UUID exactly; guarded client '
1652
+ 'checks complete info and registration list.')
1653
+ async def rac_update_infobase_reserve_working_processes(
1654
+ cluster_id: StrictStr, infobase_id: StrictStr, reserve: StrictBool,
1655
+ confirm_cluster_infobase: StrictStr) -> dict:
1656
+ try:
1657
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1658
+ except (TypeError, ValueError, AttributeError):
1659
+ raise ValueError('Invalid cluster or infobase UUID') from None
1660
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1661
+ or type(reserve) is not bool):
1662
+ raise ValueError('Invalid infobase reserve working processes flag')
1663
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1664
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1665
+ try:
1666
+ async with service.factory() as client:
1667
+ return ReadService._public_infobase_info(
1668
+ await client.update_infobase_reserve_working_processes(cluster, infobase, reserve))
1669
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1670
+ raise ValueError('RAC infobase reserve working processes update failed ('
1671
+ + type(exc).__name__ + ')') from None
1672
+
1673
+ if allow_infobase_external_session_manager_update:
1674
+ @server.tool(description='MUTATION: require or release an external session manager '
1675
+ 'for an infobase. Confirm cluster UUID:infobase UUID exactly; '
1676
+ 'guarded client checks complete info and registration list.')
1677
+ async def rac_update_infobase_external_session_manager_required(
1678
+ cluster_id: StrictStr, infobase_id: StrictStr, required: StrictBool,
1679
+ confirm_cluster_infobase: StrictStr) -> dict:
1680
+ try:
1681
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1682
+ except (TypeError, ValueError, AttributeError):
1683
+ raise ValueError('Invalid cluster or infobase UUID') from None
1684
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1685
+ or type(required) is not bool):
1686
+ raise ValueError('Invalid infobase external session manager flag')
1687
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1688
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1689
+ try:
1690
+ async with service.factory() as client:
1691
+ return ReadService._public_infobase_info(
1692
+ await client.update_infobase_external_session_manager_required(cluster, infobase, required))
1693
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1694
+ raise ValueError('RAC infobase external session manager update failed ('
1695
+ + type(exc).__name__ + ')') from None
1696
+
1697
+ if allow_infobase_local_speech_update:
1698
+ @server.tool(description='MUTATION: disable or enable local speech-to-text for an '
1699
+ 'infobase. Confirm cluster UUID:infobase UUID exactly; guarded '
1700
+ 'client checks complete info and registration list.')
1701
+ async def rac_update_infobase_disable_local_speech_to_text(
1702
+ cluster_id: StrictStr, infobase_id: StrictStr, disabled: StrictBool,
1703
+ confirm_cluster_infobase: StrictStr) -> dict:
1704
+ try:
1705
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1706
+ except (TypeError, ValueError, AttributeError):
1707
+ raise ValueError('Invalid cluster or infobase UUID') from None
1708
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1709
+ or type(disabled) is not bool):
1710
+ raise ValueError('Invalid infobase local speech-to-text flag')
1711
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1712
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1713
+ try:
1714
+ async with service.factory() as client:
1715
+ return ReadService._public_infobase_info(
1716
+ await client.update_infobase_disable_local_speech_to_text(cluster, infobase, disabled))
1717
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1718
+ raise ValueError('RAC infobase local speech-to-text update failed ('
1719
+ + type(exc).__name__ + ')') from None
1720
+
1721
+ if allow_infobase_configuration_unload_delay_update:
1722
+ @server.tool(description='MUTATION: set configuration unload delay by a working '
1723
+ 'process without active users. Confirm cluster UUID:infobase UUID '
1724
+ 'exactly; guarded client checks complete info and registration list.')
1725
+ async def rac_update_infobase_configuration_unload_delay(
1726
+ cluster_id: StrictStr, infobase_id: StrictStr, delay: StrictInt,
1727
+ confirm_cluster_infobase: StrictStr) -> dict:
1728
+ try:
1729
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1730
+ except (TypeError, ValueError, AttributeError):
1731
+ raise ValueError('Invalid cluster or infobase UUID') from None
1732
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1733
+ or type(delay) is not int or not 0 <= delay <= 0xffffffff):
1734
+ raise ValueError('Invalid infobase configuration unload delay')
1735
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1736
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1737
+ try:
1738
+ async with service.factory() as client:
1739
+ return ReadService._public_infobase_info(
1740
+ await client.update_infobase_configuration_unload_delay(cluster, infobase, delay))
1741
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1742
+ raise ValueError('RAC infobase configuration unload delay update failed ('
1743
+ + type(exc).__name__ + ')') from None
1744
+
1745
+ if allow_infobase_minimum_scheduled_jobs_period_update:
1746
+ @server.tool(description='MUTATION: set minimum scheduled-jobs start period without '
1747
+ 'active users. Confirm cluster UUID:infobase UUID exactly; '
1748
+ 'guarded client checks complete info and registration list.')
1749
+ async def rac_update_infobase_minimum_scheduled_jobs_start_period(
1750
+ cluster_id: StrictStr, infobase_id: StrictStr, period: StrictInt,
1751
+ confirm_cluster_infobase: StrictStr) -> dict:
1752
+ try:
1753
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1754
+ except (TypeError, ValueError, AttributeError):
1755
+ raise ValueError('Invalid cluster or infobase UUID') from None
1756
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1757
+ or type(period) is not int or not 0 <= period <= 0xffffffff):
1758
+ raise ValueError('Invalid infobase minimum scheduled jobs period')
1759
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1760
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1761
+ try:
1762
+ async with service.factory() as client:
1763
+ return ReadService._public_infobase_info(
1764
+ await client.update_infobase_minimum_scheduled_jobs_start_period(cluster, infobase, period))
1765
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1766
+ raise ValueError('RAC infobase minimum scheduled jobs period update failed ('
1767
+ + type(exc).__name__ + ')') from None
1768
+
1769
+ if allow_infobase_maximum_scheduled_jobs_shift_update:
1770
+ @server.tool(description='MUTATION: set maximum scheduled-jobs start shift without '
1771
+ 'active users. Confirm cluster UUID:infobase UUID exactly; '
1772
+ 'guarded client checks complete info and registration list.')
1773
+ async def rac_update_infobase_maximum_scheduled_jobs_start_shift(
1774
+ cluster_id: StrictStr, infobase_id: StrictStr, shift: StrictInt,
1775
+ confirm_cluster_infobase: StrictStr) -> dict:
1776
+ try:
1777
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1778
+ except (TypeError, ValueError, AttributeError):
1779
+ raise ValueError('Invalid cluster or infobase UUID') from None
1780
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1781
+ or type(shift) is not int or not 0 <= shift <= 255):
1782
+ raise ValueError('Invalid infobase maximum scheduled jobs shift')
1783
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1784
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1785
+ try:
1786
+ async with service.factory() as client:
1787
+ return ReadService._public_infobase_info(
1788
+ await client.update_infobase_maximum_scheduled_jobs_start_shift(cluster, infobase, shift))
1789
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1790
+ raise ValueError('RAC infobase maximum scheduled jobs shift update failed ('
1791
+ + type(exc).__name__ + ')') from None
1792
+
1793
+ if allow_infobase_safe_mode_security_profile_update:
1794
+ @server.tool(description='MUTATION: assign or clear an infobase safe-mode security '
1795
+ 'profile. Confirm cluster UUID:infobase UUID exactly; guarded '
1796
+ 'client checks complete infobase info and unchanged registration list.')
1797
+ async def rac_update_infobase_safe_mode_security_profile(
1798
+ cluster_id: StrictStr, infobase_id: StrictStr, name: StrictStr,
1799
+ confirm_cluster_infobase: StrictStr) -> dict:
1800
+ try:
1801
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1802
+ except (TypeError, ValueError, AttributeError):
1803
+ raise ValueError('Invalid cluster or infobase UUID') from None
1804
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1805
+ or type(name) is not str or '\x00' in name
1806
+ or len(name.encode('utf-8')) > 1024):
1807
+ raise ValueError('Invalid infobase safe mode security profile parameters')
1808
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1809
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1810
+ try:
1811
+ async with service.factory() as client:
1812
+ return ReadService._public_infobase_info(
1813
+ await client.update_infobase_safe_mode_security_profile(cluster, infobase, name))
1814
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1815
+ raise ValueError('RAC infobase safe mode security profile update failed ('
1816
+ + type(exc).__name__ + ')') from None
1817
+
1818
+ if allow_infobase_security_profile_update:
1819
+ @server.tool(description='MUTATION: assign or clear an infobase security profile. '
1820
+ 'Confirm cluster UUID:infobase UUID exactly; guarded client '
1821
+ 'checks complete infobase info and unchanged registration list.')
1822
+ async def rac_update_infobase_security_profile(cluster_id: StrictStr,
1823
+ infobase_id: StrictStr, name: StrictStr,
1824
+ confirm_cluster_infobase: StrictStr) -> dict:
1825
+ try:
1826
+ cluster, infobase = UUID(cluster_id), UUID(infobase_id)
1827
+ except (TypeError, ValueError, AttributeError):
1828
+ raise ValueError('Invalid cluster or infobase UUID') from None
1829
+ if (str(cluster) != cluster_id or str(infobase) != infobase_id
1830
+ or type(name) is not str or '\x00' in name
1831
+ or len(name.encode('utf-8')) > 1024):
1832
+ raise ValueError('Invalid infobase security profile parameters')
1833
+ if confirm_cluster_infobase != f'{cluster}:{infobase}':
1834
+ raise ValueError('Confirmation must match cluster UUID:infobase UUID exactly')
1835
+ try:
1836
+ async with service.factory() as client:
1837
+ return ReadService._public_infobase_info(
1838
+ await client.update_infobase_security_profile(cluster, infobase, name))
1839
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1840
+ raise ValueError('RAC infobase security profile update failed (' + type(exc).__name__ + ')') from None
1841
+
1842
+ if allow_profile_inet_update:
1843
+ @server.tool(description='MUTATION: update or insert a profile internet-resource ACL. '
1844
+ 'Confirm cluster UUID:profile name:resource name; guarded client '
1845
+ 'checks full internet-resource list after update.')
1846
+ async def rac_update_profile_inet(cluster_id: StrictStr, profile_name: StrictStr,
1847
+ inet_name: StrictStr,
1848
+ fields: dict[str, StrictStr | StrictInt],
1849
+ confirm_cluster_profile_inet: StrictStr) -> dict:
1850
+ try:
1851
+ cluster = UUID(cluster_id)
1852
+ except (TypeError, ValueError, AttributeError):
1853
+ raise ValueError('Invalid cluster UUID') from None
1854
+ if (str(cluster) != cluster_id or any(
1855
+ type(value) is not str or not value.strip() or '\x00' in value
1856
+ or len(value.encode('utf-8')) > 1024 for value in (profile_name, inet_name))
1857
+ or type(fields) is not dict or not fields
1858
+ or set(fields) - {'url', 'port', 'protocol', 'descr'}
1859
+ or any(type(value) is not int or not 0 <= value <= 65535
1860
+ if key == 'port' else type(value) is not str or '\x00' in value
1861
+ or len(value.encode('utf-8')) > 1024
1862
+ for key, value in fields.items())):
1863
+ raise ValueError('Invalid profile inet update parameters')
1864
+ if confirm_cluster_profile_inet != f'{cluster}:{profile_name}:{inet_name}':
1865
+ raise ValueError('Confirmation must match cluster:profile:inet exactly')
1866
+ try:
1867
+ async with service.factory() as client:
1868
+ return await client.update_profile_inet(cluster, profile_name, inet_name,
1869
+ **fields)
1870
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1871
+ raise ValueError('RAC profile inet update failed (' + type(exc).__name__ + ')') from None
1872
+
1873
+ if allow_profile_inet_remove:
1874
+ @server.tool(description='DESTRUCTIVE: remove a security profile internet-resource ACL. '
1875
+ 'Confirm cluster UUID:profile name:resource name exactly; '
1876
+ 'guarded client checks full inet ACL list after removal.')
1877
+ async def rac_remove_profile_inet(cluster_id: StrictStr, profile_name: StrictStr,
1878
+ inet_name: StrictStr,
1879
+ confirm_cluster_profile_inet: StrictStr) -> dict:
1880
+ try:
1881
+ cluster = UUID(cluster_id)
1882
+ except (TypeError, ValueError, AttributeError):
1883
+ raise ValueError('Invalid cluster UUID') from None
1884
+ if (str(cluster) != cluster_id or any(
1885
+ type(value) is not str or not value.strip() or '\x00' in value
1886
+ or len(value.encode('utf-8')) > 1024 for value in
1887
+ (profile_name, inet_name))):
1888
+ raise ValueError('Invalid profile inet removal parameters')
1889
+ if confirm_cluster_profile_inet != f'{cluster}:{profile_name}:{inet_name}':
1890
+ raise ValueError('Confirmation must match cluster:profile:inet exactly')
1891
+ try:
1892
+ async with service.factory() as client:
1893
+ await client.remove_profile_inet(cluster, profile_name, inet_name)
1894
+ return {'removed_profile_inet': inet_name}
1895
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1896
+ raise ValueError('RAC profile inet remove failed (' + type(exc).__name__ + ')') from None
1897
+
1898
+ if allow_profile_app_update:
1899
+ @server.tool(description='MUTATION: update or insert a profile application ACL. '
1900
+ 'Confirm cluster UUID:profile name:application name; guarded client '
1901
+ 'checks full application list after update.')
1902
+ async def rac_update_profile_app(cluster_id: StrictStr, profile_name: StrictStr,
1903
+ app_name: StrictStr, fields: dict[str, StrictStr],
1904
+ confirm_cluster_profile_app: StrictStr) -> dict:
1905
+ try:
1906
+ cluster = UUID(cluster_id)
1907
+ except (TypeError, ValueError, AttributeError):
1908
+ raise ValueError('Invalid cluster UUID') from None
1909
+ if (str(cluster) != cluster_id or any(
1910
+ type(value) is not str or not value.strip() or '\x00' in value
1911
+ or len(value.encode('utf-8')) > 1024 for value in (profile_name, app_name))
1912
+ or type(fields) is not dict or not fields
1913
+ or set(fields) - {'wild', 'descr'}
1914
+ or any(type(value) is not str or '\x00' in value
1915
+ or len(value.encode('utf-8')) > 1024 for value in fields.values())):
1916
+ raise ValueError('Invalid profile app update parameters')
1917
+ if confirm_cluster_profile_app != f'{cluster}:{profile_name}:{app_name}':
1918
+ raise ValueError('Confirmation must match cluster:profile:app exactly')
1919
+ try:
1920
+ async with service.factory() as client:
1921
+ return await client.update_profile_app(cluster, profile_name, app_name,
1922
+ **fields)
1923
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1924
+ raise ValueError('RAC profile app update failed (' + type(exc).__name__ + ')') from None
1925
+
1926
+ if allow_profile_app_remove:
1927
+ @server.tool(description='DESTRUCTIVE: remove a security profile application ACL. '
1928
+ 'Confirm cluster UUID:profile name:application name exactly; '
1929
+ 'guarded client checks full application ACL list after removal.')
1930
+ async def rac_remove_profile_app(cluster_id: StrictStr, profile_name: StrictStr,
1931
+ app_name: StrictStr,
1932
+ confirm_cluster_profile_app: StrictStr) -> dict:
1933
+ try:
1934
+ cluster = UUID(cluster_id)
1935
+ except (TypeError, ValueError, AttributeError):
1936
+ raise ValueError('Invalid cluster UUID') from None
1937
+ if (str(cluster) != cluster_id or any(
1938
+ type(value) is not str or not value.strip() or '\x00' in value
1939
+ or len(value.encode('utf-8')) > 1024 for value in
1940
+ (profile_name, app_name))):
1941
+ raise ValueError('Invalid profile application removal parameters')
1942
+ if confirm_cluster_profile_app != f'{cluster}:{profile_name}:{app_name}':
1943
+ raise ValueError('Confirmation must match cluster:profile:app exactly')
1944
+ try:
1945
+ async with service.factory() as client:
1946
+ await client.remove_profile_app(cluster, profile_name, app_name)
1947
+ return {'removed_profile_app': app_name}
1948
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1949
+ raise ValueError('RAC profile app remove failed (' + type(exc).__name__ + ')') from None
1950
+
1951
+ if allow_profile_module_update:
1952
+ @server.tool(description='MUTATION: update or insert a profile module ACL. '
1953
+ 'Confirm cluster UUID:profile name:module name; guarded client '
1954
+ 'checks full module list after update.')
1955
+ async def rac_update_profile_module(cluster_id: StrictStr, profile_name: StrictStr,
1956
+ module_name: StrictStr, fields: dict[str, StrictStr],
1957
+ confirm_cluster_profile_module: StrictStr) -> dict:
1958
+ try:
1959
+ cluster = UUID(cluster_id)
1960
+ except (TypeError, ValueError, AttributeError):
1961
+ raise ValueError('Invalid cluster UUID') from None
1962
+ if (str(cluster) != cluster_id or any(
1963
+ type(value) is not str or not value.strip() or '\x00' in value
1964
+ or len(value.encode('utf-8')) > 1024 for value in (profile_name, module_name))
1965
+ or type(fields) is not dict or not fields
1966
+ or set(fields) - {'hash_value', 'descr'}
1967
+ or any(type(value) is not str or '\x00' in value
1968
+ or len(value.encode('utf-8')) > 1024 for value in fields.values())):
1969
+ raise ValueError('Invalid profile module update parameters')
1970
+ if confirm_cluster_profile_module != f'{cluster}:{profile_name}:{module_name}':
1971
+ raise ValueError('Confirmation must match cluster:profile:module exactly')
1972
+ try:
1973
+ async with service.factory() as client:
1974
+ return await client.update_profile_module(cluster, profile_name, module_name,
1975
+ **fields)
1976
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
1977
+ raise ValueError('RAC profile module update failed (' + type(exc).__name__ + ')') from None
1978
+
1979
+ if allow_profile_module_remove:
1980
+ @server.tool(description='DESTRUCTIVE: remove a security profile module ACL. '
1981
+ 'Confirm cluster UUID:profile name:module name exactly; '
1982
+ 'guarded client checks full module ACL list after removal.')
1983
+ async def rac_remove_profile_module(cluster_id: StrictStr, profile_name: StrictStr,
1984
+ module_name: StrictStr,
1985
+ confirm_cluster_profile_module: StrictStr) -> dict:
1986
+ try:
1987
+ cluster = UUID(cluster_id)
1988
+ except (TypeError, ValueError, AttributeError):
1989
+ raise ValueError('Invalid cluster UUID') from None
1990
+ if (str(cluster) != cluster_id or any(
1991
+ type(value) is not str or not value.strip() or '\x00' in value
1992
+ or len(value.encode('utf-8')) > 1024 for value in
1993
+ (profile_name, module_name))):
1994
+ raise ValueError('Invalid profile module removal parameters')
1995
+ if confirm_cluster_profile_module != f'{cluster}:{profile_name}:{module_name}':
1996
+ raise ValueError('Confirmation must match cluster:profile:module exactly')
1997
+ try:
1998
+ async with service.factory() as client:
1999
+ await client.remove_profile_module(cluster, profile_name, module_name)
2000
+ return {'removed_profile_module': module_name}
2001
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2002
+ raise ValueError('RAC profile module remove failed (' + type(exc).__name__ + ')') from None
2003
+
2004
+ if allow_profile_addin_update:
2005
+ @server.tool(description='MUTATION: update or insert a profile add-in ACL. '
2006
+ 'Confirm cluster UUID:profile name:add-in name; guarded client '
2007
+ 'checks full add-in list after update.')
2008
+ async def rac_update_profile_addin(cluster_id: StrictStr, profile_name: StrictStr,
2009
+ addin_name: StrictStr, fields: dict[str, StrictStr],
2010
+ confirm_cluster_profile_addin: StrictStr) -> dict:
2011
+ try:
2012
+ cluster = UUID(cluster_id)
2013
+ except (TypeError, ValueError, AttributeError):
2014
+ raise ValueError('Invalid cluster UUID') from None
2015
+ if (str(cluster) != cluster_id or any(
2016
+ type(value) is not str or not value.strip() or '\x00' in value
2017
+ or len(value.encode('utf-8')) > 1024 for value in (profile_name, addin_name))
2018
+ or type(fields) is not dict or not fields
2019
+ or set(fields) - {'hash_value', 'descr'}
2020
+ or any(type(value) is not str or '\x00' in value
2021
+ or len(value.encode('utf-8')) > 1024 for value in fields.values())):
2022
+ raise ValueError('Invalid profile addin update parameters')
2023
+ if confirm_cluster_profile_addin != f'{cluster}:{profile_name}:{addin_name}':
2024
+ raise ValueError('Confirmation must match cluster:profile:addin exactly')
2025
+ try:
2026
+ async with service.factory() as client:
2027
+ return await client.update_profile_addin(cluster, profile_name, addin_name,
2028
+ **fields)
2029
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2030
+ raise ValueError('RAC profile addin update failed (' + type(exc).__name__ + ')') from None
2031
+
2032
+ if allow_profile_addin_remove:
2033
+ @server.tool(description='DESTRUCTIVE: remove a security profile add-in ACL. '
2034
+ 'Confirm cluster UUID:profile name:add-in name exactly; '
2035
+ 'guarded client checks full add-in ACL list after removal.')
2036
+ async def rac_remove_profile_addin(cluster_id: StrictStr, profile_name: StrictStr,
2037
+ addin_name: StrictStr,
2038
+ confirm_cluster_profile_addin: StrictStr) -> dict:
2039
+ try:
2040
+ cluster = UUID(cluster_id)
2041
+ except (TypeError, ValueError, AttributeError):
2042
+ raise ValueError('Invalid cluster UUID') from None
2043
+ if (str(cluster) != cluster_id or any(
2044
+ type(value) is not str or not value.strip() or '\x00' in value
2045
+ or len(value.encode('utf-8')) > 1024 for value in
2046
+ (profile_name, addin_name))):
2047
+ raise ValueError('Invalid profile addin removal parameters')
2048
+ if confirm_cluster_profile_addin != f'{cluster}:{profile_name}:{addin_name}':
2049
+ raise ValueError('Confirmation must match cluster:profile:addin exactly')
2050
+ try:
2051
+ async with service.factory() as client:
2052
+ await client.remove_profile_addin(cluster, profile_name, addin_name)
2053
+ return {'removed_profile_addin': addin_name}
2054
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2055
+ raise ValueError('RAC profile addin remove failed (' + type(exc).__name__ + ')') from None
2056
+
2057
+ if allow_profile_com_update:
2058
+ @server.tool(description='MUTATION: update or insert a profile COM-class ACL. '
2059
+ 'Confirm cluster UUID:profile name:class name; guarded client '
2060
+ 'checks complete COM ACL list after update.')
2061
+ async def rac_update_profile_com_class(cluster_id: StrictStr, profile_name: StrictStr,
2062
+ class_name: StrictStr, fields: dict[str, StrictStr],
2063
+ confirm_cluster_profile_class: StrictStr) -> dict:
2064
+ try:
2065
+ cluster = UUID(cluster_id)
2066
+ except (TypeError, ValueError, AttributeError):
2067
+ raise ValueError('Invalid cluster UUID') from None
2068
+ allowed = {'file_name', 'class_id', 'host', 'descr'}
2069
+ if (str(cluster) != cluster_id or any(
2070
+ type(value) is not str or not value.strip() or '\x00' in value
2071
+ or len(value.encode('utf-8')) > 1024 for value in (profile_name, class_name))
2072
+ or type(fields) is not dict or not fields or set(fields) - allowed
2073
+ or any(type(value) is not str or '\x00' in value
2074
+ or len(value.encode('utf-8')) > 1024 for value in fields.values())):
2075
+ raise ValueError('Invalid profile COM update parameters')
2076
+ if confirm_cluster_profile_class != f'{cluster}:{profile_name}:{class_name}':
2077
+ raise ValueError('Confirmation must match cluster:profile:class exactly')
2078
+ try:
2079
+ async with service.factory() as client:
2080
+ return await client.update_profile_com_class(cluster, profile_name, class_name,
2081
+ **fields)
2082
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2083
+ raise ValueError('RAC profile COM update failed (' + type(exc).__name__ + ')') from None
2084
+
2085
+ if allow_profile_com_remove:
2086
+ @server.tool(description='DESTRUCTIVE: remove a security profile COM-class ACL. '
2087
+ 'Confirm cluster UUID:profile name:class name exactly; guarded '
2088
+ 'client checks full COM-class ACL list after removal.')
2089
+ async def rac_remove_profile_com_class(cluster_id: StrictStr, profile_name: StrictStr,
2090
+ class_name: StrictStr,
2091
+ confirm_cluster_profile_class: StrictStr) -> dict:
2092
+ try:
2093
+ cluster = UUID(cluster_id)
2094
+ except (TypeError, ValueError, AttributeError):
2095
+ raise ValueError('Invalid cluster UUID') from None
2096
+ if (str(cluster) != cluster_id or any(
2097
+ type(value) is not str or not value.strip() or '\x00' in value
2098
+ or len(value.encode('utf-8')) > 1024 for value in
2099
+ (profile_name, class_name))):
2100
+ raise ValueError('Invalid profile COM removal parameters')
2101
+ if confirm_cluster_profile_class != f'{cluster}:{profile_name}:{class_name}':
2102
+ raise ValueError('Confirmation must match cluster:profile:class exactly')
2103
+ try:
2104
+ async with service.factory() as client:
2105
+ await client.remove_profile_com_class(cluster, profile_name, class_name)
2106
+ return {'removed_profile_com_class': class_name}
2107
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2108
+ raise ValueError('RAC profile COM remove failed (' + type(exc).__name__ + ')') from None
2109
+
2110
+ if allow_profile_directory_update:
2111
+ @server.tool(description='MUTATION: update or insert a profile directory ACL. '
2112
+ 'Confirm cluster UUID:profile name:alias exactly; guarded client '
2113
+ 'checks the full directory ACL list after update.')
2114
+ async def rac_update_profile_directory(cluster_id: StrictStr, profile_name: StrictStr,
2115
+ alias: StrictStr,
2116
+ fields: dict[str, StrictBool | StrictStr],
2117
+ confirm_cluster_profile_alias: StrictStr) -> dict:
2118
+ try:
2119
+ cluster = UUID(cluster_id)
2120
+ except (TypeError, ValueError, AttributeError):
2121
+ raise ValueError('Invalid cluster UUID') from None
2122
+ text_fields = {'physical_path', 'descr'}
2123
+ bool_fields = {'allowed_read', 'allowed_write'}
2124
+ if (str(cluster) != cluster_id or any(
2125
+ type(value) is not str or not value.strip() or '\x00' in value
2126
+ or len(value.encode('utf-8')) > 1024 for value in (profile_name, alias))
2127
+ or type(fields) is not dict or not fields
2128
+ or set(fields) - (text_fields | bool_fields)
2129
+ or any(type(value) is not str or '\x00' in value
2130
+ or len(value.encode('utf-8')) > 1024
2131
+ for key, value in fields.items() if key in text_fields)
2132
+ or any(type(value) is not bool for key, value in fields.items()
2133
+ if key in bool_fields)):
2134
+ raise ValueError('Invalid profile directory update parameters')
2135
+ if confirm_cluster_profile_alias != f'{cluster}:{profile_name}:{alias}':
2136
+ raise ValueError('Confirmation must match cluster:profile:alias exactly')
2137
+ try:
2138
+ async with service.factory() as client:
2139
+ return await client.update_profile_directory(cluster, profile_name, alias,
2140
+ **fields)
2141
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2142
+ raise ValueError('RAC profile directory update failed (' + type(exc).__name__ + ')') from None
2143
+
2144
+ if allow_profile_directory_remove:
2145
+ @server.tool(description='DESTRUCTIVE: remove a security profile directory ACL. '
2146
+ 'Confirm cluster UUID:profile name:directory alias exactly; '
2147
+ 'guarded client checks full directory ACL list after removal.')
2148
+ async def rac_remove_profile_directory(cluster_id: StrictStr, profile_name: StrictStr,
2149
+ alias: StrictStr,
2150
+ confirm_cluster_profile_alias: StrictStr) -> dict:
2151
+ try:
2152
+ cluster = UUID(cluster_id)
2153
+ except (TypeError, ValueError, AttributeError):
2154
+ raise ValueError('Invalid cluster UUID') from None
2155
+ if (str(cluster) != cluster_id or any(
2156
+ type(value) is not str or not value.strip() or '\x00' in value
2157
+ or len(value.encode('utf-8')) > 1024 for value in (profile_name, alias))):
2158
+ raise ValueError('Invalid profile directory removal parameters')
2159
+ if confirm_cluster_profile_alias != f'{cluster}:{profile_name}:{alias}':
2160
+ raise ValueError('Confirmation must match cluster:profile:alias exactly')
2161
+ try:
2162
+ async with service.factory() as client:
2163
+ await client.remove_profile_directory(cluster, profile_name, alias)
2164
+ return {'removed_profile_directory': alias}
2165
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2166
+ raise ValueError('RAC profile directory remove failed (' + type(exc).__name__ + ')') from None
2167
+
2168
+ if allow_profile_role_fields_update:
2169
+ @server.tool(description='MUTATION: update selected profile role/module fields. '
2170
+ 'Confirm cluster UUID:profile name; guarded client checks the '
2171
+ 'full list. Underlying update creates a profile if absent.')
2172
+ async def rac_update_profile_role_fields(cluster_id: StrictStr, profile_name: StrictStr,
2173
+ fields: dict[str, StrictStr],
2174
+ confirm_cluster_name: StrictStr) -> dict:
2175
+ try:
2176
+ cluster = UUID(cluster_id)
2177
+ except (TypeError, ValueError, AttributeError):
2178
+ raise ValueError('Invalid cluster UUID') from None
2179
+ allowed = {'right_extension_definition_roles', 'privileged_mode_roles',
2180
+ 'modules_available_for_extension', 'modules_not_available_for_extension'}
2181
+ if (str(cluster) != cluster_id or type(profile_name) is not str
2182
+ or not profile_name.strip() or '\x00' in profile_name
2183
+ or len(profile_name.encode('utf-8')) > 1024 or type(fields) is not dict
2184
+ or not fields or set(fields) - allowed
2185
+ or any(type(value) is not str or '\x00' in value
2186
+ or len(value.encode('utf-8')) > 1024 for value in fields.values())):
2187
+ raise ValueError('Invalid profile role fields update parameters')
2188
+ if confirm_cluster_name != f'{cluster}:{profile_name}':
2189
+ raise ValueError('Confirmation must match cluster UUID:profile name exactly')
2190
+ try:
2191
+ async with service.factory() as client:
2192
+ return await client.update_profile(cluster, profile_name, **fields)
2193
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2194
+ raise ValueError('RAC profile role fields update failed (' + type(exc).__name__ + ')') from None
2195
+
2196
+ if allow_profile_access_flags_update:
2197
+ @server.tool(description='MUTATION: update selected security profile access flags. '
2198
+ 'Confirm cluster UUID:profile name; guarded client checks full '
2199
+ 'profile list. Underlying update creates a profile if absent.')
2200
+ async def rac_update_profile_access_flags(cluster_id: StrictStr, profile_name: StrictStr,
2201
+ flags: dict[str, StrictBool],
2202
+ confirm_cluster_name: StrictStr) -> dict:
2203
+ try:
2204
+ cluster = UUID(cluster_id)
2205
+ except (TypeError, ValueError, AttributeError):
2206
+ raise ValueError('Invalid cluster UUID') from None
2207
+ allowed = {'priv', 'full_privileged_mode', 'crypto', 'directory_access', 'com_access', 'addin_access',
2208
+ 'module_access', 'app_access', 'inet_access', 'right_extension',
2209
+ 'all_modules_extension'}
2210
+ if (str(cluster) != cluster_id or type(profile_name) is not str
2211
+ or not profile_name.strip() or '\x00' in profile_name
2212
+ or len(profile_name.encode('utf-8')) > 1024 or type(flags) is not dict
2213
+ or not flags or set(flags) - allowed
2214
+ or 'priv' in flags and 'full_privileged_mode' in flags
2215
+ or any(type(value) is not bool for value in flags.values())):
2216
+ raise ValueError('Invalid profile access flags update parameters')
2217
+ if confirm_cluster_name != f'{cluster}:{profile_name}':
2218
+ raise ValueError('Confirmation must match cluster UUID:profile name exactly')
2219
+ try:
2220
+ async with service.factory() as client:
2221
+ return await client.update_profile(cluster, profile_name, **flags)
2222
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2223
+ raise ValueError('RAC profile access flags update failed (' + type(exc).__name__ + ')') from None
2224
+
2225
+ if allow_profile_config_update:
2226
+ @server.tool(description='MUTATION: change security profile configuration access flag. '
2227
+ 'Confirm cluster UUID:profile name; guarded client verifies the '
2228
+ 'full profile list. Underlying update creates a profile if absent.')
2229
+ async def rac_update_profile_config(cluster_id: StrictStr, profile_name: StrictStr,
2230
+ enabled: StrictBool, confirm_cluster_name: StrictStr) -> dict:
2231
+ try:
2232
+ cluster = UUID(cluster_id)
2233
+ except (TypeError, ValueError, AttributeError):
2234
+ raise ValueError('Invalid cluster UUID') from None
2235
+ if (str(cluster) != cluster_id or type(profile_name) is not str
2236
+ or not profile_name.strip() or '\x00' in profile_name
2237
+ or len(profile_name.encode('utf-8')) > 1024 or type(enabled) is not bool):
2238
+ raise ValueError('Invalid profile config update parameters')
2239
+ if confirm_cluster_name != f'{cluster}:{profile_name}':
2240
+ raise ValueError('Confirmation must match cluster UUID:profile name exactly')
2241
+ try:
2242
+ async with service.factory() as client:
2243
+ return await client.update_profile(cluster, profile_name, config=enabled)
2244
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2245
+ raise ValueError('RAC profile config update failed (' + type(exc).__name__ + ')') from None
2246
+
2247
+ if allow_profile_description_update:
2248
+ @server.tool(description='MUTATION: update or clear security profile description. '
2249
+ 'Confirm cluster UUID:profile name; guarded client verifies '
2250
+ 'the entire profile list after update. Creates if absent.')
2251
+ async def rac_update_profile_description(cluster_id: StrictStr, profile_name: StrictStr,
2252
+ descr: StrictStr, confirm_cluster_name: StrictStr) -> dict:
2253
+ try:
2254
+ cluster = UUID(cluster_id)
2255
+ except (TypeError, ValueError, AttributeError):
2256
+ raise ValueError('Invalid cluster UUID') from None
2257
+ if (str(cluster) != cluster_id or type(profile_name) is not str
2258
+ or not profile_name.strip() or '\x00' in profile_name
2259
+ or len(profile_name.encode('utf-8')) > 1024 or type(descr) is not str
2260
+ or '\x00' in descr or len(descr.encode('utf-8')) > 1024):
2261
+ raise ValueError('Invalid profile description update parameters')
2262
+ if confirm_cluster_name != f'{cluster}:{profile_name}':
2263
+ raise ValueError('Confirmation must match cluster UUID:profile name exactly')
2264
+ try:
2265
+ async with service.factory() as client:
2266
+ return await client.update_profile(cluster, profile_name, descr=descr)
2267
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2268
+ raise ValueError('RAC profile description update failed (' + type(exc).__name__ + ')') from None
2269
+
2270
+ if allow_profile_remove:
2271
+ @server.tool(description='DESTRUCTIVE: remove one security profile. Confirm exact '
2272
+ 'cluster UUID:profile name; guarded client verifies its absence '
2273
+ 'and all other profiles unchanged via complete list read-back.')
2274
+ async def rac_remove_profile(cluster_id: StrictStr, profile_name: StrictStr,
2275
+ confirm_cluster_name: StrictStr) -> dict:
2276
+ try:
2277
+ cluster = UUID(cluster_id)
2278
+ except (TypeError, ValueError, AttributeError):
2279
+ raise ValueError('Invalid cluster UUID') from None
2280
+ if (str(cluster) != cluster_id or type(profile_name) is not str
2281
+ or not profile_name.strip() or '\x00' in profile_name
2282
+ or len(profile_name.encode('utf-8')) > 1024):
2283
+ raise ValueError('Invalid profile removal parameters')
2284
+ if confirm_cluster_name != f'{cluster}:{profile_name}':
2285
+ raise ValueError('Confirmation must match cluster UUID:profile name exactly')
2286
+ try:
2287
+ async with service.factory() as client:
2288
+ await client.remove_profile(cluster, profile_name)
2289
+ return {'removed_profile': profile_name}
2290
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2291
+ raise ValueError('RAC profile remove failed (' + type(exc).__name__ + ')') from None
2292
+
2293
+ if allow_service_setting_insert:
2294
+ @server.tool(description='MUTATION: insert a service setting. Confirm exact '
2295
+ 'cluster UUID:working server UUID:service name:infobase name; '
2296
+ 'guarded client checks the complete settings list after insert.')
2297
+ async def rac_insert_service_setting(cluster_id: StrictStr, server_id: StrictStr,
2298
+ service_name: StrictStr, infobase_name: StrictStr,
2299
+ service_data_dir: StrictStr,
2300
+ confirm_cluster_server_service_infobase: StrictStr) -> dict:
2301
+ try:
2302
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
2303
+ except (TypeError, ValueError, AttributeError):
2304
+ raise ValueError('Invalid cluster or working server UUID') from None
2305
+ if str(cluster) != cluster_id or str(working_server) != server_id:
2306
+ raise ValueError('UUIDs must use canonical form')
2307
+ for key, value in (('service name', service_name), ('infobase name', infobase_name),
2308
+ ('service data dir', service_data_dir)):
2309
+ if (type(value) is not str or '\x00' in value or len(value.encode('utf-8')) > 1024
2310
+ or key == 'service name' and not value.strip()):
2311
+ raise ValueError('Invalid ' + key)
2312
+ if confirm_cluster_server_service_infobase != (
2313
+ f'{cluster}:{working_server}:{service_name}:{infobase_name}'):
2314
+ raise ValueError('Confirmation must match cluster:server:service:infobase exactly')
2315
+ try:
2316
+ async with service.factory() as client:
2317
+ return await client.insert_service_setting(
2318
+ cluster, working_server, service_name,
2319
+ infobase_name=infobase_name, service_data_dir=service_data_dir)
2320
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2321
+ raise ValueError('RAC service setting insert failed (' + type(exc).__name__ + ')') from None
2322
+
2323
+ if allow_service_setting_update:
2324
+ @server.tool(description='MUTATION: update a service setting data directory. '
2325
+ 'Confirm cluster UUID:working server UUID:setting UUID exactly; '
2326
+ 'guarded client checks the full settings list after update.')
2327
+ async def rac_update_service_setting(cluster_id: StrictStr, server_id: StrictStr,
2328
+ setting_id: StrictStr, service_data_dir: StrictStr,
2329
+ confirm_cluster_server_setting: StrictStr) -> dict:
2330
+ try:
2331
+ cluster, working_server, setting = (UUID(value) for value in
2332
+ (cluster_id, server_id, setting_id))
2333
+ except (TypeError, ValueError, AttributeError):
2334
+ raise ValueError('Invalid cluster, working server, or setting UUID') from None
2335
+ if (str(cluster) != cluster_id or str(working_server) != server_id
2336
+ or str(setting) != setting_id or type(service_data_dir) is not str
2337
+ or '\x00' in service_data_dir
2338
+ or len(service_data_dir.encode('utf-8')) > 1024):
2339
+ raise ValueError('Invalid service setting update parameters')
2340
+ if confirm_cluster_server_setting != f'{cluster}:{working_server}:{setting}':
2341
+ raise ValueError('Confirmation must match cluster:server:setting UUIDs exactly')
2342
+ try:
2343
+ async with service.factory() as client:
2344
+ return await client.update_service_setting(cluster, working_server, setting,
2345
+ service_data_dir)
2346
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2347
+ raise ValueError('RAC service setting update failed (' + type(exc).__name__ + ')') from None
2348
+
2349
+ if allow_service_setting_remove:
2350
+ @server.tool(description='DESTRUCTIVE: remove one service setting. Confirm exact '
2351
+ 'cluster UUID:working server UUID:setting UUID; guarded client '
2352
+ 'checks the full settings list after removal.')
2353
+ async def rac_remove_service_setting(cluster_id: StrictStr, server_id: StrictStr,
2354
+ setting_id: StrictStr,
2355
+ confirm_cluster_server_setting: StrictStr) -> None:
2356
+ try:
2357
+ cluster, working_server, setting = (UUID(value) for value in
2358
+ (cluster_id, server_id, setting_id))
2359
+ except (TypeError, ValueError, AttributeError):
2360
+ raise ValueError('Invalid cluster, working server, or setting UUID') from None
2361
+ if (str(cluster) != cluster_id or str(working_server) != server_id
2362
+ or str(setting) != setting_id):
2363
+ raise ValueError('UUIDs must use canonical form')
2364
+ if confirm_cluster_server_setting != f'{cluster}:{working_server}:{setting}':
2365
+ raise ValueError('Confirmation must match cluster:server:setting UUIDs exactly')
2366
+ try:
2367
+ async with service.factory() as client:
2368
+ await client.remove_service_setting(cluster, working_server, setting)
2369
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2370
+ raise ValueError('RAC service setting remove failed (' + type(exc).__name__ + ')') from None
2371
+
2372
+ if allow_service_settings_apply:
2373
+ @server.tool(description='MUTATION: apply pending service settings. Confirm the exact '
2374
+ 'cluster UUID:working server UUID; guarded client checks all '
2375
+ 'settings have become active.')
2376
+ async def rac_apply_service_settings(cluster_id: StrictStr, server_id: StrictStr,
2377
+ confirm_cluster_server: StrictStr) -> list[dict]:
2378
+ try:
2379
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
2380
+ except (TypeError, ValueError, AttributeError):
2381
+ raise ValueError('Invalid cluster or working server UUID') from None
2382
+ if str(cluster) != cluster_id or str(working_server) != server_id:
2383
+ raise ValueError('UUIDs must use canonical form')
2384
+ if confirm_cluster_server != f'{cluster}:{working_server}':
2385
+ raise ValueError('Confirmation must match cluster UUID:working server UUID exactly')
2386
+ try:
2387
+ async with service.factory() as client:
2388
+ return await client.apply_service_settings(cluster, working_server)
2389
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2390
+ raise ValueError('RAC service settings apply failed (' + type(exc).__name__ + ')') from None
2391
+
2392
+ if allow_rule_apply:
2393
+ @server.tool(description='MUTATION: apply cluster assignment rules in full or partial mode. '
2394
+ 'Confirm the exact cluster UUID; guarded client checks all rule lists '
2395
+ 'unchanged, but effective process allocation is not observable.')
2396
+ async def rac_apply_rules(cluster_id: StrictStr, full: StrictBool,
2397
+ confirm_cluster_id: StrictStr) -> dict:
2398
+ try:
2399
+ cluster = UUID(cluster_id)
2400
+ except (TypeError, ValueError, AttributeError):
2401
+ raise ValueError('Invalid cluster UUID') from None
2402
+ if str(cluster) != cluster_id or type(full) is not bool:
2403
+ raise ValueError('Invalid assignment rule apply parameters')
2404
+ if confirm_cluster_id != cluster_id:
2405
+ raise ValueError('Confirmation must match cluster UUID exactly')
2406
+ try:
2407
+ async with service.factory() as client:
2408
+ return await client.apply_rules(cluster, full=full)
2409
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2410
+ raise ValueError('RAC rule apply failed (' + type(exc).__name__ + ')') from None
2411
+
2412
+ if allow_rule_insert:
2413
+ @server.tool(description='MUTATION: insert a cluster assignment rule at a position. Confirm '
2414
+ 'cluster UUID:server UUID:position exactly; guarded client checks full rule list.')
2415
+ async def rac_insert_rule(cluster_id: StrictStr, server_id: StrictStr, position: StrictInt,
2416
+ rule_type: StrictStr, priority: StrictInt,
2417
+ confirm_cluster_server_position: StrictStr) -> dict:
2418
+ try:
2419
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
2420
+ except (TypeError, ValueError, AttributeError):
2421
+ raise ValueError('Invalid cluster or server UUID') from None
2422
+ if (str(cluster) != cluster_id or str(working_server) != server_id
2423
+ or type(position) is not int or not 0 <= position < 2**31
2424
+ or type(priority) is not int or not -(2**31) <= priority < 2**31
2425
+ or rule_type not in ('never', 'auto', 'always')):
2426
+ raise ValueError('Invalid assignment rule parameters')
2427
+ if confirm_cluster_server_position != f'{cluster}:{working_server}:{position}':
2428
+ raise ValueError('Confirmation must match cluster UUID:server UUID:position exactly')
2429
+ try:
2430
+ async with service.factory() as client:
2431
+ return await client.insert_rule(cluster, working_server, position,
2432
+ rule_type=rule_type, priority=priority)
2433
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2434
+ raise ValueError('RAC rule insert failed (' + type(exc).__name__ + ')') from None
2435
+
2436
+ if allow_rule_remove:
2437
+ @server.tool(description='DESTRUCTIVE: remove an assignment rule by exact cluster, working-server '
2438
+ 'and rule UUIDs. Guarded client checks the entire rule list after removal.')
2439
+ async def rac_remove_rule(cluster_id: StrictStr, server_id: StrictStr, rule_id: StrictStr,
2440
+ confirm_cluster_server_rule: StrictStr) -> dict:
2441
+ try:
2442
+ cluster, working_server, rule = UUID(cluster_id), UUID(server_id), UUID(rule_id)
2443
+ except (TypeError, ValueError, AttributeError):
2444
+ raise ValueError('Invalid cluster, server or rule UUID') from None
2445
+ if (str(cluster) != cluster_id or str(working_server) != server_id
2446
+ or str(rule) != rule_id):
2447
+ raise ValueError('Non-canonical cluster, server or rule UUID')
2448
+ if confirm_cluster_server_rule != f'{cluster}:{working_server}:{rule}':
2449
+ raise ValueError('Confirmation must match cluster UUID:server UUID:rule UUID exactly')
2450
+ try:
2451
+ async with service.factory() as client:
2452
+ await client.remove_rule(cluster, working_server, rule)
2453
+ return {'removed_rule': str(rule)}
2454
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2455
+ raise ValueError('RAC rule remove failed (' + type(exc).__name__ + ')') from None
2456
+
2457
+ if allow_rule_position_update:
2458
+ @server.tool(description='MUTATION: move an existing assignment rule without changing fields. '
2459
+ 'Confirm cluster UUID:server UUID:rule UUID; guarded client checks full rule list.')
2460
+ async def rac_move_rule(cluster_id: StrictStr, server_id: StrictStr, rule_id: StrictStr,
2461
+ position: StrictInt, confirm_cluster_server_rule: StrictStr) -> dict:
2462
+ try:
2463
+ cluster, working_server, rule = UUID(cluster_id), UUID(server_id), UUID(rule_id)
2464
+ except (TypeError, ValueError, AttributeError):
2465
+ raise ValueError('Invalid cluster, server or rule UUID') from None
2466
+ if (str(cluster) != cluster_id or str(working_server) != server_id
2467
+ or str(rule) != rule_id or type(position) is not int
2468
+ or not 0 <= position < 2**31):
2469
+ raise ValueError('Invalid assignment rule position')
2470
+ if confirm_cluster_server_rule != f'{cluster}:{working_server}:{rule}':
2471
+ raise ValueError('Confirmation must match cluster UUID:server UUID:rule UUID exactly')
2472
+ try:
2473
+ async with service.factory() as client:
2474
+ return await client.update_rule(cluster, working_server, rule, position)
2475
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2476
+ raise ValueError('RAC rule position update failed (' + type(exc).__name__ + ')') from None
2477
+
2478
+ if allow_rule_fields_update:
2479
+ @server.tool(description='MUTATION: update selected assignment-rule fields. Confirm '
2480
+ 'cluster UUID:server UUID:rule UUID; guarded client checks full rule list.')
2481
+ async def rac_update_rule_fields(cluster_id: StrictStr, server_id: StrictStr,
2482
+ rule_id: StrictStr, position: StrictInt,
2483
+ fields: dict[str, StrictStr | StrictInt],
2484
+ confirm_cluster_server_rule: StrictStr) -> dict:
2485
+ try:
2486
+ cluster, working_server, rule = UUID(cluster_id), UUID(server_id), UUID(rule_id)
2487
+ except (TypeError, ValueError, AttributeError):
2488
+ raise ValueError('Invalid cluster, server or rule UUID') from None
2489
+ if (str(cluster) != cluster_id or str(working_server) != server_id
2490
+ or str(rule) != rule_id or type(position) is not int
2491
+ or not 0 <= position < 2**31 or type(fields) is not dict or not fields
2492
+ or set(fields) - {'object_type', 'infobase_name', 'rule_type',
2493
+ 'application_ext', 'priority'}
2494
+ or any((type(value) is not int or not -(2**31) <= value < 2**31)
2495
+ if key == 'priority' else
2496
+ (type(value) is not str or '\x00' in value
2497
+ or len(value.encode('utf-8')) > 1024
2498
+ or key == 'rule_type' and value not in ('never', 'auto', 'always'))
2499
+ for key, value in fields.items())):
2500
+ raise ValueError('Invalid assignment rule fields')
2501
+ if confirm_cluster_server_rule != f'{cluster}:{working_server}:{rule}':
2502
+ raise ValueError('Confirmation must match cluster UUID:server UUID:rule UUID exactly')
2503
+ try:
2504
+ async with service.factory() as client:
2505
+ return await client.update_rule(cluster, working_server, rule, position, **fields)
2506
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2507
+ raise ValueError('RAC rule fields update failed (' + type(exc).__name__ + ')') from None
2508
+
2509
+ if allow_rule_priority_update:
2510
+ @server.tool(description='MUTATION: update priority of a selected assignment rule without changing '
2511
+ 'other rule fields. Confirm cluster UUID:server UUID:rule UUID exactly; '
2512
+ 'guarded client verifies the entire rule list.')
2513
+ async def rac_update_rule_priority(cluster_id: StrictStr, server_id: StrictStr,
2514
+ rule_id: StrictStr, position: StrictInt,
2515
+ priority: StrictInt,
2516
+ confirm_cluster_server_rule: StrictStr) -> dict:
2517
+ try:
2518
+ cluster, working_server, rule = UUID(cluster_id), UUID(server_id), UUID(rule_id)
2519
+ except (TypeError, ValueError, AttributeError):
2520
+ raise ValueError('Invalid cluster, server or rule UUID') from None
2521
+ if (str(cluster) != cluster_id or str(working_server) != server_id or str(rule) != rule_id
2522
+ or type(position) is not int or not 0 <= position < 2**31
2523
+ or type(priority) is not int or not -(2**31) <= priority < 2**31):
2524
+ raise ValueError('Invalid assignment rule parameters')
2525
+ if confirm_cluster_server_rule != f'{cluster}:{working_server}:{rule}':
2526
+ raise ValueError('Confirmation must match cluster UUID:server UUID:rule UUID exactly')
2527
+ try:
2528
+ async with service.factory() as client:
2529
+ return await client.update_rule(cluster, working_server, rule, position,
2530
+ priority=priority)
2531
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2532
+ raise ValueError('RAC rule priority update failed (' + type(exc).__name__ + ')') from None
2533
+
2534
+ if allow_counter_clear:
2535
+ @server.tool(description='DESTRUCTIVE: request clear of named counter readings. '
2536
+ 'Confirm cluster UUID:name exactly; guarded client checks configuration '
2537
+ 'unchanged, but populated reading deletion is not live-verified.')
2538
+ async def rac_clear_counter(cluster_id: StrictStr, name: StrictStr,
2539
+ object_filter: StrictStr,
2540
+ confirm_cluster_name: StrictStr) -> dict:
2541
+ try:
2542
+ cluster = UUID(cluster_id)
2543
+ except (TypeError, ValueError, AttributeError):
2544
+ raise ValueError('Invalid cluster UUID') from None
2545
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2546
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2547
+ or type(object_filter) is not str or '\x00' in object_filter
2548
+ or len(object_filter.encode('utf-8')) > 4096):
2549
+ raise ValueError('Invalid counter clear parameters')
2550
+ if confirm_cluster_name != f'{cluster}:{name}':
2551
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2552
+ try:
2553
+ async with service.factory() as client:
2554
+ return await client.clear_counter(cluster, name, object_filter)
2555
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2556
+ raise ValueError('RAC counter clear failed (' + type(exc).__name__ + ')') from None
2557
+
2558
+ if allow_counter_analysis_flags_update:
2559
+ @server.tool(description='MUTATION: update selected named-counter analysis flags. '
2560
+ 'Confirm cluster UUID:name exactly; guarded client checks full list.')
2561
+ async def rac_update_counter_analysis_flags(cluster_id: StrictStr, name: StrictStr,
2562
+ flags: dict[str, StrictBool],
2563
+ confirm_cluster_name: StrictStr) -> dict:
2564
+ try:
2565
+ cluster = UUID(cluster_id)
2566
+ except (TypeError, ValueError, AttributeError):
2567
+ raise ValueError('Invalid cluster UUID') from None
2568
+ allowed = {'duration', 'cpu_time', 'memory', 'read', 'write', 'duration_dbms',
2569
+ 'dbms_bytes', 'service', 'call', 'number_of_active_sessions',
2570
+ 'number_of_sessions'}
2571
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2572
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2573
+ or type(flags) is not dict or not flags or any(
2574
+ key not in allowed or type(value) is not bool
2575
+ for key, value in flags.items())):
2576
+ raise ValueError('Invalid counter analysis flags parameters')
2577
+ if confirm_cluster_name != f'{cluster}:{name}':
2578
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2579
+ try:
2580
+ async with service.factory() as client:
2581
+ return await client.update_counter(cluster, name, **flags)
2582
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2583
+ raise ValueError('RAC counter analysis flags update failed (' + type(exc).__name__ + ')') from None
2584
+
2585
+ if allow_counter_filter_update:
2586
+ @server.tool(description='MUTATION: update or clear a named counter object filter. '
2587
+ 'Confirm cluster UUID:name exactly; guarded client checks full list.')
2588
+ async def rac_update_counter_filter(cluster_id: StrictStr, name: StrictStr,
2589
+ object_filter: StrictStr,
2590
+ confirm_cluster_name: StrictStr) -> dict:
2591
+ try:
2592
+ cluster = UUID(cluster_id)
2593
+ except (TypeError, ValueError, AttributeError):
2594
+ raise ValueError('Invalid cluster UUID') from None
2595
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2596
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2597
+ or type(object_filter) is not str or '\x00' in object_filter
2598
+ or len(object_filter.encode('utf-8')) > 1024):
2599
+ raise ValueError('Invalid counter filter parameters')
2600
+ if confirm_cluster_name != f'{cluster}:{name}':
2601
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2602
+ try:
2603
+ async with service.factory() as client:
2604
+ return await client.update_counter(cluster, name, filter=object_filter)
2605
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2606
+ raise ValueError('RAC counter filter update failed (' + type(exc).__name__ + ')') from None
2607
+
2608
+ if allow_counter_filter_type_update:
2609
+ @server.tool(description='MUTATION: update a named counter filter type. Confirm cluster '
2610
+ 'UUID:name exactly; guarded client verifies full counter list.')
2611
+ async def rac_update_counter_filter_type(cluster_id: StrictStr, name: StrictStr,
2612
+ mode: StrictStr,
2613
+ confirm_cluster_name: StrictStr) -> dict:
2614
+ try:
2615
+ cluster = UUID(cluster_id)
2616
+ except (TypeError, ValueError, AttributeError):
2617
+ raise ValueError('Invalid cluster UUID') from None
2618
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2619
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2620
+ or mode not in ('all', 'all-selected', 'all-but-selected')):
2621
+ raise ValueError('Invalid counter filter type parameters')
2622
+ if confirm_cluster_name != f'{cluster}:{name}':
2623
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2624
+ try:
2625
+ async with service.factory() as client:
2626
+ return await client.update_counter(cluster, name, filter_type=mode)
2627
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2628
+ raise ValueError('RAC counter filter type update failed (' + type(exc).__name__ + ')') from None
2629
+
2630
+ if allow_counter_group_update:
2631
+ @server.tool(description='MUTATION: update a named counter group. Confirm cluster '
2632
+ 'UUID:name exactly; guarded client verifies full counter list.')
2633
+ async def rac_update_counter_group(cluster_id: StrictStr, name: StrictStr, group: StrictStr,
2634
+ confirm_cluster_name: StrictStr) -> dict:
2635
+ try:
2636
+ cluster = UUID(cluster_id)
2637
+ except (TypeError, ValueError, AttributeError):
2638
+ raise ValueError('Invalid cluster UUID') from None
2639
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2640
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2641
+ or group not in ('users', 'data-separation')):
2642
+ raise ValueError('Invalid counter group parameters')
2643
+ if confirm_cluster_name != f'{cluster}:{name}':
2644
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2645
+ try:
2646
+ async with service.factory() as client:
2647
+ return await client.update_counter(cluster, name, group=group)
2648
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2649
+ raise ValueError('RAC counter group update failed (' + type(exc).__name__ + ')') from None
2650
+
2651
+ if allow_counter_duration_update:
2652
+ @server.tool(description='MUTATION: toggle duration analysis for a named counter. '
2653
+ 'Confirm cluster UUID:name exactly; guarded client checks full list.')
2654
+ async def rac_update_counter_duration(cluster_id: StrictStr, name: StrictStr, enabled: StrictBool,
2655
+ confirm_cluster_name: StrictStr) -> dict:
2656
+ try:
2657
+ cluster = UUID(cluster_id)
2658
+ except (TypeError, ValueError, AttributeError):
2659
+ raise ValueError('Invalid cluster UUID') from None
2660
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2661
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2662
+ or type(enabled) is not bool):
2663
+ raise ValueError('Invalid counter duration parameters')
2664
+ if confirm_cluster_name != f'{cluster}:{name}':
2665
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2666
+ try:
2667
+ async with service.factory() as client:
2668
+ return await client.update_counter(cluster, name, duration=enabled)
2669
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2670
+ raise ValueError('RAC counter duration update failed (' + type(exc).__name__ + ')') from None
2671
+
2672
+ if allow_counter_collection_time_update:
2673
+ @server.tool(description='MUTATION: update a named counter collection time. Confirm '
2674
+ 'cluster UUID:name exactly; guarded client checks the full list.')
2675
+ async def rac_update_counter_collection_time(cluster_id: StrictStr, name: StrictStr, seconds: StrictInt,
2676
+ confirm_cluster_name: StrictStr) -> dict:
2677
+ try:
2678
+ cluster = UUID(cluster_id)
2679
+ except (TypeError, ValueError, AttributeError):
2680
+ raise ValueError('Invalid cluster UUID') from None
2681
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2682
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2683
+ or type(seconds) is not int or not 0 <= seconds < 2**63):
2684
+ raise ValueError('Invalid counter collection time parameters')
2685
+ if confirm_cluster_name != f'{cluster}:{name}':
2686
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2687
+ try:
2688
+ async with service.factory() as client:
2689
+ return await client.update_counter(cluster, name, collection_time=seconds)
2690
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2691
+ raise ValueError('RAC counter collection time update failed (' + type(exc).__name__ + ')') from None
2692
+
2693
+ if allow_counter_description_update:
2694
+ @server.tool(description='MUTATION: update the description of a named cluster counter. '
2695
+ 'Confirm cluster UUID:name exactly; guarded client verifies full list.')
2696
+ async def rac_update_counter_description(cluster_id: StrictStr, name: StrictStr, descr: StrictStr,
2697
+ confirm_cluster_name: StrictStr) -> dict:
2698
+ try:
2699
+ cluster = UUID(cluster_id)
2700
+ except (TypeError, ValueError, AttributeError):
2701
+ raise ValueError('Invalid cluster UUID') from None
2702
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2703
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2704
+ or type(descr) is not str or '\x00' in descr
2705
+ or len(descr.encode('utf-8')) > 1024):
2706
+ raise ValueError('Invalid counter description parameters')
2707
+ if confirm_cluster_name != f'{cluster}:{name}':
2708
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2709
+ try:
2710
+ async with service.factory() as client:
2711
+ return await client.update_counter(cluster, name, descr=descr)
2712
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2713
+ raise ValueError('RAC counter description update failed (' + type(exc).__name__ + ')') from None
2714
+
2715
+ if allow_counter_remove:
2716
+ @server.tool(description='DESTRUCTIVE: remove a named cluster counter. Confirm cluster '
2717
+ 'UUID:name exactly; guarded client checks the complete counter list.')
2718
+ async def rac_remove_counter(cluster_id: StrictStr, name: StrictStr,
2719
+ confirm_cluster_name: StrictStr) -> dict:
2720
+ try:
2721
+ cluster = UUID(cluster_id)
2722
+ except (TypeError, ValueError, AttributeError):
2723
+ raise ValueError('Invalid cluster UUID') from None
2724
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2725
+ or '\x00' in name or len(name.encode('utf-8')) > 1024):
2726
+ raise ValueError('Invalid counter parameters')
2727
+ if confirm_cluster_name != f'{cluster}:{name}':
2728
+ raise ValueError('Confirmation must match cluster UUID:counter name exactly')
2729
+ try:
2730
+ async with service.factory() as client:
2731
+ await client.remove_counter(cluster, name)
2732
+ return {'removed_counter': name}
2733
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2734
+ raise ValueError('RAC counter remove failed (' + type(exc).__name__ + ')') from None
2735
+
2736
+ if allow_limit_update:
2737
+ @server.tool(description='MUTATION: create or update a named cluster limit with action, '
2738
+ 'counter and duration threshold. Confirm cluster UUID:name exactly; '
2739
+ 'guarded client verifies the complete limit list.')
2740
+ async def rac_update_limit(cluster_id: StrictStr, name: StrictStr, action: StrictStr,
2741
+ counter: StrictStr, duration: StrictInt,
2742
+ confirm_cluster_name: StrictStr) -> dict:
2743
+ try:
2744
+ cluster = UUID(cluster_id)
2745
+ except (TypeError, ValueError, AttributeError):
2746
+ raise ValueError('Invalid cluster UUID') from None
2747
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2748
+ or '\x00' in name or len(name.encode('utf-8')) > 1024
2749
+ or action not in ('none', 'set-low-priority-thread', 'interrupt-current-call',
2750
+ 'interrupt-session') or type(counter) is not str
2751
+ or '\x00' in counter or len(counter.encode('utf-8')) > 1024
2752
+ or type(duration) is not int or not 0 <= duration < 2**63):
2753
+ raise ValueError('Invalid limit parameters')
2754
+ if confirm_cluster_name != f'{cluster}:{name}':
2755
+ raise ValueError('Confirmation must match cluster UUID:limit name exactly')
2756
+ try:
2757
+ async with service.factory() as client:
2758
+ return await client.update_limit(cluster, name, action=action,
2759
+ counter=counter, duration=duration)
2760
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2761
+ raise ValueError('RAC limit update failed (' + type(exc).__name__ + ')') from None
2762
+
2763
+ if allow_limit_remove:
2764
+ @server.tool(description='DESTRUCTIVE: remove a named cluster limit. Confirm the exact cluster '
2765
+ 'UUID:name; guarded client checks the complete limit list after removal.')
2766
+ async def rac_remove_limit(cluster_id: StrictStr, name: StrictStr,
2767
+ confirm_cluster_name: StrictStr) -> dict:
2768
+ try:
2769
+ cluster = UUID(cluster_id)
2770
+ except (TypeError, ValueError, AttributeError):
2771
+ raise ValueError('Invalid cluster UUID') from None
2772
+ if (str(cluster) != cluster_id or type(name) is not str or not name.strip()
2773
+ or '\x00' in name or len(name.encode('utf-8')) > 1024):
2774
+ raise ValueError('Invalid limit parameters')
2775
+ if confirm_cluster_name != f'{cluster}:{name}':
2776
+ raise ValueError('Confirmation must match cluster UUID:limit name exactly')
2777
+ try:
2778
+ async with service.factory() as client:
2779
+ await client.remove_limit(cluster, name)
2780
+ return {'removed_limit': name}
2781
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2782
+ raise ValueError('RAC limit remove failed (' + type(exc).__name__ + ')') from None
2783
+
2784
+ if allow_cluster_insert:
2785
+ @server.tool(description='MUTATION: insert a new cluster on the configured RAS. '
2786
+ 'Confirm the exact host:port; the client requires a full list/info read-back. '
2787
+ 'Only independently verified 16.0 options are exposed.')
2788
+ async def rac_insert_cluster(host: StrictStr, port: StrictInt, name: StrictStr, audit: StrictBool,
2789
+ confirm_host_port: StrictStr, expiration_timeout: StrictInt = 60,
2790
+ security_level: StrictInt = 0,
2791
+ session_fault_tolerance_level: StrictInt = 0,
2792
+ load_balancing_mode: StrictStr = 'performance',
2793
+ kill_problem_processes: StrictBool = True,
2794
+ kill_by_memory_with_dump: StrictBool = False,
2795
+ restart_schedule: StrictStr = '', ping_period: StrictInt = 0,
2796
+ ping_timeout: StrictInt = 0) -> dict:
2797
+ if (type(host) is not str or not host or '\x00' in host
2798
+ or len(host.encode('utf-8')) > 255 or type(port) is not int
2799
+ or not 1 <= port <= 65535 or type(name) is not str or '\x00' in name
2800
+ or len(name.encode('utf-8')) > 256 or type(audit) is not bool):
2801
+ raise ValueError('Invalid cluster insert parameters')
2802
+ if confirm_host_port != f'{host}:{port}':
2803
+ raise ValueError('Confirmation must match the cluster host:port exactly')
2804
+ if (type(ping_period) is not int or type(ping_timeout) is not int
2805
+ or not ((ping_period == ping_timeout == 0)
2806
+ or 0 < ping_period < ping_timeout < 2**31)):
2807
+ raise ValueError('Invalid cluster ping period/timeout pair')
2808
+ for value in (expiration_timeout, security_level, session_fault_tolerance_level):
2809
+ if type(value) is not int or not 0 <= value < 2**31:
2810
+ raise ValueError('Invalid cluster insert numeric setting')
2811
+ if (type(load_balancing_mode) is not str
2812
+ or load_balancing_mode not in ('performance', 'memory')
2813
+ or type(kill_problem_processes) is not bool
2814
+ or type(kill_by_memory_with_dump) is not bool
2815
+ or type(restart_schedule) is not str or '\x00' in restart_schedule
2816
+ or (restart_schedule and (len(restart_schedule.encode('utf-8')) > 256
2817
+ or len(restart_schedule.split()) != 6
2818
+ or any(not part.isascii() or not part.isdecimal()
2819
+ for part in restart_schedule.split())))):
2820
+ raise ValueError('Invalid cluster insert option')
2821
+ defaults = {'expiration_timeout': 60, 'security_level': 0,
2822
+ 'session_fault_tolerance_level': 0, 'load_balancing_mode': 'performance',
2823
+ 'kill_problem_processes': True, 'kill_by_memory_with_dump': False,
2824
+ 'restart_schedule': '', 'ping_period': 0, 'ping_timeout': 0}
2825
+ requested = {'expiration_timeout': expiration_timeout, 'security_level': security_level,
2826
+ 'session_fault_tolerance_level': session_fault_tolerance_level,
2827
+ 'load_balancing_mode': load_balancing_mode,
2828
+ 'kill_problem_processes': kill_problem_processes,
2829
+ 'kill_by_memory_with_dump': kill_by_memory_with_dump,
2830
+ 'restart_schedule': restart_schedule, 'ping_period': ping_period,
2831
+ 'ping_timeout': ping_timeout}
2832
+ options = {key: value for key, value in requested.items() if value != defaults[key]}
2833
+ try:
2834
+ async with service.factory() as client:
2835
+ return await client.insert_cluster(host, port, name, audit=audit, **options)
2836
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2837
+ raise ValueError('RAC cluster insert failed (' + type(exc).__name__ + ')') from None
2838
+
2839
+ if allow_cluster_remove:
2840
+ @server.tool(description='DESTRUCTIVE: remove a cluster from the configured RAS; '
2841
+ 'confirm its exact UUID. The client checks the full cluster list before and after.')
2842
+ async def rac_remove_cluster(cluster_id: StrictStr, confirm_cluster_id: StrictStr) -> list[dict]:
2843
+ cluster = UUID(cluster_id)
2844
+ if confirm_cluster_id != str(cluster):
2845
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2846
+ try:
2847
+ async with service.factory() as client:
2848
+ return await client.remove_cluster(cluster)
2849
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2850
+ raise ValueError('RAC cluster remove failed (' + type(exc).__name__ + ')') from None
2851
+
2852
+ if allow_process_turn_off:
2853
+ @server.tool(description='DESTRUCTIVE: turn off an existing process; confirm with its exact UUID. The client verifies disappearance.')
2854
+ async def rac_turn_off_process(cluster_id: StrictStr, process_id: StrictStr,
2855
+ confirm_process_id: StrictStr) -> list[dict]:
2856
+ cluster, process = UUID(cluster_id), UUID(process_id)
2857
+ if confirm_process_id != str(process):
2858
+ raise ValueError('Confirmation must match the process UUID exactly')
2859
+ async with service.factory() as client:
2860
+ try:
2861
+ return await client.turn_off_process(cluster, process)
2862
+ except (RacError, ValueError, OSError, TimeoutError) as exc:
2863
+ raise ValueError('RAC process turn-off failed (' + type(exc).__name__ + ')') from None
2864
+
2865
+ if allow_cluster_name_update:
2866
+ @server.tool(description='MUTATION: change an existing cluster name; confirm with its exact UUID. The client verifies full read-back.')
2867
+ async def rac_update_cluster_name(cluster_id: StrictStr, name: StrictStr,
2868
+ confirm_cluster_id: StrictStr) -> dict:
2869
+ cluster = UUID(cluster_id)
2870
+ if confirm_cluster_id != str(cluster):
2871
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2872
+ async with service.factory() as client:
2873
+ try:
2874
+ return await client.update_cluster_name(cluster, name)
2875
+ except (RacError, ValueError, OSError, TimeoutError) as exc:
2876
+ raise ValueError('RAC cluster name update failed (' + type(exc).__name__ + ')') from None
2877
+
2878
+ if allow_cluster_expiration_timeout_update:
2879
+ @server.tool(description='MUTATION: change cluster expiration timeout in seconds; confirm its exact UUID. Full read-back is verified.')
2880
+ async def rac_update_cluster_expiration_timeout(cluster_id: StrictStr, seconds: StrictInt,
2881
+ confirm_cluster_id: StrictStr) -> dict:
2882
+ cluster = UUID(cluster_id)
2883
+ if confirm_cluster_id != str(cluster):
2884
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2885
+ if type(seconds) is not int or not 0 <= seconds <= 2_147_483_647:
2886
+ raise ValueError('Invalid cluster expiration timeout')
2887
+ try:
2888
+ async with service.factory() as client:
2889
+ return await client.update_cluster_expiration_timeout(cluster, seconds)
2890
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2891
+ raise ValueError('RAC cluster timeout update failed (' + type(exc).__name__ + ')') from None
2892
+
2893
+ if allow_cluster_security_level_update:
2894
+ @server.tool(description='MUTATION: change cluster security level; confirm its exact UUID. Full read-back is verified.')
2895
+ async def rac_update_cluster_security_level(cluster_id: StrictStr, level: StrictInt,
2896
+ confirm_cluster_id: StrictStr) -> dict:
2897
+ cluster = UUID(cluster_id)
2898
+ if confirm_cluster_id != str(cluster):
2899
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2900
+ if type(level) is not int or not 0 <= level <= 2_147_483_647:
2901
+ raise ValueError('Invalid cluster security level')
2902
+ try:
2903
+ async with service.factory() as client:
2904
+ return await client.update_cluster_security_level(cluster, level)
2905
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2906
+ raise ValueError('RAC cluster security level update failed (' + type(exc).__name__ + ')') from None
2907
+
2908
+ if allow_cluster_session_fault_tolerance_level_update:
2909
+ @server.tool(description='MUTATION: change cluster session fault tolerance level; confirm its exact UUID. Full read-back is verified.')
2910
+ async def rac_update_cluster_session_fault_tolerance_level(cluster_id: StrictStr, level: StrictInt,
2911
+ confirm_cluster_id: StrictStr) -> dict:
2912
+ cluster = UUID(cluster_id)
2913
+ if confirm_cluster_id != str(cluster):
2914
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2915
+ if type(level) is not int or not 0 <= level <= 2_147_483_647:
2916
+ raise ValueError('Invalid cluster session fault tolerance level')
2917
+ try:
2918
+ async with service.factory() as client:
2919
+ return await client.update_cluster_session_fault_tolerance_level(cluster, level)
2920
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2921
+ raise ValueError('RAC cluster session fault tolerance update failed (' + type(exc).__name__ + ')') from None
2922
+
2923
+ if allow_cluster_load_balancing_mode_update:
2924
+ @server.tool(description='MUTATION: set cluster load balancing to performance or memory; exact UUID confirmation and full read-back required.')
2925
+ async def rac_update_cluster_load_balancing_mode(cluster_id: StrictStr, mode: StrictStr,
2926
+ confirm_cluster_id: StrictStr) -> dict:
2927
+ cluster = UUID(cluster_id)
2928
+ if confirm_cluster_id != str(cluster):
2929
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2930
+ if type(mode) is not str or mode not in ('performance', 'memory'):
2931
+ raise ValueError('Invalid cluster load balancing mode')
2932
+ try:
2933
+ async with service.factory() as client:
2934
+ return await client.update_cluster_load_balancing_mode(cluster, mode)
2935
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2936
+ raise ValueError('RAC cluster load balancing update failed (' + type(exc).__name__ + ')') from None
2937
+
2938
+ if allow_cluster_kill_problem_processes_update:
2939
+ @server.tool(description='MUTATION: change cluster automatic problem-process termination; confirm exact UUID. Full read-back required.')
2940
+ async def rac_update_cluster_kill_problem_processes(cluster_id: StrictStr, enabled: StrictBool,
2941
+ confirm_cluster_id: StrictStr) -> dict:
2942
+ cluster = UUID(cluster_id)
2943
+ if confirm_cluster_id != str(cluster):
2944
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2945
+ if type(enabled) is not bool:
2946
+ raise ValueError('Invalid kill problem processes flag')
2947
+ try:
2948
+ async with service.factory() as client:
2949
+ return await client.update_cluster_kill_problem_processes(cluster, enabled)
2950
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2951
+ raise ValueError('RAC cluster problem-process policy update failed (' + type(exc).__name__ + ')') from None
2952
+
2953
+ if allow_cluster_kill_by_memory_with_dump_update:
2954
+ @server.tool(description='MUTATION: change cluster kill-by-memory dump policy; confirm exact UUID. Full read-back required.')
2955
+ async def rac_update_cluster_kill_by_memory_with_dump(cluster_id: StrictStr, enabled: StrictBool,
2956
+ confirm_cluster_id: StrictStr) -> dict:
2957
+ cluster = UUID(cluster_id)
2958
+ if confirm_cluster_id != str(cluster):
2959
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2960
+ if type(enabled) is not bool:
2961
+ raise ValueError('Invalid kill by memory dump flag')
2962
+ try:
2963
+ async with service.factory() as client:
2964
+ return await client.update_cluster_kill_by_memory_with_dump(cluster, enabled)
2965
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2966
+ raise ValueError('RAC cluster kill-by-memory dump update failed (' + type(exc).__name__ + ')') from None
2967
+
2968
+ if allow_cluster_audit_update:
2969
+ @server.tool(description='MUTATION: change cluster access-right audit recording; confirm exact UUID. Full read-back required.')
2970
+ async def rac_update_cluster_audit(cluster_id: StrictStr, enabled: StrictBool,
2971
+ confirm_cluster_id: StrictStr) -> dict:
2972
+ cluster = UUID(cluster_id)
2973
+ if confirm_cluster_id != str(cluster):
2974
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2975
+ if type(enabled) is not bool:
2976
+ raise ValueError('Invalid cluster audit flag')
2977
+ try:
2978
+ async with service.factory() as client:
2979
+ return await client.update_cluster_audit(cluster, enabled)
2980
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2981
+ raise ValueError('RAC cluster audit update failed (' + type(exc).__name__ + ')') from None
2982
+
2983
+ if allow_cluster_ping_update:
2984
+ @server.tool(description='MUTATION: update cluster ping period and timeout together; confirm exact UUID. Full read-back required.')
2985
+ async def rac_update_cluster_ping(cluster_id: StrictStr, period: StrictInt, timeout: StrictInt,
2986
+ confirm_cluster_id: StrictStr) -> dict:
2987
+ cluster = UUID(cluster_id)
2988
+ if confirm_cluster_id != str(cluster):
2989
+ raise ValueError('Confirmation must match the cluster UUID exactly')
2990
+ if (type(period) is not int or type(timeout) is not int or
2991
+ not ((period == 0 and timeout == 0) or (0 < period < timeout <= 2_147_483_647))):
2992
+ raise ValueError('Invalid cluster ping period/timeout pair')
2993
+ try:
2994
+ async with service.factory() as client:
2995
+ return await client.update_cluster_ping(cluster, period, timeout)
2996
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
2997
+ raise ValueError('RAC cluster ping update failed (' + type(exc).__name__ + ')') from None
2998
+
2999
+ if allow_cluster_restart_schedule_update:
3000
+ @server.tool(description='MUTATION: update cluster restart schedule; confirm exact UUID. Full read-back required.')
3001
+ async def rac_update_cluster_restart_schedule(cluster_id: StrictStr, schedule: StrictStr,
3002
+ confirm_cluster_id: StrictStr) -> dict:
3003
+ cluster = UUID(cluster_id)
3004
+ if confirm_cluster_id != str(cluster):
3005
+ raise ValueError('Confirmation must match the cluster UUID exactly')
3006
+ RacClient._validate_schedule(schedule)
3007
+ try:
3008
+ async with service.factory() as client:
3009
+ return await client.update_restart_schedule(cluster, schedule)
3010
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3011
+ raise ValueError('RAC cluster restart schedule update failed (' + type(exc).__name__ + ')') from None
3012
+
3013
+ if allow_server_restart_schedule_update:
3014
+ @server.tool(description='MUTATION: update working-server restart schedule; confirm exact server UUID. Full read-back required.')
3015
+ async def rac_update_server_restart_schedule(cluster_id: StrictStr, server_id: StrictStr,
3016
+ schedule: StrictStr, confirm_server_id: StrictStr) -> dict:
3017
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3018
+ if confirm_server_id != str(working_server):
3019
+ raise ValueError('Confirmation must match the working server UUID exactly')
3020
+ RacClient._validate_schedule(schedule)
3021
+ try:
3022
+ async with service.factory() as client:
3023
+ return await client.update_server_restart_schedule(cluster, working_server, schedule)
3024
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3025
+ raise ValueError('RAC server restart schedule update failed (' + type(exc).__name__ + ')') from None
3026
+
3027
+ if allow_server_infobases_limit_update:
3028
+ @server.tool(description='MUTATION: update working-server infobases-per-process limit; confirm exact server UUID. Full read-back required.')
3029
+ async def rac_update_server_infobases_limit(cluster_id: StrictStr, server_id: StrictStr,
3030
+ limit: StrictInt, confirm_server_id: StrictStr) -> dict:
3031
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3032
+ if confirm_server_id != str(working_server):
3033
+ raise ValueError('Confirmation must match the working server UUID exactly')
3034
+ if type(limit) is not int or not 0 <= limit <= 2_147_483_647:
3035
+ raise ValueError('Invalid working-server infobases limit')
3036
+ try:
3037
+ async with service.factory() as client:
3038
+ return await client.update_server_infobases_limit(cluster, working_server, limit)
3039
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3040
+ raise ValueError('RAC server infobases limit update failed (' + type(exc).__name__ + ')') from None
3041
+
3042
+ if allow_server_connections_limit_update:
3043
+ @server.tool(description='MUTATION: update working-server connections-per-process limit; confirm exact server UUID. Full read-back required.')
3044
+ async def rac_update_server_connections_limit(cluster_id: StrictStr, server_id: StrictStr,
3045
+ limit: StrictInt, confirm_server_id: StrictStr) -> dict:
3046
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3047
+ if confirm_server_id != str(working_server):
3048
+ raise ValueError('Confirmation must match the working server UUID exactly')
3049
+ if type(limit) is not int or not 0 <= limit <= 2_147_483_647:
3050
+ raise ValueError('Invalid working-server connections limit')
3051
+ try:
3052
+ async with service.factory() as client:
3053
+ return await client.update_server_connections_limit(cluster, working_server, limit)
3054
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3055
+ raise ValueError('RAC server connections limit update failed (' + type(exc).__name__ + ')') from None
3056
+
3057
+ if allow_server_safe_call_memory_limit_update:
3058
+ @server.tool(description='MUTATION: update working-server safe-call memory limit; confirm exact server UUID. Full read-back required.')
3059
+ async def rac_update_server_safe_call_memory_limit(cluster_id: StrictStr, server_id: StrictStr,
3060
+ limit: StrictInt, confirm_server_id: StrictStr) -> dict:
3061
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3062
+ if confirm_server_id != str(working_server):
3063
+ raise ValueError('Confirmation must match the working server UUID exactly')
3064
+ if type(limit) is not int or not 0 <= limit < 2**63:
3065
+ raise ValueError('Invalid working-server safe-call memory limit')
3066
+ try:
3067
+ async with service.factory() as client:
3068
+ return await client.update_server_safe_call_memory_limit(cluster, working_server, limit)
3069
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3070
+ raise ValueError('RAC server safe-call memory limit update failed (' + type(exc).__name__ + ')') from None
3071
+
3072
+ if allow_server_critical_total_memory_update:
3073
+ @server.tool(description='MUTATION: update working-server critical total memory; confirm exact server UUID. Full read-back required.')
3074
+ async def rac_update_server_critical_total_memory(cluster_id: StrictStr, server_id: StrictStr,
3075
+ limit: StrictInt, confirm_server_id: StrictStr) -> dict:
3076
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3077
+ if confirm_server_id != str(working_server):
3078
+ raise ValueError('Confirmation must match the working server UUID exactly')
3079
+ if type(limit) is not int or not 0 <= limit < 2**63:
3080
+ raise ValueError('Invalid working-server critical total memory')
3081
+ try:
3082
+ async with service.factory() as client:
3083
+ return await client.update_server_critical_total_memory(cluster, working_server, limit)
3084
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3085
+ raise ValueError('RAC server critical total memory update failed (' + type(exc).__name__ + ')') from None
3086
+
3087
+ if allow_server_temporary_total_memory_update:
3088
+ @server.tool(description='MUTATION: update working-server temporary allowed total memory; confirm exact server UUID. Full read-back required.')
3089
+ async def rac_update_server_temporary_allowed_total_memory(cluster_id: StrictStr, server_id: StrictStr,
3090
+ limit: StrictInt, confirm_server_id: StrictStr) -> dict:
3091
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3092
+ if confirm_server_id != str(working_server):
3093
+ raise ValueError('Confirmation must match the working server UUID exactly')
3094
+ if type(limit) is not int or not 0 <= limit < 2**63:
3095
+ raise ValueError('Invalid working-server temporary allowed total memory')
3096
+ try:
3097
+ async with service.factory() as client:
3098
+ return await client.update_server_temporary_allowed_total_memory(cluster, working_server, limit)
3099
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3100
+ raise ValueError('RAC server temporary allowed total memory update failed (' + type(exc).__name__ + ')') from None
3101
+
3102
+ if allow_server_temporary_memory_time_limit_update:
3103
+ @server.tool(description='MUTATION: update working-server temporary allowed total memory time limit; confirm exact server UUID. Full read-back required.')
3104
+ async def rac_update_server_temporary_allowed_total_memory_time_limit(cluster_id: StrictStr, server_id: StrictStr,
3105
+ limit: StrictInt, confirm_server_id: StrictStr) -> dict:
3106
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3107
+ if confirm_server_id != str(working_server):
3108
+ raise ValueError('Confirmation must match the working server UUID exactly')
3109
+ if type(limit) is not int or not 0 <= limit < 2**63:
3110
+ raise ValueError('Invalid working-server temporary memory time limit')
3111
+ try:
3112
+ async with service.factory() as client:
3113
+ return await client.update_server_temporary_allowed_total_memory_time_limit(cluster, working_server, limit)
3114
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3115
+ raise ValueError('RAC server temporary memory time limit update failed (' + type(exc).__name__ + ')') from None
3116
+
3117
+ if allow_server_spn_update:
3118
+ @server.tool(description='MUTATION: update or clear working-server service principal name; confirm exact server UUID. Full read-back required.')
3119
+ async def rac_update_server_service_principal_name(cluster_id: StrictStr, server_id: StrictStr,
3120
+ name: StrictStr, confirm_server_id: StrictStr) -> dict:
3121
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3122
+ if confirm_server_id != str(working_server):
3123
+ raise ValueError('Confirmation must match the working server UUID exactly')
3124
+ if not isinstance(name, str) or '\x00' in name or len(name.encode('utf-8')) > 256:
3125
+ raise ValueError('Invalid working-server service principal name')
3126
+ try:
3127
+ async with service.factory() as client:
3128
+ return await client.update_server_service_principal_name(cluster, working_server, name)
3129
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3130
+ raise ValueError('RAC server service principal name update failed (' + type(exc).__name__ + ')') from None
3131
+
3132
+ if allow_server_dedicate_managers_update:
3133
+ @server.tool(description='MUTATION: set working-server dedicated managers to all or none; confirm exact server UUID. Full read-back required.')
3134
+ async def rac_update_server_dedicate_managers(cluster_id: StrictStr, server_id: StrictStr,
3135
+ choice: StrictStr, confirm_server_id: StrictStr) -> dict:
3136
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3137
+ if confirm_server_id != str(working_server):
3138
+ raise ValueError('Confirmation must match the working server UUID exactly')
3139
+ if type(choice) is not str or choice not in ('all', 'none'):
3140
+ raise ValueError('Invalid working-server dedicated managers choice')
3141
+ try:
3142
+ async with service.factory() as client:
3143
+ return await client.update_server_dedicate_managers(cluster, working_server, choice)
3144
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3145
+ raise ValueError('RAC server dedicated managers update failed (' + type(exc).__name__ + ')') from None
3146
+
3147
+ if allow_server_port_ranges_update:
3148
+ @server.tool(description='MUTATION: update working-server port ranges; confirm exact server UUID. Full read-back required.')
3149
+ async def rac_update_server_port_ranges(cluster_id: StrictStr, server_id: StrictStr,
3150
+ ranges: list[list[StrictInt]],
3151
+ confirm_server_id: StrictStr) -> dict:
3152
+ cluster, working_server = UUID(cluster_id), UUID(server_id)
3153
+ if confirm_server_id != str(working_server):
3154
+ raise ValueError('Confirmation must match the working server UUID exactly')
3155
+ if (type(ranges) is not list or not 1 <= len(ranges) <= 64 or
3156
+ any(type(pair) is not list or len(pair) != 2 or
3157
+ any(type(port) is not int or not 1 <= port <= 65535 for port in pair) or
3158
+ pair[0] > pair[1] for pair in ranges)):
3159
+ raise ValueError('Invalid working-server port ranges')
3160
+ ordered = sorted(ranges)
3161
+ if any(left[1] >= right[0] for left, right in zip(ordered, ordered[1:])):
3162
+ raise ValueError('Overlapping working-server port ranges')
3163
+ try:
3164
+ async with service.factory() as client:
3165
+ return await client.update_server_port_ranges(cluster, working_server,
3166
+ [tuple(pair) for pair in ranges])
3167
+ except (RacError, ValueError, OSError, TimeoutError, RuntimeError) as exc:
3168
+ raise ValueError('RAC server port ranges update failed (' + type(exc).__name__ + ')') from None
3169
+
3170
+ return server
3171
+
3172
+
3173
+ def main(argv=None):
3174
+ parser = argparse.ArgumentParser(description='RAC MCP server (read-only by default)')
3175
+ parser.add_argument('--ras-host', default='127.0.0.1')
3176
+ parser.add_argument('--ras-port', type=int, default=1545)
3177
+ parser.add_argument('--ras-server', action='append', default=[], metavar='HOST:PORT/PROTOCOL',
3178
+ help='Additional read-only RAS for cross-server search (repeatable)')
3179
+ parser.add_argument('--protocol', choices=['10.0', '16.0', 'auto'], default='16.0')
3180
+ parser.add_argument('--agent-user', help='OS-authenticated agent administrator name')
3181
+ parser.add_argument('--cluster-user', help='OS-authenticated cluster administrator name')
3182
+ parser.add_argument('--allow-process-turn-off', action='store_true',
3183
+ help='Enable destructive process turn-off MCP tool (disabled by default)')
3184
+ parser.add_argument('--allow-cluster-insert', action='store_true',
3185
+ help='Enable guarded cluster insertion MCP tool (disabled by default)')
3186
+ parser.add_argument('--allow-cluster-remove', action='store_true',
3187
+ help='Enable destructive cluster removal MCP tool (disabled by default)')
3188
+ parser.add_argument('--allow-agent-admin-register', action='store_true',
3189
+ help='Enable guarded password-free agent administrator registration (disabled by default)')
3190
+ parser.add_argument('--allow-agent-admin-remove', action='store_true',
3191
+ help='Enable destructive agent administrator removal (disabled by default)')
3192
+ parser.add_argument('--allow-cluster-admin-register', action='store_true',
3193
+ help='Enable guarded password-free cluster administrator registration (disabled by default)')
3194
+ parser.add_argument('--allow-cluster-admin-remove', action='store_true',
3195
+ help='Enable destructive cluster administrator removal (disabled by default)')
3196
+ parser.add_argument('--allow-rule-insert', action='store_true',
3197
+ help='Enable guarded assignment rule insertion (disabled by default)')
3198
+ parser.add_argument('--allow-rule-apply', action='store_true',
3199
+ help='Enable guarded assignment rule apply (disabled by default)')
3200
+ parser.add_argument('--allow-service-settings-apply', action='store_true',
3201
+ help='Enable guarded pending service settings apply (disabled by default)')
3202
+ parser.add_argument('--allow-service-setting-remove', action='store_true',
3203
+ help='Enable destructive service setting removal (disabled by default)')
3204
+ parser.add_argument('--allow-service-setting-update', action='store_true',
3205
+ help='Enable guarded service setting directory update (disabled by default)')
3206
+ parser.add_argument('--allow-service-setting-insert', action='store_true',
3207
+ help='Enable guarded service setting insertion (disabled by default)')
3208
+ parser.add_argument('--allow-profile-remove', action='store_true',
3209
+ help='Enable destructive security profile removal (disabled by default)')
3210
+ parser.add_argument('--allow-profile-description-update', action='store_true',
3211
+ help='Enable guarded profile description update (disabled by default)')
3212
+ parser.add_argument('--allow-profile-config-update', action='store_true',
3213
+ help='Enable guarded profile configuration access update (disabled by default)')
3214
+ parser.add_argument('--allow-profile-access-flags-update', action='store_true',
3215
+ help='Enable guarded profile access flags update (disabled by default)')
3216
+ parser.add_argument('--allow-profile-role-fields-update', action='store_true',
3217
+ help='Enable guarded profile role/module fields update (disabled by default)')
3218
+ parser.add_argument('--allow-profile-directory-remove', action='store_true',
3219
+ help='Enable destructive profile directory ACL removal (disabled by default)')
3220
+ parser.add_argument('--allow-profile-directory-update', action='store_true',
3221
+ help='Enable guarded profile directory ACL update (disabled by default)')
3222
+ parser.add_argument('--allow-profile-com-remove', action='store_true',
3223
+ help='Enable destructive profile COM-class ACL removal (disabled by default)')
3224
+ parser.add_argument('--allow-profile-com-update', action='store_true',
3225
+ help='Enable guarded profile COM-class ACL update (disabled by default)')
3226
+ parser.add_argument('--allow-profile-addin-remove', action='store_true',
3227
+ help='Enable destructive profile add-in ACL removal (disabled by default)')
3228
+ parser.add_argument('--allow-profile-addin-update', action='store_true',
3229
+ help='Enable guarded profile add-in ACL update (disabled by default)')
3230
+ parser.add_argument('--allow-profile-module-remove', action='store_true',
3231
+ help='Enable destructive profile module ACL removal (disabled by default)')
3232
+ parser.add_argument('--allow-profile-module-update', action='store_true',
3233
+ help='Enable guarded profile module ACL update (disabled by default)')
3234
+ parser.add_argument('--allow-profile-app-remove', action='store_true',
3235
+ help='Enable destructive profile application ACL removal (disabled by default)')
3236
+ parser.add_argument('--allow-profile-app-update', action='store_true',
3237
+ help='Enable guarded profile application ACL update (disabled by default)')
3238
+ parser.add_argument('--allow-profile-inet-remove', action='store_true',
3239
+ help='Enable destructive profile inet ACL removal (disabled by default)')
3240
+ parser.add_argument('--allow-profile-inet-update', action='store_true',
3241
+ help='Enable guarded profile inet ACL update (disabled by default)')
3242
+ parser.add_argument('--allow-infobase-security-profile-update', action='store_true',
3243
+ help='Enable guarded infobase security profile update (disabled by default)')
3244
+ parser.add_argument('--allow-infobase-safe-mode-security-profile-update', action='store_true',
3245
+ help='Enable guarded infobase safe-mode security profile update (disabled by default)')
3246
+ parser.add_argument('--allow-infobase-maximum-scheduled-jobs-shift-update', action='store_true',
3247
+ help='Enable guarded infobase scheduled-jobs shift update (disabled by default)')
3248
+ parser.add_argument('--allow-infobase-minimum-scheduled-jobs-period-update', action='store_true',
3249
+ help='Enable guarded infobase scheduled-jobs period update (disabled by default)')
3250
+ parser.add_argument('--allow-infobase-configuration-unload-delay-update', action='store_true',
3251
+ help='Enable guarded infobase configuration unload delay update (disabled by default)')
3252
+ parser.add_argument('--allow-infobase-local-speech-update', action='store_true',
3253
+ help='Enable guarded infobase local speech-to-text update (disabled by default)')
3254
+ parser.add_argument('--allow-infobase-external-session-manager-update', action='store_true',
3255
+ help='Enable guarded infobase external session manager update (disabled by default)')
3256
+ parser.add_argument('--allow-infobase-reserve-working-processes-update', action='store_true',
3257
+ help='Enable guarded infobase reserve working processes update (disabled by default)')
3258
+ parser.add_argument('--allow-infobase-denied-message-update', action='store_true',
3259
+ help='Enable guarded infobase denied message update (disabled by default)')
3260
+ parser.add_argument('--allow-infobase-license-distribution-update', action='store_true',
3261
+ help='Enable guarded infobase license distribution update (disabled by default)')
3262
+ parser.add_argument('--allow-infobase-sessions-deny-update', action='store_true',
3263
+ help='Enable guarded infobase sessions deny update (disabled by default)')
3264
+ parser.add_argument('--allow-infobase-scheduled-jobs-deny-update', action='store_true',
3265
+ help='Enable guarded infobase scheduled jobs deny update (disabled by default)')
3266
+ parser.add_argument('--allow-infobase-description-update', action='store_true',
3267
+ help='Enable guarded infobase description update (disabled by default)')
3268
+ parser.add_argument('--allow-infobase-summary-description-update', action='store_true',
3269
+ help='Enable guarded infobase summary description update (disabled by default)')
3270
+ parser.add_argument('--allow-infobase-registration-drop', action='store_true',
3271
+ help='Enable destructive infobase registration removal without database deletion (disabled by default)')
3272
+ parser.add_argument('--allow-infobase-existing-registration', action='store_true',
3273
+ help='Enable guarded registration of an existing PostgreSQL infobase (disabled by default)')
3274
+ parser.add_argument('--allow-infobase-database-create', action='store_true',
3275
+ help='Enable database creation and infobase registration (disabled by default)')
3276
+ parser.add_argument('--allow-rule-remove', action='store_true',
3277
+ help='Enable destructive assignment rule removal (disabled by default)')
3278
+ parser.add_argument('--allow-rule-priority-update', action='store_true',
3279
+ help='Enable guarded assignment rule priority update (disabled by default)')
3280
+ parser.add_argument('--allow-rule-fields-update', action='store_true',
3281
+ help='Enable guarded assignment rule fields update (disabled by default)')
3282
+ parser.add_argument('--allow-rule-position-update', action='store_true',
3283
+ help='Enable guarded assignment rule reorder (disabled by default)')
3284
+ parser.add_argument('--allow-limit-remove', action='store_true',
3285
+ help='Enable destructive named-limit removal (disabled by default)')
3286
+ parser.add_argument('--allow-limit-update', action='store_true',
3287
+ help='Enable guarded named-limit update (disabled by default)')
3288
+ parser.add_argument('--allow-counter-remove', action='store_true',
3289
+ help='Enable destructive named-counter removal (disabled by default)')
3290
+ parser.add_argument('--allow-counter-description-update', action='store_true',
3291
+ help='Enable guarded named-counter description update (disabled by default)')
3292
+ parser.add_argument('--allow-counter-collection-time-update', action='store_true',
3293
+ help='Enable guarded named-counter collection time update (disabled by default)')
3294
+ parser.add_argument('--allow-counter-duration-update', action='store_true',
3295
+ help='Enable guarded named-counter duration analysis update (disabled by default)')
3296
+ parser.add_argument('--allow-counter-group-update', action='store_true',
3297
+ help='Enable guarded named-counter group update (disabled by default)')
3298
+ parser.add_argument('--allow-counter-filter-type-update', action='store_true',
3299
+ help='Enable guarded named-counter filter type update (disabled by default)')
3300
+ parser.add_argument('--allow-counter-filter-update', action='store_true',
3301
+ help='Enable guarded named-counter object filter update (disabled by default)')
3302
+ parser.add_argument('--allow-counter-analysis-flags-update', action='store_true',
3303
+ help='Enable guarded named-counter analysis flags update (disabled by default)')
3304
+ parser.add_argument('--allow-counter-clear', action='store_true',
3305
+ help='Enable counter reading clear request (disabled by default; effect unverified)')
3306
+ parser.add_argument('--allow-cluster-name-update', action='store_true',
3307
+ help='Enable verified cluster name update MCP tool (disabled by default)')
3308
+ parser.add_argument('--allow-cluster-expiration-timeout-update', action='store_true',
3309
+ help='Enable guarded cluster expiration timeout update (disabled by default)')
3310
+ parser.add_argument('--allow-cluster-security-level-update', action='store_true',
3311
+ help='Enable guarded cluster security level update (disabled by default)')
3312
+ parser.add_argument('--allow-cluster-session-fault-tolerance-level-update', action='store_true',
3313
+ help='Enable guarded cluster session fault tolerance update (disabled by default)')
3314
+ parser.add_argument('--allow-cluster-load-balancing-mode-update', action='store_true',
3315
+ help='Enable guarded cluster load balancing mode update (disabled by default)')
3316
+ parser.add_argument('--allow-cluster-kill-problem-processes-update', action='store_true',
3317
+ help='Enable guarded cluster problem-process policy update (disabled by default)')
3318
+ parser.add_argument('--allow-cluster-kill-by-memory-with-dump-update', action='store_true',
3319
+ help='Enable guarded cluster kill-by-memory dump update (disabled by default)')
3320
+ parser.add_argument('--allow-cluster-audit-update', action='store_true',
3321
+ help='Enable guarded cluster access-right audit recording update (disabled by default)')
3322
+ parser.add_argument('--allow-cluster-ping-update', action='store_true',
3323
+ help='Enable guarded cluster ping pair update (disabled by default)')
3324
+ parser.add_argument('--allow-cluster-restart-schedule-update', action='store_true',
3325
+ help='Enable guarded cluster restart schedule update (disabled by default)')
3326
+ parser.add_argument('--allow-server-restart-schedule-update', action='store_true',
3327
+ help='Enable guarded working-server restart schedule update (disabled by default)')
3328
+ parser.add_argument('--allow-server-infobases-limit-update', action='store_true',
3329
+ help='Enable guarded working-server infobases limit update (disabled by default)')
3330
+ parser.add_argument('--allow-server-connections-limit-update', action='store_true',
3331
+ help='Enable guarded working-server connections limit update (disabled by default)')
3332
+ parser.add_argument('--allow-server-safe-call-memory-limit-update', action='store_true',
3333
+ help='Enable guarded working-server safe-call memory limit update (disabled by default)')
3334
+ parser.add_argument('--allow-server-critical-total-memory-update', action='store_true',
3335
+ help='Enable guarded working-server critical total memory update (disabled by default)')
3336
+ parser.add_argument('--allow-server-temporary-total-memory-update', action='store_true',
3337
+ help='Enable guarded working-server temporary allowed total memory update (disabled by default)')
3338
+ parser.add_argument('--allow-server-temporary-memory-time-limit-update', action='store_true',
3339
+ help='Enable guarded working-server temporary memory time limit update (disabled by default)')
3340
+ parser.add_argument('--allow-server-spn-update', action='store_true',
3341
+ help='Enable guarded working-server service principal name update (disabled by default)')
3342
+ parser.add_argument('--allow-server-dedicate-managers-update', action='store_true',
3343
+ help='Enable guarded working-server dedicated managers update (disabled by default)')
3344
+ parser.add_argument('--allow-server-port-ranges-update', action='store_true',
3345
+ help='Enable guarded working-server port ranges update (disabled by default)')
3346
+ args = parser.parse_args(argv)
3347
+ endpoints = startup_endpoints(args.ras_host, args.ras_port, args.protocol, args.ras_server)
3348
+ if args.ras_server and (args.agent_user is not None or args.cluster_user is not None or
3349
+ any(value for name, value in vars(args).items() if name.startswith('allow_'))):
3350
+ raise ValueError('Additional RAS endpoints require read-only unauthenticated mode')
3351
+ if args.allow_agent_admin_register and args.protocol != '16.0':
3352
+ raise ValueError('Agent admin register MCP tool requires protocol 16.0')
3353
+ if args.allow_agent_admin_remove and args.protocol != '16.0':
3354
+ raise ValueError('Agent admin remove MCP tool requires protocol 16.0')
3355
+ if args.allow_agent_admin_remove and args.agent_user is None:
3356
+ raise ValueError('Agent admin remove MCP tool requires an authenticated agent-user')
3357
+ if args.allow_cluster_admin_register and args.protocol != '16.0':
3358
+ raise ValueError('Cluster admin register MCP tool requires protocol 16.0')
3359
+ if args.allow_cluster_admin_remove and args.protocol != '16.0':
3360
+ raise ValueError('Cluster admin remove MCP tool requires protocol 16.0')
3361
+ if args.allow_cluster_admin_remove and args.cluster_user is None:
3362
+ raise ValueError('Cluster admin remove MCP tool requires an authenticated cluster-user')
3363
+ if args.allow_rule_insert and args.protocol != '16.0':
3364
+ raise ValueError('Rule insert MCP tool requires protocol 16.0')
3365
+ if args.allow_rule_apply and args.protocol != '16.0':
3366
+ raise ValueError('Rule apply MCP tool requires protocol 16.0')
3367
+ if args.allow_rule_apply and args.cluster_user is not None:
3368
+ raise ValueError('OS-authenticated rule apply MCP tool is not enabled')
3369
+ if args.allow_service_settings_apply and args.protocol != '16.0':
3370
+ raise ValueError('Service settings apply MCP tool requires protocol 16.0')
3371
+ if args.allow_service_settings_apply and args.cluster_user is not None:
3372
+ raise ValueError('OS-authenticated service settings apply MCP tool is not enabled')
3373
+ if args.allow_service_setting_remove and args.protocol != '16.0':
3374
+ raise ValueError('Service setting remove MCP tool requires protocol 16.0')
3375
+ if args.allow_service_setting_remove and args.cluster_user is not None:
3376
+ raise ValueError('OS-authenticated service setting remove MCP tool is not enabled')
3377
+ if args.allow_service_setting_update and args.protocol != '16.0':
3378
+ raise ValueError('Service setting update MCP tool requires protocol 16.0')
3379
+ if args.allow_service_setting_update and args.cluster_user is not None:
3380
+ raise ValueError('OS-authenticated service setting update MCP tool is not enabled')
3381
+ if args.allow_service_setting_insert and args.protocol != '16.0':
3382
+ raise ValueError('Service setting insert MCP tool requires protocol 16.0')
3383
+ if args.allow_service_setting_insert and args.cluster_user is not None:
3384
+ raise ValueError('OS-authenticated service setting insert MCP tool is not enabled')
3385
+ if args.allow_profile_remove and args.protocol != '16.0':
3386
+ raise ValueError('Profile remove MCP tool requires protocol 16.0')
3387
+ if args.allow_profile_remove and args.cluster_user is not None:
3388
+ raise ValueError('OS-authenticated profile remove MCP tool is not enabled')
3389
+ if args.allow_profile_description_update and args.protocol != '16.0':
3390
+ raise ValueError('Profile description update MCP tool requires protocol 16.0')
3391
+ if args.allow_profile_description_update and args.cluster_user is not None:
3392
+ raise ValueError('OS-authenticated profile description update MCP tool is not enabled')
3393
+ if args.allow_profile_config_update and args.protocol != '16.0':
3394
+ raise ValueError('Profile config update MCP tool requires protocol 16.0')
3395
+ if args.allow_profile_config_update and args.cluster_user is not None:
3396
+ raise ValueError('OS-authenticated profile config update MCP tool is not enabled')
3397
+ if args.allow_profile_access_flags_update and args.protocol != '16.0':
3398
+ raise ValueError('Profile access flags update MCP tool requires protocol 16.0')
3399
+ if args.allow_profile_access_flags_update and args.cluster_user is not None:
3400
+ raise ValueError('OS-authenticated profile access flags update MCP tool is not enabled')
3401
+ if args.allow_profile_role_fields_update and args.protocol != '16.0':
3402
+ raise ValueError('Profile role fields update MCP tool requires protocol 16.0')
3403
+ if args.allow_profile_role_fields_update and args.cluster_user is not None:
3404
+ raise ValueError('OS-authenticated profile role fields update MCP tool is not enabled')
3405
+ if args.allow_profile_directory_remove and args.protocol != '16.0':
3406
+ raise ValueError('Profile directory remove MCP tool requires protocol 16.0')
3407
+ if args.allow_profile_directory_remove and args.cluster_user is not None:
3408
+ raise ValueError('OS-authenticated profile directory remove MCP tool is not enabled')
3409
+ if args.allow_profile_directory_update and args.protocol != '16.0':
3410
+ raise ValueError('Profile directory update MCP tool requires protocol 16.0')
3411
+ if args.allow_profile_directory_update and args.cluster_user is not None:
3412
+ raise ValueError('OS-authenticated profile directory update MCP tool is not enabled')
3413
+ if args.allow_profile_com_remove and args.protocol != '16.0':
3414
+ raise ValueError('Profile COM remove MCP tool requires protocol 16.0')
3415
+ if args.allow_profile_com_remove and args.cluster_user is not None:
3416
+ raise ValueError('OS-authenticated profile COM remove MCP tool is not enabled')
3417
+ if args.allow_profile_com_update and args.protocol != '16.0':
3418
+ raise ValueError('Profile COM update MCP tool requires protocol 16.0')
3419
+ if args.allow_profile_com_update and args.cluster_user is not None:
3420
+ raise ValueError('OS-authenticated profile COM update MCP tool is not enabled')
3421
+ if args.allow_profile_addin_remove and args.protocol != '16.0':
3422
+ raise ValueError('Profile addin remove MCP tool requires protocol 16.0')
3423
+ if args.allow_profile_addin_remove and args.cluster_user is not None:
3424
+ raise ValueError('OS-authenticated profile addin remove MCP tool is not enabled')
3425
+ if args.allow_profile_addin_update and args.protocol != '16.0':
3426
+ raise ValueError('Profile addin update MCP tool requires protocol 16.0')
3427
+ if args.allow_profile_addin_update and args.cluster_user is not None:
3428
+ raise ValueError('OS-authenticated profile addin update MCP tool is not enabled')
3429
+ if args.allow_profile_module_remove and args.protocol != '16.0':
3430
+ raise ValueError('Profile module remove MCP tool requires protocol 16.0')
3431
+ if args.allow_profile_module_remove and args.cluster_user is not None:
3432
+ raise ValueError('OS-authenticated profile module remove MCP tool is not enabled')
3433
+ if args.allow_profile_module_update and args.protocol != '16.0':
3434
+ raise ValueError('Profile module update MCP tool requires protocol 16.0')
3435
+ if args.allow_profile_module_update and args.cluster_user is not None:
3436
+ raise ValueError('OS-authenticated profile module update MCP tool is not enabled')
3437
+ if args.allow_profile_app_remove and args.protocol != '16.0':
3438
+ raise ValueError('Profile app remove MCP tool requires protocol 16.0')
3439
+ if args.allow_profile_app_remove and args.cluster_user is not None:
3440
+ raise ValueError('OS-authenticated profile app remove MCP tool is not enabled')
3441
+ if args.allow_profile_app_update and args.protocol != '16.0':
3442
+ raise ValueError('Profile app update MCP tool requires protocol 16.0')
3443
+ if args.allow_profile_app_update and args.cluster_user is not None:
3444
+ raise ValueError('OS-authenticated profile app update MCP tool is not enabled')
3445
+ if args.allow_profile_inet_remove and args.protocol != '16.0':
3446
+ raise ValueError('Profile inet remove MCP tool requires protocol 16.0')
3447
+ if args.allow_profile_inet_remove and args.cluster_user is not None:
3448
+ raise ValueError('OS-authenticated profile inet remove MCP tool is not enabled')
3449
+ if args.allow_profile_inet_update and args.protocol != '16.0':
3450
+ raise ValueError('Profile inet update MCP tool requires protocol 16.0')
3451
+ if args.allow_profile_inet_update and args.cluster_user is not None:
3452
+ raise ValueError('OS-authenticated profile inet update MCP tool is not enabled')
3453
+ if args.allow_infobase_security_profile_update and args.protocol != '16.0':
3454
+ raise ValueError('Infobase security profile update MCP tool requires protocol 16.0')
3455
+ if args.allow_infobase_security_profile_update and args.cluster_user is not None:
3456
+ raise ValueError('OS-authenticated infobase security profile update MCP tool is not enabled')
3457
+ if args.allow_infobase_safe_mode_security_profile_update and args.protocol != '16.0':
3458
+ raise ValueError('Infobase safe mode security profile update MCP tool requires protocol 16.0')
3459
+ if args.allow_infobase_safe_mode_security_profile_update and args.cluster_user is not None:
3460
+ raise ValueError('OS-authenticated infobase safe mode security profile update MCP tool is not enabled')
3461
+ if args.allow_infobase_maximum_scheduled_jobs_shift_update and args.protocol != '16.0':
3462
+ raise ValueError('Infobase maximum scheduled jobs shift update MCP tool requires protocol 16.0')
3463
+ if args.allow_infobase_maximum_scheduled_jobs_shift_update and args.cluster_user is not None:
3464
+ raise ValueError('OS-authenticated infobase maximum scheduled jobs shift update MCP tool is not enabled')
3465
+ if args.allow_infobase_minimum_scheduled_jobs_period_update and args.protocol != '16.0':
3466
+ raise ValueError('Infobase minimum scheduled jobs period update MCP tool requires protocol 16.0')
3467
+ if args.allow_infobase_minimum_scheduled_jobs_period_update and args.cluster_user is not None:
3468
+ raise ValueError('OS-authenticated infobase minimum scheduled jobs period update MCP tool is not enabled')
3469
+ if args.allow_infobase_configuration_unload_delay_update and args.protocol != '16.0':
3470
+ raise ValueError('Infobase configuration unload delay update MCP tool requires protocol 16.0')
3471
+ if args.allow_infobase_configuration_unload_delay_update and args.cluster_user is not None:
3472
+ raise ValueError('OS-authenticated infobase configuration unload delay update MCP tool is not enabled')
3473
+ if args.allow_infobase_local_speech_update and args.protocol != '16.0':
3474
+ raise ValueError('Infobase local speech-to-text update MCP tool requires protocol 16.0')
3475
+ if args.allow_infobase_local_speech_update and args.cluster_user is not None:
3476
+ raise ValueError('OS-authenticated infobase local speech-to-text update MCP tool is not enabled')
3477
+ if args.allow_infobase_external_session_manager_update and args.protocol != '16.0':
3478
+ raise ValueError('Infobase external session manager update MCP tool requires protocol 16.0')
3479
+ if args.allow_infobase_external_session_manager_update and args.cluster_user is not None:
3480
+ raise ValueError('OS-authenticated infobase external session manager update MCP tool is not enabled')
3481
+ if args.allow_infobase_reserve_working_processes_update and args.protocol != '16.0':
3482
+ raise ValueError('Infobase reserve working processes update MCP tool requires protocol 16.0')
3483
+ if args.allow_infobase_reserve_working_processes_update and args.cluster_user is not None:
3484
+ raise ValueError('OS-authenticated infobase reserve working processes update MCP tool is not enabled')
3485
+ if args.allow_infobase_denied_message_update and args.protocol != '16.0':
3486
+ raise ValueError('Infobase denied message update MCP tool requires protocol 16.0')
3487
+ if args.allow_infobase_denied_message_update and args.cluster_user is not None:
3488
+ raise ValueError('OS-authenticated infobase denied message update MCP tool is not enabled')
3489
+ if args.allow_infobase_license_distribution_update and args.protocol != '16.0':
3490
+ raise ValueError('Infobase license distribution update MCP tool requires protocol 16.0')
3491
+ if args.allow_infobase_license_distribution_update and args.cluster_user is not None:
3492
+ raise ValueError('OS-authenticated infobase license distribution update MCP tool is not enabled')
3493
+ if args.allow_infobase_sessions_deny_update and args.protocol != '16.0':
3494
+ raise ValueError('Infobase sessions deny update MCP tool requires protocol 16.0')
3495
+ if args.allow_infobase_sessions_deny_update and args.cluster_user is not None:
3496
+ raise ValueError('OS-authenticated infobase sessions deny update MCP tool is not enabled')
3497
+ if args.allow_infobase_scheduled_jobs_deny_update and args.protocol != '16.0':
3498
+ raise ValueError('Infobase scheduled jobs deny update MCP tool requires protocol 16.0')
3499
+ if args.allow_infobase_scheduled_jobs_deny_update and args.cluster_user is not None:
3500
+ raise ValueError('OS-authenticated infobase scheduled jobs deny update MCP tool is not enabled')
3501
+ if args.allow_infobase_description_update and args.protocol != '16.0':
3502
+ raise ValueError('Infobase description update MCP tool requires protocol 16.0')
3503
+ if args.allow_infobase_description_update and args.cluster_user is not None:
3504
+ raise ValueError('OS-authenticated infobase description update MCP tool is not enabled')
3505
+ if args.allow_infobase_summary_description_update and args.protocol != '16.0':
3506
+ raise ValueError('Infobase summary description update MCP tool requires protocol 16.0')
3507
+ if args.allow_infobase_summary_description_update and args.cluster_user is not None:
3508
+ raise ValueError('OS-authenticated infobase summary description update MCP tool is not enabled')
3509
+ if args.allow_infobase_registration_drop and args.protocol != '16.0':
3510
+ raise ValueError('Infobase registration drop MCP tool requires protocol 16.0')
3511
+ if args.allow_infobase_registration_drop and args.cluster_user is not None:
3512
+ raise ValueError('OS-authenticated infobase registration drop MCP tool is not enabled')
3513
+ if args.allow_infobase_existing_registration and args.protocol != '16.0':
3514
+ raise ValueError('Existing infobase registration MCP tool requires protocol 16.0')
3515
+ if args.allow_infobase_existing_registration and args.cluster_user is not None:
3516
+ raise ValueError('OS-authenticated existing infobase registration MCP tool is not enabled')
3517
+ if args.allow_infobase_database_create and args.protocol != '16.0':
3518
+ raise ValueError('Infobase database creation MCP tool requires protocol 16.0')
3519
+ if args.allow_infobase_database_create and args.cluster_user is not None:
3520
+ raise ValueError('OS-authenticated infobase database creation MCP tool is not enabled')
3521
+ if args.allow_rule_remove and args.protocol != '16.0':
3522
+ raise ValueError('Rule remove MCP tool requires protocol 16.0')
3523
+ if args.allow_rule_priority_update and args.protocol != '16.0':
3524
+ raise ValueError('Rule priority update MCP tool requires protocol 16.0')
3525
+ if args.allow_rule_fields_update and args.protocol != '16.0':
3526
+ raise ValueError('Rule fields update MCP tool requires protocol 16.0')
3527
+ if args.allow_rule_fields_update and args.cluster_user is not None:
3528
+ raise ValueError('OS-authenticated rule fields update MCP tool is not enabled')
3529
+ if args.allow_rule_position_update and args.protocol != '16.0':
3530
+ raise ValueError('Rule position update MCP tool requires protocol 16.0')
3531
+ if args.allow_rule_position_update and args.cluster_user is not None:
3532
+ raise ValueError('OS-authenticated rule position update MCP tool is not enabled')
3533
+ if args.allow_limit_remove and args.protocol != '16.0':
3534
+ raise ValueError('Limit remove MCP tool requires protocol 16.0')
3535
+ if args.allow_limit_update and args.protocol != '16.0':
3536
+ raise ValueError('Limit update MCP tool requires protocol 16.0')
3537
+ if args.allow_limit_update and args.cluster_user is not None:
3538
+ raise ValueError('OS-authenticated limit update MCP tool is not enabled')
3539
+ if args.allow_counter_remove and args.protocol != '16.0':
3540
+ raise ValueError('Counter remove MCP tool requires protocol 16.0')
3541
+ if args.allow_counter_remove and args.cluster_user is not None:
3542
+ raise ValueError('OS-authenticated counter remove MCP tool is not enabled')
3543
+ if args.allow_counter_description_update and args.protocol != '16.0':
3544
+ raise ValueError('Counter description update MCP tool requires protocol 16.0')
3545
+ if args.allow_counter_description_update and args.cluster_user is not None:
3546
+ raise ValueError('OS-authenticated counter description update MCP tool is not enabled')
3547
+ if args.allow_counter_collection_time_update and args.protocol != '16.0':
3548
+ raise ValueError('Counter collection time update MCP tool requires protocol 16.0')
3549
+ if args.allow_counter_collection_time_update and args.cluster_user is not None:
3550
+ raise ValueError('OS-authenticated counter collection time update MCP tool is not enabled')
3551
+ if args.allow_counter_duration_update and args.protocol != '16.0':
3552
+ raise ValueError('Counter duration update MCP tool requires protocol 16.0')
3553
+ if args.allow_counter_duration_update and args.cluster_user is not None:
3554
+ raise ValueError('OS-authenticated counter duration update MCP tool is not enabled')
3555
+ if args.allow_counter_group_update and args.protocol != '16.0':
3556
+ raise ValueError('Counter group update MCP tool requires protocol 16.0')
3557
+ if args.allow_counter_group_update and args.cluster_user is not None:
3558
+ raise ValueError('OS-authenticated counter group update MCP tool is not enabled')
3559
+ if args.allow_counter_filter_type_update and args.protocol != '16.0':
3560
+ raise ValueError('Counter filter type update MCP tool requires protocol 16.0')
3561
+ if args.allow_counter_filter_type_update and args.cluster_user is not None:
3562
+ raise ValueError('OS-authenticated counter filter type update MCP tool is not enabled')
3563
+ if args.allow_counter_filter_update and args.protocol != '16.0':
3564
+ raise ValueError('Counter filter update MCP tool requires protocol 16.0')
3565
+ if args.allow_counter_filter_update and args.cluster_user is not None:
3566
+ raise ValueError('OS-authenticated counter filter update MCP tool is not enabled')
3567
+ if args.allow_counter_analysis_flags_update and args.protocol != '16.0':
3568
+ raise ValueError('Counter analysis flags update MCP tool requires protocol 16.0')
3569
+ if args.allow_counter_analysis_flags_update and args.cluster_user is not None:
3570
+ raise ValueError('OS-authenticated counter analysis flags update MCP tool is not enabled')
3571
+ if args.allow_counter_clear and args.protocol != '16.0':
3572
+ raise ValueError('Counter clear MCP tool requires protocol 16.0')
3573
+ if args.allow_counter_clear and args.cluster_user is not None:
3574
+ raise ValueError('OS-authenticated counter clear MCP tool is not enabled')
3575
+ if args.allow_process_turn_off and args.protocol != '16.0':
3576
+ raise ValueError('Process turn-off MCP tool requires protocol 16.0')
3577
+ if args.allow_process_turn_off and args.cluster_user is not None:
3578
+ raise ValueError('OS-authenticated process turn-off is not verified')
3579
+ if args.allow_cluster_insert and args.protocol != '16.0':
3580
+ raise ValueError('Cluster insert MCP tool requires protocol 16.0')
3581
+ if args.allow_cluster_insert and args.agent_user is not None:
3582
+ raise ValueError('OS-authenticated cluster insert MCP tool is not enabled')
3583
+ if args.allow_cluster_remove and args.protocol != '16.0':
3584
+ raise ValueError('Cluster remove MCP tool requires protocol 16.0')
3585
+ if args.allow_cluster_remove and args.cluster_user is not None:
3586
+ raise ValueError('OS-authenticated cluster remove MCP tool is not enabled')
3587
+ if args.allow_cluster_name_update and args.protocol != '16.0':
3588
+ raise ValueError('Cluster name update MCP tool requires protocol 16.0')
3589
+ if args.allow_cluster_name_update and args.agent_user is not None:
3590
+ raise ValueError('OS-authenticated cluster name update MCP tool is not enabled')
3591
+ if args.allow_cluster_expiration_timeout_update and args.protocol != '16.0':
3592
+ raise ValueError('Cluster timeout update MCP tool requires protocol 16.0')
3593
+ if args.allow_cluster_expiration_timeout_update and args.agent_user is not None:
3594
+ raise ValueError('OS-authenticated cluster timeout update MCP tool is not enabled')
3595
+ if args.allow_cluster_security_level_update and args.protocol != '16.0':
3596
+ raise ValueError('Cluster security level update MCP tool requires protocol 16.0')
3597
+ if args.allow_cluster_security_level_update and args.agent_user is not None:
3598
+ raise ValueError('OS-authenticated cluster security level update MCP tool is not enabled')
3599
+ if args.allow_cluster_session_fault_tolerance_level_update and args.protocol != '16.0':
3600
+ raise ValueError('Cluster session fault tolerance update MCP tool requires protocol 16.0')
3601
+ if args.allow_cluster_session_fault_tolerance_level_update and args.agent_user is not None:
3602
+ raise ValueError('OS-authenticated cluster session fault tolerance update MCP tool is not enabled')
3603
+ if args.allow_cluster_load_balancing_mode_update and args.protocol != '16.0':
3604
+ raise ValueError('Cluster load balancing update MCP tool requires protocol 16.0')
3605
+ if args.allow_cluster_load_balancing_mode_update and args.agent_user is not None:
3606
+ raise ValueError('OS-authenticated cluster load balancing update MCP tool is not enabled')
3607
+ if args.allow_cluster_kill_problem_processes_update and args.protocol != '16.0':
3608
+ raise ValueError('Cluster problem-process policy update MCP tool requires protocol 16.0')
3609
+ if args.allow_cluster_kill_problem_processes_update and args.agent_user is not None:
3610
+ raise ValueError('OS-authenticated cluster problem-process policy update MCP tool is not enabled')
3611
+ if args.allow_cluster_kill_by_memory_with_dump_update and args.protocol != '16.0':
3612
+ raise ValueError('Cluster kill-by-memory dump update MCP tool requires protocol 16.0')
3613
+ if args.allow_cluster_kill_by_memory_with_dump_update and args.agent_user is not None:
3614
+ raise ValueError('OS-authenticated cluster kill-by-memory dump update MCP tool is not enabled')
3615
+ if args.allow_cluster_audit_update and args.protocol != '16.0':
3616
+ raise ValueError('Cluster audit update MCP tool requires protocol 16.0')
3617
+ if args.allow_cluster_audit_update and args.agent_user is not None:
3618
+ raise ValueError('OS-authenticated cluster audit update MCP tool is not enabled')
3619
+ if args.allow_cluster_ping_update and args.protocol != '16.0':
3620
+ raise ValueError('Cluster ping update MCP tool requires protocol 16.0')
3621
+ if args.allow_cluster_ping_update and args.agent_user is not None:
3622
+ raise ValueError('OS-authenticated cluster ping update MCP tool is not enabled')
3623
+ if args.allow_cluster_restart_schedule_update and args.protocol != '16.0':
3624
+ raise ValueError('Cluster restart schedule update MCP tool requires protocol 16.0')
3625
+ if args.allow_cluster_restart_schedule_update and args.agent_user is not None:
3626
+ raise ValueError('OS-authenticated cluster restart schedule update MCP tool is not enabled')
3627
+ if args.allow_server_restart_schedule_update and args.protocol != '16.0':
3628
+ raise ValueError('Server restart schedule update MCP tool requires protocol 16.0')
3629
+ if args.allow_server_restart_schedule_update and args.cluster_user is not None:
3630
+ raise ValueError('OS-authenticated server restart schedule update MCP tool is not enabled')
3631
+ if args.allow_server_infobases_limit_update and args.protocol != '16.0':
3632
+ raise ValueError('Server infobases limit update MCP tool requires protocol 16.0')
3633
+ if args.allow_server_infobases_limit_update and args.cluster_user is not None:
3634
+ raise ValueError('OS-authenticated server infobases limit update MCP tool is not enabled')
3635
+ if args.allow_server_connections_limit_update and args.protocol != '16.0':
3636
+ raise ValueError('Server connections limit update MCP tool requires protocol 16.0')
3637
+ if args.allow_server_connections_limit_update and args.cluster_user is not None:
3638
+ raise ValueError('OS-authenticated server connections limit update MCP tool is not enabled')
3639
+ if args.allow_server_safe_call_memory_limit_update and args.protocol != '16.0':
3640
+ raise ValueError('Server safe-call memory limit update MCP tool requires protocol 16.0')
3641
+ if args.allow_server_safe_call_memory_limit_update and args.cluster_user is not None:
3642
+ raise ValueError('OS-authenticated server safe-call memory limit update MCP tool is not enabled')
3643
+ if args.allow_server_critical_total_memory_update and args.protocol != '16.0':
3644
+ raise ValueError('Server critical total memory update MCP tool requires protocol 16.0')
3645
+ if args.allow_server_critical_total_memory_update and args.cluster_user is not None:
3646
+ raise ValueError('OS-authenticated server critical total memory update MCP tool is not enabled')
3647
+ if args.allow_server_temporary_total_memory_update and args.protocol != '16.0':
3648
+ raise ValueError('Server temporary allowed total memory update MCP tool requires protocol 16.0')
3649
+ if args.allow_server_temporary_total_memory_update and args.cluster_user is not None:
3650
+ raise ValueError('OS-authenticated server temporary allowed total memory update MCP tool is not enabled')
3651
+ if args.allow_server_temporary_memory_time_limit_update and args.protocol != '16.0':
3652
+ raise ValueError('Server temporary memory time limit update MCP tool requires protocol 16.0')
3653
+ if args.allow_server_temporary_memory_time_limit_update and args.cluster_user is not None:
3654
+ raise ValueError('OS-authenticated server temporary memory time limit update MCP tool is not enabled')
3655
+ if args.allow_server_spn_update and args.protocol != '16.0':
3656
+ raise ValueError('Server SPN update MCP tool requires protocol 16.0')
3657
+ if args.allow_server_spn_update and args.cluster_user is not None:
3658
+ raise ValueError('OS-authenticated server SPN update MCP tool is not enabled')
3659
+ if args.allow_server_dedicate_managers_update and args.protocol != '16.0':
3660
+ raise ValueError('Server dedicated managers update MCP tool requires protocol 16.0')
3661
+ if args.allow_server_dedicate_managers_update and args.cluster_user is not None:
3662
+ raise ValueError('OS-authenticated server dedicated managers update MCP tool is not enabled')
3663
+ if args.allow_server_port_ranges_update and args.protocol != '16.0':
3664
+ raise ValueError('Server port ranges update MCP tool requires protocol 16.0')
3665
+ if args.allow_server_port_ranges_update and args.cluster_user is not None:
3666
+ raise ValueError('OS-authenticated server port ranges update MCP tool is not enabled')
3667
+ for name in (args.agent_user, args.cluster_user):
3668
+ if name is not None and (not name or '\x00' in name or len(name.encode('utf-8')) > 1024):
3669
+ raise ValueError('Invalid administrator name')
3670
+ factory = partial(RacClient, args.ras_host, args.ras_port, args.protocol,
3671
+ agent_user=args.agent_user, cluster_user=args.cluster_user)
3672
+ service = ReadService(factory)
3673
+ options = {}
3674
+ if args.ras_server:
3675
+ options['workspace_endpoints'] = endpoints
3676
+ if args.allow_agent_admin_register:
3677
+ options['allow_agent_admin_register'] = True
3678
+ if args.allow_agent_admin_remove:
3679
+ options['allow_agent_admin_remove'] = True
3680
+ if args.allow_cluster_admin_register:
3681
+ options['allow_cluster_admin_register'] = True
3682
+ if args.allow_cluster_admin_remove:
3683
+ options['allow_cluster_admin_remove'] = True
3684
+ if args.allow_rule_insert:
3685
+ options['allow_rule_insert'] = True
3686
+ if args.allow_rule_apply:
3687
+ options['allow_rule_apply'] = True
3688
+ if args.allow_service_settings_apply:
3689
+ options['allow_service_settings_apply'] = True
3690
+ if args.allow_service_setting_remove:
3691
+ options['allow_service_setting_remove'] = True
3692
+ if args.allow_service_setting_update:
3693
+ options['allow_service_setting_update'] = True
3694
+ if args.allow_service_setting_insert:
3695
+ options['allow_service_setting_insert'] = True
3696
+ if args.allow_profile_remove:
3697
+ options['allow_profile_remove'] = True
3698
+ if args.allow_profile_description_update:
3699
+ options['allow_profile_description_update'] = True
3700
+ if args.allow_profile_config_update:
3701
+ options['allow_profile_config_update'] = True
3702
+ if args.allow_profile_access_flags_update:
3703
+ options['allow_profile_access_flags_update'] = True
3704
+ if args.allow_profile_role_fields_update:
3705
+ options['allow_profile_role_fields_update'] = True
3706
+ if args.allow_profile_directory_remove:
3707
+ options['allow_profile_directory_remove'] = True
3708
+ if args.allow_profile_directory_update:
3709
+ options['allow_profile_directory_update'] = True
3710
+ if args.allow_profile_com_remove:
3711
+ options['allow_profile_com_remove'] = True
3712
+ if args.allow_profile_com_update:
3713
+ options['allow_profile_com_update'] = True
3714
+ if args.allow_profile_addin_remove:
3715
+ options['allow_profile_addin_remove'] = True
3716
+ if args.allow_profile_addin_update:
3717
+ options['allow_profile_addin_update'] = True
3718
+ if args.allow_profile_module_remove:
3719
+ options['allow_profile_module_remove'] = True
3720
+ if args.allow_profile_module_update:
3721
+ options['allow_profile_module_update'] = True
3722
+ if args.allow_profile_app_remove:
3723
+ options['allow_profile_app_remove'] = True
3724
+ if args.allow_profile_app_update:
3725
+ options['allow_profile_app_update'] = True
3726
+ if args.allow_profile_inet_remove:
3727
+ options['allow_profile_inet_remove'] = True
3728
+ if args.allow_profile_inet_update:
3729
+ options['allow_profile_inet_update'] = True
3730
+ if args.allow_infobase_security_profile_update:
3731
+ options['allow_infobase_security_profile_update'] = True
3732
+ if args.allow_infobase_safe_mode_security_profile_update:
3733
+ options['allow_infobase_safe_mode_security_profile_update'] = True
3734
+ if args.allow_infobase_maximum_scheduled_jobs_shift_update:
3735
+ options['allow_infobase_maximum_scheduled_jobs_shift_update'] = True
3736
+ if args.allow_infobase_minimum_scheduled_jobs_period_update:
3737
+ options['allow_infobase_minimum_scheduled_jobs_period_update'] = True
3738
+ if args.allow_infobase_configuration_unload_delay_update:
3739
+ options['allow_infobase_configuration_unload_delay_update'] = True
3740
+ if args.allow_infobase_local_speech_update:
3741
+ options['allow_infobase_local_speech_update'] = True
3742
+ if args.allow_infobase_external_session_manager_update:
3743
+ options['allow_infobase_external_session_manager_update'] = True
3744
+ if args.allow_infobase_reserve_working_processes_update:
3745
+ options['allow_infobase_reserve_working_processes_update'] = True
3746
+ if args.allow_infobase_denied_message_update:
3747
+ options['allow_infobase_denied_message_update'] = True
3748
+ if args.allow_infobase_license_distribution_update:
3749
+ options['allow_infobase_license_distribution_update'] = True
3750
+ if args.allow_infobase_sessions_deny_update:
3751
+ options['allow_infobase_sessions_deny_update'] = True
3752
+ if args.allow_infobase_scheduled_jobs_deny_update:
3753
+ options['allow_infobase_scheduled_jobs_deny_update'] = True
3754
+ if args.allow_infobase_description_update:
3755
+ options['allow_infobase_description_update'] = True
3756
+ if args.allow_infobase_summary_description_update:
3757
+ options['allow_infobase_summary_description_update'] = True
3758
+ if args.allow_infobase_registration_drop:
3759
+ options['allow_infobase_registration_drop'] = True
3760
+ if args.allow_infobase_existing_registration:
3761
+ options['allow_infobase_existing_registration'] = True
3762
+ if args.allow_infobase_database_create:
3763
+ options['allow_infobase_database_create'] = True
3764
+ if args.allow_rule_remove:
3765
+ options['allow_rule_remove'] = True
3766
+ if args.allow_rule_priority_update:
3767
+ options['allow_rule_priority_update'] = True
3768
+ if args.allow_rule_fields_update:
3769
+ options['allow_rule_fields_update'] = True
3770
+ if args.allow_rule_position_update:
3771
+ options['allow_rule_position_update'] = True
3772
+ if args.allow_limit_remove:
3773
+ options['allow_limit_remove'] = True
3774
+ if args.allow_limit_update:
3775
+ options['allow_limit_update'] = True
3776
+ if args.allow_counter_remove:
3777
+ options['allow_counter_remove'] = True
3778
+ if args.allow_counter_description_update:
3779
+ options['allow_counter_description_update'] = True
3780
+ if args.allow_counter_collection_time_update:
3781
+ options['allow_counter_collection_time_update'] = True
3782
+ if args.allow_counter_duration_update:
3783
+ options['allow_counter_duration_update'] = True
3784
+ if args.allow_counter_group_update:
3785
+ options['allow_counter_group_update'] = True
3786
+ if args.allow_counter_filter_type_update:
3787
+ options['allow_counter_filter_type_update'] = True
3788
+ if args.allow_counter_filter_update:
3789
+ options['allow_counter_filter_update'] = True
3790
+ if args.allow_counter_analysis_flags_update:
3791
+ options['allow_counter_analysis_flags_update'] = True
3792
+ if args.allow_counter_clear:
3793
+ options['allow_counter_clear'] = True
3794
+ if args.allow_process_turn_off:
3795
+ options['allow_process_turn_off'] = True
3796
+ if args.allow_cluster_insert:
3797
+ options['allow_cluster_insert'] = True
3798
+ if args.allow_cluster_remove:
3799
+ options['allow_cluster_remove'] = True
3800
+ if args.allow_cluster_name_update:
3801
+ options['allow_cluster_name_update'] = True
3802
+ if args.allow_cluster_expiration_timeout_update:
3803
+ options['allow_cluster_expiration_timeout_update'] = True
3804
+ if args.allow_cluster_security_level_update:
3805
+ options['allow_cluster_security_level_update'] = True
3806
+ if args.allow_cluster_session_fault_tolerance_level_update:
3807
+ options['allow_cluster_session_fault_tolerance_level_update'] = True
3808
+ if args.allow_cluster_load_balancing_mode_update:
3809
+ options['allow_cluster_load_balancing_mode_update'] = True
3810
+ if args.allow_cluster_kill_problem_processes_update:
3811
+ options['allow_cluster_kill_problem_processes_update'] = True
3812
+ if args.allow_cluster_kill_by_memory_with_dump_update:
3813
+ options['allow_cluster_kill_by_memory_with_dump_update'] = True
3814
+ if args.allow_cluster_audit_update:
3815
+ options['allow_cluster_audit_update'] = True
3816
+ if args.allow_cluster_ping_update:
3817
+ options['allow_cluster_ping_update'] = True
3818
+ if args.allow_cluster_restart_schedule_update:
3819
+ options['allow_cluster_restart_schedule_update'] = True
3820
+ if args.allow_server_restart_schedule_update:
3821
+ options['allow_server_restart_schedule_update'] = True
3822
+ if args.allow_server_infobases_limit_update:
3823
+ options['allow_server_infobases_limit_update'] = True
3824
+ if args.allow_server_connections_limit_update:
3825
+ options['allow_server_connections_limit_update'] = True
3826
+ if args.allow_server_safe_call_memory_limit_update:
3827
+ options['allow_server_safe_call_memory_limit_update'] = True
3828
+ if args.allow_server_critical_total_memory_update:
3829
+ options['allow_server_critical_total_memory_update'] = True
3830
+ if args.allow_server_temporary_total_memory_update:
3831
+ options['allow_server_temporary_total_memory_update'] = True
3832
+ if args.allow_server_temporary_memory_time_limit_update:
3833
+ options['allow_server_temporary_memory_time_limit_update'] = True
3834
+ if args.allow_server_spn_update:
3835
+ options['allow_server_spn_update'] = True
3836
+ if args.allow_server_dedicate_managers_update:
3837
+ options['allow_server_dedicate_managers_update'] = True
3838
+ if args.allow_server_port_ranges_update:
3839
+ options['allow_server_port_ranges_update'] = True
3840
+ server = create_server(service, **options)
3841
+ asyncio.run(server.run_stdio_async())
3842
+
3843
+
3844
+ if __name__ == '__main__':
3845
+ main()