Skip to main content
Glama
HenkDz

Self-Hosted Supabase MCP Server

update_auth_user

Modify authentication user data in self-hosted Supabase, including email, password, roles, and metadata, using service_role credentials for administrative updates.

Instructions

Updates fields for a user in auth.users. WARNING: Password handling is insecure. Requires service_role key and direct DB connection.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
user_idYesThe UUID of the user to update.
emailNoNew email address.
passwordNoNew plain text password (min 6 chars). WARNING: Insecure.
roleNoNew role.
user_metadataNoNew user metadata (will overwrite existing).
app_metadataNoNew app metadata (will overwrite existing).

Implementation Reference

  • The main handler function that executes the tool. It dynamically constructs an SQL UPDATE query for the auth.users table based on provided input fields (email, password, role, metadata), handles password hashing with pgcrypto, executes the query in a transaction, validates output with Zod, and manages errors like unique violations or missing pgcrypto.
    execute: async (input: UpdateAuthUserInput, context: ToolContext): Promise<UpdateAuthUserOutput> => { // Use UpdateAuthUserOutput
        const client = context.selfhostedClient;
        const { user_id, email, password, role, app_metadata, user_metadata } = input;
    
        if (!client.isPgAvailable()) {
            context.log('Direct database connection (DATABASE_URL) is required to update auth user details.', 'error');
            throw new Error('Direct database connection (DATABASE_URL) is required to update auth user details.');
        }
    
        const updates: string[] = [];
        const params: (string | object | null)[] = [];
        let paramIndex = 1;
    
        // Dynamically build SET clauses and params array
        if (email !== undefined) {
            updates.push(`email = $${paramIndex++}`);
            params.push(email);
        }
        if (password !== undefined) {
            updates.push(`encrypted_password = crypt($${paramIndex++}, gen_salt('bf'))`);
            params.push(password);
            console.warn(`SECURITY WARNING: Updating password for user ${user_id} with plain text password via direct DB update.`);
        }
        if (role !== undefined) {
            updates.push(`role = $${paramIndex++}`);
            params.push(role);
        }
        if (app_metadata !== undefined) {
            updates.push(`raw_app_meta_data = $${paramIndex++}::jsonb`);
            params.push(JSON.stringify(app_metadata));
        }
        if (user_metadata !== undefined) {
            updates.push(`raw_user_meta_data = $${paramIndex++}::jsonb`);
            params.push(JSON.stringify(user_metadata));
        }
    
        // Add user_id as the final parameter for the WHERE clause
        params.push(user_id);
        const userIdParamIndex = paramIndex;
    
        const sql = `
            UPDATE auth.users
            SET ${updates.join(', ')}, updated_at = NOW()
            WHERE id = $${userIdParamIndex}
            RETURNING id, email, role, raw_app_meta_data, raw_user_meta_data, created_at::text, updated_at::text, last_sign_in_at::text;
        `;
    
        console.error(`Attempting to update auth user ${user_id}...`);
        context.log(`Attempting to update auth user ${user_id}...`);
    
        const updatedUser = await client.executeTransactionWithPg(async (pgClient: PoolClient) => {
             // Check pgcrypto if password is being updated
             if (password !== undefined) {
                try {
                    await pgClient.query("SELECT crypt('test', gen_salt('bf'))");
                } catch (err) {
                    throw new Error('Failed to execute crypt function for password update. Ensure pgcrypto extension is enabled.');
                }
             }
    
            try {
                const result = await pgClient.query(sql, params);
                if (result.rows.length === 0) {
                    throw new Error(`User update failed: User with ID ${user_id} not found or no rows affected.`);
                }
                return UpdatedAuthUserZodSchema.parse(result.rows[0]);
            } catch (dbError: unknown) {
                let errorMessage = 'Unknown database error during user update';
                let isUniqueViolation = false;
    
                // Check for potential email unique constraint violation if email was updated
                if (typeof dbError === 'object' && dbError !== null && 'code' in dbError) {
                    if (email !== undefined && dbError.code === '23505') {
                        isUniqueViolation = true;
                        errorMessage = `User update failed: Email '${email}' likely already exists for another user.`;
                    } else if ('message' in dbError && typeof dbError.message === 'string') {
                        errorMessage = `Database error (${dbError.code}): ${dbError.message}`;
                    } else {
                        errorMessage = `Database error code: ${dbError.code}`;
                    }
                } else if (dbError instanceof Error) {
                     errorMessage = `Database error during user update: ${dbError.message}`;
                } else {
                     errorMessage = `Database error during user update: ${String(dbError)}`;
                }
    
                console.error('Error updating user in DB:', dbError);
                
                // Throw the specific error message
                throw new Error(errorMessage);
            }
        });
    
        console.error(`Successfully updated user ${user_id}.`);
        context.log(`Successfully updated user ${user_id}.`);
        return updatedUser; // Matches UpdateAuthUserOutput (AuthUser)
    },
  • Zod input schema defining the parameters for updating an auth user, with validation for UUID user_id (required implicitly via refine), optional fields, and a refine check ensuring at least one update field is provided.
    const UpdateAuthUserInputSchema = z.object({
        user_id: z.string().uuid().describe('The UUID of the user to update.'),
        email: z.string().email().optional().describe('New email address.'),
        password: z.string().min(6).optional().describe('New plain text password (min 6 chars). WARNING: Insecure.'),
        role: z.string().optional().describe('New role.'),
        app_metadata: z.record(z.unknown()).optional().describe('New app metadata (will overwrite existing).'),
        user_metadata: z.record(z.unknown()).optional().describe('New user metadata (will overwrite existing).'),
    }).refine(data => 
        data.email || data.password || data.role || data.app_metadata || data.user_metadata,
        { message: "At least one field to update (email, password, role, app_metadata, user_metadata) must be provided." }
    );
  • Zod output schema for the updated AuthUser object returned by the handler, plus TypeScript type alias to AuthUser. Used for parsing the RETURNING clause results.
    const UpdatedAuthUserZodSchema = z.object({
        id: z.string().uuid(),
        email: z.string().email().nullable(),
        role: z.string().nullable(),
        created_at: z.string().nullable(),
        updated_at: z.string().nullable(), // Expect this to be updated
        last_sign_in_at: z.string().nullable(),
        raw_app_meta_data: z.record(z.unknown()).nullable(),
        raw_user_meta_data: z.record(z.unknown()).nullable(),
    });
    // Use AuthUser for the output type hint
    type UpdateAuthUserOutput = AuthUser;
  • src/index.ts:116-117 (registration)
    Registration of the update_auth_user tool into the availableTools object used by the MCP server.
    [createAuthUserTool.name]: createAuthUserTool as AppTool,
    [updateAuthUserTool.name]: updateAuthUserTool as AppTool,
  • src/index.ts:28-28 (registration)
    Import statement for the updateAuthUserTool from its implementation file.
    import { updateAuthUserTool } from './tools/update_auth_user.js';
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively adds critical context: the 'WARNING: Password handling is insecure' highlights a security risk, and 'Requires service_role key and direct DB connection' specifies authentication and connection requirements. This goes beyond what the input schema provides, covering safety and operational constraints that are essential for an agent to understand the tool's behavior.

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 appropriately sized and front-loaded, with the core purpose stated first ('Updates fields for a user in auth.users'), followed by critical warnings and requirements. Every sentence earns its place by adding essential information without redundancy. It's concise yet comprehensive for its length, making it easy for an agent to parse quickly.

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

Completeness4/5

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

Given the complexity (6 parameters, no output schema, no annotations), the description does a good job of being complete enough. It covers the purpose, security warnings, and prerequisites, which are crucial for a mutation tool. However, it doesn't explain return values or error handling, and with no output schema, this leaves a minor gap. For a tool with significant behavioral implications, it's mostly adequate but could be slightly more comprehensive.

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 input schema already documents all parameters thoroughly. The description doesn't add any additional meaning or context about the parameters beyond what's in the schema. According to the rules, when schema coverage is high (>80%), the baseline score is 3 even with no param info in the description, which applies here.

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 ('Updates') and resource ('fields for a user in auth.users'), making the purpose immediately understandable. It doesn't explicitly differentiate from sibling tools like 'create_auth_user' or 'delete_auth_user', but the 'update' action is distinct enough to imply difference. The description is specific about what gets updated (user fields) rather than being vague or tautological.

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

Usage Guidelines3/5

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

The description provides some usage context with 'Requires service_role key and direct DB connection', which indicates prerequisites. However, it doesn't explicitly state when to use this tool versus alternatives like 'create_auth_user' or 'delete_auth_user', nor does it provide exclusions or comparisons. The guidance is implied rather than explicit, leaving some ambiguity about optimal use cases.

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