Skip to main content
Glama

Self-Hosted Supabase MCP Server

by abushadab
update_auth_user.ts7.59 kB
import { z } from 'zod'; import type { ToolContext } from './types.js'; import type { PoolClient } from 'pg'; import type { AuthUser } from '../types/index.js'; // Import AuthUser // Input schema 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." } ); type UpdateAuthUserInput = z.infer<typeof UpdateAuthUserInputSchema>; // Output schema - Zod validation for the updated user 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; // Static JSON Schema for MCP const mcpInputSchema = { type: 'object', properties: { user_id: { type: 'string', format: 'uuid', description: 'The UUID of the user to update.' }, email: { type: 'string', format: 'email', description: 'New email address.' }, password: { type: 'string', minLength: 6, description: 'New plain text password (min 6 chars). WARNING: Insecure.' }, role: { type: 'string', description: 'New role.' }, user_metadata: { type: 'object', description: 'New user metadata (will overwrite existing).' }, app_metadata: { type: 'object', description: 'New app metadata (will overwrite existing).' }, }, required: ['user_id'], }; // Tool definition export const updateAuthUserTool = { name: 'update_auth_user', description: 'Updates fields for a user in auth.users. WARNING: Password handling is insecure. Requires service_role key and direct DB connection.', inputSchema: UpdateAuthUserInputSchema, mcpInputSchema: mcpInputSchema, // Ensure defined outputSchema: UpdatedAuthUserZodSchema, 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) }, };

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/abushadab/selfhosted-supabase-mcp-basic-auth'

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