# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Redshift MCP Server implementation.""" import os import sys from awslabs.redshift_mcp_server.consts import ( DEFAULT_LOG_LEVEL, ) from awslabs.redshift_mcp_server.models import ( QueryResult, RedshiftCluster, RedshiftColumn, RedshiftDatabase, RedshiftSchema, RedshiftTable, ) from awslabs.redshift_mcp_server.redshift import ( discover_clusters, discover_columns, discover_databases, discover_schemas, discover_tables, execute_query, ) from awslabs.redshift_mcp_server.review.executor import review_cluster from awslabs.redshift_mcp_server.review.models import ReviewResult from loguru import logger from mcp.server.mcpserver import Context, MCPServer from mcp.types import ToolAnnotations from pydantic import Field # Remove default handler and add custom configuration logger.remove() logger.add( os.environ.get('LOG_FILE', sys.stderr), level=os.environ.get('FASTMCP_LOG_LEVEL', DEFAULT_LOG_LEVEL), ) mcp = MCPServer( 'awslabs.redshift-mcp-server', instructions=""" # Amazon Redshift MCP Server. This MCP server provides comprehensive access to Amazon Redshift clusters and serverless workgroups. ## Available Tools ### list_clusters Lists all available Redshift clusters and serverless workgroups in your AWS account. This tool provides essential information needed to connect to and query your Redshift instances. ### list_databases Lists all databases in a specified Redshift cluster. This tool runs the SHOW DATABASES command to discover available databases. ### list_schemas Lists all schemas in a specified database within a Redshift cluster. This tool runs the SHOW SCHEMAS command to discover available schemas. ### list_tables Lists all tables in a specified schema within a Redshift database. This tool runs the SHOW TABLES command to discover available tables. ### list_columns Lists all columns in a specified table within a Redshift schema. This tool runs the SHOW COLUMNS command to discover available columns. ### execute_query Executes SQL queries against a Redshift cluster or serverless workgroup. This tool uses the Redshift Data API to run queries and return results. ### review_cluster Runs a diagnostic review of a Redshift cluster or serverless workgroup. Returns identified potential issues and respective recommendations ordered by required mitigation effort. Requires the connected database user to hold the sys:monitor role (or be a superuser). ## Getting Started 1. Ensure your AWS configuration and credentials are configured (environment variables or profile configuration file). 2. Use the list_clusters tool to discover available Redshift instances. 3. Note the cluster identifiers for use with other tools (coming in future milestones). ## Session Management and Concurrency The server reuses one Redshift Data API session per `cluster:database`: - Queries to the same `cluster:database` are serialized (parallel calls queue; a long-running query blocks later ones to that target). - Queries to different targets run concurrently on independent sessions. - Each read-only query runs isolated in its own transaction. ## AWS Client Best Practices ### Authentication and Configuration - Default AWS credentials chain (IAM roles, ~/.aws/credentials, etc.). - AWS_PROFILE environment variable (if set). - Region configuration (in order of precedence): - AWS_REGION environment variable (highest priority) - AWS_DEFAULT_REGION environment variable - Region specified in AWS profile configuration ### Error Handling - Always print out AWS client errors in full to help diagnose configuration issues. - For region-related errors, suggest checking AWS_REGION, AWS_DEFAULT_REGION, or AWS profile configuration. - For credential errors, suggest verifying AWS credentials setup and permissions. ## Amazon Redshift Best Practices ### Query Guidelines - Always specify the database and schema when referencing objects to avoid ambiguity. - Leverage distribution in WHERE and JOIN predicates and sort keys in ORDER BY for optimal query performance. - Use LIMIT clauses for exploratory queries to avoid large result sets. - Analyze table to update table statistics if it is not updated or too off before making a decision on the query structure. - Prefer explicitly specifying columns in SELECT over "*" for better performance. ### Connection Guidelines - We use the Redshift API and Redshift Data API. - Leverage IAM authentication when possible instead of secrets (database passwords). """, dependencies=['boto3', 'loguru', 'pydantic', 'sqlglot'], ) def _read_only_annotations(title: str) -> ToolAnnotations: """Return annotations for tools that only read the caller's AWS environment.""" return ToolAnnotations( title=title, read_only_hint=True, destructive_hint=False, idempotent_hint=True, open_world_hint=True, ) @mcp.tool( name='list_clusters', annotations=_read_only_annotations('List Redshift clusters and workgroups'), ) async def list_clusters_tool(ctx: Context) -> list[RedshiftCluster]: """List all available Amazon Redshift clusters and serverless workgroups. This tool discovers and returns information about all Redshift clusters and serverless workgroups in your AWS account, including their current status, connection details, and configuration. ## Usage Requirements - Ensure your AWS credentials are properly configured (via AWS_PROFILE or default credentials). - Required IAM permissions: redshift:DescribeClusters, redshift-serverless:ListWorkgroups, redshift-serverless:GetWorkgroup. ## Response Structure Returns a list of RedshiftCluster objects with the following structure: - identifier: Unique identifier for the cluster/workgroup. - type: Type of cluster (provisioned or serverless). - status: Current status of the cluster. - database_name: Default database name. - endpoint: Connection endpoint information. - port: Connection port. - vpc_id: VPC ID where the cluster resides. - node_type: Node type (for provisioned clusters). - number_of_nodes: Number of nodes (for provisioned clusters). - creation_time: When the cluster was created. - master_username: Master username for the cluster. - publicly_accessible: Whether the cluster is publicly accessible. - encrypted: Whether the cluster is encrypted. - tags: Tags associated with the cluster. ## Usage Tips 1. Use this tool to discover available Redshift instances before attempting connections. 2. Note the cluster identifiers for use with other database tools. 3. Check the status field to ensure clusters are 'available' before querying. 4. Use the endpoint and port information for direct database connections if needed. 5. Consider the cluster type (provisioned vs serverless) when planning your queries. ## Interpretation Best Practices 1. Filter results by status to find only available clusters. 2. Use cluster identifiers as input for other Redshift tools. 3. Consider cluster configuration (node type, encryption) for performance planning. 4. Check tags for environment or team information to select appropriate clusters. """ try: logger.info('Discovering Redshift clusters and serverless workgroups') clusters = await discover_clusters() logger.info(f'Successfully retrieved {len(clusters)} clusters') return clusters except Exception as e: logger.error(f'Error in list_clusters_tool: {str(e)}') await ctx.error(f'Failed to list clusters: {str(e)}') raise @mcp.tool( name='list_databases', annotations=_read_only_annotations('List Redshift databases'), ) async def list_databases_tool( ctx: Context, cluster_identifier: str = Field( ..., description='The cluster identifier to query for databases. Must be a valid cluster identifier from the list_clusters tool.', ), database_name: str = Field( 'dev', description='The database to connect to for metadata discovery. Defaults to "dev".', ), ) -> list[RedshiftDatabase]: """List all databases in a specified Amazon Redshift cluster. This tool runs the SHOW DATABASES command to discover all databases that the user has access to in the specified cluster, including local databases and databases created from datashares. ## Usage Requirements - Ensure your AWS credentials are properly configured (via AWS_PROFILE or default credentials). - The cluster must be available and accessible. - Required IAM permissions: redshift-data:ExecuteStatement, redshift-data:DescribeStatement, redshift-data:GetStatementResult. - The user must have access to the specified database to run the discovery commands. ## Parameters - cluster_identifier: The unique identifier of the Redshift cluster to query. IMPORTANT: Use a valid cluster identifier from the list_clusters tool. - database_name: The database to connect to for metadata discovery (defaults to 'dev'). ## Response Structure Returns a list of RedshiftDatabase objects with the following structure: - database_name: The name of the database. - database_owner: The database owner user ID. - database_type: The type of database (local or shared). - database_acl: Access control information (for internal use). - parameters: The properties of the database. - database_isolation_level: The isolation level (Snapshot Isolation or Serializable). ## Usage Tips 1. First use list_clusters to get valid cluster identifiers. 2. Ensure the cluster status is 'available' before querying databases. 3. Use the default database name unless you know a specific database exists. 4. Note database types to understand if they are local or shared from datashares. 5. Shared (datashare) databases appear only if the connecting principal has been granted access to the consumer database (e.g. GRANT USAGE ON DATABASE TO ); otherwise they are omitted even though the datashare exists. ## Interpretation Best Practices 1. Focus on 'local' database types for cluster-native databases. 2. 'shared' database types indicate databases from datashares. 3. Use database names for subsequent schema and table discovery. 4. Consider database isolation levels for transaction planning. """ try: logger.info(f'Discovering databases on cluster: {cluster_identifier}') databases = await discover_databases( cluster_identifier=cluster_identifier, database_name=database_name ) logger.info( f'Successfully retrieved {len(databases)} databases from cluster {cluster_identifier}' ) return databases except Exception as e: logger.error(f'Error in list_databases_tool: {str(e)}') await ctx.error(f'Failed to list databases on cluster {cluster_identifier}: {str(e)}') raise @mcp.tool( name='list_schemas', annotations=_read_only_annotations('List Redshift schemas'), ) async def list_schemas_tool( ctx: Context, cluster_identifier: str = Field( ..., description='The cluster identifier to query for schemas. Must be a valid cluster identifier from the list_clusters tool.', ), schema_database_name: str = Field( ..., description='The database name to list schemas for. Also used to connect to. Must be a valid database name from the list_databases tool.', ), ) -> list[RedshiftSchema]: """List all schemas in a specified database within a Redshift cluster. This tool runs the SHOW SCHEMAS command to discover all schemas that the user has access to in the specified database, including local schemas, external schemas, and shared schemas from datashares. ## Usage Requirements - Ensure your AWS credentials are properly configured (via AWS_PROFILE or default credentials). - The cluster must be available and accessible. - Required IAM permissions: redshift-data:ExecuteStatement, redshift-data:DescribeStatement, redshift-data:GetStatementResult. - The user must have access to the database to run the discovery commands. ## Parameters - cluster_identifier: The unique identifier of the Redshift cluster to query. IMPORTANT: Use a valid cluster identifier from the list_clusters tool. - schema_database_name: The database name to list schemas for. Also used to connect to. IMPORTANT: Use a valid database name from the list_databases tool. ## Response Structure Returns a list of RedshiftSchema objects with the following structure: - database_name: The name of the database where the schema exists. - schema_name: The name of the schema. - schema_owner: The user ID of the schema owner. - schema_type: The type of the schema (external, local, or shared). - schema_acl: The permissions for the specified user or user group for the schema. - source_database: The name of the source database for external schema. - schema_option: The options of the schema (external schema attribute). ## Usage Tips 1. First use list_clusters to get valid cluster identifiers. 2. Then use list_databases to get valid database names for the cluster. 3. Ensure the cluster status is 'available' before querying schemas. 4. Note schema types to understand if they are local, external, or shared. 5. External schemas connect to external data sources like S3 or other databases. 6. Schemas in a shared (datashare) database appear only if the connecting principal has been granted access to the consumer database (e.g. GRANT USAGE ON DATABASE TO ); otherwise they are omitted even though the datashare exists. ## Interpretation Best Practices 1. Focus on 'local' schema types for cluster-native schemas. 2. 'external' schema types indicate connections to external data sources. 3. 'shared' schema types indicate schemas from datashares. 4. Use schema names for subsequent table and column discovery. 5. Consider schema permissions (schema_acl) for access planning. """ try: logger.info( f'Discovering schemas in database {schema_database_name} on cluster {cluster_identifier}' ) schemas = await discover_schemas( cluster_identifier=cluster_identifier, schema_database_name=schema_database_name ) logger.info( f'Successfully retrieved {len(schemas)} schemas from database {schema_database_name} on cluster {cluster_identifier}' ) return schemas except Exception as e: logger.error(f'Error in list_schemas_tool: {str(e)}') await ctx.error( f'Failed to list schemas in database {schema_database_name} on cluster {cluster_identifier}: {str(e)}' ) raise @mcp.tool( name='list_tables', annotations=_read_only_annotations('List Redshift tables'), ) async def list_tables_tool( ctx: Context, cluster_identifier: str = Field( ..., description='The cluster identifier to query for tables. Must be a valid cluster identifier from the list_clusters tool.', ), table_database_name: str = Field( ..., description='The database name to list tables for. Must be a valid database name from the list_databases tool.', ), table_schema_name: str = Field( ..., description='The schema name to list tables for. Also used to connect to. Must be a valid schema name from the list_schemas tool.', ), ) -> list[RedshiftTable]: """List all tables in a specified schema within a Redshift database. This tool runs the SHOW TABLES command to discover all tables that the user has access to in the specified schema, including base tables, views, external tables, and shared tables. ## Usage Requirements - Ensure your AWS credentials are properly configured (via AWS_PROFILE or default credentials). - The cluster must be available and accessible. - Required IAM permissions: redshift-data:ExecuteStatement, redshift-data:DescribeStatement, redshift-data:GetStatementResult. - The user must have access to the database to run the discovery commands. ## Parameters - cluster_identifier: The unique identifier of the Redshift cluster to query. IMPORTANT: Use a valid cluster identifier from the list_clusters tool. - table_database_name: The database name to list tables for. IMPORTANT: Use a valid database name from the list_databases tool. - table_schema_name: The schema name to list tables for. IMPORTANT: Use a valid schema name from the list_schemas tool. ## Response Structure Returns a list of RedshiftTable objects with the following structure: - database_name: The name of the database where the table exists. - schema_name: The schema name for the table. - table_name: The name of the table. - table_acl: The permissions for the specified user or user group for the table. - table_type: The type of the table (views, base tables, external tables, shared tables). - remarks: Remarks about the table. ## Usage Tips 1. First use list_clusters to get valid cluster identifiers. 2. Then use list_databases to get valid database names for the cluster. 3. Then use list_schemas to get valid schema names for the database. 4. Ensure the cluster status is 'available' before querying tables. 5. Note table types to understand if they are base tables, views, external tables, or shared tables. ## Interpretation Best Practices 1. Focus on 'TABLE' table types for regular database tables. 2. 'VIEW' table types indicate database views. 3. 'EXTERNAL TABLE' types indicate connections to external data sources. 4. 'SHARED TABLE' types indicate tables from datashares. 5. Use table names for subsequent column discovery and query operations. 6. Consider table permissions (table_acl) for access planning. """ try: logger.info( f'Discovering tables in schema {table_schema_name} in database {table_database_name} on cluster {cluster_identifier}' ) tables = await discover_tables( cluster_identifier=cluster_identifier, table_database_name=table_database_name, table_schema_name=table_schema_name, ) logger.info( f'Successfully retrieved {len(tables)} tables from schema {table_schema_name} in database {table_database_name} on cluster {cluster_identifier}' ) return tables except Exception as e: logger.error(f'Error in list_tables_tool: {str(e)}') await ctx.error( f'Failed to list tables in schema {table_schema_name} in database {table_database_name} on cluster {cluster_identifier}: {str(e)}' ) raise @mcp.tool( name='list_columns', annotations=_read_only_annotations('List Redshift columns'), ) async def list_columns_tool( ctx: Context, cluster_identifier: str = Field( ..., description='The cluster identifier to query for columns. Must be a valid cluster identifier from the list_clusters tool.', ), column_database_name: str = Field( ..., description='The database name to list columns for. Must be a valid database name from the list_databases tool.', ), column_schema_name: str = Field( ..., description='The schema name to list columns for. Must be a valid schema name from the list_schemas tool.', ), column_table_name: str = Field( ..., description='The table name to list columns for. Must be a valid table name from the list_tables tool.', ), ) -> list[RedshiftColumn]: """List all columns in a specified table within a Redshift schema. This tool runs the SHOW COLUMNS command to discover all columns that the user has access to in the specified table, including detailed information about data types, constraints, and column properties. ## Usage Requirements - Ensure your AWS credentials are properly configured (via AWS_PROFILE or default credentials). - The cluster must be available and accessible. - Required IAM permissions: redshift-data:ExecuteStatement, redshift-data:DescribeStatement, redshift-data:GetStatementResult. - The user must have access to the database to run the discovery commands. ## Parameters - cluster_identifier: The unique identifier of the Redshift cluster to query. IMPORTANT: Use a valid cluster identifier from the list_clusters tool. - column_database_name: The database name to list columns for. IMPORTANT: Use a valid database name from the list_databases tool. - column_schema_name: The schema name to list columns for. IMPORTANT: Use a valid schema name from the list_schemas tool. - column_table_name: The table name to list columns for. IMPORTANT: Use a valid table name from the list_tables tool. ## Response Structure Returns a list of RedshiftColumn objects with the following structure: - database_name: The name of the database. - schema_name: The name of the schema. - table_name: The name of the table. - column_name: The name of the column. - ordinal_position: The position of the column in the table. - column_default: The default value of the column. - is_nullable: Whether the column is nullable (yes or no). - data_type: The data type of the column. - character_maximum_length: The maximum number of characters in the column. - numeric_precision: The numeric precision. - numeric_scale: The numeric scale. - remarks: Remarks about the column. ## Usage Tips 1. First use list_clusters to get valid cluster identifiers. 2. Then use list_databases to get valid database names for the cluster. 3. Then use list_schemas to get valid schema names for the database. 4. Then use list_tables to get valid table names for the schema. 5. Ensure the cluster status is 'available' before querying columns. 6. Note data types and constraints for query planning and data validation. ## Interpretation Best Practices 1. Use ordinal_position to understand column order in the table. 2. Check is_nullable for required vs optional fields. 3. Use data_type information for proper data handling in queries. 4. Consider character_maximum_length for string field validation. 5. Use numeric_precision and numeric_scale for numeric field handling. 6. Use column names for SELECT statements and query construction. """ try: logger.info( f'Discovering columns in table {column_table_name} in schema {column_schema_name} in database {column_database_name} on cluster {cluster_identifier}' ) columns = await discover_columns( cluster_identifier=cluster_identifier, column_database_name=column_database_name, column_schema_name=column_schema_name, column_table_name=column_table_name, ) logger.info( f'Successfully retrieved {len(columns)} columns from table {column_table_name} in schema {column_schema_name} in database {column_database_name} on cluster {cluster_identifier}' ) return columns except Exception as e: logger.error(f'Error in list_columns_tool: {str(e)}') await ctx.error( f'Failed to list columns in table {column_table_name} in schema {column_schema_name} in database {column_database_name} on cluster {cluster_identifier}: {str(e)}' ) raise @mcp.tool( name='execute_query', annotations=_read_only_annotations('Execute read-only Redshift query'), ) async def execute_query_tool( ctx: Context, cluster_identifier: str = Field( ..., description='The cluster identifier to execute the query on. Must be a valid cluster identifier from the list_clusters tool.', ), database_name: str = Field( ..., description='The database name to execute the query against. Must be a valid database name from the list_databases tool.', ), sql: str = Field( ..., description='The SQL statement to execute. Should be a single SQL statement.' ), ) -> QueryResult: """Execute a SQL query against a Redshift cluster or serverless workgroup. This tool uses the Redshift Data API to execute SQL queries and return results. It supports both provisioned clusters and serverless workgroups, and handles various data types in the result set. ## Usage Requirements - Ensure your AWS credentials are properly configured (via AWS_PROFILE or default credentials). - The cluster must be available and accessible. - Required IAM permissions: redshift-data:ExecuteStatement, redshift-data:DescribeStatement, redshift-data:GetStatementResult. - The user must have appropriate permissions to execute queries in the specified database. ## Parameters - cluster_identifier: The unique identifier of the Redshift cluster to query. IMPORTANT: Use a valid cluster identifier from the list_clusters tool. - database_name: The database name to execute the query against. IMPORTANT: Use a valid database name from the list_databases tool. - sql: The SQL statement to execute. Should be a single SQL statement. ## Response Structure Returns a QueryResult object with the following structure: - columns: List of column names in the result set. - rows: List of rows, where each row is a list of values. - row_count: Number of rows returned. - query_id: Unique identifier for the query execution. ## Usage Tips 1. First use list_clusters to get valid cluster identifiers. 2. Then use list_databases to get valid database names for the cluster. 3. Ensure the cluster status is 'available' before executing queries. 4. Use LIMIT clauses for exploratory queries to avoid large result sets. 5. Consider using the metadata discovery tools to understand table structures before querying. ## Data Type Handling The tool automatically handles various Redshift data types: - String values (VARCHAR, CHAR, TEXT). - Numeric values (INTEGER, BIGINT, DECIMAL, FLOAT). - Boolean values. - NULL values. - Date and timestamp values (returned as strings). ## Security Considerations - Avoid dynamic SQL construction with user input. - Consider database object permissions. - Queries run in read-only mode and must be a single statement; writes and multi-statement submissions are rejected. """ try: logger.info(f'Executing query on cluster {cluster_identifier} in database {database_name}') query_result_data = await execute_query( cluster_identifier=cluster_identifier, database_name=database_name, sql=sql ) # Convert to QueryResult model query_result = QueryResult(**query_result_data) logger.info( f'Successfully executed query on cluster {cluster_identifier}: {query_result.row_count} rows returned' ) return query_result except Exception as e: logger.error(f'Error in execute_query_tool: {str(e)}') await ctx.error( f'Failed to execute query on cluster {cluster_identifier} in database {database_name}: {str(e)}' ) raise @mcp.tool( name='review_cluster', annotations=_read_only_annotations('Review Redshift cluster'), ) async def review_cluster_tool( ctx: Context, cluster_identifier: str = Field( ..., description='The cluster identifier to run the review on. Must be a valid cluster identifier from the list_clusters tool.', ), database_name: str = Field( 'dev', description='The database to connect to for querying system views. Defaults to "dev".', ), ) -> ReviewResult: """Run a diagnostic review of a Redshift cluster or serverless workgroup. Returns identified potential issues and respective recommendations ordered by required mitigation effort. ## Usage Requirements - Ensure your AWS credentials are properly configured (via AWS_PROFILE or default credentials). - The cluster must be available and accessible. - Required IAM permissions: redshift-data:ExecuteStatement, redshift-data:DescribeStatement, redshift-data:GetStatementResult. - The connected database user must be able to read Redshift system views, which require superuser or sys:monitor access. If the current user is not a superuser, it must be granted the sys:monitor role: GRANT ROLE sys:monitor TO ""; where is the output of SELECT current_user, quoted because IAM identities contain a colon (IAM: or IAMR:). - Without that access the review fails fast rather than returning partial results. ## Parameters - cluster_identifier: The unique identifier of the Redshift cluster to review. IMPORTANT: Use a valid cluster identifier from the list_clusters tool. - database_name: The database to connect to for querying system views. Defaults to "dev". ## Response Structure Returns a ReviewResult object with the following structure: - signals_evaluated: Total number of diagnostic signals evaluated. - findings: List of triggered findings (one per triggered signal branch). The number of findings is len(findings) - do NOT derive it from affected_row_count. Each finding contains: - signal_name: The specific signal (condition) that was triggered. - section: The diagnostic query section this finding belongs to. - affected_row_count: How many objects match the signal, counted in `unit` (for example, 7 tables). This is the number of affected objects, NOT a count of findings, and values are NOT comparable across different units. Each signal is an independent count(*); the same object (a table, node, ...) may match several signals, so do NOT sum affected_row_count across findings or recommendations - the totals overlap and would over-count distinct objects. - unit: Unit of affected_row_count (e.g. tables, nodes, queues, queries). - recommendation_ids: List of recommendation IDs associated with this finding. - recommendations: Deduplicated list of recommendations ordered by effort, each containing: - id: Unique identifier for the recommendation. - text: Markdown text with description and documentation links. - triggered_by_signals: Names of signals that triggered this recommendation. - queries_executed: Names of diagnostic queries that were executed. ## Usage Tips 1. First use list_clusters to get valid cluster identifiers. 2. Then use list_databases to get valid database names for the cluster. 3. Ensure the cluster status is 'available' before running the review. 4. Provisioned-only diagnostics are automatically skipped for serverless workgroups. 5. Review runs read-only diagnostic queries against system views and tables. ## Interpretation Best Practices 1. When counting findings, use the number of entries in `findings` (for example, two findings each affecting 7 tables = "2 findings across 7 tables", not 14). Rank by affected_row_count only within the same unit; counts in different units (tables vs nodes vs queues) are not comparable. 2. Each recommendation includes documentation links — always follow these links for detailed guidance. 3. Use triggered_by_signals to understand which diagnostics surfaced each recommendation. 4. A review with zero findings indicates the cluster is healthy across all evaluated signals. 5. Findings are independent per-signal diagnostics; do NOT sum affected_row_count across findings or recommendations. The same object can match several signals, so the counts overlap and are not additive. 6. Close with a call to action: when there are findings, end the response by offering to help act on them - suggest starting with the lowest-effort, highest-impact items and ask whether to proceed. When there are no findings, state that the cluster is healthy across the evaluated signals. """ try: logger.info(f'Running review on cluster {cluster_identifier}, database {database_name}') result = await review_cluster( cluster_identifier=cluster_identifier, execute_query_func=execute_query, discover_clusters_func=discover_clusters, database_name=database_name, progress_reporter_func=ctx.report_progress, ) return result except Exception as e: logger.error(f'Error in review_cluster_tool: {str(e)}') await ctx.error(f'Failed to review cluster {cluster_identifier}: {str(e)}') raise def main(): """Run the MCP server with CLI argument support.""" mcp.run() if __name__ == '__main__': main()