awslabs.iam-mcp-server 1.0.1__py3-none-any.whl → 1.0.2__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.
@@ -12,6 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """AWS IAM MCP Server."""
15
+ """AWS IAM MCP Server package."""
16
16
 
17
17
  __version__ = '1.0.0'
@@ -48,3 +48,8 @@ class Context:
48
48
  def set_region(cls, region: str):
49
49
  """Set the AWS region."""
50
50
  cls._region = region
51
+
52
+ @classmethod
53
+ def set_readonly(cls, readonly: bool):
54
+ """Set the read-only mode."""
55
+ cls._readonly = readonly
@@ -70,6 +70,16 @@ class IamPolicy(BaseModel):
70
70
  update_date: str = Field(..., description='The date and time when the policy was last updated')
71
71
 
72
72
 
73
+ class IamGroup(BaseModel):
74
+ """IAM Group model."""
75
+
76
+ group_name: str = Field(..., description='The name of the IAM group')
77
+ group_id: str = Field(..., description='The unique identifier for the group')
78
+ arn: str = Field(..., description='The Amazon Resource Name (ARN) of the group')
79
+ path: str = Field(..., description='The path to the group')
80
+ create_date: str = Field(..., description='The date and time when the group was created')
81
+
82
+
73
83
  class AccessKey(BaseModel):
74
84
  """IAM Access Key model."""
75
85
 
@@ -195,3 +205,86 @@ class PolicySimulationResponse(BaseModel):
195
205
  is_truncated: bool = Field(False, description='Whether the response is truncated')
196
206
  marker: Optional[str] = Field(None, description='Marker for pagination')
197
207
  policy_source_arn: str = Field(..., description='ARN of the principal that was simulated')
208
+
209
+
210
+ class GroupDetailsResponse(BaseModel):
211
+ """Response model for detailed group information."""
212
+
213
+ group: IamGroup = Field(..., description='Group details')
214
+ users: List[str] = Field(default_factory=list, description='List of user names in the group')
215
+ attached_policies: List[AttachedPolicy] = Field(
216
+ default_factory=list, description='List of attached managed policies'
217
+ )
218
+ inline_policies: List[str] = Field(
219
+ default_factory=list, description='List of inline policy names'
220
+ )
221
+
222
+
223
+ class GroupsListResponse(BaseModel):
224
+ """Response model for listing groups."""
225
+
226
+ groups: List[IamGroup] = Field(..., description='List of IAM groups')
227
+ is_truncated: bool = Field(False, description='Whether the response is truncated')
228
+ marker: Optional[str] = Field(None, description='Marker for pagination')
229
+ count: int = Field(..., description='Number of groups returned')
230
+
231
+
232
+ class CreateGroupResponse(BaseModel):
233
+ """Response model for creating a group."""
234
+
235
+ group: IamGroup = Field(..., description='Created group details')
236
+ message: str = Field(..., description='Success message')
237
+
238
+
239
+ class GroupMembershipResponse(BaseModel):
240
+ """Response model for group membership operations."""
241
+
242
+ message: str = Field(..., description='Operation result message')
243
+ group_name: str = Field(..., description='The name of the group')
244
+ user_name: str = Field(..., description='The name of the user')
245
+
246
+
247
+ class GroupPolicyAttachmentResponse(BaseModel):
248
+ """Response model for group policy attachment operations."""
249
+
250
+ message: str = Field(..., description='Operation result message')
251
+ group_name: str = Field(..., description='The name of the group')
252
+ policy_arn: str = Field(..., description='The ARN of the policy')
253
+
254
+
255
+ class InlinePolicy(BaseModel):
256
+ """Inline Policy model."""
257
+
258
+ policy_name: str = Field(..., description='The name of the inline policy')
259
+ policy_document: str = Field(..., description='The policy document in JSON format')
260
+
261
+
262
+ class InlinePolicyResponse(BaseModel):
263
+ """Response model for inline policy operations."""
264
+
265
+ policy_name: str = Field(..., description='The name of the policy')
266
+ policy_document: str = Field(..., description='The policy document in JSON format')
267
+ user_name: Optional[str] = Field(None, description='The name of the user (for user policies)')
268
+ role_name: Optional[str] = Field(None, description='The name of the role (for role policies)')
269
+ message: str = Field(..., description='Operation result message')
270
+
271
+
272
+ class InlinePolicyListResponse(BaseModel):
273
+ """Response model for listing inline policies."""
274
+
275
+ policy_names: List[str] = Field(..., description='List of inline policy names')
276
+ user_name: Optional[str] = Field(None, description='The name of the user (for user policies)')
277
+ role_name: Optional[str] = Field(None, description='The name of the role (for role policies)')
278
+ count: int = Field(..., description='Number of policies returned')
279
+
280
+
281
+ class ManagedPolicyResponse(BaseModel):
282
+ """Response model for managed policy document operations."""
283
+
284
+ policy_arn: str = Field(..., description='The ARN of the managed policy')
285
+ policy_name: str = Field(..., description='The name of the policy')
286
+ version_id: str = Field(..., description='The version ID of the policy')
287
+ policy_document: str = Field(..., description='The policy document in JSON format')
288
+ is_default_version: bool = Field(..., description='Whether this is the default version')
289
+ create_date: str = Field(..., description='The date and time when this version was created')
290
+ message: str = Field(..., description='Operation result message')
@@ -22,8 +22,17 @@ from awslabs.iam_mcp_server.errors import IamClientError, IamValidationError, ha
22
22
  from awslabs.iam_mcp_server.models import (
23
23
  AccessKey,
24
24
  AttachedPolicy,
25
+ CreateGroupResponse,
25
26
  CreateUserResponse,
27
+ GroupDetailsResponse,
28
+ GroupMembershipResponse,
29
+ GroupPolicyAttachmentResponse,
30
+ GroupsListResponse,
31
+ IamGroup,
26
32
  IamUser,
33
+ InlinePolicyListResponse,
34
+ InlinePolicyResponse,
35
+ ManagedPolicyResponse,
27
36
  UserDetailsResponse,
28
37
  UsersListResponse,
29
38
  )
@@ -45,10 +54,17 @@ mcp = FastMCP(
45
54
  1. **User Management**: Create, list, update, and delete IAM users
46
55
  2. **Role Management**: Create, list, update, and delete IAM roles
47
56
  3. **Policy Management**: Create, list, update, and delete IAM policies
48
- 4. **Group Management**: Create, list, update, and delete IAM groups
49
- 5. **Permission Management**: Attach/detach policies to users, roles, and groups
50
- 6. **Access Key Management**: Create, list, and delete access keys for users
51
- 7. **Security Analysis**: Analyze permissions, find unused resources, and security recommendations
57
+ 4. **Inline Policy Management**: Full CRUD operations for user and role inline policies
58
+ 5. **Group Management**: Create, list, update, and delete IAM groups
59
+ 6. **Permission Management**: Attach/detach policies to users, roles, and groups
60
+ 7. **Access Key Management**: Create, list, and delete access keys for users
61
+ 8. **Security Analysis**: Analyze permissions, find unused resources, and security recommendations
62
+
63
+ ## Inline Policy Management:
64
+ - **User Inline Policies**: Create, retrieve, update, delete, and list inline policies for users
65
+ - **Role Inline Policies**: Create, retrieve, update, delete, and list inline policies for roles
66
+ - **Policy Validation**: Automatic JSON validation for policy documents
67
+ - **Security Best Practices**: Built-in guidance for policy creation and management
52
68
 
53
69
  ## Security Best Practices:
54
70
  - Always follow the principle of least privilege
@@ -56,6 +72,8 @@ mcp = FastMCP(
56
72
  - Use roles instead of users for applications
57
73
  - Enable MFA where possible
58
74
  - Review and audit permissions regularly
75
+ - Prefer managed policies over inline policies for reusable permissions
76
+ - Test policies using simulate_principal_policy before applying
59
77
 
60
78
  ## Usage Requirements:
61
79
  - Requires valid AWS credentials with appropriate IAM permissions
@@ -180,7 +198,7 @@ async def get_user(
180
198
  inline_policies = inline_policies_response.get('PolicyNames', [])
181
199
 
182
200
  # Get groups
183
- groups_response = iam.get_groups_for_user(UserName=user_name)
201
+ groups_response = iam.list_groups_for_user(UserName=user_name)
184
202
  groups = [group['GroupName'] for group in groups_response.get('Groups', [])]
185
203
 
186
204
  # Get access keys
@@ -321,7 +339,7 @@ async def delete_user(
321
339
 
322
340
  if force:
323
341
  # Remove from all groups
324
- groups = iam.get_groups_for_user(UserName=user_name)
342
+ groups = iam.list_groups_for_user(UserName=user_name)
325
343
  for group in groups.get('Groups', []):
326
344
  iam.remove_user_from_group(GroupName=group['GroupName'], UserName=user_name)
327
345
 
@@ -542,6 +560,63 @@ async def list_policies(
542
560
  raise handle_iam_error(e)
543
561
 
544
562
 
563
+ @mcp.tool()
564
+ async def get_managed_policy_document(
565
+ policy_arn: str = Field(description='The ARN of the managed policy'),
566
+ version_id: Optional[str] = Field(
567
+ description='The version ID of the policy (defaults to current version)', default=None
568
+ ),
569
+ ) -> ManagedPolicyResponse:
570
+ """Retrieve the policy document for a managed policy.
571
+
572
+ This tool retrieves the policy document for a specific managed policy version.
573
+ Use this to examine the actual permissions and wildcards in managed policies.
574
+
575
+ Args:
576
+ policy_arn: The ARN of the managed policy
577
+ version_id: Optional version ID (defaults to current version)
578
+
579
+ Returns:
580
+ ManagedPolicyResponse containing the policy document and details
581
+ """
582
+ try:
583
+ logger.info(f'Getting managed policy document for: {policy_arn}')
584
+
585
+ if not policy_arn:
586
+ raise IamValidationError('Policy ARN is required')
587
+
588
+ iam = get_iam_client()
589
+
590
+ # Build parameters for the API call
591
+ kwargs = {'PolicyArn': policy_arn}
592
+ if version_id:
593
+ kwargs['VersionId'] = version_id
594
+
595
+ response = iam.get_policy_version(**kwargs)
596
+ policy_version = response['PolicyVersion']
597
+
598
+ # Extract policy name from ARN
599
+ policy_name = policy_arn.split('/')[-1]
600
+
601
+ result = ManagedPolicyResponse(
602
+ policy_arn=policy_arn,
603
+ policy_name=policy_name,
604
+ version_id=policy_version['VersionId'],
605
+ policy_document=json.dumps(policy_version['Document'], indent=2),
606
+ is_default_version=policy_version['IsDefaultVersion'],
607
+ create_date=policy_version['CreateDate'].isoformat(),
608
+ message=f'Successfully retrieved managed policy document for {policy_name}',
609
+ )
610
+
611
+ logger.info(f'Successfully retrieved managed policy document for: {policy_arn}')
612
+ return result
613
+
614
+ except Exception as e:
615
+ error = handle_iam_error(e)
616
+ logger.error(f'Error getting managed policy document: {error}')
617
+ raise error
618
+
619
+
545
620
  @mcp.tool()
546
621
  async def attach_user_policy(
547
622
  user_name: str = Field(description='The name of the IAM user'),
@@ -738,6 +813,761 @@ async def simulate_principal_policy(
738
813
  raise handle_iam_error(e)
739
814
 
740
815
 
816
+ # Group Management Tools
817
+
818
+
819
+ @mcp.tool()
820
+ async def list_groups(
821
+ path_prefix: Optional[str] = Field(
822
+ None, description='Path prefix to filter groups (e.g., "/division_abc/")'
823
+ ),
824
+ max_items: int = Field(100, description='Maximum number of groups to return'),
825
+ ) -> GroupsListResponse:
826
+ """List IAM groups in the account.
827
+
828
+ This tool retrieves a list of IAM groups from your AWS account with optional filtering.
829
+ Use this to get an overview of all groups or find specific groups by path prefix.
830
+
831
+ ## Usage Tips:
832
+ - Use path_prefix to filter groups by organizational structure
833
+ - Adjust max_items to control response size for large accounts
834
+ - Results may be paginated for accounts with many groups
835
+
836
+ Args:
837
+ path_prefix: Optional path prefix to filter groups
838
+ max_items: Maximum number of groups to return
839
+
840
+ Returns:
841
+ GroupsListResponse containing list of groups and metadata
842
+ """
843
+ if Context.is_readonly():
844
+ # List operations are allowed in read-only mode
845
+ pass
846
+
847
+ try:
848
+ iam = get_iam_client()
849
+
850
+ kwargs: Dict[str, Union[int, str]] = {'MaxItems': max_items}
851
+ if path_prefix:
852
+ kwargs['PathPrefix'] = path_prefix
853
+
854
+ response = iam.list_groups(**kwargs)
855
+
856
+ groups = []
857
+ for group_data in response.get('Groups', []):
858
+ group = IamGroup(
859
+ group_name=group_data['GroupName'],
860
+ group_id=group_data['GroupId'],
861
+ arn=group_data['Arn'],
862
+ path=group_data['Path'],
863
+ create_date=group_data['CreateDate'].isoformat(),
864
+ )
865
+ groups.append(group)
866
+
867
+ return GroupsListResponse(
868
+ groups=groups,
869
+ is_truncated=response.get('IsTruncated', False),
870
+ marker=response.get('Marker'),
871
+ count=len(groups),
872
+ )
873
+
874
+ except Exception as e:
875
+ raise handle_iam_error(e)
876
+
877
+
878
+ @mcp.tool()
879
+ async def get_group(
880
+ group_name: str = Field(description='The name of the IAM group to retrieve'),
881
+ ) -> GroupDetailsResponse:
882
+ """Get detailed information about a specific IAM group.
883
+
884
+ This tool retrieves comprehensive information about an IAM group including
885
+ group members, attached policies, and inline policies. Use this to get
886
+ a complete picture of a group's configuration and membership.
887
+
888
+ ## Usage Tips:
889
+ - Use this after list_groups to get detailed information about specific groups
890
+ - Review attached policies to understand group permissions
891
+ - Check group members to see who has these permissions
892
+
893
+ Args:
894
+ group_name: The name of the IAM group
895
+
896
+ Returns:
897
+ GroupDetailsResponse containing comprehensive group information
898
+ """
899
+ if Context.is_readonly():
900
+ # Get operations are allowed in read-only mode
901
+ pass
902
+
903
+ try:
904
+ iam = get_iam_client()
905
+
906
+ # Get group details and members
907
+ group_response = iam.get_group(GroupName=group_name)
908
+ group_data = group_response['Group']
909
+
910
+ group = IamGroup(
911
+ group_name=group_data['GroupName'],
912
+ group_id=group_data['GroupId'],
913
+ arn=group_data['Arn'],
914
+ path=group_data['Path'],
915
+ create_date=group_data['CreateDate'].isoformat(),
916
+ )
917
+
918
+ # Get group members
919
+ users = [user['UserName'] for user in group_response.get('Users', [])]
920
+
921
+ # Get attached managed policies
922
+ attached_policies_response = iam.list_attached_group_policies(GroupName=group_name)
923
+ attached_policies = [
924
+ AttachedPolicy(policy_name=policy['PolicyName'], policy_arn=policy['PolicyArn'])
925
+ for policy in attached_policies_response.get('AttachedPolicies', [])
926
+ ]
927
+
928
+ # Get inline policies
929
+ inline_policies_response = iam.list_group_policies(GroupName=group_name)
930
+ inline_policies = inline_policies_response.get('PolicyNames', [])
931
+
932
+ return GroupDetailsResponse(
933
+ group=group,
934
+ users=users,
935
+ attached_policies=attached_policies,
936
+ inline_policies=inline_policies,
937
+ )
938
+
939
+ except Exception as e:
940
+ raise handle_iam_error(e)
941
+
942
+
943
+ @mcp.tool()
944
+ async def create_group(
945
+ group_name: str = Field(description='The name of the new IAM group'),
946
+ path: str = Field('/', description='The path for the group'),
947
+ ) -> CreateGroupResponse:
948
+ """Create a new IAM group.
949
+
950
+ This tool creates a new IAM group in your AWS account. The group will be created
951
+ without any permissions by default - you'll need to attach policies separately.
952
+
953
+ ## Security Best Practices:
954
+ - Use descriptive group names that indicate the group's purpose
955
+ - Set appropriate paths for organizational structure
956
+ - Follow the principle of least privilege when assigning permissions later
957
+
958
+ Args:
959
+ group_name: The name of the new IAM group
960
+ path: The path for the group (default: '/')
961
+
962
+ Returns:
963
+ CreateGroupResponse containing the created group details
964
+ """
965
+ if Context.is_readonly():
966
+ raise IamValidationError('Cannot create group in read-only mode')
967
+
968
+ try:
969
+ iam = get_iam_client()
970
+
971
+ response = iam.create_group(GroupName=group_name, Path=path)
972
+
973
+ group_data = response['Group']
974
+ group = IamGroup(
975
+ group_name=group_data['GroupName'],
976
+ group_id=group_data['GroupId'],
977
+ arn=group_data['Arn'],
978
+ path=group_data['Path'],
979
+ create_date=group_data['CreateDate'].isoformat(),
980
+ )
981
+
982
+ return CreateGroupResponse(
983
+ group=group, message=f'Successfully created IAM group: {group_name}'
984
+ )
985
+
986
+ except Exception as e:
987
+ raise handle_iam_error(e)
988
+
989
+
990
+ @mcp.tool()
991
+ async def delete_group(
992
+ group_name: str = Field(description='The name of the IAM group to delete'),
993
+ force: bool = Field(
994
+ False, description='Force delete by removing all members and policies first'
995
+ ),
996
+ ) -> Dict[str, str]:
997
+ """Delete an IAM group.
998
+
999
+ Args:
1000
+ group_name: The name of the IAM group to delete
1001
+ force: If True, removes all members and attached policies first
1002
+
1003
+ Returns:
1004
+ Dictionary containing deletion status
1005
+ """
1006
+ if Context.is_readonly():
1007
+ raise IamValidationError('Cannot delete group in read-only mode')
1008
+
1009
+ try:
1010
+ iam = get_iam_client()
1011
+
1012
+ if force:
1013
+ # Remove all users from the group
1014
+ group_response = iam.get_group(GroupName=group_name)
1015
+ for user in group_response.get('Users', []):
1016
+ iam.remove_user_from_group(GroupName=group_name, UserName=user['UserName'])
1017
+
1018
+ # Detach all managed policies
1019
+ attached_policies = iam.list_attached_group_policies(GroupName=group_name)
1020
+ for policy in attached_policies.get('AttachedPolicies', []):
1021
+ iam.detach_group_policy(GroupName=group_name, PolicyArn=policy['PolicyArn'])
1022
+
1023
+ # Delete all inline policies
1024
+ inline_policies = iam.list_group_policies(GroupName=group_name)
1025
+ for policy_name in inline_policies.get('PolicyNames', []):
1026
+ iam.delete_group_policy(GroupName=group_name, PolicyName=policy_name)
1027
+
1028
+ # Delete the group
1029
+ iam.delete_group(GroupName=group_name)
1030
+
1031
+ return {'message': f'Successfully deleted IAM group: {group_name}'}
1032
+
1033
+ except Exception as e:
1034
+ raise handle_iam_error(e)
1035
+
1036
+
1037
+ @mcp.tool()
1038
+ async def add_user_to_group(
1039
+ group_name: str = Field(description='The name of the IAM group'),
1040
+ user_name: str = Field(description='The name of the IAM user'),
1041
+ ) -> GroupMembershipResponse:
1042
+ """Add a user to an IAM group.
1043
+
1044
+ Args:
1045
+ group_name: The name of the IAM group
1046
+ user_name: The name of the IAM user
1047
+
1048
+ Returns:
1049
+ GroupMembershipResponse containing operation status
1050
+ """
1051
+ if Context.is_readonly():
1052
+ raise IamValidationError('Cannot add user to group in read-only mode')
1053
+
1054
+ try:
1055
+ iam = get_iam_client()
1056
+ iam.add_user_to_group(GroupName=group_name, UserName=user_name)
1057
+
1058
+ return GroupMembershipResponse(
1059
+ message=f'Successfully added user {user_name} to group {group_name}',
1060
+ group_name=group_name,
1061
+ user_name=user_name,
1062
+ )
1063
+
1064
+ except Exception as e:
1065
+ raise handle_iam_error(e)
1066
+
1067
+
1068
+ @mcp.tool()
1069
+ async def remove_user_from_group(
1070
+ group_name: str = Field(description='The name of the IAM group'),
1071
+ user_name: str = Field(description='The name of the IAM user'),
1072
+ ) -> GroupMembershipResponse:
1073
+ """Remove a user from an IAM group.
1074
+
1075
+ Args:
1076
+ group_name: The name of the IAM group
1077
+ user_name: The name of the IAM user
1078
+
1079
+ Returns:
1080
+ GroupMembershipResponse containing operation status
1081
+ """
1082
+ if Context.is_readonly():
1083
+ raise IamValidationError('Cannot remove user from group in read-only mode')
1084
+
1085
+ try:
1086
+ iam = get_iam_client()
1087
+ iam.remove_user_from_group(GroupName=group_name, UserName=user_name)
1088
+
1089
+ return GroupMembershipResponse(
1090
+ message=f'Successfully removed user {user_name} from group {group_name}',
1091
+ group_name=group_name,
1092
+ user_name=user_name,
1093
+ )
1094
+
1095
+ except Exception as e:
1096
+ raise handle_iam_error(e)
1097
+
1098
+
1099
+ @mcp.tool()
1100
+ async def attach_group_policy(
1101
+ group_name: str = Field(description='The name of the IAM group'),
1102
+ policy_arn: str = Field(description='The ARN of the policy to attach'),
1103
+ ) -> GroupPolicyAttachmentResponse:
1104
+ """Attach a managed policy to an IAM group.
1105
+
1106
+ Args:
1107
+ group_name: The name of the IAM group
1108
+ policy_arn: The ARN of the policy to attach
1109
+
1110
+ Returns:
1111
+ GroupPolicyAttachmentResponse containing operation status
1112
+ """
1113
+ if Context.is_readonly():
1114
+ raise IamValidationError('Cannot attach policy to group in read-only mode')
1115
+
1116
+ try:
1117
+ iam = get_iam_client()
1118
+ iam.attach_group_policy(GroupName=group_name, PolicyArn=policy_arn)
1119
+
1120
+ return GroupPolicyAttachmentResponse(
1121
+ message=f'Successfully attached policy {policy_arn} to group {group_name}',
1122
+ group_name=group_name,
1123
+ policy_arn=policy_arn,
1124
+ )
1125
+
1126
+ except Exception as e:
1127
+ raise handle_iam_error(e)
1128
+
1129
+
1130
+ @mcp.tool()
1131
+ async def detach_group_policy(
1132
+ group_name: str = Field(description='The name of the IAM group'),
1133
+ policy_arn: str = Field(description='The ARN of the policy to detach'),
1134
+ ) -> GroupPolicyAttachmentResponse:
1135
+ """Detach a managed policy from an IAM group.
1136
+
1137
+ Args:
1138
+ group_name: The name of the IAM group
1139
+ policy_arn: The ARN of the policy to detach
1140
+
1141
+ Returns:
1142
+ GroupPolicyAttachmentResponse containing operation status
1143
+ """
1144
+ if Context.is_readonly():
1145
+ raise IamValidationError('Cannot detach policy from group in read-only mode')
1146
+
1147
+ try:
1148
+ iam = get_iam_client()
1149
+ iam.detach_group_policy(GroupName=group_name, PolicyArn=policy_arn)
1150
+
1151
+ return GroupPolicyAttachmentResponse(
1152
+ message=f'Successfully detached policy {policy_arn} from group {group_name}',
1153
+ group_name=group_name,
1154
+ policy_arn=policy_arn,
1155
+ )
1156
+
1157
+ except Exception as e:
1158
+ raise handle_iam_error(e)
1159
+
1160
+
1161
+ # Inline Policy Management Tools
1162
+
1163
+
1164
+ @mcp.tool()
1165
+ async def put_user_policy(
1166
+ user_name: str = Field(description='The name of the IAM user'),
1167
+ policy_name: str = Field(description='The name of the inline policy'),
1168
+ policy_document: Union[str, dict] = Field(
1169
+ description='The policy document in JSON format (string or dict)'
1170
+ ),
1171
+ ) -> InlinePolicyResponse:
1172
+ """Create or update an inline policy for an IAM user.
1173
+
1174
+ This tool creates a new inline policy or updates an existing one for the specified user.
1175
+ Inline policies are directly embedded in a single user, role, or group and have a one-to-one
1176
+ relationship with the identity.
1177
+
1178
+ ## Security Best Practices:
1179
+ - Follow the principle of least privilege when creating policies
1180
+ - Use managed policies for common permissions that can be reused
1181
+ - Regularly review and audit inline policies
1182
+ - Test policies using simulate_principal_policy before applying
1183
+
1184
+ Args:
1185
+ user_name: The name of the IAM user
1186
+ policy_name: The name of the inline policy
1187
+ policy_document: The policy document in JSON format
1188
+
1189
+ Returns:
1190
+ InlinePolicyResponse containing the policy details and operation status
1191
+ """
1192
+ try:
1193
+ logger.info(f'Creating/updating inline policy {policy_name} for user: {user_name}')
1194
+
1195
+ # Check if server is in read-only mode
1196
+ if Context.is_readonly():
1197
+ raise IamClientError(
1198
+ 'Cannot create/update inline policy: server is running in read-only mode'
1199
+ )
1200
+
1201
+ if not user_name or not policy_name:
1202
+ raise IamValidationError('User name and policy name are required')
1203
+
1204
+ iam = get_iam_client()
1205
+
1206
+ # Handle both string and dict types
1207
+ if isinstance(policy_document, dict):
1208
+ policy_doc = json.dumps(policy_document)
1209
+ else:
1210
+ policy_doc = policy_document
1211
+ # Validate JSON
1212
+ try:
1213
+ json.loads(policy_doc)
1214
+ except json.JSONDecodeError:
1215
+ raise IamValidationError('Invalid JSON in policy_document')
1216
+
1217
+ iam.put_user_policy(UserName=user_name, PolicyName=policy_name, PolicyDocument=policy_doc)
1218
+
1219
+ result = InlinePolicyResponse(
1220
+ policy_name=policy_name,
1221
+ policy_document=policy_doc,
1222
+ user_name=user_name,
1223
+ role_name=None,
1224
+ message=f'Successfully created/updated inline policy {policy_name} for user {user_name}',
1225
+ )
1226
+
1227
+ logger.info(
1228
+ f'Successfully created/updated inline policy {policy_name} for user: {user_name}'
1229
+ )
1230
+ return result
1231
+
1232
+ except Exception as e:
1233
+ error = handle_iam_error(e)
1234
+ logger.error(f'Error creating/updating inline policy: {error}')
1235
+ raise error
1236
+
1237
+
1238
+ @mcp.tool()
1239
+ async def get_user_policy(
1240
+ user_name: str = Field(description='The name of the IAM user'),
1241
+ policy_name: str = Field(description='The name of the inline policy'),
1242
+ ) -> InlinePolicyResponse:
1243
+ """Retrieve an inline policy for an IAM user.
1244
+
1245
+ This tool retrieves the policy document for a specific inline policy attached to a user.
1246
+
1247
+ Args:
1248
+ user_name: The name of the IAM user
1249
+ policy_name: The name of the inline policy
1250
+
1251
+ Returns:
1252
+ InlinePolicyResponse containing the policy document and details
1253
+ """
1254
+ try:
1255
+ logger.info(f'Getting inline policy {policy_name} for user: {user_name}')
1256
+
1257
+ if not user_name or not policy_name:
1258
+ raise IamValidationError('User name and policy name are required')
1259
+
1260
+ iam = get_iam_client()
1261
+
1262
+ response = iam.get_user_policy(UserName=user_name, PolicyName=policy_name)
1263
+
1264
+ result = InlinePolicyResponse(
1265
+ policy_name=response['PolicyName'],
1266
+ policy_document=response['PolicyDocument'],
1267
+ user_name=response['UserName'],
1268
+ role_name=None,
1269
+ message=f'Successfully retrieved inline policy {policy_name} for user {user_name}',
1270
+ )
1271
+
1272
+ logger.info(f'Successfully retrieved inline policy {policy_name} for user: {user_name}')
1273
+ return result
1274
+
1275
+ except Exception as e:
1276
+ error = handle_iam_error(e)
1277
+ logger.error(f'Error getting inline policy: {error}')
1278
+ raise error
1279
+
1280
+
1281
+ @mcp.tool()
1282
+ async def delete_user_policy(
1283
+ user_name: str = Field(description='The name of the IAM user'),
1284
+ policy_name: str = Field(description='The name of the inline policy to delete'),
1285
+ ) -> Dict[str, Any]:
1286
+ """Delete an inline policy from an IAM user.
1287
+
1288
+ This tool removes an inline policy from the specified user. The policy document
1289
+ will be permanently deleted and cannot be recovered.
1290
+
1291
+ Args:
1292
+ user_name: The name of the IAM user
1293
+ policy_name: The name of the inline policy to delete
1294
+
1295
+ Returns:
1296
+ Dictionary containing deletion status
1297
+ """
1298
+ try:
1299
+ logger.info(f'Deleting inline policy {policy_name} from user: {user_name}')
1300
+
1301
+ # Check if server is in read-only mode
1302
+ if Context.is_readonly():
1303
+ raise IamClientError(
1304
+ 'Cannot delete inline policy: server is running in read-only mode'
1305
+ )
1306
+
1307
+ if not user_name or not policy_name:
1308
+ raise IamValidationError('User name and policy name are required')
1309
+
1310
+ iam = get_iam_client()
1311
+
1312
+ iam.delete_user_policy(UserName=user_name, PolicyName=policy_name)
1313
+
1314
+ result = {
1315
+ 'message': f'Successfully deleted inline policy {policy_name} from user {user_name}',
1316
+ 'user_name': user_name,
1317
+ 'policy_name': policy_name,
1318
+ }
1319
+
1320
+ logger.info(f'Successfully deleted inline policy {policy_name} from user: {user_name}')
1321
+ return result
1322
+
1323
+ except Exception as e:
1324
+ error = handle_iam_error(e)
1325
+ logger.error(f'Error deleting inline policy: {error}')
1326
+ raise error
1327
+
1328
+
1329
+ # Role Inline Policy Management Tools
1330
+
1331
+
1332
+ @mcp.tool()
1333
+ async def put_role_policy(
1334
+ role_name: str = Field(description='The name of the IAM role'),
1335
+ policy_name: str = Field(description='The name of the inline policy'),
1336
+ policy_document: Union[str, dict] = Field(
1337
+ description='The policy document in JSON format (string or dict)'
1338
+ ),
1339
+ ) -> InlinePolicyResponse:
1340
+ """Create or update an inline policy for an IAM role.
1341
+
1342
+ This tool creates a new inline policy or updates an existing one for the specified role.
1343
+ Inline policies are directly embedded in a single user, role, or group and have a one-to-one
1344
+ relationship with the identity.
1345
+
1346
+ Args:
1347
+ role_name: The name of the IAM role
1348
+ policy_name: The name of the inline policy
1349
+ policy_document: The policy document in JSON format
1350
+
1351
+ Returns:
1352
+ InlinePolicyResponse containing the policy details and operation status
1353
+ """
1354
+ try:
1355
+ logger.info(f'Creating/updating inline policy {policy_name} for role: {role_name}')
1356
+
1357
+ # Check if server is in read-only mode
1358
+ if Context.is_readonly():
1359
+ raise IamClientError(
1360
+ 'Cannot create/update inline policy: server is running in read-only mode'
1361
+ )
1362
+
1363
+ if not role_name or not policy_name:
1364
+ raise IamValidationError('Role name and policy name are required')
1365
+
1366
+ iam = get_iam_client()
1367
+
1368
+ # Handle both string and dict types
1369
+ if isinstance(policy_document, dict):
1370
+ policy_doc = json.dumps(policy_document)
1371
+ else:
1372
+ policy_doc = policy_document
1373
+ # Validate JSON
1374
+ try:
1375
+ json.loads(policy_doc)
1376
+ except json.JSONDecodeError:
1377
+ raise IamValidationError('Invalid JSON in policy_document')
1378
+
1379
+ iam.put_role_policy(RoleName=role_name, PolicyName=policy_name, PolicyDocument=policy_doc)
1380
+
1381
+ result = InlinePolicyResponse(
1382
+ policy_name=policy_name,
1383
+ policy_document=policy_doc,
1384
+ user_name=None,
1385
+ role_name=role_name,
1386
+ message=f'Successfully created/updated inline policy {policy_name} for role {role_name}',
1387
+ )
1388
+
1389
+ logger.info(
1390
+ f'Successfully created/updated inline policy {policy_name} for role: {role_name}'
1391
+ )
1392
+ return result
1393
+
1394
+ except Exception as e:
1395
+ error = handle_iam_error(e)
1396
+ logger.error(f'Error creating/updating inline policy: {error}')
1397
+ raise error
1398
+
1399
+
1400
+ @mcp.tool()
1401
+ async def get_role_policy(
1402
+ role_name: str = Field(description='The name of the IAM role'),
1403
+ policy_name: str = Field(description='The name of the inline policy'),
1404
+ ) -> InlinePolicyResponse:
1405
+ """Retrieve an inline policy for an IAM role.
1406
+
1407
+ This tool retrieves the policy document for a specific inline policy attached to a role.
1408
+
1409
+ Args:
1410
+ role_name: The name of the IAM role
1411
+ policy_name: The name of the inline policy
1412
+
1413
+ Returns:
1414
+ InlinePolicyResponse containing the policy document and details
1415
+ """
1416
+ try:
1417
+ logger.info(f'Getting inline policy {policy_name} for role: {role_name}')
1418
+
1419
+ if not role_name or not policy_name:
1420
+ raise IamValidationError('Role name and policy name are required')
1421
+
1422
+ iam = get_iam_client()
1423
+
1424
+ response = iam.get_role_policy(RoleName=role_name, PolicyName=policy_name)
1425
+
1426
+ result = InlinePolicyResponse(
1427
+ policy_name=response['PolicyName'],
1428
+ policy_document=response['PolicyDocument'],
1429
+ user_name=None,
1430
+ role_name=response['RoleName'],
1431
+ message=f'Successfully retrieved inline policy {policy_name} for role {role_name}',
1432
+ )
1433
+
1434
+ logger.info(f'Successfully retrieved inline policy {policy_name} for role: {role_name}')
1435
+ return result
1436
+
1437
+ except Exception as e:
1438
+ error = handle_iam_error(e)
1439
+ logger.error(f'Error getting inline policy: {error}')
1440
+ raise error
1441
+
1442
+
1443
+ @mcp.tool()
1444
+ async def delete_role_policy(
1445
+ role_name: str = Field(description='The name of the IAM role'),
1446
+ policy_name: str = Field(description='The name of the inline policy to delete'),
1447
+ ) -> Dict[str, Any]:
1448
+ """Delete an inline policy from an IAM role.
1449
+
1450
+ This tool removes an inline policy from the specified role. The policy document
1451
+ will be permanently deleted and cannot be recovered.
1452
+
1453
+ Args:
1454
+ role_name: The name of the IAM role
1455
+ policy_name: The name of the inline policy to delete
1456
+
1457
+ Returns:
1458
+ Dictionary containing deletion status
1459
+ """
1460
+ try:
1461
+ logger.info(f'Deleting inline policy {policy_name} from role: {role_name}')
1462
+
1463
+ # Check if server is in read-only mode
1464
+ if Context.is_readonly():
1465
+ raise IamClientError(
1466
+ 'Cannot delete inline policy: server is running in read-only mode'
1467
+ )
1468
+
1469
+ if not role_name or not policy_name:
1470
+ raise IamValidationError('Role name and policy name are required')
1471
+
1472
+ iam = get_iam_client()
1473
+
1474
+ iam.delete_role_policy(RoleName=role_name, PolicyName=policy_name)
1475
+
1476
+ result = {
1477
+ 'message': f'Successfully deleted inline policy {policy_name} from role {role_name}',
1478
+ 'role_name': role_name,
1479
+ 'policy_name': policy_name,
1480
+ }
1481
+
1482
+ logger.info(f'Successfully deleted inline policy {policy_name} from role: {role_name}')
1483
+ return result
1484
+
1485
+ except Exception as e:
1486
+ error = handle_iam_error(e)
1487
+ logger.error(f'Error deleting inline policy: {error}')
1488
+ raise error
1489
+
1490
+
1491
+ @mcp.tool()
1492
+ async def list_user_policies(
1493
+ user_name: str = Field(description='The name of the IAM user'),
1494
+ ) -> InlinePolicyListResponse:
1495
+ """List all inline policies for an IAM user.
1496
+
1497
+ This tool retrieves the names of all inline policies attached to the specified user.
1498
+
1499
+ Args:
1500
+ user_name: The name of the IAM user
1501
+
1502
+ Returns:
1503
+ InlinePolicyListResponse containing the list of policy names
1504
+ """
1505
+ try:
1506
+ logger.info(f'Listing inline policies for user: {user_name}')
1507
+
1508
+ if not user_name:
1509
+ raise IamValidationError('User name is required')
1510
+
1511
+ iam = get_iam_client()
1512
+
1513
+ response = iam.list_user_policies(UserName=user_name)
1514
+
1515
+ result = InlinePolicyListResponse(
1516
+ policy_names=response.get('PolicyNames', []),
1517
+ user_name=user_name,
1518
+ role_name=None,
1519
+ count=len(response.get('PolicyNames', [])),
1520
+ )
1521
+
1522
+ logger.info(f'Successfully listed {result.count} inline policies for user: {user_name}')
1523
+ return result
1524
+
1525
+ except Exception as e:
1526
+ error = handle_iam_error(e)
1527
+ logger.error(f'Error listing inline policies: {error}')
1528
+ raise error
1529
+
1530
+
1531
+ @mcp.tool()
1532
+ async def list_role_policies(
1533
+ role_name: str = Field(description='The name of the IAM role'),
1534
+ ) -> InlinePolicyListResponse:
1535
+ """List all inline policies for an IAM role.
1536
+
1537
+ This tool retrieves the names of all inline policies attached to the specified role.
1538
+
1539
+ Args:
1540
+ role_name: The name of the IAM role
1541
+
1542
+ Returns:
1543
+ InlinePolicyListResponse containing the list of policy names
1544
+ """
1545
+ try:
1546
+ logger.info(f'Listing inline policies for role: {role_name}')
1547
+
1548
+ if not role_name:
1549
+ raise IamValidationError('Role name is required')
1550
+
1551
+ iam = get_iam_client()
1552
+
1553
+ response = iam.list_role_policies(RoleName=role_name)
1554
+
1555
+ result = InlinePolicyListResponse(
1556
+ policy_names=response.get('PolicyNames', []),
1557
+ user_name=None,
1558
+ role_name=role_name,
1559
+ count=len(response.get('PolicyNames', [])),
1560
+ )
1561
+
1562
+ logger.info(f'Successfully listed {result.count} inline policies for role: {role_name}')
1563
+ return result
1564
+
1565
+ except Exception as e:
1566
+ error = handle_iam_error(e)
1567
+ logger.error(f'Error listing inline policies: {error}')
1568
+ raise error
1569
+
1570
+
741
1571
  def main():
742
1572
  """Run the MCP server with CLI argument support."""
743
1573
  parser = argparse.ArgumentParser(
@@ -745,23 +1575,20 @@ def main():
745
1575
  )
746
1576
  parser.add_argument(
747
1577
  '--readonly',
748
- action=argparse.BooleanOptionalAction,
749
- help='Prevents the MCP server from performing mutating operations',
750
- default=False,
1578
+ action='store_true',
1579
+ help='Run server in read-only mode (prevents all mutating operations)',
751
1580
  )
752
- parser.add_argument('--region', help='AWS region to use for operations')
753
1581
 
754
1582
  args = parser.parse_args()
755
1583
 
756
- # Initialize context with configuration
757
- Context.initialize(readonly=args.readonly, region=args.region)
758
-
759
- if args.region:
760
- logger.info(f'Using AWS region: {args.region}')
761
-
1584
+ # Set read-only mode if specified
762
1585
  if args.readonly:
763
- logger.info('Running in read-only mode - mutating operations will be disabled')
1586
+ Context.set_readonly(True)
1587
+ logger.info('Server started in READ-ONLY mode - all mutating operations are disabled')
1588
+ else:
1589
+ logger.info('Server started in FULL ACCESS mode')
764
1590
 
1591
+ # Run the MCP server
765
1592
  mcp.run()
766
1593
 
767
1594
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.iam-mcp-server
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: An AWS Labs Model Context Protocol (MCP) server for managing AWS IAM resources including users, roles, policies, and permissions
5
5
  Project-URL: homepage, https://awslabs.github.io/mcp/
6
6
  Project-URL: docs, https://awslabs.github.io/mcp/servers/iam-mcp-server/
@@ -37,7 +37,9 @@ A Model Context Protocol (MCP) server for comprehensive AWS Identity and Access
37
37
  ### Core IAM Management
38
38
  - **User Management**: Create, list, retrieve, and delete IAM users
39
39
  - **Role Management**: Create, list, and manage IAM roles with trust policies
40
+ - **Group Management**: Create, list, retrieve, and delete IAM groups with member management
40
41
  - **Policy Management**: List and manage IAM policies (managed and inline)
42
+ - **Inline Policy Management**: Full CRUD operations for user and role inline policies
41
43
  - **Permission Management**: Attach/detach policies to users and roles
42
44
  - **Access Key Management**: Create and delete access keys for users
43
45
  - **Security Simulation**: Test policy permissions before applying them
@@ -104,6 +106,16 @@ The AWS credentials used by this server need the following IAM permissions:
104
106
  "iam:GetRole",
105
107
  "iam:CreateRole",
106
108
  "iam:DeleteRole",
109
+ "iam:ListGroups",
110
+ "iam:GetGroup",
111
+ "iam:CreateGroup",
112
+ "iam:DeleteGroup",
113
+ "iam:AddUserToGroup",
114
+ "iam:RemoveUserFromGroup",
115
+ "iam:AttachGroupPolicy",
116
+ "iam:DetachGroupPolicy",
117
+ "iam:ListAttachedGroupPolicies",
118
+ "iam:ListGroupPolicies",
107
119
  "iam:ListPolicies",
108
120
  "iam:GetPolicy",
109
121
  "iam:CreatePolicy",
@@ -116,13 +128,18 @@ The AWS credentials used by this server need the following IAM permissions:
116
128
  "iam:ListAttachedRolePolicies",
117
129
  "iam:ListUserPolicies",
118
130
  "iam:ListRolePolicies",
131
+ "iam:GetUserPolicy",
132
+ "iam:GetRolePolicy",
133
+ "iam:PutUserPolicy",
134
+ "iam:PutRolePolicy",
119
135
  "iam:GetGroupsForUser",
120
136
  "iam:ListAccessKeys",
121
137
  "iam:CreateAccessKey",
122
138
  "iam:DeleteAccessKey",
123
139
  "iam:SimulatePrincipalPolicy",
124
140
  "iam:RemoveUserFromGroup",
125
- "iam:DeleteUserPolicy"
141
+ "iam:DeleteUserPolicy",
142
+ "iam:DeleteRolePolicy"
126
143
  ],
127
144
  "Resource": "*"
128
145
  }
@@ -304,6 +321,63 @@ Create a new IAM role with a trust policy.
304
321
  - `max_session_duration` (optional): Maximum session duration in seconds (default: 3600)
305
322
  - `permissions_boundary` (optional): ARN of the permissions boundary policy
306
323
 
324
+ ### Group Management
325
+
326
+ #### `list_groups`
327
+ List IAM groups in the account with optional filtering.
328
+
329
+ **Parameters:**
330
+ - `path_prefix` (optional): Path prefix to filter groups (e.g., "/division_abc/")
331
+ - `max_items` (optional): Maximum number of groups to return (default: 100)
332
+
333
+ #### `get_group`
334
+ Get detailed information about a specific IAM group including members, attached policies, and inline policies.
335
+
336
+ **Parameters:**
337
+ - `group_name`: The name of the IAM group to retrieve
338
+
339
+ #### `create_group`
340
+ Create a new IAM group.
341
+
342
+ **Parameters:**
343
+ - `group_name`: The name of the new IAM group
344
+ - `path` (optional): The path for the group (default: "/")
345
+
346
+ #### `delete_group`
347
+ Delete an IAM group with optional force cleanup.
348
+
349
+ **Parameters:**
350
+ - `group_name`: The name of the IAM group to delete
351
+ - `force` (optional): Force delete by removing all members and policies first (default: false)
352
+
353
+ #### `add_user_to_group`
354
+ Add a user to an IAM group.
355
+
356
+ **Parameters:**
357
+ - `group_name`: The name of the IAM group
358
+ - `user_name`: The name of the IAM user
359
+
360
+ #### `remove_user_from_group`
361
+ Remove a user from an IAM group.
362
+
363
+ **Parameters:**
364
+ - `group_name`: The name of the IAM group
365
+ - `user_name`: The name of the IAM user
366
+
367
+ #### `attach_group_policy`
368
+ Attach a managed policy to an IAM group.
369
+
370
+ **Parameters:**
371
+ - `group_name`: The name of the IAM group
372
+ - `policy_arn`: The ARN of the policy to attach
373
+
374
+ #### `detach_group_policy`
375
+ Detach a managed policy from an IAM group.
376
+
377
+ **Parameters:**
378
+ - `group_name`: The name of the IAM group
379
+ - `policy_arn`: The ARN of the policy to detach
380
+
307
381
  ### Policy Management
308
382
 
309
383
  #### `list_policies`
@@ -357,6 +431,64 @@ Simulate IAM policy evaluation for a principal to test permissions.
357
431
  - `resource_arns` (optional): List of resource ARNs to test against
358
432
  - `context_entries` (optional): Context entries for the simulation
359
433
 
434
+ ### Inline Policy Management
435
+
436
+ #### `put_user_policy`
437
+ Create or update an inline policy for an IAM user.
438
+
439
+ **Parameters:**
440
+ - `user_name`: The name of the IAM user
441
+ - `policy_name`: The name of the inline policy
442
+ - `policy_document`: The policy document in JSON format (string or dict)
443
+
444
+ #### `get_user_policy`
445
+ Retrieve an inline policy for an IAM user.
446
+
447
+ **Parameters:**
448
+ - `user_name`: The name of the IAM user
449
+ - `policy_name`: The name of the inline policy
450
+
451
+ #### `delete_user_policy`
452
+ Delete an inline policy from an IAM user.
453
+
454
+ **Parameters:**
455
+ - `user_name`: The name of the IAM user
456
+ - `policy_name`: The name of the inline policy to delete
457
+
458
+ #### `list_user_policies`
459
+ List all inline policies for an IAM user.
460
+
461
+ **Parameters:**
462
+ - `user_name`: The name of the IAM user
463
+
464
+ #### `put_role_policy`
465
+ Create or update an inline policy for an IAM role.
466
+
467
+ **Parameters:**
468
+ - `role_name`: The name of the IAM role
469
+ - `policy_name`: The name of the inline policy
470
+ - `policy_document`: The policy document in JSON format (string or dict)
471
+
472
+ #### `get_role_policy`
473
+ Retrieve an inline policy for an IAM role.
474
+
475
+ **Parameters:**
476
+ - `role_name`: The name of the IAM role
477
+ - `policy_name`: The name of the inline policy
478
+
479
+ #### `delete_role_policy`
480
+ Delete an inline policy from an IAM role.
481
+
482
+ **Parameters:**
483
+ - `role_name`: The name of the IAM role
484
+ - `policy_name`: The name of the inline policy to delete
485
+
486
+ #### `list_role_policies`
487
+ List all inline policies for an IAM role.
488
+
489
+ **Parameters:**
490
+ - `role_name`: The name of the IAM role
491
+
360
492
  ## Usage Examples
361
493
 
362
494
  ### Basic User Management
@@ -398,6 +530,30 @@ role = await create_role(
398
530
  )
399
531
  ```
400
532
 
533
+ ### Group Management
534
+ ```python
535
+ # Create a new group
536
+ group = await create_group(
537
+ group_name="Developers",
538
+ path="/teams/"
539
+ )
540
+
541
+ # Add users to the group
542
+ await add_user_to_group(
543
+ group_name="Developers",
544
+ user_name="john.doe"
545
+ )
546
+
547
+ # Attach a policy to the group
548
+ await attach_group_policy(
549
+ group_name="Developers",
550
+ policy_arn="arn:aws:iam::123456789012:policy/DeveloperPolicy"
551
+ )
552
+
553
+ # Get group details including members
554
+ group_details = await get_group(group_name="Developers")
555
+ ```
556
+
401
557
  ### Policy Management
402
558
  ```python
403
559
  # List customer managed policies
@@ -420,6 +576,58 @@ simulation = await simulate_principal_policy(
420
576
  )
421
577
  ```
422
578
 
579
+ ### Inline Policy Management
580
+ ```python
581
+ # Create an inline policy for a user
582
+ policy_document = {
583
+ "Version": "2012-10-17",
584
+ "Statement": [
585
+ {
586
+ "Effect": "Allow",
587
+ "Action": ["s3:GetObject", "s3:PutObject"],
588
+ "Resource": "arn:aws:s3:::my-bucket/*"
589
+ }
590
+ ]
591
+ }
592
+
593
+ await put_user_policy(
594
+ user_name="developer",
595
+ policy_name="S3AccessPolicy",
596
+ policy_document=policy_document
597
+ )
598
+
599
+ # Retrieve an inline policy
600
+ policy = await get_user_policy(
601
+ user_name="developer",
602
+ policy_name="S3AccessPolicy"
603
+ )
604
+
605
+ # List all inline policies for a user
606
+ policies = await list_user_policies(user_name="developer")
607
+
608
+ # Create an inline policy for a role
609
+ await put_role_policy(
610
+ role_name="EC2-S3-Access-Role",
611
+ policy_name="S3ReadOnlyPolicy",
612
+ policy_document={
613
+ "Version": "2012-10-17",
614
+ "Statement": [
615
+ {
616
+ "Effect": "Allow",
617
+ "Action": "s3:GetObject",
618
+ "Resource": "*"
619
+ }
620
+ ]
621
+ }
622
+ )
623
+
624
+ # Delete an inline policy
625
+ await delete_user_policy(
626
+ user_name="developer",
627
+ policy_name="S3AccessPolicy"
628
+ )
629
+ ```
630
+
423
631
  ## Security Best Practices
424
632
 
425
633
  1. **Principle of Least Privilege**: Always grant the minimum permissions necessary
@@ -429,6 +637,8 @@ simulation = await simulate_principal_policy(
429
637
  5. **Enable MFA**: Use multi-factor authentication where possible
430
638
  6. **Permissions Boundaries**: Use permissions boundaries to set maximum permissions
431
639
  7. **Policy Simulation**: Test policies before applying them to production
640
+ 8. **Prefer Managed Policies**: Use managed policies over inline policies for reusable permissions
641
+ 9. **Inline Policy Guidelines**: Use inline policies only for permissions unique to a single identity
432
642
 
433
643
  ## Error Handling
434
644
 
@@ -0,0 +1,13 @@
1
+ awslabs/__init__.py,sha256=dzKGHmVYP-680lDPkXJrDGVhMSb0BD6_Vfu83qcA1aA,675
2
+ awslabs/iam_mcp_server/__init__.py,sha256=EkXrr_NxCPkjPWqoMupbYspWGNRVClM5ycmO42mvEZM,673
3
+ awslabs/iam_mcp_server/aws_client.py,sha256=13lAde77Q3DdOHDqE7YvF93SjdV-S9c_2uy3ChrjpaA,2994
4
+ awslabs/iam_mcp_server/context.py,sha256=Xmxrp6B-T_2OS3WEp2K1VjBHQjIo6dpvbx6V_j8ZXFo,1752
5
+ awslabs/iam_mcp_server/errors.py,sha256=Co6-rVod81qaAl__Ob5ouGly33SBhfpF61am2P5BfmE,5818
6
+ awslabs/iam_mcp_server/models.py,sha256=G7Ft_4MIXtDpGyFkdc8nJ693ZG6cWiticqHn2T1gUBE,12535
7
+ awslabs/iam_mcp_server/server.py,sha256=ApKVC0rvxSYzIsS5GiLYn8saaBv7XzY-xWmDyqjc-BM,54476
8
+ awslabs_iam_mcp_server-1.0.2.dist-info/METADATA,sha256=XTpZQKEcJyz-O54yrClndiZT_n-cYCP0h1uxYRCYbIE,19927
9
+ awslabs_iam_mcp_server-1.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ awslabs_iam_mcp_server-1.0.2.dist-info/entry_points.txt,sha256=B9fOJVT7l2NUQ1RHLUiskRtB5wADSpSI95cI9gU6dO0,78
11
+ awslabs_iam_mcp_server-1.0.2.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
12
+ awslabs_iam_mcp_server-1.0.2.dist-info/licenses/NOTICE,sha256=Qls-8qZMuKGG2WDBsLbJOBNuQxTkSI6ij_7HTi7I4ys,86
13
+ awslabs_iam_mcp_server-1.0.2.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- awslabs/__init__.py,sha256=dzKGHmVYP-680lDPkXJrDGVhMSb0BD6_Vfu83qcA1aA,675
2
- awslabs/iam_mcp_server/__init__.py,sha256=tnJPNAmwa84Ny3QliNkzX5cLfDTMrM3bXKa9QGfXrLo,665
3
- awslabs/iam_mcp_server/aws_client.py,sha256=13lAde77Q3DdOHDqE7YvF93SjdV-S9c_2uy3ChrjpaA,2994
4
- awslabs/iam_mcp_server/context.py,sha256=X28PtpwuvU1NyWIc8cBx8tyZimGfirXRegyasygBUd8,1620
5
- awslabs/iam_mcp_server/errors.py,sha256=Co6-rVod81qaAl__Ob5ouGly33SBhfpF61am2P5BfmE,5818
6
- awslabs/iam_mcp_server/models.py,sha256=ftCkBwW4IKk0g1pmspI7tXpWEZTu3zSs7nx0DwQvOfI,8424
7
- awslabs/iam_mcp_server/server.py,sha256=8OHhdRMwxE9YlYlynbA-V33ChLCQudrxWsLHk7UVPKo,26615
8
- awslabs_iam_mcp_server-1.0.1.dist-info/METADATA,sha256=i8PWiTrIXj9wYS5docL-O2U4OOgECuFas4UlYq76cBg,14297
9
- awslabs_iam_mcp_server-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- awslabs_iam_mcp_server-1.0.1.dist-info/entry_points.txt,sha256=B9fOJVT7l2NUQ1RHLUiskRtB5wADSpSI95cI9gU6dO0,78
11
- awslabs_iam_mcp_server-1.0.1.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
12
- awslabs_iam_mcp_server-1.0.1.dist-info/licenses/NOTICE,sha256=Qls-8qZMuKGG2WDBsLbJOBNuQxTkSI6ij_7HTi7I4ys,86
13
- awslabs_iam_mcp_server-1.0.1.dist-info/RECORD,,