Skip to main content
Glama
HenkDz

Self-Hosted Supabase MCP Server

execute_sql

Execute SQL queries directly on your self-hosted Supabase database for data retrieval, updates, and management operations.

Instructions

Executes an arbitrary SQL query against the database, using direct database connection when available or RPC function as fallback.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sqlYesThe SQL query to execute.
read_onlyNoHint for the RPC function whether the query is read-only (best effort).

Implementation Reference

  • The main execute handler for the 'execute_sql' tool. It retrieves the database client from context, logs the query preview, executes the SQL using a fallback mechanism, and processes/validates the response.
    execute: async (input: ExecuteSqlInput, context: ToolContext) => {
        const client = context.selfhostedClient;
    
        console.error(`Executing SQL (readOnly: ${input.read_only}): ${input.sql.substring(0, 100)}...`);
        
        const result = await executeSqlWithFallback(client, input.sql, input.read_only);
        return handleSqlResponse(result, ExecuteSqlOutputSchema);
    },
  • Zod schemas defining the input (sql query and optional read_only flag) and output (array of rows) for the execute_sql tool.
    const ExecuteSqlInputSchema = z.object({
        sql: z.string().describe('The SQL query to execute.'),
        read_only: z.boolean().optional().default(false).describe('Hint for the RPC function whether the query is read-only (best effort).'),
        // Future enhancement: Add option to force direct connection?
        // use_direct_connection: z.boolean().optional().default(false).describe('Attempt to use direct DB connection instead of RPC.'),
    });
    type ExecuteSqlInput = z.infer<typeof ExecuteSqlInputSchema>;
    
    // Output schema - expects an array of results (rows)
    const ExecuteSqlOutputSchema = z.array(z.unknown()).describe('The array of rows returned by the SQL query.');
  • src/index.ts:104-104 (registration)
    Registers the executeSqlTool in the availableTools object, which is used to populate MCP capabilities and handle tool calls in the server.
    [executeSqlTool.name]: executeSqlTool as AppTool,
  • Utility function that implements the fallback logic: prefers direct PostgreSQL connection via pg if DATABASE_URL available, otherwise uses the RPC function via Supabase client.
    export async function executeSqlWithFallback(
        client: SelfhostedSupabaseClient, 
        sql: string, 
        readOnly: boolean = true
    ): Promise<SqlExecutionResult> {
        // Try direct database connection first (bypasses JWT authentication)
        if (client.isPgAvailable()) {
            console.info('Using direct database connection (bypassing JWT)...');
            return await client.executeSqlWithPg(sql);
        }
        
        // Fallback to RPC if direct connection not available
        console.info('Falling back to RPC method...');
        return await client.executeSqlViaRpc(sql, readOnly);
    } 
  • Utility function to process SQL execution results: checks for errors and validates successful row data against the output schema, throwing descriptive errors if issues occur.
    export function handleSqlResponse<T>(result: SqlExecutionResult, schema: z.ZodSchema<T>): T {
        // Check if the result contains an error
        if ('error' in result) {
            throw new Error(`SQL Error (${result.error.code}): ${result.error.message}`);
        }
    
        // Validate the result against the schema
        try {
            return schema.parse(result);
        } catch (validationError) {
            if (validationError instanceof z.ZodError) {
                throw new Error(`Schema validation failed: ${validationError.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
            }
            throw new Error(`Unexpected validation error: ${validationError}`);
        }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but lacks critical behavioral details. It mentions connection methods but doesn't disclose permissions required, potential for data modification, rate limits, error handling, or that 'read_only' is a hint (not enforced). This is inadequate for a tool executing arbitrary SQL.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence with zero waste. It's front-loaded with the core action and includes necessary technical context about connection methods without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of executing arbitrary SQL, no annotations, and no output schema, the description is incomplete. It should warn about security risks, explain the 'read_only' hint's limitations, and describe return formats or error cases to be minimally viable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents both parameters. The description adds no additional meaning about parameters beyond implying SQL execution, which is already clear from the schema. Baseline 3 is appropriate as the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'executes' and the resource 'SQL query against the database', specifying it handles both direct connections and RPC fallbacks. However, it doesn't explicitly differentiate from sibling tools like 'apply_migration' or 'list_tables', which also interact with the database but for specific purposes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites, risks of arbitrary SQL execution, or when to prefer other tools like 'list_tables' for read-only operations or 'apply_migration' for schema changes.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HenkDz/selfhosted-supabase-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server