awslabs.elasticache-mcp-server 0.1.1__tar.gz → 0.1.3__tar.gz

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 (122) hide show
  1. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/Dockerfile +2 -2
  2. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/PKG-INFO +14 -12
  3. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/README.md +13 -11
  4. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/connect.py +91 -6
  5. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cw/get_metric_statistics.py +11 -1
  6. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/connect.py +129 -95
  7. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/connect.py +130 -19
  8. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/create.py +3 -3
  9. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/delete.py +1 -1
  10. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/models.py +2 -6
  11. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/modify.py +2 -2
  12. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/pyproject.toml +1 -1
  13. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_connect.py +403 -3
  14. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_connect_additional.py +2 -73
  15. awslabs_elasticache_mcp_server-0.1.3/tests/tools/cc/test_connect_coverage.py +425 -0
  16. awslabs_elasticache_mcp_server-0.1.3/tests/tools/cc/test_connect_coverage_additional.py +825 -0
  17. awslabs_elasticache_mcp_server-0.1.3/tests/tools/cc/test_create_additional.py +270 -0
  18. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cw/test_get_metric_statistics.py +67 -0
  19. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_connect.py +177 -50
  20. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_connect_additional.py +126 -27
  21. awslabs_elasticache_mcp_server-0.1.3/tests/tools/rg/test_connect_coverage_additional.py +370 -0
  22. awslabs_elasticache_mcp_server-0.1.3/tests/tools/rg/test_connect_optional_fields.py +571 -0
  23. awslabs_elasticache_mcp_server-0.1.3/tests/tools/rg/test_connect_partial_coverage.py +443 -0
  24. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_connect.py +374 -12
  25. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_connect_additional.py +314 -14
  26. awslabs_elasticache_mcp_server-0.1.3/tests/tools/serverless/test_connect_coverage_additional.py +360 -0
  27. awslabs_elasticache_mcp_server-0.1.3/tests/tools/serverless/test_connect_optional_fields.py +481 -0
  28. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_create.py +5 -5
  29. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_modify.py +6 -6
  30. awslabs_elasticache_mcp_server-0.1.3/uv-requirements.txt +26 -0
  31. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/uv.lock +1 -1
  32. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/.gitignore +0 -0
  33. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/.python-version +0 -0
  34. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/CHANGELOG.md +0 -0
  35. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/LICENSE +0 -0
  36. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/NOTICE +0 -0
  37. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/__init__.py +0 -0
  38. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/__init__.py +0 -0
  39. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/common/__init__.py +0 -0
  40. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/common/connection.py +0 -0
  41. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/common/decorators.py +0 -0
  42. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/common/server.py +0 -0
  43. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/context.py +0 -0
  44. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/main.py +0 -0
  45. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/__init__.py +0 -0
  46. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/__init__.py +0 -0
  47. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/create.py +0 -0
  48. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/delete.py +0 -0
  49. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/describe.py +0 -0
  50. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/modify.py +0 -0
  51. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/parsers.py +0 -0
  52. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/processors.py +0 -0
  53. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/ce/__init__.py +0 -0
  54. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/ce/get_cost_and_usage.py +0 -0
  55. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cw/__init__.py +0 -0
  56. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/__init__.py +0 -0
  57. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/create_log_group.py +0 -0
  58. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_groups.py +0 -0
  59. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_streams.py +0 -0
  60. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/filter_log_events.py +0 -0
  61. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/get_log_events.py +0 -0
  62. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/firehose/__init__.py +0 -0
  63. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/firehose/list_delivery_streams.py +0 -0
  64. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/__init__.py +0 -0
  65. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/batch_apply_update_action.py +0 -0
  66. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/batch_stop_update_action.py +0 -0
  67. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/describe_cache_engine_versions.py +0 -0
  68. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/describe_engine_default_parameters.py +0 -0
  69. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/describe_events.py +0 -0
  70. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/describe_service_updates.py +0 -0
  71. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/__init__.py +0 -0
  72. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/complete_migration.py +0 -0
  73. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/create.py +0 -0
  74. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/delete.py +0 -0
  75. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/describe.py +0 -0
  76. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/modify.py +0 -0
  77. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/parsers.py +0 -0
  78. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/processors.py +0 -0
  79. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/start_migration.py +0 -0
  80. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/test_migration.py +0 -0
  81. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/__init__.py +0 -0
  82. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/describe.py +0 -0
  83. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/docker-healthcheck.sh +0 -0
  84. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/test_connection.py +0 -0
  85. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/test_decorators.py +0 -0
  86. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/test_init.py +0 -0
  87. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/test_main.py +0 -0
  88. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/__init__.py +0 -0
  89. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_create.py +0 -0
  90. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_delete.py +0 -0
  91. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_describe.py +0 -0
  92. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_modify.py +0 -0
  93. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_parsers.py +0 -0
  94. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_processors.py +0 -0
  95. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/ce/__init__.py +0 -0
  96. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/ce/test_get_cost_and_usage.py +0 -0
  97. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/__init__.py +0 -0
  98. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_create_log_group.py +0 -0
  99. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_describe_log_groups.py +0 -0
  100. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_describe_log_streams.py +0 -0
  101. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_filter_log_events.py +0 -0
  102. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_get_log_events.py +0 -0
  103. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/firehose/test_list_delivery_streams.py +0 -0
  104. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/__init__.py +0 -0
  105. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_batch_apply_update_action.py +0 -0
  106. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_batch_stop_update_action.py +0 -0
  107. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_describe_cache_engine_versions.py +0 -0
  108. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_describe_engine_default_parameters.py +0 -0
  109. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_describe_events.py +0 -0
  110. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_describe_service_updates.py +0 -0
  111. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/__init__.py +0 -0
  112. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_complete_migration.py +0 -0
  113. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_create.py +0 -0
  114. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_delete.py +0 -0
  115. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_describe.py +0 -0
  116. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_modify.py +0 -0
  117. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_parsers.py +0 -0
  118. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_processors.py +0 -0
  119. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_start_migration.py +0 -0
  120. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_test_migration.py +0 -0
  121. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_delete.py +0 -0
  122. {awslabs_elasticache_mcp_server-0.1.1 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_describe.py +0 -0
@@ -31,11 +31,11 @@ ENV UV_PYTHON_PREFERENCE=only-system
31
31
  ENV UV_FROZEN=true
32
32
 
33
33
  # Copy the required files first
34
- COPY pyproject.toml uv.lock ./
34
+ COPY pyproject.toml uv.lock uv-requirements.txt ./
35
35
 
36
36
  # Install the project's dependencies using the lockfile and settings
37
37
  RUN --mount=type=cache,target=/root/.cache/uv \
38
- pip install uv==0.7.11 && \
38
+ pip install --require-hashes --requirement uv-requirements.txt && \
39
39
  uv sync --frozen --no-install-project --no-dev --no-editable
40
40
 
41
41
  # Then, add the rest of the project source code and install it
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.elasticache-mcp-server
3
- Version: 0.1.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
- Add the MCP to your favorite agentic tools. e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
117
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](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
  {
@@ -1,9 +1,18 @@
1
1
  # AWS ElastiCache MCP Server
2
2
 
3
- The official MCP Server for interacting with AWS ElastiCache
3
+ 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).
4
4
 
5
5
  ## Available MCP Tools
6
6
 
7
+ ### Serverless Cache Operations
8
+ - `create-serverless-cache` - Create a new ElastiCache serverless cache
9
+ - `delete-serverless-cache` - Delete a serverless cache
10
+ - `describe-serverless-caches` - Get information about serverless caches
11
+ - `modify-serverless-cache` - Modify settings of a serverless cache
12
+ - `connect-jump-host-serverless-cache` - Configure an EC2 instance as a jump host for serverless cache access
13
+ - `create-jump-host-serverless-cache` - Create an EC2 jump host to access a serverless cache via SSH tunnel
14
+ - `get-ssh-tunnel-command-serverless-cache` - Generate SSH tunnel command for serverless cache access
15
+
7
16
  ### Replication Group Operations
8
17
  - `create-replication-group` - Create an Amazon ElastiCache replication group with specified configuration
9
18
  - `delete-replication-group` - Delete an ElastiCache replication group with optional final snapshot
@@ -26,15 +35,6 @@ The official MCP Server for interacting with AWS ElastiCache
26
35
  - `create-jump-host-cache-cluster` - Create an EC2 jump host to access a cluster via SSH tunnel
27
36
  - `get-ssh-tunnel-command-cache-cluster` - Generate SSH tunnel command for cluster access
28
37
 
29
- ### Serverless Cache Operations
30
- - `create-serverless-cache` - Create a new ElastiCache serverless cache
31
- - `delete-serverless-cache` - Delete a serverless cache
32
- - `describe-serverless-caches` - Get information about serverless caches
33
- - `modify-serverless-cache` - Modify settings of a serverless cache
34
- - `connect-jump-host-serverless-cache` - Configure an EC2 instance as a jump host for serverless cache access
35
- - `create-jump-host-serverless-cache` - Create an EC2 jump host to access a serverless cache via SSH tunnel
36
- - `get-ssh-tunnel-command-serverless-cache` - Generate SSH tunnel command for serverless cache access
37
-
38
38
  ### CloudWatch Operations
39
39
  - `get-metric-statistics` - Get CloudWatch metric statistics for ElastiCache resources with customizable time periods and dimensions
40
40
 
@@ -76,7 +76,9 @@ All tools support an optional `region_name` parameter to specify which AWS regio
76
76
 
77
77
  ## Installation
78
78
 
79
- Add the MCP to your favorite agentic tools. e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
79
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/install-mcp?name=awslabs.elasticache-mcp-server&config=eyJjb21tYW5kIjoidXZ4IGF3c2xhYnMuZWxhc3RpY2FjaGUtbWNwLXNlcnZlckBsYXRlc3QiLCJlbnYiOnsiQVdTX1BST0ZJTEUiOiJkZWZhdWx0IiwiQVdTX1JFR0lPTiI6InVzLXdlc3QtMiIsIkZBU1RNQ1BfTE9HX0xFVkVMIjoiRVJST1IifSwiZGlzYWJsZWQiOmZhbHNlLCJhdXRvQXBwcm92ZSI6W119)
80
+
81
+ Add the MCP to your favorite agentic tools. (e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
80
82
 
81
83
  ```json
82
84
  {
@@ -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
- params['Dimensions'] = [{'Name': k, 'Value': v} for d in dimensions for k, v in d.items()]
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, List, Tuple, Union
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 primary cluster details
56
- primary_cluster_id = None
57
- for member in replication_group['MemberClusters']:
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
- if not primary_cluster_id:
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 primary cluster
69
- primary_cluster = elasticache_client.describe_cache_clusters(
70
- CacheClusterId=primary_cluster_id, ShowCacheNodeInfo=True
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 = primary_cluster.get('CacheSubnetGroupName')
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 {primary_cluster_id}')
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 primary node
102
- cache_port = primary_cluster['CacheNodes'][0]['Endpoint']['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, List[Dict[str, Any]], None]]:
213
- """Generates SSH tunnel commands to connect to an ElastiCache replication group through an EC2 jump host.
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, List[Dict[str, Any]]]]: Dictionary containing SSH tunnel commands and related details
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
- # Get node details for all clusters
260
- node_commands = []
261
- base_port = None
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
- node_commands.append(
289
- {
290
- 'role': cluster['CacheClusterRole'],
291
- 'clusterId': member,
292
- 'command': ssh_command,
293
- 'localPort': local_port,
294
- 'remoteEndpoint': endpoint,
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
- 'nodes': node_commands,
304
- 'basePort': base_port,
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 primary cluster details
365
- primary_cluster_id = None
366
- for member in replication_group['MemberClusters']:
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
- if not primary_cluster_id:
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 primary cluster
380
- primary_cluster = elasticache_client.describe_cache_clusters(
381
- CacheClusterId=primary_cluster_id, ShowCacheNodeInfo=True
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=primary_cluster['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
- # Raise error if no route to internet gateway found
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