- Replace legacy directory structure (api/, db/, functions/, logs/, pooler/) with single docker-compose.yml based self-hosted setup - Add selfhosted-supabase-mcp TypeScript MCP server for database management - Add .dockerignore for Docker build context - Update .gitignore to exclude .env files, volumes/, backups, logs
7.3 KiB
7.3 KiB
Notes for Minimal Selfhosted Supabase MCP Server
This document summarizes the findings and decisions made while attempting to adapt the official Supabase cloud MCP server for self-hosted use. The goal is to build a new, minimal server from scratch using these notes.
Core Requirements
- Target: Self-hosted Supabase instances.
- Scope: Single project environment.
- Runtime: Bun (v1.3+) - migrated from Node.js for faster startup and built-in TypeScript support.
- Authentication: Supabase URL and Anon Key required. Service Role Key optional (but recommended for certain operations like auto-creating helper functions).
- Configuration: Server should accept URL/Keys via CLI arguments (e.g., using
commander) or environment variables (SUPABASE_URL,SUPABASE_ANON_KEY,SUPABASE_SERVICE_ROLE_KEY). Also needsDATABASE_URLfor direct DB fallback/transactions.
Client Implementation (SelfhostedSupabaseClient)
- Primary Connection: Use
@supabase/supabase-jsclient initialized with user-provided URL and Anon Key. - Core SQL Execution:
- Prefer using a PostgreSQL RPC function (
public.execute_sql) called via the Supabase JS client (supabase.rpc(...)). This leverages the existing connection pool and permissions. - The client should check if this function exists on initialization. If not found (error code
42883), and if theserviceRoleKeyis available, attempt to create the function using a temporary service role client or the direct DB connection. - RPC Function SQL:
-- SQL to create the helper function CREATE OR REPLACE FUNCTION public.execute_sql(query text, read_only boolean DEFAULT false) RETURNS jsonb -- Using jsonb is generally preferred over json LANGUAGE plpgsql AS $$ DECLARE result jsonb; BEGIN -- Note: SET TRANSACTION READ ONLY might not behave as expected within a function -- depending on the outer transaction state. Handle read-only logic outside if needed. -- Execute the dynamic query and aggregate results into a JSONB array EXECUTE 'SELECT COALESCE(jsonb_agg(t), ''[]''::jsonb) FROM (' || query || ') t' INTO result; RETURN result; EXCEPTION WHEN others THEN -- Rethrow the error with context RAISE EXCEPTION 'Error executing SQL: %', SQLERRM; END; $$; -- Grant execution permission (run using service key or manually) GRANT EXECUTE ON FUNCTION public.execute_sql(text, boolean) TO authenticated; -- Optionally grant to anon if needed: GRANT EXECUTE ON FUNCTION public.execute_sql(text, boolean) TO anon;
- Prefer using a PostgreSQL RPC function (
- Fallback/Transactional SQL Execution:
- Implement a secondary method (
executeSqlWithPg) using thepglibrary (Node-postgres). - This method requires the
DATABASE_URLenvironment variable to be set for the direct connection string. - Initialize the
pg.Poollazily on the first call to this method. - Use this method as a fallback if the RPC call fails after initialization, or specifically for operations requiring transaction control (like
apply_migration).
- Implement a secondary method (
- Client Initialization: The factory function (
createSelfhostedSupabaseClient) should beasyncand perform the RPC check/create logic during anawait client.initialize()step before returning the client instance. - Type Safety: Use specific types (
unknown,Record<string, unknown>) instead ofany. Leverage TypeScript type inference and define types for options and responses.
Supported Tools
Based on analysis, the following tools are relevant for a self-hosted context:
- Database Operations:
list_tables(Usespg-metalogic)list_extensions(Usespg-metalogic)list_migrations(Queriessupabase_migrations.schema_migrations)apply_migration(Executes DDL + inserts intosupabase_migrations.schema_migrations; ideally usesexecuteSqlWithPgfor transaction)execute_sql(Primary interface toSelfhostedSupabaseClient.executeSql)
- Debugging:
get_logs(Needs careful implementation; direct DB query ofpg_logmight be feasible but depends on setup. May need to be removed or simplified).get_database_connections(Queriespg_stat_activity)get_database_stats(Queriespg_stat_*views)
- Development & Configuration:
get_project_url(Returns configured URL)generate_typescript_types(Relies on DB introspection, potentially wrapsupabase gen typesor usepg-meta)rebuild_hooks(Interacts withpg_netif database webhooks are used)verify_jwt_secret(Useful for Auth debugging)(Removed - security risk: exposes sensitive keys via MCP)get_anon_key(Removed - security risk: exposes sensitive keys via MCP)get_service_key
- Edge Functions (If Enabled):
list_edge_functionsdeploy_edge_function
Removed Tools (Cloud-Specific)
The following tools from the original cloud server are not applicable and should not be implemented:
- Project Management (
list_projects,create_project, etc.) - Branching (
create_branch,list_branches, etc.) - Cost Confirmation (
get_cost,confirm_cost)
Server Entry Point (selfhosted-stdio.ts)
- Use
commanderfor parsing CLI arguments (--url,--anon-key, etc.) and reading environment variables as fallbacks. - Implement an
async main()function. - Call the
async createSelfhostedSupabaseClientfactory. - Create tool instances by passing the initialized
selfhostedClientto tool generator functions (e.g.,getDatabaseOperationTools({ selfhostedClient, readOnly })). - Initialize the MCP SDK (
@modelcontextprotocol/sdk) withstdio: trueand the combined dictionary of tool instances. - Include robust error handling for client initialization and server startup.
Dependencies
- Runtime: Bun v1.3+ (replaces Node.js)
- Core:
@supabase/supabase-js,pg,zod(v4.x),commander,@modelcontextprotocol/sdk. - Dev:
@types/bun,@types/node,@types/pg,typescript
Zod v4 Compatibility Notes
Updated for Zod v4 breaking changes:
z.ZodError.errors→z.ZodError.issuesz.record(z.unknown())→z.record(z.string(), z.unknown())z.string().optional()→z.optional(z.string())for better type inferencez.coerceAPI changes handled withz.union().transform()
Useful Logic to Re-use
- SQL generation logic from
packages/pg-meta(e.g.,listTablesSql,listExtensionsSql). - The
injectableToolutility fromtools/util.tsfor structuring tool definitions with Zod schemas.
Reason for Restart
Adapting the official cloud MCP server proved overly complex due to:
- Deep integration with the multi-project/Management API paradigm.
- Need for extensive refactoring of options and logic paths.
- Inherited build complexities and dependencies not strictly necessary for a minimal self-hosted server.
Building from scratch allows for a cleaner, more focused implementation tailored specifically to the self-hosted use case.