awslabs.elasticache-mcp-server 0.1.2__tar.gz → 0.1.4__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.
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/Dockerfile +33 -23
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/PKG-INFO +5 -3
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/README.md +3 -1
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cc/connect.py +91 -6
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/firehose/list_delivery_streams.py +0 -2
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/connect.py +91 -17
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/serverless/connect.py +91 -7
- awslabs_elasticache_mcp_server-0.1.4/docker-healthcheck.sh +25 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/pyproject.toml +2 -2
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_connect.py +403 -3
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_connect_additional.py +2 -73
- awslabs_elasticache_mcp_server-0.1.4/tests/tools/cc/test_connect_coverage.py +425 -0
- awslabs_elasticache_mcp_server-0.1.4/tests/tools/cc/test_connect_coverage_additional.py +825 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_connect.py +157 -5
- awslabs_elasticache_mcp_server-0.1.4/tests/tools/rg/test_connect_coverage_additional.py +370 -0
- awslabs_elasticache_mcp_server-0.1.4/tests/tools/rg/test_connect_optional_fields.py +571 -0
- awslabs_elasticache_mcp_server-0.1.4/tests/tools/rg/test_connect_partial_coverage.py +443 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/serverless/test_connect.py +155 -5
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/serverless/test_connect_additional.py +4 -4
- awslabs_elasticache_mcp_server-0.1.4/tests/tools/serverless/test_connect_coverage_additional.py +360 -0
- awslabs_elasticache_mcp_server-0.1.4/tests/tools/serverless/test_connect_optional_fields.py +481 -0
- awslabs_elasticache_mcp_server-0.1.4/uv-requirements.txt +26 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/uv.lock +657 -457
- awslabs_elasticache_mcp_server-0.1.2/docker-healthcheck.sh +0 -26
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/.gitignore +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/.python-version +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/CHANGELOG.md +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/LICENSE +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/NOTICE +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/common/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/common/connection.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/common/decorators.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/common/server.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/context.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/main.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cc/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cc/create.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cc/delete.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cc/describe.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cc/modify.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cc/parsers.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cc/processors.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/ce/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/ce/get_cost_and_usage.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cw/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cw/get_metric_statistics.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cwlogs/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cwlogs/create_log_group.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_groups.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cwlogs/describe_log_streams.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cwlogs/filter_log_events.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/cwlogs/get_log_events.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/firehose/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/misc/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/misc/batch_apply_update_action.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/misc/batch_stop_update_action.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/misc/describe_cache_engine_versions.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/misc/describe_engine_default_parameters.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/misc/describe_events.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/misc/describe_service_updates.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/complete_migration.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/create.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/delete.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/describe.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/modify.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/parsers.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/processors.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/start_migration.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/rg/test_migration.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/serverless/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/serverless/create.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/serverless/delete.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/serverless/describe.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/serverless/models.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/awslabs/elasticache_mcp_server/tools/serverless/modify.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/test_connection.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/test_decorators.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/test_init.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/test_main.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_create.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_create_additional.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_delete.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_describe.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_modify.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_parsers.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cc/test_processors.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/ce/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/ce/test_get_cost_and_usage.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cw/test_get_metric_statistics.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cwlogs/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cwlogs/test_create_log_group.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cwlogs/test_describe_log_groups.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cwlogs/test_describe_log_streams.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cwlogs/test_filter_log_events.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/cwlogs/test_get_log_events.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/firehose/test_list_delivery_streams.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/misc/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/misc/test_batch_apply_update_action.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/misc/test_batch_stop_update_action.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/misc/test_describe_cache_engine_versions.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/misc/test_describe_engine_default_parameters.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/misc/test_describe_events.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/misc/test_describe_service_updates.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/__init__.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_complete_migration.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_connect_additional.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_create.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_delete.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_describe.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_modify.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_parsers.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_processors.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_start_migration.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/rg/test_test_migration.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/serverless/test_create.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/serverless/test_delete.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/serverless/test_describe.py +0 -0
- {awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/tests/tools/serverless/test_modify.py +0 -0
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
FROM public.ecr.aws/
|
|
15
|
+
# dependabot should continue to update this to the latest hash.
|
|
16
|
+
FROM public.ecr.aws/docker/library/python:3.13.5-alpine3.21@sha256:c9a09c45a4bcc618c7f7128585b8dd0d41d0c31a8a107db4c8255ffe0b69375d AS uv
|
|
17
17
|
|
|
18
18
|
# Install the project into `/app`
|
|
19
19
|
WORKDIR /app
|
|
@@ -31,40 +31,50 @@ 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
|
+
|
|
36
|
+
# Python optimization and uv configuration
|
|
37
|
+
ENV PIP_NO_CACHE_DIR=1 \
|
|
38
|
+
PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
39
|
+
|
|
40
|
+
# Install system dependencies and Python package manager
|
|
41
|
+
RUN apk update && \
|
|
42
|
+
apk add --no-cache --virtual .build-deps \
|
|
43
|
+
build-base \
|
|
44
|
+
gcc \
|
|
45
|
+
musl-dev \
|
|
46
|
+
libffi-dev \
|
|
47
|
+
openssl-dev \
|
|
48
|
+
cargo
|
|
35
49
|
|
|
36
50
|
# Install the project's dependencies using the lockfile and settings
|
|
37
51
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
38
|
-
pip install uv
|
|
39
|
-
uv sync --frozen --no-install-project --no-dev --no-editable
|
|
52
|
+
pip install --require-hashes --requirement uv-requirements.txt --no-cache-dir && \
|
|
53
|
+
uv sync --python 3.13 --frozen --no-install-project --no-dev --no-editable
|
|
40
54
|
|
|
41
55
|
# Then, add the rest of the project source code and install it
|
|
42
56
|
# Installing separately from its dependencies allows optimal layer caching
|
|
43
57
|
COPY . /app
|
|
44
58
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
45
|
-
uv sync --frozen --no-dev --no-editable
|
|
59
|
+
uv sync --python 3.13 --frozen --no-dev --no-editable
|
|
46
60
|
|
|
47
61
|
# Make the directory just in case it doesn't exist
|
|
48
62
|
RUN mkdir -p /root/.local
|
|
49
63
|
|
|
50
|
-
FROM public.ecr.aws/
|
|
64
|
+
FROM public.ecr.aws/docker/library/python:3.13.5-alpine3.21@sha256:c9a09c45a4bcc618c7f7128585b8dd0d41d0c31a8a107db4c8255ffe0b69375d
|
|
51
65
|
|
|
52
66
|
# Place executables in the environment at the front of the path and include other binaries
|
|
53
|
-
ENV PATH="/app/.venv/bin:$PATH
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# Install
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
chmod o+x /root
|
|
65
|
-
|
|
66
|
-
# Get the project from the uv layer
|
|
67
|
-
COPY --from=uv --chown=app:app /root/.local /root/.local
|
|
67
|
+
ENV PATH="/app/.venv/bin:$PATH" \
|
|
68
|
+
PYTHONUNBUFFERED=1
|
|
69
|
+
|
|
70
|
+
# Install runtime dependencies and create application user
|
|
71
|
+
RUN apk update && \
|
|
72
|
+
apk add --no-cache ca-certificates && \
|
|
73
|
+
update-ca-certificates && \
|
|
74
|
+
addgroup -S app && \
|
|
75
|
+
adduser -S app -G app -h /app
|
|
76
|
+
|
|
77
|
+
# Copy application artifacts from build stage
|
|
68
78
|
COPY --from=uv --chown=app:app /app/.venv /app/.venv
|
|
69
79
|
|
|
70
80
|
# Get healthcheck script
|
|
@@ -74,5 +84,5 @@ COPY ./docker-healthcheck.sh /usr/local/bin/docker-healthcheck.sh
|
|
|
74
84
|
USER app
|
|
75
85
|
|
|
76
86
|
# When running the container, add --db-path and a bind mount to the host's db file
|
|
77
|
-
HEALTHCHECK --interval=
|
|
87
|
+
HEALTHCHECK --interval=60s --timeout=10s --start-period=10s --retries=3 CMD ["docker-healthcheck.sh"]
|
|
78
88
|
ENTRYPOINT ["awslabs.elasticache-mcp-server"]
|
|
@@ -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.4
|
|
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/
|
|
@@ -23,7 +23,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: boto3>=1.34.0
|
|
25
25
|
Requires-Dist: loguru>=0.7.0
|
|
26
|
-
Requires-Dist: mcp[cli]>=1.
|
|
26
|
+
Requires-Dist: mcp[cli]>=1.11.0
|
|
27
27
|
Requires-Dist: pydantic>=2.10.6
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: commitizen>=4.2.2; extra == 'dev'
|
|
@@ -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
|
+
| Cursor | VS Code |
|
|
118
|
+
|:------:|:-------:|
|
|
119
|
+
| [](https://cursor.com/install-mcp?name=awslabs.elasticache-mcp-server&config=eyJjb21tYW5kIjoidXZ4IGF3c2xhYnMuZWxhc3RpY2FjaGUtbWNwLXNlcnZlckBsYXRlc3QiLCJlbnYiOnsiQVdTX1BST0ZJTEUiOiJkZWZhdWx0IiwiQVdTX1JFR0lPTiI6InVzLXdlc3QtMiIsIkZBU1RNQ1BfTE9HX0xFVkVMIjoiRVJST1IifSwiZGlzYWJsZWQiOmZhbHNlLCJhdXRvQXBwcm92ZSI6W119) | [](https://insiders.vscode.dev/redirect/mcp/install?name=ElastiCache%20MCP%20Server&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22awslabs.elasticache-mcp-server%40latest%22%5D%2C%22env%22%3A%7B%22AWS_PROFILE%22%3A%22default%22%2C%22AWS_REGION%22%3A%22us-west-2%22%2C%22FASTMCP_LOG_LEVEL%22%3A%22ERROR%22%7D%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D) |
|
|
118
120
|
|
|
119
121
|
Add the MCP to your favorite agentic tools. (e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
|
|
120
122
|
|
|
@@ -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
|
-
|
|
79
|
+
| Cursor | VS Code |
|
|
80
|
+
|:------:|:-------:|
|
|
81
|
+
| [](https://cursor.com/install-mcp?name=awslabs.elasticache-mcp-server&config=eyJjb21tYW5kIjoidXZ4IGF3c2xhYnMuZWxhc3RpY2FjaGUtbWNwLXNlcnZlckBsYXRlc3QiLCJlbnYiOnsiQVdTX1BST0ZJTEUiOiJkZWZhdWx0IiwiQVdTX1JFR0lPTiI6InVzLXdlc3QtMiIsIkZBU1RNQ1BfTE9HX0xFVkVMIjoiRVJST1IifSwiZGlzYWJsZWQiOmZhbHNlLCJhdXRvQXBwcm92ZSI6W119) | [](https://insiders.vscode.dev/redirect/mcp/install?name=ElastiCache%20MCP%20Server&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22awslabs.elasticache-mcp-server%40latest%22%5D%2C%22env%22%3A%7B%22AWS_PROFILE%22%3A%22default%22%2C%22AWS_REGION%22%3A%22us-west-2%22%2C%22FASTMCP_LOG_LEVEL%22%3A%22ERROR%22%7D%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D) |
|
|
80
82
|
|
|
81
83
|
Add the MCP to your favorite agentic tools. (e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.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
|
|
|
@@ -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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
SERVER="elasticache-mcp-server"
|
|
17
|
+
|
|
18
|
+
# Check if the server process is running
|
|
19
|
+
if pgrep -P 0 -a -l -x -f "/app/.venv/bin/python3 /app/.venv/bin/awslabs.$SERVER" > /dev/null; then
|
|
20
|
+
echo -n "$SERVER is running";
|
|
21
|
+
exit 0;
|
|
22
|
+
fi;
|
|
23
|
+
|
|
24
|
+
# Unhealthy
|
|
25
|
+
exit 1;
|
{awslabs_elasticache_mcp_server-0.1.2 → awslabs_elasticache_mcp_server-0.1.4}/pyproject.toml
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "awslabs.elasticache-mcp-server"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
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"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"loguru>=0.7.0",
|
|
9
|
-
"mcp[cli]>=1.
|
|
9
|
+
"mcp[cli]>=1.11.0",
|
|
10
10
|
"pydantic>=2.10.6",
|
|
11
11
|
"boto3>=1.34.0",
|
|
12
12
|
]
|