Labsco
HenkDz logo

Self-Hosted Supabase MCP Server

β˜… 139

from HenkDz

Interact with self-hosted Supabase instances for database introspection, management, and interaction.

πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedFreeAdvanced setup

Self-Hosted Supabase MCP Server

License: MIT smithery badge

Overview

This project provides a Model Context Protocol (MCP) server designed specifically for interacting with self-hosted Supabase instances. It bridges the gap between MCP clients (like IDE extensions) and your local or privately hosted Supabase projects, enabling database introspection, management, and interaction directly from your development environment.

This server was built from scratch, drawing lessons from adapting the official Supabase cloud MCP server, to provide a minimal, focused implementation tailored for the self-hosted use case.

Purpose

The primary goal of this server is to enable developers using self-hosted Supabase installations to leverage MCP-based tools for tasks such as:

  • Querying database schemas and data.
  • Managing database migrations.
  • Inspecting database statistics and connections.
  • Managing authentication users.
  • Interacting with Supabase Storage.
  • Generating type definitions.

It avoids the complexities of the official cloud server related to multi-project management and cloud-specific APIs, offering a streamlined experience for single-project, self-hosted environments.

Features (Implemented Tools)

Tools are categorized by privilege level:

  • Regular tools are accessible by any authenticated Supabase JWT (authenticated or service_role role).
  • Privileged tools require a service_role JWT (HTTP mode) or direct database/service-key access (stdio mode).

Schema & Migrations

ToolDescriptionPrivilege
list_tablesLists tables in the database schemasRegular
list_extensionsLists installed PostgreSQL extensionsRegular
list_available_extensionsLists all available (installable) extensionsRegular
list_migrationsLists applied migrations from supabase_migrations.schema_migrationsRegular
apply_migrationApplies a SQL migration and records it in supabase_migrations.schema_migrationsPrivileged
list_table_columnsLists columns for a specific tableRegular
list_indexesLists indexes for a specific tableRegular
list_constraintsLists constraints for a specific tableRegular
list_foreign_keysLists foreign keys for a specific tableRegular
list_triggersLists triggers for a specific tableRegular
list_database_functionsLists user-defined database functionsRegular
get_function_definitionGets the source definition of a functionRegular
get_trigger_definitionGets the source definition of a triggerRegular

Database Operations & Stats

ToolDescriptionPrivilege
execute_sqlExecutes an arbitrary SQL queryPrivileged
explain_queryRuns EXPLAIN ANALYZE on a queryPrivileged
get_database_connectionsShows active connections (pg_stat_activity)Regular
get_database_statsRetrieves database statistics (pg_stat_*)Regular
get_index_statsShows index usage statisticsRegular
get_vector_index_statsShows pgvector index statisticsRegular

Security & RLS

ToolDescriptionPrivilege
list_rls_policiesLists Row-Level Security policies for a tableRegular
get_rls_statusShows RLS enabled/disabled status for tablesRegular
get_advisorsRetrieves security and performance advisory noticesRegular

Project Configuration

ToolDescriptionPrivilege
get_project_urlReturns the configured Supabase URLRegular
verify_jwt_secretChecks if the JWT secret is configuredRegular

Development & Extension Tools

ToolDescriptionPrivilege
generate_typescript_typesGenerates TypeScript types from the database schemaRegular
rebuild_hooksRestarts the pg_net worker (if used)Privileged
get_logsRetrieves recent log entries (analytics stack or CSV fallback)Regular

Auth User Management

ToolDescriptionPrivilege
list_auth_usersLists users from auth.usersRegular
get_auth_userRetrieves details for a specific userRegular
create_auth_userCreates a new user in auth.users (password bcrypt-hashed via pgcrypto)Privileged
update_auth_userUpdates user details (password bcrypt-hashed if changed)Privileged
delete_auth_userDeletes a user from auth.usersPrivileged

Storage

ToolDescriptionPrivilege
list_storage_bucketsLists all storage bucketsRegular
list_storage_objectsLists objects within a specific bucketRegular
get_storage_configRetrieves storage bucket configurationRegular
update_storage_configUpdates storage bucket settingsPrivileged

Realtime Inspection

ToolDescriptionPrivilege
list_realtime_publicationsLists PostgreSQL publications (e.g. supabase_realtime)Regular

Extension-Specific Tools

ToolDescriptionPrivilege
list_cron_jobsLists scheduled jobs (requires pg_cron extension)Regular
get_cron_job_historyShows recent execution history for a cron jobRegular
list_vector_indexesLists pgvector indexes (requires pgvector extension)Regular

Edge Functions

ToolDescriptionPrivilege
list_edge_functionsLists deployed Edge FunctionsRegular
get_edge_function_detailsGets details and metadata for an Edge FunctionRegular
list_edge_function_logsRetrieves recent logs for an Edge FunctionRegular

About supabase_migrations.schema_migrations

The list_migrations and apply_migration tools rely on the supabase_migrations.schema_migrations table. This table is created and managed by the Supabase CLI β€” it is not part of the MCP server itself.

How the table is created:

The table is automatically created when you initialise or run migrations using the Supabase CLI:

Copy & paste β€” that's it
supabase db push        # pushes local migrations to a remote database
supabase migration up   # applies pending local migration files

If you have never run the Supabase CLI against your database, the table will not exist and list_migrations will return an error. You can create it manually with:

Copy & paste β€” that's it
CREATE SCHEMA IF NOT EXISTS supabase_migrations;
CREATE TABLE IF NOT EXISTS supabase_migrations.schema_migrations (
    version text NOT NULL PRIMARY KEY,
    name    text NOT NULL DEFAULT '',
    inserted_at timestamptz NOT NULL DEFAULT now()
);

Schema difference vs. official Supabase:

The Supabase cloud platform tracks additional columns (e.g. statements, dirty). This MCP server uses the minimal schema (version + name + inserted_at) that is compatible with the Supabase CLI's local-development workflow. If your existing table has extra columns they are simply ignored.

Security

HTTP transport (recommended for remote access)

When running with --transport http, the server enforces:

  • JWT authentication on all /mcp endpoints using your SUPABASE_AUTH_JWT_SECRET.
  • Privilege-based access control (RBAC) β€” the role claim in the JWT determines which tools are accessible:
    • service_role: Full access (all tools including privileged ones).
    • authenticated: Regular tools only.
    • anon: No tool access.
  • Rate limiting β€” configurable request rate limit per IP address.
  • CORS β€” configurable allow-list of origins (defaults to localhost only).
  • Security headers β€” X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, etc.
  • Request timeouts β€” configurable timeout to prevent resource exhaustion.

Stdio transport (local development)

Stdio mode has no authentication β€” all tools (including privileged ones) are accessible. It is intended for trusted local clients only (e.g., an IDE extension running on your local machine). A warning is printed on startup when this mode is used.

Password handling for auth user tools

create_auth_user and update_auth_user accept a plain-text password from the MCP client, then immediately hash it with bcrypt (via PostgreSQL's pgcrypto extension: crypt($password, gen_salt('bf'))) before storing it in auth.users. The plain-text password is never stored. Passwords are passed as query parameters (not string-interpolated into SQL), preventing SQL injection.

Note: The password travels over the MCP transport in plain text between the MCP client and server. This is inherent to the MCP protocol interface and unavoidable at this layer. Use the HTTP transport with TLS termination (e.g., behind Kong/nginx) for network protection.

SQL execution security

All database operations in the MCP server use parameterized queries ($1, $2, ...) to prevent SQL injection. The execute_sql tool is an intentional exception β€” it executes arbitrary SQL by design (it is the tool's purpose). This tool is restricted to service_role privilege level to limit exposure.

Docker Integration with Self-Hosted Supabase

This MCP server can be integrated directly into a self-hosted Supabase Docker Compose stack, making it available alongside other Supabase services via the Kong API gateway.

Architecture Overview

When integrated with Docker:

  • The MCP server runs in HTTP transport mode (not stdio)
  • It's exposed through Kong at /mcp/v1/*
  • JWT authentication is handled by the MCP server itself
  • The server has direct access to the database and all Supabase keys

Setup Steps

1. Add the MCP Server as a Git Submodule

From your Supabase Docker directory:

Copy & paste β€” that's it
git submodule add https://github.com/HenkDz/selfhosted-supabase-mcp.git selfhosted-supabase-mcp

2. Create the Dockerfile

Create volumes/mcp/Dockerfile:

Copy & paste β€” that's it
# Dockerfile for selfhosted-supabase-mcp HTTP mode
# Multi-stage build using Bun runtime for self-hosted Supabase

FROM oven/bun:1.1-alpine AS builder

WORKDIR /app

# Copy package files from submodule
COPY selfhosted-supabase-mcp/package.json selfhosted-supabase-mcp/bun.lock* ./

# Install dependencies
RUN bun install --frozen-lockfile || bun install

# Copy source code
COPY selfhosted-supabase-mcp/src ./src
COPY selfhosted-supabase-mcp/tsconfig.json ./

# Build the application
RUN bun build src/index.ts --outdir dist --target bun

# Production stage
FROM oven/bun:1.1-alpine AS runner

WORKDIR /app

# Create non-root user for security
RUN addgroup --system --gid 1001 mcp && \
    adduser --system --uid 1001 --ingroup mcp mcp

# Copy built application from builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# Set ownership
RUN chown -R mcp:mcp /app

USER mcp

# Default environment variables
ENV NODE_ENV=production

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3100/health || exit 1

# Expose HTTP port
EXPOSE 3100

# Start the MCP server in HTTP mode
CMD ["bun", "run", "dist/index.js"]

3. Add the MCP Service to docker-compose.yml

Add this service definition to your docker-compose.yml:

Copy & paste β€” that's it

## MCP Server - Model Context Protocol for AI integrations



## DISABLED BY DEFAULT - Add 'mcp' to COMPOSE_PROFILES to enable

mcp:
  container_name: ${COMPOSE_PROJECT_NAME:-supabase}-mcp
  profiles:
    - mcp
  build:
    context: .
    dockerfile: ./volumes/mcp/Dockerfile
  restart: unless-stopped
  healthcheck:
    test:
      [
        "CMD",
        "wget",
        "--no-verbose",
        "--tries=1",
        "--spider",
        "http://localhost:3100/health"
      ]
    timeout: 5s
    interval: 10s
    retries: 3
  depends_on:
    db:
      condition: service_healthy
    rest:
      condition: service_started
  environment:
    SUPABASE_URL: http://kong:8000
    SUPABASE_ANON_KEY: ${ANON_KEY}
    SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
    SUPABASE_AUTH_JWT_SECRET: ${JWT_SECRET}
    DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
  command:
    [
      "bun",
      "run",
      "dist/index.js",
      "--transport", "http",
      "--port", "3100",
      "--host", "0.0.0.0",
      "--url", "http://kong:8000",
      "--anon-key", "${ANON_KEY}",
      "--service-key", "${SERVICE_ROLE_KEY}",
      "--jwt-secret", "${JWT_SECRET}",
      "--db-url", "postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
    ]

4. Add Kong API Gateway Routes

Add the MCP routes to volumes/api/kong.yml in the services section:

Copy & paste β€” that's it

## MCP Server routes - Model Context Protocol for AI integrations



## Authentication is handled by the MCP server itself (JWT validation)

- name: mcp-v1
  _comment: 'MCP Server: /mcp/v1/* -> http://mcp:3100/*'
  url: http://mcp:3100/
  routes:
    - name: mcp-v1-all
      strip_path: true
      paths:
        - /mcp/v1/
  plugins:
    - name: cors
      config:
        origins:
          - "$SITE_URL_PATTERN"
          - "http://localhost:3000"
          - "http://127.0.0.1:3000"
        methods:
          - GET
          - POST
          - DELETE
          - OPTIONS
        headers:
          - Accept
          - Authorization
          - Content-Type
          - X-Client-Info
          - apikey
          - Mcp-Session-Id
        exposed_headers:
          - Mcp-Session-Id
        credentials: true
        max_age: 3600

5. Enable the MCP Service

The MCP service uses Docker Compose profiles, so it's disabled by default. To enable it:

Option A: Set in .env file:

Copy & paste β€” that's it
COMPOSE_PROFILES=mcp

Option B: Enable at runtime:

Copy & paste β€” that's it
docker compose --profile mcp up -d

Accessing the MCP Server

Once running, the MCP server is available at:

  • Internal (from other containers): http://mcp:3100
  • External (via Kong): http://localhost:8000/mcp/v1/

Authentication

When running in HTTP mode, the MCP server validates JWTs using the configured JWT_SECRET. Clients must include a valid Supabase JWT in the Authorization header:

Copy & paste β€” that's it
Authorization: Bearer <supabase-jwt>

The JWT's role claim determines access:

  • service_role: Full access to all tools (regular + privileged)
  • authenticated: Access to regular tools only
  • anon: No tool access

Health Check

The MCP server exposes a health endpoint:

Copy & paste β€” that's it
curl http://localhost:8000/mcp/v1/health

Security Considerations

When deploying via Docker:

  1. The MCP server runs as a non-root user (mcp:mcp)
  2. JWT authentication is enforced for all tool calls
  3. Privileged tools (like execute_sql) require service_role JWT
  4. CORS is configured via Kong - adjust origins for your deployment

Development

  • Language: TypeScript
  • Build: bun build (via bun run build)
  • Runtime: Bun v1.1+
  • Test runner: bun test
  • Dependencies: Managed via bun (bun.lock)
  • Core Libraries: @supabase/supabase-js, pg (node-postgres), zod (validation), commander (CLI args), @modelcontextprotocol/sdk (MCP server framework), express, jsonwebtoken.

License

This project is licensed under the MIT License. See the LICENSE file for details.