Skip to main content
Glama
supabase-mcp-server

supabase-mcp-server

supabase-mcp-server

CI

A Model Context Protocol (MCP) server that gives Claude (or any MCP client) read-mostly, row-level-security-aware access to a Supabase Postgres database. Personal open-source project, built with AI coding agents and tested like a QA engineer would want it tested.

The point of the repo is the safety model and the tests around it, not the number of tools.

What it does

Tool

Default

What it does

whoami

on

Role, auth.uid(), exposed schemas, row cap, which opt-in tools are enabled. No credentials.

list_tables

on

Tables/views in the exposed schemas that the role can see, with the RLS flag

describe_table

on

Columns, RLS flag, RLS policy names and commands

select_rows

on

Structured select: columns, filters (eq neq gt gte lt lte like ilike in is_null), order, limit, offset. No raw SQL

count_rows

on

Count with the same filters

run_select_sql

MCP_ALLOW_SQL=true

One SELECT/WITH statement, read-only transaction, timeout, row cap

insert_row

MCP_ALLOW_WRITES=true

Insert one row; RLS insert policies decide

Related MCP server: mcp-data-gateway

Safety model

Every tool call runs in its own transaction that:

  1. switches to the Supabase API role with SET LOCAL ROLE anon | authenticated, so the connection's own privileges are not used for queries;

  2. sets request.jwt.claims / request.jwt.claim.sub the way PostgREST does, so auth.uid() and every RLS policy behave as they would for a supabase-js client signed in as MCP_USER_ID;

  3. is READ ONLY for every tool except insert_row;

  4. has a statement_timeout (MCP_STATEMENT_TIMEOUT_MS, default 5000).

On top of that:

  • Identifiers must be plain names (validated with zod), are checked against the live catalog as seen by the role, and are always quoted. Values are always bound parameters.

  • Only schemas in MCP_SCHEMAS can be named. auth, storage and pg_catalog are not reachable by default.

  • Results are capped at MCP_MAX_ROWS (default 100, max 1000), enforced in the tool's input schema.

  • run_select_sql has a literal/comment-aware guard (single statement, SELECT/WITH/VALUES/TABLE only, no DML/DDL keywords). The guard gives early, clear errors; the read-only transaction and the role are what actually prevent writes.

  • Errors are returned as tool results (isError: true) with the Postgres code and a short hint (e.g. 42501: permission denied (row-level security or grants)), so the model can correct itself. Unknown tools are JSON-RPC errors (-32602), as the spec requires.

  • The server logs only to stderr and never prints the connection string.

What it does not do: verify a user's JWT. You choose the role and user id in the server's environment. Treat it as a local developer tool for your own data, not as a multi-user gateway. Never point it at a connection that uses service_role.

Protocol version

Built on the official TypeScript SDK (@modelcontextprotocol/sdk 1.32.2), which negotiates MCP 2025-11-25. The spec has a newer revision (2026-07-28); the server will move to it when the SDK does. The low-level Server API is used so error handling follows the spec exactly (see above).

Quick start (local Supabase)

Needs Node 22+ (24 recommended), Docker and the Supabase CLI.

git clone https://github.com/mustafaautomation/supabase-mcp-server.git
cd supabase-mcp-server
npm ci && npm run build
supabase db start          # Postgres on 54322, applies supabase/migrations and supabase/seed.sql

The demo schema has projects (public or private, owned by a user) and tasks (visible when their project is), plus an internal_notes table with no grants for API roles. The seed uses two fixed user ids: alice 11111111-1111-4111-8111-111111111111 and bob 22222222-2222-4222-8222-222222222222.

Claude Desktop

Add to claude_desktop_config.json (Settings → Developer → Edit Config), using the absolute path to your clone:

{
  "mcpServers": {
    "supabase-local": {
      "command": "node",
      "args": ["/absolute/path/to/supabase-mcp-server/dist/src/index.js"],
      "env": {
        "DATABASE_URL": "postgresql://postgres:postgres@127.0.0.1:54322/postgres",
        "MCP_DB_ROLE": "authenticated",
        "MCP_USER_ID": "11111111-1111-4111-8111-111111111111"
      }
    }
  }
}

Restart Claude Desktop, then ask e.g. "Which of my projects have open tasks?".

Claude Code

claude mcp add supabase-local \
  --env DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres \
  --env MCP_DB_ROLE=anon \
  -- node "$PWD/dist/src/index.js"
claude mcp list

Add --scope project to write a shareable .mcp.json instead (keep real connection strings out of it).

MCP Inspector

npx @modelcontextprotocol/inspector --cli -e DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres \
  --method tools/list -- node dist/src/index.js

Configuration

Variable

Default

Notes

DATABASE_URL

required

A role that can SET ROLE anon and SET ROLE authenticated (the local postgres user can)

MCP_DB_ROLE

anon

anon or authenticated

MCP_USER_ID

none

uuid returned by auth.uid(); required for authenticated, rejected for anon

MCP_SCHEMAS

public

Comma-separated allowlist

MCP_MAX_ROWS

100

1–1000

MCP_STATEMENT_TIMEOUT_MS

5000

100–60000

MCP_ALLOW_SQL

false

Registers run_select_sql

MCP_ALLOW_WRITES

false

Registers insert_row

Invalid configuration stops the server at startup with exit code 2 and a message naming the variable.

Tests

Layer

Where

What it covers

Unit

tests/unit

Config parsing, SQL builder (parameters, quoting), the SQL guard (comments, literals, dollar quotes, multi-statement), error mapping

Integration

tests/integration

Through a real MCP client (in-memory transport) against Postgres: anon vs alice vs bob visibility, tasks inheriting project RLS, invisible ungranted table, unknown columns and schemas, injection strings as data, row cap, read-only transaction, statement timeout, RLS blocking spoofed inserts, opt-in tools hidden by default, protocol errors

stdio e2e

tests/e2e

The built server as a child process, the way Claude starts it: handshake, tool list, a real query, bad arguments, unknown tool, stderr-only logging, exit code 2 on bad config

Inspector

CI

@modelcontextprotocol/inspector --cli lists 5 tools and runs count_rows

Conformance

CI

mcp-conformance-tests (pinned) must pass all MUST checks

I checked that the RLS tests catch a real regression: with the SET LOCAL ROLE line removed, the visibility and insert-policy tests fail.

npm test                                   # unit, no database
supabase db start
npm run build && npm run test:integration && npm run test:e2e
DATABASE_URL=postgresql://... npm run test:integration   # any Postgres 15+; a shim adds roles and auth.uid()

CI

.github/workflows/ci.yml; every job must pass:

  • checks: ESLint, Prettier, tsc, unit tests, build

  • supabase: Supabase CLI 2.122 supabase db start, integration and stdio tests, Inspector CLI smoke test, conformance run

  • plain-postgres: the same integration and stdio tests against a postgres:17 service with the shim in tests/sql

  • secrets: gitleaks over the full git history

License

MIT

Available Tools

5 tools
count_rowsCount rowsA
Read-only

Counts rows visible to the current role, with the same structured filters as select_rows.

Inputs: table (required), schema, filters.

describe_tableDescribe tableC
Read-only

Columns (name, type, nullability, default), the RLS flag and the names and commands of RLS policies for one table.

Inputs: table (required), schema.

list_tablesList tablesA
Read-only

Lists tables and views in the exposed schemas that the current role can see, with whether row-level security is enabled.

Inputs: schema.

select_rowsSelect rowsA
Read-only

Reads rows from one table with structured filters (eq, neq, gt, gte, lt, lte, like, ilike, in, is_null), ordering and a limit of at most 100. Rows are filtered by RLS for role anon. No raw SQL.

Inputs: table (required), limit, offset, schema, +3 more.

whoamiConnection contextA
Read-only

Shows which Supabase role and user id the server queries as, which schemas are exposed, the row cap and whether SQL and writes are enabled. Never returns credentials.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 5 tools

    Disambiguation5/5

    Each tool has a distinct scope: list_tables enumerates objects, describe_table returns schema details for one table, select_rows fetches rows, count_rows aggregates, and whoami reports session/role context. The only near-neighbours, select_rows and count_rows, are clearly separated by 'read rows' vs 'count rows' and both share the same filter vocabulary.

    Naming Consistency4/5

    Four of five tools follow a clean verb_noun snake_case pattern (list_tables, describe_table, select_rows, count_rows). whoami deviates from that convention, though it is a widely understood idiomatic name rather than an arbitrary break.

    Tool Count4/5

    Five tools is a tight, well-scoped set for read-only introspection and querying of a Postgres/Supabase schema, with no redundant entries. It sits on the lean side, especially since whoami advertises SQL and write capabilities that no tool here exposes.

    Completeness3/5

    The read path is coherent (list → describe → select/count) and RLS awareness is covered, but describe_table omits foreign keys, indexes and constraints, and there is no way to list schemas or run arbitrary SQL despite whoami reporting those flags. Write operations are also absent, so anything beyond inspection is a dead end.

    Maintenance

    ActivityMaintained
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI assistants to safely interact with PostgreSQL databases through read-only operations, providing schema discovery, table inspection, and query execution capabilities with structured context awareness.
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to query a PostgreSQL database through a small set of controlled, read-only tools for schema inspection, row lookup, and aggregate statistics.
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables read-only, SELECT-only querying of any Postgres database through MCP-compatible clients like Claude, with schema introspection and guarded SQL execution.
      MIT
    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables AI assistants to safely inspect and query PostgreSQL databases with read-only enforcement, row/timeout limits, and audit logging, providing tools for schema/table listing, read-only queries, query explanations, and table statistics.
      -