Skip to main content
Glama
Sentry01

Supabase MCP Server

by Sentry01
README.md
# Supabase MCP Server Implementation Plan

This document outlines the plan for creating a Model Context Protocol (MCP) server that connects to Supabase, allowing AI assistants like GitHub Copilot to interact with your Supabase database.

## Table of Contents
- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Implementation Steps](#implementation-steps)
- [Server Architecture](#server-architecture)
- [Configuration](#configuration)
- [Security Considerations](#security-considerations)
- [Installation Guide](#installation-guide)
- [Usage Examples](#usage-examples)
- [Troubleshooting](#troubleshooting)

## Overview

The Supabase MCP server will act as a bridge between AI assistants (like GitHub Copilot) and your Supabase database. This allows the AI to:

1. Understand your database schema
2. Know about tables and relationships
3. Assist with query writing
4. Provide context-aware suggestions related to your data model

## Prerequisites

- Node.js 18+ installed
- npm or yarn package manager
- Supabase project with admin API key
- VS Code with Copilot/MCP support
- Git

## Implementation Steps

### 1. Create Server Package

```bash
mkdir mcp-server-supabase
cd mcp-server-supabase
npm init -y
```

### 2. Install Dependencies

```bash
npm install @supabase/supabase-js @modelcontextprotocol/server dotenv
```

### 3. Basic Server Structure

Create these files:
- `src/index.js` - Main entry point
- `src/supabase-client.js` - Supabase connection handling
- `src/schema-provider.js` - Database schema extraction 
- `src/query-handler.js` - Safe query execution
- `.env.example` - Environment variable template
- `config.js` - Configuration management

### 4. Server Implementation Details

#### src/index.js

This file will initialize the MCP server and connect the components:

```javascript
const { MCPServer } = require('@modelcontextprotocol/server');
const { getSupabaseClient } = require('./supabase-client');
const { SchemaProvider } = require('./schema-provider');
const { QueryHandler } = require('./query-handler');
const config = require('./config');

async function main() {
  try {
    // Initialize Supabase client
    const supabaseClient = getSupabaseClient(config.supabase.url, config.supabase.key);
    
    // Create providers
    const schemaProvider = new SchemaProvider(supabaseClient);
    const queryHandler = new QueryHandler(supabaseClient, config.security.allowedQueries);
    
    // Initialize MCP server
    const server = new MCPServer({
      name: 'mcp-server-supabase',
      version: '1.0.0',
    });
    
    // Register handlers
    server.registerHandler('getSchema', async () => {
      return await schemaProvider.getFullSchema();
    });
    
    server.registerHandler('getTableInfo', async (params) => {
      return await schemaProvider.getTableInfo(params.tableName);
    });
    
    server.registerHandler('executeQuery', async (params) => {
      return await queryHandler.execute(params.query, params.params);
    });
    
    // Start the server
    await server.start();
    console.log('MCP Supabase server is running');
  } catch (error) {
    console.error('Failed to start MCP server:', error);
    process.exit(1);
  }
}

main();
```

#### src/supabase-client.js

```javascript
const { createClient } = require('@supabase/supabase-js');

function getSupabaseClient(url, apiKey) {
  if (!url || !apiKey) {
    throw new Error('Supabase URL and API key must be provided');
  }
  
  return createClient(url, apiKey, {
    auth: { persistSession: false },
  });
}

module.exports = { getSupabaseClient };
```

#### src/schema-provider.js

```javascript
class SchemaProvider {
  constructor(supabaseClient) {
    this.supabase = supabaseClient;
  }
  
  async getFullSchema() {
    // Query Supabase for full database schema
    const { data, error } = await this.supabase
      .rpc('get_schema_information', {});
    
    if (error) throw new Error(`Failed to get schema: ${error.message}`);
    
    return data;
  }
  
  async getTableInfo(tableName) {
    // Get detailed information about a specific table
    const { data, error } = await this.supabase
      .rpc('get_table_information', { table_name: tableName });
    
    if (error) throw new Error(`Failed to get table info: ${error.message}`);
    
    return data;
  }
}

module.exports = { SchemaProvider };
```

#### src/query-handler.js

```javascript
class QueryHandler {
  constructor(supabaseClient, allowedQueryTypes = ['SELECT']) {
    this.supabase = supabaseClient;
    this.allowedQueryTypes = allowedQueryTypes; 
  }
  
  validateQuery(queryString) {
    // Basic SQL injection prevention and query type validation
    const normalizedQuery = queryString.trim().toUpperCase();
    
    // Check if the query starts with allowed query types
    const isAllowed = this.allowedQueryTypes.some(
      type => normalizedQuery.startsWith(type)
    );
    
    if (!isAllowed) {
      throw new Error(`Query type not allowed. Allowed types: ${this.allowedQueryTypes.join(', ')}`);
    }
    
    return true;
  }
  
  async execute(queryString, params = {}) {
    // Validate query before execution
    this.validateQuery(queryString);
    
    // Execute the query through Supabase
    const { data, error } = await this.supabase
      .rpc('execute_query', { query_string: queryString, query_params: params });
    
    if (error) throw new Error(`Query execution failed: ${error.message}`);
    
    return data;
  }
}

module.exports = { QueryHandler };
```

#### config.js

```javascript
require('dotenv').config();

module.exports = {
  supabase: {
    url: process.env.SUPABASE_URL,
    key: process.env.SUPABASE_SERVICE_KEY,
  },
  server: {
    port: process.env.PORT || 3000,
  },
  security: {
    allowedQueries: process.env.ALLOWED_QUERY_TYPES 
      ? process.env.ALLOWED_QUERY_TYPES.split(',') 
      : ['SELECT'],
  }
};
```

#### .env.example

```
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_SERVICE_KEY=your-service-key
PORT=3000
ALLOWED_QUERY_TYPES=SELECT
```

### 5. Supabase Database Functions

You'll need to create these stored procedures in Supabase:

1. `get_schema_information()` - Returns database schema
2. `get_table_information(table_name TEXT)` - Returns info about specific table
3. `execute_query(query_string TEXT, query_params JSONB)` - Safely executes queries

## Server Architecture

```
┌─────────────────────┐      ┌───────────────────┐
│                     │      │                   │
│  VS Code + Copilot  │◄────►│   MCP Protocol    │
│                     │      │                   │
└─────────────────────┘      └─────────┬─────────┘
                                      │
                                      ▼
                            ┌─────────────────────┐
                            │                     │
                            │ Supabase MCP Server │
                            │                     │
                            └─────────┬───────────┘
                                      │
                                      ▼
                            ┌─────────────────────┐
                            │                     │
                            │  Supabase Database  │
                            │                     │
                            └─────────────────────┘
```

## Configuration

Add the Supabase MCP server to your VS Code `settings.json`:

```json
"mcp": {
  "inputs": [],
  "servers": {
    // ...existing servers...
    "mcp-server-supabase": {
      "command": "node",
      "args": [
        "/path/to/mcp-server-supabase/src/index.js"
      ],
      "env": {
        "SUPABASE_URL": "https://your-project-ref.supabase.co",
        "SUPABASE_SERVICE_KEY": "your-service-key",
        "ALLOWED_QUERY_TYPES": "SELECT"
      }
    }
  }
}
```

## Security Considerations

1. **API Key Management**:
   - Use a scoped API key with minimum required permissions
   - Store API keys securely, never commit to version control
   - Consider using a key rotation strategy

2. **Query Restrictions**:
   - Default to SELECT-only for safety
   - Consider implementing a query allowlist approach
   - Add rate limiting to prevent abuse

3. **Data Protection**:
   - Avoid exposing PII or sensitive data
   - Implement row-level security in Supabase
   - Consider adding data masking for sensitive fields

## Installation Guide

### Local Development

1. Clone the repository
   ```bash
   git clone https://github.com/yourusername/mcp-server-supabase.git
   cd mcp-server-supabase
   ```

2. Install dependencies
   ```bash
   npm install
   ```

3. Create `.env` file from example
   ```bash
   cp .env.example .env
   ```

4. Edit `.env` with your Supabase credentials

5. Start the server
   ```bash
   node src/index.js
   ```

### VS Code Integration

1. Update your VS Code `settings.json` with the server configuration
2. Restart VS Code
3. Verify the server is running in the VS Code MCP panel

## Usage Examples

Once integrated, you can use the Supabase MCP server in various ways:

1. **Schema exploration:**
   ```
   What tables do I have in my Supabase database?
   ```

2. **Table information:**
   ```
   What columns are in the users table?
   ```

3. **Query assistance:**
   ```
   Help me write a query to get all users who signed up in the last 7 days
   ```

## Troubleshooting

**Server won't start**
- Check your Node.js version (should be 18+)
- Verify your Supabase credentials
- Check for error logs in the terminal

**Schema not loading**
- Verify your Supabase service key has necessary permissions
- Check that the database functions are properly created

**VS Code can't connect**
- Check that the server path in settings.json is correct
- Restart VS Code after configuration changes
- Verify the server process is running