Butterbase
Full-stack backend platform MCP — provision apps, manage databases, deploy functions, and more.
Community: Submitted by a user or imported; check the owner before granting accessOnlineAPI key requiredGlobalFreeRead-only
What it can do
- Init App: Create a new backend app with isolated database and API endpoints. Returns: app_id, db_name, api_base URL Example: Input: { name: "my-blog" } Output: { app_id: "app_abc123", api_base: "http:
- List Apps: List all backend apps with basic metadata. Returns: Array of apps with id, name, db_name, region, created_at Example output: { apps: [ { id: "app_abc123", name: "my-blog", db_name: "db_abc1
- Get Schema: Get the current database schema for an app. Returns: app_id, schema (tables, columns, indexes), api_base Example output: { app_id: "app_abc123", schema: { tables: { posts: { columns: { id:
What data it sees
Do you need an account
An API key from the service settings is required
Full-stack backend platform MCP — provision apps, manage databases, deploy functions, and more.
Server tool list (43)
Raw names from tools/list. Only developers need these.
| init_app | Create a new backend app with isolated database and API endpoints. Returns: app_id, db_name, api_base URL Example: Input: { name: "my-blog" } Output: { app_id: "app_abc123", api_base: "http://localhost:4000/v1/app_abc123", _meta: { next_actions: [...] } } Next steps: Use apply_schema to define tables, then configure_oauth_provider for auth. Common errors: - Name already exists: Choose a different name or use list_apps to find existing app - Invalid characters: Use only lowercase letters, numbers, hyphens, underscores - Name too long: Maximum 63 characters The response includes _meta.next_actions with recommended next steps. |
| list_apps | List all backend apps with basic metadata. Returns: Array of apps with id, name, db_name, region, created_at Example output: { apps: [ { id: "app_abc123", name: "my-blog", db_name: "db_abc123", db_provisioned: true, region: "us-east-1", created_at: "2026-04-03T10:00:00Z" } ] } Use this to: - Check if an app name already exists before calling init_app - Find the app_id for an existing app - Verify app provisioning status Common errors: - AUTH_INVALID_API_KEY: Check your API key is set correctly Idempotency: Safe to call anytime (read-only operation). |
| get_schema | Get the current database schema for an app. Returns: app_id, schema (tables, columns, indexes), api_base Example output: { app_id: "app_abc123", schema: { tables: { posts: { columns: { id: { type: "uuid", primaryKey: true }, title: { type: "text", nullable: false } } } } }, api_base: "http://localhost:4000/v1/app_abc123" } Use this to: - Inspect current schema before making changes - Verify schema changes were applied correctly - Generate schema documentation Common errors: - RESOURCE_NOT_FOUND: App doesn't exist, verify app_id with list_apps Idempotency: Safe to call anytime (read-only operation). |
| apply_schema | Apply a declarative schema update to make the database match your desired state. The platform diffs your schema against the current database and generates safe DDL. Example: Input: { app_id: "app_abc123", schema: { tables: { posts: { columns: { id: { type: "uuid", primaryKey: true, default: "gen_random_uuid()" }, title: { type: "text", nullable: false }, author_id: { type: "uuid" }, created_at: { type: "timestamptz", default: "now()" } } } } } } Returns: Applied statements, migration_id, and _meta with next_actions Idempotency: Safe to call multiple times. If schema unchanged, returns "Schema is up to date". Destructive operations: Require explicit opt-in via _drop or _dropColumns fields. Common errors: - VALIDATION_INVALID_SCHEMA: Check schema format matches DSL specification - STATE_PREREQUISITE_MISSING: Add _drop: true or _dropColumns to authorize destructive ops - QUOTA_TABLE_LIMIT: Maximum 50 tables per app Use dry_run_schema to preview SQL without executing. |
| dry_run_schema | Preview what would change when applying a schema update for a backend app, without making any changes. |
| enable_rls | Enable row-level security on a table. This is the foundation for RLS - you must enable it before creating policies. What it does: 1. Validates the table exists 2. Enables RLS on the table 3. Forces RLS (even for table owner) Example: Input: { app_id: "app_abc123", table_name: "products" } Output: { success: true, table: "products", rls_enabled: true } Next steps: Use create_policy to add custom policies (pass user_column to get auto-populate on INSERT), or create_user_isolation_policy for the simplest setup (always includes auto-populate trigger). Note: enable_rls alone does NOT create an auto-populate trigger for user columns. If you follow up with create_policy without the user_column parameter, clients must include the user column in POST request bodies. Common errors: - VALIDATION_TABLE_NOT_FOUND: Create the table with apply_schema first Idempotency: Safe to call multiple times. |
| create_policy | Create a custom RLS policy with full control over USING and WITH CHECK expressions. Butterbase has three built-in roles (butterbase_anon, butterbase_user, butterbase_service) assigned automatically based on the request's auth header — you never create them. This tool lets you write policies that control access for any of these roles. Prerequisites: - Table must exist - RLS must be enabled on the table (use enable_rls first) IMPORTANT — Auto-populate trigger: This tool does NOT create a BEFORE INSERT trigger unless you pass user_column. Without user_column, clients must include the user column in POST bodies or the insert will be rejected with AUTH_RLS_POLICY_VIOLATION. To always auto-populate, use create_user_isolation_policy instead. Example - Public read for anonymous users: Input: { app_id: "app_abc123", table_name: "products", policy_name: "public_read_products", command: "SELECT", role: "anon", using_expression: "published = true" } Example - User-specific access: Input: { app_id: "app_abc123", table_name: "cart_items", policy_name: "users_own_cart", command: "ALL", role: "user", using_expression: "user_id = current_user_id()::uuid" } Example - INSERT policy (user can only insert their own rows): Input: { app_id: "app_abc123", table_name: "sellers", policy_name: "sellers_user_insert", command: "INSERT", with_check_expression: "user_id = current_user_id()::uuid" } Example - RESTRICTIVE cross-table INSERT check (comments only on public posts): Input: { app_id: "app_abc123", table_name: "comments", policy_name: "comments_insert_public_posts_only", command: "INSERT", role: "user", with_check_expression: "EXISTS (SELECT 1 FROM posts WHERE posts.id = post_id AND posts.is_public = true)", restrictive: true } A RESTRICTIVE policy is AND'd with permissive policies, so this check cannot be bypassed by a permissive user_isolation policy that always passes. WARNING — Cross-table subqueries in RESTRICTIVE policies: If your expression uses EXISTS(SELECT 1 FROM other_table WHERE ...), the subquery runs under the SAME user's RLS context. If other_table has user_isolation, the subquery can only see the current user's rows — even if those rows are "public". Example pitfall: A RESTRICTIVE INSERT policy on "comments" that checks EXISTS(SELECT 1 FROM posts WHERE posts.id = post_id AND posts.is_public = true) will FAIL if "posts" has user_isolation — User B cannot see User A's posts in the subquery, even if they are public. Fix: Add a permissive SELECT policy on the referenced table for the rows the subquery needs (e.g., using_expression: "is_public = true"). Or use create_user_isolation_policy with public_read_column to set this up in one call. Example - Mixed access (public read, authenticated write): Input: { app_id: "app_abc123", table_name: "posts", policy_name: "public_read_posts", command: "SELECT", using_expression: "status = 'published'" } Then create another policy for INSERT/UPDATE with user check. Available commands: SELECT, INSERT, UPDATE, DELETE, ALL (default) Expression rules by command: - SELECT, DELETE: only using_expression (WITH CHECK not supported) - INSERT: only with_check_expression (USING not supported) - UPDATE, ALL: both using_expression and with_check_expression supported Auto-populate user column: If your WITH CHECK references user_id = current_user_id(), pass user_column to install a BEFORE INSERT trigger that auto-fills the column. Without this, clients must include the column in POST bodies or the insert will be rejected with AUTH_RLS_POLICY_VIOLATION. Example with auto-populate: Input: { app_id: "app_abc123", table_name: "posts", policy_name: "posts_owner_write", command: "ALL", role: "user", using_expression: "author_id = current_user_id()::uuid", with_check_expression: "author_id = current_user_id()::uuid", user_column: "author_id" } Roles (built-in, assigned automatically — you never create them): - butterbase_anon: No auth header → write policies for public access - butterbase_user: End-user JWT → write policies using current_user_id() - butterbase_service: Platform API key → automatic bypass (no policy needed) Helper functions: - current_user_id(): Returns the authenticated user's ID as TEXT. If your column is UUID, cast it: current_user_id()::uuid - Use column comparisons for butterbase_anon policies (e.g., "published = true") Common errors: - RLS_TYPE_MISMATCH: Column type doesn't match expression type. Use ::uuid or ::text casts. Example: current_user_id()::uuid - RLS_INVALID_EXPRESSION: SQL syntax error in your expression. Check for missing quotes, operators, or parentheses - VALIDATION_TABLE_NOT_FOUND: Create the table with apply_schema first Idempotency: Safe to call multiple times (drops existing policy first). |
| create_user_isolation_policy | Enable row-level security on a table so users can only access their own data. Prerequisites (validated automatically): - Table must exist - user_column must exist in the table - user_column must be UUID or TEXT type Example: Input: { app_id: "app_abc123", table_name: "posts", user_column: "author_id" } Output: { success: true, policy_name: "posts_user_isolation", _meta: { next_actions: [...] } } What it does: 1. Enables RLS on the table 2. Creates a policy: users can only see rows where user_column = their user_id 3. Adds a trigger to auto-populate user_column on INSERT Note: Clients do NOT need to include user_column in POST bodies — it is set automatically. This differs from create_policy (without user_column), which requires the client to supply it. This is the simplest way to get auto-populate behavior. The alternative (enable_rls + create_policy) only creates the trigger if you explicitly pass user_column to create_policy. 4. Automatically creates a service bypass policy (butterbase_service role) 5. If public_read_column is provided: creates additional SELECT policies for butterbase_user and butterbase_anon so all users can read rows where that column is true. This handles the common "own rows + public read" pattern in a single call. Example with public reads: Input: { app_id: "app_abc123", table_name: "posts", user_column: "author_id", public_read_column: "is_published" } This creates: 1. User isolation policy (user can CRUD their own rows) 2. SELECT policy for butterbase_user: rows where is_published = true 3. SELECT policy for butterbase_anon: rows where is_published = true 4. Auto-populate trigger for author_id 5. Service bypass policy Row-Level Security Role Model: Butterbase has three built-in roles assigned automatically based on the request's auth: - butterbase_anon: Assigned when no auth header is sent. Access based on policies you write. - butterbase_user: Assigned when a valid end-user JWT is sent. Access based on policies you write. - butterbase_service: Assigned when a platform API key is sent. Full access (automatic bypass policy). You do not create these roles — they are built into the platform. You only write policies for butterbase_anon and butterbase_user access patterns. Service access is automatic — no need to add OR clauses for platform API keys. Policy Examples: -- Public read for anonymous users (scoped to butterbase_anon) CREATE POLICY "public_read_posts" ON posts FOR SELECT TO butterbase_anon USING (published = true); -- Authenticated users see only their posts (scoped to butterbase_user) CREATE POLICY "users_own_posts" ON posts FOR ALL TO butterbase_user USING (author_id = current_user_id()) WITH CHECK (author_id = current_user_id()); -- Service bypass policy is auto-created (no action needed) Testing: Query the table with an end-user JWT to verify isolation works. Common errors: - VALIDATION_TABLE_NOT_FOUND: Create the table with apply_schema first - VALIDATION_COLUMN_NOT_FOUND: Add user_column to schema before enabling RLS - VALIDATION_INVALID_TYPE: user_column must be UUID or TEXT type Idempotency: Safe to call multiple times (updates existing policy). |
| configure_oauth_provider | Set up OAuth provider for end-user authentication. Built-in providers (URLs and scopes auto-filled — only client_id, client_secret, and redirect_uris required): - google, github, discord, facebook, linkedin, microsoft, apple, x For any other provider, supply authorization_url, token_url, and userinfo_url manually. Example (Google — simplified): Input: { app_id: "app_abc123", provider: "google", client_id: "123456.apps.googleusercontent.com", client_secret: "GOCSPX-...", redirect_uris: ["https://api.butterbase.ai/auth/app_abc123/oauth/google/callback"] } Output: { message: "OAuth configuration saved successfully", config: { provider: "google", authorization_url: "https://accounts.google.com/o/oauth2/v2/auth", ... } } Example (Apple — requires provider_metadata): Input: { app_id: "app_abc123", provider: "apple", client_id: "com.example.app", client_secret: "placeholder", redirect_uris: ["https://api.butterbase.ai/auth/app_abc123/oauth/apple/callback"], provider_metadata: { "teamId": "ABCDE12345", "keyId": "KEY123", "privateKey": "-----BEGIN PRIVATE KEY-----\n..." } } What it does: - Configures OAuth 2.0 flow for end-user sign-in - For built-in providers (google, github, discord, facebook, linkedin, microsoft, apple, x): auto-fills authorization_url, token_url, userinfo_url, and default scopes - For custom providers: stores URLs exactly as provided - Handles provider-specific quirks automatically (PKCE for X, JWT client_secret for Apple, ID token verification for Google/LinkedIn/Apple, email fallback for GitHub) Setup steps: 1. Create OAuth app in provider console (Google Cloud, GitHub Settings, etc) 2. Set redirect URI to: {api_base}/auth/{app_id}/oauth/{provider}/callback 3. Copy client_id and client_secret 4. Call this tool with credentials and redirect_uris 5. For Apple: also provide provider_metadata with teamId, keyId, and privateKey OAuth Flow: - Frontend initiates: GET {api_base}/auth/{app_id}/oauth/{provider}?redirect_to=https://yourapp.com/auth/callback - After successful authentication, user is redirected to redirect_to URL with tokens as query parameters - If redirect_to is not provided, tokens are returned as JSON - Apple uses POST callback (form_post response mode) — handled automatically Provider notes: - X (Twitter): Does not provide email. A synthetic email ({username}@users.noreply.x.local) is used. - Apple: Only provides user's name on first authorization. Requires teamId, keyId, and privateKey in provider_metadata. - Facebook: Uses comma-separated scopes internally. Default scopes: email, public_profile. Common errors: - RESOURCE_NOT_FOUND: App doesn't exist, verify app_id with list_apps - VALIDATION_INVALID_SCHEMA: Check client_id and client_secret are not empty Idempotency: Safe to call multiple times (upserts provider config). Next steps: Test OAuth flow by visiting {api_base}/auth/{app_id}/oauth/{provider}?redirect_to=YOUR_FRONTEND_URL |
| butterbase_docs | Read comprehensive Butterbase documentation (local, no API calls). Available topics: - all: Complete documentation (default) - overview: Platform introduction and key features - mcp: MCP tool reference and examples - rest: HTTP data API (auto-generated REST endpoints) - auth: End-user authentication (OAuth, JWT) - storage: File upload/download with S3 - functions: Serverless functions (triggers, context) - frontend: Static frontend deployment (upload zip, deploy to live URL) - ai: AI model gateway (chat completions, BYOK, usage) - billing: Your Butterbase plan, usage meters, app-level Stripe Connect (subscriptions and one-time payments) - platform: MCP over HTTP, /llms.txt, subdomains, suggestions, rate limits - schema: Schema DSL reference (types, indexes, constraints) - sdk: TypeScript SDK installation, client setup, query builder, auth, storage, functions - cli: CLI installation, commands for apps, schema, functions, storage, config Example: Input: { topic: "auth" } Output: Full authentication documentation with OAuth setup, JWT handling, etc. Use this to: - Learn Butterbase features and APIs - Get code examples for common tasks - Reference schema DSL syntax - Understand authentication flow - Learn about app monetization (subscriptions and one-time purchases) Note: This is a local documentation tool. No network requests are made. Idempotency: Safe to call anytime (read-only operation). |
| get_app_config | Get detailed configuration for an app including CORS, storage settings, and metadata. Returns: Full app config with allowed_origins, storage_config, region, timestamps Example output: { id: "app_abc123", name: "my-blog", allowed_origins: ["http://localhost:3000"], storage_config: { maxFileSizeMb: 10, allowedContentTypes: ["image/*", "application/pdf"], publicReadEnabled: false }, region: "us-east-1", created_at: "2026-04-03T10:00:00Z" } Use this to: - Check current CORS settings before updating - Verify storage configuration - Get app metadata for documentation Common errors: - RESOURCE_NOT_FOUND: App doesn't exist, verify app_id with list_apps Idempotency: Safe to call anytime (read-only operation). |
| update_cors | Update CORS allowed origins to control which frontend domains can access your API. Example: Input: { app_id: "app_abc123", allowed_origins: ["http://localhost:3000", "https://myapp.com"] } Output: { message: "CORS updated successfully", app_id: "app_abc123", allowed_origins: ["http://localhost:3000", "https://myapp.com"] } Use this to: - Enable browser-based API access from your frontend - Add production domain after deploying frontend - Update origins when changing hosting providers Common errors: - RESOURCE_NOT_FOUND: App doesn't exist, verify app_id with list_apps - VALIDATION_INVALID_SCHEMA: Origins must be valid URLs with protocol (http:// or https://) - Empty array: Must provide at least one origin Idempotency: Safe to call multiple times (replaces existing origins). Note: Origins must include protocol and should not have trailing slashes. |
| update_storage_config | Update storage configuration for an app. Example: Input: { app_id: "app_abc123", publicReadEnabled: true } Output: { message: "Storage configuration updated successfully", app_id: "app_abc123", storage_config: { maxFileSizeMb: 10, allowedContentTypes: ["*/*"], publicReadEnabled: true } } Use this to: - Enable public read access so any authenticated user can download any file in the app - Disable public read access to enforce per-user file isolation (default) When publicReadEnabled is true: - Any authenticated user can generate download URLs for any file in the app - Uploads and deletes remain user-scoped (users can only manage their own files) - Ideal for social apps, CMSes, or any app where files are shared across users When publicReadEnabled is false (default): - Users can only generate download URLs for their own files - Platform auth (API key) can still access any file Common errors: - RESOURCE_NOT_FOUND: App doesn't exist, verify app_id with list_apps - VALIDATION_INVALID_SCHEMA: publicReadEnabled must be a boolean Idempotency: Safe to call multiple times (updates existing config). |
| get_rls_policies | List all row-level security policies for an app. Returns: Array of RLS policies with table names, policy names, and rules Example output: { app_id: "app_abc123", policies: [ { tablename: "posts", policyname: "posts_user_isolation", cmd: "ALL", qual: "(author_id = current_user_id())", roles: ["authenticated"] } ] } Use this to: - Verify RLS policies are configured correctly - Check which tables have user isolation enabled - Debug access control issues Common errors: - RESOURCE_NOT_FOUND: App doesn't exist, verify app_id with list_apps Idempotency: Safe to call anytime (read-only operation). Note: Empty policies array means no RLS is configured yet. |
| delete_rls_policy | Remove RLS policies from a table. By default, removes ALL policies from the table and disables RLS. If policy_name is provided, removes only that specific policy (RLS stays enabled). Example - Remove all policies: Input: { app_id: "app_abc123", table_name: "posts" } Output: { message: "RLS policies removed successfully", table: "posts", policies_removed: 3 } Example - Remove a single policy: Input: { app_id: "app_abc123", table_name: "posts", policy_name: "public_read_posts" } Output: { message: "Policy \"public_read_posts\" removed from table \"posts\"", table: "posts", policy_name: "public_read_posts", remaining_policies: 2 } Use this to: - Remove a single policy without affecting others - Disable all user isolation for a table (omit policy_name) - Remove RLS before changing table structure Warning: Removing ALL policies makes the table globally accessible. Common errors: - RESOURCE_NOT_FOUND: App, table, or policy doesn't exist Idempotency: Safe to call multiple times. |
| get_oauth_config | Get OAuth provider configuration for an app. Returns: Provider config with client_id, scopes, URLs (client_secret is redacted) Example output (single provider): { app_id: "app_abc123", provider: { provider: "google", client_id: "123456.apps.googleusercontent.com", scopes: ["email", "profile"], authorization_url: "https://accounts.google.com/o/oauth2/v2/auth", token_url: "https://oauth2.googleapis.com/token", userinfo_url: "https://www.googleapis.com/oauth2/v2/userinfo" } } Example output (all providers): { app_id: "app_abc123", providers: [ { provider: "google", client_id: "...", ... }, { provider: "github", client_id: "...", ... } ] } Use this to: - Verify OAuth configuration is correct - Check which providers are configured - Get authorization URLs for documentation Common errors: - RESOURCE_NOT_FOUND: App or provider doesn't exist Idempotency: Safe to call anytime (read-only operation). |
| update_oauth_provider | Update existing OAuth provider configuration (change credentials, scopes, URLs, or metadata). Example: Input: { app_id: "app_abc123", provider: "google", scopes: ["email", "profile", "openid"] } Output: { message: "OAuth provider updated", app_id: "app_abc123", provider: "google" } Use this to: - Rotate OAuth credentials (update client_id/client_secret) - Change requested scopes - Update custom provider URLs - Update Apple provider_metadata (teamId, keyId, privateKey) Common errors: - RESOURCE_NOT_FOUND: App or provider doesn't exist, use configure_oauth_provider to create - VALIDATION_INVALID_SCHEMA: Check URLs are valid if updating custom provider Idempotency: Safe to call multiple times (updates existing config). Note: Only provide fields you want to change. Omitted fields keep their current values. |
| delete_oauth_provider | Remove OAuth provider configuration from an app. Example: Input: { app_id: "app_abc123", provider: "google" } Output: { message: "OAuth provider deleted", app_id: "app_abc123", provider: "google" } Use this to: - Remove unused OAuth providers - Clean up test configurations - Disable a sign-in method Common errors: - RESOURCE_NOT_FOUND: App or provider doesn't exist Warning: This will prevent users from signing in with this provider. Existing user sessions remain valid until they expire. Idempotency: Safe to call multiple times (no-op if provider already deleted). |
| get_storage_objects | List all uploaded files in app storage with metadata. Returns: Array of storage objects with id, filename, content_type, size_bytes, created_at Example output: { app_id: "app_abc123", objects: [ { id: "obj_xyz789", filename: "profile.jpg", content_type: "image/jpeg", size_bytes: 524288, user_id: "user_abc", s3_key: "app_abc123/obj_xyz789", created_at: "2026-04-03T10:00:00Z" } ] } Each object includes id (UUID) and s3_key (bucket path). Use id with generate_download_url — s3_key is not a URL and cannot be used as img src. Use this to: - List all files in storage - Find object_id for a specific file - Audit storage usage - Clean up unused files Common errors: - RESOURCE_NOT_FOUND: App doesn't exist Idempotency: Safe to call anytime (read-only operation). |
| delete_storage_object | Delete a file from app storage (removes from S3 and database). Example: Input: { app_id: "app_abc123", object_id: "obj_xyz789" } Output: { message: "Storage object deleted", object_id: "obj_xyz789" } Use this to: - Free up storage quota - Remove user-uploaded content - Clean up test files Warning: This permanently deletes the file from S3. Cannot be undone. Common errors: - RESOURCE_NOT_FOUND: App or object doesn't exist Idempotency: Safe to call multiple times (no-op if already deleted). Note: If object_id is referenced in your database (e.g., users.avatar_id), update those references before deletion. |
| generate_upload_url | Generate a presigned S3 URL for uploading files to app storage. Example: Input: { app_id: "app_abc123", filename: "profile.jpg", content_type: "image/jpeg", size_bytes: 524288 } Output: { upload_url: "https://s3.amazonaws.com/...", object_id: "obj_xyz789", expires_at: "2026-04-04T11:00:00Z", _meta: { resource_info: { storage_used_bytes: 52428800, ... } } } Upload workflow: 1. Call this tool to get upload_url and object_id 2. PUT file to upload_url with Content-Type header 3. Store object_id in your database (e.g., users.avatar_id) 4. Use generate_download_url to retrieve the file later Set public: true to make the file downloadable by any authenticated user (e.g., post images, avatars). Files are private by default — only the uploading user can generate download URLs. The API may also return object_key (bucket path). That value is for debugging/metadata only — it is NOT a browser URL. Persist object_id only for later download; never store object_key in a column meant for a public URL or use it as img src. Common errors: - QUOTA_STORAGE_EXCEEDED: Delete unused files or upgrade storage plan - QUOTA_FILE_SIZE_EXCEEDED: Maximum 10 MB per file - RESOURCE_NOT_FOUND: App doesn't exist Note: Upload URL expires in 15 minutes. Response includes storage quota info in _meta.resource_info. |
| generate_download_url | Generate a presigned S3 URL for downloading a file from app storage. Example: Input: { app_id: "app_abc123", object_id: "obj_xyz789" } Output: { download_url: "https://s3.amazonaws.com/...", filename: "profile.jpg", content_type: "image/jpeg", size_bytes: 524288, expires_at: "2026-04-04T11:00:00Z" } object_id must be the UUID returned from upload or from get_storage_objects (the object's id). Do not pass the object key / s3 path (e.g. app_id/user_id/uuid_file.jpg) — that will not work. Use this to: - Retrieve uploaded files for display or download - Generate temporary public URLs for file access - Serve files to end users without exposing S3 credentials Common errors: - RESOURCE_NOT_FOUND: App or object doesn't exist, use get_storage_objects to list files; verify you used object id (UUID), not object key path Note: Download URL expires in 1 hour. Generate a new URL if expired. Idempotency: Safe to call multiple times (generates new URL each time). |
| query_audit_logs | Query authentication audit logs with filtering and pagination. Returns: Array of auth events with user_id, event_type, success, timestamps Example output: { logs: [ { id: "log_abc123", app_id: "app_abc123", user_id: "user_xyz789", event_type: "login", event_data: { provider: "google" }, ip_address: "192.168.1.1", user_agent: "Mozilla/5.0...", success: true, created_at: "2026-04-03T10:00:00Z" }, { event_type: "token_refresh", success: false, error_message: "Token expired" } ] } Event types: - login: User signed in - register: New user created - token_refresh: Access token refreshed - logout: User signed out Use this to: - Audit user authentication activity - Debug login failures - Monitor suspicious activity (failed attempts, unusual IPs) - Track user sessions Common errors: - RESOURCE_NOT_FOUND: App doesn't exist Idempotency: Safe to call anytime (read-only operation). Note: Use limit and offset for pagination. Logs retained for 90 days. |
| delete_app | Delete an app and ALL its resources permanently. Example: Input: { app_id: "app_abc123" } Output: { message: "App deleted successfully", app_id: "app_abc123", db_name: "db_abc123" } What gets deleted: - App database (all tables, data, schemas) - Storage files (all uploaded files in S3) - Serverless functions (all deployed functions) - OAuth configurations - RLS policies - Audit logs - Control plane records WARNING: This is PERMANENT and IRREVERSIBLE. All data will be lost. Use this to: - Clean up test/demo apps - Remove unused apps - Delete apps before recreating with same name Common errors: - RESOURCE_NOT_FOUND: App doesn't exist Idempotency: Safe to call multiple times (no-op if already deleted). Best practice: Export important data before deletion. Consider using a different app name instead of deleting. |
| list_migrations | List all schema migrations applied to an app (most recent first). Returns: Array of migrations with id, description, SQL, applied_at Example output: { app_id: "app_abc123", migrations: [ { id: 3, description: "Add user_id column to posts", applied_sql: "ALTER TABLE posts ADD COLUMN user_id UUID;", applied_at: "2026-04-03T10:15:00Z" }, { id: 2, description: "Create posts table", applied_sql: "CREATE TABLE posts (id UUID PRIMARY KEY, ...);", applied_at: "2026-04-03T10:00:00Z" } ] } Use this to: - Audit schema change history - Debug schema issues by reviewing applied SQL - Understand database evolution over time - Verify migrations were applied correctly Common errors: - RESOURCE_NOT_FOUND: App doesn't exist Idempotency: Safe to call anytime (read-only operation). Note: Migrations are created by apply_schema. Each migration has a unique sequential ID. |
| deploy_function | Deploy or update a serverless function with custom business logic. Example: Input: { app_id: "app_abc123", name: "send-welcome-email", code: "export async function handler(req, ctx) { ... }", trigger: { type: "http", config: { method: "POST", path: "/welcome", auth: "required" } } } Output: { function_id: "fn_xyz789", name: "send-welcome-email", url: "https://api.butterbase.ai/v1/app_abc123/fn/send-welcome-email", status: "deployed" } Function signature: export async function handler(request: Request, context: { db: PostgresClient, // Query your app database env: Record<string, string>, // Access envVars user: { id: string } | null // Current user (if auth: required) }): Promise<Response> IMPORTANT: Handlers MUST return a Response object (Web API standard). Do NOT return plain objects like { status: 200, body: "..." }. Example: export async function handler(req, ctx) { const data = { hello: "world" }; return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); } Row-Level Security in Functions: Functions respect RLS policies based on how they're invoked: - Invoked with end-user JWT → butterbase_user role (RLS enforced) * ctx.db queries see only the user's data * ctx.user.id contains the authenticated user ID * Use case: User-facing operations - Invoked with platform API key → butterbase_service role (RLS bypassed) * ctx.db queries see all data * ctx.user is null * Use case: Admin operations, background jobs - Invoked by cron trigger → butterbase_service role (RLS bypassed) * ctx.db queries see all data * ctx.user is null * Use case: Scheduled tasks, cleanup jobs Trigger types: - http: Invoke via HTTP request (GET, POST, etc) - cron: Schedule periodic execution (e.g., "0 9 * * *" = daily at 9am) - websocket: Trigger on WebSocket event from client via realtime connection - s3_upload: Trigger on file upload [not yet implemented] - webhook: Receive webhooks from external services [not yet implemented] Common errors: - VALIDATION_INVALID_SCHEMA: Check code exports a handler function - RESOURCE_NOT_FOUND: App doesn't exist - Syntax error: Code must be valid TypeScript/JavaScript Idempotency: Safe to call multiple times (updates existing function with same name). Next steps: Use invoke_function to test, then get_function_logs to debug. |
| invoke_function | Invoke a deployed function and return its full HTTP response. Example — POST with body: Input: { app_id: "app_abc123", function_name: "submit-inquiry", body: { email: "user@example.com", message: "hello" } } Output: { status: 200, headers: { "content-type": "application/json" }, body: { id: "uuid-1234" }, duration_ms: 47 } Example — GET (no body): Input: { app_id: "app_abc123", function_name: "public-catalog", method: "GET" } Parameters: - method defaults to POST. The function's trigger config determines which methods are valid. - body is sent as JSON. Omit for GET/HEAD requests. - headers are merged with the default auth headers. Use this to: - Test a function immediately after deployment - Debug function logic with different inputs - Verify function response format and status codes Common errors: - RESOURCE_NOT_FOUND: Function doesn't exist, use list_functions to verify - Function timeout: Increase timeoutMs in deploy_function - Runtime error: Check get_function_logs for stack trace Idempotency: Depends on function implementation (may have side effects). |
| list_functions | List all deployed functions with status, metrics, and invocation URLs. Returns: Array of functions with name, trigger, url, status, metrics Example output: { functions: [ { id: "fn_xyz789", name: "send-welcome-email", trigger: { type: "http", config: { method: "POST" } }, url: "https://api.butterbase.ai/v1/app_abc123/fn/send-welcome-email", status: "deployed", deployedAt: "2026-04-03T10:00:00Z", invocationCount: 42, errorRate: 0.02, avgDuration: 245 } ] } Use this to: - See all deployed functions - Get function URLs for API documentation - Monitor function performance metrics - Find function_name for invoke_function Common errors: - RESOURCE_NOT_FOUND: App doesn't exist Idempotency: Safe to call anytime (read-only operation). |
| delete_function | Delete a deployed function permanently. Example: Input: { app_id: "app_abc123", function_name: "send-welcome-email" } Output: { message: "Function deleted successfully", app_id: "app_abc123", function_name: "send-welcome-email" } Use this to: - Clean up test functions - Remove deprecated functions - Free up function name for redeployment Warning: This permanently deletes the function. It will stop being invoked immediately. Common errors: - RESOURCE_NOT_FOUND: Function doesn't exist, use list_functions to verify Idempotency: Safe to call multiple times (no-op if already deleted). Note: This is a soft delete (sets deleted_at timestamp). The function record remains in the database for audit purposes. |
| get_function_logs | Retrieve recent invocation logs for debugging and monitoring. Returns: Array of log entries with timestamp, statusCode, duration, errors Example output: { logs: [ { timestamp: "2026-04-03T10:15:30Z", method: "POST", path: "/welcome", statusCode: 200, duration: 245, memoryUsed: 45 }, { timestamp: "2026-04-03T10:14:20Z", statusCode: 500, error: "Database connection failed", stack: "Error: Connection timeout at..." } ], hasMore: false } Use this to: - Debug function errors with stack traces - Monitor function performance (duration, memory) - Audit function invocations - Filter errors with level: "error" Common errors: - RESOURCE_NOT_FOUND: Function doesn't exist Idempotency: Safe to call anytime (read-only operation). Note: Logs are retained for 7 days. Use since parameter for time-based filtering. |
| update_jwt_config | Update JWT token expiration times for access and refresh tokens. Example: Input: { app_id: "app_abc123", accessTokenTtl: "1h", refreshTokenTtlDays: 30 } Output: { message: "JWT config updated", app_id: "app_abc123", jwt_config: { accessTokenTtl: "1h", refreshTokenTtlDays: 30 } } Token types: - Access token: Short-lived token for API requests (default: 15m) - Refresh token: Long-lived token to get new access tokens (default: 7 days) Time formats: - Access token: "15m", "1h", "2h", "1d" (s=seconds, m=minutes, h=hours, d=days) - Refresh token: Integer days (7, 30, 90) Use this to: - Increase security with shorter access tokens - Improve UX with longer refresh tokens - Balance security vs. convenience Common errors: - RESOURCE_NOT_FOUND: App doesn't exist - VALIDATION_INVALID_SCHEMA: Check time format is valid Idempotency: Safe to call multiple times (updates config). Note: Changes apply to new tokens only. Existing tokens keep their original expiration. |
| update_function_env | Update environment variables for a deployed function without redeploying code. Use this to: - Rotate secrets (API keys, tokens, passwords) - Update configuration values - Change environment-specific settings This is much faster than redeploying the entire function and avoids the risk of accidentally wiping env vars (issue #17). Example: Input: { app_id: "app_abc123", function_name: "send-email", envVars: { "SENDGRID_API_KEY": "new-key-123", "FROM_EMAIL": "noreply@example.com" } } Output: { message: "Environment variables updated successfully", function: { id: "...", name: "send-email", updated_at: "2024-01-15T10:30:00Z" } } Common errors: - RESOURCE_NOT_FOUND: Function doesn't exist, use list_functions to verify - VALIDATION_INVALID_SCHEMA: envVars must be an object Idempotency: Safe to call multiple times with the same values. Note: The function cache is automatically invalidated after updating env vars. |
| select_rows | Query rows from a table using the auto-generated REST API. By default, this tool authenticates with the platform API key (butterbase_service role), which bypasses Row-Level Security and returns ALL rows regardless of RLS policies. To test RLS enforcement from this tool, use the as_role and as_user parameters: - as_role: "anon" — simulate an anonymous request (butterbase_anon role) - as_role: "user", as_user: "<user-uuid>" — simulate a specific end-user (butterbase_user role) Without as_role, this tool always runs as butterbase_service (full access). Use this to: - Fetch data from tables (as admin/service — sees all rows) - Filter, sort, and paginate results - Select specific columns Example — Basic query: Input: { app_id: "app_abc123", table: "posts", limit: 10 } Output: [ { id: "uuid-1", title: "Hello World", created_at: "2024-01-15T10:00:00Z" }, ... ] Example — With filters: Input: { app_id: "app_abc123", table: "posts", filters: { "status": "eq.published", "created_at": "gt.2024-01-01" }, order: "created_at.desc", limit: 20 } Filter operators: - eq (equals): status=eq.published - neq (not equals): status=neq.draft - gt (greater than): age=gt.18 - gte (greater than or equal): age=gte.18 - lt (less than): price=lt.100 - lte (less than or equal): price=lte.100 - like (pattern match): title=like.%hello% - ilike (case-insensitive): title=ilike.%hello% - is (null/true/false): deleted_at=is.null - in (list): id=in.(1,2,3) - fts (full-text search): title=fts.hello world Common errors: - VALIDATION_TABLE_NOT_FOUND: Table doesn't exist, use get_schema to verify - VALIDATION_INVALID_SCHEMA: Invalid filter format Idempotency: Safe to call multiple times (read-only operation). |
| insert_row | Insert a new row into a table using the auto-generated REST API. By default, this tool authenticates with the platform API key (butterbase_service role), which bypasses Row-Level Security. Inserts via this tool are not subject to RLS policies. To test RLS enforcement on writes, use the as_role and as_user parameters: - as_role: "anon" — simulate an anonymous insert (butterbase_anon role) - as_role: "user", as_user: "<user-uuid>" — simulate a specific end-user (butterbase_user role) Without as_role, this tool always runs as butterbase_service (full access, bypasses RLS). Use this to: - Add new records to tables (as admin/service — bypasses RLS) - Bootstrap initial data - Create test data Example: Input: { app_id: "app_abc123", table: "posts", data: { "title": "Hello World", "body": "This is my first post", "status": "draft" } } Output: { id: "uuid-1234", title: "Hello World", body: "This is my first post", status: "draft", created_at: "2024-01-15T10:00:00Z" } Notes: - Only provide columns that exist in the table schema - Columns with defaults (like id, created_at) can be omitted - The response includes the full inserted row with generated values Common errors: - VALIDATION_TABLE_NOT_FOUND: Table doesn't exist, use get_schema to verify - VALIDATION_UNIQUE_CONSTRAINT_VIOLATION: Duplicate value in unique column - VALIDATION_FOREIGN_KEY_VIOLATION: Referenced record doesn't exist - VALIDATION_NOT_NULL_VIOLATION: Required field is missing Idempotency: Not idempotent - creates a new row each time. |
| generate_service_key | Generate a new API key (service key) for programmatic access to the Control API. Use this to: - Create API keys for automation scripts - Generate keys for CI/CD pipelines - Provide keys to team members or services The generated key (bb_sk_...) can be used to: - Access all MCP tools programmatically - Call the Control API directly - Manage apps, schemas, functions, and data Example: Input: { name: "CI/CD Pipeline Key" } Output: { key: "bb_sk_a1b2c3d4e5f6...", key_id: "uuid-1234", prefix: "bb_sk_a1b2c3", name: "CI/CD Pipeline Key", created_at: "2024-01-15T10:00:00Z" } IMPORTANT: The full key is only shown ONCE. Store it securely - it cannot be retrieved again. Common errors: - AUTH_INSUFFICIENT_PERMISSIONS: Only authenticated users can generate keys Idempotency: Not idempotent - creates a new key each time. Security notes: - Keys have full access to all your apps and data - Treat keys like passwords - never commit them to git - Revoke keys immediately if compromised - Use descriptive names to track key usage |
| create_frontend_deployment | Create a frontend deployment and get an upload URL. Upload your built frontend as a zip file to the returned URL, then use start_frontend_deployment to trigger the deploy. Steps: 1. Call this tool to get an upload URL 2. Upload your zip file to the URL (e.g. curl -X PUT "{uploadUrl}" -H "Content-Type: application/zip" --data-binary @frontend.zip) 3. Call start_frontend_deployment with the returned deployment_id Example: Input: { app_id: "app_abc123", framework: "react-vite" } Output: { deployment_id: "uuid-1234", uploadUrl: "https://...", expiresIn: 900, maxSizeBytes: 104857600 } Prerequisites: - App must exist (use init_app to create) Free plan: 1 deployment per app. Deploying again automatically replaces the previous deployment (no need to delete first). Starter+: unlimited deployments. Framework options: - react-vite: React app built with Vite (zip the dist/ folder) - nextjs-static: Next.js static export (zip the out/ folder) - static: Plain HTML/CSS/JS - other: Any framework that produces static output SPA routing: For SPA frameworks (react-vite, nextjs-static, other), a _redirects file is auto-injected so all routes serve index.html. If your zip already includes a _redirects file, it is preserved. IMPORTANT — Zip file paths must use forward slashes (/), not backslashes (\). On Windows, zips created with built-in tools use backslashes, which causes all files to be served as text/html (breaking JS/CSS with MIME errors). On Windows use Git Bash or WSL to run: cd dist && zip -r ../frontend.zip . Common errors: - RESOURCE_NOT_FOUND: App doesn't exist Idempotency: Not idempotent — creates a new deployment each time (replaces existing on free plan). Next steps: Upload your zip to the returned URL, then call start_frontend_deployment. |
| start_frontend_deployment | Start a frontend deployment after uploading your zip file. Call this after uploading your zip to the URL returned by create_frontend_deployment. Triggers the deployment and polls until it completes (up to 5 minutes). Example: Input: { app_id: "app_abc123", deployment_id: "uuid-1234" } Output: { deployment_id: "uuid-1234", url: "https://your-app.pages.dev", status: "READY" } Deployment statuses: - BUILDING: Deployment is being built - READY: Site is live at the returned URL - ERROR: Deployment failed Common errors: - INVALID_STATUS: Deployment is not in WAITING status (zip may not have been uploaded yet) - UPLOAD_EXPIRED: The upload URL expired before the zip was uploaded - RESOURCE_NOT_FOUND: Deployment or app doesn't exist Idempotency: Not idempotent — can only start a deployment once. Next steps: Visit the returned URL to see your live frontend. |
| list_frontend_deployments | List frontend deployment history for an app. Example: Input: { app_id: "app_abc123" } Output: { deployments: [ { id: "uuid-1234", framework: "react-vite", url: "https://your-app.pages.dev", status: "READY", fileCount: 15, totalSizeBytes: 524288, createdAt: "2024-01-15T10:00:00Z", updatedAt: "2024-01-15T10:05:00Z" } ] } Returns up to 50 most recent deployments for the app. Deployment statuses: - WAITING: Deployment created, awaiting zip upload - UPLOADING: Files are being processed - BUILDING: Deployment is being built - READY: Site is live at the returned URL - ERROR: Deployment failed (see error field) - CANCELED: Deployment was canceled Common errors: - RESOURCE_NOT_FOUND: App doesn't exist Idempotency: Safe to call anytime (read-only operation). |
| set_frontend_env | Set environment variables for frontend builds. Example: Input: { app_id: "app_abc123", vars: { "VITE_API_URL": "https://api.example.com", "VITE_APP_NAME": "My App", "NEXT_PUBLIC_API_KEY": "pk_test_123" } } Output: { message: "Frontend environment variables updated successfully", keys: ["VITE_API_URL", "VITE_APP_NAME", "NEXT_PUBLIC_API_KEY"] } What it does: - Stores encrypted environment variables for the app - Variables are available for future frontend builds - Upserts: updates existing variables or creates new ones Important notes: - These variables are NOT automatically injected into deployments - The AI agent must read these variables and inject them during the build process - For Vite: prefix with VITE_ (e.g., VITE_API_URL) - For Next.js: prefix with NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_API_URL) - For Create React App: prefix with REACT_APP_ (e.g., REACT_APP_API_URL) Common use cases: - API endpoints: VITE_API_URL, NEXT_PUBLIC_API_URL - Feature flags: VITE_ENABLE_ANALYTICS - Public keys: NEXT_PUBLIC_STRIPE_KEY - App configuration: VITE_APP_NAME, VITE_APP_VERSION Security: - Values are encrypted at rest - Only variable keys are returned (not values) - Never store secrets or private keys in frontend env vars Common errors: - VALIDATION_INVALID_SCHEMA: vars must be a non-empty object - RESOURCE_NOT_FOUND: App doesn't exist Idempotency: Safe to call multiple times (upserts variables). Next steps: Use get_frontend_env to list configured variable keys. |
| submit_suggestion | Submit feedback, bug reports, or feature suggestions to the Butterbase platform team. Use this tool when you encounter issues with Butterbase tools, want to suggest improvements, or when a user asks you to report something to the Butterbase team. Categories: - bug_report: Something isn't working as expected or documented. Example: "apply_schema fails silently when adding an enum column with a default value" - feature_request: A capability that doesn't exist yet but would be useful. Example: "Support for composite unique constraints across multiple columns" - improvement: An existing feature works but could be better. Example: "get_schema should include index definitions in its output" - documentation: The docs are missing, unclear, or incorrect. Example: "The deploy_function tool description doesn't mention the 50MB size limit" Source: - agent: You (the AI agent) are reporting this on your own initiative - human_prompted: The human user asked you to report this Returns: The created suggestion with a unique ID and status. Example output: { suggestion: { id: "a1b2c3d4-...", category: "bug_report", severity: "medium", description: "apply_schema returns success but...", affected_tool: "apply_schema", status: "new", created_at: "2026-04-05T10:00:00Z" } } Recent tool calls are automatically captured as context — you don't need to manually describe what you called. Just describe the issue or suggestion clearly. Idempotency: Each call creates a new suggestion. Avoid submitting duplicates. |
| configure_realtime | Enable realtime WebSocket notifications for database tables. When enabled, any INSERT, UPDATE, or DELETE on the specified tables will be broadcast to connected WebSocket clients in real time. Example: Input: { app_id: "app_abc123", tables: ["messages", "notifications"] } Output: { configured: [{ table: "messages", status: "enabled" }, ...] } After configuring, clients connect via WebSocket: ws://api.butterbase.local/v1/{app_id}/realtime Client sends: { "type": "subscribe", "table": "messages" } Server sends: { "type": "change", "table": "messages", "op": "INSERT", "record": {...} } RLS enforcement: - End-user JWT connections only receive changes they have permission to see - API key / service connections receive all changes (RLS bypassed) - Anonymous connections use butterbase_anon role policies Prerequisites: - Tables must already exist (use apply_schema first) - For user-scoped data, enable RLS on the table first Idempotent: safe to call multiple times. Already-enabled tables are skipped. |
| get_realtime_config | Get the realtime WebSocket configuration for an app. Returns which tables have realtime enabled, whether an active LISTEN connection exists, and the WebSocket URL to connect to. Example: Input: { app_id: "app_abc123" } Output: { app_id: "app_abc123", tables: [{ table_name: "messages", enabled: true, events: ["INSERT","UPDATE","DELETE"] }], active_connection: true, websocket_url: "ws://api.butterbase.local/v1/app_abc123/realtime" } |
| seed_database | Insert multiple rows into a table in a single call. Useful for seeding sample data, bootstrapping test fixtures, or populating lookup tables. IMPORTANT: This tool authenticates with the platform API key (butterbase_service role), which bypasses Row-Level Security. Inserts via this tool are not subject to RLS policies. Rows are inserted sequentially. If a row fails (e.g., duplicate key, constraint violation), the tool skips it and continues with the remaining rows. The response reports how many rows were inserted vs failed, with error details for each failure. Example: Input: { app_id: "app_abc123", table: "products", rows: [ { "name": "Widget", "price": 999, "category": "tools" }, { "name": "Gadget", "price": 1499, "category": "electronics" }, { "name": "Doohickey", "price": 299, "category": "tools" } ] } Output: { inserted: 3, failed: 0, errors: [], rows: [ { id: "uuid-1", ... }, { id: "uuid-2", ... }, { id: "uuid-3", ... } ] } Notes: - Columns with defaults (like id, created_at) can be omitted - Each row is an independent insert — failures don't roll back other rows - Maximum 100 rows per call Common errors (per row): - VALIDATION_UNIQUE_CONSTRAINT_VIOLATION: Duplicate value in unique column - VALIDATION_FOREIGN_KEY_VIOLATION: Referenced record doesn't exist - VALIDATION_NOT_NULL_VIOLATION: Required field is missing Idempotency: Not idempotent — creates new rows each time. |