diff --git a/src/cloudwatch-appsignals-mcp-server/awslabs/cloudwatch_appsignals_mcp_server/enablement_guides/templates/ec2/ec2-python-enablement.md b/src/cloudwatch-appsignals-mcp-server/awslabs/cloudwatch_appsignals_mcp_server/enablement_guides/templates/ec2/ec2-python-enablement.md new file mode 100644 index 0000000000..14ee94867f --- /dev/null +++ b/src/cloudwatch-appsignals-mcp-server/awslabs/cloudwatch_appsignals_mcp_server/enablement_guides/templates/ec2/ec2-python-enablement.md @@ -0,0 +1 @@ +Placeholder content just to verify the tool can fetch the file. diff --git a/src/cloudwatch-appsignals-mcp-server/awslabs/cloudwatch_appsignals_mcp_server/enablement_tools.py b/src/cloudwatch-appsignals-mcp-server/awslabs/cloudwatch_appsignals_mcp_server/enablement_tools.py new file mode 100644 index 0000000000..9a013cc4f6 --- /dev/null +++ b/src/cloudwatch-appsignals-mcp-server/awslabs/cloudwatch_appsignals_mcp_server/enablement_tools.py @@ -0,0 +1,150 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""CloudWatch Application Signals MCP Server - Enablement Tools.""" + +from enum import Enum +from loguru import logger +from pathlib import Path + + +class Platform(str, Enum): + """Supported deployment platforms.""" + + EC2 = 'ec2' + ECS = 'ecs' + LAMBDA = 'lambda' + EKS = 'eks' + + +class ServiceLanguage(str, Enum): + """Supported service programming languages.""" + + PYTHON = 'python' + NODEJS = 'nodejs' + JAVA = 'java' + DOTNET = 'dotnet' + + +async def get_application_signals_enablement_guide( + platform: Platform, service_language: ServiceLanguage, iac_directory: str, app_directory: str +) -> str: + """Get enablement guide for AWS Application Signals. + + This tool returns step-by-step enablement instructions that guide you through + modifying your infrastructure and application code to enable Application Signals, + which is the preferred way to enable automatic instrumentation for services on AWS. + + After calling this tool, you should: + 1. Review the enablement guide and create a work list of required changes + 2. For each step in the work list: + - Identify the specific files in iac_directory or app_directory that need modification + - Apply the changes as specified in the guide + - Verify the changes are correct before moving to the next step + + Important guidelines: + - Use ABSOLUTE PATHS (iac_directory and app_directory) when reading and writing files + - Modify IaC code, Dockerfiles, and dependency files (requirements.txt, pyproject.toml, + package.json, pom.xml, build.gradle, *.csproj, etc.) as needed + - Do NOT modify actual application logic files (.py, .js, .java source code) + - Read application files if needed to understand the setup, but avoid modifying them + + Args: + platform: The AWS platform where the service runs (ec2, ecs, lambda, eks). + To help user determine: check their IaC for ECS services, Lambda functions, EKS deployments, or EC2 instances. + service_language: The service's programming language (python, nodejs, java, dotnet). + To help user determine: check for package.json (nodejs), requirements.txt (python), pom.xml (java), or .csproj (dotnet). + iac_directory: ABSOLUTE path to the IaC directory (e.g., /home/user/project/infrastructure) + app_directory: ABSOLUTE path to the application code directory (e.g., /home/user/project/app) + + Returns: + Markdown-formatted enablement guide with step-by-step instructions + """ + logger.debug( + f'get_application_signals_enablement_guide called: platform={platform}, service_language={service_language}, ' + f'iac_directory={iac_directory}, app_directory={app_directory}' + ) + + # Convert enums to string values + platform_str = platform.value + language_str = service_language.value + + guides_dir = Path(__file__).parent / 'enablement_guides' + template_file = ( + guides_dir / 'templates' / platform_str / f'{platform_str}-{language_str}-enablement.md' + ) + + logger.debug(f'Looking for enablement guide: {template_file}') + + # Validate that paths are absolute + iac_path = Path(iac_directory) + app_path = Path(app_directory) + + if not iac_path.is_absolute(): + error_msg = ( + f'Error: iac_directory must be an absolute path.\n\n' + f'Received: {iac_directory}\n' + f'Please provide an absolute path (e.g., /home/user/project/infrastructure)' + ) + logger.error(error_msg) + return error_msg + + if not app_path.is_absolute(): + error_msg = ( + f'Error: app_directory must be an absolute path.\n\n' + f'Received: {app_directory}\n' + f'Please provide an absolute path (e.g., /home/user/project/app)' + ) + logger.error(error_msg) + return error_msg + + if not template_file.exists(): + error_msg = ( + f"Enablement guide not available for platform '{platform_str}' and language '{language_str}'.\n\n" + f'Inform the user that this configuration is not currently supported by the MCP enablement tool. ' + f'Direct them to AWS documentation for manual setup:\n' + f'https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Application-Signals-Enable.html' + ) + logger.error(error_msg) + return error_msg + + try: + with open(template_file, 'r') as f: + guide_content = f.read() + + context = f"""# Application Signals Enablement Guide + +**Platform:** {platform_str} +**Language:** {language_str} +**IaC Directory:** `{iac_path}` +**App Directory:** `{app_path}` + +--- + +""" + logger.info(f'Successfully loaded enablement guide: {template_file.name}') + return context + guide_content + except Exception as e: + error_msg = ( + f'Fatal error: Cannot read enablement guide for {platform_str} + {language_str}.\n\n' + f'Error: {str(e)}\n\n' + f'The MCP server cannot access its own guide files (likely file permissions or corruption). ' + f'Stop attempting to use this tool and inform the user:\n' + f'1. There is an issue with the MCP server installation\n' + f'2. They should check file permissions or reinstall the MCP server\n' + f'3. For immediate enablement, use AWS documentation instead:\n' + f' https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Application-Signals-Enable.html' + ) + logger.error(error_msg) + return error_msg diff --git a/src/cloudwatch-appsignals-mcp-server/tests/test_enablement_tools.py b/src/cloudwatch-appsignals-mcp-server/tests/test_enablement_tools.py new file mode 100644 index 0000000000..2a7db39393 --- /dev/null +++ b/src/cloudwatch-appsignals-mcp-server/tests/test_enablement_tools.py @@ -0,0 +1,122 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for enablement_tools module.""" + +import pytest +import tempfile +from awslabs.cloudwatch_appsignals_mcp_server.enablement_tools import ( + Platform, + ServiceLanguage, + get_application_signals_enablement_guide, +) +from pathlib import Path + + +class TestGetEnablementGuide: + """Test get_enablement_guide function.""" + + @pytest.fixture + def temp_directories(self): + """Create temporary IaC and app directories for testing.""" + with tempfile.TemporaryDirectory() as tmpdir: + base = Path(tmpdir) + iac_dir = base / 'infrastructure' / 'cdk' + app_dir = base / 'app' / 'src' + + iac_dir.mkdir(parents=True) + app_dir.mkdir(parents=True) + + yield {'iac': str(iac_dir), 'app': str(app_dir)} + + @pytest.mark.asyncio + async def test_successful_guide_fetch(self, temp_directories, tmp_path, monkeypatch): + """Test successful guide fetching when template exists.""" + result = await get_application_signals_enablement_guide( + platform=Platform.EC2, + service_language=ServiceLanguage.PYTHON, + iac_directory=temp_directories['iac'], + app_directory=temp_directories['app'], + ) + + assert '# Application Signals Enablement Guide' in result + assert 'Placeholder content just to verify the tool can fetch the file.' in result + assert temp_directories['iac'] in result + assert temp_directories['app'] in result + + @pytest.mark.asyncio + async def test_all_valid_platforms(self, temp_directories): + """Test that all valid platforms are accepted.""" + valid_platforms = [Platform.EC2, Platform.ECS, Platform.LAMBDA, Platform.EKS] + + for platform in valid_platforms: + result = await get_application_signals_enablement_guide( + platform=platform, + service_language=ServiceLanguage.PYTHON, + iac_directory=temp_directories['iac'], + app_directory=temp_directories['app'], + ) + + # Should either succeed or say template not found, but not invalid platform + assert 'Error: Invalid platform' not in result + + @pytest.mark.asyncio + async def test_all_valid_languages(self, temp_directories): + """Test that all valid languages are accepted.""" + valid_languages = [ + ServiceLanguage.PYTHON, + ServiceLanguage.NODEJS, + ServiceLanguage.JAVA, + ServiceLanguage.DOTNET, + ] + + for language in valid_languages: + result = await get_application_signals_enablement_guide( + platform=Platform.EC2, + service_language=language, + iac_directory=temp_directories['iac'], + app_directory=temp_directories['app'], + ) + + # Should either succeed or say template not found, but not invalid language + assert 'Error: Invalid language' not in result + + @pytest.mark.asyncio + async def test_relative_path_rejected(self, temp_directories): + """Test that relative paths are rejected with clear error message.""" + result = await get_application_signals_enablement_guide( + platform=Platform.EC2, + service_language=ServiceLanguage.PYTHON, + iac_directory='infrastructure/cdk', + app_directory=temp_directories['app'], + ) + + assert 'Error: iac_directory must be an absolute path' in result + assert 'infrastructure/cdk' in result + + @pytest.mark.asyncio + async def test_relative_app_directory_rejected(self, temp_directories): + """Test that relative app directory is rejected with clear error message.""" + result = await get_application_signals_enablement_guide( + platform=Platform.EC2, + service_language=ServiceLanguage.PYTHON, + iac_directory=temp_directories['iac'], + app_directory='app/src', + ) + + assert 'Error: app_directory must be an absolute path' in result + assert 'app/src' in result + + @pytest.mark.asyncio + async def test_absolute_path_handling(self, temp_directories): + """Test that absolute paths are handled correctly.""" + result = await get_application_signals_enablement_guide( + platform=Platform.EC2, + service_language=ServiceLanguage.PYTHON, + iac_directory=temp_directories['iac'], + app_directory=temp_directories['app'], + ) + + assert '# Application Signals Enablement Guide' in result + assert temp_directories['iac'] in result + assert temp_directories['app'] in result