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,65 @@
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 cache cluster 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 Optional
22
+
23
+
24
+ @mcp.tool(name='delete-cache-cluster')
25
+ @handle_exceptions
26
+ async def delete_cache_cluster(
27
+ cache_cluster_id: str,
28
+ final_snapshot_identifier: Optional[str] = None,
29
+ ) -> dict:
30
+ """Delete an Amazon ElastiCache cache cluster.
31
+
32
+ This tool deletes an existing cache cluster. Optionally, it can create a final
33
+ snapshot of the cluster before deletion.
34
+
35
+ Parameters:
36
+ cache_cluster_id (str): The ID of the cache cluster to delete.
37
+ final_snapshot_identifier (Optional[str]): The user-supplied name of a final
38
+ cache cluster snapshot. This is the unique name that identifies the
39
+ snapshot. ElastiCache creates the snapshot, and then deletes the cache
40
+ cluster immediately afterward.
41
+
42
+ Returns:
43
+ Dict containing information about the deleted cache cluster.
44
+ """
45
+ # Check if readonly mode is enabled
46
+ if Context.readonly_mode():
47
+ raise ValueError(
48
+ 'You have configured this tool in readonly mode. To make this change you will have to update your configuration.'
49
+ )
50
+
51
+ # Get ElastiCache client
52
+ elasticache_client = ElastiCacheConnectionManager.get_connection()
53
+
54
+ # Build delete request
55
+ delete_request = {
56
+ 'CacheClusterId': cache_cluster_id,
57
+ }
58
+
59
+ # Add optional final snapshot if provided
60
+ if final_snapshot_identifier:
61
+ delete_request['FinalSnapshotIdentifier'] = final_snapshot_identifier
62
+
63
+ # Delete the cache cluster
64
+ response = elasticache_client.delete_cache_cluster(**delete_request)
65
+ return response
@@ -0,0 +1,80 @@
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 cache clusters 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-cache-clusters')
24
+ @handle_exceptions
25
+ async def describe_cache_clusters(
26
+ cache_cluster_id: Optional[str] = None,
27
+ max_records: Optional[int] = None,
28
+ marker: Optional[str] = None,
29
+ show_cache_node_info: Optional[bool] = None,
30
+ show_cache_clusters_not_in_replication_groups: Optional[bool] = None,
31
+ ) -> Dict:
32
+ """Describe one or more ElastiCache cache clusters.
33
+
34
+ This tool returns information about provisioned cache clusters. If a cache cluster ID
35
+ is specified, information about only that cache cluster is returned. Otherwise, information
36
+ about up to MaxRecords cache clusters is returned.
37
+
38
+ Parameters:
39
+ cache_cluster_id (Optional[str]): The identifier for the cache cluster to describe.
40
+ If not provided, information about all cache clusters is returned.
41
+ max_records (Optional[int]): The maximum number of records to include in the response.
42
+ If more records exist than the specified MaxRecords value, a marker is included
43
+ in the response so that the remaining results can be retrieved.
44
+ marker (Optional[str]): An optional marker returned from a previous request. Use this marker
45
+ for pagination of results from this operation. If this parameter is specified,
46
+ the response includes only records beyond the marker, up to the value specified
47
+ by MaxRecords.
48
+ show_cache_node_info (Optional[bool]): Whether to include detailed information about
49
+ cache nodes in the response.
50
+ show_cache_clusters_not_in_replication_groups (Optional[bool]): Whether to show only
51
+ cache clusters that are not members of a replication group.
52
+
53
+ Returns:
54
+ Dict containing information about the cache cluster(s), including:
55
+ - CacheClusters: List of cache clusters
56
+ - Marker: Pagination marker for next set of results
57
+ """
58
+ # Get ElastiCache client
59
+ elasticache_client = ElastiCacheConnectionManager.get_connection()
60
+
61
+ # Build describe request
62
+ describe_request = {}
63
+
64
+ # Add optional parameters if provided
65
+ if cache_cluster_id:
66
+ describe_request['CacheClusterId'] = cache_cluster_id
67
+ if max_records:
68
+ describe_request['MaxRecords'] = max_records
69
+ if marker:
70
+ describe_request['Marker'] = marker
71
+ if show_cache_node_info is not None:
72
+ describe_request['ShowCacheNodeInfo'] = show_cache_node_info
73
+ if show_cache_clusters_not_in_replication_groups is not None:
74
+ describe_request['ShowCacheClustersNotInReplicationGroups'] = (
75
+ show_cache_clusters_not_in_replication_groups
76
+ )
77
+
78
+ # Describe the cache cluster(s)
79
+ response = elasticache_client.describe_cache_clusters(**describe_request)
80
+ return response
@@ -0,0 +1,159 @@
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 cache cluster 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 ..rg.processors import process_log_delivery_configurations
22
+ from .processors import process_scale_config
23
+ from pydantic import BaseModel, ConfigDict, Field
24
+ from typing import Any, Dict, List, Optional, Union
25
+
26
+
27
+ class ModifyCacheClusterRequest(BaseModel):
28
+ """Request model for modifying an ElastiCache cache cluster."""
29
+
30
+ model_config = ConfigDict(validate_by_name=True, arbitrary_types_allowed=True)
31
+
32
+ cache_cluster_id: str = Field(..., description='The cache cluster to modify')
33
+ num_cache_nodes: Optional[int] = Field(None, description='The new number of cache nodes')
34
+ cache_node_ids_to_remove: Optional[List[str]] = Field(
35
+ None, description='Cache node IDs to remove when scaling down'
36
+ )
37
+ az_mode: Optional[str] = Field(
38
+ None,
39
+ description='Specifies whether nodes in this Memcached cluster are created in a single AZ or multiple AZs',
40
+ )
41
+ new_availability_zones: Optional[List[str]] = Field(
42
+ None, description='List of Availability Zones to use when modifying AZ mode'
43
+ )
44
+ cache_security_group_names: Optional[List[str]] = Field(
45
+ None, description='List of cache security group names to associate'
46
+ )
47
+ security_group_ids: Optional[List[str]] = Field(
48
+ None, description='List of Amazon VPC security group IDs'
49
+ )
50
+ preferred_maintenance_window: Optional[str] = Field(
51
+ None, description='The weekly time range for maintenance'
52
+ )
53
+ notification_topic_arn: Optional[str] = Field(
54
+ None, description='The ARN of an SNS topic for notifications'
55
+ )
56
+ cache_parameter_group_name: Optional[str] = Field(
57
+ None, description='The name of the cache parameter group to apply'
58
+ )
59
+ notification_topic_status: Optional[str] = Field(
60
+ None, description='The status of the SNS notification topic'
61
+ )
62
+ apply_immediately: Optional[bool] = Field(
63
+ None, description='Whether to apply changes immediately or during maintenance window'
64
+ )
65
+ engine_version: Optional[str] = Field(
66
+ None, description='The upgraded version of the cache engine'
67
+ )
68
+ auto_minor_version_upgrade: Optional[bool] = Field(
69
+ None, description='Enable/disable automatic minor version upgrades'
70
+ )
71
+ snapshot_retention_limit: Optional[int] = Field(
72
+ None, description='The number of days to retain backups'
73
+ )
74
+ snapshot_window: Optional[str] = Field(None, description='The daily time range for backups')
75
+ cache_node_type: Optional[str] = Field(
76
+ None, description='The new compute and memory capacity of the nodes'
77
+ )
78
+ auth_token: Optional[str] = Field(
79
+ None, description='The password used to access a password protected server'
80
+ )
81
+ auth_token_update_strategy: Optional[str] = Field(
82
+ None, description="Strategy to use when updating auth token ('SET', 'ROTATE', 'DELETE')"
83
+ )
84
+ log_delivery_configurations: Optional[Union[str, List[Dict]]] = Field(
85
+ None, description='Log delivery configurations'
86
+ )
87
+ scale_config: Optional[Union[str, Dict]] = Field(None, description='Scale configuration')
88
+
89
+
90
+ @mcp.tool(name='modify-cache-cluster')
91
+ @handle_exceptions
92
+ async def modify_cache_cluster(request: ModifyCacheClusterRequest) -> Dict:
93
+ """Modify an existing Amazon ElastiCache cache cluster."""
94
+ # Check if readonly mode is enabled
95
+ if Context.readonly_mode():
96
+ raise ValueError(
97
+ 'You have configured this tool in readonly mode. To make this change you will have to update your configuration.'
98
+ )
99
+
100
+ # Get ElastiCache client
101
+ elasticache_client = ElastiCacheConnectionManager.get_connection()
102
+
103
+ # Convert request model to dictionary, only including non-None values
104
+ modify_request: Dict[str, Any] = {'CacheClusterId': request.cache_cluster_id}
105
+
106
+ if request.num_cache_nodes is not None:
107
+ modify_request['NumCacheNodes'] = request.num_cache_nodes
108
+ if request.cache_node_ids_to_remove:
109
+ modify_request['CacheNodeIdsToRemove'] = request.cache_node_ids_to_remove
110
+ if request.az_mode:
111
+ modify_request['AZMode'] = request.az_mode
112
+ if request.new_availability_zones:
113
+ modify_request['NewAvailabilityZones'] = request.new_availability_zones
114
+ if request.cache_security_group_names:
115
+ modify_request['CacheSecurityGroupNames'] = request.cache_security_group_names
116
+ if request.security_group_ids:
117
+ modify_request['SecurityGroupIds'] = request.security_group_ids
118
+ if request.preferred_maintenance_window:
119
+ modify_request['PreferredMaintenanceWindow'] = request.preferred_maintenance_window
120
+ if request.notification_topic_arn:
121
+ modify_request['NotificationTopicArn'] = request.notification_topic_arn
122
+ if request.cache_parameter_group_name:
123
+ modify_request['CacheParameterGroupName'] = request.cache_parameter_group_name
124
+ if request.notification_topic_status:
125
+ modify_request['NotificationTopicStatus'] = request.notification_topic_status
126
+ if request.apply_immediately is not None:
127
+ modify_request['ApplyImmediately'] = request.apply_immediately
128
+ if request.engine_version:
129
+ modify_request['EngineVersion'] = request.engine_version
130
+ if request.auto_minor_version_upgrade is not None:
131
+ modify_request['AutoMinorVersionUpgrade'] = request.auto_minor_version_upgrade
132
+ if request.snapshot_retention_limit is not None:
133
+ modify_request['SnapshotRetentionLimit'] = request.snapshot_retention_limit
134
+ if request.snapshot_window:
135
+ modify_request['SnapshotWindow'] = request.snapshot_window
136
+ if request.cache_node_type:
137
+ modify_request['CacheNodeType'] = request.cache_node_type
138
+ if request.auth_token:
139
+ modify_request['AuthToken'] = request.auth_token
140
+ if request.auth_token_update_strategy:
141
+ modify_request['AuthTokenUpdateStrategy'] = request.auth_token_update_strategy
142
+ if request.log_delivery_configurations:
143
+ try:
144
+ processed_configs = process_log_delivery_configurations(
145
+ request.log_delivery_configurations
146
+ )
147
+ modify_request['LogDeliveryConfigurations'] = processed_configs
148
+ except ValueError as e:
149
+ return {'error': str(e)}
150
+ if request.scale_config:
151
+ try:
152
+ processed_scale_config = process_scale_config(request.scale_config)
153
+ modify_request['ScaleConfig'] = processed_scale_config
154
+ except ValueError as e:
155
+ return {'error': str(e)}
156
+
157
+ # Modify the cache cluster
158
+ response = elasticache_client.modify_cache_cluster(**modify_request)
159
+ return response
@@ -0,0 +1,78 @@
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
+ """Parser functions for ElastiCache cache cluster tools."""
16
+
17
+ from typing import Any, Dict
18
+
19
+
20
+ def parse_shorthand_scale_config(config: str) -> Dict[str, Any]:
21
+ """Parse a scale configuration from shorthand syntax.
22
+
23
+ Args:
24
+ config: Shorthand syntax string for scale configuration
25
+ Format: ReplicasPerNodeGroup=int,AutomaticFailoverEnabled=bool,ScaleOutEnabled=bool,
26
+ ScaleInEnabled=bool,TargetCapacity=int,MinCapacity=int,MaxCapacity=int
27
+
28
+ Returns:
29
+ Dictionary containing the parsed scale configuration
30
+
31
+ Raises:
32
+ ValueError: If the syntax is invalid
33
+ """
34
+ if not config:
35
+ raise ValueError('Empty scale configuration')
36
+
37
+ result = {}
38
+ pairs = config.split(',')
39
+
40
+ # Define valid keys and their processors
41
+ key_processors = {
42
+ 'ReplicasPerNodeGroup': int,
43
+ 'AutomaticFailoverEnabled': lambda x: x.lower() == 'true',
44
+ 'ScaleOutEnabled': lambda x: x.lower() == 'true',
45
+ 'ScaleInEnabled': lambda x: x.lower() == 'true',
46
+ 'TargetCapacity': int,
47
+ 'MinCapacity': int,
48
+ 'MaxCapacity': int,
49
+ }
50
+
51
+ for pair in pairs:
52
+ if '=' not in pair:
53
+ raise ValueError(f'Invalid format. Each parameter must be in key=value format: {pair}')
54
+
55
+ key, value = pair.split('=', 1)
56
+ if not key or not value:
57
+ raise ValueError(f'Empty key or value: {pair}')
58
+
59
+ if key not in key_processors:
60
+ raise ValueError(f'Invalid parameter: {key}')
61
+
62
+ try:
63
+ result[key] = key_processors[key](value)
64
+ except ValueError as e:
65
+ raise ValueError(f'Invalid value for {key}: {value}') from e
66
+
67
+ # Validate capacity values if present
68
+ if 'MinCapacity' in result and 'MaxCapacity' in result:
69
+ if result['MinCapacity'] > result['MaxCapacity']:
70
+ raise ValueError('MinCapacity cannot be greater than MaxCapacity')
71
+ if 'TargetCapacity' in result:
72
+ if (
73
+ result['TargetCapacity'] < result['MinCapacity']
74
+ or result['TargetCapacity'] > result['MaxCapacity']
75
+ ):
76
+ raise ValueError('TargetCapacity must be between MinCapacity and MaxCapacity')
77
+
78
+ return result
@@ -0,0 +1,74 @@
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
+ """Processor functions for ElastiCache cache cluster tools."""
16
+
17
+ from .parsers import parse_shorthand_scale_config
18
+ from typing import Dict, Union
19
+
20
+
21
+ def process_scale_config(scale_config: Union[str, Dict]) -> Dict:
22
+ """Process scale configuration in either shorthand or JSON format.
23
+
24
+ Args:
25
+ scale_config: Scale configuration in either format
26
+ Shorthand format: "ReplicasPerNodeGroup=int,AutomaticFailoverEnabled=bool,..."
27
+ JSON format: Dictionary with scale configuration parameters
28
+
29
+ Returns:
30
+ Dictionary containing the processed scale configuration
31
+
32
+ Raises:
33
+ ValueError: If the configuration is invalid
34
+ """
35
+ if isinstance(scale_config, str):
36
+ # Parse shorthand syntax
37
+ try:
38
+ return parse_shorthand_scale_config(scale_config)
39
+ except ValueError as e:
40
+ raise ValueError(f'Invalid scale config shorthand syntax: {str(e)}')
41
+ else:
42
+ # Handle JSON format
43
+ if not isinstance(scale_config, dict):
44
+ raise ValueError('Scale configuration must be a dictionary or a shorthand string')
45
+
46
+ # Validate required fields and types
47
+ field_types = {
48
+ 'ReplicasPerNodeGroup': int,
49
+ 'AutomaticFailoverEnabled': bool,
50
+ 'ScaleOutEnabled': bool,
51
+ 'ScaleInEnabled': bool,
52
+ 'TargetCapacity': int,
53
+ 'MinCapacity': int,
54
+ 'MaxCapacity': int,
55
+ }
56
+
57
+ processed_config = {}
58
+ for field, field_type in field_types.items():
59
+ if field in scale_config:
60
+ if not isinstance(scale_config[field], field_type):
61
+ raise ValueError(f'{field} must be of type {field_type.__name__}')
62
+ processed_config[field] = scale_config[field]
63
+
64
+ # Validate capacity values if present
65
+ if 'MinCapacity' in processed_config and 'MaxCapacity' in processed_config:
66
+ if processed_config['MinCapacity'] > processed_config['MaxCapacity']:
67
+ raise ValueError('MinCapacity cannot be greater than MaxCapacity')
68
+ if 'TargetCapacity' in processed_config and (
69
+ processed_config['TargetCapacity'] < processed_config['MinCapacity']
70
+ or processed_config['TargetCapacity'] > processed_config['MaxCapacity']
71
+ ):
72
+ raise ValueError('TargetCapacity must be between MinCapacity and MaxCapacity')
73
+
74
+ return processed_config
@@ -0,0 +1,19 @@
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
+ """Cost Explorer tools for ElastiCache MCP server."""
16
+
17
+ from .get_cost_and_usage import get_cost_and_usage, GetCostAndUsageRequest
18
+
19
+ __all__ = ['get_cost_and_usage', 'GetCostAndUsageRequest']
@@ -0,0 +1,76 @@
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
+ """Get cost and usage data for ElastiCache resources."""
16
+
17
+ from ...common.connection import CostExplorerConnectionManager
18
+ from ...common.decorators import handle_exceptions
19
+ from ...common.server import mcp
20
+ from pydantic import BaseModel, ConfigDict, Field
21
+ from typing import Any, Dict
22
+
23
+
24
+ class GetCostAndUsageRequest(BaseModel):
25
+ """Request model for getting cost and usage data."""
26
+
27
+ model_config = ConfigDict(validate_by_name=True, arbitrary_types_allowed=True)
28
+
29
+ time_period: str = Field(
30
+ ..., description='Time period for the cost and usage data. Format: YYYY-MM-DD/YYYY-MM-DD'
31
+ )
32
+ granularity: str = Field(
33
+ ..., description='The granularity of the cost and usage data (DAILY, MONTHLY, or HOURLY)'
34
+ )
35
+
36
+
37
+ @mcp.tool(name='get-cost-and-usage')
38
+ @handle_exceptions
39
+ async def get_cost_and_usage(request: GetCostAndUsageRequest) -> Dict[str, Any]:
40
+ """Get cost and usage data for ElastiCache resources.
41
+
42
+ This tool retrieves cost and usage data for ElastiCache resources with customizable
43
+ time periods and granularity. It uses default configurations for:
44
+ - Metrics: BlendedCost, UnblendedCost, UsageQuantity
45
+ - Group By: SERVICE dimension and Environment tag
46
+ - Filter: Filtered to Amazon ElastiCache service
47
+
48
+ Args:
49
+ request: The GetCostAndUsageRequest object containing:
50
+ - time_period: Time period in YYYY-MM-DD/YYYY-MM-DD format
51
+ - granularity: Data granularity (DAILY, MONTHLY, or HOURLY)
52
+
53
+ Returns:
54
+ Dict containing the cost and usage data.
55
+ """
56
+ # Get Cost Explorer client
57
+ ce_client = CostExplorerConnectionManager.get_connection()
58
+
59
+ # Split time period into start and end dates
60
+ start_date, end_date = request.time_period.split('/')
61
+
62
+ # Prepare request parameters
63
+ params = {
64
+ 'TimePeriod': {'Start': start_date, 'End': end_date},
65
+ 'Granularity': request.granularity,
66
+ 'Metrics': ['BlendedCost', 'UnblendedCost', 'UsageQuantity'],
67
+ 'GroupBy': [
68
+ {'Type': 'DIMENSION', 'Key': 'SERVICE'},
69
+ {'Type': 'TAG', 'Key': 'Environment'},
70
+ ],
71
+ 'Filter': {'Dimensions': {'Key': 'SERVICE', 'Values': ['Amazon ElastiCache']}},
72
+ }
73
+
74
+ # Get cost and usage data
75
+ response = ce_client.get_cost_and_usage(**params)
76
+ return response
@@ -0,0 +1,19 @@
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
+ """CloudWatch tools."""
16
+
17
+ from .get_metric_statistics import get_metric_statistics
18
+
19
+ __all__ = ['get_metric_statistics']
@@ -0,0 +1,85 @@
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
+ """Tool for getting CloudWatch metric statistics."""
16
+
17
+ from ...common.connection import CloudWatchConnectionManager
18
+ from ...common.decorators import handle_exceptions
19
+ from ...common.server import mcp
20
+ from datetime import datetime
21
+ from typing import Any, Dict, List, Optional
22
+
23
+
24
+ @mcp.tool(name='get-metric-statistics')
25
+ @handle_exceptions
26
+ async def get_metric_statistics(
27
+ metric_name: str,
28
+ start_time: str,
29
+ end_time: str,
30
+ period: int,
31
+ dimensions: Optional[List[Dict[str, str]]] = None,
32
+ statistics: Optional[List[str]] = None,
33
+ extended_statistics: Optional[List[str]] = None,
34
+ unit: Optional[str] = None,
35
+ ) -> Dict[str, Any]:
36
+ """Get CloudWatch metric statistics.
37
+
38
+ Args:
39
+ metric_name: The name of the metric
40
+ start_time: The start time in ISO 8601 format
41
+ end_time: The end time in ISO 8601 format
42
+ period: The granularity, in seconds, of the returned data points
43
+ dimensions: The dimensions to filter by
44
+ statistics: The metric statistics to return
45
+ extended_statistics: The percentile statistics to return
46
+ unit: The unit for the metric
47
+
48
+ Returns:
49
+ Dict containing metric statistics
50
+ """
51
+ client = CloudWatchConnectionManager.get_connection()
52
+
53
+ # Convert ISO 8601 strings to datetime objects
54
+ start = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
55
+ end = datetime.fromisoformat(end_time.replace('Z', '+00:00'))
56
+
57
+ # Build request parameters
58
+ params: Dict[str, Any] = {
59
+ 'Namespace': 'AWS/ElastiCache',
60
+ 'MetricName': metric_name,
61
+ 'StartTime': start,
62
+ 'EndTime': end,
63
+ 'Period': period,
64
+ }
65
+
66
+ # Add optional parameters
67
+ if dimensions:
68
+ params['Dimensions'] = [{'Name': k, 'Value': v} for d in dimensions for k, v in d.items()]
69
+ if statistics:
70
+ params['Statistics'] = statistics
71
+ if extended_statistics:
72
+ params['ExtendedStatistics'] = extended_statistics
73
+ if unit:
74
+ params['Unit'] = unit
75
+
76
+ # Make API call
77
+ response = client.get_metric_statistics(**params)
78
+
79
+ # Extract relevant information
80
+ datapoints = response.get('Datapoints', [])
81
+ label = response.get('Label')
82
+
83
+ result = {'Label': label, 'Datapoints': datapoints}
84
+
85
+ return result