awslabs.elasticache-mcp-server 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. awslabs/__init__.py +16 -0
  2. awslabs/elasticache_mcp_server/__init__.py +17 -0
  3. awslabs/elasticache_mcp_server/common/__init__.py +15 -0
  4. awslabs/elasticache_mcp_server/common/connection.py +117 -0
  5. awslabs/elasticache_mcp_server/common/decorators.py +41 -0
  6. awslabs/elasticache_mcp_server/common/server.py +30 -0
  7. awslabs/elasticache_mcp_server/context.py +39 -0
  8. awslabs/elasticache_mcp_server/main.py +52 -0
  9. awslabs/elasticache_mcp_server/tools/__init__.py +15 -0
  10. awslabs/elasticache_mcp_server/tools/cc/__init__.py +31 -0
  11. awslabs/elasticache_mcp_server/tools/cc/connect.py +444 -0
  12. awslabs/elasticache_mcp_server/tools/cc/create.py +212 -0
  13. awslabs/elasticache_mcp_server/tools/cc/delete.py +65 -0
  14. awslabs/elasticache_mcp_server/tools/cc/describe.py +80 -0
  15. awslabs/elasticache_mcp_server/tools/cc/modify.py +159 -0
  16. awslabs/elasticache_mcp_server/tools/cc/parsers.py +78 -0
  17. awslabs/elasticache_mcp_server/tools/cc/processors.py +74 -0
  18. awslabs/elasticache_mcp_server/tools/ce/__init__.py +19 -0
  19. awslabs/elasticache_mcp_server/tools/ce/get_cost_and_usage.py +76 -0
  20. awslabs/elasticache_mcp_server/tools/cw/__init__.py +19 -0
  21. awslabs/elasticache_mcp_server/tools/cw/get_metric_statistics.py +85 -0
  22. awslabs/elasticache_mcp_server/tools/cwlogs/__init__.py +29 -0
  23. awslabs/elasticache_mcp_server/tools/cwlogs/create_log_group.py +68 -0
  24. awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_groups.py +123 -0
  25. awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_streams.py +120 -0
  26. awslabs/elasticache_mcp_server/tools/cwlogs/filter_log_events.py +122 -0
  27. awslabs/elasticache_mcp_server/tools/cwlogs/get_log_events.py +99 -0
  28. awslabs/elasticache_mcp_server/tools/firehose/__init__.py +19 -0
  29. awslabs/elasticache_mcp_server/tools/firehose/list_delivery_streams.py +63 -0
  30. awslabs/elasticache_mcp_server/tools/misc/__init__.py +31 -0
  31. awslabs/elasticache_mcp_server/tools/misc/batch_apply_update_action.py +62 -0
  32. awslabs/elasticache_mcp_server/tools/misc/batch_stop_update_action.py +62 -0
  33. awslabs/elasticache_mcp_server/tools/misc/describe_cache_engine_versions.py +79 -0
  34. awslabs/elasticache_mcp_server/tools/misc/describe_engine_default_parameters.py +64 -0
  35. awslabs/elasticache_mcp_server/tools/misc/describe_events.py +86 -0
  36. awslabs/elasticache_mcp_server/tools/misc/describe_service_updates.py +71 -0
  37. awslabs/elasticache_mcp_server/tools/rg/__init__.py +54 -0
  38. awslabs/elasticache_mcp_server/tools/rg/complete_migration.py +94 -0
  39. awslabs/elasticache_mcp_server/tools/rg/connect.py +537 -0
  40. awslabs/elasticache_mcp_server/tools/rg/create.py +318 -0
  41. awslabs/elasticache_mcp_server/tools/rg/delete.py +68 -0
  42. awslabs/elasticache_mcp_server/tools/rg/describe.py +68 -0
  43. awslabs/elasticache_mcp_server/tools/rg/modify.py +236 -0
  44. awslabs/elasticache_mcp_server/tools/rg/parsers.py +268 -0
  45. awslabs/elasticache_mcp_server/tools/rg/processors.py +227 -0
  46. awslabs/elasticache_mcp_server/tools/rg/start_migration.py +151 -0
  47. awslabs/elasticache_mcp_server/tools/rg/test_migration.py +139 -0
  48. awslabs/elasticache_mcp_server/tools/serverless/__init__.py +37 -0
  49. awslabs/elasticache_mcp_server/tools/serverless/connect.py +451 -0
  50. awslabs/elasticache_mcp_server/tools/serverless/create.py +174 -0
  51. awslabs/elasticache_mcp_server/tools/serverless/delete.py +49 -0
  52. awslabs/elasticache_mcp_server/tools/serverless/describe.py +69 -0
  53. awslabs/elasticache_mcp_server/tools/serverless/models.py +160 -0
  54. awslabs/elasticache_mcp_server/tools/serverless/modify.py +95 -0
  55. awslabs_elasticache_mcp_server-0.1.1.dist-info/METADATA +257 -0
  56. awslabs_elasticache_mcp_server-0.1.1.dist-info/RECORD +60 -0
  57. awslabs_elasticache_mcp_server-0.1.1.dist-info/WHEEL +4 -0
  58. awslabs_elasticache_mcp_server-0.1.1.dist-info/entry_points.txt +2 -0
  59. awslabs_elasticache_mcp_server-0.1.1.dist-info/licenses/LICENSE +175 -0
  60. awslabs_elasticache_mcp_server-0.1.1.dist-info/licenses/NOTICE +2 -0
@@ -0,0 +1,318 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Create replication group tool for ElastiCache MCP server."""
16
+
17
+ from ...common.connection import ElastiCacheConnectionManager
18
+ from ...common.decorators import handle_exceptions
19
+ from ...common.server import mcp
20
+ from ...context import Context
21
+ from .processors import process_log_delivery_configurations, process_nodegroup_configuration
22
+ from pydantic import BaseModel, ConfigDict, Field
23
+ from typing import Any, Dict, List, Optional, Union
24
+
25
+
26
+ class Tag(BaseModel):
27
+ """Tag model for ElastiCache resources."""
28
+
29
+ Key: str = Field(..., description='The key for the tag')
30
+ Value: Optional[str] = Field(None, description="The tag's value")
31
+ model_config = ConfigDict(validate_by_name=True, arbitrary_types_allowed=True)
32
+
33
+
34
+ class NodeGroupConfiguration(BaseModel):
35
+ """Node group configuration model."""
36
+
37
+ NodeGroupId: Optional[str] = Field(None, description='The identifier for the node group')
38
+ ReplicaCount: Optional[int] = Field(None, description='The number of replica nodes')
39
+ Slots: Optional[str] = Field(None, description='The keyspace for the node group')
40
+ PrimaryAvailabilityZone: Optional[str] = Field(
41
+ None, description='The Availability Zone where the primary node will be launched'
42
+ )
43
+ ReplicaAvailabilityZones: Optional[List[str]] = Field(
44
+ None, description='A list of Availability Zones where the replica nodes will be launched'
45
+ )
46
+ model_config = ConfigDict(validate_by_name=True, arbitrary_types_allowed=True)
47
+
48
+
49
+ class LogDeliveryDestinationDetails(BaseModel):
50
+ """Log delivery destination details model."""
51
+
52
+ CloudWatchLogsDetails: Optional[Dict] = Field(
53
+ None, description='The configuration details of CloudWatch Logs destination'
54
+ )
55
+ KinesisFirehoseDetails: Optional[Dict] = Field(
56
+ None, description='The configuration details of Kinesis Data Firehose destination'
57
+ )
58
+ model_config = ConfigDict(validate_by_name=True, arbitrary_types_allowed=True)
59
+
60
+
61
+ class LogDeliveryConfiguration(BaseModel):
62
+ """Log delivery configuration model."""
63
+
64
+ LogType: str = Field(..., description='The type of log to deliver')
65
+ DestinationType: str = Field(..., description='The type of destination to deliver to')
66
+ DestinationDetails: LogDeliveryDestinationDetails = Field(
67
+ ..., description='The configuration details of the destination'
68
+ )
69
+ LogFormat: str = Field(..., description='The format of the logs')
70
+ Enabled: bool = Field(..., description='Whether log delivery is enabled')
71
+ model_config = ConfigDict(validate_by_name=True, arbitrary_types_allowed=True)
72
+
73
+
74
+ class CreateReplicationGroupRequest(BaseModel):
75
+ """Request model for creating an ElastiCache replication group."""
76
+
77
+ model_config = ConfigDict(validate_by_name=True, arbitrary_types_allowed=True)
78
+
79
+ replication_group_id: str = Field(..., description='The identifier of the replication group')
80
+ replication_group_description: str = Field(
81
+ ..., description='The description of the replication group'
82
+ )
83
+ cache_node_type: Optional[str] = Field(
84
+ None, description='The compute and memory capacity of nodes'
85
+ )
86
+ engine: Optional[str] = Field(None, description='The name of the cache engine')
87
+ engine_version: Optional[str] = Field(
88
+ None, description='The version number of the cache engine'
89
+ )
90
+ num_cache_clusters: Optional[int] = Field(None, description='The number of cache clusters')
91
+ preferred_cache_cluster_azs: Optional[List[str]] = Field(
92
+ None, description='List of Availability Zones'
93
+ )
94
+ num_node_groups: Optional[int] = Field(None, description='The number of node groups')
95
+ replicas_per_node_group: Optional[int] = Field(
96
+ None, description='The number of replica nodes in each node group'
97
+ )
98
+ node_group_configuration: Optional[Union[str, List[NodeGroupConfiguration]]] = Field(
99
+ None, description='Configuration for each node group'
100
+ )
101
+ cache_parameter_group_name: Optional[str] = Field(
102
+ None, description='The name of the parameter group to associate'
103
+ )
104
+ cache_subnet_group_name: Optional[str] = Field(
105
+ None, description='The name of the cache subnet group to use'
106
+ )
107
+ cache_security_group_names: Optional[List[str]] = Field(
108
+ None, description='List of cache security group names'
109
+ )
110
+ security_group_ids: Optional[List[str]] = Field(
111
+ None, description='List of Amazon VPC security group IDs'
112
+ )
113
+ tags: Optional[Union[str, List[Tag], Dict[str, Optional[str]]]] = Field(
114
+ None, description='Tags to apply to the replication group'
115
+ )
116
+ snapshot_arns: Optional[List[str]] = Field(
117
+ None, description='List of ARNs of snapshots to restore from'
118
+ )
119
+ snapshot_name: Optional[str] = Field(
120
+ None, description='The name of a snapshot to restore from'
121
+ )
122
+ preferred_maintenance_window: Optional[str] = Field(
123
+ None, description='The weekly time range for maintenance'
124
+ )
125
+ port: Optional[int] = Field(
126
+ None, description='The port number on which the cache accepts connections'
127
+ )
128
+ notification_topic_arn: Optional[str] = Field(
129
+ None, description='The ARN of an SNS topic for notifications'
130
+ )
131
+ auto_minor_version_upgrade: Optional[bool] = Field(
132
+ None, description='Enable/disable automatic minor version upgrades'
133
+ )
134
+ snapshot_retention_limit: Optional[int] = Field(
135
+ None, description='The number of days to retain backups'
136
+ )
137
+ snapshot_window: Optional[str] = Field(None, description='The daily time range for backups')
138
+ auth_token: Optional[str] = Field(
139
+ None, description='Password used to access a password protected server'
140
+ )
141
+ transit_encryption_enabled: Optional[bool] = Field(
142
+ None, description='Enable/disable encryption in transit'
143
+ )
144
+ at_rest_encryption_enabled: Optional[bool] = Field(
145
+ None, description='Enable/disable encryption at rest'
146
+ )
147
+ kms_key_id: Optional[str] = Field(
148
+ None, description='The ID of the KMS key used to encrypt the disk'
149
+ )
150
+ user_group_ids: Optional[List[str]] = Field(
151
+ None, description='List of user group IDs to associate'
152
+ )
153
+ log_delivery_configurations: Optional[Union[str, List[LogDeliveryConfiguration]]] = Field(
154
+ None, description='Log delivery configurations'
155
+ )
156
+
157
+
158
+ def prepare_request_dict(request: CreateReplicationGroupRequest) -> Dict[str, Any]:
159
+ """Prepare the request dictionary for the AWS API.
160
+
161
+ Args:
162
+ request: The CreateReplicationGroupRequest object
163
+
164
+ Returns:
165
+ Dict containing the properly formatted request parameters
166
+ """
167
+ # Start with required parameters
168
+ create_request: Dict[str, Any] = {
169
+ 'ReplicationGroupId': request.replication_group_id,
170
+ 'ReplicationGroupDescription': request.replication_group_description,
171
+ }
172
+
173
+ # Optional string parameters
174
+ for param_name, value in [
175
+ ('CacheNodeType', request.cache_node_type),
176
+ ('Engine', request.engine),
177
+ ('EngineVersion', request.engine_version),
178
+ ('CacheParameterGroupName', request.cache_parameter_group_name),
179
+ ('CacheSubnetGroupName', request.cache_subnet_group_name),
180
+ ('SnapshotName', request.snapshot_name),
181
+ ('PreferredMaintenanceWindow', request.preferred_maintenance_window),
182
+ ('NotificationTopicArn', request.notification_topic_arn),
183
+ ('SnapshotWindow', request.snapshot_window),
184
+ ('AuthToken', request.auth_token),
185
+ ('KmsKeyId', request.kms_key_id),
186
+ ]:
187
+ if value:
188
+ create_request[param_name] = str(value)
189
+
190
+ # Optional numeric parameters
191
+ for param_name, value in [
192
+ ('NumCacheClusters', request.num_cache_clusters),
193
+ ('NumNodeGroups', request.num_node_groups),
194
+ ('ReplicasPerNodeGroup', request.replicas_per_node_group),
195
+ ('Port', request.port),
196
+ ('SnapshotRetentionLimit', request.snapshot_retention_limit),
197
+ ]:
198
+ if value is not None:
199
+ create_request[param_name] = value
200
+
201
+ # Optional boolean parameters
202
+ for param_name, value in [
203
+ ('AutoMinorVersionUpgrade', request.auto_minor_version_upgrade),
204
+ ('TransitEncryptionEnabled', request.transit_encryption_enabled),
205
+ ('AtRestEncryptionEnabled', request.at_rest_encryption_enabled),
206
+ ]:
207
+ if value is not None:
208
+ create_request[param_name] = value
209
+
210
+ # Optional list parameters
211
+ for param_name, value in [
212
+ ('PreferredCacheClusterAZs', request.preferred_cache_cluster_azs),
213
+ ('CacheSecurityGroupNames', request.cache_security_group_names),
214
+ ('SecurityGroupIds', request.security_group_ids),
215
+ ('SnapshotArns', request.snapshot_arns),
216
+ ('UserGroupIds', request.user_group_ids),
217
+ ]:
218
+ if value:
219
+ create_request[param_name] = list(value)
220
+
221
+ # Handle node group configuration
222
+ if request.node_group_configuration:
223
+ if isinstance(request.node_group_configuration, list):
224
+ configs = [
225
+ config.model_dump(exclude_none=True) for config in request.node_group_configuration
226
+ ]
227
+ create_request['NodeGroupConfiguration'] = configs
228
+ else:
229
+ processed_config = process_nodegroup_configuration(request.node_group_configuration)
230
+ if processed_config:
231
+ create_request['NodeGroupConfiguration'] = processed_config
232
+
233
+ # Handle tags
234
+ if request.tags:
235
+ if isinstance(request.tags, str):
236
+ # Parse shorthand syntax: Key=string,Value=string
237
+ tag_list = []
238
+ try:
239
+ pairs = [p.strip() for p in request.tags.split(',') if p.strip()]
240
+ for pair in pairs:
241
+ if '=' not in pair:
242
+ raise ValueError(
243
+ 'Invalid tag format. Each tag must be in Key=Value format'
244
+ )
245
+ key, value = pair.split('=', 1)
246
+ key = key.strip()
247
+ value = value.strip() if value.strip() else None
248
+ if not key:
249
+ raise ValueError('Tag key cannot be empty')
250
+ tag_list.append({'Key': key, 'Value': value})
251
+ create_request['Tags'] = tag_list
252
+ except Exception as e:
253
+ raise ValueError(
254
+ f'Invalid tag shorthand syntax. Expected format: Key=string,Value=string. Error: {str(e)}'
255
+ )
256
+ elif isinstance(request.tags, dict):
257
+ # Handle dictionary format
258
+ tag_list = []
259
+ for k, v in request.tags.items():
260
+ if not k:
261
+ raise ValueError('Tag key cannot be empty')
262
+ tag_list.append({'Key': k, 'Value': v})
263
+ create_request['Tags'] = tag_list
264
+ elif isinstance(request.tags, list):
265
+ create_request['Tags'] = [tag.model_dump(exclude_none=True) for tag in request.tags]
266
+
267
+ # Handle log delivery configurations
268
+ if request.log_delivery_configurations:
269
+ if isinstance(request.log_delivery_configurations, list):
270
+ configs = [
271
+ config.model_dump(exclude_none=True)
272
+ for config in request.log_delivery_configurations
273
+ ]
274
+ create_request['LogDeliveryConfigurations'] = configs
275
+ else:
276
+ processed_configs = process_log_delivery_configurations(
277
+ request.log_delivery_configurations
278
+ )
279
+ if processed_configs:
280
+ create_request['LogDeliveryConfigurations'] = processed_configs
281
+
282
+ return create_request
283
+
284
+
285
+ @mcp.tool(name='create-replication-group')
286
+ @handle_exceptions
287
+ async def create_replication_group(request: CreateReplicationGroupRequest) -> Dict:
288
+ """Create an Amazon ElastiCache replication group.
289
+
290
+ This tool creates a new replication group with specified configuration including:
291
+ - Basic replication group settings
292
+ - Cache node configuration
293
+ - Network and security settings
294
+ - Encryption settings
295
+ - Backup and maintenance settings
296
+ - Monitoring and logging settings
297
+
298
+ Args:
299
+ request: The CreateReplicationGroupRequest object containing all parameters
300
+
301
+ Returns:
302
+ Dict containing information about the created replication group.
303
+ """
304
+ # Check if readonly mode is enabled
305
+ if Context.readonly_mode():
306
+ raise ValueError(
307
+ 'You have configured this tool in readonly mode. To make this change you will have to update your configuration.'
308
+ )
309
+
310
+ # Get ElastiCache client
311
+ elasticache_client = ElastiCacheConnectionManager.get_connection()
312
+
313
+ # Prepare request dictionary
314
+ create_request = prepare_request_dict(request)
315
+
316
+ # Create the replication group
317
+ response = elasticache_client.create_replication_group(**create_request)
318
+ return response
@@ -0,0 +1,68 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Delete replication group tool for ElastiCache MCP server."""
16
+
17
+ from ...common.connection import ElastiCacheConnectionManager
18
+ from ...common.decorators import handle_exceptions
19
+ from ...common.server import mcp
20
+ from ...context import Context
21
+ from typing import Dict, Optional
22
+
23
+
24
+ @mcp.tool(name='delete-replication-group')
25
+ @handle_exceptions
26
+ async def delete_replication_group(
27
+ replication_group_id: str,
28
+ retain_primary_cluster: Optional[bool] = None,
29
+ final_snapshot_name: Optional[str] = None,
30
+ ) -> Dict:
31
+ """Delete an Amazon ElastiCache replication group.
32
+
33
+ This tool deletes an existing replication group. You can optionally retain the primary cluster
34
+ as a standalone cache cluster or create a final snapshot before deletion.
35
+
36
+ Parameters:
37
+ replication_group_id (str): The identifier of the replication group to delete.
38
+ retain_primary_cluster (Optional[bool]): If True, retains the primary cluster as a standalone
39
+ cache cluster. If False, deletes all clusters in the replication group.
40
+ final_snapshot_name (Optional[str]): The name of a final cache cluster snapshot to create
41
+ before deleting the replication group.
42
+
43
+ Returns:
44
+ Dict containing information about the deleted replication group.
45
+ """
46
+ # Check if readonly mode is enabled
47
+ if Context.readonly_mode():
48
+ raise ValueError(
49
+ 'You have configured this tool in readonly mode. To make this change you will have to update your configuration.'
50
+ )
51
+
52
+ # Get ElastiCache client
53
+ elasticache_client = ElastiCacheConnectionManager.get_connection()
54
+
55
+ # Build delete request
56
+ delete_request = {
57
+ 'ReplicationGroupId': replication_group_id,
58
+ }
59
+
60
+ # Add optional parameters if provided
61
+ if retain_primary_cluster is not None:
62
+ delete_request['RetainPrimaryCluster'] = str(retain_primary_cluster).lower()
63
+ if final_snapshot_name:
64
+ delete_request['FinalSnapshotIdentifier'] = final_snapshot_name
65
+
66
+ # Delete the replication group
67
+ response = elasticache_client.delete_replication_group(**delete_request)
68
+ return response
@@ -0,0 +1,68 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Describe replication groups tool for ElastiCache MCP server."""
16
+
17
+ from ...common.connection import ElastiCacheConnectionManager
18
+ from ...common.decorators import handle_exceptions
19
+ from ...common.server import mcp
20
+ from typing import Dict, Optional
21
+
22
+
23
+ @mcp.tool(name='describe-replication-groups')
24
+ @handle_exceptions
25
+ async def describe_replication_groups(
26
+ replication_group_id: Optional[str] = None,
27
+ max_records: Optional[int] = None,
28
+ marker: Optional[str] = None,
29
+ ) -> Dict:
30
+ """Describe one or more ElastiCache replication groups.
31
+
32
+ This tool returns information about provisioned replication groups. If a replication group ID
33
+ is specified, information about only that replication group is returned. Otherwise, information
34
+ about up to MaxRecords replication groups is returned.
35
+
36
+ Parameters:
37
+ replication_group_id (Optional[str]): The identifier for the replication group to describe.
38
+ If not provided, information about all replication groups is returned.
39
+ max_records (Optional[int]): The maximum number of records to include in the response.
40
+ If more records exist than the specified MaxRecords value, a marker is included
41
+ in the response so that the remaining results can be retrieved.
42
+ marker (Optional[str]): An optional marker returned from a previous request. Use this marker
43
+ for pagination of results from this operation. If this parameter is specified,
44
+ the response includes only records beyond the marker, up to the value specified
45
+ by MaxRecords.
46
+
47
+ Returns:
48
+ Dict containing information about the replication group(s), including:
49
+ - ReplicationGroups: List of replication groups
50
+ - Marker: Pagination marker for next set of results
51
+ """
52
+ # Get ElastiCache client
53
+ elasticache_client = ElastiCacheConnectionManager.get_connection()
54
+
55
+ # Build describe request
56
+ describe_request = {}
57
+
58
+ # Add optional parameters if provided
59
+ if replication_group_id:
60
+ describe_request['ReplicationGroupId'] = replication_group_id
61
+ if max_records:
62
+ describe_request['MaxRecords'] = max_records
63
+ if marker:
64
+ describe_request['Marker'] = marker
65
+
66
+ # Describe the replication group(s)
67
+ response = elasticache_client.describe_replication_groups(**describe_request)
68
+ return response
@@ -0,0 +1,236 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Modify replication group tool for ElastiCache MCP server."""
16
+
17
+ import json
18
+ from ...common.connection import ElastiCacheConnectionManager
19
+ from ...common.decorators import handle_exceptions
20
+ from ...common.server import mcp
21
+ from ...context import Context
22
+ from .processors import process_log_delivery_configurations, process_resharding_configuration
23
+ from pydantic import BaseModel, ConfigDict, Field
24
+ from typing import Any, Dict, List, Optional, Union
25
+
26
+
27
+ @mcp.tool(name='modify-replication-group-shard-configuration')
28
+ @handle_exceptions
29
+ async def modify_replication_group_shard_configuration(
30
+ replication_group_id: str,
31
+ node_group_count: int,
32
+ apply_immediately: Optional[bool] = None,
33
+ resharding_configuration: Optional[Union[str, List[Dict]]] = None,
34
+ ) -> Dict:
35
+ """Modify the shard configuration of an existing Amazon ElastiCache replication group.
36
+
37
+ This tool modifies the shard configuration of an existing replication group by:
38
+ - Modifying the number of replicas in a shard
39
+ - Specifying preferred availability zones for replicas
40
+
41
+ Parameters:
42
+ replication_group_id (str): The identifier of the replication group to modify.
43
+ node_group_count (int): The number of node groups (shards) in the replication group.
44
+ apply_immediately (Optional[bool]): Whether to apply changes immediately or during maintenance window.
45
+ resharding_configuration (Optional[Union[str, List[Dict]]]): Resharding configuration in either shorthand string format or list of dictionaries format.
46
+ Shorthand format: "NodeGroupId=string,NewShardConfiguration={NewReplicaCount=integer,PreferredAvailabilityZones=string1,string2}"
47
+ Multiple configurations can be separated by spaces.
48
+ JSON format: List of dictionaries with required fields:
49
+ - NodeGroupId: string
50
+ - NewShardConfiguration:
51
+ - NewReplicaCount: integer
52
+ - PreferredAvailabilityZones: list of strings (optional)
53
+
54
+ Returns:
55
+ Dict containing information about the modified replication group.
56
+ """
57
+ # Check if readonly mode is enabled
58
+ if Context.readonly_mode():
59
+ raise ValueError(
60
+ 'You have configured this tool in readonly mode. To make this change you will have to update your configuration.'
61
+ )
62
+
63
+ # Get ElastiCache client
64
+ elasticache_client = ElastiCacheConnectionManager.get_connection()
65
+
66
+ # Build modify request
67
+ modify_request = {
68
+ 'ReplicationGroupId': replication_group_id,
69
+ 'NodeGroupCount': node_group_count,
70
+ }
71
+
72
+ # Add optional parameters if provided
73
+ if apply_immediately is not None:
74
+ modify_request['ApplyImmediately'] = str(apply_immediately).lower()
75
+
76
+ if resharding_configuration:
77
+ try:
78
+ processed_configs = process_resharding_configuration(resharding_configuration)
79
+ modify_request['ReshardingConfiguration'] = json.dumps(processed_configs)
80
+ except ValueError as e:
81
+ return {'error': str(e)}
82
+
83
+ # Modify the replication group shard configuration
84
+ response = elasticache_client.modify_replication_group_shard_configuration(**modify_request)
85
+ return response
86
+
87
+
88
+ class ModifyReplicationGroupRequest(BaseModel):
89
+ """Request model for modifying an ElastiCache replication group."""
90
+
91
+ model_config = ConfigDict(extra='allow') # Allow extra fields to support future API additions
92
+
93
+ replication_group_id: str = Field(..., description='The identifier of the replication group')
94
+ apply_immediately: Optional[bool] = None
95
+ auto_minor_version_upgrade: Optional[bool] = None
96
+ automatic_failover_enabled: Optional[bool] = None
97
+ cache_node_type: Optional[str] = None
98
+ cache_parameter_group_name: Optional[str] = None
99
+ cache_security_group_names: Optional[List[str]] = None
100
+ engine_version: Optional[str] = None
101
+ log_delivery_configurations: Optional[Union[str, List[Dict]]] = None
102
+ maintenance_window: Optional[str] = None
103
+ multi_az_enabled: Optional[bool] = None
104
+ notification_topic_arn: Optional[str] = None
105
+ notification_topic_status: Optional[str] = None
106
+ num_node_groups: Optional[int] = None
107
+ preferred_node_groups_to_remove: Optional[List[int]] = None
108
+ primary_cluster_id: Optional[str] = None
109
+ replicas_per_node_group: Optional[int] = None
110
+ replication_group_description: Optional[str] = None
111
+ security_group_ids: Optional[List[str]] = None
112
+ snapshot_retention_limit: Optional[int] = None
113
+ snapshot_window: Optional[str] = None
114
+ user_group_ids_to_add: Optional[List[str]] = None
115
+ user_group_ids_to_remove: Optional[List[str]] = None
116
+ node_group_id: Optional[str] = None
117
+ remove_user_groups: Optional[bool] = None
118
+ auth_token: Optional[str] = None
119
+ auth_token_update_strategy: Optional[str] = None
120
+
121
+
122
+ def prepare_modify_request_dict(request: ModifyReplicationGroupRequest) -> Dict[str, Any]:
123
+ """Prepare the request dictionary for the AWS API.
124
+
125
+ Args:
126
+ request: The ModifyReplicationGroupRequest object
127
+
128
+ Returns:
129
+ Dict containing the properly formatted request parameters
130
+ """
131
+ # Start with required parameters
132
+ modify_request: Dict[str, Any] = {
133
+ 'ReplicationGroupId': request.replication_group_id,
134
+ }
135
+
136
+ # Optional string parameters
137
+ for param_name, value in [
138
+ ('CacheNodeType', request.cache_node_type),
139
+ ('CacheParameterGroupName', request.cache_parameter_group_name),
140
+ ('EngineVersion', request.engine_version),
141
+ ('PreferredMaintenanceWindow', request.maintenance_window),
142
+ ('NotificationTopicArn', request.notification_topic_arn),
143
+ ('NotificationTopicStatus', request.notification_topic_status),
144
+ ('PrimaryClusterId', request.primary_cluster_id),
145
+ ('ReplicationGroupDescription', request.replication_group_description),
146
+ ('SnapshotWindow', request.snapshot_window),
147
+ ('NodeGroupId', request.node_group_id),
148
+ ('AuthToken', request.auth_token),
149
+ ('AuthTokenUpdateStrategy', request.auth_token_update_strategy),
150
+ ]:
151
+ if value:
152
+ modify_request[param_name] = str(value)
153
+
154
+ # Optional numeric parameters
155
+ for param_name, value in [
156
+ ('NodeGroupCount', request.num_node_groups),
157
+ ('ReplicasPerNodeGroup', request.replicas_per_node_group),
158
+ ('SnapshotRetentionLimit', request.snapshot_retention_limit),
159
+ ]:
160
+ if value is not None:
161
+ modify_request[param_name] = value
162
+
163
+ # Optional boolean parameters
164
+ for param_name, value in [
165
+ ('ApplyImmediately', request.apply_immediately),
166
+ ('AutoMinorVersionUpgrade', request.auto_minor_version_upgrade),
167
+ ('AutomaticFailoverEnabled', request.automatic_failover_enabled),
168
+ ('MultiAZEnabled', request.multi_az_enabled),
169
+ ('RemoveUserGroups', request.remove_user_groups),
170
+ ]:
171
+ if value is not None:
172
+ modify_request[param_name] = value
173
+
174
+ # Optional list parameters
175
+ for param_name, value in [
176
+ ('CacheSecurityGroupNames', request.cache_security_group_names),
177
+ ('SecurityGroupIds', request.security_group_ids),
178
+ ('UserGroupIdsToAdd', request.user_group_ids_to_add),
179
+ ('UserGroupIdsToRemove', request.user_group_ids_to_remove),
180
+ ]:
181
+ if value:
182
+ modify_request[param_name] = list(value)
183
+
184
+ # Handle node groups to remove
185
+ if request.preferred_node_groups_to_remove:
186
+ modify_request['NodeGroupsToRemove'] = request.preferred_node_groups_to_remove
187
+
188
+ # Handle log delivery configurations
189
+ if request.log_delivery_configurations:
190
+ try:
191
+ processed_configs = process_log_delivery_configurations(
192
+ request.log_delivery_configurations
193
+ )
194
+ if processed_configs:
195
+ modify_request['LogDeliveryConfigurations'] = processed_configs
196
+ except ValueError as e:
197
+ return {'error': str(e)}
198
+
199
+ return modify_request
200
+
201
+
202
+ @mcp.tool(name='modify-replication-group')
203
+ @handle_exceptions
204
+ async def modify_replication_group(request: ModifyReplicationGroupRequest) -> Dict:
205
+ """Modify an existing Amazon ElastiCache replication group.
206
+
207
+ This tool modifies the settings of an existing replication group including:
208
+ - Node configuration
209
+ - Security settings
210
+ - Maintenance settings
211
+ - Backup settings
212
+ - Engine settings
213
+ - Monitoring settings
214
+ - User group settings
215
+
216
+ Args:
217
+ request: The ModifyReplicationGroupRequest object containing all parameters
218
+
219
+ Returns:
220
+ Dict containing information about the modified replication group.
221
+ """
222
+ # Check if readonly mode is enabled
223
+ if Context.readonly_mode():
224
+ raise ValueError(
225
+ 'You have configured this tool in readonly mode. To make this change you will have to update your configuration.'
226
+ )
227
+
228
+ # Get ElastiCache client
229
+ elasticache_client = ElastiCacheConnectionManager.get_connection()
230
+
231
+ # Prepare request dictionary
232
+ modify_request = prepare_modify_request_dict(request)
233
+
234
+ # Modify the replication group
235
+ response = elasticache_client.modify_replication_group(**modify_request)
236
+ return response