This is page 508 of 514. 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-appsignals-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
├── 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
│ │ │ └── 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
│ │ │ ├── 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
│ │ │ └── mutable_sql_detector.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
│ │ ├── 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.py
│ │ │ ├── prompts
│ │ │ │ ├── __init__.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_data.py
│ │ │ │ ├── sitewise_gateways.py
│ │ │ │ └── sitewise_metadata_transfer.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
│ │ │ ├── test_client.py
│ │ │ ├── test_init.py
│ │ │ ├── test_main.py
│ │ │ ├── test_models.py
│ │ │ ├── test_server.py
│ │ │ ├── test_sitewise_access.py
│ │ │ ├── test_sitewise_asset_models.py
│ │ │ ├── test_sitewise_assets.py
│ │ │ ├── test_sitewise_data.py
│ │ │ ├── test_sitewise_gateways.py
│ │ │ ├── test_sitewise_metadata_transfer.py
│ │ │ └── test_validation.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
│ │ ├── 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
│ │ │ ├── tools
│ │ │ │ ├── common
│ │ │ │ │ └── base_tool.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
│ │ │ │ ├── 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
│ │ │ ├── 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_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_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_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_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-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
│ │ │ ├── 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-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
│ │ │ │ ├── data
│ │ │ │ │ └── metric_metadata.json
│ │ │ │ ├── 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_metrics_error_handling.py
│ │ │ │ ├── test_metrics_models.py
│ │ │ │ ├── test_metrics_server.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
│ │ │ ├── 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_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
│ │ │ ├── mutable_sql_detector.py
│ │ │ └── server.py
│ │ ├── CHANGELOG.md
│ │ ├── docker-healthcheck.sh
│ │ ├── Dockerfile
│ │ ├── LICENSE
│ │ ├── NOTICE
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── conftest.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/aws-pricing-mcp-server/tests/test_server.py:
--------------------------------------------------------------------------------
```python
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Tests for the server module of the aws-pricing-mcp-server."""
16 |
17 | import pytest
18 | from awslabs.aws_pricing_mcp_server.models import PricingFilter
19 | from awslabs.aws_pricing_mcp_server.pricing_transformer import (
20 | _is_free_product,
21 | )
22 | from awslabs.aws_pricing_mcp_server.server import (
23 | analyze_cdk_project_wrapper,
24 | generate_cost_report_wrapper,
25 | get_bedrock_patterns,
26 | get_price_list_urls,
27 | get_pricing,
28 | get_pricing_attribute_values,
29 | get_pricing_service_attributes,
30 | get_pricing_service_codes,
31 | )
32 | from unittest.mock import patch
33 |
34 |
35 | class TestAnalyzeCdkProject:
36 | """Tests for the analyze_cdk_project_wrapper function."""
37 |
38 | @pytest.mark.asyncio
39 | async def test_analyze_valid_project(self, mock_context, sample_cdk_project):
40 | """Test analyzing a valid CDK project."""
41 | result = await analyze_cdk_project_wrapper(mock_context, sample_cdk_project)
42 |
43 | assert result is not None
44 | assert result['status'] == 'success'
45 | assert 'services' in result
46 |
47 | # Check for expected services
48 | services = {service['name'] for service in result['services']}
49 | assert 'lambda' in services
50 | assert 'dynamodb' in services
51 | assert 's3' in services
52 | assert 'iam' in services
53 |
54 | @pytest.mark.asyncio
55 | async def test_analyze_invalid_project(self, mock_context, temp_output_dir):
56 | """Test analyzing an invalid/empty project directory."""
57 | result = await analyze_cdk_project_wrapper(mock_context, temp_output_dir)
58 |
59 | assert result is not None
60 | assert result['status'] == 'success'
61 | assert 'services' in result
62 | assert (
63 | len(result['services']) == 0
64 | ) # Empty project still returns success with empty services
65 |
66 | @pytest.mark.asyncio
67 | async def test_analyze_nonexistent_project(self, mock_context):
68 | """Test analyzing a nonexistent project directory."""
69 | result = await analyze_cdk_project_wrapper(mock_context, '/nonexistent/path')
70 |
71 | assert result is not None
72 | assert 'services' in result
73 | assert len(result['services']) == 0 # Nonexistent path returns success with empty services
74 |
75 |
76 | class TestGetPricing:
77 | """Tests for the get_pricing function."""
78 |
79 | @pytest.mark.asyncio
80 | async def test_get_valid_pricing(self, mock_boto3, mock_context):
81 | """Test getting pricing for a valid service."""
82 | with patch('boto3.Session', return_value=mock_boto3.Session()):
83 | result = await get_pricing(mock_context, 'AWSLambda', 'us-west-2')
84 |
85 | assert result is not None
86 | assert result['status'] == 'success'
87 | assert result['service_name'] == 'AWSLambda'
88 | assert 'data' in result
89 | assert isinstance(result['data'], list)
90 | assert len(result['data']) > 0
91 | assert 'message' in result
92 | assert 'AWSLambda' in result['message']
93 | assert 'us-west-2' in result['message']
94 |
95 | @pytest.mark.asyncio
96 | async def test_get_pricing_with_filters(self, mock_boto3, mock_context):
97 | """Test getting pricing with filters."""
98 | # Create filters using the Pydantic models
99 | filters = [
100 | PricingFilter(Field='instanceType', Value='t3.medium'),
101 | PricingFilter(Field='location', Value='US East (N. Virginia)', Type='EQUALS'),
102 | ]
103 |
104 | with patch('boto3.Session', return_value=mock_boto3.Session()):
105 | result = await get_pricing(mock_context, 'AmazonEC2', 'us-east-1', filters)
106 |
107 | assert result is not None
108 | assert result['status'] == 'success'
109 | assert result['service_name'] == 'AmazonEC2'
110 | assert isinstance(result['data'], list)
111 |
112 | # Verify that the mocked pricing client was called with correct filters
113 | pricing_client = mock_boto3.Session().client('pricing')
114 | pricing_client.get_products.assert_called_once()
115 | call_args = pricing_client.get_products.call_args[1]
116 | assert 'Filters' in call_args
117 | assert len(call_args['Filters']) == 3 # region + 2 custom filters
118 |
119 | # Check that our custom filters are included
120 | filter_fields = [f['Field'] for f in call_args['Filters']]
121 | assert 'instanceType' in filter_fields
122 | assert 'location' in filter_fields
123 | assert 'regionCode' in filter_fields # Always added by the function
124 |
125 | @pytest.mark.asyncio
126 | async def test_pricing_filter_model_validation(self):
127 | """Test that PricingFilter model validates correctly."""
128 | # Test valid filter creation
129 | valid_filter = PricingFilter(Field='instanceType', Value='t3.medium')
130 | assert valid_filter.field == 'instanceType'
131 | assert valid_filter.value == 't3.medium'
132 | assert valid_filter.type == 'EQUALS'
133 |
134 | # Test serialization with aliases
135 | filter_dict = valid_filter.model_dump(by_alias=True)
136 | assert 'Field' in filter_dict
137 | assert 'Value' in filter_dict
138 | assert 'Type' in filter_dict
139 | assert filter_dict['Field'] == 'instanceType'
140 | assert filter_dict['Value'] == 't3.medium'
141 | assert filter_dict['Type'] == 'EQUALS'
142 |
143 | @pytest.mark.asyncio
144 | async def test_new_filter_types_validation(self):
145 | """Test that new filter types work correctly."""
146 | # Test ANY_OF filter type
147 | any_of_filter = PricingFilter(
148 | Field='instanceType', Value=['t3.medium', 'm5.large'], Type='ANY_OF'
149 | )
150 | assert any_of_filter.type == 'ANY_OF'
151 | assert any_of_filter.value == ['t3.medium', 'm5.large']
152 |
153 | # Test CONTAINS filter type
154 | contains_filter = PricingFilter(Field='instanceType', Value='m5', Type='CONTAINS')
155 | assert contains_filter.type == 'CONTAINS'
156 | assert contains_filter.value == 'm5'
157 |
158 | # Test NONE_OF filter type
159 | none_of_filter = PricingFilter(Field='instanceType', Value=['t2', 'm4'], Type='NONE_OF')
160 | assert none_of_filter.type == 'NONE_OF'
161 | assert none_of_filter.value == ['t2', 'm4']
162 |
163 | # Test serialization converts ANY_OF and NONE_OF to comma-separated strings
164 | any_of_dict = any_of_filter.model_dump(by_alias=True)
165 | assert any_of_dict['Type'] == 'ANY_OF'
166 | assert any_of_dict['Value'] == 't3.medium,m5.large' # Should be comma-separated string
167 |
168 | contains_dict = contains_filter.model_dump(by_alias=True)
169 | assert contains_dict['Type'] == 'CONTAINS'
170 | assert contains_dict['Value'] == 'm5' # Should remain as string
171 |
172 | none_of_dict = none_of_filter.model_dump(by_alias=True)
173 | assert none_of_dict['Type'] == 'NONE_OF'
174 | assert none_of_dict['Value'] == 't2,m4' # Should be comma-separated string
175 |
176 | @pytest.mark.asyncio
177 | async def test_filter_serialization_comma_separated(self):
178 | """Test that ANY_OF and NONE_OF filters serialize values as comma-separated strings."""
179 | # Test ANY_OF filter serialization
180 | any_of_filter = PricingFilter(
181 | Field='instanceType', Value=['t3.medium', 'm5.large'], Type='ANY_OF'
182 | )
183 | serialized = any_of_filter.model_dump(by_alias=True)
184 | assert serialized['Value'] == 't3.medium,m5.large' # Should be comma-separated string
185 | assert serialized['Type'] == 'ANY_OF'
186 |
187 | # Test NONE_OF filter serialization
188 | none_of_filter = PricingFilter(
189 | Field='instanceType', Value=['t2.micro', 'm4.large'], Type='NONE_OF'
190 | )
191 | serialized = none_of_filter.model_dump(by_alias=True)
192 | assert serialized['Value'] == 't2.micro,m4.large' # Should be comma-separated string
193 | assert serialized['Type'] == 'NONE_OF'
194 |
195 | # Test EQUALS filter serialization (should not change)
196 | equals_filter = PricingFilter(Field='instanceType', Value='m5.large', Type='EQUALS')
197 | serialized = equals_filter.model_dump(by_alias=True)
198 | assert serialized['Value'] == 'm5.large' # Should remain a string
199 | assert serialized['Type'] == 'EQUALS'
200 |
201 | # Test CONTAINS filter serialization (should not change)
202 | contains_filter = PricingFilter(Field='instanceType', Value='m5', Type='CONTAINS')
203 | serialized = contains_filter.model_dump(by_alias=True)
204 | assert serialized['Value'] == 'm5' # Should remain a string
205 | assert serialized['Type'] == 'CONTAINS'
206 |
207 | @pytest.mark.asyncio
208 | async def test_multi_region_pricing(self, mock_boto3, mock_context):
209 | """Test getting pricing for multiple regions using ANY_OF filter."""
210 | with patch('boto3.Session', return_value=mock_boto3.Session()):
211 | result = await get_pricing(
212 | mock_context, 'AmazonEC2', ['us-east-1', 'us-west-2', 'eu-west-1']
213 | )
214 |
215 | assert result is not None
216 | assert result['status'] == 'success'
217 | assert result['service_name'] == 'AmazonEC2'
218 |
219 | # Verify that the mocked pricing client was called with correct multi-region filter
220 | pricing_client = mock_boto3.Session().client('pricing')
221 | pricing_client.get_products.assert_called_once()
222 | call_args = pricing_client.get_products.call_args[1]
223 | assert 'Filters' in call_args
224 |
225 | # Should have exactly one region filter (automatically added)
226 | region_filters = [f for f in call_args['Filters'] if f['Field'] == 'regionCode']
227 | assert len(region_filters) == 1
228 |
229 | # The region filter should use ANY_OF with comma-separated values
230 | region_filter = region_filters[0]
231 | assert region_filter['Type'] == 'ANY_OF'
232 | assert region_filter['Value'] == 'us-east-1,us-west-2,eu-west-1'
233 |
234 | @pytest.mark.asyncio
235 | async def test_single_region_backward_compatibility(self, mock_boto3, mock_context):
236 | """Test that single region strings still work with EQUALS for backward compatibility."""
237 | with patch('boto3.Session', return_value=mock_boto3.Session()):
238 | result = await get_pricing(mock_context, 'AmazonEC2', 'us-east-1')
239 |
240 | assert result is not None
241 | assert result['status'] == 'success'
242 | assert result['service_name'] == 'AmazonEC2'
243 |
244 | # Verify that the mocked pricing client was called with EQUALS for single region
245 | pricing_client = mock_boto3.Session().client('pricing')
246 | pricing_client.get_products.assert_called_once()
247 | call_args = pricing_client.get_products.call_args[1]
248 | assert 'Filters' in call_args
249 |
250 | # Should have exactly one region filter
251 | region_filters = [f for f in call_args['Filters'] if f['Field'] == 'regionCode']
252 | assert len(region_filters) == 1
253 |
254 | # The region filter should use EQUALS for backward compatibility
255 | region_filter = region_filters[0]
256 | assert region_filter['Type'] == 'EQUALS'
257 | assert region_filter['Value'] == 'us-east-1'
258 |
259 | @pytest.mark.asyncio
260 | async def test_get_pricing_response_structure_validation(self, mock_boto3, mock_context):
261 | """Test that the response structure is properly validated."""
262 | # Mock a more realistic pricing response
263 | pricing_client = mock_boto3.Session().client('pricing')
264 | pricing_client.get_products.return_value = {
265 | 'PriceList': [
266 | '{"product":{"sku":"ABC123","productFamily":"Compute","attributes":{"instanceType":"t3.medium"}},"terms":{"OnDemand":{"ABC123.TERM1":{"priceDimensions":{"ABC123.TERM1.DIM1":{"unit":"Hrs","pricePerUnit":{"USD":"0.0416"}}}}}},"serviceCode":"AmazonEC2"}'
267 | ]
268 | }
269 |
270 | with patch('boto3.Session', return_value=mock_boto3.Session()):
271 | result = await get_pricing(mock_context, 'AmazonEC2', 'us-east-1')
272 |
273 | # Validate top-level response structure
274 | assert result['status'] == 'success'
275 | assert result['service_name'] == 'AmazonEC2'
276 | assert isinstance(result['data'], list)
277 | assert len(result['data']) == 1
278 | assert isinstance(result['message'], str)
279 |
280 | # Validate the pricing data structure (data is already parsed from JSON)
281 | pricing_item = result['data'][0]
282 |
283 | # Validate required fields in pricing item
284 | assert 'product' in pricing_item
285 | assert 'terms' in pricing_item
286 | assert 'sku' in pricing_item['product']
287 | assert 'attributes' in pricing_item['product']
288 | assert 'OnDemand' in pricing_item['terms']
289 |
290 | # Validate pricing structure
291 | product = pricing_item['product']
292 | assert product['sku'] == 'ABC123'
293 | assert 'instanceType' in product['attributes']
294 | assert product['attributes']['instanceType'] == 't3.medium'
295 |
296 | @pytest.mark.asyncio
297 | async def test_get_pricing_empty_results(self, mock_boto3, mock_context):
298 | """Test handling of empty pricing results."""
299 | pricing_client = mock_boto3.Session().client('pricing')
300 | pricing_client.get_products.return_value = {'PriceList': []}
301 |
302 | with patch('boto3.Session', return_value=mock_boto3.Session()):
303 | result = await get_pricing(mock_context, 'InvalidService', 'us-west-2')
304 |
305 | assert result is not None
306 | assert result['status'] == 'error'
307 | assert result['error_type'] == 'empty_results'
308 | assert 'InvalidService' in result['message']
309 | assert 'No results found for given filters' in result['message']
310 | assert result['service_code'] == 'InvalidService'
311 | assert result['region'] == 'us-west-2'
312 | assert 'examples' in result
313 | assert 'Example service codes' in result['examples']
314 | assert 'Example regions' in result['examples']
315 | assert 'suggestion' in result
316 | assert (
317 | 'Verify that the service code is valid. Use get_service_codes() to get valid service codes'
318 | in result['suggestion']
319 | )
320 | assert (
321 | 'Validate region and filter values using get_pricing_attribute_values()'
322 | in result['suggestion']
323 | )
324 | assert 'Test with fewer filters' in result['suggestion']
325 | mock_context.error.assert_called_once()
326 |
327 | @pytest.mark.asyncio
328 | async def test_get_pricing_api_error(self, mock_boto3, mock_context):
329 | """Test handling of API errors."""
330 | pricing_client = mock_boto3.Session().client('pricing')
331 | pricing_client.get_products.side_effect = Exception('API Error')
332 |
333 | with patch('boto3.Session', return_value=mock_boto3.Session()):
334 | result = await get_pricing(mock_context, 'AWSLambda', 'us-west-2')
335 |
336 | assert result is not None
337 | assert result['status'] == 'error'
338 | assert result['error_type'] == 'api_error'
339 | assert 'API Error' in result['message']
340 | assert result['service_code'] == 'AWSLambda'
341 | assert result['region'] == 'us-west-2'
342 | assert 'suggestion' in result
343 | mock_context.error.assert_called_once()
344 |
345 | @pytest.mark.asyncio
346 | async def test_get_pricing_data_processing_error(self, mock_boto3, mock_context):
347 | """Test handling of data processing errors in transform_pricing_data."""
348 | pricing_client = mock_boto3.Session().client('pricing')
349 | pricing_client.get_products.return_value = {'PriceList': ['invalid json']}
350 |
351 | with patch('boto3.Session', return_value=mock_boto3.Session()):
352 | result = await get_pricing(mock_context, 'AWSLambda', 'us-west-2')
353 |
354 | assert result is not None
355 | assert result['status'] == 'error'
356 | assert result['error_type'] == 'data_processing_error'
357 | assert 'Failed to process pricing data' in result['message']
358 | assert result['service_code'] == 'AWSLambda'
359 | assert result['region'] == 'us-west-2'
360 | mock_context.error.assert_called_once()
361 |
362 | @pytest.mark.asyncio
363 | async def test_get_pricing_client_creation_error(self, mock_context):
364 | """Test handling of client creation errors."""
365 | with patch(
366 | 'awslabs.aws_pricing_mcp_server.server.create_pricing_client',
367 | side_effect=Exception('Client creation failed'),
368 | ):
369 | result = await get_pricing(mock_context, 'AWSLambda', 'us-west-2')
370 |
371 | assert result is not None
372 | assert result['status'] == 'error'
373 | assert result['error_type'] == 'client_creation_failed'
374 | assert 'Failed to create AWS Pricing client' in result['message']
375 | assert 'Client creation failed' in result['message']
376 | assert result['service_code'] == 'AWSLambda'
377 | assert result['region'] == 'us-west-2'
378 | mock_context.error.assert_called_once()
379 |
380 | @pytest.mark.asyncio
381 | async def test_get_pricing_result_threshold_exceeded(self, mock_boto3, mock_context):
382 | """Test that the tool returns an error when result character count exceeds the threshold."""
383 | # Create a mock response with large JSON records that exceed character threshold
384 | # Each record is about 500 characters, so 100 records = ~50,000 characters (exceeds 40,000 default)
385 | large_price_list = []
386 | for i in range(100):
387 | record = f'{{"sku":"SKU{i:03d}","product":{{"productFamily":"Compute Instance","attributes":{{"instanceType":"m5.large","location":"US East (N. Virginia)","tenancy":"Shared","operatingSystem":"Linux"}}}},"terms":{{"OnDemand":{{"SKU{i:03d}.JRTCKXETXF":{{"priceDimensions":{{"SKU{i:03d}.JRTCKXETXF.6YS6EN2CT7":{{"unit":"Hrs","pricePerUnit":{{"USD":"0.096"}}}}}}}}}}}}}}'
388 | large_price_list.append(record)
389 |
390 | pricing_client = mock_boto3.Session().client('pricing')
391 | pricing_client.get_products.return_value = {'PriceList': large_price_list}
392 |
393 | with patch('boto3.Session', return_value=mock_boto3.Session()):
394 | result = await get_pricing(
395 | mock_context, 'AmazonEC2', 'us-east-1', max_allowed_characters=10000
396 | )
397 |
398 | assert result['status'] == 'error'
399 | assert result['error_type'] == 'result_too_large'
400 | assert 'exceeding the limit of 10,000' in result['message']
401 | assert 'output_options={"pricing_terms": ["OnDemand"]}' in result['message']
402 | assert 'significantly reduce response size' in result['suggestion']
403 | assert result['total_count'] == 100
404 | assert result['max_allowed_characters'] == 10000
405 | assert len(result['sample_records']) == 3 # First 3 records as context
406 | assert 'Add more specific filters' in result['suggestion']
407 | mock_context.error.assert_called_once()
408 |
409 | @pytest.mark.asyncio
410 | async def test_get_pricing_unlimited_results(self, mock_boto3, mock_context):
411 | """Test that max_allowed_characters=-1 allows unlimited results."""
412 | # Create a mock response with large records that would normally exceed character limit
413 | large_price_list = []
414 | for i in range(100):
415 | record = f'{{"sku":"SKU{i:03d}","product":{{"productFamily":"Compute Instance","attributes":{{"instanceType":"m5.large","location":"US East (N. Virginia)","tenancy":"Shared","operatingSystem":"Linux"}}}},"terms":{{"OnDemand":{{"SKU{i:03d}.JRTCKXETXF":{{"priceDimensions":{{"SKU{i:03d}.JRTCKXETXF.6YS6EN2CT7":{{"unit":"Hrs","pricePerUnit":{{"USD":"0.096"}}}}}}}}}}}}}}'
416 | large_price_list.append(record)
417 |
418 | pricing_client = mock_boto3.Session().client('pricing')
419 | pricing_client.get_products.return_value = {'PriceList': large_price_list}
420 |
421 | with patch('boto3.Session', return_value=mock_boto3.Session()):
422 | result = await get_pricing(
423 | mock_context, 'AmazonEC2', 'us-east-1', max_allowed_characters=-1
424 | )
425 |
426 | assert result['status'] == 'success'
427 | assert len(result['data']) == 100 # All results should be returned
428 | assert 'Retrieved pricing for AmazonEC2' in result['message']
429 | mock_context.info.assert_called_once()
430 |
431 | @pytest.mark.asyncio
432 | async def test_get_pricing_without_region(self, mock_boto3, mock_context):
433 | """Test get_pricing works without region parameter for global services."""
434 | pricing_client = mock_boto3.Session().client('pricing')
435 | pricing_client.get_products.return_value = {
436 | 'PriceList': ['{"sku":"ABC123","product":{"productFamily":"Data Transfer"}}']
437 | }
438 |
439 | with patch('boto3.Session', return_value=mock_boto3.Session()):
440 | result = await get_pricing(mock_context, 'AWSDataTransfer', region=None)
441 |
442 | assert result['status'] == 'success'
443 | assert result['service_name'] == 'AWSDataTransfer'
444 |
445 | # Verify no region filter was added
446 | pricing_client.get_products.assert_called_once()
447 | call_kwargs = pricing_client.get_products.call_args[1]
448 | assert 'Filters' in call_kwargs
449 | # Should have no filters since region is None and no other filters provided
450 | assert len(call_kwargs['Filters']) == 0
451 |
452 | @pytest.mark.asyncio
453 | async def test_get_pricing_region_none_explicit(self, mock_boto3, mock_context):
454 | """Test get_pricing with explicit region=None."""
455 | pricing_client = mock_boto3.Session().client('pricing')
456 | pricing_client.get_products.return_value = {
457 | 'PriceList': ['{"sku":"DEF456","product":{"productFamily":"CloudFront"}}']
458 | }
459 |
460 | with patch('boto3.Session', return_value=mock_boto3.Session()):
461 | result = await get_pricing(mock_context, 'AmazonCloudFront', None)
462 |
463 | assert result['status'] == 'success'
464 | assert result['service_name'] == 'AmazonCloudFront'
465 |
466 | # Verify API was called without region filter
467 | pricing_client.get_products.assert_called_once()
468 | call_kwargs = pricing_client.get_products.call_args[1]
469 | region_filters = [f for f in call_kwargs['Filters'] if f['Field'] == 'regionCode']
470 | assert len(region_filters) == 0
471 |
472 | @pytest.mark.asyncio
473 | async def test_get_pricing_with_filters_no_region(self, mock_boto3, mock_context):
474 | """Test get_pricing with filters but no region."""
475 | filters = [PricingFilter(Field='operation', Value='DataTransfer-Out-Bytes')]
476 |
477 | pricing_client = mock_boto3.Session().client('pricing')
478 | pricing_client.get_products.return_value = {
479 | 'PriceList': ['{"sku":"GHI789","product":{"productFamily":"Data Transfer"}}']
480 | }
481 |
482 | with patch('boto3.Session', return_value=mock_boto3.Session()):
483 | result = await get_pricing(mock_context, 'AWSDataTransfer', None, filters)
484 |
485 | assert result['status'] == 'success'
486 |
487 | # Verify only custom filters were added, no region filter
488 | pricing_client.get_products.assert_called_once()
489 | call_kwargs = pricing_client.get_products.call_args[1]
490 | assert len(call_kwargs['Filters']) == 1
491 | assert call_kwargs['Filters'][0]['Field'] == 'operation'
492 |
493 | @pytest.mark.asyncio
494 | async def test_get_pricing_custom_threshold(self, mock_context, mock_boto3):
495 | """Test that custom max_allowed_characters threshold works correctly."""
496 | # Create a mock response with small records that fit within lower thresholds
497 | small_price_list = [f'{{"sku":"SKU{i}","product":{{}}}}' for i in range(10)]
498 |
499 | pricing_client = mock_boto3.Session().client('pricing')
500 | pricing_client.get_products.return_value = {'PriceList': small_price_list}
501 |
502 | with patch('boto3.Session', return_value=mock_boto3.Session()):
503 | # Should succeed with threshold of 1000 characters (small records should fit)
504 | result = await get_pricing(
505 | mock_context, 'AmazonEC2', 'us-east-1', None, max_allowed_characters=1000
506 | )
507 | assert result['status'] == 'success'
508 | assert len(result['data']) == 10
509 |
510 | # Should fail with threshold of 100 characters (records are too large)
511 | result = await get_pricing(
512 | mock_context, 'AmazonEC2', 'us-east-1', None, max_allowed_characters=100
513 | )
514 | assert result['status'] == 'error'
515 | assert result['error_type'] == 'result_too_large'
516 | assert result['total_count'] == 10
517 | assert result['max_allowed_characters'] == 100
518 |
519 | @pytest.mark.asyncio
520 | @pytest.mark.parametrize(
521 | 'max_results,next_token,expected_max_results,expect_next_token',
522 | [
523 | (None, None, 100, False), # Default values
524 | (25, None, 25, False), # Custom max_results
525 | (None, 'test-token-123', 100, True), # Custom next_token
526 | (50, 'input-token-abc', 50, True), # Both parameters
527 | ],
528 | )
529 | async def test_get_pricing_pagination_parameters(
530 | self,
531 | mock_context,
532 | mock_boto3,
533 | max_results,
534 | next_token,
535 | expected_max_results,
536 | expect_next_token,
537 | ):
538 | """Test various pagination parameter combinations."""
539 | pricing_client = mock_boto3.Session().client('pricing')
540 | pricing_client.get_products.return_value = {'PriceList': ['{"sku":"ABC123"}']}
541 |
542 | kwargs = {'service_code': 'AmazonEC2', 'region': 'us-east-1'}
543 | if max_results is not None:
544 | kwargs['max_results'] = max_results
545 | if next_token is not None:
546 | kwargs['next_token'] = next_token
547 |
548 | with patch('boto3.Session', return_value=mock_boto3.Session()):
549 | result = await get_pricing(mock_context, **kwargs)
550 |
551 | assert result['status'] == 'success'
552 |
553 | # Verify pagination parameters were passed correctly
554 | pricing_client.get_products.assert_called_once()
555 | call_kwargs = pricing_client.get_products.call_args[1]
556 | assert call_kwargs['MaxResults'] == expected_max_results
557 |
558 | if expect_next_token:
559 | assert call_kwargs['NextToken'] == next_token
560 | else:
561 | assert 'NextToken' not in call_kwargs
562 |
563 | @pytest.mark.asyncio
564 | @pytest.mark.parametrize(
565 | 'api_response,expected_next_token_in_result',
566 | [
567 | (
568 | {'PriceList': ['{"sku":"ABC123"}'], 'NextToken': 'next-page-token-456'},
569 | 'next-page-token-456',
570 | ), # API returns NextToken
571 | ({'PriceList': ['{"sku":"ABC123"}']}, None), # API doesn't return NextToken
572 | (
573 | {
574 | 'PriceList': ['{"sku":"ABC123"}', '{"sku":"DEF456"}'],
575 | 'NextToken': 'final-token-789',
576 | },
577 | 'final-token-789',
578 | ), # Multiple records with NextToken
579 | ],
580 | )
581 | async def test_get_pricing_response_next_token(
582 | self, mock_context, mock_boto3, api_response, expected_next_token_in_result
583 | ):
584 | """Test next_token handling in response based on API response."""
585 | pricing_client = mock_boto3.Session().client('pricing')
586 | pricing_client.get_products.return_value = api_response
587 |
588 | with patch('boto3.Session', return_value=mock_boto3.Session()):
589 | result = await get_pricing(mock_context, 'AmazonEC2', 'us-east-1')
590 |
591 | assert result['status'] == 'success'
592 |
593 | if expected_next_token_in_result:
594 | assert 'next_token' in result
595 | assert result['next_token'] == expected_next_token_in_result
596 | else:
597 | assert 'next_token' not in result
598 |
599 |
600 | class TestGetBedrockPatterns:
601 | """Tests for the get_bedrock_patterns function."""
602 |
603 | @pytest.mark.asyncio
604 | async def test_get_patterns(self, mock_context):
605 | """Test getting Bedrock architecture patterns."""
606 | result = await get_bedrock_patterns(mock_context)
607 |
608 | assert result is not None
609 | assert isinstance(result, str)
610 | assert 'Bedrock' in result
611 | assert 'Knowledge Base' in result
612 |
613 |
614 | class TestGenerateCostReport:
615 | """Tests for the generate_cost_report_wrapper function."""
616 |
617 | @pytest.mark.asyncio
618 | async def test_generate_markdown_report(self, mock_context, sample_pricing_data_web):
619 | """Test generating a markdown cost report."""
620 | result = await generate_cost_report_wrapper(
621 | mock_context,
622 | pricing_data=sample_pricing_data_web,
623 | service_name='AWS Lambda',
624 | related_services=['DynamoDB'],
625 | pricing_model='ON DEMAND',
626 | assumptions=['Standard configuration'],
627 | exclusions=['Custom configurations'],
628 | format='markdown',
629 | )
630 |
631 | assert result is not None
632 | assert isinstance(result, str)
633 |
634 | @pytest.mark.asyncio
635 | async def test_generate_csv_report(self, mock_context, sample_pricing_data_web):
636 | """Test generating a CSV cost report."""
637 | result = await generate_cost_report_wrapper(
638 | mock_context,
639 | pricing_data=sample_pricing_data_web,
640 | service_name='AWS Lambda',
641 | format='csv',
642 | pricing_model='ON DEMAND',
643 | related_services=None,
644 | assumptions=None,
645 | exclusions=None,
646 | output_file=None,
647 | detailed_cost_data=None,
648 | recommendations=None,
649 | )
650 |
651 | assert result is not None
652 | assert isinstance(result, str)
653 | assert ',' in result # Verify it's CSV format
654 |
655 | # Verify basic structure
656 | lines = result.split('\n')
657 | assert len(lines) > 1 # Has header and data
658 |
659 | @pytest.mark.asyncio
660 | async def test_generate_report_with_detailed_data(
661 | self, mock_context, sample_pricing_data_web, temp_output_dir
662 | ):
663 | """Test generating a report with detailed cost data."""
664 | detailed_cost_data = {
665 | 'services': {
666 | 'AWS Lambda': {
667 | 'usage': '1M requests per month',
668 | 'estimated_cost': '$20.00',
669 | 'unit_pricing': {
670 | 'requests': '$0.20 per 1M requests',
671 | 'compute': '$0.0000166667 per GB-second',
672 | },
673 | }
674 | }
675 | }
676 |
677 | result = await generate_cost_report_wrapper(
678 | mock_context,
679 | pricing_data=sample_pricing_data_web,
680 | service_name='AWS Lambda',
681 | detailed_cost_data=detailed_cost_data,
682 | output_file=f'{temp_output_dir}/report.md',
683 | pricing_model='ON DEMAND',
684 | related_services=None,
685 | assumptions=None,
686 | exclusions=None,
687 | recommendations=None,
688 | )
689 |
690 | assert result is not None
691 | assert isinstance(result, str)
692 | assert 'AWS Lambda' in result
693 | assert '$20.00' in result
694 | assert '1M requests per month' in result
695 |
696 | @pytest.mark.asyncio
697 | async def test_generate_report_error_handling(self, mock_context):
698 | """Test error handling in report generation."""
699 | result = await generate_cost_report_wrapper(
700 | mock_context,
701 | pricing_data={'status': 'error'},
702 | service_name='Invalid Service',
703 | pricing_model='ON DEMAND',
704 | related_services=None,
705 | assumptions=None,
706 | exclusions=None,
707 | output_file=None,
708 | detailed_cost_data=None,
709 | recommendations=None,
710 | )
711 |
712 | assert '# Invalid Service Cost Analysis' in result
713 |
714 |
715 | class TestGetPricingServiceAttributes:
716 | """Tests for the get_pricing_service_attributes function."""
717 |
718 | @pytest.mark.asyncio
719 | @pytest.mark.parametrize(
720 | 'service_code,attributes,expected',
721 | [
722 | (
723 | 'AmazonEC2',
724 | ['instanceType', 'location', 'tenancy', 'operatingSystem'],
725 | ['instanceType', 'location', 'operatingSystem', 'tenancy'],
726 | ),
727 | (
728 | 'AmazonRDS',
729 | ['engineCode', 'instanceType', 'location', 'databaseEngine'],
730 | ['databaseEngine', 'engineCode', 'instanceType', 'location'],
731 | ),
732 | ],
733 | )
734 | async def test_get_pricing_service_attributes(
735 | self, mock_context, mock_boto3, service_code, attributes, expected
736 | ):
737 | """Test getting service attributes for various AWS services."""
738 | pricing_client = mock_boto3.Session().client('pricing')
739 | pricing_client.describe_services.return_value = {
740 | 'Services': [{'ServiceCode': service_code, 'AttributeNames': attributes}]
741 | }
742 |
743 | with patch('boto3.Session', return_value=mock_boto3.Session()):
744 | result = await get_pricing_service_attributes(mock_context, service_code)
745 |
746 | assert result == expected
747 | pricing_client.describe_services.assert_called_once_with(ServiceCode=service_code)
748 | mock_context.info.assert_called()
749 |
750 | @pytest.mark.asyncio
751 | @pytest.mark.parametrize(
752 | 'service_code,attributes,filter_pattern,expected_matches,expected_count,test_description',
753 | [
754 | # Basic filtering tests
755 | (
756 | 'AmazonEC2',
757 | ['instanceType', 'instanceFamily', 'location', 'memory', 'vcpu'],
758 | 'instance',
759 | ['instanceFamily', 'instanceType'],
760 | None,
761 | 'basic_instance_filter',
762 | ),
763 | (
764 | 'AmazonRDS',
765 | ['engineCode', 'instanceType', 'location', 'databaseEngine', 'storageType'],
766 | 'engine',
767 | ['databaseEngine', 'engineCode'],
768 | None,
769 | 'engine_attributes_filter',
770 | ),
771 | (
772 | 'AmazonEC2',
773 | ['instanceType', 'location', 'tenancy', 'operatingSystem', 'storage'],
774 | 'Type',
775 | ['instanceType', 'storageType']
776 | if 'storageType'
777 | in ['instanceType', 'location', 'tenancy', 'operatingSystem', 'storage']
778 | else ['instanceType'],
779 | None,
780 | 'case_insensitive_type_filter',
781 | ),
782 | # Regex pattern tests
783 | (
784 | 'AmazonEC2',
785 | [
786 | 'instanceType',
787 | 'instanceFamily',
788 | 'location',
789 | 'memory',
790 | 'vcpu',
791 | 'networkPerformance',
792 | ],
793 | '^instance',
794 | ['instanceFamily', 'instanceType'],
795 | None,
796 | 'starts_with_instance_regex',
797 | ),
798 | (
799 | 'AmazonRDS',
800 | ['engineCode', 'instanceType', 'location', 'databaseEngine', 'deploymentOption'],
801 | 'Type$',
802 | ['instanceType'],
803 | None,
804 | 'ends_with_type_regex',
805 | ),
806 | (
807 | 'AmazonS3',
808 | ['storageClass', 'location', 'durability', 'availability'],
809 | 'location|durability',
810 | ['durability', 'location'],
811 | None,
812 | 'alternation_regex',
813 | ),
814 | # No filter cases
815 | (
816 | 'AmazonEC2',
817 | ['instanceType', 'location', 'tenancy'],
818 | None,
819 | None,
820 | 3,
821 | 'no_filter_all_attributes',
822 | ),
823 | (
824 | 'AmazonRDS',
825 | ['engineCode', 'instanceType', 'location'],
826 | '',
827 | None,
828 | 3,
829 | 'empty_filter_all_attributes',
830 | ),
831 | # Edge cases - removed filter_no_matches as it's properly tested in error scenarios
832 | (
833 | 'AmazonS3',
834 | ['storageClass', 'location'],
835 | 'Storage',
836 | ['storageClass'],
837 | None,
838 | 'case_insensitive_partial_match',
839 | ),
840 | ],
841 | )
842 | async def test_get_pricing_service_attributes_filtering_happy_path(
843 | self,
844 | mock_context,
845 | mock_boto3,
846 | service_code,
847 | attributes,
848 | filter_pattern,
849 | expected_matches,
850 | expected_count,
851 | test_description,
852 | ):
853 | """Test successful filtering of service attributes with various regex patterns."""
854 | pricing_client = mock_boto3.Session().client('pricing')
855 | pricing_client.describe_services.return_value = {
856 | 'Services': [{'ServiceCode': service_code, 'AttributeNames': attributes}]
857 | }
858 |
859 | with patch('boto3.Session', return_value=mock_boto3.Session()):
860 | result = await get_pricing_service_attributes(
861 | mock_context, service_code, filter=filter_pattern
862 | )
863 |
864 | assert isinstance(result, list), (
865 | f'Failed {test_description}: expected list, got {type(result)}'
866 | )
867 |
868 | if expected_matches is not None:
869 | # Test specific matches
870 | assert len(result) == len(expected_matches), (
871 | f'Failed {test_description}: expected {len(expected_matches)} matches, got {len(result)}'
872 | )
873 | for attr in expected_matches:
874 | assert attr in result, f'Failed {test_description}: missing {attr} in results'
875 | # Verify results are sorted
876 | assert result == sorted(result), (
877 | f'Failed {test_description}: results not sorted properly'
878 | )
879 | elif expected_count is not None:
880 | # Test count-only cases (like no filter)
881 | assert len(result) == expected_count, (
882 | f'Failed {test_description}: expected {expected_count} attributes, got {len(result)}'
883 | )
884 |
885 | pricing_client.describe_services.assert_called_once_with(ServiceCode=service_code)
886 | mock_context.info.assert_called()
887 |
888 | @pytest.mark.asyncio
889 | @pytest.mark.parametrize(
890 | 'service_code,attributes,filter_pattern,expected_error_type,test_description',
891 | [
892 | (
893 | 'AmazonEC2',
894 | ['instanceType', 'location', 'tenancy'],
895 | 'nonexistent',
896 | 'no_matches_found',
897 | 'filter_no_matches',
898 | ),
899 | (
900 | 'AmazonRDS',
901 | ['engineCode', 'instanceType', 'location'],
902 | '[invalid',
903 | 'invalid_regex',
904 | 'invalid_regex_pattern',
905 | ),
906 | (
907 | 'AmazonS3',
908 | ['storageClass', 'location'],
909 | '\\',
910 | 'invalid_regex',
911 | 'invalid_escape_sequence',
912 | ),
913 | ],
914 | )
915 | async def test_get_pricing_service_attributes_filtering_errors(
916 | self,
917 | mock_context,
918 | mock_boto3,
919 | service_code,
920 | attributes,
921 | filter_pattern,
922 | expected_error_type,
923 | test_description,
924 | ):
925 | """Test error scenarios in service attributes filtering."""
926 | pricing_client = mock_boto3.Session().client('pricing')
927 | pricing_client.describe_services.return_value = {
928 | 'Services': [{'ServiceCode': service_code, 'AttributeNames': attributes}]
929 | }
930 |
931 | with patch('boto3.Session', return_value=mock_boto3.Session()):
932 | result = await get_pricing_service_attributes(
933 | mock_context, service_code, filter=filter_pattern
934 | )
935 |
936 | assert isinstance(result, dict), (
937 | f'Failed {test_description}: expected dict (error), got {type(result)}'
938 | )
939 | assert result['status'] == 'error', f'Failed {test_description}: expected error status'
940 | assert result['error_type'] == expected_error_type, (
941 | f'Failed {test_description}: expected error_type {expected_error_type}, got {result.get("error_type")}'
942 | )
943 | assert result['service_code'] == service_code, (
944 | f'Failed {test_description}: service_code not in error response'
945 | )
946 |
947 | if expected_error_type == 'invalid_regex':
948 | assert (
949 | filter_pattern in result['message']
950 | or 'Invalid regex pattern' in result['message']
951 | ), f'Failed {test_description}: filter pattern or regex error not in error message'
952 | assert 'examples' in result, (
953 | f'Failed {test_description}: examples not provided in error response'
954 | )
955 | elif expected_error_type == 'no_matches_found':
956 | assert 'No service attributes match' in result['message'], (
957 | f'Failed {test_description}: no matches message not in error response'
958 | )
959 |
960 | mock_context.error.assert_called()
961 |
962 | @pytest.mark.asyncio
963 | @pytest.mark.parametrize(
964 | 'error_scenario,service_code,expected_error_type,expected_in_message',
965 | [
966 | ('service_not_found', 'InvalidService', 'service_not_found', 'InvalidService'),
967 | ('api_error', 'AmazonEC2', 'api_error', 'API Error'),
968 | (
969 | 'empty_attributes',
970 | 'TestService',
971 | 'empty_results',
972 | 'no filterable attributes available',
973 | ),
974 | ],
975 | )
976 | async def test_get_pricing_service_attributes_errors(
977 | self,
978 | mock_context,
979 | mock_boto3,
980 | error_scenario,
981 | service_code,
982 | expected_error_type,
983 | expected_in_message,
984 | ):
985 | """Test various error scenarios for get_pricing_service_attributes."""
986 | if error_scenario == 'service_not_found':
987 | pricing_client = mock_boto3.Session().client('pricing')
988 | pricing_client.describe_services.return_value = {'Services': []}
989 |
990 | elif error_scenario == 'api_error':
991 | pricing_client = mock_boto3.Session().client('pricing')
992 | pricing_client.describe_services.side_effect = Exception('API Error')
993 |
994 | elif error_scenario == 'empty_attributes':
995 | pricing_client = mock_boto3.Session().client('pricing')
996 | pricing_client.describe_services.return_value = {
997 | 'Services': [{'ServiceCode': service_code, 'AttributeNames': []}]
998 | }
999 |
1000 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1001 | result = await get_pricing_service_attributes(mock_context, service_code)
1002 |
1003 | # Common assertions for all error scenarios
1004 | assert isinstance(result, dict)
1005 | assert result['status'] == 'error'
1006 | assert result['error_type'] == expected_error_type
1007 | assert expected_in_message in result['message']
1008 | assert result['service_code'] == service_code
1009 | assert 'suggestion' in result or 'examples' in result
1010 | mock_context.error.assert_called()
1011 |
1012 | @pytest.mark.asyncio
1013 | async def test_get_pricing_service_attributes_client_creation_error(self, mock_context):
1014 | """Test handling of client creation errors."""
1015 | with patch(
1016 | 'awslabs.aws_pricing_mcp_server.server.create_pricing_client',
1017 | side_effect=Exception('Client creation failed'),
1018 | ):
1019 | result = await get_pricing_service_attributes(mock_context, 'AmazonEC2')
1020 |
1021 | assert isinstance(result, dict)
1022 | assert result['status'] == 'error'
1023 | assert result['error_type'] == 'client_creation_failed'
1024 | assert 'Failed to create AWS Pricing client' in result['message']
1025 | assert 'Client creation failed' in result['message']
1026 | assert result['service_code'] == 'AmazonEC2'
1027 | mock_context.error.assert_called()
1028 |
1029 | @pytest.mark.asyncio
1030 | async def test_get_pricing_service_attributes_filter_with_api_errors(
1031 | self, mock_context, mock_boto3
1032 | ):
1033 | """Test that filtering errors are handled properly when combined with API errors."""
1034 | pricing_client = mock_boto3.Session().client('pricing')
1035 | pricing_client.describe_services.side_effect = Exception('API Error')
1036 |
1037 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1038 | result = await get_pricing_service_attributes(
1039 | mock_context, 'AmazonEC2', filter='instance'
1040 | )
1041 |
1042 | # Should return API error, not filter error
1043 | assert isinstance(result, dict)
1044 | assert result['status'] == 'error'
1045 | assert result['error_type'] == 'api_error'
1046 | assert 'API Error' in result['message']
1047 | mock_context.error.assert_called()
1048 |
1049 |
1050 | class TestGetPricingAttributeValues:
1051 | """Tests for the get_pricing_attribute_values function."""
1052 |
1053 | @pytest.mark.asyncio
1054 | @pytest.mark.parametrize(
1055 | 'service_code,attribute_names,raw_values_map,filters,expected,test_description',
1056 | [
1057 | # Basic success cases without filters
1058 | (
1059 | 'AmazonEC2',
1060 | ['instanceType'],
1061 | {'instanceType': ['t2.micro', 't2.small', 't3.medium', 'm5.large']},
1062 | None,
1063 | {'instanceType': ['m5.large', 't2.micro', 't2.small', 't3.medium']},
1064 | 'single_attribute_no_filter',
1065 | ),
1066 | (
1067 | 'AmazonEC2',
1068 | ['instanceType', 'location'],
1069 | {
1070 | 'instanceType': ['t2.micro', 't2.small', 't3.medium'],
1071 | 'location': ['US East (N. Virginia)', 'US West (Oregon)', 'EU (Ireland)'],
1072 | },
1073 | None,
1074 | {
1075 | 'instanceType': ['t2.micro', 't2.small', 't3.medium'],
1076 | 'location': ['EU (Ireland)', 'US East (N. Virginia)', 'US West (Oregon)'],
1077 | },
1078 | 'multiple_attributes_no_filter',
1079 | ),
1080 | (
1081 | 'AmazonRDS',
1082 | ['engineCode', 'instanceType'],
1083 | {
1084 | 'engineCode': ['mysql', 'postgres', 'aurora-mysql'],
1085 | 'instanceType': ['db.t3.micro', 'db.t3.small'],
1086 | },
1087 | None,
1088 | {
1089 | 'engineCode': ['aurora-mysql', 'mysql', 'postgres'],
1090 | 'instanceType': ['db.t3.micro', 'db.t3.small'],
1091 | },
1092 | 'different_service_no_filter',
1093 | ),
1094 | # Success cases with filters
1095 | (
1096 | 'AmazonEC2',
1097 | ['instanceType', 'location'],
1098 | {
1099 | 'instanceType': ['t2.micro', 't2.small', 't3.medium', 'm5.large'],
1100 | 'location': ['US East (N. Virginia)', 'US West (Oregon)', 'EU (Ireland)'],
1101 | },
1102 | {'instanceType': 't3'},
1103 | {
1104 | 'instanceType': ['t3.medium'], # Filtered
1105 | 'location': [
1106 | 'EU (Ireland)',
1107 | 'US East (N. Virginia)',
1108 | 'US West (Oregon)',
1109 | ], # All values
1110 | },
1111 | 'partial_filtering',
1112 | ),
1113 | (
1114 | 'AmazonEC2',
1115 | ['instanceType', 'location'],
1116 | {
1117 | 'instanceType': ['t2.micro', 't2.small', 't3.medium', 'm5.large'],
1118 | 'location': ['US East (N. Virginia)', 'US West (Oregon)', 'EU (Ireland)'],
1119 | },
1120 | {'instanceType': 't3', 'location': 'US'},
1121 | {
1122 | 'instanceType': ['t3.medium'], # Filtered
1123 | 'location': ['US East (N. Virginia)', 'US West (Oregon)'], # Filtered
1124 | },
1125 | 'filter_all_attributes',
1126 | ),
1127 | (
1128 | 'AmazonRDS',
1129 | ['engineCode', 'instanceType'],
1130 | {
1131 | 'engineCode': ['mysql', 'postgres', 'aurora-mysql', 'aurora-postgres'],
1132 | 'instanceType': ['db.t3.micro', 'db.t3.small', 'db.m5.large'],
1133 | },
1134 | {'engineCode': '^aurora', 'instanceType': r'\.t3\.'},
1135 | {
1136 | 'engineCode': ['aurora-mysql', 'aurora-postgres'], # Starts with aurora
1137 | 'instanceType': ['db.t3.micro', 'db.t3.small'], # Contains .t3.
1138 | },
1139 | 'regex_patterns',
1140 | ),
1141 | (
1142 | 'AmazonEC2',
1143 | ['instanceType', 'location'],
1144 | {
1145 | 'instanceType': ['t2.micro', 't2.small', 't3.medium'],
1146 | 'location': ['US East (N. Virginia)', 'US West (Oregon)'],
1147 | },
1148 | {'instanceType': 'nonexistent'},
1149 | {
1150 | 'instanceType': [], # No matches
1151 | 'location': ['US East (N. Virginia)', 'US West (Oregon)'], # All values
1152 | },
1153 | 'filter_no_matches',
1154 | ),
1155 | # Additional filter test cases
1156 | (
1157 | 'AmazonEC2',
1158 | ['instanceType'],
1159 | {'instanceType': ['t2.micro', 't3.medium']},
1160 | {},
1161 | {'instanceType': ['t2.micro', 't3.medium']},
1162 | 'empty_filters_dict',
1163 | ),
1164 | (
1165 | 'AmazonEC2',
1166 | ['location'],
1167 | {'location': ['US East (N. Virginia)', 'US West (Oregon)', 'EU (Ireland)']},
1168 | {'location': 'us'},
1169 | {'location': ['US East (N. Virginia)', 'US West (Oregon)']},
1170 | 'case_insensitive_filtering',
1171 | ),
1172 | (
1173 | 'AmazonEC2',
1174 | ['instanceType'],
1175 | {'instanceType': ['t2.micro', 't3.medium']},
1176 | {'instanceType': 't3', 'nonRequestedAttribute': 'someFilter'},
1177 | {'instanceType': ['t3.medium']},
1178 | 'ignore_non_requested_attribute_filter',
1179 | ),
1180 | ],
1181 | )
1182 | async def test_get_pricing_attribute_values_happy_path(
1183 | self,
1184 | mock_context,
1185 | mock_boto3,
1186 | service_code,
1187 | attribute_names,
1188 | raw_values_map,
1189 | filters,
1190 | expected,
1191 | test_description,
1192 | ):
1193 | """Test successful cases for getting attribute values with and without filtering."""
1194 | pricing_client = mock_boto3.Session().client('pricing')
1195 |
1196 | # Set up mock to return different values based on the attribute name
1197 | def mock_get_attribute_values(ServiceCode, AttributeName, **kwargs):
1198 | if AttributeName in raw_values_map:
1199 | return {
1200 | 'AttributeValues': [{'Value': val} for val in raw_values_map[AttributeName]]
1201 | }
1202 | return {'AttributeValues': []}
1203 |
1204 | pricing_client.get_attribute_values.side_effect = mock_get_attribute_values
1205 |
1206 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1207 | result = await get_pricing_attribute_values(
1208 | mock_context, service_code, attribute_names, filters
1209 | )
1210 |
1211 | assert result == expected, f"Failed test case '{test_description}'"
1212 | assert pricing_client.get_attribute_values.call_count == len(attribute_names)
1213 | mock_context.info.assert_called()
1214 |
1215 | @pytest.mark.asyncio
1216 | async def test_get_pricing_attribute_values_filter_invalid_regex(
1217 | self, mock_context, mock_boto3
1218 | ):
1219 | """Test error handling when invalid regex pattern is provided."""
1220 | pricing_client = mock_boto3.Session().client('pricing')
1221 | pricing_client.get_attribute_values.return_value = {
1222 | 'AttributeValues': [{'Value': 't2.micro'}, {'Value': 't3.medium'}]
1223 | }
1224 |
1225 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1226 | result = await get_pricing_attribute_values(
1227 | mock_context, 'AmazonEC2', ['instanceType'], {'instanceType': '[invalid'}
1228 | )
1229 |
1230 | # Should return error due to invalid regex
1231 | assert isinstance(result, dict)
1232 | assert result['status'] == 'error'
1233 | assert result['error_type'] == 'invalid_regex'
1234 | assert 'Invalid regex pattern "[invalid"' in result['message']
1235 | assert result['service_code'] == 'AmazonEC2'
1236 | assert result['attribute_name'] == 'instanceType'
1237 | assert result['filter_pattern'] == '[invalid'
1238 | assert 'examples' in result
1239 | mock_context.error.assert_called()
1240 |
1241 | @pytest.mark.asyncio
1242 | async def test_get_pricing_attribute_values_filter_for_non_requested_attribute(
1243 | self, mock_context, mock_boto3
1244 | ):
1245 | """Test that filters for non-requested attributes are ignored."""
1246 | pricing_client = mock_boto3.Session().client('pricing')
1247 | pricing_client.get_attribute_values.return_value = {
1248 | 'AttributeValues': [{'Value': 't2.micro'}, {'Value': 't3.medium'}]
1249 | }
1250 |
1251 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1252 | result = await get_pricing_attribute_values(
1253 | mock_context,
1254 | 'AmazonEC2',
1255 | ['instanceType'],
1256 | {'instanceType': 't3', 'nonRequestedAttribute': 'someFilter'},
1257 | )
1258 |
1259 | # Should succeed and ignore the filter for non-requested attribute
1260 | assert result == {'instanceType': ['t3.medium']}
1261 | assert pricing_client.get_attribute_values.call_count == 1
1262 | mock_context.info.assert_called()
1263 |
1264 | @pytest.mark.asyncio
1265 | async def test_get_pricing_attribute_values_empty_filters_dict(self, mock_context, mock_boto3):
1266 | """Test that empty filters dictionary works like no filters."""
1267 | pricing_client = mock_boto3.Session().client('pricing')
1268 | pricing_client.get_attribute_values.return_value = {
1269 | 'AttributeValues': [{'Value': 't2.micro'}, {'Value': 't3.medium'}]
1270 | }
1271 |
1272 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1273 | result = await get_pricing_attribute_values(
1274 | mock_context, 'AmazonEC2', ['instanceType'], {}
1275 | )
1276 |
1277 | # Should return all values (no filtering applied)
1278 | assert result == {'instanceType': ['t2.micro', 't3.medium']}
1279 | assert pricing_client.get_attribute_values.call_count == 1
1280 | mock_context.info.assert_called()
1281 |
1282 | @pytest.mark.asyncio
1283 | async def test_get_pricing_attribute_values_case_insensitive_filtering(
1284 | self, mock_context, mock_boto3
1285 | ):
1286 | """Test that filtering is case-insensitive."""
1287 | pricing_client = mock_boto3.Session().client('pricing')
1288 | pricing_client.get_attribute_values.return_value = {
1289 | 'AttributeValues': [
1290 | {'Value': 'US East (N. Virginia)'},
1291 | {'Value': 'US West (Oregon)'},
1292 | {'Value': 'EU (Ireland)'},
1293 | ]
1294 | }
1295 |
1296 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1297 | result = await get_pricing_attribute_values(
1298 | mock_context, 'AmazonEC2', ['location'], {'location': 'us'}
1299 | )
1300 |
1301 | # Should match both US regions (case-insensitive)
1302 | assert result == {'location': ['US East (N. Virginia)', 'US West (Oregon)']}
1303 | assert pricing_client.get_attribute_values.call_count == 1
1304 | mock_context.info.assert_called()
1305 |
1306 | @pytest.mark.asyncio
1307 | async def test_get_pricing_attribute_values_single_attribute_with_pagination(
1308 | self, mock_context, mock_boto3
1309 | ):
1310 | """Test getting attribute values with pagination handling for single attribute."""
1311 | pricing_client = mock_boto3.Session().client('pricing')
1312 | pricing_client.get_attribute_values.side_effect = [
1313 | {
1314 | 'AttributeValues': [{'Value': 't2.micro'}, {'Value': 't2.small'}],
1315 | 'NextToken': 'token',
1316 | },
1317 | {'AttributeValues': [{'Value': 't3.medium'}, {'Value': 'm5.large'}]},
1318 | ]
1319 |
1320 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1321 | result = await get_pricing_attribute_values(
1322 | mock_context, 'AmazonEC2', ['instanceType']
1323 | )
1324 |
1325 | expected = {'instanceType': ['m5.large', 't2.micro', 't2.small', 't3.medium']}
1326 | assert result == expected
1327 | assert pricing_client.get_attribute_values.call_count == 2
1328 | assert (
1329 | pricing_client.get_attribute_values.call_args_list[1][1].get('NextToken')
1330 | == 'token'
1331 | )
1332 |
1333 | @pytest.mark.asyncio
1334 | async def test_get_pricing_attribute_values_empty_attribute_list(
1335 | self, mock_context, mock_boto3
1336 | ):
1337 | """Test error handling when empty attribute list is provided."""
1338 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1339 | result = await get_pricing_attribute_values(mock_context, 'AmazonEC2', [])
1340 |
1341 | # Verify error response structure
1342 | assert isinstance(result, dict)
1343 | assert result['status'] == 'error'
1344 | assert result['error_type'] == 'empty_attribute_list'
1345 | assert 'No attribute names provided' in result['message']
1346 | assert result['service_code'] == 'AmazonEC2'
1347 | assert result['attribute_names'] == []
1348 | assert 'get_pricing_service_attributes()' in result['suggestion']
1349 | mock_context.error.assert_called()
1350 |
1351 | @pytest.mark.asyncio
1352 | async def test_get_pricing_attribute_values_single_attribute_empty(
1353 | self, mock_context, mock_boto3
1354 | ):
1355 | """Test getting attribute values when no values are returned for single attribute."""
1356 | pricing_client = mock_boto3.Session().client('pricing')
1357 | pricing_client.get_attribute_values.return_value = {'AttributeValues': []}
1358 |
1359 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1360 | result = await get_pricing_attribute_values(
1361 | mock_context, 'InvalidService', ['invalidAttribute']
1362 | )
1363 |
1364 | # Verify error response structure
1365 | assert isinstance(result, dict)
1366 | assert result['status'] == 'error'
1367 | assert result['error_type'] == 'no_attribute_values_found'
1368 | assert 'InvalidService' in result['message']
1369 | assert 'invalidAttribute' in result['message']
1370 | assert result['service_code'] == 'InvalidService'
1371 | assert result['attribute_name'] == 'invalidAttribute'
1372 | assert result['failed_attribute'] == 'invalidAttribute'
1373 | assert result['requested_attributes'] == ['invalidAttribute']
1374 | assert 'get_service_codes()' in result['suggestion']
1375 | assert 'get_service_attributes()' in result['suggestion']
1376 | assert 'examples' in result
1377 | assert 'Common service codes' in result['examples']
1378 | assert 'Common attributes' in result['examples']
1379 | mock_context.error.assert_called()
1380 |
1381 | @pytest.mark.asyncio
1382 | async def test_get_pricing_attribute_values_all_or_nothing_failure(
1383 | self, mock_context, mock_boto3
1384 | ):
1385 | """Test all-or-nothing behavior when one attribute fails in multi-attribute request."""
1386 | pricing_client = mock_boto3.Session().client('pricing')
1387 |
1388 | # First attribute succeeds, second fails
1389 | def mock_get_attribute_values(ServiceCode, AttributeName, **kwargs):
1390 | if AttributeName == 'instanceType':
1391 | return {'AttributeValues': [{'Value': 't2.micro'}, {'Value': 't3.medium'}]}
1392 | elif AttributeName == 'invalidAttribute':
1393 | return {'AttributeValues': []} # Empty result causes failure
1394 |
1395 | pricing_client.get_attribute_values.side_effect = mock_get_attribute_values
1396 |
1397 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1398 | result = await get_pricing_attribute_values(
1399 | mock_context, 'AmazonEC2', ['instanceType', 'invalidAttribute']
1400 | )
1401 |
1402 | # Should return error because second attribute failed
1403 | assert isinstance(result, dict)
1404 | assert result['status'] == 'error'
1405 | assert result['error_type'] == 'no_attribute_values_found'
1406 | assert (
1407 | 'Failed to retrieve values for attribute "invalidAttribute"' in result['message']
1408 | )
1409 | assert result['failed_attribute'] == 'invalidAttribute'
1410 | assert result['requested_attributes'] == ['instanceType', 'invalidAttribute']
1411 |
1412 | @pytest.mark.asyncio
1413 | async def test_get_pricing_attribute_values_api_error_in_multi_attribute(
1414 | self, mock_context, mock_boto3
1415 | ):
1416 | """Test handling of API errors when getting attribute values in multi-attribute request."""
1417 | pricing_client = mock_boto3.Session().client('pricing')
1418 |
1419 | # First attribute succeeds, second raises API error
1420 | def mock_get_attribute_values(ServiceCode, AttributeName, **kwargs):
1421 | if AttributeName == 'instanceType':
1422 | return {'AttributeValues': [{'Value': 't2.micro'}, {'Value': 't3.medium'}]}
1423 | elif AttributeName == 'location':
1424 | raise Exception('API Error')
1425 |
1426 | pricing_client.get_attribute_values.side_effect = mock_get_attribute_values
1427 |
1428 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1429 | result = await get_pricing_attribute_values(
1430 | mock_context, 'AmazonEC2', ['instanceType', 'location']
1431 | )
1432 |
1433 | # Should return error because second attribute had API error
1434 | assert isinstance(result, dict)
1435 | assert result['status'] == 'error'
1436 | assert result['error_type'] == 'api_error'
1437 | assert 'Failed to retrieve values for attribute "location"' in result['message']
1438 | assert 'API Error' in result['message']
1439 | assert result['failed_attribute'] == 'location'
1440 | assert result['requested_attributes'] == ['instanceType', 'location']
1441 |
1442 | @pytest.mark.asyncio
1443 | async def test_get_pricing_attribute_values_client_creation_error(self, mock_context):
1444 | """Test handling of client creation errors."""
1445 | with patch(
1446 | 'awslabs.aws_pricing_mcp_server.server.create_pricing_client',
1447 | side_effect=Exception('Client creation failed'),
1448 | ):
1449 | result = await get_pricing_attribute_values(
1450 | mock_context, 'AmazonEC2', ['instanceType']
1451 | )
1452 |
1453 | assert isinstance(result, dict)
1454 | assert result['status'] == 'error'
1455 | assert result['error_type'] == 'client_creation_failed'
1456 | assert 'Failed to create AWS Pricing client' in result['message']
1457 | assert 'Client creation failed' in result['message']
1458 | assert result['service_code'] == 'AmazonEC2'
1459 | assert result['attribute_names'] == ['instanceType']
1460 | mock_context.error.assert_called()
1461 |
1462 |
1463 | class TestGetPricingServiceCodesFiltering:
1464 | """Tests for regex filtering functionality in get_pricing_service_codes."""
1465 |
1466 | @pytest.fixture
1467 | def mock_service_codes_response(self, mock_boto3):
1468 | """Mock service codes response with a variety of AWS services for testing filters."""
1469 | pricing_client = mock_boto3.Session().client('pricing')
1470 | pricing_client.describe_services.return_value = {
1471 | 'Services': [
1472 | {'ServiceCode': 'AmazonBedrock'},
1473 | {'ServiceCode': 'AmazonBedrockService'},
1474 | {'ServiceCode': 'AmazonEC2'},
1475 | {'ServiceCode': 'AmazonS3'},
1476 | {'ServiceCode': 'AmazonRDS'},
1477 | {'ServiceCode': 'AWSLambda'},
1478 | {'ServiceCode': 'AmazonDynamoDB'},
1479 | {'ServiceCode': 'AmazonElasticSearch'},
1480 | {'ServiceCode': 'AmazonKendra'},
1481 | {'ServiceCode': 'AmazonSageMaker'},
1482 | ]
1483 | }
1484 | return mock_boto3
1485 |
1486 | @pytest.mark.asyncio
1487 | @pytest.mark.parametrize(
1488 | 'filter_pattern,expected_matches,expected_count,test_description',
1489 | [
1490 | # Case sensitivity tests
1491 | ('bedrock', ['AmazonBedrock', 'AmazonBedrockService'], None, 'basic_case_insensitive'),
1492 | (
1493 | 'BEDROCK',
1494 | ['AmazonBedrock', 'AmazonBedrockService'],
1495 | None,
1496 | 'uppercase_case_insensitive',
1497 | ),
1498 | ('BeDrOcK', ['AmazonBedrock', 'AmazonBedrockService'], None, 'mixed_case_insensitive'),
1499 | # Regex pattern tests
1500 | ('^AmazonBedrock$', ['AmazonBedrock'], None, 'exact_match_regex'),
1501 | (
1502 | '^Amazon',
1503 | [
1504 | 'AmazonBedrock',
1505 | 'AmazonBedrockService',
1506 | 'AmazonEC2',
1507 | 'AmazonS3',
1508 | 'AmazonRDS',
1509 | 'AmazonDynamoDB',
1510 | 'AmazonElasticSearch',
1511 | 'AmazonKendra',
1512 | 'AmazonSageMaker',
1513 | ],
1514 | None,
1515 | 'starts_with_regex',
1516 | ),
1517 | ('Lambda|S3', ['AWSLambda', 'AmazonS3'], None, 'alternation_regex'),
1518 | ('Amazon.*DB', ['AmazonDynamoDB'], None, 'wildcard_regex'),
1519 | ('EC2', ['AmazonEC2'], None, 'simple_substring'),
1520 | ('AWS', ['AWSLambda'], None, 'aws_prefix'),
1521 | (
1522 | 'Kendra|SageMaker',
1523 | ['AmazonKendra', 'AmazonSageMaker'],
1524 | None,
1525 | 'multiple_alternation',
1526 | ),
1527 | ('Search', ['AmazonElasticSearch'], None, 'partial_match'),
1528 | # No filter cases
1529 | ('', None, 10, 'empty_filter_all_services'),
1530 | (None, None, 10, 'none_filter_all_services'),
1531 | ],
1532 | )
1533 | async def test_regex_filtering_happy_path(
1534 | self,
1535 | mock_context,
1536 | mock_service_codes_response,
1537 | filter_pattern,
1538 | expected_matches,
1539 | expected_count,
1540 | test_description,
1541 | ):
1542 | """Test successful regex filter patterns and no-filter cases."""
1543 | with patch('boto3.Session', return_value=mock_service_codes_response.Session()):
1544 | result = await get_pricing_service_codes(mock_context, filter=filter_pattern)
1545 |
1546 | assert isinstance(result, list), (
1547 | f'Failed {test_description}: expected list, got {type(result)}'
1548 | )
1549 |
1550 | if expected_matches is not None:
1551 | # Test specific matches
1552 | assert len(result) == len(expected_matches), (
1553 | f'Failed {test_description}: expected {len(expected_matches)} matches, got {len(result)}'
1554 | )
1555 | for service in expected_matches:
1556 | assert service in result, (
1557 | f'Failed {test_description}: missing {service} in results'
1558 | )
1559 | elif expected_count is not None:
1560 | # Test count-only cases (like no filter)
1561 | assert len(result) == expected_count, (
1562 | f'Failed {test_description}: expected {expected_count} services, got {len(result)}'
1563 | )
1564 |
1565 | @pytest.mark.asyncio
1566 | @pytest.mark.parametrize(
1567 | 'filter_pattern,expected_error_type,test_description',
1568 | [
1569 | (r'\bEC2\b', 'no_matches_found', 'word_boundary_no_matches'),
1570 | (r'\.', 'no_matches_found', 'literal_dot_no_matches'),
1571 | ('NonExistentService', 'no_matches_found', 'nonexistent_service'),
1572 | ('[invalid', 'invalid_regex', 'invalid_regex_pattern'),
1573 | ],
1574 | )
1575 | async def test_regex_filtering_error_cases(
1576 | self,
1577 | mock_context,
1578 | mock_service_codes_response,
1579 | filter_pattern,
1580 | expected_error_type,
1581 | test_description,
1582 | ):
1583 | """Test regex filter patterns that result in errors (invalid regex or no matches)."""
1584 | with patch('boto3.Session', return_value=mock_service_codes_response.Session()):
1585 | result = await get_pricing_service_codes(mock_context, filter=filter_pattern)
1586 |
1587 | assert isinstance(result, dict), (
1588 | f'Failed {test_description}: expected dict (error), got {type(result)}'
1589 | )
1590 | assert result['status'] == 'error', f'Failed {test_description}: expected error status'
1591 | assert result['error_type'] == expected_error_type, (
1592 | f'Failed {test_description}: expected error_type {expected_error_type}, got {result.get("error_type")}'
1593 | )
1594 |
1595 | if expected_error_type == 'invalid_regex':
1596 | assert filter_pattern in result['message'], (
1597 | f'Failed {test_description}: filter pattern not in error message'
1598 | )
1599 | elif expected_error_type == 'no_matches_found':
1600 | assert (
1601 | 'No service codes match' in result['message']
1602 | or 'no matches' in result['message'].lower()
1603 | )
1604 |
1605 | mock_context.error.assert_called()
1606 |
1607 | @pytest.mark.asyncio
1608 | @pytest.mark.parametrize(
1609 | 'error_scenario,setup_error,expected_error_type',
1610 | [
1611 | ('api_error', Exception('API Error'), 'api_error'),
1612 | ('client_error', 'client_creation', 'client_creation_failed'),
1613 | ],
1614 | )
1615 | async def test_filter_error_scenarios(
1616 | self, mock_context, mock_boto3, error_scenario, setup_error, expected_error_type
1617 | ):
1618 | """Test error handling scenarios with filtering enabled."""
1619 | if error_scenario == 'api_error':
1620 | pricing_client = mock_boto3.Session().client('pricing')
1621 | pricing_client.describe_services.side_effect = setup_error
1622 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1623 | result = await get_pricing_service_codes(mock_context, filter='bedrock')
1624 | else: # client_error
1625 | with patch(
1626 | 'awslabs.aws_pricing_mcp_server.server.create_pricing_client',
1627 | side_effect=Exception('Client creation failed'),
1628 | ):
1629 | result = await get_pricing_service_codes(mock_context, filter='bedrock')
1630 |
1631 | assert isinstance(result, dict)
1632 | assert result['status'] == 'error'
1633 | assert result['error_type'] == expected_error_type
1634 | mock_context.error.assert_called()
1635 |
1636 | @pytest.mark.asyncio
1637 | async def test_filter_with_pagination(self, mock_context, mock_boto3):
1638 | """Test that filtering works correctly with paginated API responses."""
1639 | pricing_client = mock_boto3.Session().client('pricing')
1640 |
1641 | # Set up mock with pagination
1642 | pricing_client.describe_services.side_effect = [
1643 | # First page
1644 | {
1645 | 'Services': [
1646 | {'ServiceCode': 'AmazonBedrock'},
1647 | {'ServiceCode': 'AmazonEC2'},
1648 | ],
1649 | 'NextToken': 'page2token',
1650 | },
1651 | # Second page
1652 | {
1653 | 'Services': [
1654 | {'ServiceCode': 'AmazonBedrockService'},
1655 | {'ServiceCode': 'AWSLambda'},
1656 | ]
1657 | },
1658 | ]
1659 |
1660 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1661 | # Filter for services containing "bedrock"
1662 | result = await get_pricing_service_codes(mock_context, filter='bedrock')
1663 |
1664 | assert isinstance(result, list)
1665 | assert len(result) == 2 # Should find matches from both pages
1666 | assert 'AmazonBedrock' in result
1667 | assert 'AmazonBedrockService' in result
1668 | assert 'AmazonEC2' not in result
1669 | assert 'AWSLambda' not in result
1670 |
1671 |
1672 | class TestServerIntegration:
1673 | """Integration tests for the server module."""
1674 |
1675 | @pytest.mark.asyncio
1676 | async def test_get_pricing_service_codes_integration(self, mock_context, mock_boto3):
1677 | """Test the get_pricing_service_codes tool returns well-known service codes."""
1678 | # Mock the boto3 pricing client response
1679 | pricing_client = mock_boto3.Session().client('pricing')
1680 | pricing_client.describe_services.return_value = {
1681 | 'Services': [
1682 | {'ServiceCode': 'AmazonEC2'},
1683 | {'ServiceCode': 'AmazonS3'},
1684 | {'ServiceCode': 'AmazonRDS'},
1685 | {'ServiceCode': 'AWSLambda'},
1686 | {'ServiceCode': 'AmazonDynamoDB'},
1687 | ]
1688 | }
1689 |
1690 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1691 | # Call the get_pricing_service_codes function directly
1692 | service_codes = await get_pricing_service_codes(mock_context, filter=None)
1693 |
1694 | # Verify we got a successful response
1695 | assert service_codes is not None
1696 | assert isinstance(service_codes, list)
1697 |
1698 | # Check for well-known AWS service codes that should be present
1699 | expected_codes = ['AmazonEC2', 'AmazonS3', 'AmazonRDS', 'AWSLambda', 'AmazonDynamoDB']
1700 |
1701 | # Assert that the expected codes are present in the response
1702 | for code in expected_codes:
1703 | assert code in service_codes, f'Expected service code {code} not found in response'
1704 |
1705 | # Verify that the mock was called correctly
1706 | pricing_client.describe_services.assert_called()
1707 |
1708 | # Verify context was used correctly
1709 | mock_context.info.assert_called()
1710 |
1711 | @pytest.mark.asyncio
1712 | @pytest.mark.parametrize(
1713 | 'error_scenario,side_effect,expected_error_type',
1714 | [
1715 | ('client_creation_failed', 'create_pricing_client', 'client_creation_failed'),
1716 | ('api_error', 'describe_services', 'api_error'),
1717 | ('empty_results', None, 'empty_results'),
1718 | ],
1719 | )
1720 | async def test_get_pricing_service_codes_errors(
1721 | self, mock_context, mock_boto3, error_scenario, side_effect, expected_error_type
1722 | ):
1723 | """Test error handling scenarios for get_pricing_service_codes."""
1724 | if error_scenario == 'client_creation_failed':
1725 | with patch(
1726 | 'awslabs.aws_pricing_mcp_server.server.create_pricing_client',
1727 | side_effect=Exception('Client creation failed'),
1728 | ):
1729 | result = await get_pricing_service_codes(mock_context)
1730 | elif error_scenario == 'api_error':
1731 | pricing_client = mock_boto3.Session().client('pricing')
1732 | pricing_client.describe_services.side_effect = Exception('API Error')
1733 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1734 | result = await get_pricing_service_codes(mock_context)
1735 | elif error_scenario == 'empty_results':
1736 | pricing_client = mock_boto3.Session().client('pricing')
1737 | pricing_client.describe_services.return_value = {'Services': []}
1738 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1739 | result = await get_pricing_service_codes(mock_context)
1740 | else:
1741 | # Should not reach here with current parametrize values
1742 | raise ValueError(f'Unknown error scenario: {error_scenario}')
1743 |
1744 | assert isinstance(result, dict)
1745 | assert result['status'] == 'error'
1746 | assert result['error_type'] == expected_error_type
1747 | mock_context.error.assert_called()
1748 |
1749 | @pytest.mark.asyncio
1750 | async def test_get_pricing_service_codes_pagination(self, mock_context, mock_boto3):
1751 | """Test that get_pricing_service_codes correctly handles pagination."""
1752 | # Mock the boto3 pricing client response for pagination
1753 | pricing_client = mock_boto3.Session().client('pricing')
1754 |
1755 | # Set up a mock with pagination
1756 | pricing_client.describe_services.side_effect = [
1757 | # First call returns first page with NextToken
1758 | {
1759 | 'Services': [
1760 | {'ServiceCode': 'AmazonEC2'},
1761 | {'ServiceCode': 'AmazonS3'},
1762 | ],
1763 | 'NextToken': 'page2token',
1764 | },
1765 | # Second call with NextToken returns second page
1766 | {
1767 | 'Services': [
1768 | {'ServiceCode': 'AmazonRDS'},
1769 | {'ServiceCode': 'AWSLambda'},
1770 | {'ServiceCode': 'AmazonDynamoDB'},
1771 | ]
1772 | # No NextToken in the response means this is the last page
1773 | },
1774 | ]
1775 |
1776 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1777 | # Call the get_pricing_service_codes function directly
1778 | service_codes = await get_pricing_service_codes(mock_context, filter=None)
1779 |
1780 | # Verify we got a successful response that combines both pages
1781 | assert service_codes is not None
1782 | assert isinstance(service_codes, list)
1783 | assert len(service_codes) == 5 # Total from both pages
1784 |
1785 | # Verify pagination happened
1786 | assert pricing_client.describe_services.call_count == 2
1787 |
1788 | # First call should have no NextToken
1789 | first_call_kwargs = pricing_client.describe_services.call_args_list[0][1]
1790 | assert 'NextToken' not in first_call_kwargs
1791 |
1792 | # Second call should include the NextToken from the first response
1793 | second_call_kwargs = pricing_client.describe_services.call_args_list[1][1]
1794 | assert second_call_kwargs.get('NextToken') == 'page2token'
1795 |
1796 | @pytest.mark.asyncio
1797 | async def test_pricing_workflow(self, mock_context, mock_boto3):
1798 | """Test the complete pricing analysis workflow."""
1799 | # 1. Get pricing from API
1800 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1801 | api_pricing = await get_pricing(mock_context, 'AWSLambda', 'us-west-2')
1802 | assert api_pricing is not None
1803 | assert api_pricing['status'] == 'success'
1804 |
1805 | # 2. Generate cost report
1806 | report = await generate_cost_report_wrapper(
1807 | mock_context,
1808 | pricing_data=api_pricing,
1809 | service_name='AWS Lambda',
1810 | pricing_model='ON DEMAND',
1811 | related_services=None,
1812 | assumptions=None,
1813 | exclusions=None,
1814 | output_file=None,
1815 | detailed_cost_data=None,
1816 | recommendations=None,
1817 | )
1818 | assert report is not None
1819 | assert isinstance(report, str)
1820 | assert 'AWS Lambda' in report
1821 |
1822 |
1823 | class TestIsFreeProduct:
1824 | """Tests for the _is_free_product function with multi-currency support."""
1825 |
1826 | def _create_pricing_data(self, price_per_unit: dict) -> dict:
1827 | """Helper to create test pricing data structure."""
1828 | return {
1829 | 'terms': {
1830 | 'OnDemand': {
1831 | 'TEST.TERM.CODE': {
1832 | 'priceDimensions': {'TEST.TERM.CODE.DIM': {'pricePerUnit': price_per_unit}}
1833 | }
1834 | }
1835 | }
1836 | }
1837 |
1838 | @pytest.mark.parametrize(
1839 | 'price_per_unit,expected_result,test_description',
1840 | [
1841 | # Test case 1: All currencies zero (truly free)
1842 | ({'USD': '0.0000', 'CNY': '0.0000'}, True, 'truly_free_all_zero'),
1843 | # Test case 2: CNY only, non-zero (should be False)
1844 | ({'CNY': '5.2000'}, False, 'cny_only_paid'),
1845 | # Test case 3: Free in USD, paid in CNY (should be False)
1846 | ({'USD': '0.0000', 'CNY': '3.5000'}, False, 'usd_free_cny_paid'),
1847 | # Test case 4: Invalid CNY price format (should be False)
1848 | ({'CNY': 'N/A'}, False, 'invalid_cny_format'),
1849 | # Test case 5: Multiple currencies with CNY paid
1850 | (
1851 | {'USD': '0.0000', 'EUR': '0.0000', 'CNY': '8.7500'},
1852 | False,
1853 | 'multi_currency_cny_paid',
1854 | ),
1855 | ],
1856 | )
1857 | def test_is_free_product_multi_currency(
1858 | self, price_per_unit, expected_result, test_description
1859 | ):
1860 | """Test _is_free_product correctly handles CNY and other currencies."""
1861 | pricing_data = self._create_pricing_data(price_per_unit)
1862 | result = _is_free_product(pricing_data)
1863 |
1864 | assert result == expected_result, (
1865 | f"Failed test case '{test_description}': "
1866 | f'Expected {expected_result}, got {result} for pricing {price_per_unit}'
1867 | )
1868 |
1869 |
1870 | class TestGetPriceListUrls:
1871 | """Tests for the get_price_list_urls function."""
1872 |
1873 | @pytest.mark.asyncio
1874 | async def test_get_price_list_urls_success(self, mock_context, mock_boto3):
1875 | """Test successful retrieval of price list file URLs for all formats."""
1876 | pricing_client = mock_boto3.Session().client('pricing')
1877 |
1878 | # Mock list_price_lists response
1879 | pricing_client.list_price_lists.return_value = {
1880 | 'PriceLists': [
1881 | {
1882 | 'PriceListArn': 'arn:aws:pricing::123456789012:price-list/AmazonEC2',
1883 | 'FileFormats': ['CSV', 'JSON'],
1884 | }
1885 | ]
1886 | }
1887 |
1888 | # Mock get_price_list_file_url response - called for each format
1889 | pricing_client.get_price_list_file_url.side_effect = [
1890 | {'Url': 'https://example.com/pricing.csv'},
1891 | {'Url': 'https://example.com/pricing.json'},
1892 | ]
1893 |
1894 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1895 | result = await get_price_list_urls(
1896 | mock_context, 'AmazonEC2', 'us-east-1', '2023-06-01 00:00'
1897 | )
1898 |
1899 | # Check that we get all available format URLs
1900 | assert len(result) == 2 # csv + json
1901 | assert result['csv'] == 'https://example.com/pricing.csv'
1902 | assert result['json'] == 'https://example.com/pricing.json'
1903 |
1904 | # Verify API calls
1905 | pricing_client.list_price_lists.assert_called_once_with(
1906 | ServiceCode='AmazonEC2',
1907 | EffectiveDate='2023-06-01 00:00',
1908 | RegionCode='us-east-1',
1909 | CurrencyCode='USD',
1910 | )
1911 |
1912 | # Verify get_price_list_file_url was called for each format
1913 | assert pricing_client.get_price_list_file_url.call_count == 2
1914 | pricing_client.get_price_list_file_url.assert_any_call(
1915 | PriceListArn='arn:aws:pricing::123456789012:price-list/AmazonEC2', FileFormat='CSV'
1916 | )
1917 | pricing_client.get_price_list_file_url.assert_any_call(
1918 | PriceListArn='arn:aws:pricing::123456789012:price-list/AmazonEC2', FileFormat='JSON'
1919 | )
1920 |
1921 | @pytest.mark.asyncio
1922 | async def test_get_price_list_urls_default_date(self, mock_context, mock_boto3):
1923 | """Test that current timestamp is used when effective_date is not provided."""
1924 | pricing_client = mock_boto3.Session().client('pricing')
1925 |
1926 | pricing_client.list_price_lists.return_value = {
1927 | 'PriceLists': [
1928 | {
1929 | 'PriceListArn': 'arn:aws:pricing::123456789012:price-list/AmazonS3',
1930 | 'FileFormats': ['CSV'],
1931 | }
1932 | ]
1933 | }
1934 |
1935 | pricing_client.get_price_list_file_url.return_value = {
1936 | 'Url': 'https://example.com/pricing.csv'
1937 | }
1938 |
1939 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1940 | result = await get_price_list_urls(mock_context, 'AmazonS3', 'us-west-2')
1941 |
1942 | # Check that we get all available format URLs
1943 | assert len(result) == 1 # csv only
1944 | assert result['csv'] == 'https://example.com/pricing.csv'
1945 |
1946 | @pytest.mark.asyncio
1947 | async def test_get_price_list_urls_format_failure(self, mock_context, mock_boto3):
1948 | """Test that any format failure results in an error."""
1949 | pricing_client = mock_boto3.Session().client('pricing')
1950 |
1951 | # Mock list_price_lists response with both formats
1952 | pricing_client.list_price_lists.return_value = {
1953 | 'PriceLists': [
1954 | {
1955 | 'PriceListArn': 'arn:aws:pricing::123456789012:price-list/AmazonEC2',
1956 | 'FileFormats': ['CSV', 'JSON'],
1957 | }
1958 | ]
1959 | }
1960 |
1961 | # Mock CSV succeeding but JSON failing
1962 | pricing_client.get_price_list_file_url.side_effect = [
1963 | {'Url': 'https://example.com/pricing.csv'}, # CSV succeeds
1964 | Exception('Failed to get JSON URL'), # JSON fails
1965 | ]
1966 |
1967 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1968 | result = await get_price_list_urls(mock_context, 'AmazonEC2', 'us-east-1')
1969 |
1970 | # Should return error when any format fails
1971 | assert result['status'] == 'error'
1972 | assert result['error_type'] == 'format_url_failed'
1973 | assert 'Failed to get download URL for format "JSON"' in result['message']
1974 | assert result['price_list_arn'] == 'arn:aws:pricing::123456789012:price-list/AmazonEC2'
1975 | assert result['file_format'] == 'json'
1976 |
1977 | # Verify error was logged
1978 | mock_context.error.assert_called_once()
1979 |
1980 | @pytest.mark.asyncio
1981 | async def test_get_price_list_urls_no_price_lists(self, mock_context, mock_boto3):
1982 | """Test error handling when no price lists are found."""
1983 | pricing_client = mock_boto3.Session().client('pricing')
1984 | pricing_client.list_price_lists.return_value = {'PriceLists': []}
1985 |
1986 | with patch('boto3.Session', return_value=mock_boto3.Session()):
1987 | result = await get_price_list_urls(mock_context, 'InvalidService', 'us-east-1')
1988 |
1989 | assert result['status'] == 'error'
1990 | assert result['error_type'] == 'no_price_list_found'
1991 | assert 'InvalidService' in result['message']
1992 | assert result['service_code'] == 'InvalidService'
1993 | assert result['region'] == 'us-east-1'
1994 | mock_context.error.assert_called()
1995 |
1996 | @pytest.mark.asyncio
1997 | async def test_get_price_list_urls_unsupported_format(self, mock_context, mock_boto3):
1998 | """Test handling when some formats are not supported."""
1999 | pricing_client = mock_boto3.Session().client('pricing')
2000 | pricing_client.list_price_lists.return_value = {
2001 | 'PriceLists': [
2002 | {
2003 | 'PriceListArn': 'arn:aws:pricing::123456789012:price-list/AmazonEC2',
2004 | 'FileFormats': ['CSV'], # Only CSV supported
2005 | }
2006 | ]
2007 | }
2008 |
2009 | pricing_client.get_price_list_file_url.return_value = {
2010 | 'Url': 'https://example.com/pricing.csv'
2011 | }
2012 |
2013 | with patch('boto3.Session', return_value=mock_boto3.Session()):
2014 | result = await get_price_list_urls(mock_context, 'AmazonEC2', 'us-east-1')
2015 |
2016 | # Should still return successful result with available formats
2017 | assert len(result) == 1 # csv only
2018 | assert result['csv'] == 'https://example.com/pricing.csv'
2019 | assert 'json' not in result # JSON format not supported
2020 |
2021 | @pytest.mark.asyncio
2022 | async def test_get_price_list_urls_list_api_error(self, mock_context, mock_boto3):
2023 | """Test error handling when list_price_lists API call fails."""
2024 | pricing_client = mock_boto3.Session().client('pricing')
2025 | pricing_client.list_price_lists.side_effect = Exception('API Error')
2026 |
2027 | with patch('boto3.Session', return_value=mock_boto3.Session()):
2028 | result = await get_price_list_urls(mock_context, 'AmazonEC2', 'us-east-1')
2029 |
2030 | assert result['status'] == 'error'
2031 | assert result['error_type'] == 'list_price_lists_failed'
2032 | assert 'API Error' in result['message']
2033 | assert result['service_code'] == 'AmazonEC2'
2034 | assert result['region'] == 'us-east-1'
2035 | mock_context.error.assert_called()
2036 |
2037 | @pytest.mark.asyncio
2038 | async def test_get_price_list_urls_get_url_api_error(self, mock_context, mock_boto3):
2039 | """Test error handling when get_price_list_file_url API call fails."""
2040 | pricing_client = mock_boto3.Session().client('pricing')
2041 |
2042 | pricing_client.list_price_lists.return_value = {
2043 | 'PriceLists': [
2044 | {
2045 | 'PriceListArn': 'arn:aws:pricing::123456789012:price-list/AmazonEC2',
2046 | 'FileFormats': ['CSV'],
2047 | }
2048 | ]
2049 | }
2050 |
2051 | pricing_client.get_price_list_file_url.side_effect = Exception('URL API Error')
2052 |
2053 | with patch('boto3.Session', return_value=mock_boto3.Session()):
2054 | result = await get_price_list_urls(mock_context, 'AmazonEC2', 'us-east-1')
2055 |
2056 | assert result['status'] == 'error'
2057 | assert result['error_type'] == 'format_url_failed'
2058 | assert 'Failed to get download URL for format "CSV"' in result['message']
2059 | assert result['price_list_arn'] == 'arn:aws:pricing::123456789012:price-list/AmazonEC2'
2060 | assert result['file_format'] == 'csv'
2061 | mock_context.error.assert_called()
2062 |
2063 | @pytest.mark.asyncio
2064 | async def test_get_price_list_urls_no_download_url(self, mock_context, mock_boto3):
2065 | """Test error handling when no download URL is returned."""
2066 | pricing_client = mock_boto3.Session().client('pricing')
2067 |
2068 | pricing_client.list_price_lists.return_value = {
2069 | 'PriceLists': [
2070 | {
2071 | 'PriceListArn': 'arn:aws:pricing::123456789012:price-list/AmazonEC2',
2072 | 'FileFormats': ['CSV'],
2073 | }
2074 | ]
2075 | }
2076 |
2077 | pricing_client.get_price_list_file_url.return_value = {} # No URL
2078 |
2079 | with patch('boto3.Session', return_value=mock_boto3.Session()):
2080 | result = await get_price_list_urls(mock_context, 'AmazonEC2', 'us-east-1')
2081 |
2082 | assert result['status'] == 'error'
2083 | assert result['error_type'] == 'empty_url_response'
2084 | assert 'AWS API returned empty URL for format "CSV"' in result['message']
2085 | assert result['price_list_arn'] == 'arn:aws:pricing::123456789012:price-list/AmazonEC2'
2086 | assert result['file_format'] == 'csv'
2087 | mock_context.error.assert_called()
2088 |
2089 | @pytest.mark.asyncio
2090 | async def test_get_price_list_urls_unexpected_error(self, mock_context):
2091 | """Test error handling for unexpected errors."""
2092 | with patch('boto3.Session', side_effect=Exception('Unexpected Error')):
2093 | result = await get_price_list_urls(mock_context, 'AmazonEC2', 'us-east-1')
2094 |
2095 | assert result['status'] == 'error'
2096 | assert result['error_type'] == 'client_creation_failed'
2097 | assert 'Unexpected Error' in result['message']
2098 | assert result['service_code'] == 'AmazonEC2'
2099 | assert result['region'] == 'us-east-1'
2100 | mock_context.error.assert_called()
2101 |
2102 | @pytest.mark.asyncio
2103 | async def test_get_price_list_urls_no_supported_formats(self, mock_context, mock_boto3):
2104 | """Test error handling when price list has no supported formats."""
2105 | pricing_client = mock_boto3.Session().client('pricing')
2106 |
2107 | pricing_client.list_price_lists.return_value = {
2108 | 'PriceLists': [
2109 | {
2110 | 'PriceListArn': 'arn:aws:pricing::123456789012:price-list/AmazonEC2',
2111 | 'FileFormats': [], # Empty list of formats
2112 | }
2113 | ]
2114 | }
2115 |
2116 | with patch('boto3.Session', return_value=mock_boto3.Session()):
2117 | result = await get_price_list_urls(mock_context, 'AmazonEC2', 'us-east-1')
2118 |
2119 | assert result['status'] == 'error'
2120 | assert result['error_type'] == 'no_formats_available'
2121 | assert 'no file formats are available for service "AmazonEC2"' in result['message']
2122 | assert result['service_code'] == 'AmazonEC2'
2123 | assert result['region'] == 'us-east-1'
2124 | assert result['price_list_arn'] == 'arn:aws:pricing::123456789012:price-list/AmazonEC2'
2125 | mock_context.error.assert_called()
2126 |
```