redshift-mcp-server
README.md
# Redshift MCP Server
**Give AI assistants secure, read-only access to your Amazon Redshift data warehouse.**
This TypeScript-based [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server enables LLMs to inspect schemas, execute queries, and understand your data warehouse structure.
> π Based on the [original implementation](https://github.com/paschmaria/redshift-mcp-server) by paschmaria, with production-ready enhancements.
## β¨ Features
- π **Read-only queries** with automatic transaction safety
- ποΈ **Schema introspection** - tables, columns, relationships
- π **Smart sampling** - optional PII redaction (emails, phones)
- π **Statistics** - table sizes, row counts, distribution keys
- π **Column search** - find columns across all schemas
- π **Dual modes** - STDIO (IDEs) + HTTP (web/cloud)
- π **Bearer auth** - production-ready security
- βΈοΈ **Cloud-native** - stateless mode, health checks, K8s-ready
- π³ **Docker** - single-command deployment
## π Quick Start
### Local Setup (5 minutes)
```bash
# 1. Clone and install
git clone <repository-url>
cd redshift-mcp-server
npm install
# 2. Build
npm run build
# 3. Configure
export DATABASE_URL="redshift://user:pass@host:5439/db?ssl=true"
# 4. Run (STDIO mode for IDE)
npm start
# OR run HTTP mode for web/cloud
export TRANSPORT_MODE="http"
npm start
# Server: http://localhost:3000/mcp or http://localhost:3000/
```
### Docker (1 minute)
```bash
# Build
docker build -t redshift-mcp:latest .
# Run STDIO (for IDEs)
docker run -e DATABASE_URL='redshift://...' -i --rm redshift-mcp:latest
# Run HTTP with auth (for production)
docker run \
-e DATABASE_URL='redshift://...' \
-e TRANSPORT_MODE=http \
-e STATELESS_MODE=true \
-e ENABLE_AUTH=true \
-e API_TOKEN=your-secret-token \
-e REDACT_PII=false \
-p 3000:3000 \
redshift-mcp:latest
```
---
## π Table of Contents
- [Configuration](#-configuration)
- [Transport Modes](#-transport-modes)
- [Authentication](#-authentication)
- [IDE Integration](#-ide-integration)
- [Testing](#-testing)
- [A2A (Experimental)](#-a2a-experimental)
- [Dust.tt Integration](#-dusttt-integration)
- [Kubernetes Deployment](#%EF%B8%8F-kubernetes-deployment)
- [Available Tools](#-available-tools)
- [Troubleshooting](#-troubleshooting)
---
## βοΈ Configuration
### Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `DATABASE_URL` | β
Yes | - | Redshift connection string |
| `TRANSPORT_MODE` | No | `stdio` | `stdio` for IDEs, `http` for web/cloud |
| `PORT` | No | `3000` | HTTP server port |
| `STATELESS_MODE` | No | `false` | `true` for horizontal scaling |
| `ENABLE_AUTH` | No | `false` | Enable Bearer token authentication |
| `API_TOKEN` | No | - | Bearer token (required if `ENABLE_AUTH=true`) |
| `ALLOWED_ORIGINS` | No | `*` | CORS allowed origins |
| `ENABLE_RESUMABILITY` | No | `false` | Event resumability (stateful mode only) |
| `REDACT_PII` | No | `false` | Redact email/phone in output data |
### Database URL Format
```
redshift://username:password@hostname:port/database?ssl=true&timeout=600
```
**Example:**
```bash
DATABASE_URL="redshift://admin:MyPass123@cluster.us-east-1.redshift.amazonaws.com:5439/analytics?ssl=true"
```
### Configuration File (`.env`)
```bash
# Copy example
cp .env.example .env
# Edit with your values
DATABASE_URL="redshift://..."
TRANSPORT_MODE="http"
STATELESS_MODE="true"
ENABLE_AUTH="true"
API_TOKEN="your-secret-token-here"
REDACT_PII="false"
```
> β οΈ **Special characters in passwords:** If your `DATABASE_URL` password contains shell metacharacters (`$`, `` ` ``, `\`, etc.), use **single quotes** in `.env`, not double quotes. When you `source` (or `set -a && source .env && set +a`) a `.env` file, bash performs variable expansion inside double-quoted values. For example, a password containing `$9` will be silently expanded as a positional parameter (usually to an empty string), truncating/corrupting your password.
>
> ```bash
> # β Wrong - $9 gets expanded by bash on `source`
> DATABASE_URL="redshift://user:kjash$90sbb@host:5439/db?ssl=true"
>
> # β
Correct - single quotes prevent all expansion
> DATABASE_URL='redshift://user:kjash$90sbb@host:5439/db?ssl=true'
> ```
---
## π Transport Modes
Choose the right transport mode for your use case:
### STDIO Mode (Default)
**Best for:** IDEs, CLI tools, local development
```bash
# Default mode - no configuration needed
export DATABASE_URL="redshift://..."
npm start
```
**Clients:**
- Cursor IDE
- Windsurf
- Claude Desktop
- Custom CLI tools
**How it works:** Communicates via standard input/output streams
### HTTP Mode
**Best for:** Web apps, Dust.tt, Kubernetes, remote integrations
```bash
# Enable HTTP transport
export DATABASE_URL="redshift://..."
export TRANSPORT_MODE="http"
npm start
```
**Endpoints:**
- `POST/GET/DELETE /mcp` - MCP protocol endpoint
- `POST/GET/DELETE /` - Root path (alias for `/mcp`)
- `GET /health` - Health check with metrics
- `GET /ready` - Readiness probe
**Stateful vs Stateless:**
| Mode | Best For | Sessions | Scaling | Set With |
|------|----------|----------|---------|----------|
| **Stateful** | IDE clients, MCP Inspector | β
Session-based | Needs sticky sessions | `STATELESS_MODE=false` (default) |
| **Stateless** | Dust.tt, K8s, APIs | β No sessions | β
Horizontal scaling | `STATELESS_MODE=true` |
**Production recommendation:** Use `STATELESS_MODE=true` for cloud deployments
---
## π Authentication
### Bearer Token Auth (Production)
Enable authentication for production deployments (required for Dust.tt, recommended for K8s):
```bash
export TRANSPORT_MODE="http"
export ENABLE_AUTH="true"
export API_TOKEN="your-super-secret-token-here"
npm start
```
**How it works:**
1. Clients send requests with `Authorization: Bearer <token>` header
2. Server validates token against `API_TOKEN`
3. Invalid/missing tokens receive `401 Unauthorized`
**Security features:**
- OPTIONS requests (CORS preflight) don't require auth
- Health/ready endpoints don't require auth
- OAuth discovery endpoints return 404 (tells clients OAuth is not available)
**Testing authentication:**
```bash
# Without token - should fail
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1}'
# Returns: 401 Unauthorized
# With token - should work
curl -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer your-super-secret-token-here" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
# Returns: 200 OK with server capabilities
```
**Best practices:**
- Generate strong tokens: `openssl rand -hex 32`
- Store tokens in secrets (K8s Secrets, env vars, vault)
- Rotate tokens regularly (every 90 days)
- Use HTTPS in production (ngrok, load balancer, ingress)
---
## π» IDE Integration
### Cursor / Windsurf / Claude Desktop
**Add to your MCP config file:**
- Cursor: `.cursor/mcp.json`
- Windsurf: `mcp_config.json`
- Claude Desktop: `claude_desktop_config.json`
#### Option 1: Node.js (Recommended)
```json
{
"mcpServers": {
"redshift": {
"command": "node",
"args": ["/absolute/path/to/redshift-mcp-server/dist/index.js"],
"env": {
"DATABASE_URL": "redshift://user:pass@host:5439/db?ssl=true",
"REDACT_PII": "false"
}
}
}
}
```
β οΈ **Important:** Use absolute paths, not relative paths!
#### Option 2: Docker
```json
{
"mcpServers": {
"redshift": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "DATABASE_URL",
"-e", "REDACT_PII",
"redshift-mcp:latest"
],
"env": {
"DATABASE_URL": "redshift://user:pass@host:5439/db?ssl=true",
"REDACT_PII": "false"
}
}
}
}
```
**After configuration:**
1. Restart your IDE
2. Tools appear automatically in MCP settings
3. Ask AI: "What tables are in my database?"
---
## π MCP Inspector (Testing Tool)
Anthropic's [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is a web-based tool for testing MCP servers.
**Setup:**
```bash
# 1. Start server with auth (optional)
export DATABASE_URL="redshift://..."
export TRANSPORT_MODE="http"
export STATELESS_MODE="true"
export ENABLE_AUTH="true"
export API_TOKEN="test-token-123"
npm start
```
**2. Open MCP Inspector and connect:**
- **Transport:** Streamable HTTP
- **Connection:** Direct
- **URL:** `http://localhost:3000/mcp` or `http://localhost:3000/`
- **Authentication:** Custom Header (if enabled)
- Header: `Authorization`
- Value: `Bearer test-token-123`
**3. Test tools:**
- List tools
- Execute `query` tool
- Check resources
---
## π§ͺ Testing
There's no local emulator for Redshift itself, so tests run against plain Postgres (`postgres:16-alpine` via Docker) with a compatibility shim for the handful of Redshift-only system views this project relies on (`SVV_COLUMNS`, `SVV_TABLES`, `SVV_TABLE_INFO`, and `pg_attribute.attisdistkey`/`attsortkeyord`). It's a real emulator for *this project's* query surface, not a general Redshift emulator β Redshift-only SQL like `COPY`/`UNLOAD`, or distribution/sort-aware query plans, still isn't covered, and `is_distkey`/`is_sortkey` always come back `false`.
### Integration tests (`npm test`)
```bash
npm test
```
This spins up the emulator (Docker required), seeds it with a small `clients` / `orders` / `order_items` dataset (see [`test/docker/init/`](./test/docker/init/)) plus a second `marketing` schema, runs the full [`RedshiftTools`](./src/core/redshift-tools.ts) suite against it via [vitest](https://vitest.dev), and tears the container down again β no manual setup, and no state left behind. It's safe to run in CI (e.g. the existing `publish-mcp.yml` workflow already calls `npm run test --if-present`, and GitHub's `ubuntu-latest` runners ship Docker).
Useful variants:
```bash
npm run test:watch # vitest in watch mode
npm run test:db:up # start the emulator and leave it running
npm run test:db:logs # tail Postgres logs
npm run test:db:down # stop it
KEEP_TEST_DB=true npm test # run the suite but leave the container up afterward, for poking around
```
Connect to the running emulator directly with `psql "redshift://redshift:redshift@localhost:5439/analytics"` (or any Postgres client) once it's up.
### End-to-end smoke test (`npm run test:e2e`)
```bash
npm run test:e2e
```
This drives the **real `claude` CLI** against a real build of this server, connected to the same local emulator, and asks it a natural-language question ("how many orders have status 'completed'?") to confirm the whole stack β MCP protocol, tool schema, SQL generation, DB round-trip β works end to end, not just the internal `RedshiftTools` methods.
The MCP server is registered for that single invocation only, via:
```bash
claude -p "..." \
--mcp-config <temp-file>.json \
--strict-mcp-config \
--restricted \
--allowedTools "mcp__redshift-e2e__query"
```
`--mcp-config` loads the server from a throwaway JSON file instead of any persistent config; `--strict-mcp-config` ignores every other MCP server configured on the machine; `--restricted` drops code-execution tools and ignores project/user settings files. Nothing is ever written to project, user, or local Claude Code settings, so there's no "disconnect" step afterward β the server was never added anywhere durable, and other sessions on the machine are unaffected regardless of how the script exits. This is preferable to `claude mcp add` + `claude mcp remove`, which mutate shared config and would need explicit, failure-prone cleanup.
**Requires:** the `claude` CLI installed and authenticated, and Docker. Not run in CI or as part of `npm test` β it makes a real LLM call and costs API usage (capped at $0.50 via `--max-budget-usd`).
---
## π€ A2A (Experimental)
Two extra, unpublished sibling packages β `a2a-agent/` and `a2a-bridge/` β expose this server to *other agents* over the [A2A protocol](https://a2a-protocol.org), not just to the Claude Code session sitting directly on top of it. Not part of this npm package; doesn't affect anything above.
See **[A2A.md](./A2A.md)** for the architecture, configuration, how to run it, and how to test it (unit tests + a real end-to-end script driving the actual `claude` CLI).
---
## βοΈ Dust.tt Integration
[Dust.tt](https://dust.tt) supports remote MCP servers. Here's how to connect:
### Step 1: Expose Your Server
**Option A: ngrok (Quick testing)**
```bash
# Start server with auth
export DATABASE_URL="redshift://..."
export TRANSPORT_MODE="http"
export STATELESS_MODE="true"
export ENABLE_AUTH="true"
export API_TOKEN="your-secret-token"
npm start
# In another terminal, expose
ngrok http 3000
# You'll get: https://abc123.ngrok.io
```
**Option B: Kubernetes (Production)**
See [Kubernetes Deployment](#%EF%B8%8F-kubernetes-deployment) section below.
### Step 2: Configure in Dust.tt
1. Go to Dust.tt β **Connections** β **Add MCP Server**
2. Fill in:
- **Server Name:** Redshift Data Warehouse
- **URL:** `https://your-ngrok-url.ngrok.io/mcp` or `https://your-domain.com/mcp`
- **Authentication:** Bearer Token
- **Token:** `your-secret-token` (same as `API_TOKEN`)
3. Click **Save**
β
**Success!** Dust.tt agents can now query your Redshift data.
**Troubleshooting:**
- β "404 Not Found" β Use `/mcp` suffix or root `/` path
- β "401 Unauthorized" β Check token matches `API_TOKEN` exactly
- β "OAuth error" β Select "Bearer Token" auth (not "Automatic")
### Step 3: Test in Dust.tt
Ask your Dust.tt agent:
- "What tables are in my Redshift database?"
- "Show me the schema of the users table"
- "How many rows are in the orders table?"
**Learn more:** [Dust.tt MCP Guide](https://blog.dust.tt/give-dust-agents-access-to-your-internal-systems-with-custom-mcp-servers/)
---
## βΈοΈ Kubernetes Deployment
**Production-ready K8s deployment with horizontal scaling:**
**Complete manifest:**
```yaml
apiVersion: v1
kind: Secret
metadata:
name: redshift-mcp-secrets
type: Opaque
stringData:
database-url: "redshift://user:pass@host:5439/db?ssl=true"
api-token: "your-super-secret-token"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redshift-mcp-server
spec:
replicas: 3 # Horizontal scaling with stateless mode
selector:
matchLabels:
app: redshift-mcp-server
template:
metadata:
labels:
app: redshift-mcp-server
spec:
containers:
- name: server
image: your-registry/redshift-mcp:latest
ports:
- containerPort: 3000
env:
- name: TRANSPORT_MODE
value: "http"
- name: STATELESS_MODE
value: "true" # Enable for horizontal scaling
- name: ENABLE_AUTH
value: "true"
- name: API_TOKEN
valueFrom:
secretKeyRef:
name: redshift-mcp-secrets
key: api-token
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: redshift-mcp-secrets
key: database-url
- name: ALLOWED_ORIGINS
value: "https://dust.tt"
- name: REDACT_PII
value: "false"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: redshift-mcp-service
spec:
selector:
app: redshift-mcp-server
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: redshift-mcp-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- mcp.your-company.com
secretName: mcp-tls
rules:
- host: mcp.your-company.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: redshift-mcp-service
port:
number: 80
```
**Key configuration points:**
| Feature | Configuration | Why |
|---------|---------------|-----|
| **Horizontal Scaling** | `STATELESS_MODE=true`, `replicas: 3` | No sticky sessions needed |
| **Security** | `ENABLE_AUTH=true`, token in Secret | Protect your data |
| **Health Checks** | `/health` and `/ready` endpoints | Auto-restart unhealthy pods |
| **TLS** | Ingress with cert-manager | HTTPS required for production |
| **Resources** | Adjust based on query load | Start with 256Mi RAM, 100m CPU |
---
## π οΈ Available Tools
The MCP server exposes these tools to AI assistants:
### 1. `query` - Execute SQL
**Execute read-only SQL queries** with automatic transaction safety.
```json
// Input
{
"sql": "SELECT table_name FROM information_schema.tables LIMIT 10"
}
// Output
[
{"table_name": "users"},
{"table_name": "orders"},
...
]
```
**Features:**
- Automatic `BEGIN TRANSACTION READ ONLY`
- Safe for production use
- Returns results as JSON array
**Example prompts:**
- "Show me all tables in the public schema"
- "What are the top 10 customers by revenue?"
- "Count rows in the orders table"
### 2. `describe_table` - Table Schema
**Get comprehensive table information** including columns, data types, and Redshift-specific attributes.
```json
// Input
{
"schema": "public",
"table": "users"
}
// Output
{
"schema": "public",
"table": "users",
"columns": [
{
"column_name": "id",
"data_type": "integer",
"is_nullable": "NO",
"is_distkey": true,
"is_sortkey": true
},
...
]
}
```
**Includes:**
- Column names and data types
- Nullability
- Distribution keys (DISTKEY)
- Sort keys (SORTKEY)
- Defaults and constraints
**Example prompts:**
- "Describe the structure of the users table"
- "What columns are in the orders table?"
- "Show me the schema for public.payments"
### 3. `find_column` - Search Columns
**Find tables containing columns** matching a search pattern.
```json
// Input
{
"pattern": "email"
}
// Output
[
{
"table_schema": "public",
"table_name": "users",
"column_name": "email",
"data_type": "varchar"
},
{
"table_schema": "public",
"table_name": "contacts",
"column_name": "contact_email",
"data_type": "varchar"
}
]
```
**Use cases:**
- Find all tables with customer IDs
- Locate PII fields across schemas
- Discover relationships between tables
**Example prompts:**
- "Find all columns containing 'customer'"
- "Which tables have an 'updated_at' column?"
- "Search for columns with 'amount' in the name"
### Resources (Contextual Information)
These are auto-discovered and provided to AI assistants:
| Resource | URI Pattern | Description |
|----------|-------------|-------------|
| **Schema Lists** | `redshift://host/schema/{schema}` | All tables in a schema |
| **Table Schemas** | `redshift://host/{schema}/{table}/schema` | Column definitions, keys |
| **Sample Data** | `redshift://host/{schema}/{table}/sample` | 5 sample rows (unredacted by default) |
| **Statistics** | `redshift://host/{schema}/{table}/statistics` | Size, rows, distribution |
**PII Redaction:** Email and phone fields can be redacted in sample data by setting `REDACT_PII=true` (disabled by default).
---
## π§ Troubleshooting
### Common Issues
#### β Connection Fails
**Symptoms:** `ENOTFOUND`, `ECONNREFUSED`, or timeout errors
**Solutions:**
1. **Check DATABASE_URL format**:
```bash
redshift://username:password@cluster.region.redshift.amazonaws.com:5439/database?ssl=true
```
2. **Verify network access:** Security groups, VPC settings, public access
3. **Test with psql:** `psql "$DATABASE_URL"`
#### β Authentication 401 Unauthorized
**Solutions:**
1. Verify token matches: `API_TOKEN="abc123"` β `Authorization: Bearer abc123`
2. Select "Bearer Token" in Dust.tt (not "Automatic")
3. Check request headers in logs
#### β MCP Inspector Won't Connect
**Solutions:**
1. Enable stateless mode: `STATELESS_MODE="true"`
2. Use correct URL: `http://localhost:3000/mcp` or `http://localhost:3000/`
3. Add auth header if enabled: `Authorization: Bearer your-token`
#### β Dust.tt 404 Not Found
**Solutions:**
1. Use full path: `https://your-ngrok-url.ngrok.io/mcp`
2. Check ngrok logs for actual requests
3. Verify auth token is correct
#### β IDE Tools Not Showing
**Solutions:**
1. Use absolute paths in config
2. Verify build: `npm run build && ls -la dist/index.js`
3. Restart IDE after config changes
### Debug Commands
```bash
# Health check
curl http://localhost:3000/health
# Test with auth
curl -H "Authorization: Bearer token" http://localhost:3000/mcp
# Test DB connection
psql "$DATABASE_URL" -c "SELECT 1;"
```
---
## ποΈ Architecture
```
src/
βββ core/
β βββ redshift-tools.ts # Pure DB logic (transport-agnostic)
βββ mcp/
β βββ server.ts # MCP protocol handler
βββ transports/
β βββ stdio.ts # STDIO transport
β βββ streamable-http.ts # HTTP/SSE transport
βββ middleware/
β βββ auth.ts # Bearer token authentication
βββ index.ts # Application entry point
```
**Key principles:**
- π§© **Core logic** is transport-agnostic (reusable)
- π **Transports** are pluggable (STDIO, HTTP, WebSocket)
- π **Middleware** is modular (auth, CORS, logging)
- βοΈ **Config** is environment-driven (12-factor)
**See [ARCHITECTURE.md](./ARCHITECTURE.md) for details.**
---
## π Resources
- [MCP Specification](https://modelcontextprotocol.io)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [Dust.tt MCP Guide](https://blog.dust.tt/give-dust-agents-access-to-your-internal-systems-with-custom-mcp-servers/)
- [Original Implementation](https://github.com/paschmaria/redshift-mcp-server)
---
## π Security
**Built-in protections:**
- π **Read-only transactions** - All queries in `BEGIN TRANSACTION READ ONLY`
- π· **PII redaction** - Optional email/phone redaction in samples
- π **Bearer auth** - Token-based access control
- π **SSL/TLS** - Encrypted database connections
**Best practices:**
1. Use dedicated **read-only Redshift user**
2. Limit **permissions** to necessary schemas/tables
3. Enable **auth for production**: `ENABLE_AUTH=true`
4. Use **strong tokens**: `openssl rand -hex 32`
5. **Rotate credentials** every 90 days
6. Deploy in **private network** when possible
7. **Monitor query logs** for suspicious activity
---
## π License & Credits
**Based on:** [paschmaria/redshift-mcp-server](https://github.com/paschmaria/redshift-mcp-server)
**Enhancements:**
- β
Streamable HTTP + stateless mode
- β
Bearer token authentication
- β
Kubernetes-ready deployment
- β
Root path (`/`) + OAuth discovery
- β
Clean architecture with separation of concerns
**HTTP Transport Inspiration:**
The HTTP/SSE transport implementation took inspiration from:
- [mcp-streamable-http](https://github.com/invariantlabs-ai/mcp-streamable-http)
- [mcp-streamable-http-typescript-server](https://github.com/ferrants/mcp-streamable-http-typescript-server)
- [http-oauth-mcp-server](https://github.com/NapthaAI/http-oauth-mcp-server)
**Stack:** TypeScript 5.3+ | Node.js 16+ | MCP SDK 1.8.0 | Express.js
---
**π Questions? Issues? PRs welcome!**
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues