awslabs.elasticache-mcp-server 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- awslabs/elasticache_mcp_server/tools/cc/connect.py +91 -6
- awslabs/elasticache_mcp_server/tools/cw/get_metric_statistics.py +11 -1
- awslabs/elasticache_mcp_server/tools/rg/connect.py +129 -95
- awslabs/elasticache_mcp_server/tools/serverless/connect.py +130 -19
- awslabs/elasticache_mcp_server/tools/serverless/create.py +3 -3
- awslabs/elasticache_mcp_server/tools/serverless/delete.py +1 -1
- awslabs/elasticache_mcp_server/tools/serverless/models.py +2 -6
- awslabs/elasticache_mcp_server/tools/serverless/modify.py +2 -2
- {awslabs_elasticache_mcp_server-0.1.1.dist-info → awslabs_elasticache_mcp_server-0.1.3.dist-info}/METADATA +14 -12
- {awslabs_elasticache_mcp_server-0.1.1.dist-info → awslabs_elasticache_mcp_server-0.1.3.dist-info}/RECORD +14 -14
- {awslabs_elasticache_mcp_server-0.1.1.dist-info → awslabs_elasticache_mcp_server-0.1.3.dist-info}/WHEEL +0 -0
- {awslabs_elasticache_mcp_server-0.1.1.dist-info → awslabs_elasticache_mcp_server-0.1.3.dist-info}/entry_points.txt +0 -0
- {awslabs_elasticache_mcp_server-0.1.1.dist-info → awslabs_elasticache_mcp_server-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {awslabs_elasticache_mcp_server-0.1.1.dist-info → awslabs_elasticache_mcp_server-0.1.3.dist-info}/licenses/NOTICE +0 -0
|
@@ -19,7 +19,7 @@ from ...common.decorators import handle_exceptions
|
|
|
19
19
|
from ...common.server import mcp
|
|
20
20
|
from ...context import Context
|
|
21
21
|
from botocore.exceptions import ClientError
|
|
22
|
-
from typing import Any, Dict, Tuple, Union
|
|
22
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
async def _configure_security_groups(
|
|
@@ -252,18 +252,20 @@ async def get_ssh_tunnel_command_cc(
|
|
|
252
252
|
@handle_exceptions
|
|
253
253
|
async def create_jump_host_cc(
|
|
254
254
|
cache_cluster_id: str,
|
|
255
|
-
subnet_id: str,
|
|
256
|
-
security_group_id: str,
|
|
257
255
|
key_name: str,
|
|
256
|
+
subnet_id: Optional[str] = None,
|
|
257
|
+
security_group_id: Optional[str] = None,
|
|
258
258
|
instance_type: str = 't3.small',
|
|
259
259
|
) -> Dict[str, Any]:
|
|
260
260
|
"""Creates an EC2 jump host instance to access an ElastiCache cluster via SSH tunnel.
|
|
261
261
|
|
|
262
262
|
Args:
|
|
263
263
|
cache_cluster_id (str): ID of the ElastiCache cluster to connect to
|
|
264
|
-
subnet_id (str): ID of the subnet to launch the EC2 instance in (must be public)
|
|
265
|
-
security_group_id (str): ID of the security group to assign to the EC2 instance
|
|
266
264
|
key_name (str): Name of the EC2 key pair to use for SSH access
|
|
265
|
+
subnet_id (str, optional): ID of the subnet to launch the EC2 instance in (must be public).
|
|
266
|
+
If not provided and cache uses default VPC, will auto-select a default subnet.
|
|
267
|
+
security_group_id (str, optional): ID of the security group to assign to the EC2 instance.
|
|
268
|
+
If not provided and cache uses default VPC, will use the default security group.
|
|
267
269
|
instance_type (str, optional): EC2 instance type. Defaults to "t3.small"
|
|
268
270
|
|
|
269
271
|
Returns:
|
|
@@ -306,6 +308,59 @@ async def create_jump_host_cc(
|
|
|
306
308
|
)['CacheSubnetGroups'][0]
|
|
307
309
|
cache_vpc_id = cache_subnet_group['VpcId']
|
|
308
310
|
|
|
311
|
+
# Check if cache is in default VPC
|
|
312
|
+
vpcs = ec2_client.describe_vpcs(VpcIds=[cache_vpc_id])['Vpcs']
|
|
313
|
+
cache_vpc = vpcs[0] if vpcs else None
|
|
314
|
+
is_default_vpc = cache_vpc and cache_vpc.get('IsDefault', False)
|
|
315
|
+
|
|
316
|
+
# Auto-select subnet if not provided and cache is in default VPC
|
|
317
|
+
if not subnet_id and is_default_vpc:
|
|
318
|
+
# Get default subnets in the default VPC
|
|
319
|
+
subnets = ec2_client.describe_subnets(
|
|
320
|
+
Filters=[
|
|
321
|
+
{'Name': 'vpc-id', 'Values': [cache_vpc_id]},
|
|
322
|
+
{'Name': 'default-for-az', 'Values': ['true']},
|
|
323
|
+
]
|
|
324
|
+
)['Subnets']
|
|
325
|
+
|
|
326
|
+
if subnets:
|
|
327
|
+
# Pick the first available default subnet
|
|
328
|
+
subnet_id = subnets[0]['SubnetId']
|
|
329
|
+
else:
|
|
330
|
+
# Fallback to any public subnet in the VPC
|
|
331
|
+
all_subnets = ec2_client.describe_subnets(
|
|
332
|
+
Filters=[{'Name': 'vpc-id', 'Values': [cache_vpc_id]}]
|
|
333
|
+
)['Subnets']
|
|
334
|
+
|
|
335
|
+
for subnet in all_subnets:
|
|
336
|
+
if subnet.get('MapPublicIpOnLaunch', False):
|
|
337
|
+
subnet_id = subnet['SubnetId']
|
|
338
|
+
break
|
|
339
|
+
|
|
340
|
+
# Auto-select security group if not provided and cache is in default VPC
|
|
341
|
+
if not security_group_id and is_default_vpc:
|
|
342
|
+
# Get the default security group for the VPC
|
|
343
|
+
security_groups = ec2_client.describe_security_groups(
|
|
344
|
+
Filters=[
|
|
345
|
+
{'Name': 'vpc-id', 'Values': [cache_vpc_id]},
|
|
346
|
+
{'Name': 'group-name', 'Values': ['default']},
|
|
347
|
+
]
|
|
348
|
+
)['SecurityGroups']
|
|
349
|
+
|
|
350
|
+
if security_groups:
|
|
351
|
+
security_group_id = security_groups[0]['GroupId']
|
|
352
|
+
|
|
353
|
+
# Validate required parameters after auto-selection
|
|
354
|
+
if not subnet_id:
|
|
355
|
+
raise ValueError(
|
|
356
|
+
'subnet_id is required. Either provide a subnet_id or ensure the cache cluster is in the default VPC with default subnets available.'
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if not security_group_id:
|
|
360
|
+
raise ValueError(
|
|
361
|
+
'security_group_id is required. Either provide a security_group_id or ensure the cache cluster is in the default VPC.'
|
|
362
|
+
)
|
|
363
|
+
|
|
309
364
|
# Get subnet details and verify it's public
|
|
310
365
|
subnet_response = ec2_client.describe_subnets(SubnetIds=[subnet_id])
|
|
311
366
|
subnet = subnet_response['Subnets'][0]
|
|
@@ -318,6 +373,7 @@ async def create_jump_host_cc(
|
|
|
318
373
|
)
|
|
319
374
|
|
|
320
375
|
# Check if subnet is public by looking for route to internet gateway
|
|
376
|
+
# or if it's a default subnet in the default VPC (which are automatically public)
|
|
321
377
|
route_tables = ec2_client.describe_route_tables(
|
|
322
378
|
Filters=[{'Name': 'association.subnet-id', 'Values': [subnet_id]}]
|
|
323
379
|
)['RouteTables']
|
|
@@ -331,9 +387,38 @@ async def create_jump_host_cc(
|
|
|
331
387
|
if is_public:
|
|
332
388
|
break
|
|
333
389
|
|
|
390
|
+
# If no explicit route table association, check the main route table for the VPC
|
|
391
|
+
if not is_public and not route_tables:
|
|
392
|
+
main_route_tables = ec2_client.describe_route_tables(
|
|
393
|
+
Filters=[
|
|
394
|
+
{'Name': 'vpc-id', 'Values': [subnet_vpc_id]},
|
|
395
|
+
{'Name': 'association.main', 'Values': ['true']},
|
|
396
|
+
]
|
|
397
|
+
)['RouteTables']
|
|
398
|
+
|
|
399
|
+
for rt in main_route_tables:
|
|
400
|
+
for route in rt.get('Routes', []):
|
|
401
|
+
if route.get('GatewayId', '').startswith('igw-'):
|
|
402
|
+
is_public = True
|
|
403
|
+
break
|
|
404
|
+
if is_public:
|
|
405
|
+
break
|
|
406
|
+
|
|
407
|
+
# If not found via route table, check if it's a default subnet in default VPC
|
|
408
|
+
if not is_public:
|
|
409
|
+
# Check if this is the default VPC
|
|
410
|
+
vpcs = ec2_client.describe_vpcs(VpcIds=[subnet_vpc_id])['Vpcs']
|
|
411
|
+
vpc = vpcs[0] if vpcs else None
|
|
412
|
+
|
|
413
|
+
if vpc and vpc.get('IsDefault', False):
|
|
414
|
+
# In default VPC, check if this is a default subnet
|
|
415
|
+
# Default subnets have MapPublicIpOnLaunch set to True
|
|
416
|
+
if subnet.get('DefaultForAz', False) or subnet.get('MapPublicIpOnLaunch', False):
|
|
417
|
+
is_public = True
|
|
418
|
+
|
|
334
419
|
if not is_public:
|
|
335
420
|
raise ValueError(
|
|
336
|
-
f'Subnet {subnet_id} is not public (no route to internet gateway found). '
|
|
421
|
+
f'Subnet {subnet_id} is not public (no route to internet gateway found and not a default subnet in default VPC). '
|
|
337
422
|
'The subnet must be public to allow SSH access to the jump host.'
|
|
338
423
|
)
|
|
339
424
|
|
|
@@ -65,7 +65,17 @@ async def get_metric_statistics(
|
|
|
65
65
|
|
|
66
66
|
# Add optional parameters
|
|
67
67
|
if dimensions:
|
|
68
|
-
|
|
68
|
+
# Ensure dimensions are properly formatted as [{'Name': name, 'Value': value}, ...]
|
|
69
|
+
formatted_dimensions = []
|
|
70
|
+
for d in dimensions:
|
|
71
|
+
# Check if the dimension is already in the correct format
|
|
72
|
+
if 'Name' in d and 'Value' in d:
|
|
73
|
+
formatted_dimensions.append(d)
|
|
74
|
+
else:
|
|
75
|
+
# Convert from {key: value} format to {'Name': key, 'Value': value}
|
|
76
|
+
for k, v in d.items():
|
|
77
|
+
formatted_dimensions.append({'Name': k, 'Value': v})
|
|
78
|
+
params['Dimensions'] = formatted_dimensions
|
|
69
79
|
if statistics:
|
|
70
80
|
params['Statistics'] = statistics
|
|
71
81
|
if extended_statistics:
|
|
@@ -19,7 +19,7 @@ from ...common.decorators import handle_exceptions
|
|
|
19
19
|
from ...common.server import mcp
|
|
20
20
|
from ...context import Context
|
|
21
21
|
from botocore.exceptions import ClientError
|
|
22
|
-
from typing import Any, Dict,
|
|
22
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
async def _configure_security_groups(
|
|
@@ -52,28 +52,21 @@ async def _configure_security_groups(
|
|
|
52
52
|
ReplicationGroupId=replication_group_id
|
|
53
53
|
)['ReplicationGroups'][0]
|
|
54
54
|
|
|
55
|
-
# Get
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
cluster = elasticache_client.describe_cache_clusters(
|
|
59
|
-
CacheClusterId=member, ShowCacheNodeInfo=True
|
|
60
|
-
)['CacheClusters'][0]
|
|
61
|
-
if cluster['CacheClusterRole'].lower() == 'primary':
|
|
62
|
-
primary_cluster_id = member
|
|
63
|
-
break
|
|
55
|
+
# Get first cluster details (MemberClusters doesn't have notion of primary cluster)
|
|
56
|
+
if not replication_group['MemberClusters']:
|
|
57
|
+
raise ValueError(f'No clusters found in replication group {replication_group_id}')
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
raise ValueError(f'No primary cluster found in replication group {replication_group_id}')
|
|
59
|
+
first_cluster_id = replication_group['MemberClusters'][0]
|
|
67
60
|
|
|
68
|
-
# Get cache cluster VPC ID from
|
|
69
|
-
|
|
70
|
-
CacheClusterId=
|
|
61
|
+
# Get cache cluster VPC ID from first cluster
|
|
62
|
+
first_cluster = elasticache_client.describe_cache_clusters(
|
|
63
|
+
CacheClusterId=first_cluster_id, ShowCacheNodeInfo=True
|
|
71
64
|
)['CacheClusters'][0]
|
|
72
65
|
|
|
73
|
-
# Get subnet group name from cluster
|
|
74
|
-
subnet_group_name =
|
|
66
|
+
# Get subnet group name from first cluster
|
|
67
|
+
subnet_group_name = first_cluster.get('CacheSubnetGroupName')
|
|
75
68
|
if not subnet_group_name:
|
|
76
|
-
raise ValueError(f'No cache subnet group found for cluster {
|
|
69
|
+
raise ValueError(f'No cache subnet group found for cluster {first_cluster_id}')
|
|
77
70
|
|
|
78
71
|
# Get VPC ID from subnet group
|
|
79
72
|
try:
|
|
@@ -98,8 +91,8 @@ async def _configure_security_groups(
|
|
|
98
91
|
f'EC2 instance VPC ({instance_vpc_id}) does not match replication group VPC ({cache_vpc_id})'
|
|
99
92
|
)
|
|
100
93
|
|
|
101
|
-
# Get cache cluster port from
|
|
102
|
-
cache_port =
|
|
94
|
+
# Get cache cluster port from first node
|
|
95
|
+
cache_port = first_cluster['CacheNodes'][0]['Endpoint']['Port']
|
|
103
96
|
|
|
104
97
|
# Get cache cluster security groups from all member clusters
|
|
105
98
|
cache_security_groups = set()
|
|
@@ -209,15 +202,15 @@ async def connect_jump_host_rg(replication_group_id: str, instance_id: str) -> D
|
|
|
209
202
|
@handle_exceptions
|
|
210
203
|
async def get_ssh_tunnel_command_rg(
|
|
211
204
|
replication_group_id: str, instance_id: str
|
|
212
|
-
) -> Dict[str, Union[str, int
|
|
213
|
-
"""Generates SSH tunnel
|
|
205
|
+
) -> Dict[str, Union[str, int]]:
|
|
206
|
+
"""Generates an SSH tunnel command to connect to an ElastiCache replication group through an EC2 jump host.
|
|
214
207
|
|
|
215
208
|
Args:
|
|
216
209
|
replication_group_id (str): ID of the ElastiCache replication group to connect to
|
|
217
210
|
instance_id (str): ID of the EC2 instance to use as jump host
|
|
218
211
|
|
|
219
212
|
Returns:
|
|
220
|
-
Dict[str, Union[str, int
|
|
213
|
+
Dict[str, Union[str, int]]: Dictionary containing the SSH tunnel command and related details
|
|
221
214
|
|
|
222
215
|
Raises:
|
|
223
216
|
ValueError: If required resources not found or information cannot be retrieved
|
|
@@ -256,52 +249,28 @@ async def get_ssh_tunnel_command_rg(
|
|
|
256
249
|
ReplicationGroupId=replication_group_id
|
|
257
250
|
)['ReplicationGroups'][0]
|
|
258
251
|
|
|
259
|
-
#
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
for member in replication_group['MemberClusters']:
|
|
264
|
-
cluster = elasticache_client.describe_cache_clusters(
|
|
265
|
-
CacheClusterId=member, ShowCacheNodeInfo=True
|
|
266
|
-
)['CacheClusters'][0]
|
|
267
|
-
|
|
268
|
-
if not cluster.get('CacheNodes'):
|
|
269
|
-
continue
|
|
270
|
-
|
|
271
|
-
node = cluster['CacheNodes'][0]
|
|
272
|
-
endpoint = node['Endpoint']['Address']
|
|
273
|
-
port = node['Endpoint']['Port']
|
|
274
|
-
|
|
275
|
-
if base_port is None:
|
|
276
|
-
base_port = port
|
|
277
|
-
|
|
278
|
-
# For replicas, use different local ports to avoid conflicts
|
|
279
|
-
local_port = port
|
|
280
|
-
if cluster['CacheClusterRole'].lower() != 'primary':
|
|
281
|
-
local_port = port + 1000 # Use a different port range for replicas
|
|
282
|
-
|
|
283
|
-
ssh_command = (
|
|
284
|
-
f'ssh -i "{key_name}.pem" -fN -l {user} '
|
|
285
|
-
f'-L {local_port}:{endpoint}:{port} {public_dns} -v'
|
|
252
|
+
# Use the ConfigurationEndpoint for the SSH tunnel
|
|
253
|
+
if 'ConfigurationEndpoint' not in replication_group:
|
|
254
|
+
raise ValueError(
|
|
255
|
+
f'No ConfigurationEndpoint found for replication group {replication_group_id}'
|
|
286
256
|
)
|
|
287
257
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
'remotePort': port,
|
|
296
|
-
}
|
|
297
|
-
)
|
|
258
|
+
endpoint = replication_group['ConfigurationEndpoint']['Address']
|
|
259
|
+
port = replication_group['ConfigurationEndpoint']['Port']
|
|
260
|
+
|
|
261
|
+
# Generate a single SSH tunnel command
|
|
262
|
+
ssh_command = (
|
|
263
|
+
f'ssh -i "{key_name}.pem" -fN -l {user} -L {port}:{endpoint}:{port} {public_dns} -v'
|
|
264
|
+
)
|
|
298
265
|
|
|
299
266
|
return {
|
|
267
|
+
'command': ssh_command,
|
|
300
268
|
'keyName': key_name,
|
|
301
269
|
'user': user,
|
|
302
270
|
'jumpHostDns': public_dns,
|
|
303
|
-
'
|
|
304
|
-
'
|
|
271
|
+
'localPort': port,
|
|
272
|
+
'remoteEndpoint': endpoint,
|
|
273
|
+
'remotePort': port,
|
|
305
274
|
}
|
|
306
275
|
|
|
307
276
|
except Exception as e:
|
|
@@ -312,18 +281,20 @@ async def get_ssh_tunnel_command_rg(
|
|
|
312
281
|
@handle_exceptions
|
|
313
282
|
async def create_jump_host_rg(
|
|
314
283
|
replication_group_id: str,
|
|
315
|
-
subnet_id: str,
|
|
316
|
-
security_group_id: str,
|
|
317
284
|
key_name: str,
|
|
285
|
+
subnet_id: Optional[str] = None,
|
|
286
|
+
security_group_id: Optional[str] = None,
|
|
318
287
|
instance_type: str = 't3.small',
|
|
319
288
|
) -> Dict[str, Any]:
|
|
320
289
|
"""Creates an EC2 jump host instance to access an ElastiCache replication group via SSH tunnel.
|
|
321
290
|
|
|
322
291
|
Args:
|
|
323
292
|
replication_group_id (str): ID of the ElastiCache replication group to connect to
|
|
324
|
-
subnet_id (str): ID of the subnet to launch the EC2 instance in (must be public)
|
|
325
|
-
security_group_id (str): ID of the security group to assign to the EC2 instance
|
|
326
293
|
key_name (str): Name of the EC2 key pair to use for SSH access
|
|
294
|
+
subnet_id (str, optional): ID of the subnet to launch the EC2 instance in (must be public).
|
|
295
|
+
If not provided and replication group uses default VPC, will auto-select a default subnet.
|
|
296
|
+
security_group_id (str, optional): ID of the security group to assign to the EC2 instance.
|
|
297
|
+
If not provided and replication group uses default VPC, will use the default security group.
|
|
327
298
|
instance_type (str, optional): EC2 instance type. Defaults to "t3.small"
|
|
328
299
|
|
|
329
300
|
Returns:
|
|
@@ -361,31 +332,75 @@ async def create_jump_host_rg(
|
|
|
361
332
|
ReplicationGroupId=replication_group_id
|
|
362
333
|
)['ReplicationGroups'][0]
|
|
363
334
|
|
|
364
|
-
# Get
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
cluster = elasticache_client.describe_cache_clusters(
|
|
368
|
-
CacheClusterId=member, ShowCacheNodeInfo=True
|
|
369
|
-
)['CacheClusters'][0]
|
|
370
|
-
if cluster['CacheClusterRole'].lower() == 'primary':
|
|
371
|
-
primary_cluster_id = member
|
|
372
|
-
break
|
|
335
|
+
# Get first cluster details (MemberClusters doesn't have notion of primary cluster)
|
|
336
|
+
if not replication_group['MemberClusters']:
|
|
337
|
+
raise ValueError(f'No clusters found in replication group {replication_group_id}')
|
|
373
338
|
|
|
374
|
-
|
|
375
|
-
raise ValueError(
|
|
376
|
-
f'No primary cluster found in replication group {replication_group_id}'
|
|
377
|
-
)
|
|
339
|
+
first_cluster_id = replication_group['MemberClusters'][0]
|
|
378
340
|
|
|
379
|
-
# Get VPC details from
|
|
380
|
-
|
|
381
|
-
CacheClusterId=
|
|
341
|
+
# Get VPC details from first cluster
|
|
342
|
+
first_cluster = elasticache_client.describe_cache_clusters(
|
|
343
|
+
CacheClusterId=first_cluster_id, ShowCacheNodeInfo=True
|
|
382
344
|
)['CacheClusters'][0]
|
|
383
345
|
|
|
384
346
|
cache_subnet_group = elasticache_client.describe_cache_subnet_groups(
|
|
385
|
-
CacheSubnetGroupName=
|
|
347
|
+
CacheSubnetGroupName=first_cluster['CacheSubnetGroupName']
|
|
386
348
|
)['CacheSubnetGroups'][0]
|
|
387
349
|
cache_vpc_id = cache_subnet_group['VpcId']
|
|
388
350
|
|
|
351
|
+
# Check if replication group is in default VPC
|
|
352
|
+
vpcs = ec2_client.describe_vpcs(VpcIds=[cache_vpc_id])['Vpcs']
|
|
353
|
+
cache_vpc = vpcs[0] if vpcs else None
|
|
354
|
+
is_default_vpc = cache_vpc and cache_vpc.get('IsDefault', False)
|
|
355
|
+
|
|
356
|
+
# Auto-select subnet if not provided and replication group is in default VPC
|
|
357
|
+
if not subnet_id and is_default_vpc:
|
|
358
|
+
# Get default subnets in the default VPC
|
|
359
|
+
subnets = ec2_client.describe_subnets(
|
|
360
|
+
Filters=[
|
|
361
|
+
{'Name': 'vpc-id', 'Values': [cache_vpc_id]},
|
|
362
|
+
{'Name': 'default-for-az', 'Values': ['true']},
|
|
363
|
+
]
|
|
364
|
+
)['Subnets']
|
|
365
|
+
|
|
366
|
+
if subnets:
|
|
367
|
+
# Pick the first available default subnet
|
|
368
|
+
subnet_id = subnets[0]['SubnetId']
|
|
369
|
+
else:
|
|
370
|
+
# Fallback to any public subnet in the VPC
|
|
371
|
+
all_subnets = ec2_client.describe_subnets(
|
|
372
|
+
Filters=[{'Name': 'vpc-id', 'Values': [cache_vpc_id]}]
|
|
373
|
+
)['Subnets']
|
|
374
|
+
|
|
375
|
+
for subnet in all_subnets:
|
|
376
|
+
if subnet.get('MapPublicIpOnLaunch', False):
|
|
377
|
+
subnet_id = subnet['SubnetId']
|
|
378
|
+
break
|
|
379
|
+
|
|
380
|
+
# Auto-select security group if not provided and replication group is in default VPC
|
|
381
|
+
if not security_group_id and is_default_vpc:
|
|
382
|
+
# Get the default security group for the VPC
|
|
383
|
+
security_groups = ec2_client.describe_security_groups(
|
|
384
|
+
Filters=[
|
|
385
|
+
{'Name': 'vpc-id', 'Values': [cache_vpc_id]},
|
|
386
|
+
{'Name': 'group-name', 'Values': ['default']},
|
|
387
|
+
]
|
|
388
|
+
)['SecurityGroups']
|
|
389
|
+
|
|
390
|
+
if security_groups:
|
|
391
|
+
security_group_id = security_groups[0]['GroupId']
|
|
392
|
+
|
|
393
|
+
# Validate required parameters after auto-selection
|
|
394
|
+
if not subnet_id:
|
|
395
|
+
raise ValueError(
|
|
396
|
+
'subnet_id is required. Either provide a subnet_id or ensure the replication group is in the default VPC with default subnets available.'
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
if not security_group_id:
|
|
400
|
+
raise ValueError(
|
|
401
|
+
'security_group_id is required. Either provide a security_group_id or ensure the replication group is in the default VPC.'
|
|
402
|
+
)
|
|
403
|
+
|
|
389
404
|
# Get subnet details and verify it's public
|
|
390
405
|
subnet_response = ec2_client.describe_subnets(SubnetIds=[subnet_id])
|
|
391
406
|
subnet = subnet_response['Subnets'][0]
|
|
@@ -398,20 +413,11 @@ async def create_jump_host_rg(
|
|
|
398
413
|
)
|
|
399
414
|
|
|
400
415
|
# Check if subnet is public by looking for route to internet gateway
|
|
416
|
+
# or if it's a default subnet in the default VPC (which are automatically public)
|
|
401
417
|
route_tables = ec2_client.describe_route_tables(
|
|
402
418
|
Filters=[{'Name': 'association.subnet-id', 'Values': [subnet_id]}]
|
|
403
419
|
)['RouteTables']
|
|
404
420
|
|
|
405
|
-
# If no explicit route table association, check main route table
|
|
406
|
-
if not route_tables:
|
|
407
|
-
route_tables = ec2_client.describe_route_tables(
|
|
408
|
-
Filters=[
|
|
409
|
-
{'Name': 'vpc-id', 'Values': [subnet_vpc_id]},
|
|
410
|
-
{'Name': 'association.main', 'Values': ['true']},
|
|
411
|
-
]
|
|
412
|
-
)['RouteTables']
|
|
413
|
-
|
|
414
|
-
# Check for route to internet gateway
|
|
415
421
|
is_public = False
|
|
416
422
|
for rt in route_tables:
|
|
417
423
|
for route in rt.get('Routes', []):
|
|
@@ -421,10 +427,38 @@ async def create_jump_host_rg(
|
|
|
421
427
|
if is_public:
|
|
422
428
|
break
|
|
423
429
|
|
|
424
|
-
#
|
|
430
|
+
# If no explicit route table association, check the main route table for the VPC
|
|
431
|
+
if not is_public and not route_tables:
|
|
432
|
+
main_route_tables = ec2_client.describe_route_tables(
|
|
433
|
+
Filters=[
|
|
434
|
+
{'Name': 'vpc-id', 'Values': [subnet_vpc_id]},
|
|
435
|
+
{'Name': 'association.main', 'Values': ['true']},
|
|
436
|
+
]
|
|
437
|
+
)['RouteTables']
|
|
438
|
+
|
|
439
|
+
for rt in main_route_tables:
|
|
440
|
+
for route in rt.get('Routes', []):
|
|
441
|
+
if route.get('GatewayId', '').startswith('igw-'):
|
|
442
|
+
is_public = True
|
|
443
|
+
break
|
|
444
|
+
if is_public:
|
|
445
|
+
break
|
|
446
|
+
|
|
447
|
+
# If not found via route table, check if it's a default subnet in default VPC
|
|
448
|
+
if not is_public:
|
|
449
|
+
# Check if this is the default VPC
|
|
450
|
+
vpcs = ec2_client.describe_vpcs(VpcIds=[subnet_vpc_id])['Vpcs']
|
|
451
|
+
vpc = vpcs[0] if vpcs else None
|
|
452
|
+
|
|
453
|
+
if vpc and vpc.get('IsDefault', False):
|
|
454
|
+
# In default VPC, check if this is a default subnet
|
|
455
|
+
# Default subnets have MapPublicIpOnLaunch set to True
|
|
456
|
+
if subnet.get('DefaultForAz', False) or subnet.get('MapPublicIpOnLaunch', False):
|
|
457
|
+
is_public = True
|
|
458
|
+
|
|
425
459
|
if not is_public:
|
|
426
460
|
raise ValueError(
|
|
427
|
-
f'Subnet {subnet_id} is not public (no route to internet gateway found). '
|
|
461
|
+
f'Subnet {subnet_id} is not public (no route to internet gateway found and not a default subnet in default VPC). '
|
|
428
462
|
'The subnet must be public to allow SSH access to the jump host.'
|
|
429
463
|
)
|
|
430
464
|
|
|
@@ -19,7 +19,7 @@ from ...common.decorators import handle_exceptions
|
|
|
19
19
|
from ...common.server import mcp
|
|
20
20
|
from ...context import Context
|
|
21
21
|
from botocore.exceptions import ClientError
|
|
22
|
-
from typing import Any, Dict, Tuple, Union
|
|
22
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
async def _configure_security_groups(
|
|
@@ -53,15 +53,28 @@ async def _configure_security_groups(
|
|
|
53
53
|
)['ServerlessCaches'][0]
|
|
54
54
|
|
|
55
55
|
# Get cache security groups
|
|
56
|
-
cache_security_groups = serverless_cache['
|
|
56
|
+
cache_security_groups = serverless_cache['SecurityGroupIds']
|
|
57
57
|
if not cache_security_groups:
|
|
58
58
|
raise ValueError(f'No security groups found for serverless cache {serverless_cache_name}')
|
|
59
59
|
|
|
60
|
-
# Get cache VPC ID
|
|
61
|
-
|
|
60
|
+
# Get cache VPC ID from subnet IDs
|
|
61
|
+
if not serverless_cache.get('SubnetIds'):
|
|
62
|
+
raise ValueError(f'No subnet IDs found for serverless cache {serverless_cache_name}')
|
|
62
63
|
|
|
63
|
-
# Get
|
|
64
|
-
|
|
64
|
+
# Get subnet details to find VPC ID
|
|
65
|
+
subnet_response = ec2_client.describe_subnets(SubnetIds=[serverless_cache['SubnetIds'][0]])
|
|
66
|
+
cache_vpc_id = subnet_response['Subnets'][0]['VpcId']
|
|
67
|
+
|
|
68
|
+
# Get cache port dynamically from endpoint if available
|
|
69
|
+
# Set default port based on engine type
|
|
70
|
+
engine = serverless_cache.get('Engine', '').lower()
|
|
71
|
+
if engine == 'memcached':
|
|
72
|
+
cache_port = 11211 # Default port for Memcached
|
|
73
|
+
else:
|
|
74
|
+
cache_port = 6379 # Default port for Redis/Valkey
|
|
75
|
+
|
|
76
|
+
if serverless_cache.get('Endpoint') and serverless_cache['Endpoint'].get('Port'):
|
|
77
|
+
cache_port = serverless_cache['Endpoint']['Port']
|
|
65
78
|
|
|
66
79
|
# Get EC2 instance details
|
|
67
80
|
instance_info = ec2_client.describe_instances(InstanceIds=[instance_id])
|
|
@@ -83,8 +96,7 @@ async def _configure_security_groups(
|
|
|
83
96
|
raise ValueError(f'No security groups found for EC2 instance {instance_id}')
|
|
84
97
|
|
|
85
98
|
# For each cache security group, ensure it allows inbound access from EC2 security groups
|
|
86
|
-
for
|
|
87
|
-
cache_sg_id = cache_sg['SecurityGroupId']
|
|
99
|
+
for cache_sg_id in cache_security_groups:
|
|
88
100
|
cache_sg_info = ec2_client.describe_security_groups(GroupIds=[cache_sg_id])[
|
|
89
101
|
'SecurityGroups'
|
|
90
102
|
][0]
|
|
@@ -223,9 +235,19 @@ async def get_ssh_tunnel_command_serverless(
|
|
|
223
235
|
ServerlessCacheName=serverless_cache_name
|
|
224
236
|
)['ServerlessCaches'][0]
|
|
225
237
|
|
|
226
|
-
# Get cache endpoint and port
|
|
238
|
+
# Get cache endpoint and port
|
|
227
239
|
cache_endpoint = serverless_cache['Endpoint']['Address']
|
|
228
|
-
|
|
240
|
+
|
|
241
|
+
# Get cache port dynamically from endpoint if available
|
|
242
|
+
# Set default port based on engine type
|
|
243
|
+
engine = serverless_cache.get('Engine', '').lower()
|
|
244
|
+
if engine == 'memcached':
|
|
245
|
+
cache_port = 11211 # Default port for Memcached
|
|
246
|
+
else:
|
|
247
|
+
cache_port = 6379 # Default port for Redis/Valkey
|
|
248
|
+
|
|
249
|
+
if serverless_cache.get('Endpoint') and serverless_cache['Endpoint'].get('Port'):
|
|
250
|
+
cache_port = serverless_cache['Endpoint']['Port']
|
|
229
251
|
|
|
230
252
|
# Generate SSH tunnel command
|
|
231
253
|
ssh_command = (
|
|
@@ -251,18 +273,20 @@ async def get_ssh_tunnel_command_serverless(
|
|
|
251
273
|
@handle_exceptions
|
|
252
274
|
async def create_jump_host_serverless(
|
|
253
275
|
serverless_cache_name: str,
|
|
254
|
-
subnet_id: str,
|
|
255
|
-
security_group_id: str,
|
|
256
276
|
key_name: str,
|
|
277
|
+
subnet_id: Optional[str] = None,
|
|
278
|
+
security_group_id: Optional[str] = None,
|
|
257
279
|
instance_type: str = 't3.small',
|
|
258
280
|
) -> Dict[str, Any]:
|
|
259
281
|
"""Creates an EC2 jump host instance to access an ElastiCache serverless cache via SSH tunnel.
|
|
260
282
|
|
|
261
283
|
Args:
|
|
262
284
|
serverless_cache_name (str): Name of the ElastiCache serverless cache to connect to
|
|
263
|
-
subnet_id (str): ID of the subnet to launch the EC2 instance in (must be public)
|
|
264
|
-
security_group_id (str): ID of the security group to assign to the EC2 instance
|
|
265
285
|
key_name (str): Name of the EC2 key pair to use for SSH access
|
|
286
|
+
subnet_id (str, optional): ID of the subnet to launch the EC2 instance in (must be public).
|
|
287
|
+
If not provided and serverless cache uses default VPC, will auto-select a default subnet.
|
|
288
|
+
security_group_id (str, optional): ID of the security group to assign to the EC2 instance.
|
|
289
|
+
If not provided and serverless cache uses default VPC, will use the default security group.
|
|
266
290
|
instance_type (str, optional): EC2 instance type. Defaults to "t3.small"
|
|
267
291
|
|
|
268
292
|
Returns:
|
|
@@ -301,14 +325,72 @@ async def create_jump_host_serverless(
|
|
|
301
325
|
)['ServerlessCaches'][0]
|
|
302
326
|
|
|
303
327
|
# Get cache security groups
|
|
304
|
-
cache_security_groups = serverless_cache['
|
|
328
|
+
cache_security_groups = serverless_cache['SecurityGroupIds']
|
|
305
329
|
if not cache_security_groups:
|
|
306
330
|
raise ValueError(
|
|
307
331
|
f'No security groups found for serverless cache {serverless_cache_name}'
|
|
308
332
|
)
|
|
309
333
|
|
|
310
|
-
# Get cache VPC ID
|
|
311
|
-
|
|
334
|
+
# Get cache VPC ID from subnet IDs
|
|
335
|
+
if not serverless_cache.get('SubnetIds'):
|
|
336
|
+
raise ValueError(f'No subnet IDs found for serverless cache {serverless_cache_name}')
|
|
337
|
+
|
|
338
|
+
# Get subnet details to find VPC ID
|
|
339
|
+
subnet_response = ec2_client.describe_subnets(SubnetIds=[serverless_cache['SubnetIds'][0]])
|
|
340
|
+
cache_vpc_id = subnet_response['Subnets'][0]['VpcId']
|
|
341
|
+
|
|
342
|
+
# Check if serverless cache is in default VPC
|
|
343
|
+
vpcs = ec2_client.describe_vpcs(VpcIds=[cache_vpc_id])['Vpcs']
|
|
344
|
+
cache_vpc = vpcs[0] if vpcs else None
|
|
345
|
+
is_default_vpc = cache_vpc and cache_vpc.get('IsDefault', False)
|
|
346
|
+
|
|
347
|
+
# Auto-select subnet if not provided and serverless cache is in default VPC
|
|
348
|
+
if not subnet_id and is_default_vpc:
|
|
349
|
+
# Get default subnets in the default VPC
|
|
350
|
+
subnets = ec2_client.describe_subnets(
|
|
351
|
+
Filters=[
|
|
352
|
+
{'Name': 'vpc-id', 'Values': [cache_vpc_id]},
|
|
353
|
+
{'Name': 'default-for-az', 'Values': ['true']},
|
|
354
|
+
]
|
|
355
|
+
)['Subnets']
|
|
356
|
+
|
|
357
|
+
if subnets:
|
|
358
|
+
# Pick the first available default subnet
|
|
359
|
+
subnet_id = subnets[0]['SubnetId']
|
|
360
|
+
else:
|
|
361
|
+
# Fallback to any public subnet in the VPC
|
|
362
|
+
all_subnets = ec2_client.describe_subnets(
|
|
363
|
+
Filters=[{'Name': 'vpc-id', 'Values': [cache_vpc_id]}]
|
|
364
|
+
)['Subnets']
|
|
365
|
+
|
|
366
|
+
for subnet in all_subnets:
|
|
367
|
+
if subnet.get('MapPublicIpOnLaunch', False):
|
|
368
|
+
subnet_id = subnet['SubnetId']
|
|
369
|
+
break
|
|
370
|
+
|
|
371
|
+
# Auto-select security group if not provided and serverless cache is in default VPC
|
|
372
|
+
if not security_group_id and is_default_vpc:
|
|
373
|
+
# Get the default security group for the VPC
|
|
374
|
+
security_groups = ec2_client.describe_security_groups(
|
|
375
|
+
Filters=[
|
|
376
|
+
{'Name': 'vpc-id', 'Values': [cache_vpc_id]},
|
|
377
|
+
{'Name': 'group-name', 'Values': ['default']},
|
|
378
|
+
]
|
|
379
|
+
)['SecurityGroups']
|
|
380
|
+
|
|
381
|
+
if security_groups:
|
|
382
|
+
security_group_id = security_groups[0]['GroupId']
|
|
383
|
+
|
|
384
|
+
# Validate required parameters after auto-selection
|
|
385
|
+
if not subnet_id:
|
|
386
|
+
raise ValueError(
|
|
387
|
+
'subnet_id is required. Either provide a subnet_id or ensure the serverless cache is in the default VPC with default subnets available.'
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
if not security_group_id:
|
|
391
|
+
raise ValueError(
|
|
392
|
+
'security_group_id is required. Either provide a security_group_id or ensure the serverless cache is in the default VPC.'
|
|
393
|
+
)
|
|
312
394
|
|
|
313
395
|
# Get subnet details and verify it's public
|
|
314
396
|
subnet_response = ec2_client.describe_subnets(SubnetIds=[subnet_id])
|
|
@@ -322,6 +404,7 @@ async def create_jump_host_serverless(
|
|
|
322
404
|
)
|
|
323
405
|
|
|
324
406
|
# Check if subnet is public by looking for route to internet gateway
|
|
407
|
+
# or if it's a default subnet in the default VPC (which are automatically public)
|
|
325
408
|
route_tables = ec2_client.describe_route_tables(
|
|
326
409
|
Filters=[{'Name': 'association.subnet-id', 'Values': [subnet_id]}]
|
|
327
410
|
)['RouteTables']
|
|
@@ -335,10 +418,38 @@ async def create_jump_host_serverless(
|
|
|
335
418
|
if is_public:
|
|
336
419
|
break
|
|
337
420
|
|
|
338
|
-
#
|
|
421
|
+
# If no explicit route table association, check the main route table for the VPC
|
|
422
|
+
if not is_public and not route_tables:
|
|
423
|
+
main_route_tables = ec2_client.describe_route_tables(
|
|
424
|
+
Filters=[
|
|
425
|
+
{'Name': 'vpc-id', 'Values': [subnet_vpc_id]},
|
|
426
|
+
{'Name': 'association.main', 'Values': ['true']},
|
|
427
|
+
]
|
|
428
|
+
)['RouteTables']
|
|
429
|
+
|
|
430
|
+
for rt in main_route_tables:
|
|
431
|
+
for route in rt.get('Routes', []):
|
|
432
|
+
if route.get('GatewayId', '').startswith('igw-'):
|
|
433
|
+
is_public = True
|
|
434
|
+
break
|
|
435
|
+
if is_public:
|
|
436
|
+
break
|
|
437
|
+
|
|
438
|
+
# If not found via route table, check if it's a default subnet in default VPC
|
|
439
|
+
if not is_public:
|
|
440
|
+
# Check if this is the default VPC
|
|
441
|
+
vpcs = ec2_client.describe_vpcs(VpcIds=[subnet_vpc_id])['Vpcs']
|
|
442
|
+
vpc = vpcs[0] if vpcs else None
|
|
443
|
+
|
|
444
|
+
if vpc and vpc.get('IsDefault', False):
|
|
445
|
+
# In default VPC, check if this is a default subnet
|
|
446
|
+
# Default subnets have MapPublicIpOnLaunch set to True
|
|
447
|
+
if subnet.get('DefaultForAz', False) or subnet.get('MapPublicIpOnLaunch', False):
|
|
448
|
+
is_public = True
|
|
449
|
+
|
|
339
450
|
if not is_public:
|
|
340
451
|
raise ValueError(
|
|
341
|
-
f'Subnet {subnet_id} is not public (no route to internet gateway found). '
|
|
452
|
+
f'Subnet {subnet_id} is not public (no route to internet gateway found and not a default subnet in default VPC). '
|
|
342
453
|
'The subnet must be public to allow SSH access to the jump host.'
|
|
343
454
|
)
|
|
344
455
|
|
|
@@ -22,7 +22,7 @@ from .models import CreateServerlessCacheRequest
|
|
|
22
22
|
from typing import Dict
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
@mcp.tool(name='
|
|
25
|
+
@mcp.tool(name='create-serverless-cache')
|
|
26
26
|
@handle_exceptions
|
|
27
27
|
async def create_serverless_cache(request: CreateServerlessCacheRequest) -> Dict:
|
|
28
28
|
"""Create a new Amazon ElastiCache serverless cache.
|
|
@@ -54,7 +54,7 @@ async def create_serverless_cache(request: CreateServerlessCacheRequest) -> Dict
|
|
|
54
54
|
3. JSON array: [{"Key": "string", "Value": "string"}, {"Key": "string2", "Value": null}]
|
|
55
55
|
|
|
56
56
|
Can be None if no tags are needed.
|
|
57
|
-
|
|
57
|
+
security_group_ids (Optional[List[str]]): List of security group IDs.
|
|
58
58
|
cache_usage_limits (Optional[CacheUsageLimits]): Usage limits for the cache. Structure:
|
|
59
59
|
{
|
|
60
60
|
"DataStorage": {
|
|
@@ -121,7 +121,7 @@ async def create_serverless_cache(request: CreateServerlessCacheRequest) -> Dict
|
|
|
121
121
|
for param_name, value in [
|
|
122
122
|
('SnapshotArnsToRestore', request.snapshot_arns_to_restore),
|
|
123
123
|
('SubnetIds', request.subnet_ids),
|
|
124
|
-
('
|
|
124
|
+
('SecurityGroupIds', request.security_group_ids),
|
|
125
125
|
]:
|
|
126
126
|
if value:
|
|
127
127
|
create_request[param_name] = list(map(str, value))
|
|
@@ -89,9 +89,7 @@ class CreateServerlessCacheRequest(BaseModel):
|
|
|
89
89
|
'a list of Tag objects, or a dict of key-value pairs'
|
|
90
90
|
),
|
|
91
91
|
)
|
|
92
|
-
|
|
93
|
-
None, description='List of VPC security group IDs'
|
|
94
|
-
)
|
|
92
|
+
security_group_ids: Optional[List[str]] = Field(None, description='List of security group IDs')
|
|
95
93
|
cache_usage_limits: Optional[CacheUsageLimits] = Field(
|
|
96
94
|
None, description='Usage limits for the cache'
|
|
97
95
|
)
|
|
@@ -143,9 +141,7 @@ class ModifyServerlessCacheRequest(BaseModel):
|
|
|
143
141
|
user_group_id: Optional[str] = Field(
|
|
144
142
|
None, description='ID of the user group to associate with the cache'
|
|
145
143
|
)
|
|
146
|
-
|
|
147
|
-
None, description='List of VPC security group IDs'
|
|
148
|
-
)
|
|
144
|
+
security_group_ids: Optional[List[str]] = Field(None, description='List of security group IDs')
|
|
149
145
|
|
|
150
146
|
@field_validator('daily_snapshot_time')
|
|
151
147
|
def validate_snapshot_time(cls, v):
|
|
@@ -21,7 +21,7 @@ from .models import ModifyServerlessCacheRequest
|
|
|
21
21
|
from typing import Dict
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
@mcp.tool(name='
|
|
24
|
+
@mcp.tool(name='modify-serverless-cache')
|
|
25
25
|
@handle_exceptions
|
|
26
26
|
async def modify_serverless_cache(request: ModifyServerlessCacheRequest) -> Dict:
|
|
27
27
|
"""Modify an Amazon ElastiCache serverless cache.
|
|
@@ -53,7 +53,7 @@ async def modify_serverless_cache(request: ModifyServerlessCacheRequest) -> Dict
|
|
|
53
53
|
"Minimum": int # Minimum ECPU per second
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
security_group_ids (Optional[List[str]]): List of
|
|
56
|
+
security_group_ids (Optional[List[str]]): List of security group IDs.
|
|
57
57
|
user_group_id (Optional[str]): ID of the user group to associate with the cache.
|
|
58
58
|
|
|
59
59
|
Returns:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awslabs.elasticache-mcp-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: An AWS Labs Model Context Protocol (MCP) server for Amazon ElastiCache
|
|
5
5
|
Project-URL: homepage, https://awslabs.github.io/mcp/
|
|
6
6
|
Project-URL: docs, https://awslabs.github.io/mcp/servers/elasticache-mcp-server/
|
|
@@ -38,10 +38,19 @@ Description-Content-Type: text/markdown
|
|
|
38
38
|
|
|
39
39
|
# AWS ElastiCache MCP Server
|
|
40
40
|
|
|
41
|
-
The official MCP Server for interacting with AWS ElastiCache
|
|
41
|
+
The official MCP Server for interacting with AWS ElastiCache control plane. In order to interact with your data in ElastiCache Serverless caches and self-designed clusters use the [Valkey MCP Server](https://github.com/awslabs/mcp/blob/main/src/valkey-mcp-server) or the [Memcached MCP Server](https://github.com/awslabs/mcp/blob/main/src/memcached-mcp-server).
|
|
42
42
|
|
|
43
43
|
## Available MCP Tools
|
|
44
44
|
|
|
45
|
+
### Serverless Cache Operations
|
|
46
|
+
- `create-serverless-cache` - Create a new ElastiCache serverless cache
|
|
47
|
+
- `delete-serverless-cache` - Delete a serverless cache
|
|
48
|
+
- `describe-serverless-caches` - Get information about serverless caches
|
|
49
|
+
- `modify-serverless-cache` - Modify settings of a serverless cache
|
|
50
|
+
- `connect-jump-host-serverless-cache` - Configure an EC2 instance as a jump host for serverless cache access
|
|
51
|
+
- `create-jump-host-serverless-cache` - Create an EC2 jump host to access a serverless cache via SSH tunnel
|
|
52
|
+
- `get-ssh-tunnel-command-serverless-cache` - Generate SSH tunnel command for serverless cache access
|
|
53
|
+
|
|
45
54
|
### Replication Group Operations
|
|
46
55
|
- `create-replication-group` - Create an Amazon ElastiCache replication group with specified configuration
|
|
47
56
|
- `delete-replication-group` - Delete an ElastiCache replication group with optional final snapshot
|
|
@@ -64,15 +73,6 @@ The official MCP Server for interacting with AWS ElastiCache
|
|
|
64
73
|
- `create-jump-host-cache-cluster` - Create an EC2 jump host to access a cluster via SSH tunnel
|
|
65
74
|
- `get-ssh-tunnel-command-cache-cluster` - Generate SSH tunnel command for cluster access
|
|
66
75
|
|
|
67
|
-
### Serverless Cache Operations
|
|
68
|
-
- `create-serverless-cache` - Create a new ElastiCache serverless cache
|
|
69
|
-
- `delete-serverless-cache` - Delete a serverless cache
|
|
70
|
-
- `describe-serverless-caches` - Get information about serverless caches
|
|
71
|
-
- `modify-serverless-cache` - Modify settings of a serverless cache
|
|
72
|
-
- `connect-jump-host-serverless-cache` - Configure an EC2 instance as a jump host for serverless cache access
|
|
73
|
-
- `create-jump-host-serverless-cache` - Create an EC2 jump host to access a serverless cache via SSH tunnel
|
|
74
|
-
- `get-ssh-tunnel-command-serverless-cache` - Generate SSH tunnel command for serverless cache access
|
|
75
|
-
|
|
76
76
|
### CloudWatch Operations
|
|
77
77
|
- `get-metric-statistics` - Get CloudWatch metric statistics for ElastiCache resources with customizable time periods and dimensions
|
|
78
78
|
|
|
@@ -114,7 +114,9 @@ All tools support an optional `region_name` parameter to specify which AWS regio
|
|
|
114
114
|
|
|
115
115
|
## Installation
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
[](https://cursor.com/install-mcp?name=awslabs.elasticache-mcp-server&config=eyJjb21tYW5kIjoidXZ4IGF3c2xhYnMuZWxhc3RpY2FjaGUtbWNwLXNlcnZlckBsYXRlc3QiLCJlbnYiOnsiQVdTX1BST0ZJTEUiOiJkZWZhdWx0IiwiQVdTX1JFR0lPTiI6InVzLXdlc3QtMiIsIkZBU1RNQ1BfTE9HX0xFVkVMIjoiRVJST1IifSwiZGlzYWJsZWQiOmZhbHNlLCJhdXRvQXBwcm92ZSI6W119)
|
|
118
|
+
|
|
119
|
+
Add the MCP to your favorite agentic tools. (e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
|
|
118
120
|
|
|
119
121
|
```json
|
|
120
122
|
{
|
|
@@ -8,7 +8,7 @@ awslabs/elasticache_mcp_server/common/decorators.py,sha256=jIQQH3N9Ko604eb65ZwKn
|
|
|
8
8
|
awslabs/elasticache_mcp_server/common/server.py,sha256=5W1gSo7_BWqbSiWRWZq2NkFy35rv8LhkPorjj0_7w9k,1131
|
|
9
9
|
awslabs/elasticache_mcp_server/tools/__init__.py,sha256=KSEbmf3L19fZWgATz6RIuYcnZaoGwjhAFAlu_eeb_78,664
|
|
10
10
|
awslabs/elasticache_mcp_server/tools/cc/__init__.py,sha256=5PAZpzh_4AU5EUf1xR8_Y3M4toKbhzOIaKJ57l8x-nA,1148
|
|
11
|
-
awslabs/elasticache_mcp_server/tools/cc/connect.py,sha256=
|
|
11
|
+
awslabs/elasticache_mcp_server/tools/cc/connect.py,sha256=POaioP5ExNkCNEETAVe7Hha8aTf5--RW1khdOeLm6es,21077
|
|
12
12
|
awslabs/elasticache_mcp_server/tools/cc/create.py,sha256=cZzqA1MyUvtrv11kqAzscNlqsuOeaUmqmbZlIdqyfW4,9943
|
|
13
13
|
awslabs/elasticache_mcp_server/tools/cc/delete.py,sha256=i6jecaSHJicrI8H3a4bFZtLJYIfeWWs6IkypCXKZLzg,2435
|
|
14
14
|
awslabs/elasticache_mcp_server/tools/cc/describe.py,sha256=3glRiRc5ebGeDKPlyMf7PTP1YnaOBMuGhwWkfscbJRA,3642
|
|
@@ -18,7 +18,7 @@ awslabs/elasticache_mcp_server/tools/cc/processors.py,sha256=mJ0MTtxRgMcpLD2KGuF
|
|
|
18
18
|
awslabs/elasticache_mcp_server/tools/ce/__init__.py,sha256=nY1ui4V0U_cHm4qLsyBeDCRGQnhE36PEVon-n30meV0,806
|
|
19
19
|
awslabs/elasticache_mcp_server/tools/ce/get_cost_and_usage.py,sha256=2mEXSRFIYfZ6PpEbhmyN2qMVrH-epJfY6NeYeT4JkDQ,2923
|
|
20
20
|
awslabs/elasticache_mcp_server/tools/cw/__init__.py,sha256=WMqauaWifQk1YoSmaV1SpZqOyb30RT7mWvZMshYC97U,735
|
|
21
|
-
awslabs/elasticache_mcp_server/tools/cw/get_metric_statistics.py,sha256=
|
|
21
|
+
awslabs/elasticache_mcp_server/tools/cw/get_metric_statistics.py,sha256=vyta4rQ_VcGysxcl4jI1gBbQ8zwNMblgoXhE8hP4DqM,3369
|
|
22
22
|
awslabs/elasticache_mcp_server/tools/cwlogs/__init__.py,sha256=5pcisxzfTCCbs6JWK7bmy1w9YD-oxHHTfIxVIKcCUFY,1034
|
|
23
23
|
awslabs/elasticache_mcp_server/tools/cwlogs/create_log_group.py,sha256=6LYK7K86vPyKBNy54WiPCiDKOxM38nWiEUtkMu-snWE,2407
|
|
24
24
|
awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_groups.py,sha256=FMfMsIhzdD9As86TC7h5nBRfQ3GEFZVMaHts-icLDPU,4652
|
|
@@ -36,7 +36,7 @@ awslabs/elasticache_mcp_server/tools/misc/describe_events.py,sha256=OFm8S0-Hnk-c
|
|
|
36
36
|
awslabs/elasticache_mcp_server/tools/misc/describe_service_updates.py,sha256=ppnPpSbChI7KOqk-M1PTQvoDRu4maIGmS7reNnJt9R4,2990
|
|
37
37
|
awslabs/elasticache_mcp_server/tools/rg/__init__.py,sha256=RyGA5z-CeuOjw8X3GeFwpW33WzzThoErmuJ0ddu-gC0,1983
|
|
38
38
|
awslabs/elasticache_mcp_server/tools/rg/complete_migration.py,sha256=yU1qSI5WRdKvrFp1rSPrtwd1fIrAJmfgoOodZixBzl8,3732
|
|
39
|
-
awslabs/elasticache_mcp_server/tools/rg/connect.py,sha256=
|
|
39
|
+
awslabs/elasticache_mcp_server/tools/rg/connect.py,sha256=f8pxLQxur8HW12GnsR1GyyzuVsog6A55bRx0kTfNIdw,23052
|
|
40
40
|
awslabs/elasticache_mcp_server/tools/rg/create.py,sha256=SUe8_vyvQ3emeCru1g9yp9ZxfHmk-ASNpu36DaBoSXU,13495
|
|
41
41
|
awslabs/elasticache_mcp_server/tools/rg/delete.py,sha256=PA-D8YK2ePs1aa6BRc7OYsrHNSbjGazwN9UP1fDnBDA,2747
|
|
42
42
|
awslabs/elasticache_mcp_server/tools/rg/describe.py,sha256=wt7tdYKe3VPkxgDj7MdcmNtXwxjRKprKxkwQPx9C1Fg,2984
|
|
@@ -46,15 +46,15 @@ awslabs/elasticache_mcp_server/tools/rg/processors.py,sha256=2DEsrF7ucCg7vKhXfoO
|
|
|
46
46
|
awslabs/elasticache_mcp_server/tools/rg/start_migration.py,sha256=bReeWPZbCoU7gKlmR8Nfy8VrweVThTrvp4KibK_fmp4,6042
|
|
47
47
|
awslabs/elasticache_mcp_server/tools/rg/test_migration.py,sha256=Ye05Lxf84tQShOOokm-agL9GDPFSZTrL31A1A7368EE,5597
|
|
48
48
|
awslabs/elasticache_mcp_server/tools/serverless/__init__.py,sha256=q_te85wY15COM7iFszg1SrY4ianS_BmQrvTEMYpxm3A,1306
|
|
49
|
-
awslabs/elasticache_mcp_server/tools/serverless/connect.py,sha256=
|
|
50
|
-
awslabs/elasticache_mcp_server/tools/serverless/create.py,sha256=
|
|
51
|
-
awslabs/elasticache_mcp_server/tools/serverless/delete.py,sha256=
|
|
49
|
+
awslabs/elasticache_mcp_server/tools/serverless/connect.py,sha256=T_d2gOe-1GGdVjRClvDXvCPlaTUR0VROzfBqmaMRYlU,22558
|
|
50
|
+
awslabs/elasticache_mcp_server/tools/serverless/create.py,sha256=0L4Tji3aSrPmsLYvib-s4D_fuEMaWk541ix9mti_rKE,7498
|
|
51
|
+
awslabs/elasticache_mcp_server/tools/serverless/delete.py,sha256=SByDCgcfNj23zL7ijO0ze_1QL0iTWBO4PVsBxvu1BGc,1858
|
|
52
52
|
awslabs/elasticache_mcp_server/tools/serverless/describe.py,sha256=GwHuHfi4vltAxOv7hLvvsKiq7GoGF7LUcrt0uCi4mAM,2567
|
|
53
|
-
awslabs/elasticache_mcp_server/tools/serverless/models.py,sha256=
|
|
54
|
-
awslabs/elasticache_mcp_server/tools/serverless/modify.py,sha256=
|
|
55
|
-
awslabs_elasticache_mcp_server-0.1.
|
|
56
|
-
awslabs_elasticache_mcp_server-0.1.
|
|
57
|
-
awslabs_elasticache_mcp_server-0.1.
|
|
58
|
-
awslabs_elasticache_mcp_server-0.1.
|
|
59
|
-
awslabs_elasticache_mcp_server-0.1.
|
|
60
|
-
awslabs_elasticache_mcp_server-0.1.
|
|
53
|
+
awslabs/elasticache_mcp_server/tools/serverless/models.py,sha256=0QTnp8ygVJ4j49JznT4IRlFzdyKYiMXopoJNifO93ok,6634
|
|
54
|
+
awslabs/elasticache_mcp_server/tools/serverless/modify.py,sha256=zBYYhwWF6p8c4WGCZl5tpocXR00pNAHpdqJAF8KRJyI,3868
|
|
55
|
+
awslabs_elasticache_mcp_server-0.1.3.dist-info/METADATA,sha256=z2Jfebuhogj_Dab_a8ZciBeFqrjNsFdMG-UKEcW7TIM,11510
|
|
56
|
+
awslabs_elasticache_mcp_server-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
57
|
+
awslabs_elasticache_mcp_server-0.1.3.dist-info/entry_points.txt,sha256=058QYfdxCKFALC9V2L5AQz36-jBT8fV8HVLOuIVngAA,92
|
|
58
|
+
awslabs_elasticache_mcp_server-0.1.3.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
|
|
59
|
+
awslabs_elasticache_mcp_server-0.1.3.dist-info/licenses/NOTICE,sha256=89EDSSt20vYGgAdEzUiW5Y_VMpZ5CDd2RvXs8BDdhKk,98
|
|
60
|
+
awslabs_elasticache_mcp_server-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|