Skip to content

fix(redshift-mcp-server): handle missing DBName field in discover_clusters#2685

Merged
grayhemp merged 4 commits into
awslabs:mainfrom
abhu85:fix/redshift-dbname-keyerror-2331
Mar 31, 2026
Merged

fix(redshift-mcp-server): handle missing DBName field in discover_clusters#2685
grayhemp merged 4 commits into
awslabs:mainfrom
abhu85:fix/redshift-dbname-keyerror-2331

Conversation

@abhu85
Copy link
Copy Markdown
Contributor

@abhu85 abhu85 commented Mar 16, 2026

Summary

  • Fixes KeyError: 'DBName' when discover_clusters() processes clusters without the DBName field present
  • Uses safe dictionary access via .get('DBName', 'dev') with default to 'dev' (Redshift's default database name)
  • Adds regression test to verify clusters without DBName are handled gracefully

Problem

The describe_clusters API may return cluster metadata without a DBName field in certain scenarios (e.g., clusters created via certain methods, or clusters in specific states). The current implementation used direct dictionary access cluster['DBName'] which raises KeyError when the field is missing.

Per AWS API Reference, DBName is marked as "Required: No".

Root Cause

Line 417 in redshift.py:

'database_name': cluster['DBName'],  # Direct access - raises KeyError if missing

Other fields in the same block already used safe access patterns (.get()), but DBName was missed.

Fix

Changed to safe dictionary access with default value:

'database_name': cluster.get('DBName', 'dev'),  # Returns 'dev' if missing

This matches the existing Serverless workgroup behavior which already defaults to 'dev'.

Tests Run

All 13 discover tests pass, including new test for missing DBName scenario.

Fixes #2331


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.

…sters

The describe_clusters API may return cluster metadata without a DBName
field in certain scenarios. Previously, direct key access caused a
KeyError when this field was missing.

Fix: Use dict.get('DBName') for safe access, returning None when the
field is absent instead of raising an exception.

Includes regression test that verifies the function handles clusters
without DBName gracefully.

Fixes: awslabs#2331

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: abhu85 <60182103+abhu85@users.noreply.github.com>
Per maintainer @grayhemp's feedback in issue awslabs#2331, default to 'dev'
(Redshift's default database name) instead of None when DBName field
is missing from the cluster metadata.

This matches the behavior of the Serverless workgroup discovery code
which already defaults to 'dev'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@abhu85
Copy link
Copy Markdown
Contributor Author

abhu85 commented Mar 20, 2026

Thanks @grayhemp for the guidance!

I've updated the PR to default to 'dev' instead of None:

'database_name': cluster.get('DBName', 'dev'),

This matches the existing Serverless workgroup behavior (lines 454-456) which already defaults to 'dev'.

Regarding Serverless - I checked and it already handles the missing database name case correctly via:

'database_name': workgroup_detail.get('configParameters', [{}])[0].get('parameterValue', 'dev'),

So only the provisioned cluster path needed this fix.

@grayhemp
Copy link
Copy Markdown
Contributor

I've updated the PR to default to 'dev' instead of None:
...
This matches the existing Serverless workgroup behavior (lines 454-456) which already defaults to 'dev'.

Indeed, awesome. Thank you very much.

Comment thread src/redshift-mcp-server/tests/test_redshift.py Outdated
@grayhemp
Copy link
Copy Markdown
Contributor

Additionally, could you please run E2E tests and post the results, similarly to how it is done here #1927 (see the description)? You might need to modify the prompts to exclude the PRs specific part as it's not merged yet. Thanks again.

Per maintainer feedback, modified test_discover_clusters_provisioned() to
return 2 mock entries instead of a separate test method:
- Entry 1: Full cluster data (extends entry 2)
- Entry 2: Minimal data with DBName omitted

This tests the .get('DBName', 'dev') fix within the existing test coverage,
avoiding code duplication and extra test methods.
@abhu85
Copy link
Copy Markdown
Contributor Author

abhu85 commented Mar 23, 2026

@grayhemp Thanks for the feedback! I've refactored the test as suggested:

  • Modified test_discover_clusters_provisioned() to return 2 mock entries
  • First entry (full_cluster) extends second entry (minimal_cluster) to avoid code duplication
  • Minimal entry has DBName omitted to test the .get('DBName', 'dev') fix
  • Removed the separate test_discover_clusters_provisioned_missing_dbname test method

All tests pass locally:

tests/test_redshift.py::TestDiscoverFunctions::test_discover_clusters_provisioned PASSED
tests/test_redshift.py::TestDiscoverFunctions::test_discover_clusters_provisioned_error PASSED
tests/test_redshift.py::TestDiscoverFunctions::test_discover_clusters_serverless PASSED
tests/test_redshift.py::TestDiscoverFunctions::test_discover_clusters_serverless_error PASSED

Regarding E2E tests - I don't have a Redshift cluster available for testing. Could you run E2E tests on your end, or is there a test cluster I could use?

@grayhemp
Copy link
Copy Markdown
Contributor

grayhemp commented Mar 30, 2026

kiro-cli chat \
    --model claude-opus-4.6-1m \
    --no-interactive \
    --trust-tools "@{awslabs.redshift-mcp-server}/*,execute_bash,fs_read" \
'Use Redshift MCP Server to run end-to-end (integration) tests covering the scenarios for the changes introduced in the current branch only. Get the test scenario ideas from the unit tests under the project directory. Provide a short testing summary, one line per scenario.'

Change Specific Integration Test Summary

# Scenario Result
1 Cluster discovery returns provisioned clusters with all fields populated (including database_name) ✅ PASS
2 Cluster discovery returns serverless workgroups alongside provisioned clusters ✅ PASS
3 database_name field is always present — no KeyError when DBName key is missing from API response (core fix for #2331) ✅ PASS
4 Optional fields (tags, endpoint, port, node_type, number_of_nodes) are handled gracefully with defaults ✅ PASS
5 Downstream list_databases works using the discovered database_name on both provisioned and serverless clusters ✅ PASS
6 Downstream list_schemas works using the discovered database on a provisioned cluster ✅ PASS
7 End-to-end query execution succeeds using the discovered database_name ("dev") from cluster discovery ✅ PASS

All 7 scenarios passed. The .get('DBName', 'dev') fix correctly defaults database_name to
"dev" when the key is absent, and the full discovery-to-query pipeline works without
errors.

@grayhemp
Copy link
Copy Markdown
Contributor

kiro-cli chat \
    --model claude-opus-4.6-1m \
    --no-interactive \
    --trust-tools "@{awslabs.redshift-mcp-server}/*,execute_bash,fs_read" \
'Use Redshift MCP Server to run a complete set of end-to-end (integration) tests covering all its tools. Check both provisioned and serverless clusters, including the database schema exploration in both. Check the SQL read-only protection, transaction breaker protection, failed user SQL behavior. Get the test scenario ideas from the unit tests under the project directory. Provide a short testing summary, one line per tool.'

Complete Integration Test Summary

Tool Result Details
list_clusters ✅ PASS Discovered 2 provisioned clusters + 1 serverless workgroup with correct metadata (type, status, endpoint, node_type, encryption)
list_databases (provisioned) ✅ PASS Returned dev and sample_data_dev databases with owner, type, and isolation level on redshift-cluster-analytics
list_databases (serverless) ✅ PASS Returned same databases on redshift-workgroup-etl serverless workgroup
list_schemas (provisioned) ✅ PASS Returned 4 schemas (information_schema, pg_catalog, public, tickit) with correct ACLs on provisioned cluster
list_schemas (serverless) ✅ PASS Returned same 4 schemas on serverless workgroup with IAM role-based ACLs
list_tables (provisioned) ✅ PASS Returned 7 tickit tables (category, date, event, listing, sales, users, venue) with TABLE type on provisioned
list_tables (serverless) ✅ PASS Returned identical 7 tables on serverless workgroup
list_columns (provisioned) ✅ PASS Returned 18 columns for users table with correct data types (integer, character, boolean), ordinal positions, and nullability
list_columns (serverless) ✅ PASS Returned identical 18 columns on serverless workgroup
execute_query (provisioned SELECT) ✅ PASS Returned 3 rows with columns, row_count, execution_time_ms, and query_id on provisioned cluster
execute_query (serverless SELECT) ✅ PASS Returned 3 rows with same structure on serverless workgroup
execute_query (read-only protection) ✅ PASS INSERT blocked with "transaction is read-only" — BEGIN READ ONLY wrapper works
execute_query (transaction breaker: END) ✅ PASS END; DROP TABLE rejected with "suspicious pattern" before reaching Redshift
execute_query (transaction breaker: COMMIT) ✅ PASS COMMIT; INSERT rejected with "suspicious pattern" before reaching Redshift
execute_query (transaction breaker: ABORT) ✅ PASS ABORT work; SELECT rejected with "suspicious pattern"
execute_query (transaction breaker: ROLLBACK) ✅ PASS ROLLBACK TRANSACTION; SELECT rejected with "suspicious pattern"
execute_query (failed SQL: syntax error) ✅ PASS Returns clear Redshift error "syntax error at or near" — transaction properly closed via END
execute_query (failed SQL: missing table) ✅ PASS Returns "relation does not exist" — transaction properly closed, no orphaned sessions
Data integrity verification ✅ PASS 49,990 users confirmed intact after all attack/error tests — no data was modified

All 19 test scenarios passed across both provisioned and serverless cluster types. The three protection layers (read-only transactions, transaction breaker regex, graceful error handling with END cleanup) all function correctly in production.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.29%. Comparing base (6fda1e9) to head (8dec995).
⚠️ Report is 22 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2685      +/-   ##
==========================================
- Coverage   91.30%   91.29%   -0.02%     
==========================================
  Files        1001     1001              
  Lines       73725    73725              
  Branches    11880    11880              
==========================================
- Hits        67316    67305      -11     
- Misses       3959     3966       +7     
- Partials     2450     2454       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@grayhemp
Copy link
Copy Markdown
Contributor

@abhu85 Thank you. I ran E2E tests and they all look good. Let me get the second reviewer/approval.

Copy link
Copy Markdown
Member

@scottschreckengaust scottschreckengaust left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@grayhemp grayhemp added this pull request to the merge queue Mar 31, 2026
Merged via the queue into awslabs:main with commit 6185e83 Mar 31, 2026
147 checks passed
@github-project-automation github-project-automation Bot moved this from To triage to Done in awslabs/mcp Project Mar 31, 2026
ibrahimcesar pushed a commit to ibrahimcesar/mcp that referenced this pull request Jun 2, 2026
…sters (awslabs#2685)

* fix(redshift-mcp-server): handle missing DBName field in discover_clusters

The describe_clusters API may return cluster metadata without a DBName
field in certain scenarios. Previously, direct key access caused a
KeyError when this field was missing.

Fix: Use dict.get('DBName') for safe access, returning None when the
field is absent instead of raising an exception.

Includes regression test that verifies the function handles clusters
without DBName gracefully.

Fixes: awslabs#2331

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: abhu85 <60182103+abhu85@users.noreply.github.com>

* fix: default DBName to 'dev' per maintainer feedback

Per maintainer @grayhemp's feedback in issue awslabs#2331, default to 'dev'
(Redshift's default database name) instead of None when DBName field
is missing from the cluster metadata.

This matches the behavior of the Serverless workgroup discovery code
which already defaults to 'dev'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: merge DBName default test into existing provisioned test

Per maintainer feedback, modified test_discover_clusters_provisioned() to
return 2 mock entries instead of a separate test method:
- Entry 1: Full cluster data (extends entry 2)
- Entry 2: Minimal data with DBName omitted

This tests the .get('DBName', 'dev') fix within the existing test coverage,
avoiding code duplication and extra test methods.

---------

Signed-off-by: abhu85 <60182103+abhu85@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Redshift MCP: list_clusters fails with KeyError: 'DBName' when DBName field is missing from cluster metadata

3 participants