This is page 609 of 612. Use http://codebase.md/awslabs/mcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .devcontainer
│ └── devcontainer.json
├── .github
│ ├── actions
│ │ ├── build-and-push-container-image
│ │ │ └── action.yml
│ │ └── clear-space-ubuntu-latest-agressively
│ │ └── action.yml
│ ├── codecov.yml
│ ├── CODEOWNERS
│ ├── dependabot.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.yml
│ │ ├── documentation.yml
│ │ ├── feature_request.yml
│ │ ├── rfc.yml
│ │ └── support_awslabs_mcp_servers.yml
│ ├── pull_request_template.md
│ ├── SECURITY
│ ├── SUPPORT
│ └── workflows
│ ├── aws-api-mcp-upgrade-version.yml
│ ├── bandit-requirements.txt
│ ├── bandit.yml
│ ├── cfn_nag.yml
│ ├── check-gh-pages-builds.yml
│ ├── check-license-header-hash.txt
│ ├── check-license-header.json
│ ├── check-license-header.yml
│ ├── checkov.yml
│ ├── codeql.yml
│ ├── dependency-review-action.yml
│ ├── detect-secrets-requirements.txt
│ ├── gh-pages.yml
│ ├── merge-prevention.yml
│ ├── powershell.yml
│ ├── pre-commit-requirements.txt
│ ├── pre-commit.yml
│ ├── pull-request-lint.yml
│ ├── python.yml
│ ├── RELEASE_INSTRUCTIONS.md
│ ├── release-initiate-branch.yml
│ ├── release-merge-tag.yml
│ ├── release.py
│ ├── release.yml
│ ├── scanners.yml
│ ├── scorecard-analysis.yml
│ ├── semgrep-requirements.txt
│ ├── semgrep.yml
│ ├── stale.yml
│ ├── trivy.yml
│ └── typescript.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── .ruff.toml
├── .secrets.baseline
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DESIGN_GUIDELINES.md
├── DEVELOPER_GUIDE.md
├── docs
│ └── images
│ └── root-readme
│ ├── cline-api-provider-filled.png
│ ├── cline-chat-interface.png
│ ├── cline-custom-instructions.png
│ ├── cline-select-aws-profile.png
│ ├── cline-select-bedrock.png
│ ├── configure-mcp-servers.png
│ ├── install-cline-extension.png
│ ├── mcp-servers-installed.png
│ └── select-mcp-servers.png
├── docusaurus
│ ├── .gitignore
│ ├── docs
│ │ ├── installation.md
│ │ ├── intro.md
│ │ ├── samples
│ │ │ ├── index.md
│ │ │ ├── mcp-integration-with-kb.md
│ │ │ ├── mcp-integration-with-nova-canvas.md
│ │ │ └── stepfunctions-tool-mcp-server.md
│ │ ├── servers
│ │ │ ├── amazon-bedrock-agentcore-mcp-server.md
│ │ │ ├── amazon-keyspaces-mcp-server.md
│ │ │ ├── amazon-mq-mcp-server.md
│ │ │ ├── amazon-neptune-mcp-server.md
│ │ │ ├── amazon-qbusiness-anonymous-mcp-server.md
│ │ │ ├── amazon-qindex-mcp-server.md
│ │ │ ├── amazon-sns-sqs-mcp-server.md
│ │ │ ├── aurora-dsql-mcp-server.md
│ │ │ ├── aws-api-mcp-server.md
│ │ │ ├── aws-appsync-mcp-server.md
│ │ │ ├── aws-bedrock-custom-model-import-mcp-server.md
│ │ │ ├── aws-bedrock-data-automation-mcp-server.md
│ │ │ ├── aws-dataprocessing-mcp-server.md
│ │ │ ├── aws-diagram-mcp-server.md
│ │ │ ├── aws-documentation-mcp-server.md
│ │ │ ├── aws-healthomics-mcp-server.md
│ │ │ ├── aws-iot-sitewise-mcp-server.md
│ │ │ ├── aws-knowledge-mcp-server.md
│ │ │ ├── aws-location-mcp-server.md
│ │ │ ├── aws-msk-mcp-server.md
│ │ │ ├── aws-pricing-mcp-server.md
│ │ │ ├── aws-serverless-mcp-server.md
│ │ │ ├── aws-support-mcp-server.md
│ │ │ ├── bedrock-kb-retrieval-mcp-server.md
│ │ │ ├── billing-cost-management-mcp-server.md
│ │ │ ├── ccapi-mcp-server.md
│ │ │ ├── cdk-mcp-server.md
│ │ │ ├── cfn-mcp-server.md
│ │ │ ├── cloudtrail-mcp-server.md
│ │ │ ├── cloudwatch-applicationsignals-mcp-server.md
│ │ │ ├── cloudwatch-mcp-server.md
│ │ │ ├── code-doc-gen-mcp-server.md
│ │ │ ├── core-mcp-server.md
│ │ │ ├── cost-explorer-mcp-server.md
│ │ │ ├── documentdb-mcp-server.md
│ │ │ ├── dynamodb-mcp-server.md
│ │ │ ├── ecs-mcp-server.md
│ │ │ ├── eks-mcp-server.md
│ │ │ ├── elasticache-mcp-server.md
│ │ │ ├── finch-mcp-server.md
│ │ │ ├── frontend-mcp-server.md
│ │ │ ├── git-repo-research-mcp-server.md
│ │ │ ├── healthlake-mcp-server.md
│ │ │ ├── iam-mcp-server.md
│ │ │ ├── kendra-index-mcp-server.md
│ │ │ ├── lambda-tool-mcp-server.md
│ │ │ ├── memcached-mcp-server.md
│ │ │ ├── mysql-mcp-server.md
│ │ │ ├── nova-canvas-mcp-server.md
│ │ │ ├── openapi-mcp-server.md
│ │ │ ├── postgres-mcp-server.md
│ │ │ ├── prometheus-mcp-server.md
│ │ │ ├── redshift-mcp-server.md
│ │ │ ├── s3-tables-mcp-server.md
│ │ │ ├── stepfunctions-tool-mcp-server.md
│ │ │ ├── syntheticdata-mcp-server.md
│ │ │ ├── terraform-mcp-server.md
│ │ │ ├── timestream-for-influxdb-mcp-server.md
│ │ │ ├── valkey-mcp-server.md
│ │ │ └── well-architected-security-mcp-server.mdx
│ │ └── vibe_coding.md
│ ├── docusaurus.config.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── sidebars.ts
│ ├── src
│ │ ├── components
│ │ │ ├── HomepageFeatures
│ │ │ │ └── styles.module.css
│ │ │ └── ServerCards
│ │ │ ├── index.tsx
│ │ │ └── styles.module.css
│ │ ├── css
│ │ │ ├── custom.css
│ │ │ └── doc-override.css
│ │ └── pages
│ │ ├── index.module.css
│ │ └── servers.tsx
│ ├── static
│ │ ├── .nojekyll
│ │ ├── assets
│ │ │ ├── icons
│ │ │ │ ├── activity.svg
│ │ │ │ ├── book-open.svg
│ │ │ │ ├── cpu.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── dollar-sign.svg
│ │ │ │ ├── help-circle.svg
│ │ │ │ ├── key.svg
│ │ │ │ ├── server.svg
│ │ │ │ ├── share-2.svg
│ │ │ │ ├── tool.svg
│ │ │ │ └── zap.svg
│ │ │ └── server-cards.json
│ │ └── img
│ │ ├── aws-logo.svg
│ │ └── logo.png
│ └── tsconfig.json
├── LICENSE
├── NOTICE
├── README.md
├── samples
│ ├── mcp-integration-with-kb
│ │ ├── .env.example
│ │ ├── .python-version
│ │ ├── assets
│ │ │ └── simplified-mcp-flow-diagram.png
│ │ ├── clients
│ │ │ └── client_server.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── user_interfaces
│ │ │ └── chat_bedrock_st.py
│ │ └── uv.lock
│ ├── mcp-integration-with-nova-canvas
│ │ ├── .env.example
│ │ ├── .python-version
│ │ ├── clients
│ │ │ └── client_server.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── user_interfaces
│ │ │ └── image_generator_st.py
│ │ └── uv.lock
│ ├── README.md
│ └── stepfunctions-tool-mcp-server
│ ├── README.md
│ └── sample_state_machines
│ ├── customer-create
│ │ └── app.py
│ ├── customer-id-from-email
│ │ └── app.py
│ ├── customer-info-from-id
│ │ └── app.py
│ └── template.yml
├── scripts
│ ├── README.md
│ └── verify_package_name.py
├── src
│ ├── amazon-bedrock-agentcore-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── amazon_bedrock_agentcore_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── config.py
│ │ │ ├── server.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── cache.py
│ │ │ ├── doc_fetcher.py
│ │ │ ├── indexer.py
│ │ │ ├── text_processor.py
│ │ │ └── url_validator.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── SECURITY.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_cache.py
│ │ │ ├── test_config.py
│ │ │ ├── test_doc_fetcher.py
│ │ │ ├── test_indexer.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_server.py
│ │ │ ├── test_text_processor.py
│ │ │ └── test_url_validator.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── amazon-kendra-index-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── amazon_kendra_index_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── server.py
│ │ │ └── util.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── amazon-keyspaces-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── amazon_keyspaces_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── client.py
│ │ │ ├── config.py
│ │ │ ├── consts.py
│ │ │ ├── llm_context.py
│ │ │ ├── models.py
│ │ │ ├── server.py
│ │ │ └── services.py
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── run_tests.sh
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_client.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_query_analysis_service.py
│ │ │ ├── test_server.py
│ │ │ └── test_services.py
│ │ └── uv.lock
│ ├── amazon-mq-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── amazon_mq_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── aws_service_mcp_generator.py
│ │ │ ├── consts.py
│ │ │ ├── rabbitmq
│ │ │ │ ├── __init__.py
│ │ │ │ ├── admin.py
│ │ │ │ ├── connection.py
│ │ │ │ ├── doc
│ │ │ │ │ ├── rabbitmq_broker_sizing_guide.md
│ │ │ │ │ ├── rabbitmq_performance_optimization_best_practice.md
│ │ │ │ │ ├── rabbitmq_production_deployment_guidelines.md
│ │ │ │ │ ├── rabbitmq_quorum_queue_migration_guide.md
│ │ │ │ │ └── rabbitmq_setup_best_practice.md
│ │ │ │ ├── handlers.py
│ │ │ │ └── module.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── example
│ │ │ └── sample_mcp_q_cli.json
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── .gitignore
│ │ │ ├── rabbitmq
│ │ │ │ ├── __init__.py
│ │ │ │ ├── conftest.py
│ │ │ │ ├── test_admin.py
│ │ │ │ ├── test_connection.py
│ │ │ │ ├── test_handlers.py
│ │ │ │ └── test_module.py
│ │ │ ├── test_aws_service_mcp_generator.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── amazon-neptune-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── amazon_neptune_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── exceptions.py
│ │ │ ├── graph_store
│ │ │ │ ├── __init__.py
│ │ │ │ ├── analytics.py
│ │ │ │ ├── base.py
│ │ │ │ └── database.py
│ │ │ ├── models.py
│ │ │ ├── neptune.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_analytics.py
│ │ │ ├── test_database.py
│ │ │ ├── test_exceptions.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_models.py
│ │ │ ├── test_neptune.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── amazon-qbusiness-anonymous-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── amazon_qbusiness_anonymous_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── clients.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── amazon-qindex-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── amazon_qindex_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── clients.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_clients.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ └── test_server.py
│ │ └── uv.lock
│ ├── amazon-sns-sqs-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── amazon_sns_sqs_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── common.py
│ │ │ ├── consts.py
│ │ │ ├── generator.py
│ │ │ ├── server.py
│ │ │ ├── sns.py
│ │ │ └── sqs.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── print_tools.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── run_tests.sh
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── test_common.py
│ │ │ ├── test_generator.py
│ │ │ ├── test_server.py
│ │ │ ├── test_sns.py
│ │ │ └── test_sqs.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aurora-dsql-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aurora_dsql_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── consts.py
│ │ │ ├── mutable_sql_detector.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_connection_reuse.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_profile_option.py
│ │ │ ├── test_readonly_enforcement.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-api-mcp-server
│ │ ├── .gitattributes
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_api_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── core
│ │ │ │ ├── __init__.py
│ │ │ │ ├── agent_scripts
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── manager.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ └── registry
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── application-failure-troubleshooting.script.md
│ │ │ │ │ ├── cloudtral-mutli-region-setup.script.md
│ │ │ │ │ ├── create_amazon_aurora_db_cluster_with_instances.script.md
│ │ │ │ │ ├── lambda-timeout-debugging.script.md
│ │ │ │ │ ├── scripts_format.md
│ │ │ │ │ └── troubleshoot-permissions-with-cloudtrail-events.script.md
│ │ │ │ ├── aws
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── driver.py
│ │ │ │ │ ├── pagination.py
│ │ │ │ │ ├── regions.py
│ │ │ │ │ ├── service.py
│ │ │ │ │ └── services.py
│ │ │ │ ├── common
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── command_metadata.py
│ │ │ │ │ ├── command.py
│ │ │ │ │ ├── config.py
│ │ │ │ │ ├── errors.py
│ │ │ │ │ ├── file_operations.py
│ │ │ │ │ ├── file_system_controls.py
│ │ │ │ │ ├── helpers.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── data
│ │ │ │ │ └── api_metadata.json
│ │ │ │ ├── metadata
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── read_only_operations_list.py
│ │ │ │ ├── parser
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── custom_validators
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── botocore_param_validator.py
│ │ │ │ │ │ ├── ec2_validator.py
│ │ │ │ │ │ └── ssm_validator.py
│ │ │ │ │ ├── interpretation.py
│ │ │ │ │ ├── lexer.py
│ │ │ │ │ └── parser.py
│ │ │ │ ├── py.typed
│ │ │ │ └── security
│ │ │ │ ├── __init__.py
│ │ │ │ ├── aws_api_customization.json
│ │ │ │ └── policy.py
│ │ │ ├── middleware
│ │ │ │ ├── __init__.py
│ │ │ │ └── http_header_validation_middleware.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── CONTRIBUTING.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── agent_scripts
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_manager.py
│ │ │ │ └── test_registry
│ │ │ │ ├── another_valid_script.script.md
│ │ │ │ ├── test_script.script.md
│ │ │ │ └── valid_script.script.md
│ │ │ ├── aws
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_driver.py
│ │ │ │ ├── test_pagination.py
│ │ │ │ ├── test_service.py
│ │ │ │ └── test_services.py
│ │ │ ├── common
│ │ │ │ ├── test_command.py
│ │ │ │ ├── test_config.py
│ │ │ │ ├── test_file_operations.py
│ │ │ │ ├── test_file_system_controls.py
│ │ │ │ ├── test_file_validation.py
│ │ │ │ └── test_helpers.py
│ │ │ ├── fixtures.py
│ │ │ ├── history_handler.py
│ │ │ ├── metadata
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_read_only_operations_list.py
│ │ │ ├── middleware
│ │ │ │ └── test_http_header_validation_middleware.py
│ │ │ ├── parser
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_file_path_detection.py
│ │ │ │ ├── test_lexer.py
│ │ │ │ ├── test_parser_customizations.py
│ │ │ │ └── test_parser.py
│ │ │ ├── test_security_policy.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-appsync-mcp-server
│ │ ├── .dockerignore
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_appsync_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── decorators.py
│ │ │ ├── helpers.py
│ │ │ ├── operations
│ │ │ │ ├── __init__.py
│ │ │ │ ├── create_api_cache.py
│ │ │ │ ├── create_api_key.py
│ │ │ │ ├── create_api.py
│ │ │ │ ├── create_channel_namespace.py
│ │ │ │ ├── create_datasource.py
│ │ │ │ ├── create_domain_name.py
│ │ │ │ ├── create_function.py
│ │ │ │ ├── create_graphql_api.py
│ │ │ │ ├── create_resolver.py
│ │ │ │ └── create_schema.py
│ │ │ ├── server.py
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── create_api_cache.py
│ │ │ │ ├── create_api_key.py
│ │ │ │ ├── create_api.py
│ │ │ │ ├── create_channel_namespace.py
│ │ │ │ ├── create_datasource.py
│ │ │ │ ├── create_domain_name.py
│ │ │ │ ├── create_function.py
│ │ │ │ ├── create_graphql_api.py
│ │ │ │ ├── create_resolver.py
│ │ │ │ └── create_schema.py
│ │ │ └── validators.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_all_create_tools_write_protection.py
│ │ │ ├── test_create_api_cache.py
│ │ │ ├── test_create_api_key.py
│ │ │ ├── test_create_api.py
│ │ │ ├── test_create_channel_namespace.py
│ │ │ ├── test_create_datasource_tool.py
│ │ │ ├── test_create_datasource.py
│ │ │ ├── test_create_domain_name.py
│ │ │ ├── test_create_function.py
│ │ │ ├── test_create_graphql_api.py
│ │ │ ├── test_create_resolver.py
│ │ │ ├── test_create_schema_tool.py
│ │ │ ├── test_create_schema.py
│ │ │ ├── test_helpers.py
│ │ │ ├── test_server.py
│ │ │ ├── test_validators.py
│ │ │ └── test_write_operation.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-bedrock-custom-model-import-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_bedrock_custom_model_import_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── client.py
│ │ │ ├── llm_context.py
│ │ │ ├── models.py
│ │ │ ├── prompts.py
│ │ │ ├── server.py
│ │ │ ├── services
│ │ │ │ ├── __init__.py
│ │ │ │ ├── imported_model_service.py
│ │ │ │ └── model_import_service.py
│ │ │ ├── tools
│ │ │ │ ├── create_model_import_job.py
│ │ │ │ ├── delete_imported_model.py
│ │ │ │ ├── get_imported_model.py
│ │ │ │ ├── get_model_import_job.py
│ │ │ │ ├── list_imported_models.py
│ │ │ │ └── list_model_import_jobs.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── aws.py
│ │ │ ├── config.py
│ │ │ ├── consts.py
│ │ │ └── matching.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── services
│ │ │ │ ├── test_imported_model_service.py
│ │ │ │ └── test_model_import_service.py
│ │ │ ├── test_client.py
│ │ │ ├── test_init.py
│ │ │ ├── test_llm_context.py
│ │ │ ├── test_prompts.py
│ │ │ ├── test_server.py
│ │ │ ├── tools
│ │ │ │ ├── test_create_model_import_job.py
│ │ │ │ ├── test_delete_imported_model.py
│ │ │ │ ├── test_get_imported_model.py
│ │ │ │ ├── test_get_model_import_job.py
│ │ │ │ ├── test_list_imported_models.py
│ │ │ │ └── test_list_model_import_jobs.py
│ │ │ └── utils
│ │ │ ├── test_aws.py
│ │ │ ├── test_config.py
│ │ │ └── test_matching.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-bedrock-data-automation-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_bedrock_data_automation_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── helpers.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_helpers.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-dataprocessing-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_dataprocessing_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── core
│ │ │ │ ├── __init__.py
│ │ │ │ └── glue_data_catalog
│ │ │ │ ├── __init__.py
│ │ │ │ ├── data_catalog_database_manager.py
│ │ │ │ ├── data_catalog_handler.py
│ │ │ │ └── data_catalog_table_manager.py
│ │ │ ├── handlers
│ │ │ │ ├── __init__.py
│ │ │ │ ├── athena
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── athena_data_catalog_handler.py
│ │ │ │ │ ├── athena_query_handler.py
│ │ │ │ │ └── athena_workgroup_handler.py
│ │ │ │ ├── commons
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── common_resource_handler.py
│ │ │ │ ├── emr
│ │ │ │ │ ├── emr_ec2_cluster_handler.py
│ │ │ │ │ ├── emr_ec2_instance_handler.py
│ │ │ │ │ └── emr_ec2_steps_handler.py
│ │ │ │ └── glue
│ │ │ │ ├── __init__.py
│ │ │ │ ├── crawler_handler.py
│ │ │ │ ├── data_catalog_handler.py
│ │ │ │ ├── glue_commons_handler.py
│ │ │ │ ├── glue_etl_handler.py
│ │ │ │ ├── interactive_sessions_handler.py
│ │ │ │ └── worklows_handler.py
│ │ │ ├── models
│ │ │ │ ├── __init__.py
│ │ │ │ ├── athena_models.py
│ │ │ │ ├── common_resource_models.py
│ │ │ │ ├── data_catalog_models.py
│ │ │ │ ├── emr_models.py
│ │ │ │ └── glue_models.py
│ │ │ ├── server.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── aws_helper.py
│ │ │ ├── consts.py
│ │ │ ├── logging_helper.py
│ │ │ └── sql_analyzer.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── core
│ │ │ │ ├── __init__.py
│ │ │ │ └── glue_data_catalog
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_data_catalog_database_manager.py
│ │ │ │ ├── test_data_catalog_handler.py
│ │ │ │ └── test_data_catalog_table_manager.py
│ │ │ ├── handlers
│ │ │ │ ├── __init__.py
│ │ │ │ ├── athena
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── test_athena_data_catalog_handler.py
│ │ │ │ │ ├── test_athena_query_handler.py
│ │ │ │ │ ├── test_athena_workgroup_handler.py
│ │ │ │ │ └── test_custom_tags_athena.py
│ │ │ │ ├── commons
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── test_common_resource_handler.py
│ │ │ │ ├── emr
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── test_custom_tags_emr.py
│ │ │ │ │ ├── test_emr_ec2_cluster_handler.py
│ │ │ │ │ ├── test_emr_ec2_instance_handler.py
│ │ │ │ │ └── test_emr_ec2_steps_handler.py
│ │ │ │ └── glue
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_crawler_handler.py
│ │ │ │ ├── test_custom_tags_glue.py
│ │ │ │ ├── test_data_catalog_handler.py
│ │ │ │ ├── test_glue_commons_handler.py
│ │ │ │ ├── test_glue_etl_handler.py
│ │ │ │ ├── test_glue_interactive_sessions_handler.py
│ │ │ │ └── test_glue_workflows_handler.py
│ │ │ ├── models
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_athena_models.py
│ │ │ │ ├── test_common_resource_models.py
│ │ │ │ ├── test_data_catalog_models.py
│ │ │ │ ├── test_emr_models.py
│ │ │ │ ├── test_glue_models.py
│ │ │ │ ├── test_interactive_sessions_models.py
│ │ │ │ └── test_workflows_models.py
│ │ │ ├── test_init.py
│ │ │ ├── test_server.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── test_aws_helper.py
│ │ │ ├── test_custom_tags.py
│ │ │ ├── test_logging_helper.py
│ │ │ └── test_sql_analyzer.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-diagram-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_diagram_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── diagrams_tools.py
│ │ │ ├── models.py
│ │ │ ├── scanner.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── .gitignore
│ │ │ ├── conftest.py
│ │ │ ├── README.md
│ │ │ ├── resources
│ │ │ │ ├── __init__.py
│ │ │ │ └── example_diagrams
│ │ │ │ ├── __init__.py
│ │ │ │ ├── aws_example.py
│ │ │ │ ├── flow_example.py
│ │ │ │ └── sequence_example.py
│ │ │ ├── test_diagrams.py
│ │ │ ├── test_models.py
│ │ │ ├── test_sarif_fix.py
│ │ │ ├── test_scanner.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-documentation-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_documentation_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── models.py
│ │ │ ├── server_aws_cn.py
│ │ │ ├── server_aws.py
│ │ │ ├── server_utils.py
│ │ │ ├── server.py
│ │ │ └── util.py
│ │ ├── basic-usage.gif
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── constants.py
│ │ │ ├── resources
│ │ │ │ └── lambda_sns_raw.html
│ │ │ ├── test_aws_cn_get_available_services_live.py
│ │ │ ├── test_aws_cn_read_documentation_live.py
│ │ │ ├── test_aws_read_documentation_live.py
│ │ │ ├── test_aws_recommend_live.py
│ │ │ ├── test_aws_search_live.py
│ │ │ ├── test_metadata_handling.py
│ │ │ ├── test_models.py
│ │ │ ├── test_server_aws_cn.py
│ │ │ ├── test_server_aws.py
│ │ │ ├── test_server_utils.py
│ │ │ ├── test_server.py
│ │ │ └── test_util.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-healthomics-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_healthomics_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── consts.py
│ │ │ ├── models.py
│ │ │ ├── server.py
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── helper_tools.py
│ │ │ │ ├── run_analysis.py
│ │ │ │ ├── troubleshooting.py
│ │ │ │ ├── workflow_analysis.py
│ │ │ │ ├── workflow_execution.py
│ │ │ │ ├── workflow_linting.py
│ │ │ │ └── workflow_management.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── aws_utils.py
│ │ │ ├── s3_utils.py
│ │ │ └── validation_utils.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── docs
│ │ │ └── workflow_linting.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_aws_utils.py
│ │ │ ├── test_consts.py
│ │ │ ├── test_helper_tools.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_models.py
│ │ │ ├── test_run_analysis.py
│ │ │ ├── test_s3_utils.py
│ │ │ ├── test_server.py
│ │ │ ├── test_troubleshooting.py
│ │ │ ├── test_workflow_analysis.py
│ │ │ ├── test_workflow_execution.py
│ │ │ ├── test_workflow_linting.py
│ │ │ ├── test_workflow_management.py
│ │ │ └── test_workflow_tools.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-iot-sitewise-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_iot_sitewise_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── client.py
│ │ │ ├── models
│ │ │ │ ├── computation_data_models.py
│ │ │ │ └── metadata_transfer_data_models.py
│ │ │ ├── prompts
│ │ │ │ ├── __init__.py
│ │ │ │ ├── anomaly_detection_workflow.py
│ │ │ │ ├── asset_hierarchy.py
│ │ │ │ ├── bulk_import_workflow.py
│ │ │ │ ├── data_exploration.py
│ │ │ │ └── data_ingestion.py
│ │ │ ├── server.py
│ │ │ ├── tool_metadata.py
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── sitewise_access.py
│ │ │ │ ├── sitewise_asset_models.py
│ │ │ │ ├── sitewise_assets.py
│ │ │ │ ├── sitewise_computation_models.py
│ │ │ │ ├── sitewise_data.py
│ │ │ │ ├── sitewise_executions.py
│ │ │ │ ├── sitewise_gateways.py
│ │ │ │ ├── sitewise_metadata_transfer.py
│ │ │ │ └── timestamp_tools.py
│ │ │ ├── validation_utils.py
│ │ │ └── validation.py
│ │ ├── CHANGELOG.md
│ │ ├── DEVELOPMENT.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── examples
│ │ │ └── wind_farm_example.py
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── run_server.py
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── models
│ │ │ │ ├── test_computation_data_models.py
│ │ │ │ └── test_metadata_transfer_data_models.py
│ │ │ ├── test_client.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_server.py
│ │ │ ├── test_validation_utils.py
│ │ │ ├── test_validation.py
│ │ │ └── tools
│ │ │ ├── test_sitewise_access.py
│ │ │ ├── test_sitewise_asset_models.py
│ │ │ ├── test_sitewise_assets.py
│ │ │ ├── test_sitewise_computation_models.py
│ │ │ ├── test_sitewise_data.py
│ │ │ ├── test_sitewise_executions.py
│ │ │ ├── test_sitewise_gateways.py
│ │ │ ├── test_sitewise_metadata_transfer.py
│ │ │ └── test_timestamp_tools.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-knowledge-mcp-server
│ │ └── README.md
│ ├── aws-location-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_location_server
│ │ │ ├── __init__.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_server_integration.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-msk-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_msk_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── server.py
│ │ │ └── tools
│ │ │ ├── __init__.py
│ │ │ ├── common_functions
│ │ │ │ ├── __init__.py
│ │ │ │ ├── client_manager.py
│ │ │ │ └── common_functions.py
│ │ │ ├── logs_and_telemetry
│ │ │ │ ├── __init__.py
│ │ │ │ ├── cluster_metrics_tools.py
│ │ │ │ ├── list_customer_iam_access.py
│ │ │ │ └── metric_config.py
│ │ │ ├── mutate_cluster
│ │ │ │ ├── __init__.py
│ │ │ │ ├── batch_associate_scram_secret.py
│ │ │ │ ├── batch_disassociate_scram_secret.py
│ │ │ │ ├── create_cluster_v2.py
│ │ │ │ ├── put_cluster_policy.py
│ │ │ │ ├── reboot_broker.py
│ │ │ │ ├── update_broker_count.py
│ │ │ │ ├── update_broker_storage.py
│ │ │ │ ├── update_broker_type.py
│ │ │ │ ├── update_cluster_configuration.py
│ │ │ │ ├── update_monitoring.py
│ │ │ │ └── update_security.py
│ │ │ ├── mutate_config
│ │ │ │ ├── __init__.py
│ │ │ │ ├── create_configuration.py
│ │ │ │ ├── tag_resource.py
│ │ │ │ ├── untag_resource.py
│ │ │ │ └── update_configuration.py
│ │ │ ├── mutate_vpc
│ │ │ │ ├── __init__.py
│ │ │ │ ├── create_vpc_connection.py
│ │ │ │ ├── delete_vpc_connection.py
│ │ │ │ └── reject_client_vpc_connection.py
│ │ │ ├── read_cluster
│ │ │ │ ├── __init__.py
│ │ │ │ ├── describe_cluster_operation.py
│ │ │ │ ├── describe_cluster.py
│ │ │ │ ├── get_bootstrap_brokers.py
│ │ │ │ ├── get_cluster_policy.py
│ │ │ │ ├── get_compatible_kafka_versions.py
│ │ │ │ ├── list_client_vpc_connections.py
│ │ │ │ ├── list_cluster_operations.py
│ │ │ │ ├── list_nodes.py
│ │ │ │ └── list_scram_secrets.py
│ │ │ ├── read_config
│ │ │ │ ├── __init__.py
│ │ │ │ ├── describe_configuration_revision.py
│ │ │ │ ├── describe_configuration.py
│ │ │ │ ├── list_configuration_revisions.py
│ │ │ │ └── list_tags_for_resource.py
│ │ │ ├── read_global
│ │ │ │ ├── __init__.py
│ │ │ │ ├── list_clusters.py
│ │ │ │ ├── list_configurations.py
│ │ │ │ ├── list_kafka_versions.py
│ │ │ │ └── list_vpc_connections.py
│ │ │ ├── read_vpc
│ │ │ │ ├── __init__.py
│ │ │ │ └── describe_vpc_connection.py
│ │ │ └── static_tools
│ │ │ ├── __init__.py
│ │ │ └── cluster_best_practices.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_client_manager.py
│ │ │ ├── test_cluster_metrics_tools.py
│ │ │ ├── test_common_functions.py
│ │ │ ├── test_create_cluster_v2.py
│ │ │ ├── test_create_configuration.py
│ │ │ ├── test_create_vpc_connection.py
│ │ │ ├── test_delete_vpc_connection.py
│ │ │ ├── test_describe_cluster_operation.py
│ │ │ ├── test_describe_cluster.py
│ │ │ ├── test_describe_configuration_revision.py
│ │ │ ├── test_describe_configuration.py
│ │ │ ├── test_describe_vpc_connection.py
│ │ │ ├── test_get_bootstrap_brokers.py
│ │ │ ├── test_get_cluster_policy.py
│ │ │ ├── test_get_compatible_kafka_versions.py
│ │ │ ├── test_init.py
│ │ │ ├── test_list_client_vpc_connections.py
│ │ │ ├── test_list_cluster_operations.py
│ │ │ ├── test_list_clusters.py
│ │ │ ├── test_list_configuration_revisions.py
│ │ │ ├── test_list_configurations.py
│ │ │ ├── test_list_customer_iam_access.py
│ │ │ ├── test_list_kafka_versions.py
│ │ │ ├── test_list_nodes.py
│ │ │ ├── test_list_scram_secrets.py
│ │ │ ├── test_list_tags_for_resource.py
│ │ │ ├── test_list_vpc_connections.py
│ │ │ ├── test_logs_and_telemetry.py
│ │ │ ├── test_main.py
│ │ │ ├── test_mutate_cluster_init.py
│ │ │ ├── test_mutate_cluster_success_cases.py
│ │ │ ├── test_mutate_cluster.py
│ │ │ ├── test_mutate_config_init.py
│ │ │ ├── test_mutate_vpc_init.py
│ │ │ ├── test_read_cluster_init_updated.py
│ │ │ ├── test_read_cluster_init.py
│ │ │ ├── test_read_config_init.py
│ │ │ ├── test_read_global_init.py
│ │ │ ├── test_read_vpc_init.py
│ │ │ ├── test_reject_client_vpc_connection.py
│ │ │ ├── test_server.py
│ │ │ ├── test_static_tools_init.py
│ │ │ ├── test_tag_resource.py
│ │ │ ├── test_tool_descriptions.py
│ │ │ ├── test_untag_resource.py
│ │ │ └── test_update_configuration.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-pricing-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_pricing_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── cdk_analyzer.py
│ │ │ ├── consts.py
│ │ │ ├── helpers.py
│ │ │ ├── models.py
│ │ │ ├── pricing_client.py
│ │ │ ├── pricing_transformer.py
│ │ │ ├── report_generator.py
│ │ │ ├── server.py
│ │ │ ├── static
│ │ │ │ ├── __init__.py
│ │ │ │ ├── COST_REPORT_TEMPLATE.md
│ │ │ │ └── patterns
│ │ │ │ ├── __init__.py
│ │ │ │ └── BEDROCK.md
│ │ │ └── terraform_analyzer.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_cdk_analyzer.py
│ │ │ ├── test_helpers.py
│ │ │ ├── test_pricing_client.py
│ │ │ ├── test_pricing_transformer.py
│ │ │ ├── test_report_generator.py
│ │ │ ├── test_server.py
│ │ │ └── test_terraform_analyzer.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── aws-serverless-mcp-server
│ │ ├── .pre-commit.config.yaml
│ │ ├── .python-version
│ │ ├── .secrets.baseline
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_serverless_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── models.py
│ │ │ ├── resources
│ │ │ │ ├── __init__.py
│ │ │ │ ├── deployment_details.py
│ │ │ │ ├── deployment_list.py
│ │ │ │ ├── template_details.py
│ │ │ │ └── template_list.py
│ │ │ ├── server.py
│ │ │ ├── template
│ │ │ │ ├── __init__.py
│ │ │ │ ├── registry.py
│ │ │ │ ├── renderer.py
│ │ │ │ └── templates
│ │ │ │ ├── backend.j2
│ │ │ │ ├── frontend.j2
│ │ │ │ ├── fullstack.j2
│ │ │ │ └── README.md
│ │ │ ├── templates
│ │ │ │ ├── __init__.py
│ │ │ │ └── iam_policies.py
│ │ │ ├── tools
│ │ │ │ ├── common
│ │ │ │ │ └── base_tool.py
│ │ │ │ ├── esm
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── esm_diagnosis.py
│ │ │ │ │ ├── esm_guidance.py
│ │ │ │ │ ├── esm_recommend.py
│ │ │ │ │ └── secure_esm_guidance.py
│ │ │ │ ├── guidance
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── deploy_serverless_app_help.py
│ │ │ │ │ ├── get_iac_guidance.py
│ │ │ │ │ ├── get_lambda_event_schemas.py
│ │ │ │ │ ├── get_lambda_guidance.py
│ │ │ │ │ └── get_serverless_templates.py
│ │ │ │ ├── poller
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── esm_diagnosis.py
│ │ │ │ │ ├── esm_guidance.py
│ │ │ │ │ └── esm_recommend.py
│ │ │ │ ├── sam
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── sam_build.py
│ │ │ │ │ ├── sam_deploy.py
│ │ │ │ │ ├── sam_init.py
│ │ │ │ │ ├── sam_local_invoke.py
│ │ │ │ │ └── sam_logs.py
│ │ │ │ ├── schemas
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── describe_schema.py
│ │ │ │ │ ├── list_registries.py
│ │ │ │ │ └── search_schema.py
│ │ │ │ └── webapps
│ │ │ │ ├── __init__.py
│ │ │ │ ├── configure_domain.py
│ │ │ │ ├── deploy_webapp.py
│ │ │ │ ├── get_metrics.py
│ │ │ │ ├── update_webapp_frontend.py
│ │ │ │ ├── utils
│ │ │ │ │ ├── deploy_service.py
│ │ │ │ │ ├── frontend_uploader.py
│ │ │ │ │ └── startup_script_generator.py
│ │ │ │ └── webapp_deployment_help.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── aws_client_helper.py
│ │ │ ├── cloudformation.py
│ │ │ ├── const.py
│ │ │ ├── data_scrubber.py
│ │ │ ├── deployment_manager.py
│ │ │ ├── github.py
│ │ │ └── process.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── README.md
│ │ │ ├── test_cloudformation.py
│ │ │ ├── test_configure_domain.py
│ │ │ ├── test_data_scrubber.py
│ │ │ ├── test_deploy_serverless_app_help.py
│ │ │ ├── test_deploy_service.py
│ │ │ ├── test_deploy_webapp.py
│ │ │ ├── test_deployment_details.py
│ │ │ ├── test_deployment_help.py
│ │ │ ├── test_deployment_list.py
│ │ │ ├── test_deployment_manager.py
│ │ │ ├── test_esm_diagnosis.py
│ │ │ ├── test_esm_guidance.py
│ │ │ ├── test_esm_recommend.py
│ │ │ ├── test_frontend_uploader.py
│ │ │ ├── test_get_iac_guidance.py
│ │ │ ├── test_get_lambda_event_schemas.py
│ │ │ ├── test_get_lambda_guidance.py
│ │ │ ├── test_get_metrics.py
│ │ │ ├── test_get_serverless_templates.py
│ │ │ ├── test_github.py
│ │ │ ├── test_iam_policies.py
│ │ │ ├── test_models.py
│ │ │ ├── test_process.py
│ │ │ ├── test_sam_build.py
│ │ │ ├── test_sam_deploy.py
│ │ │ ├── test_sam_init.py
│ │ │ ├── test_sam_local_invoke.py
│ │ │ ├── test_sam_logs.py
│ │ │ ├── test_schemas.py
│ │ │ ├── test_secure_esm_guidance.py
│ │ │ ├── test_server.py
│ │ │ ├── test_startup_script_generator.py
│ │ │ ├── test_template_details.py
│ │ │ ├── test_template_list.py
│ │ │ ├── test_template_registry.py
│ │ │ ├── test_template_renderer.py
│ │ │ └── test_update_webapp_frontend.py
│ │ └── uv.lock
│ ├── aws-support-mcp-server
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── aws_support_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── client.py
│ │ │ ├── consts.py
│ │ │ ├── debug_helper.py
│ │ │ ├── errors.py
│ │ │ ├── formatters.py
│ │ │ ├── models.py
│ │ │ └── server.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftests.py
│ │ │ ├── test_aws_support_mcp_server.py
│ │ │ └── test_models.py
│ │ └── uv.lock
│ ├── bedrock-kb-retrieval-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── bedrock_kb_retrieval_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── knowledgebases
│ │ │ │ ├── __init__.py
│ │ │ │ ├── clients.py
│ │ │ │ ├── discovery.py
│ │ │ │ └── retrieval.py
│ │ │ ├── models.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── run_tests.sh
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── .gitignore
│ │ │ ├── conftest.py
│ │ │ ├── README.md
│ │ │ ├── test_clients.py
│ │ │ ├── test_discovery.py
│ │ │ ├── test_env_config.py
│ │ │ ├── test_models.py
│ │ │ ├── test_retrieval.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── billing-cost-management-mcp-server
│ │ ├── __init__.py
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── billing_cost_management_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── models.py
│ │ │ ├── prompts
│ │ │ │ ├── __init__.py
│ │ │ │ ├── decorator.py
│ │ │ │ ├── graviton_migration.py
│ │ │ │ ├── README.md
│ │ │ │ ├── savings_plans.py
│ │ │ │ └── types.py
│ │ │ ├── server.py
│ │ │ ├── templates
│ │ │ │ └── recommendation_templates
│ │ │ │ ├── ebs_volume.template
│ │ │ │ ├── ec2_asg.template
│ │ │ │ ├── ec2_instance.template
│ │ │ │ ├── ecs_service.template
│ │ │ │ ├── idle.template
│ │ │ │ ├── lambda_function.template
│ │ │ │ ├── rds_database.template
│ │ │ │ ├── reserved_instances.template
│ │ │ │ └── savings_plans.template
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── aws_pricing_operations.py
│ │ │ │ ├── aws_pricing_tools.py
│ │ │ │ ├── bcm_pricing_calculator_tools.py
│ │ │ │ ├── budget_tools.py
│ │ │ │ ├── compute_optimizer_tools.py
│ │ │ │ ├── cost_anomaly_tools.py
│ │ │ │ ├── cost_comparison_tools.py
│ │ │ │ ├── cost_explorer_operations.py
│ │ │ │ ├── cost_explorer_tools.py
│ │ │ │ ├── cost_optimization_hub_helpers.py
│ │ │ │ ├── cost_optimization_hub_tools.py
│ │ │ │ ├── free_tier_usage_tools.py
│ │ │ │ ├── recommendation_details_tools.py
│ │ │ │ ├── ri_performance_tools.py
│ │ │ │ ├── sp_performance_tools.py
│ │ │ │ ├── storage_lens_tools.py
│ │ │ │ └── unified_sql_tools.py
│ │ │ └── utilities
│ │ │ ├── __init__.py
│ │ │ ├── aws_service_base.py
│ │ │ ├── constants.py
│ │ │ ├── logging_utils.py
│ │ │ └── sql_utils.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── requirements.txt
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── prompts
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_prompts.py
│ │ │ ├── README.md
│ │ │ ├── test_models.py
│ │ │ ├── test_server.py
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── fixtures.py
│ │ │ │ ├── test_aws_bcm_pricing_calculator_tools.py
│ │ │ │ ├── test_aws_pricing_tools.py
│ │ │ │ ├── test_budget_tools.py
│ │ │ │ ├── test_compute_optimizer_tools.py
│ │ │ │ ├── test_cost_anomaly_tools_enhanced.py
│ │ │ │ ├── test_cost_anomaly_tools.py
│ │ │ │ ├── test_cost_comparison_tools.py
│ │ │ │ ├── test_cost_explorer_operations.py
│ │ │ │ ├── test_cost_explorer_tools.py
│ │ │ │ ├── test_cost_optimization_hub_helpers.py
│ │ │ │ ├── test_cost_optimization_hub_tools.py
│ │ │ │ ├── test_free_tier_usage_tools_new.py
│ │ │ │ ├── test_recommendation_details_tools.py
│ │ │ │ ├── test_ri_performance_tools.py
│ │ │ │ ├── test_sp_performance_tools.py
│ │ │ │ ├── test_storage_lens_tools.py
│ │ │ │ └── test_unified_sql_tools.py
│ │ │ └── utilities
│ │ │ ├── test_aws_service_base.py
│ │ │ └── test_sql_utils.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── ccapi-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── ccapi_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── aws_client.py
│ │ │ ├── cloud_control_utils.py
│ │ │ ├── context.py
│ │ │ ├── errors.py
│ │ │ ├── iac_generator.py
│ │ │ ├── impl
│ │ │ │ ├── __init__.py
│ │ │ │ ├── tools
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── explanation.py
│ │ │ │ │ ├── infrastructure_generation.py
│ │ │ │ │ ├── resource_operations.py
│ │ │ │ │ ├── security_scanning.py
│ │ │ │ │ └── session_management.py
│ │ │ │ └── utils
│ │ │ │ ├── __init__.py
│ │ │ │ └── validation.py
│ │ │ ├── infrastructure_generator.py
│ │ │ ├── models
│ │ │ │ ├── __init__.py
│ │ │ │ └── models.py
│ │ │ ├── schema_manager.py
│ │ │ ├── server.py
│ │ │ └── static
│ │ │ └── __init__.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── run_tests.sh
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_aws_client.py
│ │ │ ├── test_checkov_install.py
│ │ │ ├── test_cloud_control_utils.py
│ │ │ ├── test_context.py
│ │ │ ├── test_errors.py
│ │ │ ├── test_explanation.py
│ │ │ ├── test_iac_generator.py
│ │ │ ├── test_infrastructure_generation.py
│ │ │ ├── test_infrastructure_generator.py
│ │ │ ├── test_models.py
│ │ │ ├── test_resource_operations.py
│ │ │ ├── test_schema_manager.py
│ │ │ ├── test_security_scanning.py
│ │ │ ├── test_server.py
│ │ │ ├── test_session_management.py
│ │ │ └── test_validation.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── cdk-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── cdk_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── core
│ │ │ │ ├── __init__.py
│ │ │ │ ├── resources.py
│ │ │ │ ├── search_utils.py
│ │ │ │ ├── server.py
│ │ │ │ └── tools.py
│ │ │ ├── data
│ │ │ │ ├── __init__.py
│ │ │ │ ├── cdk_nag_parser.py
│ │ │ │ ├── construct_descriptions.py
│ │ │ │ ├── genai_cdk_loader.py
│ │ │ │ ├── lambda_layer_parser.py
│ │ │ │ ├── lambda_powertools_loader.py
│ │ │ │ ├── schema_generator.py
│ │ │ │ └── solutions_constructs_parser.py
│ │ │ ├── server.py
│ │ │ └── static
│ │ │ ├── __init__.py
│ │ │ ├── CDK_GENERAL_GUIDANCE.md
│ │ │ ├── CDK_NAG_GUIDANCE.md
│ │ │ └── lambda_powertools
│ │ │ ├── bedrock.md
│ │ │ ├── cdk.md
│ │ │ ├── dependencies.md
│ │ │ ├── index.md
│ │ │ ├── insights.md
│ │ │ ├── logging.md
│ │ │ ├── metrics.md
│ │ │ └── tracing.md
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── core
│ │ │ │ ├── test_resources_enhanced.py
│ │ │ │ ├── test_resources.py
│ │ │ │ ├── test_search_utils.py
│ │ │ │ ├── test_server.py
│ │ │ │ └── test_tools.py
│ │ │ └── data
│ │ │ ├── test_cdk_nag_parser.py
│ │ │ ├── test_genai_cdk_loader.py
│ │ │ ├── test_lambda_powertools_loader.py
│ │ │ ├── test_schema_generator.py
│ │ │ └── test_solutions_constructs_parser.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── cfn-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── cfn_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── aws_client.py
│ │ │ ├── cloud_control_utils.py
│ │ │ ├── context.py
│ │ │ ├── errors.py
│ │ │ ├── iac_generator.py
│ │ │ ├── schema_manager.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── run_tests.sh
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_aws_client.py
│ │ │ ├── test_cloud_control_utils.py
│ │ │ ├── test_errors.py
│ │ │ ├── test_iac_generator.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_schema_manager.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── cloudtrail-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── cloudtrail_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── common.py
│ │ │ ├── models.py
│ │ │ ├── server.py
│ │ │ └── tools.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_models.py
│ │ │ ├── test_server.py
│ │ │ └── test_tools.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── cloudwatch-applicationsignals-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── cloudwatch_applicationsignals_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── audit_presentation_utils.py
│ │ │ ├── audit_utils.py
│ │ │ ├── aws_clients.py
│ │ │ ├── canary_utils.py
│ │ │ ├── server.py
│ │ │ ├── service_audit_utils.py
│ │ │ ├── service_tools.py
│ │ │ ├── sli_report_client.py
│ │ │ ├── slo_tools.py
│ │ │ ├── trace_tools.py
│ │ │ └── utils.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_audit_presentation_utils.py
│ │ │ ├── test_audit_utils.py
│ │ │ ├── test_aws_profile.py
│ │ │ ├── test_canary_utils.py
│ │ │ ├── test_initialization.py
│ │ │ ├── test_server_audit_functions.py
│ │ │ ├── test_server_audit_tools.py
│ │ │ ├── test_server.py
│ │ │ ├── test_service_audit_utils.py
│ │ │ ├── test_service_tools_operations.py
│ │ │ ├── test_sli_report_client.py
│ │ │ ├── test_slo_tools.py
│ │ │ └── test_utils.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── cloudwatch-appsignals-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── cloudwatch_appsignals_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── audit_presentation_utils.py
│ │ │ ├── audit_utils.py
│ │ │ ├── aws_clients.py
│ │ │ ├── canary_utils.py
│ │ │ ├── enablement_guides
│ │ │ │ └── templates
│ │ │ │ └── ec2
│ │ │ │ └── ec2-python-enablement.md
│ │ │ ├── enablement_tools.py
│ │ │ ├── server.py
│ │ │ ├── service_audit_utils.py
│ │ │ ├── service_tools.py
│ │ │ ├── sli_report_client.py
│ │ │ ├── slo_tools.py
│ │ │ ├── trace_tools.py
│ │ │ └── utils.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_audit_presentation_utils.py
│ │ │ ├── test_audit_utils.py
│ │ │ ├── test_aws_profile.py
│ │ │ ├── test_canary_utils.py
│ │ │ ├── test_enablement_tools.py
│ │ │ ├── test_initialization.py
│ │ │ ├── test_server_audit_functions.py
│ │ │ ├── test_server_audit_tools.py
│ │ │ ├── test_server.py
│ │ │ ├── test_service_audit_utils.py
│ │ │ ├── test_service_tools_operations.py
│ │ │ ├── test_sli_report_client.py
│ │ │ ├── test_slo_tools.py
│ │ │ └── test_utils.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── cloudwatch-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── cloudwatch_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── cloudwatch_alarms
│ │ │ │ ├── models.py
│ │ │ │ └── tools.py
│ │ │ ├── cloudwatch_logs
│ │ │ │ ├── models.py
│ │ │ │ └── tools.py
│ │ │ ├── cloudwatch_metrics
│ │ │ │ ├── cloudformation_template_generator.py
│ │ │ │ ├── constants.py
│ │ │ │ ├── data
│ │ │ │ │ └── metric_metadata.json
│ │ │ │ ├── metric_analyzer.py
│ │ │ │ ├── metric_data_decomposer.py
│ │ │ │ ├── models.py
│ │ │ │ └── tools.py
│ │ │ ├── common.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── cloudwatch_alarms
│ │ │ │ ├── test_active_alarms.py
│ │ │ │ ├── test_alarm_history_integration.py
│ │ │ │ ├── test_alarm_history.py
│ │ │ │ └── test_alarms_error_handling.py
│ │ │ ├── cloudwatch_logs
│ │ │ │ ├── test_logs_error_handling.py
│ │ │ │ ├── test_logs_models.py
│ │ │ │ └── test_logs_server.py
│ │ │ ├── cloudwatch_metrics
│ │ │ │ ├── test_analyze_metric.py
│ │ │ │ ├── test_cloudformation_template_generator.py
│ │ │ │ ├── test_decomposer_trend.py
│ │ │ │ ├── test_metric_analyzer.py
│ │ │ │ ├── test_metrics_error_handling.py
│ │ │ │ ├── test_metrics_models.py
│ │ │ │ ├── test_metrics_server.py
│ │ │ │ ├── test_seasonal_detector.py
│ │ │ │ ├── test_seasonality_enum.py
│ │ │ │ ├── test_utils.py
│ │ │ │ └── test_validation_error.py
│ │ │ ├── test_common_and_server.py
│ │ │ ├── test_init.py
│ │ │ └── test_main.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── code-doc-gen-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── code_doc_gen_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── server.py
│ │ │ └── utils
│ │ │ ├── doc_generator.py
│ │ │ ├── models.py
│ │ │ ├── repomix_manager.py
│ │ │ └── templates.py
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_doc_generator_edge_cases.py
│ │ │ ├── test_doc_generator.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_repomix_manager_scenarios.py
│ │ │ ├── test_repomix_manager.py
│ │ │ ├── test_repomix_statistics.py
│ │ │ ├── test_server_extended.py
│ │ │ ├── test_server.py
│ │ │ └── test_templates.py
│ │ └── uv.lock
│ ├── core-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── core_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── server.py
│ │ │ └── static
│ │ │ ├── __init__.py
│ │ │ └── PROMPT_UNDERSTANDING.md
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── README.md
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_response_types.py
│ │ │ ├── test_server.py
│ │ │ └── test_static.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── cost-explorer-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── cost_explorer_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── comparison_handler.py
│ │ │ ├── constants.py
│ │ │ ├── cost_usage_handler.py
│ │ │ ├── forecasting_handler.py
│ │ │ ├── helpers.py
│ │ │ ├── metadata_handler.py
│ │ │ ├── models.py
│ │ │ ├── server.py
│ │ │ └── utility_handler.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_comparison_handler.py
│ │ │ ├── test_cost_usage_handler.py
│ │ │ ├── test_forecasting_handler.py
│ │ │ ├── test_helpers.py
│ │ │ ├── test_metadata_handler.py
│ │ │ ├── test_models.py
│ │ │ ├── test_server.py
│ │ │ └── test_utility_handler.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── documentdb-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ └── documentdb_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── analytic_tools.py
│ │ │ ├── config.py
│ │ │ ├── connection_tools.py
│ │ │ ├── db_management_tools.py
│ │ │ ├── query_tools.py
│ │ │ ├── server.py
│ │ │ └── write_tools.py
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_analytic_tools.py
│ │ │ ├── test_connection_tools.py
│ │ │ ├── test_db_management_tools.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_query_tools.py
│ │ │ └── test_write_tools.py
│ │ └── uv.lock
│ ├── dynamodb-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── dynamodb_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── common.py
│ │ │ ├── database_analysis_queries.py
│ │ │ ├── database_analyzers.py
│ │ │ ├── markdown_formatter.py
│ │ │ ├── prompts
│ │ │ │ └── dynamodb_architect.md
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── evals
│ │ │ │ ├── dynamic_evaluators.py
│ │ │ │ ├── evaluation_registry.py
│ │ │ │ ├── logging_config.py
│ │ │ │ ├── multiturn_evaluator.py
│ │ │ │ ├── README.md
│ │ │ │ ├── scenarios.py
│ │ │ │ └── test_dspy_evals.py
│ │ │ ├── test_dynamodb_server.py
│ │ │ ├── test_markdown_formatter.py
│ │ │ └── test_source_db_integration.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── ecs-mcp-server
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── ecs_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── api
│ │ │ │ ├── __init__.py
│ │ │ │ ├── containerize.py
│ │ │ │ ├── delete.py
│ │ │ │ ├── ecs_troubleshooting.py
│ │ │ │ ├── infrastructure.py
│ │ │ │ ├── resource_management.py
│ │ │ │ ├── status.py
│ │ │ │ └── troubleshooting_tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── detect_image_pull_failures.py
│ │ │ │ ├── fetch_cloudformation_status.py
│ │ │ │ ├── fetch_network_configuration.py
│ │ │ │ ├── fetch_service_events.py
│ │ │ │ ├── fetch_task_failures.py
│ │ │ │ ├── fetch_task_logs.py
│ │ │ │ ├── get_ecs_troubleshooting_guidance.py
│ │ │ │ └── utils.py
│ │ │ ├── main.py
│ │ │ ├── modules
│ │ │ │ ├── __init__.py
│ │ │ │ ├── aws_knowledge_proxy.py
│ │ │ │ ├── containerize.py
│ │ │ │ ├── delete.py
│ │ │ │ ├── deployment_status.py
│ │ │ │ ├── infrastructure.py
│ │ │ │ ├── resource_management.py
│ │ │ │ └── troubleshooting.py
│ │ │ ├── templates
│ │ │ │ ├── ecr_infrastructure.json
│ │ │ │ └── ecs_infrastructure.json
│ │ │ └── utils
│ │ │ ├── arn_parser.py
│ │ │ ├── aws.py
│ │ │ ├── config.py
│ │ │ ├── docker.py
│ │ │ ├── security.py
│ │ │ ├── templates.py
│ │ │ └── time_utils.py
│ │ ├── DEVELOPMENT.md
│ │ ├── pyproject.toml
│ │ ├── pyrightconfig.json
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── integ
│ │ │ │ └── mcp-inspector
│ │ │ │ ├── .gitignore
│ │ │ │ ├── README.md
│ │ │ │ ├── run-tests.sh
│ │ │ │ └── scenarios
│ │ │ │ ├── 01_comprehensive_troubleshooting
│ │ │ │ │ ├── 01_create.sh
│ │ │ │ │ ├── 02_validate.sh
│ │ │ │ │ ├── 03_cleanup.sh
│ │ │ │ │ ├── description.txt
│ │ │ │ │ └── utils
│ │ │ │ │ ├── mcp_helpers.sh
│ │ │ │ │ └── validation_helpers.sh
│ │ │ │ └── 02_test_knowledge_proxy_tools
│ │ │ │ ├── 01_create.sh
│ │ │ │ ├── 02_validate.sh
│ │ │ │ ├── 03_cleanup.sh
│ │ │ │ ├── description.txt
│ │ │ │ └── utils
│ │ │ │ ├── knowledge_validation_helpers.sh
│ │ │ │ └── mcp_knowledge_helpers.sh
│ │ │ ├── llm_testing
│ │ │ │ ├── invalid_cfn_template.yaml
│ │ │ │ ├── README.md
│ │ │ │ ├── run_tests.sh
│ │ │ │ ├── scenarios
│ │ │ │ │ ├── 01_cloudformation_failure
│ │ │ │ │ │ ├── 01_create.sh
│ │ │ │ │ │ ├── 02_validate.sh
│ │ │ │ │ │ ├── 03_prompts.txt
│ │ │ │ │ │ ├── 04_evaluation.md
│ │ │ │ │ │ ├── 05_cleanup.sh
│ │ │ │ │ │ └── description.txt
│ │ │ │ │ ├── 02_service_failure
│ │ │ │ │ │ ├── 01_create.sh
│ │ │ │ │ │ ├── 02_validate.sh
│ │ │ │ │ │ ├── 03_prompts.txt
│ │ │ │ │ │ ├── 04_evaluation.md
│ │ │ │ │ │ ├── 05_cleanup.sh
│ │ │ │ │ │ └── description.txt
│ │ │ │ │ ├── 03_task_exit_failure
│ │ │ │ │ │ ├── 01_create.sh
│ │ │ │ │ │ ├── 02_validate.sh
│ │ │ │ │ │ ├── 03_prompts.txt
│ │ │ │ │ │ ├── 04_evaluation.md
│ │ │ │ │ │ ├── 05_cleanup.sh
│ │ │ │ │ │ └── description.txt
│ │ │ │ │ ├── 04_network_configuration_failure
│ │ │ │ │ │ ├── 01_create.sh
│ │ │ │ │ │ ├── 02_validate.sh
│ │ │ │ │ │ ├── 03_prompts.txt
│ │ │ │ │ │ ├── 05_cleanup.sh
│ │ │ │ │ │ └── description.txt
│ │ │ │ │ ├── 05_resource_constraint_failure
│ │ │ │ │ │ ├── 01_create.sh
│ │ │ │ │ │ ├── 02_validate.sh
│ │ │ │ │ │ ├── 03_prompts.txt
│ │ │ │ │ │ ├── 05_cleanup.sh
│ │ │ │ │ │ └── description.txt
│ │ │ │ │ └── 06_load_balancer_failure
│ │ │ │ │ ├── 01_create.sh
│ │ │ │ │ ├── 02_validate.sh
│ │ │ │ │ ├── 03_prompts.txt
│ │ │ │ │ ├── 05_cleanup.sh
│ │ │ │ │ └── description.txt
│ │ │ │ ├── SCRIPT_IMPROVEMENTS.md
│ │ │ │ └── utils
│ │ │ │ ├── aws_helpers.sh
│ │ │ │ └── evaluation_template.md
│ │ │ └── unit
│ │ │ ├── __init__.py
│ │ │ ├── api
│ │ │ │ ├── conftest.py
│ │ │ │ ├── test_delete_api.py
│ │ │ │ ├── test_ecs_troubleshooting.py
│ │ │ │ ├── test_resource_management_api.py
│ │ │ │ └── troubleshooting_tools
│ │ │ │ └── test_fetch_network_configuration.py
│ │ │ ├── conftest.py
│ │ │ ├── modules
│ │ │ │ ├── test_aws_knowledge_proxy.py
│ │ │ │ └── test_resource_management_module.py
│ │ │ ├── test_aws_role_utils.py
│ │ │ ├── test_aws_utils.py
│ │ │ ├── test_containerize.py
│ │ │ ├── test_delete.py
│ │ │ ├── test_docker_utils.py
│ │ │ ├── test_docker_with_role.py
│ │ │ ├── test_image_pull_failure_extended.py
│ │ │ ├── test_image_pull_failure.py
│ │ │ ├── test_infrastructure_role.py
│ │ │ ├── test_infrastructure.py
│ │ │ ├── test_integration.py
│ │ │ ├── test_main.py
│ │ │ ├── test_resource_management_api_operation.py
│ │ │ ├── test_resource_management_tool.py
│ │ │ ├── test_resource_management.py
│ │ │ ├── test_security_integration.py
│ │ │ ├── test_status_pytest.py
│ │ │ ├── test_status.py
│ │ │ ├── troubleshooting_tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── conftest.py
│ │ │ │ ├── test_detect_image_pull_failures.py
│ │ │ │ ├── test_fetch_cloudformation_status.py
│ │ │ │ ├── test_fetch_service_events.py
│ │ │ │ ├── test_fetch_task_failures.py
│ │ │ │ ├── test_fetch_task_logs.py
│ │ │ │ ├── test_get_ecs_troubleshooting_guidance.py
│ │ │ │ ├── test_is_ecr_image_security.py
│ │ │ │ └── test_utils.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── async_test_utils.py
│ │ │ ├── test_arn_parser.py
│ │ │ ├── test_config.py
│ │ │ ├── test_docker.py
│ │ │ ├── test_response_sanitization.py
│ │ │ ├── test_security_extended.py
│ │ │ ├── test_security.py
│ │ │ ├── test_templates.py
│ │ │ └── test_time_utils.py
│ │ └── uv.lock
│ ├── eks-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── eks_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── aws_helper.py
│ │ │ ├── cloudwatch_handler.py
│ │ │ ├── cloudwatch_metrics_guidance_handler.py
│ │ │ ├── consts.py
│ │ │ ├── data
│ │ │ │ └── eks_cloudwatch_metrics_guidance.json
│ │ │ ├── eks_kb_handler.py
│ │ │ ├── eks_stack_handler.py
│ │ │ ├── iam_handler.py
│ │ │ ├── insights_handler.py
│ │ │ ├── k8s_apis.py
│ │ │ ├── k8s_client_cache.py
│ │ │ ├── k8s_handler.py
│ │ │ ├── logging_helper.py
│ │ │ ├── models.py
│ │ │ ├── scripts
│ │ │ │ └── update_eks_cloudwatch_metrics_guidance.py
│ │ │ ├── server.py
│ │ │ ├── templates
│ │ │ │ ├── eks-templates
│ │ │ │ │ └── eks-with-vpc.yaml
│ │ │ │ └── k8s-templates
│ │ │ │ ├── deployment.yaml
│ │ │ │ └── service.yaml
│ │ │ └── vpc_config_handler.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_aws_helper.py
│ │ │ ├── test_cloudwatch_handler.py
│ │ │ ├── test_cloudwatch_metrics_guidance_handler.py
│ │ │ ├── test_eks_kb_handler.py
│ │ │ ├── test_eks_stack_handler.py
│ │ │ ├── test_iam_handler.py
│ │ │ ├── test_init.py
│ │ │ ├── test_insights_handler.py
│ │ │ ├── test_k8s_apis.py
│ │ │ ├── test_k8s_client_cache.py
│ │ │ ├── test_k8s_handler.py
│ │ │ ├── test_logging_helper.py
│ │ │ ├── test_main.py
│ │ │ ├── test_models.py
│ │ │ ├── test_server.py
│ │ │ └── test_vpc_config_handler.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── elasticache-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── elasticache_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── common
│ │ │ │ ├── __init__.py
│ │ │ │ ├── connection.py
│ │ │ │ ├── decorators.py
│ │ │ │ └── server.py
│ │ │ ├── context.py
│ │ │ ├── main.py
│ │ │ └── tools
│ │ │ ├── __init__.py
│ │ │ ├── cc
│ │ │ │ ├── __init__.py
│ │ │ │ ├── connect.py
│ │ │ │ ├── create.py
│ │ │ │ ├── delete.py
│ │ │ │ ├── describe.py
│ │ │ │ ├── modify.py
│ │ │ │ ├── parsers.py
│ │ │ │ └── processors.py
│ │ │ ├── ce
│ │ │ │ ├── __init__.py
│ │ │ │ └── get_cost_and_usage.py
│ │ │ ├── cw
│ │ │ │ ├── __init__.py
│ │ │ │ └── get_metric_statistics.py
│ │ │ ├── cwlogs
│ │ │ │ ├── __init__.py
│ │ │ │ ├── create_log_group.py
│ │ │ │ ├── describe_log_groups.py
│ │ │ │ ├── describe_log_streams.py
│ │ │ │ ├── filter_log_events.py
│ │ │ │ └── get_log_events.py
│ │ │ ├── firehose
│ │ │ │ ├── __init__.py
│ │ │ │ └── list_delivery_streams.py
│ │ │ ├── misc
│ │ │ │ ├── __init__.py
│ │ │ │ ├── batch_apply_update_action.py
│ │ │ │ ├── batch_stop_update_action.py
│ │ │ │ ├── describe_cache_engine_versions.py
│ │ │ │ ├── describe_engine_default_parameters.py
│ │ │ │ ├── describe_events.py
│ │ │ │ └── describe_service_updates.py
│ │ │ ├── rg
│ │ │ │ ├── __init__.py
│ │ │ │ ├── complete_migration.py
│ │ │ │ ├── connect.py
│ │ │ │ ├── create.py
│ │ │ │ ├── delete.py
│ │ │ │ ├── describe.py
│ │ │ │ ├── modify.py
│ │ │ │ ├── parsers.py
│ │ │ │ ├── processors.py
│ │ │ │ ├── start_migration.py
│ │ │ │ └── test_migration.py
│ │ │ └── serverless
│ │ │ ├── __init__.py
│ │ │ ├── connect.py
│ │ │ ├── create.py
│ │ │ ├── delete.py
│ │ │ ├── describe.py
│ │ │ ├── models.py
│ │ │ └── modify.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_connection.py
│ │ │ ├── test_decorators.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ └── tools
│ │ │ ├── cc
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_connect_additional.py
│ │ │ │ ├── test_connect_coverage_additional.py
│ │ │ │ ├── test_connect_coverage.py
│ │ │ │ ├── test_connect.py
│ │ │ │ ├── test_create_additional.py
│ │ │ │ ├── test_create.py
│ │ │ │ ├── test_delete.py
│ │ │ │ ├── test_describe.py
│ │ │ │ ├── test_modify.py
│ │ │ │ ├── test_parsers.py
│ │ │ │ └── test_processors.py
│ │ │ ├── ce
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_get_cost_and_usage.py
│ │ │ ├── cw
│ │ │ │ └── test_get_metric_statistics.py
│ │ │ ├── cwlogs
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_create_log_group.py
│ │ │ │ ├── test_describe_log_groups.py
│ │ │ │ ├── test_describe_log_streams.py
│ │ │ │ ├── test_filter_log_events.py
│ │ │ │ └── test_get_log_events.py
│ │ │ ├── firehose
│ │ │ │ └── test_list_delivery_streams.py
│ │ │ ├── misc
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_batch_apply_update_action.py
│ │ │ │ ├── test_batch_stop_update_action.py
│ │ │ │ ├── test_describe_cache_engine_versions.py
│ │ │ │ ├── test_describe_engine_default_parameters.py
│ │ │ │ ├── test_describe_events.py
│ │ │ │ └── test_describe_service_updates.py
│ │ │ ├── rg
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_complete_migration.py
│ │ │ │ ├── test_connect_additional.py
│ │ │ │ ├── test_connect_coverage_additional.py
│ │ │ │ ├── test_connect_optional_fields.py
│ │ │ │ ├── test_connect_partial_coverage.py
│ │ │ │ ├── test_connect.py
│ │ │ │ ├── test_create.py
│ │ │ │ ├── test_delete.py
│ │ │ │ ├── test_describe.py
│ │ │ │ ├── test_modify.py
│ │ │ │ ├── test_parsers.py
│ │ │ │ ├── test_processors.py
│ │ │ │ ├── test_start_migration.py
│ │ │ │ └── test_test_migration.py
│ │ │ └── serverless
│ │ │ ├── test_connect_additional.py
│ │ │ ├── test_connect_coverage_additional.py
│ │ │ ├── test_connect_optional_fields.py
│ │ │ ├── test_connect.py
│ │ │ ├── test_create.py
│ │ │ ├── test_delete.py
│ │ │ ├── test_describe.py
│ │ │ └── test_modify.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── finch-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── finch_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── consts.py
│ │ │ ├── models.py
│ │ │ ├── server.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── build.py
│ │ │ ├── common.py
│ │ │ ├── ecr.py
│ │ │ ├── push.py
│ │ │ └── vm.py
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_cli_flags.py
│ │ │ ├── test_logging_configuration.py
│ │ │ ├── test_server.py
│ │ │ ├── test_utils_build.py
│ │ │ ├── test_utils_common.py
│ │ │ ├── test_utils_ecr.py
│ │ │ ├── test_utils_push.py
│ │ │ └── test_utils_vm.py
│ │ └── uv.lock
│ ├── frontend-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── frontend_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── server.py
│ │ │ ├── static
│ │ │ │ └── react
│ │ │ │ ├── essential-knowledge.md
│ │ │ │ └── troubleshooting.md
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ └── file_utils.py
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_file_utils.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ └── test_server.py
│ │ └── uv.lock
│ ├── git-repo-research-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── git_repo_research_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── defaults.py
│ │ │ ├── embeddings.py
│ │ │ ├── github_search.py
│ │ │ ├── indexer.py
│ │ │ ├── models.py
│ │ │ ├── repository.py
│ │ │ ├── search.py
│ │ │ ├── server.py
│ │ │ └── utils.py
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── run_tests.sh
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_errors_repository.py
│ │ │ ├── test_github_search_edge_cases.py
│ │ │ ├── test_graphql_github_search.py
│ │ │ ├── test_local_repository.py
│ │ │ ├── test_repository_utils.py
│ │ │ ├── test_rest_github_search.py
│ │ │ ├── test_search.py
│ │ │ ├── test_server.py
│ │ │ └── test_url_repository.py
│ │ └── uv.lock
│ ├── healthlake-mcp-server
│ │ ├── .dockerignore
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── healthlake_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── fhir_operations.py
│ │ │ ├── main.py
│ │ │ ├── models.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── CONTRIBUTING.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── examples
│ │ │ ├── mcp_config.json
│ │ │ └── README.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_fhir_client_comprehensive.py
│ │ │ ├── test_fhir_error_scenarios.py
│ │ │ ├── test_fhir_operations.py
│ │ │ ├── test_integration_mock_based.py
│ │ │ ├── test_main_edge_cases.py
│ │ │ ├── test_main.py
│ │ │ ├── test_mcp_integration_coverage.py
│ │ │ ├── test_models_edge_cases.py
│ │ │ ├── test_models.py
│ │ │ ├── test_readonly_mode.py
│ │ │ ├── test_server_core.py
│ │ │ ├── test_server_error_handling.py
│ │ │ ├── test_server_mcp_handlers.py
│ │ │ ├── test_server_toolhandler.py
│ │ │ └── test_server_validation.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── iam-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── iam_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── aws_client.py
│ │ │ ├── context.py
│ │ │ ├── errors.py
│ │ │ ├── models.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── DESIGN_COMPLIANCE.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── examples
│ │ │ ├── get_policy_document_example.py
│ │ │ └── inline_policy_demo.py
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── run_tests.sh
│ │ ├── tests
│ │ │ ├── test_context.py
│ │ │ ├── test_errors.py
│ │ │ ├── test_inline_policies.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── lambda-tool-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── lambda_tool_mcp_server
│ │ │ ├── __init__.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── examples
│ │ │ ├── README.md
│ │ │ └── sample_functions
│ │ │ ├── customer-create
│ │ │ │ └── app.py
│ │ │ ├── customer-id-from-email
│ │ │ │ └── app.py
│ │ │ ├── customer-info-from-id
│ │ │ │ └── app.py
│ │ │ └── template.yml
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── .gitignore
│ │ │ ├── conftest.py
│ │ │ ├── README.md
│ │ │ ├── test_format_lambda_response.py
│ │ │ ├── test_integration_coverage.py
│ │ │ ├── test_integration.py
│ │ │ ├── test_register_lambda_functions.py
│ │ │ ├── test_schema_integration.py
│ │ │ ├── test_server_coverage_additional.py
│ │ │ ├── test_server_coverage.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── mcp-lambda-handler
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ └── mcp_lambda_handler
│ │ │ ├── __init__.py
│ │ │ ├── mcp_lambda_handler.py
│ │ │ ├── session.py
│ │ │ └── types.py
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ └── test_lambda_handler.py
│ │ └── uv.lock
│ ├── memcached-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── memcached_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── common
│ │ │ │ ├── config.py
│ │ │ │ ├── connection.py
│ │ │ │ └── server.py
│ │ │ ├── context.py
│ │ │ ├── main.py
│ │ │ └── tools
│ │ │ └── cache.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── ELASTICACHECONNECT.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_cache_readonly.py
│ │ │ ├── test_cache.py
│ │ │ ├── test_connection.py
│ │ │ ├── test_init.py
│ │ │ └── test_main.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── mysql-mcp-server
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── mysql_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── connection
│ │ │ │ ├── __init__.py
│ │ │ │ ├── abstract_db_connection.py
│ │ │ │ ├── asyncmy_pool_connection.py
│ │ │ │ ├── db_connection_singleton.py
│ │ │ │ └── rds_data_api_connection.py
│ │ │ ├── mutable_sql_detector.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_abstract_db_connection.py
│ │ │ ├── test_asyncmy_pool_connection.py
│ │ │ ├── test_db_connection_singleton.py
│ │ │ ├── test_rds_data_api_connection.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── nova-canvas-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── nova_canvas_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── consts.py
│ │ │ ├── models.py
│ │ │ ├── novacanvas.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── .gitignore
│ │ │ ├── conftest.py
│ │ │ ├── README.md
│ │ │ ├── test_models.py
│ │ │ ├── test_novacanvas.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── openapi-mcp-server
│ │ ├── .coveragerc
│ │ ├── .dockerignore
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── AUTHENTICATION.md
│ │ ├── AWS_BEST_PRACTICES.md
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── openapi_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── api
│ │ │ │ ├── __init__.py
│ │ │ │ └── config.py
│ │ │ ├── auth
│ │ │ │ ├── __init__.py
│ │ │ │ ├── api_key_auth.py
│ │ │ │ ├── auth_cache.py
│ │ │ │ ├── auth_errors.py
│ │ │ │ ├── auth_factory.py
│ │ │ │ ├── auth_protocol.py
│ │ │ │ ├── auth_provider.py
│ │ │ │ ├── base_auth.py
│ │ │ │ ├── basic_auth.py
│ │ │ │ ├── bearer_auth.py
│ │ │ │ ├── cognito_auth.py
│ │ │ │ └── register.py
│ │ │ ├── patch
│ │ │ │ └── __init__.py
│ │ │ ├── prompts
│ │ │ │ ├── __init__.py
│ │ │ │ ├── generators
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── operation_prompts.py
│ │ │ │ │ └── workflow_prompts.py
│ │ │ │ ├── models.py
│ │ │ │ └── prompt_manager.py
│ │ │ ├── server.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── cache_provider.py
│ │ │ ├── config.py
│ │ │ ├── error_handler.py
│ │ │ ├── http_client.py
│ │ │ ├── metrics_provider.py
│ │ │ ├── openapi_validator.py
│ │ │ └── openapi.py
│ │ ├── CHANGELOG.md
│ │ ├── DEPLOYMENT.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── OBSERVABILITY.md
│ │ ├── pyproject.toml
│ │ ├── pyrightconfig.json
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── api
│ │ │ │ └── test_config.py
│ │ │ ├── auth
│ │ │ │ ├── test_api_key_auth.py
│ │ │ │ ├── test_auth_cache.py
│ │ │ │ ├── test_auth_errors.py
│ │ │ │ ├── test_auth_factory_caching.py
│ │ │ │ ├── test_auth_factory_coverage.py
│ │ │ │ ├── test_auth_factory.py
│ │ │ │ ├── test_auth_protocol_additional.py
│ │ │ │ ├── test_auth_protocol_boost.py
│ │ │ │ ├── test_auth_protocol_coverage.py
│ │ │ │ ├── test_auth_protocol_extended.py
│ │ │ │ ├── test_auth_protocol_improved.py
│ │ │ │ ├── test_auth_protocol.py
│ │ │ │ ├── test_auth_provider_additional.py
│ │ │ │ ├── test_base_auth_coverage.py
│ │ │ │ ├── test_base_auth.py
│ │ │ │ ├── test_basic_auth.py
│ │ │ │ ├── test_bearer_auth.py
│ │ │ │ ├── test_cognito_auth_additional_coverage.py
│ │ │ │ ├── test_cognito_auth_boost_coverage.py
│ │ │ │ ├── test_cognito_auth_client_credentials.py
│ │ │ │ ├── test_cognito_auth_coverage_boost.py
│ │ │ │ ├── test_cognito_auth_exceptions.py
│ │ │ │ ├── test_cognito_auth.py
│ │ │ │ ├── test_register_coverage.py
│ │ │ │ └── test_register.py
│ │ │ ├── prompts
│ │ │ │ ├── standalone
│ │ │ │ │ ├── test_operation_prompt.py
│ │ │ │ │ ├── test_prompt_arguments.py
│ │ │ │ │ └── test_secure_operation_prompt.py
│ │ │ │ ├── test_mcp_prompt_manager_integration.py
│ │ │ │ ├── test_mcp_prompt_manager.py
│ │ │ │ ├── test_models_dict_method.py
│ │ │ │ ├── test_operation_prompts_extended.py
│ │ │ │ ├── test_prompt_manager_additional.py
│ │ │ │ ├── test_prompt_manager_comprehensive.py
│ │ │ │ ├── test_prompt_manager_coverage.py
│ │ │ │ └── test_prompt_registration.py
│ │ │ ├── README.md
│ │ │ ├── test_api_name.py
│ │ │ ├── test_cache_coverage_89.py
│ │ │ ├── test_client.py
│ │ │ ├── test_coverage_boost.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main_extended.py
│ │ │ ├── test_main.py
│ │ │ ├── test_openapi_coverage_89.py
│ │ │ ├── test_server_auth_errors.py
│ │ │ ├── test_server_coverage_boost_2.py
│ │ │ ├── test_server_coverage_boost.py
│ │ │ ├── test_server_exception_handling.py
│ │ │ ├── test_server_extended.py
│ │ │ ├── test_server_httpx_version.py
│ │ │ ├── test_server_part1.py
│ │ │ ├── test_server_route_logging.py
│ │ │ ├── test_server_signal_handlers.py
│ │ │ ├── test_server.py
│ │ │ └── utils
│ │ │ ├── test_cache_provider.py
│ │ │ ├── test_error_handler_boost.py
│ │ │ ├── test_error_handler_extended.py
│ │ │ ├── test_error_handler_fix.py
│ │ │ ├── test_error_handler.py
│ │ │ ├── test_http_client_comprehensive.py
│ │ │ ├── test_http_client_extended.py
│ │ │ ├── test_http_client_extended2.py
│ │ │ ├── test_http_client_import_error.py
│ │ │ ├── test_http_client.py
│ │ │ ├── test_metrics_provider_decorators.py
│ │ │ ├── test_metrics_provider_extended2.py
│ │ │ ├── test_metrics_provider_prometheus.py
│ │ │ ├── test_metrics_provider.py
│ │ │ ├── test_openapi_validator.py
│ │ │ └── test_openapi.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── postgres-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── postgres_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── connection
│ │ │ │ ├── __init__.py
│ │ │ │ ├── abstract_db_connection.py
│ │ │ │ ├── db_connection_singleton.py
│ │ │ │ ├── psycopg_pool_connection.py
│ │ │ │ └── rds_api_connection.py
│ │ │ ├── mutable_sql_detector.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_psycopg_connector.py
│ │ │ ├── test_server.py
│ │ │ └── test_singleton.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── prometheus-mcp-server
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── prometheus_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── consts.py
│ │ │ ├── models.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_aws_credentials.py
│ │ │ ├── test_config_manager.py
│ │ │ ├── test_consts.py
│ │ │ ├── test_coverage_gaps.py
│ │ │ ├── test_coverage_improvement.py
│ │ │ ├── test_final_coverage.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_models.py
│ │ │ ├── test_prometheus_client.py
│ │ │ ├── test_prometheus_connection.py
│ │ │ ├── test_security_validator.py
│ │ │ ├── test_server_coverage.py
│ │ │ ├── test_tools.py
│ │ │ └── test_workspace_config.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── redshift-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── redshift_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── consts.py
│ │ │ ├── models.py
│ │ │ ├── redshift.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_redshift.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── s3-tables-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── s3_tables_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── constants.py
│ │ │ ├── database.py
│ │ │ ├── engines
│ │ │ │ ├── __init__.py
│ │ │ │ └── pyiceberg.py
│ │ │ ├── file_processor
│ │ │ │ ├── __init__.py
│ │ │ │ ├── csv.py
│ │ │ │ ├── parquet.py
│ │ │ │ └── utils.py
│ │ │ ├── models.py
│ │ │ ├── namespaces.py
│ │ │ ├── resources.py
│ │ │ ├── s3_operations.py
│ │ │ ├── server.py
│ │ │ ├── table_buckets.py
│ │ │ ├── tables.py
│ │ │ └── utils.py
│ │ ├── CHANGELOG.md
│ │ ├── CONTEXT.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_csv.py
│ │ │ ├── test_database.py
│ │ │ ├── test_file_processor_utils.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_namespaces.py
│ │ │ ├── test_parquet.py
│ │ │ ├── test_pyiceberg.py
│ │ │ ├── test_resources.py
│ │ │ ├── test_s3_operations.py
│ │ │ ├── test_server.py
│ │ │ ├── test_table_buckets.py
│ │ │ ├── test_tables.py
│ │ │ └── test_utils.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── stepfunctions-tool-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── stepfunctions_tool_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── aws_helper.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── test_aws_helper.py
│ │ │ ├── test_create_state_machine_tool.py
│ │ │ ├── test_filter_state_machines_by_tag.py
│ │ │ ├── test_format_state_machine_response.py
│ │ │ ├── test_get_schema_arn_from_state_machine_arn.py
│ │ │ ├── test_get_schema_from_registry.py
│ │ │ ├── test_invoke_express_state_machine_impl.py
│ │ │ ├── test_invoke_standard_state_machine_impl.py
│ │ │ ├── test_main.py
│ │ │ ├── test_register_state_machines.py
│ │ │ ├── test_sanitize_tool_name.py
│ │ │ ├── test_server.py
│ │ │ └── test_validate_state_machine_name.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── syntheticdata-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── syntheticdata_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── pandas_interpreter.py
│ │ │ ├── server.py
│ │ │ └── storage
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── loader.py
│ │ │ └── s3.py
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_constants.py
│ │ │ ├── test_pandas_interpreter.py
│ │ │ ├── test_server.py
│ │ │ └── test_storage
│ │ │ ├── __init__.py
│ │ │ ├── test_loader.py
│ │ │ └── test_s3.py
│ │ └── uv.lock
│ ├── terraform-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── terraform_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── impl
│ │ │ │ ├── resources
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── terraform_aws_provider_resources_listing.py
│ │ │ │ │ └── terraform_awscc_provider_resources_listing.py
│ │ │ │ └── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── execute_terraform_command.py
│ │ │ │ ├── execute_terragrunt_command.py
│ │ │ │ ├── run_checkov_scan.py
│ │ │ │ ├── search_aws_provider_docs.py
│ │ │ │ ├── search_awscc_provider_docs.py
│ │ │ │ ├── search_specific_aws_ia_modules.py
│ │ │ │ ├── search_user_provided_module.py
│ │ │ │ └── utils.py
│ │ │ ├── models
│ │ │ │ ├── __init__.py
│ │ │ │ └── models.py
│ │ │ ├── scripts
│ │ │ │ ├── generate_aws_provider_resources.py
│ │ │ │ ├── generate_awscc_provider_resources.py
│ │ │ │ └── scrape_aws_terraform_best_practices.py
│ │ │ ├── server.py
│ │ │ └── static
│ │ │ ├── __init__.py
│ │ │ ├── AWS_PROVIDER_RESOURCES.md
│ │ │ ├── AWS_TERRAFORM_BEST_PRACTICES.md
│ │ │ ├── AWSCC_PROVIDER_RESOURCES.md
│ │ │ ├── MCP_INSTRUCTIONS.md
│ │ │ └── TERRAFORM_WORKFLOW_GUIDE.md
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── .gitignore
│ │ │ ├── conftest.py
│ │ │ ├── README.md
│ │ │ ├── test_command_impl.py
│ │ │ ├── test_execute_terraform_command.py
│ │ │ ├── test_execute_terragrunt_command.py
│ │ │ ├── test_models.py
│ │ │ ├── test_parameter_annotations.py
│ │ │ ├── test_resources.py
│ │ │ ├── test_run_checkov_scan.py
│ │ │ ├── test_search_user_provided_module.py
│ │ │ ├── test_server.py
│ │ │ ├── test_tool_implementations.py
│ │ │ ├── test_utils_additional.py
│ │ │ └── test_utils.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── timestream-for-influxdb-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── timestream_for_influxdb_mcp_server
│ │ │ ├── __init__.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ └── test_server.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ ├── valkey-mcp-server
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── awslabs
│ │ │ ├── __init__.py
│ │ │ └── valkey_mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── common
│ │ │ │ ├── __init__.py
│ │ │ │ ├── config.py
│ │ │ │ ├── connection.py
│ │ │ │ └── server.py
│ │ │ ├── context.py
│ │ │ ├── main.py
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── bitmap.py
│ │ │ │ ├── hash.py
│ │ │ │ ├── hyperloglog.py
│ │ │ │ ├── json.py
│ │ │ │ ├── list.py
│ │ │ │ ├── misc.py
│ │ │ │ ├── server_management.py
│ │ │ │ ├── set.py
│ │ │ │ ├── sorted_set.py
│ │ │ │ ├── stream.py
│ │ │ │ └── string.py
│ │ │ └── version.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── ELASTICACHECONNECT.md
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_bitmap.py
│ │ │ ├── test_config.py
│ │ │ ├── test_connection.py
│ │ │ ├── test_hash.py
│ │ │ ├── test_hyperloglog.py
│ │ │ ├── test_init.py
│ │ │ ├── test_json_additional.py
│ │ │ ├── test_json_readonly.py
│ │ │ ├── test_json.py
│ │ │ ├── test_list_additional.py
│ │ │ ├── test_list_readonly.py
│ │ │ ├── test_list.py
│ │ │ ├── test_main.py
│ │ │ ├── test_misc.py
│ │ │ ├── test_server_management.py
│ │ │ ├── test_set_readonly.py
│ │ │ ├── test_set.py
│ │ │ ├── test_sorted_set_additional.py
│ │ │ ├── test_sorted_set_readonly.py
│ │ │ ├── test_sorted_set.py
│ │ │ ├── test_stream_additional.py
│ │ │ ├── test_stream_readonly.py
│ │ │ ├── test_stream.py
│ │ │ └── test_string.py
│ │ ├── uv-requirements.txt
│ │ └── uv.lock
│ └── well-architected-security-mcp-server
│ ├── .python-version
│ ├── awslabs
│ │ └── well_architected_security_mcp_server
│ │ ├── __init__.py
│ │ ├── consts.py
│ │ ├── server.py
│ │ └── util
│ │ ├── __init__.py
│ │ ├── network_security.py
│ │ ├── prompt_utils.py
│ │ ├── resource_utils.py
│ │ ├── security_services.py
│ │ └── storage_security.py
│ ├── PROMPT_TEMPLATE.md
│ ├── pyproject.toml
│ ├── README.md
│ ├── tests
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── README.md
│ │ ├── test_access_analyzer_fix.py
│ │ ├── test_network_security_additional.py
│ │ ├── test_network_security.py
│ │ ├── test_prompt_utils_coverage.py
│ │ ├── test_prompt_utils.py
│ │ ├── test_resource_utils_fix.py
│ │ ├── test_resource_utils.py
│ │ ├── test_security_services_additional.py
│ │ ├── test_security_services_coverage.py
│ │ ├── test_security_services.py
│ │ ├── test_server_additional.py
│ │ ├── test_server_coverage.py
│ │ ├── test_server_prompts.py
│ │ ├── test_server_security_findings.py
│ │ ├── test_server.py
│ │ ├── test_storage_security_additional.py
│ │ ├── test_storage_security_comprehensive.py
│ │ ├── test_storage_security_edge_cases.py
│ │ ├── test_storage_security_recommendations.py
│ │ ├── test_storage_security.py
│ │ └── test_user_agent_config.py
│ └── uv.lock
└── VIBE_CODING_TIPS_TRICKS.md
```
# Files
--------------------------------------------------------------------------------
/src/cloudwatch-applicationsignals-mcp-server/tests/test_server.py:
--------------------------------------------------------------------------------
```python
1 | """Tests for CloudWatch Application Signals MCP Server."""
2 |
3 | import json
4 | import pytest
5 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import _filter_operation_targets, main
6 | from awslabs.cloudwatch_applicationsignals_mcp_server.service_tools import (
7 | get_service_detail,
8 | list_monitored_services,
9 | query_service_metrics,
10 | )
11 | from awslabs.cloudwatch_applicationsignals_mcp_server.slo_tools import get_slo
12 | from awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools import (
13 | check_transaction_search_enabled,
14 | get_trace_summaries_paginated,
15 | list_slis,
16 | query_sampled_traces,
17 | search_transaction_spans,
18 | )
19 | from awslabs.cloudwatch_applicationsignals_mcp_server.utils import remove_null_values
20 | from botocore.exceptions import ClientError
21 | from datetime import datetime, timedelta, timezone
22 | from unittest.mock import AsyncMock, MagicMock, patch
23 |
24 |
25 | @pytest.fixture(autouse=True)
26 | def mock_aws_clients():
27 | """Mock all AWS clients to prevent real API calls during tests."""
28 | # Create mock clients
29 | mock_logs_client = MagicMock()
30 | mock_applicationsignals_client = MagicMock()
31 | mock_cloudwatch_client = MagicMock()
32 | mock_xray_client = MagicMock()
33 | mock_synthetics_client = MagicMock()
34 | mock_s3_client = MagicMock()
35 |
36 | # Patch the clients in all modules where they're imported
37 | patches = [
38 | # Original aws_clients module
39 | patch(
40 | 'awslabs.cloudwatch_applicationsignals_mcp_server.aws_clients.logs_client',
41 | mock_logs_client,
42 | ),
43 | patch(
44 | 'awslabs.cloudwatch_applicationsignals_mcp_server.aws_clients.applicationsignals_client',
45 | mock_applicationsignals_client,
46 | ),
47 | patch(
48 | 'awslabs.cloudwatch_applicationsignals_mcp_server.aws_clients.cloudwatch_client',
49 | mock_cloudwatch_client,
50 | ),
51 | patch(
52 | 'awslabs.cloudwatch_applicationsignals_mcp_server.aws_clients.xray_client',
53 | mock_xray_client,
54 | ),
55 | patch(
56 | 'awslabs.cloudwatch_applicationsignals_mcp_server.aws_clients.synthetics_client',
57 | mock_synthetics_client,
58 | ),
59 | patch(
60 | 'awslabs.cloudwatch_applicationsignals_mcp_server.aws_clients.s3_client',
61 | mock_s3_client,
62 | ),
63 | # Service tools module (check what's actually imported)
64 | patch(
65 | 'awslabs.cloudwatch_applicationsignals_mcp_server.service_tools.applicationsignals_client',
66 | mock_applicationsignals_client,
67 | ),
68 | patch(
69 | 'awslabs.cloudwatch_applicationsignals_mcp_server.service_tools.cloudwatch_client',
70 | mock_cloudwatch_client,
71 | ),
72 | # SLO tools module (check what's actually imported)
73 | patch(
74 | 'awslabs.cloudwatch_applicationsignals_mcp_server.slo_tools.applicationsignals_client',
75 | mock_applicationsignals_client,
76 | ),
77 | # Trace tools module (logs_client, xray_client, and applicationsignals_client are imported)
78 | patch(
79 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.logs_client',
80 | mock_logs_client,
81 | ),
82 | patch(
83 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.xray_client',
84 | mock_xray_client,
85 | ),
86 | patch(
87 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.applicationsignals_client',
88 | mock_applicationsignals_client,
89 | ),
90 | # SLI report client module (applicationsignals_client and cloudwatch_client are imported)
91 | patch(
92 | 'awslabs.cloudwatch_applicationsignals_mcp_server.sli_report_client.applicationsignals_client',
93 | mock_applicationsignals_client,
94 | ),
95 | patch(
96 | 'awslabs.cloudwatch_applicationsignals_mcp_server.sli_report_client.cloudwatch_client',
97 | mock_cloudwatch_client,
98 | ),
99 | patch(
100 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.synthetics_client',
101 | mock_synthetics_client,
102 | ),
103 | patch('awslabs.cloudwatch_applicationsignals_mcp_server.server.s3_client', mock_s3_client),
104 | patch('awslabs.cloudwatch_applicationsignals_mcp_server.server.iam_client', MagicMock()),
105 | ]
106 |
107 | # Start all patches
108 | for p in patches:
109 | p.start()
110 |
111 | try:
112 | yield {
113 | 'logs_client': mock_logs_client,
114 | 'applicationsignals_client': mock_applicationsignals_client,
115 | 'cloudwatch_client': mock_cloudwatch_client,
116 | 'xray_client': mock_xray_client,
117 | 'synthetics_client': mock_synthetics_client,
118 | 's3_client': mock_s3_client,
119 | }
120 | finally:
121 | # Stop all patches
122 | for p in patches:
123 | p.stop()
124 |
125 |
126 | @pytest.fixture
127 | def mock_mcp():
128 | """Mock the FastMCP instance."""
129 | with patch('awslabs.cloudwatch_applicationsignals_mcp_server.server.mcp') as mock:
130 | yield mock
131 |
132 |
133 | @pytest.mark.asyncio
134 | async def test_list_monitored_services_success(mock_aws_clients):
135 | """Test successful listing of monitored services."""
136 | mock_response = {
137 | 'ServiceSummaries': [
138 | {
139 | 'KeyAttributes': {
140 | 'Name': 'test-service',
141 | 'Type': 'AWS::ECS::Service',
142 | 'Environment': 'production',
143 | }
144 | }
145 | ]
146 | }
147 |
148 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_response
149 |
150 | result = await list_monitored_services()
151 |
152 | assert 'Application Signals Services (1 total)' in result
153 | assert 'test-service' in result
154 | assert 'AWS::ECS::Service' in result
155 |
156 |
157 | @pytest.mark.asyncio
158 | async def test_list_monitored_services_empty(mock_aws_clients):
159 | """Test when no services are found."""
160 | mock_response = {'ServiceSummaries': []}
161 |
162 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_response
163 |
164 | result = await list_monitored_services()
165 |
166 | assert result == 'No services found in Application Signals.'
167 |
168 |
169 | @pytest.mark.asyncio
170 | async def test_get_service_detail_success(mock_aws_clients):
171 | """Test successful retrieval of service details."""
172 | mock_list_response = {
173 | 'ServiceSummaries': [
174 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
175 | ]
176 | }
177 |
178 | mock_get_response = {
179 | 'Service': {
180 | 'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'},
181 | 'AttributeMaps': [{'Platform': 'ECS', 'Application': 'test-app'}],
182 | 'MetricReferences': [
183 | {
184 | 'Namespace': 'AWS/ApplicationSignals',
185 | 'MetricName': 'Latency',
186 | 'MetricType': 'GAUGE',
187 | 'Dimensions': [{'Name': 'Service', 'Value': 'test-service'}],
188 | }
189 | ],
190 | 'LogGroupReferences': [{'Identifier': '/aws/ecs/test-service'}],
191 | }
192 | }
193 |
194 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
195 | mock_aws_clients['applicationsignals_client'].get_service.return_value = mock_get_response
196 |
197 | result = await get_service_detail('test-service')
198 |
199 | assert 'Service Details: test-service' in result
200 | assert 'AWS::ECS::Service' in result
201 | assert 'Platform: ECS' in result
202 | assert 'AWS/ApplicationSignals/Latency' in result
203 | assert '/aws/ecs/test-service' in result
204 |
205 |
206 | @pytest.mark.asyncio
207 | async def test_get_service_detail_not_found(mock_aws_clients):
208 | """Test when service is not found."""
209 | mock_response = {'ServiceSummaries': []}
210 |
211 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_response
212 |
213 | result = await get_service_detail('nonexistent-service')
214 |
215 | assert "Service 'nonexistent-service' not found" in result
216 |
217 |
218 | @pytest.mark.asyncio
219 | async def test_query_service_metrics_success(mock_aws_clients):
220 | """Test successful query of service metrics."""
221 | mock_list_response = {
222 | 'ServiceSummaries': [
223 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
224 | ]
225 | }
226 |
227 | mock_get_response = {
228 | 'Service': {
229 | 'MetricReferences': [
230 | {
231 | 'Namespace': 'AWS/ApplicationSignals',
232 | 'MetricName': 'Latency',
233 | 'Dimensions': [{'Name': 'Service', 'Value': 'test-service'}],
234 | }
235 | ]
236 | }
237 | }
238 |
239 | mock_metric_response = {
240 | 'Datapoints': [
241 | {
242 | 'Timestamp': datetime.now(timezone.utc),
243 | 'Average': 100.5,
244 | 'p99': 150.2,
245 | 'Unit': 'Milliseconds',
246 | }
247 | ]
248 | }
249 |
250 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
251 | mock_aws_clients['applicationsignals_client'].get_service.return_value = mock_get_response
252 | mock_aws_clients['cloudwatch_client'].get_metric_statistics.return_value = mock_metric_response
253 |
254 | result = await query_service_metrics(
255 | service_name='test-service',
256 | metric_name='Latency',
257 | statistic='Average',
258 | extended_statistic='p99',
259 | hours=1,
260 | )
261 |
262 | assert 'Metrics for test-service - Latency' in result
263 | assert 'Average Statistics:' in result
264 | assert 'p99 Statistics:' in result
265 |
266 |
267 | @pytest.mark.asyncio
268 | async def test_query_service_metrics_list_available(mock_aws_clients):
269 | """Test listing available metrics when no specific metric is requested."""
270 | mock_list_response = {
271 | 'ServiceSummaries': [
272 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
273 | ]
274 | }
275 |
276 | mock_get_response = {
277 | 'Service': {
278 | 'MetricReferences': [
279 | {
280 | 'MetricName': 'Latency',
281 | 'Namespace': 'AWS/ApplicationSignals',
282 | 'MetricType': 'GAUGE',
283 | },
284 | {
285 | 'MetricName': 'Error',
286 | 'Namespace': 'AWS/ApplicationSignals',
287 | 'MetricType': 'COUNT',
288 | },
289 | ]
290 | }
291 | }
292 |
293 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
294 | mock_aws_clients['applicationsignals_client'].get_service.return_value = mock_get_response
295 |
296 | result = await query_service_metrics(
297 | service_name='test-service',
298 | metric_name='', # Empty to list available metrics
299 | statistic='Average',
300 | extended_statistic='p99',
301 | hours=1,
302 | )
303 |
304 | assert "Available metrics for service 'test-service'" in result
305 | assert 'Latency' in result
306 | assert 'Error' in result
307 |
308 |
309 | @pytest.mark.asyncio
310 | async def test_get_slo_success(mock_aws_clients):
311 | """Test successful retrieval of SLO details."""
312 | mock_slo_response = {
313 | 'Slo': {
314 | 'Name': 'test-slo',
315 | 'Arn': 'arn:aws:application-signals:us-east-1:123456789012:slo/test-slo',
316 | 'Description': 'Test SLO for latency',
317 | 'EvaluationType': 'REQUEST_BASED',
318 | 'CreatedTime': '2024-01-01T00:00:00Z',
319 | 'LastUpdatedTime': '2024-01-02T00:00:00Z',
320 | 'Goal': {
321 | 'AttainmentGoal': 99.9,
322 | 'WarningThreshold': 99.0,
323 | 'Interval': {'RollingInterval': {'Duration': 7, 'DurationUnit': 'DAYS'}},
324 | },
325 | 'RequestBasedSli': {
326 | 'RequestBasedSliMetric': {
327 | 'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'},
328 | 'OperationName': 'GET /api/test',
329 | 'MetricType': 'LATENCY',
330 | 'MetricDataQueries': [
331 | {
332 | 'Id': 'query1',
333 | 'MetricStat': {
334 | 'Metric': {
335 | 'Namespace': 'AWS/ApplicationSignals',
336 | 'MetricName': 'Latency',
337 | 'Dimensions': [{'Name': 'Service', 'Value': 'test-service'}],
338 | },
339 | 'Period': 60,
340 | 'Stat': 'Average',
341 | },
342 | }
343 | ],
344 | },
345 | 'MetricThreshold': 1000,
346 | 'ComparisonOperator': 'GreaterThan',
347 | },
348 | }
349 | }
350 |
351 | mock_aws_clients[
352 | 'applicationsignals_client'
353 | ].get_service_level_objective.return_value = mock_slo_response
354 |
355 | result = await get_slo('test-slo-id')
356 |
357 | assert 'Service Level Objective Details' in result
358 | assert 'test-slo' in result
359 | assert 'REQUEST_BASED' in result
360 | assert '99.9%' in result
361 | assert 'GET /api/test' in result
362 | assert 'annotation[aws.local.operation]="GET /api/test"' in result
363 |
364 |
365 | @pytest.mark.asyncio
366 | async def test_get_slo_not_found(mock_aws_clients):
367 | """Test when SLO is not found."""
368 | mock_aws_clients['applicationsignals_client'].get_service_level_objective.return_value = {
369 | 'Slo': None
370 | }
371 |
372 | result = await get_slo('nonexistent-slo')
373 |
374 | assert 'No SLO found with ID: nonexistent-slo' in result
375 |
376 |
377 | @pytest.mark.asyncio
378 | async def test_search_transaction_spans_success(mock_aws_clients):
379 | """Test successful transaction search."""
380 | mock_query_response = {
381 | 'queryId': 'test-query-id',
382 | 'status': 'Complete',
383 | 'statistics': {'recordsMatched': 10},
384 | 'results': [
385 | [
386 | {'field': 'spanId', 'value': 'span1'},
387 | {'field': 'timestamp', 'value': '2024-01-01T00:00:00Z'},
388 | ]
389 | ],
390 | }
391 |
392 | with patch(
393 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
394 | ) as mock_check:
395 | mock_check.return_value = (True, 'CloudWatchLogs', 'ACTIVE')
396 | mock_aws_clients['logs_client'].start_query.return_value = {'queryId': 'test-query-id'}
397 | mock_aws_clients['logs_client'].get_query_results.return_value = mock_query_response
398 |
399 | result = await search_transaction_spans(
400 | log_group_name='aws/spans',
401 | start_time='2024-01-01T00:00:00+00:00', # Fixed ISO format
402 | end_time='2024-01-01T01:00:00+00:00',
403 | query_string='fields @timestamp, spanId',
404 | limit=100,
405 | max_timeout=30,
406 | )
407 |
408 | assert result['status'] == 'Complete'
409 | assert result['queryId'] == 'test-query-id'
410 | assert len(result['results']) == 1
411 | assert result['results'][0]['spanId'] == 'span1'
412 |
413 |
414 | @pytest.mark.asyncio
415 | async def test_search_transaction_spans_not_enabled(mock_aws_clients):
416 | """Test when transaction search is not enabled."""
417 | with patch(
418 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
419 | ) as mock_check:
420 | mock_check.return_value = (False, 'XRay', 'INACTIVE')
421 |
422 | result = await search_transaction_spans(
423 | log_group_name='',
424 | start_time='2024-01-01T00:00:00+00:00',
425 | end_time='2024-01-01T01:00:00+00:00',
426 | query_string='fields @timestamp',
427 | limit=None,
428 | max_timeout=30,
429 | )
430 |
431 | assert result['status'] == 'Transaction Search Not Available'
432 | assert not result['transaction_search_status']['enabled']
433 |
434 |
435 | @pytest.mark.asyncio
436 | async def test_list_slis_success(mock_aws_clients):
437 | """Test successful listing of SLI status."""
438 | mock_services_response = {
439 | 'ServiceSummaries': [
440 | {
441 | 'KeyAttributes': {
442 | 'Name': 'test-service',
443 | 'Type': 'AWS::ECS::Service',
444 | 'Environment': 'production',
445 | }
446 | }
447 | ]
448 | }
449 |
450 | # Mock boto3.client calls in SLIReportClient
451 | with patch('boto3.client') as mock_boto3_client:
452 | with patch(
453 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
454 | ) as mock_check:
455 | # Configure boto3.client to return our mocked clients
456 | def boto3_client_side_effect(service_name, **kwargs):
457 | if service_name == 'application-signals':
458 | return mock_aws_clients['applicationsignals_client']
459 | elif service_name == 'cloudwatch':
460 | return mock_aws_clients['cloudwatch_client']
461 | else:
462 | return MagicMock()
463 |
464 | mock_boto3_client.side_effect = boto3_client_side_effect
465 |
466 | mock_aws_clients[
467 | 'applicationsignals_client'
468 | ].list_services.return_value = mock_services_response
469 | mock_check.return_value = (True, 'CloudWatchLogs', 'ACTIVE')
470 |
471 | # Mock SLO summaries response for SLIReportClient
472 | mock_slo_response = {
473 | 'SloSummaries': [
474 | {
475 | 'Name': 'test-slo',
476 | 'Arn': 'arn:aws:application-signals:us-east-1:123456789012:slo/test-slo',
477 | 'KeyAttributes': {'Name': 'test-service'},
478 | 'OperationName': 'GET /api',
479 | 'CreatedTime': datetime.now(timezone.utc),
480 | }
481 | ]
482 | }
483 | mock_aws_clients[
484 | 'applicationsignals_client'
485 | ].list_service_level_objectives.return_value = mock_slo_response
486 |
487 | # Mock metric data response showing breach
488 | mock_metric_response = {
489 | 'MetricDataResults': [
490 | {
491 | 'Id': 'slo0',
492 | 'Timestamps': [datetime.now(timezone.utc)],
493 | 'Values': [1.0], # Breach count > 0 indicates breach
494 | }
495 | ]
496 | }
497 | mock_aws_clients[
498 | 'cloudwatch_client'
499 | ].get_metric_data.return_value = mock_metric_response
500 |
501 | result = await list_slis(hours=24)
502 |
503 | assert 'SLI Status Report - Last 24 hours' in result
504 | assert 'Transaction Search: ENABLED' in result
505 | assert 'BREACHED SERVICES:' in result
506 | assert 'test-service' in result
507 |
508 |
509 | @pytest.mark.asyncio
510 | async def test_query_sampled_traces_success(mock_aws_clients):
511 | """Test successful query of sampled traces."""
512 | mock_traces = [
513 | {
514 | 'Id': 'trace1',
515 | 'Duration': 0.5,
516 | 'ResponseTime': 500,
517 | 'HasError': False,
518 | 'HasFault': True,
519 | 'HasThrottle': False,
520 | 'Http': {'HttpStatus': 500},
521 | 'FaultRootCauses': [
522 | {
523 | 'Services': [
524 | {
525 | 'Name': 'test-service',
526 | 'Exceptions': [{'Message': 'Internal server error'}],
527 | }
528 | ]
529 | }
530 | ],
531 | }
532 | ]
533 |
534 | with patch(
535 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.get_trace_summaries_paginated'
536 | ) as mock_get_traces:
537 | with patch(
538 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
539 | ) as mock_check:
540 | mock_get_traces.return_value = mock_traces
541 | mock_check.return_value = (False, 'XRay', 'INACTIVE')
542 |
543 | result_json = await query_sampled_traces(
544 | start_time='2024-01-01T00:00:00Z',
545 | end_time='2024-01-01T01:00:00Z',
546 | filter_expression='service("test-service"){fault = true}',
547 | )
548 |
549 | result = json.loads(result_json)
550 | assert result['TraceCount'] == 1
551 | assert result['TraceSummaries'][0]['Id'] == 'trace1'
552 | assert result['TraceSummaries'][0]['HasFault'] is True
553 |
554 |
555 | @pytest.mark.asyncio
556 | async def test_query_sampled_traces_time_window_too_large(mock_aws_clients):
557 | """Test when time window is too large."""
558 | result_json = await query_sampled_traces(
559 | start_time='2024-01-01T00:00:00Z',
560 | end_time='2024-01-02T00:00:00Z', # 24 hours > 6 hours max
561 | filter_expression='service("test-service")',
562 | )
563 |
564 | result = json.loads(result_json)
565 | assert 'error' in result
566 | assert 'Time window too large' in result['error']
567 |
568 |
569 | def test_get_trace_summaries_paginated():
570 | """Test paginated trace retrieval."""
571 | mock_client = MagicMock()
572 | mock_responses = [
573 | {'TraceSummaries': [{'Id': 'trace1'}, {'Id': 'trace2'}], 'NextToken': 'token1'},
574 | {'TraceSummaries': [{'Id': 'trace3'}]},
575 | ]
576 | mock_client.get_trace_summaries.side_effect = mock_responses
577 |
578 | start_time = datetime.now(timezone.utc) - timedelta(hours=1)
579 | end_time = datetime.now(timezone.utc)
580 |
581 | traces = get_trace_summaries_paginated(
582 | mock_client, start_time, end_time, 'service("test")', max_traces=10
583 | )
584 |
585 | assert len(traces) == 3
586 | assert traces[0]['Id'] == 'trace1'
587 | assert traces[2]['Id'] == 'trace3'
588 |
589 |
590 | def test_get_trace_summaries_paginated_with_error():
591 | """Test paginated trace retrieval with error."""
592 | mock_client = MagicMock()
593 | mock_client.get_trace_summaries.side_effect = Exception('API Error')
594 |
595 | start_time = datetime.now(timezone.utc) - timedelta(hours=1)
596 | end_time = datetime.now(timezone.utc)
597 |
598 | traces = get_trace_summaries_paginated(
599 | mock_client, start_time, end_time, 'service("test")', max_traces=10
600 | )
601 |
602 | assert len(traces) == 0 # Should return empty list on error
603 |
604 |
605 | def test_check_transaction_search_enabled(mock_aws_clients):
606 | """Test checking transaction search status."""
607 | mock_aws_clients['xray_client'].get_trace_segment_destination.return_value = {
608 | 'Destination': 'CloudWatchLogs',
609 | 'Status': 'ACTIVE',
610 | }
611 |
612 | is_enabled, destination, status = check_transaction_search_enabled()
613 |
614 | assert is_enabled is True
615 | assert destination == 'CloudWatchLogs'
616 | assert status == 'ACTIVE'
617 |
618 |
619 | def test_check_transaction_search_enabled_not_active(mock_aws_clients):
620 | """Test checking transaction search when not active."""
621 | mock_aws_clients['xray_client'].get_trace_segment_destination.return_value = {
622 | 'Destination': 'XRay',
623 | 'Status': 'INACTIVE',
624 | }
625 |
626 | is_enabled, destination, status = check_transaction_search_enabled()
627 |
628 | assert is_enabled is False
629 | assert destination == 'XRay'
630 | assert status == 'INACTIVE'
631 |
632 |
633 | def test_check_transaction_search_enabled_error(mock_aws_clients):
634 | """Test checking transaction search with error."""
635 | mock_aws_clients['xray_client'].get_trace_segment_destination.side_effect = Exception(
636 | 'API Error'
637 | )
638 |
639 | is_enabled, destination, status = check_transaction_search_enabled()
640 |
641 | assert is_enabled is False
642 | assert destination == 'Unknown'
643 | assert status == 'Error'
644 |
645 |
646 | def test_remove_null_values():
647 | """Test remove_null_values function."""
648 | # Test with mix of None and non-None values
649 | input_dict = {
650 | 'key1': 'value1',
651 | 'key2': None,
652 | 'key3': 'value3',
653 | 'key4': None,
654 | 'key5': 0, # Should not be removed
655 | 'key6': '', # Should not be removed
656 | 'key7': False, # Should not be removed
657 | }
658 |
659 | result = remove_null_values(input_dict)
660 |
661 | assert result == {
662 | 'key1': 'value1',
663 | 'key3': 'value3',
664 | 'key5': 0,
665 | 'key6': '',
666 | 'key7': False,
667 | }
668 | assert 'key2' not in result
669 | assert 'key4' not in result
670 |
671 |
672 | @pytest.mark.asyncio
673 | async def test_list_monitored_services_client_error(mock_aws_clients):
674 | """Test ClientError handling in list_monitored_services."""
675 | mock_aws_clients['applicationsignals_client'].list_services.side_effect = ClientError(
676 | error_response={
677 | 'Error': {
678 | 'Code': 'AccessDeniedException',
679 | 'Message': 'User is not authorized to perform this action',
680 | }
681 | },
682 | operation_name='ListServices',
683 | )
684 |
685 | result = await list_monitored_services()
686 |
687 | assert 'AWS Error: User is not authorized to perform this action' in result
688 |
689 |
690 | @pytest.mark.asyncio
691 | async def test_list_monitored_services_general_exception(mock_aws_clients):
692 | """Test general exception handling in list_monitored_services."""
693 | mock_aws_clients['applicationsignals_client'].list_services.side_effect = Exception(
694 | 'Unexpected error occurred'
695 | )
696 |
697 | result = await list_monitored_services()
698 |
699 | assert 'Error: Unexpected error occurred' in result
700 |
701 |
702 | @pytest.mark.asyncio
703 | async def test_get_service_detail_client_error(mock_aws_clients):
704 | """Test ClientError handling in get_service_detail."""
705 | mock_list_response = {
706 | 'ServiceSummaries': [
707 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
708 | ]
709 | }
710 |
711 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
712 | mock_aws_clients['applicationsignals_client'].get_service.side_effect = ClientError(
713 | error_response={
714 | 'Error': {
715 | 'Code': 'ResourceNotFoundException',
716 | 'Message': 'Service not found in Application Signals',
717 | }
718 | },
719 | operation_name='GetService',
720 | )
721 |
722 | result = await get_service_detail('test-service')
723 |
724 | assert 'AWS Error: Service not found in Application Signals' in result
725 |
726 |
727 | @pytest.mark.asyncio
728 | async def test_get_service_detail_general_exception(mock_aws_clients):
729 | """Test general exception handling in get_service_detail."""
730 | mock_list_response = {
731 | 'ServiceSummaries': [
732 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
733 | ]
734 | }
735 |
736 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
737 | mock_aws_clients['applicationsignals_client'].get_service.side_effect = Exception(
738 | 'Unexpected error in get_service'
739 | )
740 |
741 | result = await get_service_detail('test-service')
742 |
743 | assert 'Error: Unexpected error in get_service' in result
744 |
745 |
746 | @pytest.mark.asyncio
747 | async def test_query_service_metrics_no_datapoints(mock_aws_clients):
748 | """Test query service metrics when no datapoints are returned."""
749 | mock_list_response = {
750 | 'ServiceSummaries': [
751 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
752 | ]
753 | }
754 |
755 | mock_get_response = {
756 | 'Service': {
757 | 'MetricReferences': [
758 | {
759 | 'Namespace': 'AWS/ApplicationSignals',
760 | 'MetricName': 'Latency',
761 | 'Dimensions': [{'Name': 'Service', 'Value': 'test-service'}],
762 | }
763 | ]
764 | }
765 | }
766 |
767 | mock_metric_response = {'Datapoints': []}
768 |
769 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
770 | mock_aws_clients['applicationsignals_client'].get_service.return_value = mock_get_response
771 | mock_aws_clients['cloudwatch_client'].get_metric_statistics.return_value = mock_metric_response
772 |
773 | result = await query_service_metrics(
774 | service_name='test-service',
775 | metric_name='Latency',
776 | statistic='Average',
777 | extended_statistic='p99',
778 | hours=1,
779 | )
780 |
781 | assert 'No data points found' in result
782 |
783 |
784 | @pytest.mark.asyncio
785 | async def test_search_transaction_spans_timeout(mock_aws_clients):
786 | """Test search transaction spans with timeout."""
787 | with patch(
788 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
789 | ) as mock_check:
790 | with patch(
791 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.timer'
792 | ) as mock_timer:
793 | # Mock asyncio.sleep to prevent actual waiting
794 | with patch('asyncio.sleep', new_callable=AsyncMock):
795 | mock_check.return_value = (True, 'CloudWatchLogs', 'ACTIVE')
796 | mock_aws_clients['logs_client'].start_query.return_value = {
797 | 'queryId': 'test-query-id'
798 | }
799 | mock_aws_clients['logs_client'].get_query_results.return_value = {
800 | 'status': 'Running'
801 | }
802 |
803 | # Simulate timeout by making timer exceed max_timeout
804 | mock_timer.side_effect = [
805 | 0,
806 | 0,
807 | 0,
808 | 31,
809 | 31,
810 | ] # start_time_perf, poll_start, poll check 1, poll check 2
811 |
812 | result = await search_transaction_spans(
813 | log_group_name='',
814 | start_time='2024-01-01T00:00:00+00:00',
815 | end_time='2024-01-01T01:00:00+00:00',
816 | query_string='fields @timestamp',
817 | limit=None,
818 | max_timeout=30,
819 | )
820 |
821 | assert result['status'] == 'Polling Timeout'
822 | assert 'did not complete within 30 seconds' in result['message']
823 |
824 |
825 | def test_main_normal_execution(mock_mcp):
826 | """Test normal execution of main function."""
827 | main()
828 | mock_mcp.run.assert_called_once_with(transport='stdio')
829 |
830 |
831 | def test_main_keyboard_interrupt(mock_mcp):
832 | """Test KeyboardInterrupt handling in main function."""
833 | mock_mcp.run.side_effect = KeyboardInterrupt()
834 | # Should not raise an exception
835 | main()
836 | mock_mcp.run.assert_called_once_with(transport='stdio')
837 |
838 |
839 | def test_main_general_exception(mock_mcp):
840 | """Test general exception handling in main function."""
841 | mock_mcp.run.side_effect = Exception('Server error')
842 | with pytest.raises(Exception, match='Server error'):
843 | main()
844 | mock_mcp.run.assert_called_once_with(transport='stdio')
845 |
846 |
847 | @pytest.mark.asyncio
848 | async def test_get_slo_period_based(mock_aws_clients):
849 | """Test get_slo with period-based SLI configuration."""
850 | mock_slo_response = {
851 | 'Slo': {
852 | 'Name': 'test-slo-period',
853 | 'Arn': 'arn:aws:application-signals:us-east-1:123456789012:slo/test-slo',
854 | 'EvaluationType': 'PERIOD_BASED',
855 | 'Sli': {
856 | 'SliMetric': {
857 | 'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::Lambda::Function'},
858 | 'OperationName': 'ProcessOrder',
859 | 'MetricType': 'AVAILABILITY',
860 | 'DependencyConfig': {
861 | 'DependencyKeyAttributes': {'Name': 'payment-service'},
862 | 'DependencyOperationName': 'ProcessPayment',
863 | },
864 | },
865 | 'MetricThreshold': 0.99,
866 | 'ComparisonOperator': 'LessThan',
867 | },
868 | 'BurnRateConfigurations': [
869 | {'LookBackWindowMinutes': 5},
870 | {'LookBackWindowMinutes': 60},
871 | ],
872 | }
873 | }
874 |
875 | mock_aws_clients[
876 | 'applicationsignals_client'
877 | ].get_service_level_objective.return_value = mock_slo_response
878 |
879 | result = await get_slo('test-slo-period')
880 |
881 | assert 'PERIOD_BASED' in result
882 | assert 'ProcessOrder' in result
883 | assert 'annotation[aws.remote.operation]="ProcessPayment"' in result
884 | assert 'Burn Rate Configurations' in result
885 |
886 |
887 | @pytest.mark.asyncio
888 | async def test_list_slis_with_error_in_sli_client(mock_aws_clients):
889 | """Test list_slis when SLIReportClient throws error for some services."""
890 | mock_services_response = {
891 | 'ServiceSummaries': [
892 | {
893 | 'KeyAttributes': {
894 | 'Name': 'test-service-1',
895 | 'Type': 'AWS::ECS::Service',
896 | 'Environment': 'production',
897 | }
898 | },
899 | {
900 | 'KeyAttributes': {
901 | 'Name': 'test-service-2',
902 | 'Type': 'AWS::Lambda::Function',
903 | 'Environment': 'staging',
904 | }
905 | },
906 | ]
907 | }
908 |
909 | with patch(
910 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.SLIReportClient'
911 | ) as mock_sli_client:
912 | with patch(
913 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
914 | ) as mock_check:
915 | mock_aws_clients[
916 | 'applicationsignals_client'
917 | ].list_services.return_value = mock_services_response
918 | mock_check.return_value = (False, 'XRay', 'INACTIVE')
919 |
920 | # First service succeeds, second fails
921 | mock_report = MagicMock()
922 | mock_report.breached_slo_count = 0
923 | mock_report.ok_slo_count = 3
924 | mock_report.total_slo_count = 3
925 | mock_report.sli_status = 'OK'
926 | mock_report.start_time = datetime.now(timezone.utc) - timedelta(hours=24)
927 | mock_report.end_time = datetime.now(timezone.utc)
928 |
929 | mock_sli_client.return_value.generate_sli_report.side_effect = [
930 | mock_report,
931 | Exception('Failed to get SLI report'),
932 | ]
933 |
934 | result = await list_slis(hours=24)
935 |
936 | assert 'Transaction Search: NOT ENABLED' in result
937 | assert 'HEALTHY SERVICES:' in result
938 | assert 'INSUFFICIENT DATA:' in result
939 | assert 'test-service-1' in result
940 | assert 'test-service-2' in result
941 |
942 |
943 | @pytest.mark.asyncio
944 | async def test_query_service_metrics_service_not_found(mock_aws_clients):
945 | """Test query service metrics when service is not found."""
946 | mock_list_response = {'ServiceSummaries': []}
947 |
948 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
949 |
950 | result = await query_service_metrics(
951 | service_name='nonexistent-service',
952 | metric_name='Latency',
953 | statistic='Average',
954 | extended_statistic='p99',
955 | hours=1,
956 | )
957 |
958 | assert "Service 'nonexistent-service' not found" in result
959 |
960 |
961 | @pytest.mark.asyncio
962 | async def test_query_service_metrics_no_metrics(mock_aws_clients):
963 | """Test query service metrics when service has no metrics."""
964 | mock_list_response = {
965 | 'ServiceSummaries': [
966 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
967 | ]
968 | }
969 |
970 | mock_get_response = {'Service': {'MetricReferences': []}}
971 |
972 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
973 | mock_aws_clients['applicationsignals_client'].get_service.return_value = mock_get_response
974 |
975 | result = await query_service_metrics(
976 | service_name='test-service',
977 | metric_name='Latency',
978 | statistic='Average',
979 | extended_statistic='p99',
980 | hours=1,
981 | )
982 |
983 | assert "No metrics found for service 'test-service'" in result
984 |
985 |
986 | @pytest.mark.asyncio
987 | async def test_query_service_metrics_metric_not_found(mock_aws_clients):
988 | """Test query service metrics when specific metric is not found."""
989 | mock_list_response = {
990 | 'ServiceSummaries': [
991 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
992 | ]
993 | }
994 |
995 | mock_get_response = {
996 | 'Service': {
997 | 'MetricReferences': [
998 | {
999 | 'Namespace': 'AWS/ApplicationSignals',
1000 | 'MetricName': 'Error',
1001 | 'Dimensions': [{'Name': 'Service', 'Value': 'test-service'}],
1002 | }
1003 | ]
1004 | }
1005 | }
1006 |
1007 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_list_response
1008 | mock_aws_clients['applicationsignals_client'].get_service.return_value = mock_get_response
1009 |
1010 | result = await query_service_metrics(
1011 | service_name='test-service',
1012 | metric_name='Latency', # Looking for Latency but only Error exists
1013 | statistic='Average',
1014 | extended_statistic='p99',
1015 | hours=1,
1016 | )
1017 |
1018 | assert "Metric 'Latency' not found" in result
1019 | assert 'Available: Error' in result
1020 |
1021 |
1022 | @pytest.mark.asyncio
1023 | async def test_query_service_metrics_client_error(mock_aws_clients):
1024 | """Test query service metrics with client error."""
1025 | mock_aws_clients['applicationsignals_client'].list_services.side_effect = ClientError(
1026 | error_response={
1027 | 'Error': {
1028 | 'Code': 'AccessDeniedException',
1029 | 'Message': 'User is not authorized',
1030 | }
1031 | },
1032 | operation_name='ListServices',
1033 | )
1034 |
1035 | result = await query_service_metrics(
1036 | service_name='test-service',
1037 | metric_name='Latency',
1038 | statistic='Average',
1039 | extended_statistic='p99',
1040 | hours=1,
1041 | )
1042 |
1043 | assert 'AWS Error: User is not authorized' in result
1044 |
1045 |
1046 | @pytest.mark.asyncio
1047 | async def test_search_transaction_spans_failed_query(mock_aws_clients):
1048 | """Test search transaction spans when query fails."""
1049 | with patch(
1050 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
1051 | ) as mock_check:
1052 | mock_check.return_value = (True, 'CloudWatchLogs', 'ACTIVE')
1053 | mock_aws_clients['logs_client'].start_query.return_value = {'queryId': 'test-query-id'}
1054 | mock_aws_clients['logs_client'].get_query_results.return_value = {
1055 | 'queryId': 'test-query-id',
1056 | 'status': 'Failed',
1057 | 'statistics': {'error': 'Query syntax error'},
1058 | }
1059 |
1060 | result = await search_transaction_spans(
1061 | log_group_name='',
1062 | start_time='2024-01-01T00:00:00+00:00',
1063 | end_time='2024-01-01T01:00:00+00:00',
1064 | query_string='invalid query',
1065 | limit=None,
1066 | max_timeout=30,
1067 | )
1068 |
1069 | assert result['status'] == 'Failed'
1070 |
1071 |
1072 | @pytest.mark.asyncio
1073 | async def test_get_slo_client_error(mock_aws_clients):
1074 | """Test get_slo with client error."""
1075 | mock_aws_clients[
1076 | 'applicationsignals_client'
1077 | ].get_service_level_objective.side_effect = ClientError(
1078 | error_response={
1079 | 'Error': {
1080 | 'Code': 'ResourceNotFoundException',
1081 | 'Message': 'SLO not found',
1082 | }
1083 | },
1084 | operation_name='GetServiceLevelObjective',
1085 | )
1086 |
1087 | result = await get_slo('test-slo-id')
1088 |
1089 | assert 'AWS Error: SLO not found' in result
1090 |
1091 |
1092 | @pytest.mark.asyncio
1093 | async def test_search_transaction_spans_empty_log_group(mock_aws_clients):
1094 | """Test search transaction spans with empty log group defaults to aws/spans."""
1095 | with patch(
1096 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
1097 | ) as mock_check:
1098 | mock_check.return_value = (True, 'CloudWatchLogs', 'ACTIVE')
1099 | mock_aws_clients['logs_client'].start_query.return_value = {'queryId': 'test-query-id'}
1100 | mock_aws_clients['logs_client'].get_query_results.return_value = {
1101 | 'queryId': 'test-query-id',
1102 | 'status': 'Complete',
1103 | 'results': [],
1104 | }
1105 |
1106 | await search_transaction_spans(
1107 | log_group_name='', # Empty string should default to 'aws/spans'
1108 | start_time='2024-01-01T00:00:00+00:00',
1109 | end_time='2024-01-01T01:00:00+00:00',
1110 | query_string='fields @timestamp',
1111 | limit=None,
1112 | max_timeout=30,
1113 | )
1114 |
1115 | # Verify start_query was called with default 'aws/spans'
1116 | mock_aws_clients['logs_client'].start_query.assert_called()
1117 | call_args = mock_aws_clients['logs_client'].start_query.call_args[1]
1118 | assert 'aws/spans' in call_args['logGroupNames']
1119 |
1120 |
1121 | @pytest.mark.asyncio
1122 | async def test_list_slis_no_services(mock_aws_clients):
1123 | """Test list_slis when no services exist."""
1124 | mock_aws_clients['applicationsignals_client'].list_services.return_value = {
1125 | 'ServiceSummaries': []
1126 | }
1127 |
1128 | result = await list_slis(hours=24)
1129 |
1130 | assert 'No services found in Application Signals.' in result
1131 |
1132 |
1133 | @pytest.mark.asyncio
1134 | async def test_get_slo_with_calendar_interval(mock_aws_clients):
1135 | """Test get_slo with calendar interval in goal."""
1136 | mock_slo_response = {
1137 | 'Slo': {
1138 | 'Name': 'test-slo-calendar',
1139 | 'Goal': {
1140 | 'AttainmentGoal': 99.5,
1141 | 'Interval': {
1142 | 'CalendarInterval': {
1143 | 'Duration': 1,
1144 | 'DurationUnit': 'MONTH',
1145 | 'StartTime': '2024-01-01T00:00:00Z',
1146 | }
1147 | },
1148 | },
1149 | }
1150 | }
1151 |
1152 | mock_aws_clients[
1153 | 'applicationsignals_client'
1154 | ].get_service_level_objective.return_value = mock_slo_response
1155 |
1156 | result = await get_slo('test-slo-calendar')
1157 |
1158 | assert 'Calendar 1 MONTH starting 2024-01-01T00:00:00Z' in result
1159 |
1160 |
1161 | @pytest.mark.asyncio
1162 | async def test_query_service_metrics_different_periods(mock_aws_clients):
1163 | """Test query service metrics with different time periods."""
1164 | # Test data for different hour ranges
1165 | test_cases = [
1166 | (2, 60), # 2 hours -> 1 minute period
1167 | (12, 300), # 12 hours -> 5 minute period
1168 | (48, 3600), # 48 hours -> 1 hour period
1169 | ]
1170 |
1171 | for hours, expected_period in test_cases:
1172 | mock_list_response = {
1173 | 'ServiceSummaries': [
1174 | {'KeyAttributes': {'Name': 'test-service', 'Type': 'AWS::ECS::Service'}}
1175 | ]
1176 | }
1177 |
1178 | mock_get_response = {
1179 | 'Service': {
1180 | 'MetricReferences': [
1181 | {
1182 | 'Namespace': 'AWS/ApplicationSignals',
1183 | 'MetricName': 'Latency',
1184 | 'Dimensions': [],
1185 | }
1186 | ]
1187 | }
1188 | }
1189 |
1190 | mock_metric_response = {
1191 | 'Datapoints': [{'Timestamp': datetime.now(timezone.utc), 'Average': 100.0}]
1192 | }
1193 |
1194 | mock_aws_clients[
1195 | 'applicationsignals_client'
1196 | ].list_services.return_value = mock_list_response
1197 | mock_aws_clients['applicationsignals_client'].get_service.return_value = mock_get_response
1198 | mock_aws_clients[
1199 | 'cloudwatch_client'
1200 | ].get_metric_statistics.return_value = mock_metric_response
1201 |
1202 | await query_service_metrics(
1203 | service_name='test-service',
1204 | metric_name='Latency',
1205 | statistic='Average',
1206 | extended_statistic='p99',
1207 | hours=hours,
1208 | )
1209 |
1210 | # Verify the period was set correctly
1211 | call_args = mock_aws_clients['cloudwatch_client'].get_metric_statistics.call_args[1]
1212 | assert call_args['Period'] == expected_period
1213 |
1214 |
1215 | @pytest.mark.asyncio
1216 | async def test_query_service_metrics_general_exception(mock_aws_clients):
1217 | """Test query service metrics with unexpected exception."""
1218 | mock_aws_clients['applicationsignals_client'].list_services.side_effect = Exception(
1219 | 'Unexpected error'
1220 | )
1221 |
1222 | result = await query_service_metrics(
1223 | service_name='test-service',
1224 | metric_name='Latency',
1225 | statistic='Average',
1226 | extended_statistic='p99',
1227 | hours=1,
1228 | )
1229 |
1230 | assert 'Error: Unexpected error' in result
1231 |
1232 |
1233 | @pytest.mark.asyncio
1234 | async def test_search_transaction_spans_general_exception(mock_aws_clients):
1235 | """Test search transaction spans with general exception."""
1236 | with patch(
1237 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
1238 | ) as mock_check:
1239 | mock_check.return_value = (True, 'CloudWatchLogs', 'ACTIVE')
1240 | mock_aws_clients['logs_client'].start_query.side_effect = Exception('Query failed')
1241 |
1242 | with pytest.raises(Exception) as exc_info:
1243 | await search_transaction_spans(
1244 | log_group_name='aws/spans',
1245 | start_time='2024-01-01T00:00:00+00:00',
1246 | end_time='2024-01-01T01:00:00+00:00',
1247 | query_string='fields @timestamp',
1248 | limit=100,
1249 | max_timeout=30,
1250 | )
1251 |
1252 | assert 'Query failed' in str(exc_info.value)
1253 |
1254 |
1255 | @pytest.mark.asyncio
1256 | async def test_list_monitored_services_with_attributes_branch(mock_aws_clients):
1257 | """Test list_monitored_services with key attributes that trigger the branch."""
1258 | mock_response = {
1259 | 'ServiceSummaries': [
1260 | {
1261 | 'KeyAttributes': {} # Empty attributes to test the branch
1262 | }
1263 | ]
1264 | }
1265 |
1266 | mock_aws_clients['applicationsignals_client'].list_services.return_value = mock_response
1267 |
1268 | result = await list_monitored_services()
1269 |
1270 | assert 'Application Signals Services (1 total)' in result
1271 | assert 'Key Attributes:' not in result # Should not show when empty
1272 |
1273 |
1274 | @pytest.mark.asyncio
1275 | async def test_get_trace_summaries_paginated_with_limit(mock_aws_clients):
1276 | """Test get_trace_summaries_paginated when it hits the max_traces limit."""
1277 | # Mock responses with more traces than the limit
1278 | mock_response_1 = {
1279 | 'TraceSummaries': [{'Id': f'trace-{i}', 'Duration': 100} for i in range(10)],
1280 | 'NextToken': 'token1',
1281 | }
1282 | mock_response_2 = {
1283 | 'TraceSummaries': [{'Id': f'trace-{i}', 'Duration': 100} for i in range(10, 15)]
1284 | }
1285 |
1286 | mock_aws_clients['xray_client'].get_trace_summaries.side_effect = [
1287 | mock_response_1,
1288 | mock_response_2,
1289 | ]
1290 |
1291 | # Test with max_traces=12
1292 | traces = get_trace_summaries_paginated(
1293 | mock_aws_clients['xray_client'],
1294 | datetime.now(timezone.utc),
1295 | datetime.now(timezone.utc),
1296 | 'service("test")',
1297 | max_traces=12,
1298 | )
1299 |
1300 | # The function continues until it gets all traces from the current page
1301 | # before checking the limit, so we might get more than max_traces
1302 | assert len(traces) >= 12 # Should have at least the limit
1303 |
1304 |
1305 | @pytest.mark.asyncio
1306 | async def test_get_slo_with_period_based_sli_full_details(mock_aws_clients):
1307 | """Test get_slo with comprehensive period-based SLI configuration."""
1308 | mock_response = {
1309 | 'Slo': {
1310 | 'Name': 'test-slo',
1311 | 'Arn': 'arn:aws:slo:test',
1312 | 'Description': 'Test SLO',
1313 | 'EvaluationType': 'PERIOD_BASED',
1314 | 'CreatedTime': datetime.now(timezone.utc),
1315 | 'LastUpdatedTime': datetime.now(timezone.utc),
1316 | 'Goal': {
1317 | 'AttainmentGoal': 99.9,
1318 | 'WarningThreshold': 95,
1319 | 'Interval': {
1320 | 'CalendarInterval': {
1321 | 'Duration': 1,
1322 | 'DurationUnit': 'MONTH',
1323 | 'StartTime': datetime.now(timezone.utc),
1324 | }
1325 | },
1326 | },
1327 | 'Sli': {
1328 | 'SliMetric': {
1329 | 'KeyAttributes': {'Service': 'test-service', 'Environment': 'prod'},
1330 | 'OperationName': 'GetItem',
1331 | 'MetricType': 'LATENCY',
1332 | 'MetricDataQueries': [
1333 | {
1334 | 'Id': 'query1',
1335 | 'MetricStat': {
1336 | 'Metric': {
1337 | 'Namespace': 'AWS/ApplicationSignals',
1338 | 'MetricName': 'Latency',
1339 | 'Dimensions': [
1340 | {'Name': 'Service', 'Value': 'test-service'},
1341 | {'Name': 'Operation', 'Value': 'GetItem'},
1342 | ],
1343 | },
1344 | 'Period': 300,
1345 | 'Stat': 'p99',
1346 | 'Unit': 'Milliseconds',
1347 | },
1348 | 'ReturnData': True,
1349 | },
1350 | {'Id': 'query2', 'Expression': 'query1 * 2', 'ReturnData': False},
1351 | ],
1352 | 'DependencyConfig': {
1353 | 'DependencyKeyAttributes': {
1354 | 'RemoteService': 'downstream-service',
1355 | 'RemoteEnvironment': 'prod',
1356 | },
1357 | 'DependencyOperationName': 'ProcessRequest',
1358 | },
1359 | },
1360 | 'MetricThreshold': 1000,
1361 | 'ComparisonOperator': 'LessThan',
1362 | },
1363 | 'BurnRateConfigurations': [
1364 | {'LookBackWindowMinutes': 5},
1365 | {'LookBackWindowMinutes': 60},
1366 | ],
1367 | }
1368 | }
1369 |
1370 | mock_aws_clients[
1371 | 'applicationsignals_client'
1372 | ].get_service_level_objective.return_value = mock_response
1373 |
1374 | result = await get_slo('test-slo-id')
1375 |
1376 | # Verify all sections are present
1377 | assert 'Service Level Objective Details' in result
1378 | assert 'Goal Configuration' in result
1379 | assert 'Calendar 1 MONTH' in result
1380 | assert 'Period-Based SLI Configuration' in result
1381 | assert 'Key Attributes:' in result
1382 | assert 'Service: test-service' in result
1383 | assert 'Operation Name: GetItem' in result
1384 | assert 'Metric Data Queries:' in result
1385 | assert 'Query ID: query1' in result
1386 | assert 'Namespace: AWS/ApplicationSignals' in result
1387 | assert 'Dimensions:' in result
1388 | assert 'Expression: query1 * 2' in result
1389 | assert 'ReturnData: False' in result
1390 | assert 'Dependency Configuration:' in result
1391 | assert 'RemoteService: downstream-service' in result
1392 | assert 'Dependency Operation: ProcessRequest' in result
1393 | assert 'Burn Rate Configurations:' in result
1394 |
1395 |
1396 | @pytest.mark.asyncio
1397 | async def test_get_slo_with_request_based_sli_full_details(mock_aws_clients):
1398 | """Test get_slo with comprehensive request-based SLI configuration."""
1399 | mock_response = {
1400 | 'Slo': {
1401 | 'Name': 'test-slo-rbs',
1402 | 'Arn': 'arn:aws:slo:test-rbs',
1403 | 'Goal': {
1404 | 'AttainmentGoal': 99.5,
1405 | 'Interval': {'RollingInterval': {'Duration': 7, 'DurationUnit': 'DAY'}},
1406 | },
1407 | 'RequestBasedSli': {
1408 | 'RequestBasedSliMetric': {
1409 | 'KeyAttributes': {'Service': 'api-service', 'Type': 'AWS::Lambda::Function'},
1410 | 'OperationName': 'ProcessOrder',
1411 | 'MetricType': 'AVAILABILITY',
1412 | 'MetricDataQueries': [
1413 | {
1414 | 'Id': 'success',
1415 | 'MetricStat': {
1416 | 'Metric': {
1417 | 'Namespace': 'AWS/Lambda',
1418 | 'MetricName': 'Success',
1419 | 'Dimensions': [
1420 | {'Name': 'FunctionName', 'Value': 'process-order'}
1421 | ],
1422 | },
1423 | 'Period': 60,
1424 | 'Stat': 'Sum',
1425 | },
1426 | },
1427 | {
1428 | 'Id': 'errors',
1429 | 'MetricStat': {
1430 | 'Metric': {
1431 | 'Namespace': 'AWS/Lambda',
1432 | 'MetricName': 'Errors',
1433 | 'Dimensions': [
1434 | {'Name': 'FunctionName', 'Value': 'process-order'}
1435 | ],
1436 | },
1437 | 'Period': 60,
1438 | 'Stat': 'Sum',
1439 | 'Unit': 'Count',
1440 | },
1441 | },
1442 | {'Id': 'availability', 'Expression': 'success / (success + errors) * 100'},
1443 | ],
1444 | 'DependencyConfig': {
1445 | 'DependencyKeyAttributes': {'Database': 'orders-db'},
1446 | 'DependencyOperationName': 'Query',
1447 | },
1448 | },
1449 | 'MetricThreshold': 99.0,
1450 | 'ComparisonOperator': 'GreaterThan',
1451 | },
1452 | }
1453 | }
1454 |
1455 | mock_aws_clients[
1456 | 'applicationsignals_client'
1457 | ].get_service_level_objective.return_value = mock_response
1458 |
1459 | result = await get_slo('test-slo-rbs-id')
1460 |
1461 | # Verify request-based sections
1462 | assert 'Request-Based SLI Configuration:' in result
1463 | assert 'api-service' in result
1464 | assert 'ProcessOrder' in result
1465 | assert 'AVAILABILITY' in result
1466 | assert 'Expression: success / (success + errors) * 100' in result
1467 | assert 'Dependency Configuration:' in result
1468 | assert 'Database: orders-db' in result
1469 | assert 'Unit: Count' in result
1470 |
1471 |
1472 | @pytest.mark.asyncio
1473 | async def test_get_slo_general_exception(mock_aws_clients):
1474 | """Test get_slo with general exception."""
1475 | mock_aws_clients[
1476 | 'applicationsignals_client'
1477 | ].get_service_level_objective.side_effect = Exception('Unexpected error')
1478 |
1479 | result = await get_slo('test-slo-id')
1480 |
1481 | assert 'Error: Unexpected error' in result
1482 |
1483 |
1484 | @pytest.mark.asyncio
1485 | async def test_search_transaction_spans_with_none_log_group(mock_aws_clients):
1486 | """Test search_transaction_spans when log_group_name is None."""
1487 | with patch(
1488 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
1489 | ) as mock_check:
1490 | mock_check.return_value = (True, 'CloudWatchLogs', 'ACTIVE')
1491 | mock_aws_clients['logs_client'].start_query.return_value = {'queryId': 'test-query-id'}
1492 | mock_aws_clients['logs_client'].get_query_results.return_value = {
1493 | 'queryId': 'test-query-id',
1494 | 'status': 'Complete',
1495 | 'results': [],
1496 | }
1497 |
1498 | # Pass None for log_group_name to test the default handling
1499 | await search_transaction_spans(
1500 | log_group_name=None, # type: ignore
1501 | start_time='2024-01-01T00:00:00+00:00',
1502 | end_time='2024-01-01T01:00:00+00:00',
1503 | query_string='fields @timestamp',
1504 | limit=100,
1505 | max_timeout=30,
1506 | )
1507 |
1508 | # Verify it used the default log group
1509 | call_args = mock_aws_clients['logs_client'].start_query.call_args[1]
1510 | assert 'aws/spans' in call_args['logGroupNames']
1511 |
1512 |
1513 | @pytest.mark.asyncio
1514 | async def test_search_transaction_spans_complete_with_statistics(mock_aws_clients):
1515 | """Test search_transaction_spans when query completes with detailed statistics."""
1516 | with patch(
1517 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.check_transaction_search_enabled'
1518 | ) as mock_check:
1519 | mock_check.return_value = (True, 'CloudWatchLogs', 'ACTIVE')
1520 | mock_aws_clients['logs_client'].start_query.return_value = {'queryId': 'test-query-id'}
1521 |
1522 | # First return Running, then Complete
1523 | mock_aws_clients['logs_client'].get_query_results.side_effect = [
1524 | {'queryId': 'test-query-id', 'status': 'Running'},
1525 | {
1526 | 'queryId': 'test-query-id',
1527 | 'status': 'Complete',
1528 | 'statistics': {
1529 | 'recordsMatched': 100,
1530 | 'recordsScanned': 1000,
1531 | 'bytesScanned': 50000,
1532 | },
1533 | 'results': [
1534 | [
1535 | {'field': 'spanId', 'value': 'span1'},
1536 | {'field': '@timestamp', 'value': '2024-01-01 00:00:00'},
1537 | ]
1538 | ],
1539 | },
1540 | ]
1541 |
1542 | result = await search_transaction_spans(
1543 | log_group_name='aws/spans',
1544 | start_time='2024-01-01T00:00:00+00:00',
1545 | end_time='2024-01-01T01:00:00+00:00',
1546 | query_string='fields @timestamp, spanId',
1547 | limit=100,
1548 | max_timeout=30,
1549 | )
1550 |
1551 | assert result['status'] == 'Complete'
1552 | assert result['statistics']['recordsMatched'] == 100
1553 | assert len(result['results']) == 1
1554 |
1555 |
1556 | @pytest.mark.asyncio
1557 | async def test_list_slis_general_exception(mock_aws_clients):
1558 | """Test list_slis with general exception."""
1559 | mock_aws_clients['applicationsignals_client'].list_services.side_effect = Exception(
1560 | 'Service unavailable'
1561 | )
1562 |
1563 | result = await list_slis(hours=24)
1564 |
1565 | assert 'Error getting SLI status: Service unavailable' in result
1566 |
1567 |
1568 | @pytest.mark.asyncio
1569 | async def test_query_sampled_traces_with_defaults(mock_aws_clients):
1570 | """Test query_sampled_traces with default start_time and end_time."""
1571 | mock_trace_response = {
1572 | 'TraceSummaries': [
1573 | {
1574 | 'Id': 'trace1',
1575 | 'Duration': 100,
1576 | 'HasError': True,
1577 | 'ErrorRootCauses': [
1578 | {
1579 | 'Services': [
1580 | {
1581 | 'Name': 'test-service',
1582 | 'Names': ['test-service'],
1583 | 'Type': 'AWS::ECS::Service',
1584 | 'AccountId': '123456789012',
1585 | 'EntityPath': [
1586 | {'Name': 'test-service', 'Coverage': 1.0, 'Remote': False}
1587 | ],
1588 | 'Inferred': False,
1589 | }
1590 | ],
1591 | 'ClientImpacting': True,
1592 | }
1593 | ],
1594 | }
1595 | ]
1596 | }
1597 |
1598 | with patch(
1599 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.get_trace_summaries_paginated'
1600 | ) as mock_paginated:
1601 | mock_paginated.return_value = mock_trace_response['TraceSummaries']
1602 |
1603 | # Call without start_time and end_time to test defaults
1604 | result_json = await query_sampled_traces(
1605 | filter_expression='service("test-service")',
1606 | start_time=None,
1607 | end_time=None,
1608 | region='us-east-1',
1609 | )
1610 |
1611 | result = json.loads(result_json)
1612 | assert result['TraceCount'] == 1
1613 | assert result['TraceSummaries'][0]['HasError'] is True
1614 |
1615 | # Verify the time window was set to 3 hours
1616 | call_args = mock_paginated.call_args[0]
1617 | time_diff = call_args[2] - call_args[1] # end_time - start_time
1618 | assert 2.9 < time_diff.total_seconds() / 3600 < 3.1 # Approximately 3 hours
1619 |
1620 |
1621 | @pytest.mark.asyncio
1622 | async def test_query_sampled_traces_with_annotations(mock_aws_clients):
1623 | """Test query_sampled_traces with annotations filtering."""
1624 | mock_trace = {
1625 | 'Id': 'trace1',
1626 | 'Duration': 100,
1627 | 'Annotations': {
1628 | 'aws.local.operation': 'GetItem',
1629 | 'aws.remote.operation': 'Query',
1630 | 'custom.field': 'should-be-filtered',
1631 | 'another.field': 'also-filtered',
1632 | },
1633 | 'Users': [
1634 | {'UserName': 'user1', 'ServiceIds': []},
1635 | {'UserName': 'user2', 'ServiceIds': []},
1636 | {'UserName': 'user3', 'ServiceIds': []}, # Should be limited to 2
1637 | ],
1638 | }
1639 |
1640 | with patch(
1641 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.get_trace_summaries_paginated'
1642 | ) as mock_paginated:
1643 | mock_paginated.return_value = [mock_trace]
1644 |
1645 | result_json = await query_sampled_traces(
1646 | start_time='2024-01-01T00:00:00Z',
1647 | end_time='2024-01-01T01:00:00Z',
1648 | filter_expression='service("test")',
1649 | )
1650 |
1651 | result = json.loads(result_json)
1652 | trace_summary = result['TraceSummaries'][0]
1653 |
1654 | # Check annotations were filtered
1655 | assert 'Annotations' in trace_summary
1656 | assert 'aws.local.operation' in trace_summary['Annotations']
1657 | assert 'aws.remote.operation' in trace_summary['Annotations']
1658 | assert 'custom.field' not in trace_summary['Annotations']
1659 |
1660 | # Check users were limited
1661 | assert len(trace_summary['Users']) == 2
1662 |
1663 |
1664 | @pytest.mark.asyncio
1665 | async def test_query_sampled_traces_with_fault_causes(mock_aws_clients):
1666 | """Test query_sampled_traces with fault root causes."""
1667 | mock_trace = {
1668 | 'Id': 'trace1',
1669 | 'Duration': 100,
1670 | 'HasFault': True,
1671 | 'FaultRootCauses': [
1672 | {'Services': [{'Name': 'service1', 'Exceptions': [{'Message': 'Test fault error'}]}]},
1673 | {'Services': [{'Name': 'service2'}]},
1674 | {'Services': [{'Name': 'service3'}]},
1675 | {'Services': [{'Name': 'service4'}]}, # Should be limited to 3
1676 | ],
1677 | 'ResponseTimeRootCauses': [{'Services': [{'Name': 'slow-service'}]}],
1678 | }
1679 |
1680 | with patch(
1681 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.get_trace_summaries_paginated'
1682 | ) as mock_paginated:
1683 | mock_paginated.return_value = [mock_trace]
1684 |
1685 | result_json = await query_sampled_traces(
1686 | start_time='2024-01-01T00:00:00Z', end_time='2024-01-01T01:00:00Z'
1687 | )
1688 |
1689 | result = json.loads(result_json)
1690 | trace_summary = result['TraceSummaries'][0]
1691 |
1692 | # Check root causes were limited to 3
1693 | assert len(trace_summary['FaultRootCauses']) == 3
1694 | assert 'ResponseTimeRootCauses' in trace_summary
1695 |
1696 |
1697 | @pytest.mark.asyncio
1698 | async def test_query_sampled_traces_general_exception(mock_aws_clients):
1699 | """Test query_sampled_traces with general exception."""
1700 | with patch(
1701 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.get_trace_summaries_paginated'
1702 | ) as mock_paginated:
1703 | mock_paginated.side_effect = Exception('Trace query failed')
1704 |
1705 | result_json = await query_sampled_traces(
1706 | start_time='2024-01-01T00:00:00Z', end_time='2024-01-01T01:00:00Z'
1707 | )
1708 |
1709 | result = json.loads(result_json)
1710 | assert 'error' in result
1711 | assert 'Trace query failed' in result['error']
1712 |
1713 |
1714 | @pytest.mark.asyncio
1715 | async def test_query_sampled_traces_datetime_conversion(mock_aws_clients):
1716 | """Test query_sampled_traces with datetime objects that need conversion."""
1717 | # The convert_datetime function in server.py only processes top-level fields,
1718 | # not nested datetime objects. Let's test with a datetime at the top level.
1719 | mock_trace = {
1720 | 'Id': 'trace1',
1721 | 'Duration': 100,
1722 | 'Http': {'HttpStatus': 200, 'HttpMethod': 'GET'},
1723 | 'StartTime': datetime.now(timezone.utc), # This will be processed by convert_datetime
1724 | 'EndTime': datetime.now(timezone.utc) + timedelta(minutes=1),
1725 | }
1726 |
1727 | with patch(
1728 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.get_trace_summaries_paginated'
1729 | ) as mock_paginated:
1730 | mock_paginated.return_value = [mock_trace]
1731 |
1732 | result_json = await query_sampled_traces(
1733 | start_time='2024-01-01T00:00:00Z', end_time='2024-01-01T01:00:00Z'
1734 | )
1735 |
1736 | # Should not raise JSON serialization error
1737 | result = json.loads(result_json)
1738 | assert result['TraceCount'] == 1
1739 | # The datetime fields should have been converted during processing
1740 | trace_summary = result['TraceSummaries'][0]
1741 | assert (
1742 | 'StartTime' not in trace_summary
1743 | ) # These fields are not included in the simplified output
1744 | assert 'EndTime' not in trace_summary
1745 |
1746 |
1747 | @pytest.mark.asyncio
1748 | async def test_query_sampled_traces_deduplication(mock_aws_clients):
1749 | """Test query_sampled_traces deduplicates traces with same fault message.
1750 |
1751 | Note: Only FaultRootCauses are deduplicated, not ErrorRootCauses.
1752 | This is because the primary use case is investigating server faults (5xx errors),
1753 | not client errors (4xx).
1754 | """
1755 | # Create 5 traces with the same fault message
1756 | mock_traces = [
1757 | {
1758 | 'Id': f'trace{i}',
1759 | 'Duration': 100 + i * 10,
1760 | 'ResponseTime': 95 + i * 10,
1761 | 'HasFault': True,
1762 | 'FaultRootCauses': [
1763 | {
1764 | 'Services': [
1765 | {
1766 | 'Name': 'test-service',
1767 | 'Exceptions': [{'Message': 'Database connection timeout'}],
1768 | }
1769 | ]
1770 | }
1771 | ],
1772 | }
1773 | for i in range(1, 6)
1774 | ]
1775 |
1776 | # Add 2 traces with ErrorRootCauses (these should NOT be deduplicated)
1777 | mock_traces.extend(
1778 | [
1779 | {
1780 | 'Id': 'trace6',
1781 | 'Duration': 200,
1782 | 'HasError': True,
1783 | 'ErrorRootCauses': [
1784 | {
1785 | 'Services': [
1786 | {
1787 | 'Name': 'api-service',
1788 | 'Exceptions': [{'Message': 'Invalid API key'}],
1789 | }
1790 | ]
1791 | }
1792 | ],
1793 | },
1794 | {
1795 | 'Id': 'trace7',
1796 | 'Duration': 210,
1797 | 'HasError': True,
1798 | 'ErrorRootCauses': [
1799 | {
1800 | 'Services': [
1801 | {
1802 | 'Name': 'api-service',
1803 | 'Exceptions': [{'Message': 'Invalid API key'}],
1804 | }
1805 | ]
1806 | }
1807 | ],
1808 | },
1809 | ]
1810 | )
1811 |
1812 | # Add 2 healthy traces
1813 | mock_traces.extend(
1814 | [
1815 | {
1816 | 'Id': 'trace8',
1817 | 'Duration': 50,
1818 | 'ResponseTime': 45,
1819 | 'HasError': False,
1820 | 'HasFault': False,
1821 | },
1822 | {
1823 | 'Id': 'trace9',
1824 | 'Duration': 55,
1825 | 'ResponseTime': 50,
1826 | 'HasError': False,
1827 | 'HasFault': False,
1828 | },
1829 | ]
1830 | )
1831 |
1832 | with patch(
1833 | 'awslabs.cloudwatch_applicationsignals_mcp_server.trace_tools.get_trace_summaries_paginated'
1834 | ) as mock_paginated:
1835 | mock_paginated.return_value = mock_traces
1836 |
1837 | result_json = await query_sampled_traces(
1838 | start_time='2024-01-01T00:00:00Z', end_time='2024-01-01T01:00:00Z'
1839 | )
1840 |
1841 | result = json.loads(result_json)
1842 |
1843 | # Verify deduplication worked - should only have 5 traces
1844 | # 1 for database timeout fault (deduplicated from 5)
1845 | # 2 for API key errors (NOT deduplicated - only faults are deduped)
1846 | # 2 healthy traces (not deduplicated)
1847 | assert result['TraceCount'] == 5
1848 | assert len(result['TraceSummaries']) == 5
1849 |
1850 | # Verify deduplication stats
1851 | assert 'DeduplicationStats' in result
1852 | assert result['DeduplicationStats']['OriginalTraceCount'] == 9
1853 | assert result['DeduplicationStats']['DuplicatesRemoved'] == 4 # 9 - 5 = 4
1854 | assert (
1855 | result['DeduplicationStats']['UniqueFaultMessages'] == 1
1856 | ) # Only counting FaultRootCauses
1857 |
1858 | # Find the trace with fault
1859 | db_trace = next(
1860 | (
1861 | t
1862 | for t in result['TraceSummaries']
1863 | if t.get('FaultRootCauses')
1864 | and any(
1865 | 'Database connection timeout' in str(s.get('Exceptions', []))
1866 | for cause in t['FaultRootCauses']
1867 | for s in cause.get('Services', [])
1868 | )
1869 | ),
1870 | None,
1871 | )
1872 | assert db_trace is not None
1873 | assert db_trace['HasFault'] is True
1874 |
1875 | # Verify both error traces are present (not deduplicated)
1876 | error_traces = [
1877 | t
1878 | for t in result['TraceSummaries']
1879 | if t.get('ErrorRootCauses')
1880 | and any(
1881 | 'Invalid API key' in str(s.get('Exceptions', []))
1882 | for cause in t['ErrorRootCauses']
1883 | for s in cause.get('Services', [])
1884 | )
1885 | ]
1886 | assert len(error_traces) == 2 # Both error traces should be kept
1887 | assert all(t['HasError'] is True for t in error_traces)
1888 |
1889 | # Verify healthy traces are included
1890 | healthy_count = sum(
1891 | 1
1892 | for t in result['TraceSummaries']
1893 | if not t.get('HasError') and not t.get('HasFault') and not t.get('HasThrottle')
1894 | )
1895 | assert healthy_count == 2
1896 |
1897 |
1898 | def test_main_success(mock_aws_clients):
1899 | """Test main function normal execution."""
1900 | with patch('awslabs.cloudwatch_applicationsignals_mcp_server.server.mcp') as mock_mcp:
1901 | main()
1902 | mock_mcp.run.assert_called_once_with(transport='stdio')
1903 |
1904 |
1905 | def test_main_exception(mock_aws_clients):
1906 | """Test main function with general exception."""
1907 | with patch('awslabs.cloudwatch_applicationsignals_mcp_server.server.mcp') as mock_mcp:
1908 | mock_mcp.run.side_effect = Exception('Server error')
1909 |
1910 | with pytest.raises(Exception) as exc_info:
1911 | main()
1912 |
1913 | assert 'Server error' in str(exc_info.value)
1914 |
1915 |
1916 | def test_main_entry_point(mock_aws_clients):
1917 | """Test the if __name__ == '__main__' entry point."""
1918 | # The __main__ block is simple and just calls main()
1919 | # We can't easily test it without executing the module
1920 | # So we'll just ensure the main() function works
1921 | # The actual line 1346 will be covered when the module is imported
1922 | # during normal test execution
1923 |
1924 | # Instead, let's just verify the main function exists and is callable
1925 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import main
1926 |
1927 | assert callable(main)
1928 |
1929 | # And verify that running main with mocked mcp doesn't raise
1930 | with patch('awslabs.cloudwatch_applicationsignals_mcp_server.server.mcp') as mock_mcp:
1931 | mock_mcp.run.side_effect = KeyboardInterrupt()
1932 | # Should handle KeyboardInterrupt gracefully
1933 | main()
1934 |
1935 |
1936 | @pytest.mark.asyncio
1937 | async def test_analyze_canary_failures_no_runs(mock_aws_clients):
1938 | """Test analyze_canary_failures when no runs are found."""
1939 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
1940 |
1941 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': []}
1942 | mock_aws_clients['synthetics_client'].get_canary.return_value = {
1943 | 'Canary': {'Name': 'test-canary'}
1944 | }
1945 |
1946 | result = await analyze_canary_failures('test-canary', 'us-east-1')
1947 |
1948 | assert 'No run history found for test-canary' in result
1949 |
1950 |
1951 | @pytest.mark.asyncio
1952 | async def test_analyze_canary_failures_healthy_canary(mock_aws_clients):
1953 | """Test analyze_canary_failures with healthy canary."""
1954 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
1955 |
1956 | mock_runs = [
1957 | {
1958 | 'Id': 'run1',
1959 | 'Status': {'State': 'PASSED'},
1960 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
1961 | }
1962 | ]
1963 |
1964 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
1965 | mock_aws_clients['synthetics_client'].get_canary.return_value = {
1966 | 'Canary': {'Name': 'test-canary'}
1967 | }
1968 |
1969 | with patch(
1970 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
1971 | ) as mock_insights:
1972 | mock_insights.return_value = 'Telemetry insights available'
1973 |
1974 | result = await analyze_canary_failures('test-canary', 'us-east-1')
1975 |
1976 | assert 'Canary is healthy - no failures since last success' in result
1977 | assert '🔍 Comprehensive Failure Analysis for test-canary' in result
1978 |
1979 |
1980 | @pytest.mark.asyncio
1981 | async def test_analyze_canary_failures_telemetry_unavailable(mock_aws_clients):
1982 | """Test analyze_canary_failures when telemetry is unavailable."""
1983 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
1984 |
1985 | mock_runs = [
1986 | {
1987 | 'Id': 'run1',
1988 | 'Status': {'State': 'PASSED'},
1989 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
1990 | }
1991 | ]
1992 |
1993 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
1994 | mock_aws_clients['synthetics_client'].get_canary.return_value = {
1995 | 'Canary': {'Name': 'test-canary'}
1996 | }
1997 |
1998 | with patch(
1999 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2000 | ) as mock_insights:
2001 | mock_insights.side_effect = Exception('Telemetry API error')
2002 |
2003 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2004 |
2005 | assert 'Telemetry API unavailable: Telemetry API error' in result
2006 |
2007 |
2008 | @pytest.mark.asyncio
2009 | async def test_analyze_canary_failures_with_failures(mock_aws_clients):
2010 | """Test analyze_canary_failures with actual failures."""
2011 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2012 |
2013 | mock_runs = [
2014 | {
2015 | 'Id': 'failed-run-1',
2016 | 'Status': {'State': 'FAILED', 'StateReason': 'Navigation timeout'},
2017 | 'Timeline': {'Started': '2024-01-01T01:00:00Z'},
2018 | },
2019 | {
2020 | 'Id': 'failed-run-2',
2021 | 'Status': {'State': 'FAILED', 'StateReason': 'Navigation timeout'},
2022 | 'Timeline': {'Started': '2024-01-01T00:30:00Z'},
2023 | },
2024 | {
2025 | 'Id': 'success-run',
2026 | 'Status': {'State': 'PASSED'},
2027 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2028 | },
2029 | ]
2030 |
2031 | mock_canary = {
2032 | 'Name': 'test-canary',
2033 | 'ArtifactS3Location': 's3://test-bucket/canary/us-east-1/test-canary',
2034 | }
2035 |
2036 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2037 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2038 |
2039 | # Mock S3 artifacts
2040 | mock_aws_clients['s3_client'].list_objects_v2.return_value = {
2041 | 'Contents': [
2042 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/test.har'},
2043 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/screenshot.png'},
2044 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/logs.txt'},
2045 | ]
2046 | }
2047 |
2048 | with patch(
2049 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2050 | ) as mock_insights:
2051 | with patch(
2052 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_har_file'
2053 | ) as mock_har:
2054 | with patch(
2055 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_screenshots'
2056 | ) as mock_screenshots:
2057 | with patch(
2058 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_log_files'
2059 | ) as mock_logs:
2060 | mock_insights.return_value = 'Telemetry insights'
2061 | mock_har.return_value = {
2062 | 'failed_requests': 2,
2063 | 'total_requests': 10,
2064 | 'request_details': [
2065 | {'url': 'https://example.com', 'status': 500, 'time': 1000}
2066 | ],
2067 | }
2068 | mock_screenshots.return_value = {'insights': ['Screenshot analysis']}
2069 | mock_logs.return_value = {'insights': ['Log analysis']}
2070 |
2071 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2072 |
2073 | assert 'Found 2 consecutive failures since last success' in result
2074 | assert 'All failures have same cause: Navigation timeout' in result
2075 |
2076 |
2077 | @pytest.mark.asyncio
2078 | async def test_analyze_canary_failures_iam_analysis(mock_aws_clients):
2079 | """Test analyze_canary_failures with IAM-related failures."""
2080 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2081 |
2082 | mock_runs = [
2083 | {
2084 | 'Id': 'failed-run',
2085 | 'Status': {'State': 'FAILED', 'StateReason': 'Access denied'},
2086 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2087 | }
2088 | ]
2089 |
2090 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2091 |
2092 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2093 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2094 |
2095 | with patch(
2096 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2097 | ) as mock_insights:
2098 | with patch(
2099 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_iam_role_and_policies'
2100 | ) as mock_iam:
2101 | with patch(
2102 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.check_resource_arns_correct'
2103 | ) as mock_arn:
2104 | mock_insights.return_value = 'Telemetry insights'
2105 | mock_iam.return_value = {
2106 | 'status': 'issues_found',
2107 | 'checks': {'role_exists': 'PASS', 'policies_attached': 'FAIL'},
2108 | 'issues_found': ['Missing S3 permissions'],
2109 | 'recommendations': ['Add S3 read permissions'],
2110 | }
2111 | mock_arn.return_value = {
2112 | 'correct': False,
2113 | 'error': 'Invalid bucket ARN',
2114 | 'issues': ['Bucket name mismatch'],
2115 | }
2116 |
2117 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2118 |
2119 | assert 'RUNNING COMPREHENSIVE IAM ANALYSIS' in result
2120 | assert 'IAM Role Analysis Status: issues_found' in result
2121 | assert 'ALL IAM ISSUES FOUND (2 total):' in result
2122 | assert 'IAM Policy: Missing S3 permissions' in result
2123 | assert 'Resource ARN: Bucket name mismatch' in result
2124 |
2125 |
2126 | @pytest.mark.asyncio
2127 | async def test_analyze_canary_failures_enospc_error(mock_aws_clients):
2128 | """Test analyze_canary_failures with ENOSPC error."""
2129 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2130 |
2131 | mock_runs = [
2132 | {
2133 | 'Id': 'failed-run',
2134 | 'Status': {'State': 'FAILED', 'StateReason': 'ENOSPC: no space left on device'},
2135 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2136 | }
2137 | ]
2138 |
2139 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2140 |
2141 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2142 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2143 |
2144 | with patch(
2145 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2146 | ) as mock_insights:
2147 | with patch(
2148 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.extract_disk_memory_usage_metrics'
2149 | ) as mock_metrics:
2150 | mock_insights.return_value = 'Telemetry insights'
2151 | mock_metrics.return_value = {
2152 | 'maxEphemeralStorageUsageInMb': 512.5,
2153 | 'maxEphemeralStorageUsagePercent': 95.2,
2154 | }
2155 |
2156 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2157 |
2158 | assert 'DISK USAGE ROOT CAUSE ANALYSIS:' in result
2159 | assert 'Storage: 512.5 MB peak' in result
2160 | assert 'Usage: 95.2% peak' in result
2161 |
2162 |
2163 | @pytest.mark.asyncio
2164 | async def test_analyze_canary_failures_protocol_error(mock_aws_clients):
2165 | """Test analyze_canary_failures with protocol error."""
2166 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2167 |
2168 | mock_runs = [
2169 | {
2170 | 'Id': 'failed-run',
2171 | 'Status': {
2172 | 'State': 'FAILED',
2173 | 'StateReason': 'Protocol error (Target.activateTarget): Session closed',
2174 | },
2175 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2176 | }
2177 | ]
2178 |
2179 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2180 |
2181 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2182 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2183 |
2184 | with patch(
2185 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2186 | ) as mock_insights:
2187 | with patch(
2188 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.extract_disk_memory_usage_metrics'
2189 | ) as mock_metrics:
2190 | mock_insights.return_value = 'Telemetry insights'
2191 | mock_metrics.return_value = {'maxSyntheticsMemoryUsageInMB': 256.8}
2192 |
2193 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2194 |
2195 | assert 'MEMORY USAGE ROOT CAUSE ANALYSIS:' in result
2196 | assert 'Memory: 256.8 MB peak' in result
2197 |
2198 |
2199 | @pytest.mark.asyncio
2200 | async def test_analyze_canary_failures_navigation_timeout_with_har(mock_aws_clients):
2201 | """Test analyze_canary_failures with navigation timeout and HAR analysis."""
2202 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2203 |
2204 | mock_runs = [
2205 | {
2206 | 'Id': 'failed-run',
2207 | 'Status': {'State': 'FAILED', 'StateReason': 'Navigation timed out after 30000ms'},
2208 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2209 | }
2210 | ]
2211 |
2212 | mock_canary = {
2213 | 'Name': 'test-canary',
2214 | 'ArtifactS3Location': 's3://test-bucket/canary/us-east-1/test-canary',
2215 | }
2216 |
2217 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2218 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2219 |
2220 | # Mock S3 to return HAR files
2221 | mock_aws_clients['s3_client'].list_objects_v2.return_value = {
2222 | 'Contents': [
2223 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/test.har'},
2224 | ]
2225 | }
2226 |
2227 | with patch(
2228 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2229 | ) as mock_insights:
2230 | with patch(
2231 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_har_file'
2232 | ) as mock_har:
2233 | mock_insights.return_value = 'Telemetry insights'
2234 | mock_har.return_value = {
2235 | 'failed_requests': 5,
2236 | 'total_requests': 10,
2237 | 'request_details': [
2238 | {'url': 'https://example.com/slow', 'status': 200, 'time': 5000}
2239 | ],
2240 | 'insights': [
2241 | 'Slow DNS resolution detected',
2242 | 'High server response time',
2243 | 'Network connectivity issues',
2244 | 'Resource loading delays',
2245 | 'JavaScript execution timeout',
2246 | ],
2247 | }
2248 |
2249 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2250 |
2251 | assert '🔍 Comprehensive Failure Analysis for test-canary' in result
2252 | assert 'Slow DNS resolution detected' in result
2253 |
2254 |
2255 | @pytest.mark.asyncio
2256 | async def test_analyze_canary_failures_s3_exception(mock_aws_clients):
2257 | """Test analyze_canary_failures when S3 operations fail."""
2258 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2259 |
2260 | mock_runs = [
2261 | {
2262 | 'Id': 'failed-run',
2263 | 'Status': {'State': 'FAILED', 'StateReason': 'Test failure'},
2264 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2265 | }
2266 | ]
2267 |
2268 | mock_canary = {
2269 | 'Name': 'test-canary',
2270 | 'ArtifactS3Location': 's3://test-bucket/canary/us-east-1/test-canary',
2271 | }
2272 |
2273 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2274 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2275 |
2276 | mock_aws_clients['s3_client'].list_objects_v2.side_effect = Exception('S3 access denied')
2277 |
2278 | with patch(
2279 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2280 | ) as mock_insights:
2281 | with patch(
2282 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_canary_logs_with_time_window'
2283 | ) as mock_logs:
2284 | mock_insights.return_value = 'Telemetry insights'
2285 | mock_logs.return_value = {
2286 | 'status': 'success',
2287 | 'time_window': '2024-01-01 00:00:00 - 2024-01-01 00:05:00',
2288 | 'total_events': 5,
2289 | 'error_events': [
2290 | {'timestamp': datetime.now(timezone.utc), 'message': 'Test error message'}
2291 | ],
2292 | }
2293 |
2294 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2295 |
2296 | # Should fall back to CloudWatch Logs analysis when S3 fails
2297 | assert '⚠️ Artifacts not available - Checking CloudWatch Logs for root cause' in result
2298 | assert 'CLOUDWATCH LOGS ANALYSIS' in result
2299 |
2300 |
2301 | @pytest.mark.asyncio
2302 | async def test_analyze_canary_failures_visual_variation(mock_aws_clients):
2303 | """Test analyze_canary_failures with visual variation error."""
2304 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2305 |
2306 | mock_runs = [
2307 | {
2308 | 'Id': 'failed-run',
2309 | 'Status': {'State': 'FAILED', 'StateReason': 'Visual variation detected'},
2310 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2311 | }
2312 | ]
2313 |
2314 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2315 |
2316 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2317 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2318 |
2319 | with patch(
2320 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2321 | ) as mock_insights:
2322 | with patch(
2323 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_code'
2324 | ) as mock_code:
2325 | mock_insights.return_value = 'Telemetry insights'
2326 | mock_code.return_value = {'code_content': 'const synthetics = require("Synthetics");'}
2327 |
2328 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2329 |
2330 | assert 'VISUAL MONITORING ISSUE DETECTED' in result
2331 | assert 'Website UI changed - not a technical failure' in result
2332 | assert 'canary code:' in result
2333 |
2334 |
2335 | @pytest.mark.asyncio
2336 | async def test_analyze_canary_failures_get_canary_code_exception(mock_aws_clients):
2337 | """Test analyze_canary_failures when get_canary_code fails."""
2338 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2339 |
2340 | mock_runs = [
2341 | {
2342 | 'Id': 'failed-run',
2343 | 'Status': {'State': 'FAILED', 'StateReason': 'Test failure'},
2344 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2345 | }
2346 | ]
2347 |
2348 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2349 |
2350 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2351 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2352 |
2353 | with patch(
2354 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2355 | ) as mock_insights:
2356 | with patch(
2357 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_code'
2358 | ) as mock_code:
2359 | mock_insights.return_value = 'Telemetry insights'
2360 | # Make get_canary_code raise an exception
2361 | mock_code.side_effect = Exception('Code retrieval failed')
2362 |
2363 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2364 |
2365 | assert 'Note: Could not retrieve canary code: Code retrieval failed' in result
2366 |
2367 |
2368 | @pytest.mark.asyncio
2369 | async def test_analyze_canary_failures_iam_analysis_exception(mock_aws_clients):
2370 | """Test analyze_canary_failures when IAM analysis fails."""
2371 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2372 |
2373 | mock_runs = [
2374 | {
2375 | 'Id': 'failed-run',
2376 | 'Status': {'State': 'FAILED', 'StateReason': 'Access denied'},
2377 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2378 | }
2379 | ]
2380 |
2381 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2382 |
2383 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2384 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2385 |
2386 | with patch(
2387 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2388 | ) as mock_insights:
2389 | with patch(
2390 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_iam_role_and_policies'
2391 | ) as mock_iam:
2392 | mock_insights.return_value = 'Telemetry insights'
2393 | # Make IAM analysis raise an exception
2394 | mock_iam.side_effect = Exception('IAM analysis failed')
2395 |
2396 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2397 |
2398 | assert '⚠️ IAM analysis failed: IAM analysis failed' in result
2399 |
2400 |
2401 | @pytest.mark.asyncio
2402 | async def test_analyze_canary_failures_disk_usage_exception(mock_aws_clients):
2403 | """Test analyze_canary_failures when disk usage analysis fails."""
2404 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2405 |
2406 | mock_runs = [
2407 | {
2408 | 'Id': 'failed-run',
2409 | 'Status': {'State': 'FAILED', 'StateReason': 'ENOSPC: no space left on device'},
2410 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2411 | }
2412 | ]
2413 |
2414 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2415 |
2416 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2417 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2418 |
2419 | with patch(
2420 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2421 | ) as mock_insights:
2422 | with patch(
2423 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.extract_disk_memory_usage_metrics'
2424 | ) as mock_metrics:
2425 | mock_insights.return_value = 'Telemetry insights'
2426 | # Make disk usage analysis raise an exception
2427 | mock_metrics.side_effect = Exception('Disk usage analysis failed')
2428 |
2429 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2430 |
2431 | assert (
2432 | '⚠️ Could not generate disk usage debugging code: Disk usage analysis failed'
2433 | in result
2434 | )
2435 |
2436 |
2437 | @pytest.mark.asyncio
2438 | async def test_analyze_canary_failures_memory_usage_exception(mock_aws_clients):
2439 | """Test analyze_canary_failures when memory usage analysis fails."""
2440 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2441 |
2442 | mock_runs = [
2443 | {
2444 | 'Id': 'failed-run',
2445 | 'Status': {
2446 | 'State': 'FAILED',
2447 | 'StateReason': 'Protocol error (Target.activateTarget): Session closed',
2448 | },
2449 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2450 | }
2451 | ]
2452 |
2453 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2454 |
2455 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2456 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2457 |
2458 | with patch(
2459 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2460 | ) as mock_insights:
2461 | with patch(
2462 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.extract_disk_memory_usage_metrics'
2463 | ) as mock_metrics:
2464 | mock_insights.return_value = 'Telemetry insights'
2465 | # Make memory usage analysis raise an exception
2466 | mock_metrics.side_effect = Exception('Memory usage analysis failed')
2467 |
2468 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2469 |
2470 | assert (
2471 | '⚠️ Could not collect memory usage metrics: Memory usage analysis failed' in result
2472 | )
2473 |
2474 |
2475 | @pytest.mark.asyncio
2476 | async def test_analyze_canary_failures_har_timeout_exception(mock_aws_clients):
2477 | """Test analyze_canary_failures when HAR timeout analysis fails."""
2478 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2479 |
2480 | mock_runs = [
2481 | {
2482 | 'Id': 'failed-run',
2483 | 'Status': {'State': 'FAILED', 'StateReason': 'Navigation timed out after 30000ms'},
2484 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2485 | }
2486 | ]
2487 |
2488 | mock_canary = {
2489 | 'Name': 'test-canary',
2490 | 'ArtifactS3Location': 's3://test-bucket/canary/us-east-1/test-canary',
2491 | }
2492 |
2493 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2494 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2495 |
2496 | # Mock S3 to return HAR files
2497 | mock_aws_clients['s3_client'].list_objects_v2.return_value = {
2498 | 'Contents': [
2499 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/test.har'},
2500 | ]
2501 | }
2502 |
2503 | with patch(
2504 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2505 | ) as mock_insights:
2506 | with patch(
2507 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_har_file'
2508 | ) as mock_har:
2509 | mock_insights.return_value = 'Telemetry insights'
2510 | # Make HAR analysis raise an exception
2511 | mock_har.side_effect = Exception('HAR analysis failed')
2512 |
2513 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2514 |
2515 | assert '⚠️ HAR analysis failed: HAR analysis failed' in result
2516 |
2517 |
2518 | @pytest.mark.asyncio
2519 | async def test_analyze_canary_failures_success_artifacts_exception(mock_aws_clients):
2520 | """Test analyze_canary_failures when success artifacts retrieval fails."""
2521 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2522 |
2523 | mock_runs = [
2524 | {
2525 | 'Id': 'failed-run',
2526 | 'Status': {'State': 'FAILED', 'StateReason': 'Test failure'},
2527 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2528 | },
2529 | {
2530 | 'Id': 'success-run',
2531 | 'Status': {'State': 'PASSED'},
2532 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2533 | },
2534 | ]
2535 |
2536 | mock_canary = {
2537 | 'Name': 'test-canary',
2538 | 'ArtifactS3Location': 's3://test-bucket/canary/us-east-1/test-canary',
2539 | }
2540 |
2541 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2542 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2543 |
2544 | # Mock S3 to return failure artifacts but fail on success artifacts
2545 | def s3_side_effect(*args, **kwargs):
2546 | prefix = kwargs.get('Prefix', '')
2547 | if 'success' in prefix or len(prefix.split('/')) > 5: # Simulate success path failure
2548 | raise Exception('Success artifacts access failed')
2549 | return {
2550 | 'Contents': [
2551 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/test.har'},
2552 | ]
2553 | }
2554 |
2555 | mock_aws_clients['s3_client'].list_objects_v2.side_effect = s3_side_effect
2556 |
2557 | with patch(
2558 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2559 | ) as mock_insights:
2560 | with patch(
2561 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_har_file'
2562 | ) as mock_har:
2563 | mock_insights.return_value = 'Telemetry insights'
2564 | mock_har.return_value = {
2565 | 'failed_requests': 2,
2566 | 'total_requests': 10,
2567 | 'request_details': [],
2568 | }
2569 |
2570 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2571 |
2572 | # Should still process failure artifacts even if success artifacts fail
2573 | assert '🔍 Comprehensive Failure Analysis for test-canary' in result
2574 |
2575 |
2576 | @pytest.mark.asyncio
2577 | async def test_analyze_canary_failures_no_failure_timestamp(mock_aws_clients):
2578 | """Test analyze_canary_failures when failure has no timestamp."""
2579 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2580 |
2581 | mock_runs = [
2582 | {
2583 | 'Id': 'failed-run',
2584 | 'Status': {'State': 'FAILED', 'StateReason': 'Test failure'},
2585 | 'Timeline': {}, # No Started timestamp
2586 | }
2587 | ]
2588 |
2589 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2590 |
2591 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2592 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2593 |
2594 | with patch(
2595 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2596 | ) as mock_insights:
2597 | mock_insights.return_value = 'Telemetry insights'
2598 |
2599 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2600 |
2601 | assert '📋 No failure timestamp available for targeted log analysis' in result
2602 |
2603 |
2604 | @pytest.mark.asyncio
2605 | async def test_analyze_canary_failures_log_analysis_failure(mock_aws_clients):
2606 | """Test analyze_canary_failures when log analysis fails."""
2607 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2608 |
2609 | mock_runs = [
2610 | {
2611 | 'Id': 'failed-run',
2612 | 'Status': {'State': 'FAILED', 'StateReason': 'Test failure'},
2613 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2614 | }
2615 | ]
2616 |
2617 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2618 |
2619 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2620 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2621 |
2622 | with patch(
2623 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2624 | ) as mock_insights:
2625 | with patch(
2626 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_canary_logs_with_time_window'
2627 | ) as mock_logs:
2628 | mock_insights.return_value = 'Telemetry insights'
2629 | mock_logs.return_value = {
2630 | 'status': 'failed',
2631 | 'insights': ['Log analysis failed due to missing log group'],
2632 | }
2633 |
2634 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2635 |
2636 | assert '📋 Log analysis failed due to missing log group' in result
2637 |
2638 |
2639 | @pytest.mark.asyncio
2640 | async def test_analyze_canary_failures_main_exception(mock_aws_clients):
2641 | """Test analyze_canary_failures when main function fails."""
2642 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2643 |
2644 | # Make get_canary_runs raise an exception
2645 | mock_aws_clients['synthetics_client'].get_canary_runs.side_effect = Exception('API error')
2646 |
2647 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2648 |
2649 | assert '❌ Error in comprehensive failure analysis: API error' in result
2650 |
2651 |
2652 | @pytest.mark.asyncio
2653 | async def test_analyze_canary_failures_no_har_files_navigation_timeout(mock_aws_clients):
2654 | """Test analyze_canary_failures navigation timeout without HAR files."""
2655 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2656 |
2657 | mock_runs = [
2658 | {
2659 | 'Id': 'failed-run',
2660 | 'Status': {'State': 'FAILED', 'StateReason': 'Navigation timed out after 30000ms'},
2661 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2662 | }
2663 | ]
2664 |
2665 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2666 |
2667 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2668 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2669 |
2670 | with patch(
2671 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2672 | ) as mock_insights:
2673 | mock_insights.return_value = 'Telemetry insights'
2674 |
2675 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2676 |
2677 | assert 'NAVIGATION TIMEOUT DETECTED:' in result
2678 | assert 'No HAR files available for detailed analysis' in result
2679 |
2680 |
2681 | @pytest.mark.asyncio
2682 | async def test_analyze_canary_failures_artifact_location_without_s3_prefix(mock_aws_clients):
2683 | """Test analyze_canary_failures with artifact location without s3:// prefix."""
2684 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2685 |
2686 | mock_runs = [
2687 | {
2688 | 'Id': 'failed-run',
2689 | 'Status': {'State': 'FAILED', 'StateReason': 'Test failure'},
2690 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2691 | }
2692 | ]
2693 |
2694 | mock_canary = {
2695 | 'Name': 'test-canary',
2696 | 'ArtifactS3Location': 'test-bucket/canary/us-east-1/test-canary', # No s3:// prefix
2697 | }
2698 |
2699 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2700 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2701 |
2702 | # Mock S3 to return artifacts
2703 | mock_aws_clients['s3_client'].list_objects_v2.return_value = {
2704 | 'Contents': [
2705 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/test.har'},
2706 | ]
2707 | }
2708 |
2709 | with patch(
2710 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2711 | ) as mock_insights:
2712 | with patch(
2713 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_har_file'
2714 | ) as mock_har:
2715 | mock_insights.return_value = 'Telemetry insights'
2716 | mock_har.return_value = {
2717 | 'failed_requests': 1,
2718 | 'total_requests': 5,
2719 | 'request_details': [],
2720 | }
2721 |
2722 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2723 |
2724 | # Should still process artifacts even without s3:// prefix
2725 | assert 'FAILURE ANALYSIS' in result
2726 |
2727 |
2728 | @pytest.mark.asyncio
2729 | async def test_analyze_canary_failures_empty_base_path(mock_aws_clients):
2730 | """Test analyze_canary_failures with empty base path."""
2731 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2732 |
2733 | mock_runs = [
2734 | {
2735 | 'Id': 'failed-run',
2736 | 'Status': {'State': 'FAILED', 'StateReason': 'Test failure'},
2737 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2738 | }
2739 | ]
2740 |
2741 | mock_canary = {
2742 | 'Name': 'test-canary',
2743 | 'ArtifactS3Location': 's3://test-bucket', # Only bucket, no path
2744 | }
2745 |
2746 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2747 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2748 |
2749 | # Mock S3 to return artifacts
2750 | mock_aws_clients['s3_client'].list_objects_v2.return_value = {
2751 | 'Contents': [
2752 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/test.har'},
2753 | ]
2754 | }
2755 |
2756 | with patch(
2757 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2758 | ) as mock_insights:
2759 | with patch(
2760 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_har_file'
2761 | ) as mock_har:
2762 | mock_insights.return_value = 'Telemetry insights'
2763 | mock_har.return_value = {
2764 | 'failed_requests': 1,
2765 | 'total_requests': 5,
2766 | 'request_details': [],
2767 | }
2768 |
2769 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2770 |
2771 | # Should construct default canary path when base_path is empty
2772 | assert 'FAILURE ANALYSIS' in result
2773 |
2774 |
2775 | @pytest.mark.asyncio
2776 | async def test_analyze_canary_failures_multiple_failure_causes(mock_aws_clients):
2777 | """Test analyze_canary_failures with multiple different failure causes."""
2778 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2779 |
2780 | mock_runs = [
2781 | {
2782 | 'Id': 'failed-run-1',
2783 | 'Status': {'State': 'FAILED', 'StateReason': 'Navigation timeout'},
2784 | 'Timeline': {'Started': '2024-01-01T00:00:00Z'},
2785 | },
2786 | {
2787 | 'Id': 'failed-run-2',
2788 | 'Status': {'State': 'FAILED', 'StateReason': 'Access denied'},
2789 | 'Timeline': {'Started': '2024-01-01T00:01:00Z'},
2790 | },
2791 | {
2792 | 'Id': 'success-run',
2793 | 'Status': {'State': 'PASSED'},
2794 | 'Timeline': {'Started': '2023-12-31T23:59:00Z'},
2795 | },
2796 | ]
2797 |
2798 | mock_canary = {'Name': 'test-canary', 'ArtifactS3Location': ''}
2799 |
2800 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2801 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2802 |
2803 | with patch(
2804 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2805 | ) as mock_insights:
2806 | mock_insights.return_value = 'Telemetry insights'
2807 |
2808 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2809 |
2810 | assert 'Multiple failure causes (2 different issues):' in result
2811 | assert '1. **Navigation timeout** (1 occurrences)' in result
2812 | assert '2. **Access denied** (1 occurrences)' in result
2813 |
2814 |
2815 | @pytest.mark.asyncio
2816 | async def test_analyze_canary_failures_no_failure_time_fallback(mock_aws_clients):
2817 | """Test analyze_canary_failures fallback when no failure time."""
2818 | from awslabs.cloudwatch_applicationsignals_mcp_server.server import analyze_canary_failures
2819 |
2820 | mock_runs = [
2821 | {
2822 | 'Id': 'failed-run',
2823 | 'Status': {'State': 'FAILED', 'StateReason': 'Test failure'},
2824 | 'Timeline': {}, # No Started time
2825 | }
2826 | ]
2827 |
2828 | mock_canary = {
2829 | 'Name': 'test-canary',
2830 | 'ArtifactS3Location': 's3://test-bucket/canary/us-east-1/test-canary',
2831 | }
2832 |
2833 | mock_aws_clients['synthetics_client'].get_canary_runs.return_value = {'CanaryRuns': mock_runs}
2834 | mock_aws_clients['synthetics_client'].get_canary.return_value = {'Canary': mock_canary}
2835 |
2836 | # Mock S3 to return artifacts
2837 | mock_aws_clients['s3_client'].list_objects_v2.return_value = {
2838 | 'Contents': [
2839 | {'Key': 'canary/us-east-1/test-canary/2024/01/01/test.har'},
2840 | ]
2841 | }
2842 |
2843 | with patch(
2844 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.get_canary_metrics_and_service_insights'
2845 | ) as mock_insights:
2846 | with patch(
2847 | 'awslabs.cloudwatch_applicationsignals_mcp_server.server.analyze_har_file'
2848 | ) as mock_har:
2849 | mock_insights.return_value = 'Telemetry insights'
2850 | mock_har.return_value = {
2851 | 'failed_requests': 1,
2852 | 'total_requests': 5,
2853 | 'request_details': [],
2854 | }
2855 |
2856 | result = await analyze_canary_failures('test-canary', 'us-east-1')
2857 |
2858 | # Should use current time when no failure time available
2859 | assert 'FAILURE ANALYSIS' in result
2860 |
2861 |
2862 | def test_filter_operation_targets_fault_to_availability():
2863 | """Test _filter_operation_targets converts Fault to Availability."""
2864 | provided = [
2865 | {
2866 | 'Type': 'service_operation',
2867 | 'Data': {
2868 | 'ServiceOperation': {
2869 | 'Service': {'Type': 'Service', 'Name': 'test-service'},
2870 | 'Operation': 'GET /api',
2871 | 'MetricType': 'Fault',
2872 | }
2873 | },
2874 | }
2875 | ]
2876 |
2877 | operation_targets, has_wildcards = _filter_operation_targets(provided)
2878 |
2879 | # Verify the MetricType was changed from Fault to Availability
2880 | assert len(operation_targets) == 1
2881 | assert operation_targets[0]['Data']['ServiceOperation']['MetricType'] == 'Availability'
2882 | assert has_wildcards is False
2883 |
2884 |
2885 | def test_filter_operation_targets_non_fault_unchanged():
2886 | """Test _filter_operation_targets leaves non-Fault MetricTypes unchanged."""
2887 | provided = [
2888 | {
2889 | 'Type': 'service_operation',
2890 | 'Data': {
2891 | 'ServiceOperation': {
2892 | 'Service': {'Type': 'Service', 'Name': 'test-service'},
2893 | 'Operation': 'GET /api',
2894 | 'MetricType': 'Latency',
2895 | }
2896 | },
2897 | },
2898 | {
2899 | 'Type': 'service_operation',
2900 | 'Data': {
2901 | 'ServiceOperation': {
2902 | 'Service': {'Type': 'Service', 'Name': 'test-service-2'},
2903 | 'Operation': 'POST /api',
2904 | 'MetricType': 'Error',
2905 | }
2906 | },
2907 | },
2908 | ]
2909 |
2910 | operation_targets, has_wildcards = _filter_operation_targets(provided)
2911 |
2912 | # Verify non-Fault MetricTypes are unchanged
2913 | assert len(operation_targets) == 2
2914 | assert operation_targets[0]['Data']['ServiceOperation']['MetricType'] == 'Latency'
2915 | assert operation_targets[1]['Data']['ServiceOperation']['MetricType'] == 'Error'
2916 | assert has_wildcards is False
2917 |
2918 |
2919 | def test_filter_operation_targets_multiple_fault_conversions():
2920 | """Test _filter_operation_targets converts multiple Fault entries to Availability."""
2921 | provided = [
2922 | {
2923 | 'Type': 'service_operation',
2924 | 'Data': {
2925 | 'ServiceOperation': {
2926 | 'Service': {'Type': 'Service', 'Name': 'service-1'},
2927 | 'Operation': 'GET /api',
2928 | 'MetricType': 'Fault',
2929 | }
2930 | },
2931 | },
2932 | {
2933 | 'Type': 'service_operation',
2934 | 'Data': {
2935 | 'ServiceOperation': {
2936 | 'Service': {'Type': 'Service', 'Name': 'service-2'},
2937 | 'Operation': 'POST /api',
2938 | 'MetricType': 'Latency',
2939 | }
2940 | },
2941 | },
2942 | {
2943 | 'Type': 'service_operation',
2944 | 'Data': {
2945 | 'ServiceOperation': {
2946 | 'Service': {'Type': 'Service', 'Name': 'service-3'},
2947 | 'Operation': 'PUT /api',
2948 | 'MetricType': 'Fault',
2949 | }
2950 | },
2951 | },
2952 | ]
2953 |
2954 | operation_targets, has_wildcards = _filter_operation_targets(provided)
2955 |
2956 | # Verify multiple Fault entries are converted
2957 | assert len(operation_targets) == 3
2958 | assert operation_targets[0]['Data']['ServiceOperation']['MetricType'] == 'Availability'
2959 | assert operation_targets[1]['Data']['ServiceOperation']['MetricType'] == 'Latency'
2960 | assert operation_targets[2]['Data']['ServiceOperation']['MetricType'] == 'Availability'
2961 | assert has_wildcards is False
2962 |
2963 |
2964 | def test_filter_operation_targets_with_wildcards():
2965 | """Test _filter_operation_targets detects wildcards and converts Fault to Availability."""
2966 | provided = [
2967 | {
2968 | 'Type': 'service_operation',
2969 | 'Data': {
2970 | 'ServiceOperation': {
2971 | 'Service': {'Type': 'Service', 'Name': '*payment*'},
2972 | 'Operation': '*GET*',
2973 | 'MetricType': 'Fault',
2974 | }
2975 | },
2976 | }
2977 | ]
2978 |
2979 | operation_targets, has_wildcards = _filter_operation_targets(provided)
2980 |
2981 | # Verify wildcard detection and Fault conversion
2982 | assert len(operation_targets) == 1
2983 | assert operation_targets[0]['Data']['ServiceOperation']['MetricType'] == 'Availability'
2984 | assert has_wildcards is True
2985 |
2986 |
2987 | def test_filter_operation_targets_ignores_non_service_operation():
2988 | """Test _filter_operation_targets ignores non-service_operation targets."""
2989 | provided = [
2990 | {
2991 | 'Type': 'service',
2992 | 'Data': {'Service': {'Type': 'Service', 'Name': 'test-service'}},
2993 | },
2994 | {
2995 | 'Type': 'service_operation',
2996 | 'Data': {
2997 | 'ServiceOperation': {
2998 | 'Service': {'Type': 'Service', 'Name': 'test-service'},
2999 | 'Operation': 'GET /api',
3000 | 'MetricType': 'Fault',
3001 | }
3002 | },
3003 | },
3004 | ]
3005 |
3006 | operation_targets, has_wildcards = _filter_operation_targets(provided)
3007 |
3008 | # Verify only service_operation targets are included
3009 | assert len(operation_targets) == 1
3010 | assert operation_targets[0]['Type'] == 'service_operation'
3011 | assert operation_targets[0]['Data']['ServiceOperation']['MetricType'] == 'Availability'
3012 | assert has_wildcards is False
3013 |
3014 |
3015 | def test_filter_operation_targets_empty_metric_type():
3016 | """Test _filter_operation_targets handles empty MetricType gracefully."""
3017 | provided = [
3018 | {
3019 | 'Type': 'service_operation',
3020 | 'Data': {
3021 | 'ServiceOperation': {
3022 | 'Service': {'Type': 'Service', 'Name': 'test-service'},
3023 | 'Operation': 'GET /api',
3024 | 'MetricType': '',
3025 | }
3026 | },
3027 | }
3028 | ]
3029 |
3030 | operation_targets, has_wildcards = _filter_operation_targets(provided)
3031 |
3032 | # Verify empty MetricType is unchanged
3033 | assert len(operation_targets) == 1
3034 | assert operation_targets[0]['Data']['ServiceOperation']['MetricType'] == ''
3035 | assert has_wildcards is False
3036 |
3037 |
3038 | def test_filter_operation_targets_missing_metric_type():
3039 | """Test _filter_operation_targets handles missing MetricType gracefully."""
3040 | provided = [
3041 | {
3042 | 'Type': 'service_operation',
3043 | 'Data': {
3044 | 'ServiceOperation': {
3045 | 'Service': {'Type': 'Service', 'Name': 'test-service'},
3046 | 'Operation': 'GET /api',
3047 | # MetricType is missing
3048 | }
3049 | },
3050 | }
3051 | ]
3052 |
3053 | operation_targets, has_wildcards = _filter_operation_targets(provided)
3054 |
3055 | # Verify missing MetricType doesn't cause errors
3056 | assert len(operation_targets) == 1
3057 | # MetricType should remain missing (empty string from .get())
3058 | assert operation_targets[0]['Data']['ServiceOperation'].get('MetricType', '') == ''
3059 | assert has_wildcards is False
3060 |
3061 |
3062 | def test_filter_operation_targets_case_sensitive():
3063 | """Test _filter_operation_targets is case-sensitive for Fault conversion."""
3064 | provided = [
3065 | {
3066 | 'Type': 'service_operation',
3067 | 'Data': {
3068 | 'ServiceOperation': {
3069 | 'Service': {'Type': 'Service', 'Name': 'test-service'},
3070 | 'Operation': 'GET /api',
3071 | 'MetricType': 'fault', # lowercase
3072 | }
3073 | },
3074 | },
3075 | {
3076 | 'Type': 'service_operation',
3077 | 'Data': {
3078 | 'ServiceOperation': {
3079 | 'Service': {'Type': 'Service', 'Name': 'test-service-2'},
3080 | 'Operation': 'POST /api',
3081 | 'MetricType': 'FAULT', # uppercase
3082 | }
3083 | },
3084 | },
3085 | ]
3086 |
3087 | operation_targets, has_wildcards = _filter_operation_targets(provided)
3088 |
3089 | # Verify only exact case "Fault" is converted
3090 | assert len(operation_targets) == 2
3091 | assert operation_targets[0]['Data']['ServiceOperation']['MetricType'] == 'fault' # unchanged
3092 | assert operation_targets[1]['Data']['ServiceOperation']['MetricType'] == 'FAULT' # unchanged
3093 | assert has_wildcards is False
3094 |
```