Skip to content

Commit bd12d16

Browse files
committed
redshift fixes
1 parent 57f21f0 commit bd12d16

File tree

6 files changed

+374
-157
lines changed

6 files changed

+374
-157
lines changed

README.md

Lines changed: 26 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,67 +1210,7 @@ docker run --rm -i \
12101210
Amazon Redshift is a fast, scalable data warehouse that makes it simple and cost-effective to analyze all your data using standard SQL and your existing business intelligence tools.
12111211

12121212
> [!Note]
1213-
> Redshift requires a custom tools configuration file because it doesn't support all PostgreSQL features used in the prebuilt tools (e.g., `array_agg` with `ORDER BY`).
1214-
1215-
### Creating the Redshift Configuration
1216-
1217-
First, create a file named `redshift.yaml` with the following content:
1218-
1219-
```yaml
1220-
tools:
1221-
- name: list_tables
1222-
description: Lists detailed schema information for tables. If table_names are provided, shows details for those specific tables; otherwise shows all tables in user schemas.
1223-
parameters:
1224-
type: object
1225-
properties:
1226-
table_names:
1227-
type: string
1228-
description: Optional comma-separated list of table names. If empty, details for all tables in user-accessible schemas will be listed.
1229-
required:
1230-
- table_names
1231-
steps:
1232-
- type: sql
1233-
query: |
1234-
SELECT
1235-
c.table_schema AS schema_name,
1236-
c.table_name,
1237-
c.column_name,
1238-
c.ordinal_position AS column_position,
1239-
c.data_type,
1240-
c.is_nullable,
1241-
c.column_default
1242-
FROM
1243-
information_schema.columns c
1244-
WHERE
1245-
c.table_schema NOT IN ('pg_catalog', 'information_schema', 'pg_temp_1', 'pg_toast', 'pg_internal')
1246-
AND (
1247-
:table_names IS NULL
1248-
OR :table_names = ''
1249-
OR (',' || :table_names || ',') LIKE ('%,' || c.table_name || ',%')
1250-
)
1251-
ORDER BY
1252-
c.table_schema,
1253-
c.table_name,
1254-
c.ordinal_position;
1255-
params:
1256-
- name: table_names
1257-
type: string
1258-
description: Optional comma-separated list of table names
1259-
1260-
- name: execute_sql
1261-
description: Execute arbitrary SQL queries against the Redshift database.
1262-
parameters:
1263-
type: object
1264-
properties:
1265-
sql:
1266-
type: string
1267-
description: The SQL query to execute.
1268-
required:
1269-
- sql
1270-
steps:
1271-
- type: sql
1272-
query: "{{sql}}"
1273-
```
1213+
> Redshift uses a custom Docker image with pre-configured tools optimized for Redshift's SQL dialect.
12741214
12751215
### Docker Command
12761216

@@ -1280,18 +1220,14 @@ REDSHIFT_DATABASE=mydb \
12801220
REDSHIFT_USER=awsuser \
12811221
REDSHIFT_PASSWORD=your-password \
12821222
REDSHIFT_PORT=5439 \
1283-
REDSHIFT_TOOLS_FILE=/path/to/redshift.yaml \
12841223
docker run --rm -i \
12851224
--name mcp-redshift \
1286-
-e POSTGRES_HOST=$REDSHIFT_HOST \
1287-
-e POSTGRES_DATABASE=$REDSHIFT_DATABASE \
1288-
-e POSTGRES_USER=$REDSHIFT_USER \
1289-
-e POSTGRES_PASSWORD=$REDSHIFT_PASSWORD \
1290-
-e POSTGRES_PORT=$REDSHIFT_PORT \
1291-
-v $REDSHIFT_TOOLS_FILE:/config/redshift.yaml \
1292-
us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest \
1293-
--tools-file /config/redshift.yaml \
1294-
--stdio
1225+
-e REDSHIFT_HOST \
1226+
-e REDSHIFT_DATABASE \
1227+
-e REDSHIFT_USER \
1228+
-e REDSHIFT_PASSWORD \
1229+
-e REDSHIFT_PORT \
1230+
us-central1-docker.pkg.dev/database-toolbox/toolbox/redshift:latest
12951231
```
12961232

12971233
### MCP Client Configuration
@@ -1301,37 +1237,33 @@ docker run --rm -i \
13011237
"command": "docker",
13021238
"args": [
13031239
"run", "--rm", "-i",
1304-
"-e", "POSTGRES_HOST",
1305-
"-e", "POSTGRES_DATABASE",
1306-
"-e", "POSTGRES_USER",
1307-
"-e", "POSTGRES_PASSWORD",
1308-
"-e", "POSTGRES_PORT",
1309-
"-v", "${REDSHIFT_TOOLS_FILE}:/config/redshift.yaml",
1310-
"us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest",
1311-
"--tools-file", "/config/redshift.yaml",
1312-
"--stdio"
1240+
"-e", "REDSHIFT_HOST",
1241+
"-e", "REDSHIFT_DATABASE",
1242+
"-e", "REDSHIFT_USER",
1243+
"-e", "REDSHIFT_PASSWORD",
1244+
"-e", "REDSHIFT_PORT",
1245+
"us-central1-docker.pkg.dev/database-toolbox/toolbox/redshift:latest"
13131246
],
13141247
"env": {
1315-
"POSTGRES_HOST": "your-cluster.redshift.amazonaws.com",
1316-
"POSTGRES_DATABASE": "mydb",
1317-
"POSTGRES_USER": "awsuser",
1318-
"POSTGRES_PASSWORD": "your-password",
1319-
"POSTGRES_PORT": "5439",
1320-
"REDSHIFT_TOOLS_FILE": "/path/to/redshift.yaml"
1248+
"REDSHIFT_HOST": "your-cluster.redshift.amazonaws.com",
1249+
"REDSHIFT_DATABASE": "mydb",
1250+
"REDSHIFT_USER": "awsuser",
1251+
"REDSHIFT_PASSWORD": "your-password",
1252+
"REDSHIFT_PORT": "5439"
13211253
}
13221254
}
13231255
```
13241256

13251257
### Environment Variables
13261258

1327-
| Variable | Required | Description | Default | Example |
1328-
| --------------------- | -------- | -------------------------------- | ------- | ------------------------------------- |
1329-
| `POSTGRES_HOST` | Yes | Redshift cluster endpoint | - | `your-cluster.redshift.amazonaws.com` |
1330-
| `POSTGRES_PORT` | No | Redshift port | `5439` | `5439` |
1331-
| `POSTGRES_DATABASE` | Yes | Database name | - | `mydb` |
1332-
| `POSTGRES_USER` | Yes | Username | - | `awsuser` |
1333-
| `POSTGRES_PASSWORD` | Yes | Password | - | `your-password` |
1334-
| `REDSHIFT_TOOLS_FILE` | Yes | Path to tools configuration YAML | - | `/path/to/redshift.yaml` |
1259+
| Variable | Required | Description | Default | Example |
1260+
| ------------------- | -------- | ------------------------- | -------- | ------------------------------------- |
1261+
| `REDSHIFT_HOST` | Yes | Redshift cluster endpoint | - | `your-cluster.redshift.amazonaws.com` |
1262+
| `REDSHIFT_PORT` | No | Redshift port | `5439` | `5439` |
1263+
| `REDSHIFT_DATABASE` | Yes | Database name | - | `mydb` |
1264+
| `REDSHIFT_USER` | Yes | Username | - | `awsuser` |
1265+
| `REDSHIFT_PASSWORD` | Yes | Password | - | `your-password` |
1266+
| `REDSHIFT_SSL_MODE` | No | SSL mode | `prefer` | `disable`, `require`, `verify-full` |
13351267

13361268

13371269
## Snowflake

images/redshift/entrypoint.sh

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,40 @@ check_redshift_tools_file() {
3030
fi
3131
}
3232

33+
# Function to map REDSHIFT_* environment variables to POSTGRES_*
34+
# The toolbox uses POSTGRES_* internally for the postgres kind source
35+
map_redshift_to_postgres_vars() {
36+
# Map REDSHIFT_HOST to POSTGRES_HOST if POSTGRES_HOST is not set
37+
if [[ -n "${REDSHIFT_HOST:-}" && -z "${POSTGRES_HOST:-}" ]]; then
38+
export POSTGRES_HOST="${REDSHIFT_HOST}"
39+
fi
40+
41+
# Map REDSHIFT_PORT to POSTGRES_PORT if POSTGRES_PORT is not set
42+
if [[ -n "${REDSHIFT_PORT:-}" && -z "${POSTGRES_PORT:-}" ]]; then
43+
export POSTGRES_PORT="${REDSHIFT_PORT}"
44+
fi
45+
46+
# Map REDSHIFT_DATABASE to POSTGRES_DATABASE if POSTGRES_DATABASE is not set
47+
if [[ -n "${REDSHIFT_DATABASE:-}" && -z "${POSTGRES_DATABASE:-}" ]]; then
48+
export POSTGRES_DATABASE="${REDSHIFT_DATABASE}"
49+
fi
50+
51+
# Map REDSHIFT_USER to POSTGRES_USER if POSTGRES_USER is not set
52+
if [[ -n "${REDSHIFT_USER:-}" && -z "${POSTGRES_USER:-}" ]]; then
53+
export POSTGRES_USER="${REDSHIFT_USER}"
54+
fi
55+
56+
# Map REDSHIFT_PASSWORD to POSTGRES_PASSWORD if POSTGRES_PASSWORD is not set
57+
if [[ -n "${REDSHIFT_PASSWORD:-}" && -z "${POSTGRES_PASSWORD:-}" ]]; then
58+
export POSTGRES_PASSWORD="${REDSHIFT_PASSWORD}"
59+
fi
60+
61+
# Map REDSHIFT_SSL_MODE to POSTGRES_SSL_MODE if POSTGRES_SSL_MODE is not set
62+
if [[ -n "${REDSHIFT_SSL_MODE:-}" && -z "${POSTGRES_SSL_MODE:-}" ]]; then
63+
export POSTGRES_SSL_MODE="${REDSHIFT_SSL_MODE}"
64+
fi
65+
}
66+
3367
# Main execution
3468
main() {
3569
log "Starting Redshift custom toolbox entrypoint"
@@ -40,9 +74,12 @@ main() {
4074
# Check if Redshift tools file exists
4175
check_redshift_tools_file
4276

43-
# Execute the original toolbox with Redshift tools file and passed arguments
44-
log "Executing Redshift toolbox with arguments: $*"
45-
exec "$ORIGINAL_ENTRYPOINT" "--tools-file" "$REDSHIFT_TOOLS_FILE" "$@"
77+
# Map REDSHIFT_* environment variables to POSTGRES_*
78+
map_redshift_to_postgres_vars
79+
80+
# Execute the original toolbox with Redshift tools file, --stdio, and any additional arguments
81+
log "Executing Redshift toolbox with arguments: --tools-file $REDSHIFT_TOOLS_FILE --stdio $*"
82+
exec "$ORIGINAL_ENTRYPOINT" "--tools-file" "$REDSHIFT_TOOLS_FILE" "--stdio" "$@"
4683
}
4784

4885
# Handle edge cases

tests/redshift/example_usage.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example usage of the Redshift MCP Server
4+
Demonstrates how to use the list_tables and execute_sql tools
5+
"""
6+
7+
import json
8+
import subprocess
9+
import sys
10+
from pathlib import Path
11+
12+
# Configuration - update these with your Redshift credentials
13+
REDSHIFT_HOST = "redshift-prod-cluster.cd6o5qin6bij.us-west-2.redshift.amazonaws.com"
14+
REDSHIFT_DATABASE = "redshift"
15+
REDSHIFT_USER = "redshift_readonly"
16+
REDSHIFT_PASSWORD = "your-password"
17+
REDSHIFT_PORT = "5439"
18+
DOCKER_IMAGE = "redshift-toolbox:latest" # Or use the built image
19+
20+
21+
def send_mcp_request(process, method, params, request_id):
22+
"""Send an MCP JSON-RPC request"""
23+
request = {
24+
"jsonrpc": "2.0",
25+
"method": method,
26+
"params": params,
27+
"id": request_id,
28+
}
29+
process.stdin.write(json.dumps(request) + "\n")
30+
process.stdin.flush()
31+
32+
33+
def read_mcp_response(process):
34+
"""Read an MCP JSON-RPC response"""
35+
line = process.stdout.readline()
36+
if not line:
37+
return None
38+
return json.loads(line)
39+
40+
41+
def list_tables_example(process, table_names=""):
42+
"""Example: List tables"""
43+
print(f"\n📋 Listing tables (table_names='{table_names}')...")
44+
send_mcp_request(
45+
process,
46+
"tools/call",
47+
{
48+
"name": "list_tables",
49+
"arguments": {"table_names": table_names},
50+
},
51+
request_id=100,
52+
)
53+
54+
response = read_mcp_response(process)
55+
if response and "result" in response:
56+
content = response["result"].get("content", [])
57+
print(f"✓ Found {len(content)} columns")
58+
59+
# Parse and show first few columns
60+
if content:
61+
print("\nFirst few columns:")
62+
for i, item in enumerate(content[:5]):
63+
if item.get("type") == "text":
64+
col_info = json.loads(item["text"])
65+
print(f" - {col_info['schema_name']}.{col_info['table_name']}.{col_info['column_name']} ({col_info['data_type']})")
66+
return True
67+
else:
68+
print(f"✗ Error: {response.get('error', 'Unknown error')}")
69+
return False
70+
71+
72+
def execute_sql_example(process, sql_query):
73+
"""Example: Execute SQL query"""
74+
print(f"\n🔍 Executing SQL: {sql_query[:50]}...")
75+
send_mcp_request(
76+
process,
77+
"tools/call",
78+
{
79+
"name": "execute_sql",
80+
"arguments": {"sql": sql_query},
81+
},
82+
request_id=200,
83+
)
84+
85+
response = read_mcp_response(process)
86+
if response and "result" in response:
87+
content = response["result"].get("content", [])
88+
print("✓ Query executed successfully")
89+
90+
# Show results
91+
for item in content:
92+
if item.get("type") == "text":
93+
print(f" {item['text']}")
94+
return True
95+
else:
96+
print(f"✗ Error: {response.get('error', 'Unknown error')}")
97+
return False
98+
99+
100+
def main():
101+
print("🚀 Redshift MCP Server Examples\n")
102+
103+
# Start the Docker container
104+
cmd = [
105+
"docker", "run", "--rm", "-i",
106+
"-e", "POSTGRES_HOST",
107+
"-e", "POSTGRES_DATABASE",
108+
"-e", "POSTGRES_USER",
109+
"-e", "POSTGRES_PASSWORD",
110+
"-e", "POSTGRES_PORT",
111+
DOCKER_IMAGE,
112+
"--stdio",
113+
]
114+
115+
env = {
116+
"POSTGRES_HOST": REDSHIFT_HOST,
117+
"POSTGRES_DATABASE": REDSHIFT_DATABASE,
118+
"POSTGRES_USER": REDSHIFT_USER,
119+
"POSTGRES_PASSWORD": REDSHIFT_PASSWORD,
120+
"POSTGRES_PORT": REDSHIFT_PORT,
121+
}
122+
123+
process = subprocess.Popen(
124+
cmd,
125+
stdin=subprocess.PIPE,
126+
stdout=subprocess.PIPE,
127+
stderr=subprocess.PIPE,
128+
text=True,
129+
env=env,
130+
)
131+
132+
try:
133+
# Initialize
134+
print("1. Initializing MCP connection...")
135+
send_mcp_request(
136+
process,
137+
"initialize",
138+
{
139+
"protocolVersion": "1.0.0",
140+
"capabilities": {},
141+
"clientInfo": {"name": "example-client", "version": "1.0.0"},
142+
},
143+
request_id=1,
144+
)
145+
146+
init_response = read_mcp_response(process)
147+
if init_response and "result" in init_response:
148+
print("✓ Connected to Redshift MCP Server")
149+
else:
150+
print(f"✗ Failed to initialize: {init_response}")
151+
return
152+
153+
# Example 1: List all tables
154+
list_tables_example(process, table_names="")
155+
156+
# Example 2: List specific tables
157+
list_tables_example(process, table_names="stripe_subscriptions,teams_aggregate")
158+
159+
# Example 3: Simple query
160+
execute_sql_example(process, "SELECT current_date AS today;")
161+
162+
# Example 4: Count rows
163+
execute_sql_example(process, "SELECT COUNT(*) as total FROM analytics.stripe_subscriptions;")
164+
165+
# Example 5: Query with filters
166+
execute_sql_example(
167+
process,
168+
"SELECT org_id, subscription_status, plan_slug FROM analytics.teams_aggregate LIMIT 5;"
169+
)
170+
171+
print("\n✅ Examples completed!")
172+
173+
except Exception as e:
174+
print(f"✗ Error: {e}")
175+
stderr_output = process.stderr.read() if process.stderr else ""
176+
if stderr_output:
177+
print(f"✗ Stderr: {stderr_output}")
178+
finally:
179+
process.terminate()
180+
process.wait(timeout=5)
181+
182+
183+
if __name__ == "__main__":
184+
main()
185+

0 commit comments

Comments
 (0)