How to Add Supabase MCP to Claude Code: Complete Setup Guide
Transform your Claude Code workflow with direct database access for lightning-fast development*
## Why MCP + Supabase Changes Everything
Traditional development workflow:
- Write code → Open Supabase dashboard → Run SQL manually → Test → Repeat
With MCP (Model Context Protocol):
- Tell Claude what you want → Claude directly executes database operations → Instant results
**Real Example**: "Create a prospects table with auto-research pipeline" becomes a single Claude conversation instead of 20+ manual steps.
---
## Prerequisites
- Claude Code CLI installed and working
- Supabase project with database access
- Node.js/Next.js project (this guide uses Next.js 14)
---
## Step 1: Get Your Supabase Database Password
1. **Open Supabase Dashboard**
- Go to your project dashboard
- Navigate to **Settings** → **Database**
2. **Copy Database Password**
- Look for "Database password" section
- Click "Reset database password" if needed
- **Save this password** - you'll need it in multiple places
3. **Note Your Connection Details**
```
Project ID: [your-project-id]
Host: aws-0-us-east-2.pooler.supabase.com
Port: 6543
Database: postgres
```
---
## Step 2: Configure Environment Variables
Add these to your `.env.local` file:
```env
# Supabase Standard Connection (for regular operations)
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1…..
# MCP Dangerous Mode (for DDL operations)
SUPABASE_DB_PASSWORD=your-actual-database-password
DATABASE_URL=postgresql://postgres.your-project-id:your-actual-database-password@aws-0-us-east-2.pooler.supabase.com:6543/postgres
```
**Critical**: Replace placeholders with your actual values:
- `your-project-id`: From Supabase dashboard URL
- `your-actual-database-password`: The password from Step 1
---
## Step 3: Create the MCP Server
Create `mcp/server.ts` in your project root:
```typescript
import { supabase } from '../lib/supabase';
import { Client } from 'pg';
export interface MCPFunction {
name: string;
description: string;
parameters: any;
handler: (params: any) => Promise<any>;
}
/**
* MCP Function: Run Dangerous SQL (DDL Operations)
* ⚠️ WARNING: This can execute ANY SQL including schema changes
*/
export async function runDangerousSQL(params: {
sql: string;
confirmDangerous?: boolean;
otp?: string;
}) {
try {
// Safety checks
if (process.env.NODE_ENV === 'production') {
throw new Error('🚫 Dangerous SQL blocked in production');
}
if (!params.confirmDangerous) {
throw new Error('🚫 Must set confirmDangerous: true');
}
// Simple OTP check
const expectedOTP = process.env.DANGEROUS_SQL_OTP || '1234';
if (params.otp !== expectedOTP) {
throw new Error('🚫 Invalid OTP for dangerous SQL');
}
// Direct PostgreSQL connection
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error('Missing DATABASE_URL environment variable');
}
const client = new Client({
connectionString: databaseUrl,
ssl: { rejectUnauthorized: false }
});
try {
await client.connect();
const result = await client.query(params.sql);
return {
success: true,
message: '✅ SQL executed successfully',
command: result.command,
rowsAffected: result.rowCount || 0,
data: result.rows || [],
fields: result.fields?.map(f => ({
name: f.name,
dataTypeID: f.dataTypeID
})) || []
};
} finally {
await client.end();
}
} catch (error: any) {
return {
success: false,
error: error.message
};
}
}
/**
* MCP Function: Get Database State
*/
export async function getDatabaseState() {
try {
const { data: users } = await supabase
.from('users')
.select('email, role')
.order('role');
return {
success: true,
data: {
totalUsers: users?.length || 0,
userRoles: users || []
}
};
} catch (error: any) {
return {
success: false,
error: error.message
};
}
}
// Export all functions
export const mcpFunctions: MCPFunction[] = [
{
name: 'runDangerousSQL',
description: 'Execute raw SQL (dev only)',
parameters: {},
handler: runDangerousSQL
},
{
name: 'getDatabaseState',
description: 'Get database state',
parameters: {},
handler: getDatabaseState
}
];
```
---
## Step 4: Create the MCP API Route
Create `app/api/mcp/route.ts`:
```typescript
import { NextRequest, NextResponse } from 'next/server';
import {
runDangerousSQL,
getDatabaseState
} from '../../../mcp/server';
// Map of available MCP functions
const mcpFunctions = {
runDangerousSQL,
getDatabaseState
};
/**
* MCP Function Call Endpoint
* POST /api/mcp
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { function: functionName, parameters = {} } = body;
if (!functionName) {
return NextResponse.json(
{
success: false,
error: 'Function name is required',
availableFunctions: Object.keys(mcpFunctions)
},
{ status: 400 }
);
}
const mcpFunction = mcpFunctions[functionName as keyof typeof mcpFunctions];
if (!mcpFunction) {
return NextResponse.json(
{
success: false,
error: `Function '${functionName}' not found`,
availableFunctions: Object.keys(mcpFunctions)
},
{ status: 404 }
);
}
console.log(`🔧 MCP Call: ${functionName}`, parameters);
// Execute the MCP function
const result = await mcpFunction(parameters);
return NextResponse.json({
function: functionName,
parameters,
timestamp: new Date().toISOString(),
...result
});
} catch (error: any) {
console.error('❌ MCP Error:', error);
return NextResponse.json(
{
success: false,
error: error.message,
timestamp: new Date().toISOString()
},
{ status: 500 }
);
}
}
/**
* List Available MCP Functions
* GET /api/mcp
*/
export async function GET() {
return NextResponse.json({
success: true,
functions: Object.keys(mcpFunctions),
usage: {
description: 'Call MCP functions by POSTing to /api/mcp',
example: {
function: 'getDatabaseState',
parameters: {}
}
}
});
}
```
---
## Step 5: Install Required Dependencies
```bash
npm install pg @types/pg
```
---
## Step 6: Test Your Setup
### Basic Test (Standard MCP)
```bash
curl -X POST http://localhost:3000/api/mcp \
-H "Content-Type: application/json" \
-d '{"function": "getDatabaseState", "parameters": {}}'
```
**Expected Response:**
```json
{
"function": "getDatabaseState",
"success": true,
"data": {
"totalUsers": 3,
"userRoles": [...]
}
}
```
### Advanced Test (Dangerous Mode)
```bash
curl -X POST http://localhost:3000/api/mcp \
-H "Content-Type: application/json" \
-d '{
"function": "runDangerousSQL",
"parameters": {
"sql": "SELECT current_database(), current_user;",
"confirmDangerous": true,
"otp": "1234"
}
}'
```
**Expected Response:**
```json
{
"function": "runDangerousSQL",
"success": true,
"message": "✅ SQL executed successfully",
"data": [
{
"current_database": "postgres",
"current_user": "postgres"
}
]
}
```
---
## Step 7: Using with Claude Code
Once setup is complete, you can ask Claude to perform database operations directly:
**Example Conversation:**
```
You: "Create a new prospects table with columns for company name, website, and status"
Claude: I'll create the prospects table for you using MCP dangerous mode.
[Claude executes the SQL via MCP and shows results]
Claude: ✅ Prospects table created successfully with the following columns:
- id (UUID, primary key)
- company_name (TEXT, not null)
- website_url (TEXT)
- status (TEXT with constraints)
- created_at (TIMESTAMPTZ)
```
---
## Common Issues & Solutions
### Issue: "SASL authentication error"
**Cause**: Wrong database password or connection string format
**Solution**: Double-check password from Supabase dashboard, ensure URL encoding if special characters
### Issue: "Function not found"
**Cause**: MCP function not properly exported
**Solution**: Verify function is in `mcpFunctions` object in both files
### Issue: "Dangerous SQL blocked"
**Cause**: Missing required parameters
**Solution**: Always include `confirmDangerous: true` and `otp: "1234"`
---
## Security Best Practices
1. **Development Only**: Dangerous mode automatically blocks in production
2. **Environment Variables**: Never commit database passwords to git
3. **OTP Protection**: Change default OTP in production environments
4. **Principle of Least Privilege**: Use standard MCP for regular operations
5. **Audit Trail**: All dangerous SQL operations are logged
---
## Real-World Example: LEAP Engine Setup
With MCP configured, I was able to set up an entire autonomous prospect research pipeline with Claude in one conversation:
1. **Platform Setup**: Created `platform_settings` table and updated user role constraints
2. **Constraint Fixes**: Updated prospect table constraints for new source types
3. **Pipeline Test**: Created a test prospect with auto-research enabled
4. **Result**: Full working system in under 10 minutes vs. hours of manual work
**The conversation flow:**
```
User: "Set up the platform for LEAP Engine autonomous prospect pitch"
Claude: [Analyzes requirements]
[Runs dangerous SQL to create tables]
[Updates constraints]
[Tests prospect creation]
[Confirms research pipeline working]
User: [Gets working system ready for production]
```
---
## Next Steps
1. **Add More MCP Functions**: Create functions for common database operations
2. **Build Custom Workflows**: Let Claude handle complex multi-step database tasks
3. **Integrate with CI/CD**: Use MCP for automated database migrations
4. **Monitor Usage**: Add logging and monitoring for MCP operations
---
## Conclusion
MCP + Supabase transforms Claude Code from a basic coding assistant into a full-stack development partner. Instead of context-switching between tools, you can describe what you want and Claude executes it directly.
**Before**: Manual SQL → Dashboard navigation → Copy/paste → Test → Debug
**After**: "Claude, set up the user authentication system" → Done
This setup has cut my database development time by 80% and eliminated countless manual errors. The autonomous prospect research pipeline that took me weeks to plan was implemented and tested in a single Claude conversation.
---
*Have you set up MCP with Claude Code? Share your experience and any additional tips in the comments!*