awslabs.elasticache-mcp-server 0.1.2__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.2 → awslabs_elasticache_mcp_server-0.1.3}/Dockerfile +2 -2
  2. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/PKG-INFO +1 -1
  3. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/connect.py +91 -6
  4. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/connect.py +91 -17
  5. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/connect.py +91 -7
  6. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/pyproject.toml +1 -1
  7. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_connect.py +403 -3
  8. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_connect_additional.py +2 -73
  9. awslabs_elasticache_mcp_server-0.1.3/tests/tools/cc/test_connect_coverage.py +425 -0
  10. awslabs_elasticache_mcp_server-0.1.3/tests/tools/cc/test_connect_coverage_additional.py +825 -0
  11. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_connect.py +157 -5
  12. awslabs_elasticache_mcp_server-0.1.3/tests/tools/rg/test_connect_coverage_additional.py +370 -0
  13. awslabs_elasticache_mcp_server-0.1.3/tests/tools/rg/test_connect_optional_fields.py +571 -0
  14. awslabs_elasticache_mcp_server-0.1.3/tests/tools/rg/test_connect_partial_coverage.py +443 -0
  15. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_connect.py +155 -5
  16. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_connect_additional.py +4 -4
  17. awslabs_elasticache_mcp_server-0.1.3/tests/tools/serverless/test_connect_coverage_additional.py +360 -0
  18. awslabs_elasticache_mcp_server-0.1.3/tests/tools/serverless/test_connect_optional_fields.py +481 -0
  19. awslabs_elasticache_mcp_server-0.1.3/uv-requirements.txt +26 -0
  20. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/uv.lock +1 -1
  21. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/.gitignore +0 -0
  22. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/.python-version +0 -0
  23. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/CHANGELOG.md +0 -0
  24. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/LICENSE +0 -0
  25. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/NOTICE +0 -0
  26. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/README.md +0 -0
  27. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/__init__.py +0 -0
  28. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/__init__.py +0 -0
  29. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/common/__init__.py +0 -0
  30. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/common/connection.py +0 -0
  31. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/common/decorators.py +0 -0
  32. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/common/server.py +0 -0
  33. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/context.py +0 -0
  34. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/main.py +0 -0
  35. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/__init__.py +0 -0
  36. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/__init__.py +0 -0
  37. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/create.py +0 -0
  38. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/delete.py +0 -0
  39. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/describe.py +0 -0
  40. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/modify.py +0 -0
  41. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/parsers.py +0 -0
  42. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cc/processors.py +0 -0
  43. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/ce/__init__.py +0 -0
  44. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/ce/get_cost_and_usage.py +0 -0
  45. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cw/__init__.py +0 -0
  46. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cw/get_metric_statistics.py +0 -0
  47. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/__init__.py +0 -0
  48. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/create_log_group.py +0 -0
  49. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_groups.py +0 -0
  50. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_streams.py +0 -0
  51. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/filter_log_events.py +0 -0
  52. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/cwlogs/get_log_events.py +0 -0
  53. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/firehose/__init__.py +0 -0
  54. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/firehose/list_delivery_streams.py +0 -0
  55. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/__init__.py +0 -0
  56. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/batch_apply_update_action.py +0 -0
  57. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/batch_stop_update_action.py +0 -0
  58. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/describe_cache_engine_versions.py +0 -0
  59. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/describe_engine_default_parameters.py +0 -0
  60. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/describe_events.py +0 -0
  61. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/misc/describe_service_updates.py +0 -0
  62. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/__init__.py +0 -0
  63. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/complete_migration.py +0 -0
  64. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/create.py +0 -0
  65. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/delete.py +0 -0
  66. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/describe.py +0 -0
  67. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/modify.py +0 -0
  68. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/parsers.py +0 -0
  69. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/processors.py +0 -0
  70. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/start_migration.py +0 -0
  71. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/rg/test_migration.py +0 -0
  72. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/__init__.py +0 -0
  73. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/create.py +0 -0
  74. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/delete.py +0 -0
  75. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/describe.py +0 -0
  76. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/models.py +0 -0
  77. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/awslabs/elasticache_mcp_server/tools/serverless/modify.py +0 -0
  78. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/docker-healthcheck.sh +0 -0
  79. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/test_connection.py +0 -0
  80. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/test_decorators.py +0 -0
  81. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/test_init.py +0 -0
  82. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/test_main.py +0 -0
  83. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/__init__.py +0 -0
  84. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_create.py +0 -0
  85. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_create_additional.py +0 -0
  86. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_delete.py +0 -0
  87. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_describe.py +0 -0
  88. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_modify.py +0 -0
  89. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_parsers.py +0 -0
  90. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cc/test_processors.py +0 -0
  91. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/ce/__init__.py +0 -0
  92. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/ce/test_get_cost_and_usage.py +0 -0
  93. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cw/test_get_metric_statistics.py +0 -0
  94. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/__init__.py +0 -0
  95. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_create_log_group.py +0 -0
  96. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_describe_log_groups.py +0 -0
  97. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_describe_log_streams.py +0 -0
  98. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_filter_log_events.py +0 -0
  99. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/cwlogs/test_get_log_events.py +0 -0
  100. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/firehose/test_list_delivery_streams.py +0 -0
  101. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/__init__.py +0 -0
  102. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_batch_apply_update_action.py +0 -0
  103. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_batch_stop_update_action.py +0 -0
  104. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_describe_cache_engine_versions.py +0 -0
  105. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_describe_engine_default_parameters.py +0 -0
  106. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_describe_events.py +0 -0
  107. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/misc/test_describe_service_updates.py +0 -0
  108. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/__init__.py +0 -0
  109. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_complete_migration.py +0 -0
  110. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_connect_additional.py +0 -0
  111. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_create.py +0 -0
  112. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_delete.py +0 -0
  113. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_describe.py +0 -0
  114. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_modify.py +0 -0
  115. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_parsers.py +0 -0
  116. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_processors.py +0 -0
  117. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_start_migration.py +0 -0
  118. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/rg/test_test_migration.py +0 -0
  119. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_create.py +0 -0
  120. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_delete.py +0 -0
  121. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_describe.py +0 -0
  122. {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.3}/tests/tools/serverless/test_modify.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.2
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/
@@ -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
 
@@ -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(
@@ -281,18 +281,20 @@ async def get_ssh_tunnel_command_rg(
281
281
  @handle_exceptions
282
282
  async def create_jump_host_rg(
283
283
  replication_group_id: str,
284
- subnet_id: str,
285
- security_group_id: str,
286
284
  key_name: str,
285
+ subnet_id: Optional[str] = None,
286
+ security_group_id: Optional[str] = None,
287
287
  instance_type: str = 't3.small',
288
288
  ) -> Dict[str, Any]:
289
289
  """Creates an EC2 jump host instance to access an ElastiCache replication group via SSH tunnel.
290
290
 
291
291
  Args:
292
292
  replication_group_id (str): ID of the ElastiCache replication group to connect to
293
- subnet_id (str): ID of the subnet to launch the EC2 instance in (must be public)
294
- security_group_id (str): ID of the security group to assign to the EC2 instance
295
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.
296
298
  instance_type (str, optional): EC2 instance type. Defaults to "t3.small"
297
299
 
298
300
  Returns:
@@ -346,6 +348,59 @@ async def create_jump_host_rg(
346
348
  )['CacheSubnetGroups'][0]
347
349
  cache_vpc_id = cache_subnet_group['VpcId']
348
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
+
349
404
  # Get subnet details and verify it's public
350
405
  subnet_response = ec2_client.describe_subnets(SubnetIds=[subnet_id])
351
406
  subnet = subnet_response['Subnets'][0]
@@ -358,20 +413,11 @@ async def create_jump_host_rg(
358
413
  )
359
414
 
360
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)
361
417
  route_tables = ec2_client.describe_route_tables(
362
418
  Filters=[{'Name': 'association.subnet-id', 'Values': [subnet_id]}]
363
419
  )['RouteTables']
364
420
 
365
- # If no explicit route table association, check main route table
366
- if not route_tables:
367
- route_tables = ec2_client.describe_route_tables(
368
- Filters=[
369
- {'Name': 'vpc-id', 'Values': [subnet_vpc_id]},
370
- {'Name': 'association.main', 'Values': ['true']},
371
- ]
372
- )['RouteTables']
373
-
374
- # Check for route to internet gateway
375
421
  is_public = False
376
422
  for rt in route_tables:
377
423
  for route in rt.get('Routes', []):
@@ -381,10 +427,38 @@ async def create_jump_host_rg(
381
427
  if is_public:
382
428
  break
383
429
 
384
- # 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
+
385
459
  if not is_public:
386
460
  raise ValueError(
387
- 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). '
388
462
  'The subnet must be public to allow SSH access to the jump host.'
389
463
  )
390
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(
@@ -273,18 +273,20 @@ async def get_ssh_tunnel_command_serverless(
273
273
  @handle_exceptions
274
274
  async def create_jump_host_serverless(
275
275
  serverless_cache_name: str,
276
- subnet_id: str,
277
- security_group_id: str,
278
276
  key_name: str,
277
+ subnet_id: Optional[str] = None,
278
+ security_group_id: Optional[str] = None,
279
279
  instance_type: str = 't3.small',
280
280
  ) -> Dict[str, Any]:
281
281
  """Creates an EC2 jump host instance to access an ElastiCache serverless cache via SSH tunnel.
282
282
 
283
283
  Args:
284
284
  serverless_cache_name (str): Name of the ElastiCache serverless cache to connect to
285
- subnet_id (str): ID of the subnet to launch the EC2 instance in (must be public)
286
- security_group_id (str): ID of the security group to assign to the EC2 instance
287
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.
288
290
  instance_type (str, optional): EC2 instance type. Defaults to "t3.small"
289
291
 
290
292
  Returns:
@@ -337,6 +339,59 @@ async def create_jump_host_serverless(
337
339
  subnet_response = ec2_client.describe_subnets(SubnetIds=[serverless_cache['SubnetIds'][0]])
338
340
  cache_vpc_id = subnet_response['Subnets'][0]['VpcId']
339
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
+ )
394
+
340
395
  # Get subnet details and verify it's public
341
396
  subnet_response = ec2_client.describe_subnets(SubnetIds=[subnet_id])
342
397
  subnet = subnet_response['Subnets'][0]
@@ -349,6 +404,7 @@ async def create_jump_host_serverless(
349
404
  )
350
405
 
351
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)
352
408
  route_tables = ec2_client.describe_route_tables(
353
409
  Filters=[{'Name': 'association.subnet-id', 'Values': [subnet_id]}]
354
410
  )['RouteTables']
@@ -362,10 +418,38 @@ async def create_jump_host_serverless(
362
418
  if is_public:
363
419
  break
364
420
 
365
- # Raise error if no route to internet gateway found
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
+
366
450
  if not is_public:
367
451
  raise ValueError(
368
- 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). '
369
453
  'The subnet must be public to allow SSH access to the jump host.'
370
454
  )
371
455
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "awslabs.elasticache-mcp-server"
3
- version = "0.1.2"
3
+ version = "0.1.3"
4
4
  description = "An AWS Labs Model Context Protocol (MCP) server for Amazon ElastiCache"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"