awslabs.eks-mcp-server 0.1.16__py3-none-any.whl → 0.1.18__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.
- awslabs/__init__.py +1 -0
- awslabs/eks_mcp_server/__init__.py +1 -1
- awslabs/eks_mcp_server/cloudwatch_handler.py +42 -54
- awslabs/eks_mcp_server/cloudwatch_metrics_guidance_handler.py +15 -10
- awslabs/eks_mcp_server/eks_kb_handler.py +22 -4
- awslabs/eks_mcp_server/eks_stack_handler.py +97 -122
- awslabs/eks_mcp_server/iam_handler.py +36 -36
- awslabs/eks_mcp_server/insights_handler.py +29 -32
- awslabs/eks_mcp_server/k8s_handler.py +125 -145
- awslabs/eks_mcp_server/models.py +52 -62
- awslabs/eks_mcp_server/vpc_config_handler.py +18 -35
- {awslabs_eks_mcp_server-0.1.16.dist-info → awslabs_eks_mcp_server-0.1.18.dist-info}/METADATA +2 -2
- awslabs_eks_mcp_server-0.1.18.dist-info/RECORD +28 -0
- {awslabs_eks_mcp_server-0.1.16.dist-info → awslabs_eks_mcp_server-0.1.18.dist-info}/WHEEL +1 -1
- awslabs_eks_mcp_server-0.1.16.dist-info/RECORD +0 -28
- {awslabs_eks_mcp_server-0.1.16.dist-info → awslabs_eks_mcp_server-0.1.18.dist-info}/entry_points.txt +0 -0
- {awslabs_eks_mcp_server-0.1.16.dist-info → awslabs_eks_mcp_server-0.1.18.dist-info}/licenses/LICENSE +0 -0
- {awslabs_eks_mcp_server-0.1.16.dist-info → awslabs_eks_mcp_server-0.1.18.dist-info}/licenses/NOTICE +0 -0
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
"""EKS stack handler for the EKS MCP Server."""
|
|
16
16
|
|
|
17
|
+
import json
|
|
17
18
|
import os
|
|
18
19
|
import yaml
|
|
19
20
|
from awslabs.eks_mcp_server.aws_helper import AwsHelper
|
|
@@ -31,15 +32,12 @@ from awslabs.eks_mcp_server.consts import (
|
|
|
31
32
|
)
|
|
32
33
|
from awslabs.eks_mcp_server.logging_helper import LogLevel, log_with_request_id
|
|
33
34
|
from awslabs.eks_mcp_server.models import (
|
|
34
|
-
|
|
35
|
-
DeployStackResponse,
|
|
36
|
-
DescribeStackResponse,
|
|
37
|
-
GenerateTemplateResponse,
|
|
35
|
+
ManageEksStacksData,
|
|
38
36
|
)
|
|
39
37
|
from mcp.server.fastmcp import Context
|
|
40
|
-
from mcp.types import TextContent
|
|
38
|
+
from mcp.types import CallToolResult, TextContent
|
|
41
39
|
from pydantic import Field
|
|
42
|
-
from typing import Any, Dict, Optional, Tuple
|
|
40
|
+
from typing import Any, Dict, Optional, Tuple
|
|
43
41
|
|
|
44
42
|
|
|
45
43
|
class EksStackHandler:
|
|
@@ -128,9 +126,7 @@ class EksStackHandler:
|
|
|
128
126
|
description="""Name of the EKS cluster (for generate, deploy, describe and delete operations).
|
|
129
127
|
This name will be used to derive the CloudFormation stack name and will be embedded in the cluster resources.""",
|
|
130
128
|
),
|
|
131
|
-
) ->
|
|
132
|
-
GenerateTemplateResponse, DeployStackResponse, DescribeStackResponse, DeleteStackResponse
|
|
133
|
-
]:
|
|
129
|
+
) -> CallToolResult:
|
|
134
130
|
"""Manage EKS CloudFormation stacks with both read and write operations.
|
|
135
131
|
|
|
136
132
|
This tool provides operations for managing EKS CloudFormation stacks, including creating templates,
|
|
@@ -158,10 +154,10 @@ class EksStackHandler:
|
|
|
158
154
|
|
|
159
155
|
## Response Information
|
|
160
156
|
The response type varies based on the operation:
|
|
161
|
-
- generate: Returns
|
|
162
|
-
- deploy: Returns
|
|
163
|
-
- describe: Returns
|
|
164
|
-
- delete: Returns
|
|
157
|
+
- generate: Returns CallToolResult with the template path
|
|
158
|
+
- deploy: Returns CallToolResult with stack name, ARN, and cluster name
|
|
159
|
+
- describe: Returns CallToolResult with stack details, outputs, and status
|
|
160
|
+
- delete: Returns CallToolResult with stack name, ID, and cluster name
|
|
165
161
|
|
|
166
162
|
## Usage Tips
|
|
167
163
|
- Use the describe operation first to check if a cluster already exists
|
|
@@ -177,8 +173,7 @@ class EksStackHandler:
|
|
|
177
173
|
cluster_name: Name of the EKS cluster (for all operations)
|
|
178
174
|
|
|
179
175
|
Returns:
|
|
180
|
-
|
|
181
|
-
Response specific to the operation performed
|
|
176
|
+
ManageEksStacksResponse: Response with fields populated based on the operation performed
|
|
182
177
|
"""
|
|
183
178
|
try:
|
|
184
179
|
# Check if write access is disabled and trying to perform a mutating operation
|
|
@@ -188,40 +183,11 @@ class EksStackHandler:
|
|
|
188
183
|
error_message = f'Operation {operation} is not allowed without write access'
|
|
189
184
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
190
185
|
|
|
191
|
-
# Return
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
template_path='',
|
|
197
|
-
)
|
|
198
|
-
elif operation == DEPLOY_OPERATION:
|
|
199
|
-
return DeployStackResponse(
|
|
200
|
-
isError=True,
|
|
201
|
-
content=[TextContent(type='text', text=error_message)],
|
|
202
|
-
stack_name='',
|
|
203
|
-
stack_arn='',
|
|
204
|
-
cluster_name=cluster_name or '',
|
|
205
|
-
)
|
|
206
|
-
elif operation == DELETE_OPERATION:
|
|
207
|
-
return DeleteStackResponse(
|
|
208
|
-
isError=True,
|
|
209
|
-
content=[TextContent(type='text', text=error_message)],
|
|
210
|
-
stack_name='',
|
|
211
|
-
stack_id='',
|
|
212
|
-
cluster_name=cluster_name or '',
|
|
213
|
-
)
|
|
214
|
-
else: # Default to describe operation
|
|
215
|
-
return DescribeStackResponse(
|
|
216
|
-
isError=True,
|
|
217
|
-
content=[TextContent(type='text', text=error_message)],
|
|
218
|
-
stack_name='',
|
|
219
|
-
stack_id='',
|
|
220
|
-
cluster_name=cluster_name or '',
|
|
221
|
-
creation_time='',
|
|
222
|
-
stack_status='',
|
|
223
|
-
outputs={},
|
|
224
|
-
)
|
|
186
|
+
# Return error response
|
|
187
|
+
return CallToolResult(
|
|
188
|
+
isError=True,
|
|
189
|
+
content=[TextContent(type='text', text=error_message)],
|
|
190
|
+
)
|
|
225
191
|
|
|
226
192
|
if operation == GENERATE_OPERATION:
|
|
227
193
|
if template_file is None:
|
|
@@ -270,16 +236,9 @@ class EksStackHandler:
|
|
|
270
236
|
else:
|
|
271
237
|
error_message = f'Invalid operation: {operation}. Must be one of: generate, deploy, describe, delete'
|
|
272
238
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
273
|
-
|
|
274
|
-
return DescribeStackResponse(
|
|
239
|
+
return CallToolResult(
|
|
275
240
|
isError=True,
|
|
276
241
|
content=[TextContent(type='text', text=error_message)],
|
|
277
|
-
stack_name='',
|
|
278
|
-
stack_id='',
|
|
279
|
-
cluster_name=cluster_name or '',
|
|
280
|
-
creation_time='',
|
|
281
|
-
stack_status='',
|
|
282
|
-
outputs={},
|
|
283
242
|
)
|
|
284
243
|
except ValueError as e:
|
|
285
244
|
# Re-raise ValueError for parameter validation errors
|
|
@@ -288,21 +247,14 @@ class EksStackHandler:
|
|
|
288
247
|
except Exception as e:
|
|
289
248
|
error_message = f'Error in manage_eks_stacks: {str(e)}'
|
|
290
249
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
291
|
-
|
|
292
|
-
return DescribeStackResponse(
|
|
250
|
+
return CallToolResult(
|
|
293
251
|
isError=True,
|
|
294
252
|
content=[TextContent(type='text', text=error_message)],
|
|
295
|
-
stack_name='',
|
|
296
|
-
stack_id='',
|
|
297
|
-
cluster_name=cluster_name or '',
|
|
298
|
-
creation_time='',
|
|
299
|
-
stack_status='',
|
|
300
|
-
outputs={},
|
|
301
253
|
)
|
|
302
254
|
|
|
303
255
|
async def _generate_template(
|
|
304
256
|
self, ctx: Context, template_path: str, cluster_name: str
|
|
305
|
-
) ->
|
|
257
|
+
) -> CallToolResult:
|
|
306
258
|
"""Generate a CloudFormation template at the specified path with the cluster name embedded.
|
|
307
259
|
|
|
308
260
|
The template creates a complete EKS environment including:
|
|
@@ -354,29 +306,42 @@ class EksStackHandler:
|
|
|
354
306
|
f'Generated CloudFormation template at {template_path} with cluster name {cluster_name}',
|
|
355
307
|
)
|
|
356
308
|
|
|
357
|
-
|
|
309
|
+
data = ManageEksStacksData(
|
|
310
|
+
operation=GENERATE_OPERATION,
|
|
311
|
+
template_path=template_path,
|
|
312
|
+
cluster_name=cluster_name,
|
|
313
|
+
stack_name='',
|
|
314
|
+
stack_id='',
|
|
315
|
+
stack_arn='',
|
|
316
|
+
creation_time='',
|
|
317
|
+
stack_status='',
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return CallToolResult(
|
|
358
321
|
isError=False,
|
|
359
322
|
content=[
|
|
360
323
|
TextContent(
|
|
361
324
|
type='text',
|
|
362
325
|
text=f'CloudFormation template generated at {template_path} with cluster name {cluster_name}',
|
|
363
|
-
)
|
|
326
|
+
),
|
|
327
|
+
TextContent(
|
|
328
|
+
type='text',
|
|
329
|
+
text=json.dumps(data.model_dump()),
|
|
330
|
+
),
|
|
364
331
|
],
|
|
365
|
-
template_path=template_path,
|
|
366
332
|
)
|
|
367
333
|
except Exception as e:
|
|
368
334
|
error_message = f'Failed to generate template: {str(e)}'
|
|
369
335
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
370
336
|
|
|
371
|
-
return
|
|
337
|
+
return CallToolResult(
|
|
372
338
|
isError=True,
|
|
373
339
|
content=[TextContent(type='text', text=error_message or 'Unknown error')],
|
|
374
|
-
template_path='',
|
|
375
340
|
)
|
|
376
341
|
|
|
377
342
|
async def _deploy_stack(
|
|
378
343
|
self, ctx: Context, template_file: str, stack_name: str, cluster_name: str
|
|
379
|
-
) ->
|
|
344
|
+
) -> CallToolResult:
|
|
380
345
|
"""Deploy a CloudFormation stack from the specified template file."""
|
|
381
346
|
try:
|
|
382
347
|
# Create CloudFormation client
|
|
@@ -395,14 +360,11 @@ class EksStackHandler:
|
|
|
395
360
|
if stack:
|
|
396
361
|
stack_exists = True
|
|
397
362
|
if not success:
|
|
398
|
-
return
|
|
363
|
+
return CallToolResult(
|
|
399
364
|
isError=True,
|
|
400
365
|
content=[
|
|
401
366
|
TextContent(type='text', text=error_message or 'Unknown error')
|
|
402
367
|
],
|
|
403
|
-
stack_name=stack_name,
|
|
404
|
-
stack_arn='',
|
|
405
|
-
cluster_name=cluster_name,
|
|
406
368
|
)
|
|
407
369
|
except Exception:
|
|
408
370
|
# Stack doesn't exist, we'll create it
|
|
@@ -447,33 +409,42 @@ class EksStackHandler:
|
|
|
447
409
|
f'CloudFormation stack {operation_text} initiated. Stack ARN: {response["StackId"]}',
|
|
448
410
|
)
|
|
449
411
|
|
|
450
|
-
|
|
412
|
+
data = ManageEksStacksData(
|
|
413
|
+
operation=DEPLOY_OPERATION,
|
|
414
|
+
stack_name=stack_name,
|
|
415
|
+
stack_arn=response['StackId'],
|
|
416
|
+
stack_id=response['StackId'],
|
|
417
|
+
cluster_name=cluster_name,
|
|
418
|
+
template_path='',
|
|
419
|
+
creation_time='',
|
|
420
|
+
stack_status='',
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
return CallToolResult(
|
|
451
424
|
isError=False,
|
|
452
425
|
content=[
|
|
453
426
|
TextContent(
|
|
454
427
|
type='text',
|
|
455
428
|
text=f'CloudFormation stack {operation_text} initiated. Stack {operation_text} is in progress and typically takes 15-20 minutes to complete.',
|
|
456
|
-
)
|
|
429
|
+
),
|
|
430
|
+
TextContent(
|
|
431
|
+
type='text',
|
|
432
|
+
text=json.dumps(data.model_dump()),
|
|
433
|
+
),
|
|
457
434
|
],
|
|
458
|
-
stack_name=stack_name,
|
|
459
|
-
stack_arn=response['StackId'],
|
|
460
|
-
cluster_name=cluster_name,
|
|
461
435
|
)
|
|
462
436
|
except Exception as e:
|
|
463
437
|
error_message = f'Failed to deploy stack: {str(e)}'
|
|
464
438
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
465
439
|
|
|
466
|
-
return
|
|
440
|
+
return CallToolResult(
|
|
467
441
|
isError=True,
|
|
468
442
|
content=[TextContent(type='text', text=error_message or 'Unknown error')],
|
|
469
|
-
stack_name=stack_name,
|
|
470
|
-
stack_arn='',
|
|
471
|
-
cluster_name=cluster_name,
|
|
472
443
|
)
|
|
473
444
|
|
|
474
445
|
async def _describe_stack(
|
|
475
446
|
self, ctx: Context, stack_name: str, cluster_name: str
|
|
476
|
-
) ->
|
|
447
|
+
) -> CallToolResult:
|
|
477
448
|
"""Describe a CloudFormation stack."""
|
|
478
449
|
try:
|
|
479
450
|
# Verify stack ownership
|
|
@@ -491,15 +462,9 @@ class EksStackHandler:
|
|
|
491
462
|
creation_time = stack['CreationTime'].isoformat()
|
|
492
463
|
stack_status = stack['StackStatus']
|
|
493
464
|
|
|
494
|
-
return
|
|
465
|
+
return CallToolResult(
|
|
495
466
|
isError=True,
|
|
496
467
|
content=[TextContent(type='text', text=error_message or 'Unknown error')],
|
|
497
|
-
stack_name=stack_name,
|
|
498
|
-
stack_id=stack_id,
|
|
499
|
-
cluster_name=cluster_name,
|
|
500
|
-
creation_time=creation_time,
|
|
501
|
-
stack_status=stack_status,
|
|
502
|
-
outputs={},
|
|
503
468
|
)
|
|
504
469
|
|
|
505
470
|
# Extract outputs
|
|
@@ -533,34 +498,38 @@ class EksStackHandler:
|
|
|
533
498
|
|
|
534
499
|
stack_status = stack.get('StackStatus', '')
|
|
535
500
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
content=[
|
|
539
|
-
TextContent(
|
|
540
|
-
type='text',
|
|
541
|
-
text=f'Successfully described CloudFormation stack {stack_name} for EKS cluster {cluster_name}',
|
|
542
|
-
)
|
|
543
|
-
],
|
|
501
|
+
data = ManageEksStacksData(
|
|
502
|
+
operation=DESCRIBE_OPERATION,
|
|
544
503
|
stack_name=stack_name,
|
|
545
504
|
stack_id=stack_id,
|
|
546
505
|
cluster_name=cluster_name,
|
|
547
506
|
creation_time=creation_time,
|
|
548
507
|
stack_status=stack_status,
|
|
549
508
|
outputs=outputs,
|
|
509
|
+
template_path='',
|
|
510
|
+
stack_arn='',
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
return CallToolResult(
|
|
514
|
+
isError=False,
|
|
515
|
+
content=[
|
|
516
|
+
TextContent(
|
|
517
|
+
type='text',
|
|
518
|
+
text=f'Successfully described CloudFormation stack {stack_name} for EKS cluster {cluster_name}',
|
|
519
|
+
),
|
|
520
|
+
TextContent(
|
|
521
|
+
type='text',
|
|
522
|
+
text=json.dumps(data.model_dump()),
|
|
523
|
+
),
|
|
524
|
+
],
|
|
550
525
|
)
|
|
551
526
|
except Exception as e:
|
|
552
527
|
error_message = f'Failed to describe stack: {str(e)}'
|
|
553
528
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
554
529
|
|
|
555
|
-
return
|
|
530
|
+
return CallToolResult(
|
|
556
531
|
isError=True,
|
|
557
532
|
content=[TextContent(type='text', text=error_message or 'Unknown error')],
|
|
558
|
-
stack_name=stack_name,
|
|
559
|
-
stack_id='',
|
|
560
|
-
cluster_name=cluster_name,
|
|
561
|
-
creation_time='',
|
|
562
|
-
stack_status='',
|
|
563
|
-
outputs={},
|
|
564
533
|
)
|
|
565
534
|
|
|
566
535
|
def _remove_checkov_metadata(self, resource: Dict[str, Any]) -> None:
|
|
@@ -581,7 +550,7 @@ class EksStackHandler:
|
|
|
581
550
|
|
|
582
551
|
async def _delete_stack(
|
|
583
552
|
self, ctx: Context, stack_name: str, cluster_name: str
|
|
584
|
-
) ->
|
|
553
|
+
) -> CallToolResult:
|
|
585
554
|
"""Delete a CloudFormation stack."""
|
|
586
555
|
try:
|
|
587
556
|
# Create CloudFormation client
|
|
@@ -595,12 +564,9 @@ class EksStackHandler:
|
|
|
595
564
|
if stack:
|
|
596
565
|
stack_id = stack['StackId']
|
|
597
566
|
|
|
598
|
-
return
|
|
567
|
+
return CallToolResult(
|
|
599
568
|
isError=True,
|
|
600
569
|
content=[TextContent(type='text', text=error_message or 'Unknown error')],
|
|
601
|
-
stack_name=stack_name,
|
|
602
|
-
stack_id=stack_id,
|
|
603
|
-
cluster_name=cluster_name,
|
|
604
570
|
)
|
|
605
571
|
|
|
606
572
|
# Safely extract stack ID
|
|
@@ -617,26 +583,35 @@ class EksStackHandler:
|
|
|
617
583
|
f'Initiated deletion of CloudFormation stack {stack_name} for EKS cluster {cluster_name}',
|
|
618
584
|
)
|
|
619
585
|
|
|
620
|
-
|
|
586
|
+
data = ManageEksStacksData(
|
|
587
|
+
operation=DELETE_OPERATION,
|
|
588
|
+
stack_name=stack_name,
|
|
589
|
+
stack_id=stack_id,
|
|
590
|
+
cluster_name=cluster_name,
|
|
591
|
+
template_path='',
|
|
592
|
+
stack_arn='',
|
|
593
|
+
creation_time='',
|
|
594
|
+
stack_status='',
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
return CallToolResult(
|
|
621
598
|
isError=False,
|
|
622
599
|
content=[
|
|
623
600
|
TextContent(
|
|
624
601
|
type='text',
|
|
625
602
|
text=f'Initiated deletion of CloudFormation stack {stack_name} for EKS cluster {cluster_name}. Deletion is in progress.',
|
|
626
|
-
)
|
|
603
|
+
),
|
|
604
|
+
TextContent(
|
|
605
|
+
type='text',
|
|
606
|
+
text=json.dumps(data.model_dump()),
|
|
607
|
+
),
|
|
627
608
|
],
|
|
628
|
-
stack_name=stack_name,
|
|
629
|
-
stack_id=stack_id,
|
|
630
|
-
cluster_name=cluster_name,
|
|
631
609
|
)
|
|
632
610
|
except Exception as e:
|
|
633
611
|
error_message = f'Failed to delete stack: {str(e)}'
|
|
634
612
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
635
613
|
|
|
636
|
-
return
|
|
614
|
+
return CallToolResult(
|
|
637
615
|
isError=True,
|
|
638
616
|
content=[TextContent(type='text', text=error_message or 'Unknown error')],
|
|
639
|
-
stack_name=stack_name,
|
|
640
|
-
stack_id='',
|
|
641
|
-
cluster_name=cluster_name,
|
|
642
617
|
)
|
|
@@ -18,12 +18,12 @@ import json
|
|
|
18
18
|
from awslabs.eks_mcp_server.aws_helper import AwsHelper
|
|
19
19
|
from awslabs.eks_mcp_server.logging_helper import LogLevel, log_with_request_id
|
|
20
20
|
from awslabs.eks_mcp_server.models import (
|
|
21
|
-
|
|
21
|
+
AddInlinePolicyData,
|
|
22
22
|
PolicySummary,
|
|
23
|
-
|
|
23
|
+
RoleDescriptionData,
|
|
24
24
|
)
|
|
25
25
|
from mcp.server.fastmcp import Context
|
|
26
|
-
from mcp.types import TextContent
|
|
26
|
+
from mcp.types import CallToolResult, TextContent
|
|
27
27
|
from pydantic import Field
|
|
28
28
|
from typing import Any, Dict, List, Union
|
|
29
29
|
|
|
@@ -57,7 +57,7 @@ class IAMHandler:
|
|
|
57
57
|
...,
|
|
58
58
|
description='Name of the IAM role to get policies for. The role must exist in your AWS account.',
|
|
59
59
|
),
|
|
60
|
-
) ->
|
|
60
|
+
) -> CallToolResult:
|
|
61
61
|
"""Get all policies attached to an IAM role.
|
|
62
62
|
|
|
63
63
|
This tool retrieves all policies associated with an IAM role, providing a comprehensive view
|
|
@@ -112,34 +112,36 @@ class IAMHandler:
|
|
|
112
112
|
else:
|
|
113
113
|
assume_role_policy_document = role['AssumeRolePolicyDocument']
|
|
114
114
|
|
|
115
|
-
# Create the response
|
|
116
|
-
|
|
115
|
+
# Create the response with structured data
|
|
116
|
+
data = RoleDescriptionData(
|
|
117
|
+
role_arn=role['Arn'],
|
|
118
|
+
assume_role_policy_document=assume_role_policy_document,
|
|
119
|
+
description=role.get('Description'),
|
|
120
|
+
managed_policies=managed_policies,
|
|
121
|
+
inline_policies=inline_policies,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return CallToolResult(
|
|
117
125
|
isError=False,
|
|
118
126
|
content=[
|
|
119
127
|
TextContent(
|
|
120
128
|
type='text',
|
|
121
129
|
text=f'Successfully retrieved details for IAM role: {role_name}',
|
|
122
|
-
)
|
|
130
|
+
),
|
|
131
|
+
TextContent(
|
|
132
|
+
type='text',
|
|
133
|
+
text=json.dumps(data.model_dump()),
|
|
134
|
+
),
|
|
123
135
|
],
|
|
124
|
-
role_arn=role['Arn'],
|
|
125
|
-
assume_role_policy_document=assume_role_policy_document,
|
|
126
|
-
description=role.get('Description'),
|
|
127
|
-
managed_policies=managed_policies,
|
|
128
|
-
inline_policies=inline_policies,
|
|
129
136
|
)
|
|
130
137
|
except Exception as e:
|
|
131
138
|
error_message = f'Failed to describe IAM role: {str(e)}'
|
|
132
139
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
133
140
|
|
|
134
141
|
# Return a response with error status
|
|
135
|
-
return
|
|
142
|
+
return CallToolResult(
|
|
136
143
|
isError=True,
|
|
137
144
|
content=[TextContent(type='text', text=error_message)],
|
|
138
|
-
role_arn='',
|
|
139
|
-
assume_role_policy_document={},
|
|
140
|
-
description=None,
|
|
141
|
-
managed_policies=[],
|
|
142
|
-
inline_policies=[],
|
|
143
145
|
)
|
|
144
146
|
|
|
145
147
|
async def add_inline_policy(
|
|
@@ -156,7 +158,7 @@ class IAMHandler:
|
|
|
156
158
|
description="""Permissions to include in the policy as IAM policy statements in JSON format.
|
|
157
159
|
Can be either a single statement object or an array of statement objects.""",
|
|
158
160
|
),
|
|
159
|
-
) ->
|
|
161
|
+
) -> CallToolResult:
|
|
160
162
|
"""Add a new inline policy to an IAM role.
|
|
161
163
|
|
|
162
164
|
This tool creates a new inline policy with the specified permissions and adds it to an IAM role.
|
|
@@ -204,12 +206,9 @@ class IAMHandler:
|
|
|
204
206
|
if not self.allow_write:
|
|
205
207
|
error_message = 'Adding inline policies requires --allow-write flag'
|
|
206
208
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
207
|
-
return
|
|
209
|
+
return CallToolResult(
|
|
208
210
|
isError=True,
|
|
209
211
|
content=[TextContent(type='text', text=error_message)],
|
|
210
|
-
policy_name=policy_name,
|
|
211
|
-
role_name=role_name,
|
|
212
|
-
permissions_added={},
|
|
213
212
|
)
|
|
214
213
|
|
|
215
214
|
# Get IAM client
|
|
@@ -223,12 +222,9 @@ class IAMHandler:
|
|
|
223
222
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
224
223
|
|
|
225
224
|
# Return a response with error status
|
|
226
|
-
return
|
|
225
|
+
return CallToolResult(
|
|
227
226
|
isError=True,
|
|
228
227
|
content=[TextContent(type='text', text=error_message)],
|
|
229
|
-
policy_name=policy_name,
|
|
230
|
-
role_name=role_name,
|
|
231
|
-
permissions_added={},
|
|
232
228
|
)
|
|
233
229
|
|
|
234
230
|
def _get_managed_policies(self, ctx, iam_client, role_name):
|
|
@@ -324,12 +320,9 @@ class IAMHandler:
|
|
|
324
320
|
# If we get here, the policy exists
|
|
325
321
|
error_message = f'Policy {policy_name} already exists in role {role_name}. Cannot modify existing policies.'
|
|
326
322
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
327
|
-
return
|
|
323
|
+
return CallToolResult(
|
|
328
324
|
isError=True,
|
|
329
325
|
content=[TextContent(type='text', text=error_message)],
|
|
330
|
-
policy_name=policy_name,
|
|
331
|
-
role_name=role_name,
|
|
332
|
-
permissions_added={},
|
|
333
326
|
)
|
|
334
327
|
except iam_client.exceptions.NoSuchEntityException:
|
|
335
328
|
# Policy doesn't exist, we can create it
|
|
@@ -346,17 +339,24 @@ class IAMHandler:
|
|
|
346
339
|
RoleName=role_name, PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)
|
|
347
340
|
)
|
|
348
341
|
|
|
349
|
-
|
|
342
|
+
data = AddInlinePolicyData(
|
|
343
|
+
policy_name=policy_name,
|
|
344
|
+
role_name=role_name,
|
|
345
|
+
permissions_added=permissions,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
return CallToolResult(
|
|
350
349
|
isError=False,
|
|
351
350
|
content=[
|
|
352
351
|
TextContent(
|
|
353
352
|
type='text',
|
|
354
353
|
text=f'Successfully created new inline policy {policy_name} in role {role_name}',
|
|
355
|
-
)
|
|
354
|
+
),
|
|
355
|
+
TextContent(
|
|
356
|
+
type='text',
|
|
357
|
+
text=json.dumps(data.model_dump()),
|
|
358
|
+
),
|
|
356
359
|
],
|
|
357
|
-
policy_name=policy_name,
|
|
358
|
-
role_name=role_name,
|
|
359
|
-
permissions_added=permissions,
|
|
360
360
|
)
|
|
361
361
|
|
|
362
362
|
def _add_permissions_to_document(self, policy_document, permissions):
|