# mcp MCP server

Butterbase MCP server — manage your backend: schemas, auth, functions, storage, RAG, deploys.

## Links
- Registry page: https://www.getdrio.com/mcp/io-github-butterbase-ai-mcp
- Repository: https://github.com/butterbase-ai/butterbase-oss
- Website: https://butterbase.ai

## Install
- Command: `npx -y @butterbase/mcp`
- Endpoint: https://api.butterbase.ai/mcp
- Auth: Auth required by registry metadata

## Setup notes
- Remote header: Authorization (required; secret)
- Package: Npm @butterbase/mcp v1.0.0
- Environment variable: BUTTERBASE_API_KEY (required; secret)
- The upstream registry signals required auth or secrets.
- Remote endpoint: https://api.butterbase.ai/mcp
- Header: Authorization

## Tools
- init_app (Initialize App) - Create a new backend app with isolated database and API endpoints.

Returns: app_id, api_url, url (frontend URL), and provisioning status.

Example:
  Input: { name: "my-blog" }
  Output: {
    app_id: "app_abc123",
    api_url: "https://api.butterbase.dev/v1/app_abc123",
    url: "https://my-blog.butterbase.dev",
    _meta: { next_actions: [...] }
  }

URL guide:
  - api_url: Your API endpoint for database queries, auth, and functions (e.g. https://api.butterbase.dev/v1/app_abc123)
  - url: Your frontend URL where your deployed site is served (e.g. https://my-blog.butterbase.dev)
  - These are different! The api_url is for backend requests, the url is where users visit your app.

Next steps: Use manage_schema (action: "apply") to define tables, then manage_oauth (action: "configure") for auth.

Common errors:
  - Name already exists: Choose a different name or use manage_app (action: "list") 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. Endpoint: https://api.butterbase.ai/mcp
- manage_schema (Manage Schema) - Manage the database schema: read current schema, apply changes, preview changes, and audit migration history.

Actions:
  - "get":             Get the current schema (tables, columns, indexes) and api_base
  - "apply":           Apply a declarative schema. Diffs against current and runs the safe DDL.
  - "dry_run":         Preview the SQL that "apply" would run, without executing
  - "list_migrations": List applied migrations (most recent first)

Parameters by action:
  get:             { app_id, action: "get" }
  apply:           { app_id, action: "apply",   schema, name? }
  dry_run:         { app_id, action: "dry_run", schema }
  list_migrations: { app_id, action: "list_migrations" }

Schema example:
  {
    tables: {
      posts: {
        columns: {
          id: { type: "uuid", primaryKey: true, default: "gen_random_uuid()" },
          title: { type: "text", nullable: false },
          author_id: { type: "uuid", references: { table: "users", column: "id", onDelete: "CASCADE" } },
          created_at: { type: "timestamptz", default: "now()" }
        }
      }
    }
  }

Idempotency: "apply" is safe to call multiple times. If the schema is already up-to-date, returns "Schema is up to date".

Destructive operations: Require explicit opt-in via the _drop (table-level) or _dropColumns (column-level) fields.

Common errors:
  - VALIDATION_INVALID_SCHEMA: schema format does not match the DSL
  - STATE_PREREQUISITE_MISSING: add _drop / _dropColumns to authorize destructive ops
  - QUOTA_TABLE_LIMIT: max 50 tables per app
  - RESOURCE_NOT_FOUND: app_id does not exist Endpoint: https://api.butterbase.ai/mcp
- manage_rls (Manage RLS) - Manage Row-Level Security (RLS): enable on tables, create/update/delete policies, list, and one-shot user isolation setup.

Actions:
  - "enable":               Enable RLS on a table (foundation — no policies yet)
  - "create_policy":        Create a custom RLS policy with USING / WITH CHECK expressions
  - "update_policy":        Atomically update an existing policy (drops and re-creates in one tx)
  - "create_user_isolation": One-shot — enable RLS, create policy so users see only their rows, install auto-populate trigger
  - "list":                 List all RLS policies for the app (and tables_with_rls without policies)
  - "delete":               Delete one policy (if policy_name set) or ALL policies on the table (and disable RLS)

Parameters by action:
  enable:                { app_id, action: "enable", table_name }
  create_policy:         { app_id, action: "create_policy", table_name, policy_name, command?, role?, using_expression?, with_check_expression?, restrictive?, user_column? }
  update_policy:         { app_id, action: "update_policy", table_name, policy_name, command?, role?, using_expression?, with_check_expression?, restrictive? }
  create_user_isolation: { app_id, action: "create_user_isolation", table_name, user_column, public_read_column? }
  list:                  { app_id, action: "list" }
  delete:                { app_id, action: "delete", table_name, policy_name? }

Built-in roles (assigned automatically by the platform — you never create them):
  - butterbase_anon:    no auth header → "anon" in policies
  - butterbase_user:    valid end-user JWT → "user" in policies; current_user_id() returns user id
  - butterbase_service: platform API key → automatic full-access bypass; no policy needed

create_policy guidance:
  - command defaults to ALL. SELECT/DELETE: only using_expression. INSERT: only with_check_expression. UPDATE/ALL: both.
  - role: omit to apply to all roles, or set "anon" / "user" to scope and prevent cross-role policy leaks.
  - restrictive: true → policy is AND'd with permissive ones; useful for cross-table checks that must always hold.
  - user_column: pass to install a BEFORE INSERT trigger that auto-fills the column from current_user_id() —
    without it, clients must include the column in POST bodies or insert is rejected with AUTH_RLS_POLICY_VIOLATION.
  - For UUID columns, cast: current_user_id()::uuid

Cross-table subqueries pitfall:
  EXISTS(SELECT 1 FROM other_table WHERE ...) inside a policy runs under the SAME user's RLS context.
  If other_table has user_isolation, the subquery only sees the current user's rows, even for "public" rows.
  Fix: add a permissive SELECT policy on the referenced table for the rows the subquery needs, OR use
  "create_user_isolation" with public_read_column to set this up in one call.

create_user_isolation does:
  1. Enables RLS on the table
  2. User isolation policy (rows where user_column = current_user_id())
  3. Auto-populate trigger for user_column on INSERT
  4. Auto service bypass policy
  5. If public_read_column set: extra SELECT policies for butterbase_user + butterbase_anon
     allowing reads where that boolean column is true ("own rows + public read" pattern in one call)

delete behavior:
  - With policy_name: removes that single policy (RLS stays enabled)
  - Without policy_name: removes ALL policies AND disables RLS — table becomes globally accessible

Common errors:
  - VALIDATION_TABLE_NOT_FOUND: create the table with manage_schema (action: "apply") first
  - VALIDATION_COLUMN_NOT_FOUND: user_column missing from the table
  - VALIDATION_INVALID_TYPE: user_column must be UUID or TEXT
  - RLS_TYPE_MISMATCH: cast types in expressions, e.g. current_user_id()::uuid
  - RLS_INVALID_EXPRESSION: SQL syntax error
  - RESOURCE_NOT_FOUND: policy doesn't exist (update_policy) — use create_policy first

Idempotency: enable, create_policy, update_policy, create_user_isolation, delete — all safe to retry. Endpoint: https://api.butterbase.ai/mcp
- manage_oauth (Manage OAuth Providers) - Manage OAuth providers for end-user authentication (configure, get, update, delete).

Actions:
  - "configure": Set up a new OAuth provider (idempotent upsert)
  - "get":       Read provider config (single provider or all). client_secret is redacted.
  - "update":    Patch existing provider — only supplied fields change
  - "delete":    Remove a provider. Existing sessions remain valid until expiry.

Built-in providers (URLs/scopes auto-filled — only client_id, client_secret, redirect_uris required):
  google, github, discord, facebook, linkedin, microsoft, apple, x
For any other provider name, supply authorization_url, token_url, userinfo_url manually.

Parameters by action:
  configure: { app_id, action: "configure", provider, client_id, client_secret, redirect_uris, scopes?, authorization_url?, token_url?, userinfo_url?, provider_metadata? }
  get:       { app_id, action: "get", provider? }   // omit provider to list all
  update:    { app_id, action: "update", provider, ...fields-to-change }
  delete:    { app_id, action: "delete", provider }

Example — configure (Google):
  Input: { app_id: "app_abc123", action: "configure", provider: "google",
           client_id: "...", client_secret: "GOCSPX-...",
           redirect_uris: ["https://api.butterbase.ai/auth/app_abc123/oauth/google/callback"] }

Example — configure (Apple, requires provider_metadata):
  Input: { ..., provider: "apple", provider_metadata: { teamId, keyId, privateKey } }

Provider notes:
  - X (Twitter): no email — synthetic {username}@users.noreply.x.local is used
  - Apple: only returns name on first auth; uses POST callback (handled automatically); requires provider_metadata { teamId, keyId, privateKey }
  - Facebook: default scopes email, public_profile

OAuth flow after configure:
  GET {api_base}/auth/{app_id}/oauth/{provider}?redirect_to=https://yourapp.com/auth/callback
  After successful authentication, user is redirected to redirect_to with tokens as query params.

Common errors:
  - RESOURCE_NOT_FOUND: app or provider doesn't exist
  - VALIDATION_INVALID_SCHEMA: empty client_id/client_secret, or invalid URL on a custom provider

Idempotency: configure/update/delete are safe to retry.

Warning (delete): prevents future sign-ins via that provider; existing sessions remain valid until they expire. Endpoint: https://api.butterbase.ai/mcp
- butterbase_docs (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
  - regions: Choosing a region at app creation, moving apps between regions, discovering the live region list
  - 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
  - integrations: Third-party integrations (OAuth connect flow, tool execution, SDK, CLI)
  - substrate: Per-user memory + action coordination plane for AI agents (entities, decisions, attention rules, action ledger, outbox, ws stream, ctx.substrate inside functions)

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). Endpoint: https://api.butterbase.ai/mcp
- manage_storage (Manage Storage) - Manage app storage: presigned upload/download URLs, list/delete objects, update config.

Actions:
  - "upload_url":    Get a presigned PUT URL to upload a file (expires in 15 min)
  - "download_url":  Get a presigned GET URL for a stored file (expires in 1 hour)
  - "list":          List all objects in app storage with metadata
  - "delete":        Permanently delete an object from S3 + database
  - "update_config": Update storage config (e.g., publicReadEnabled)

Parameters by action:
  upload_url:    { app_id, action: "upload_url", filename, content_type, size_bytes, public? }
  download_url:  { app_id, action: "download_url", object_id }
  list:          { app_id, action: "list" }
  delete:        { app_id, action: "delete", object_id }
  update_config: { app_id, action: "update_config", publicReadEnabled? }

object_id is the UUID returned from upload or list. Do NOT pass the s3_key / bucket path
(e.g. app_id/user_id/uuid_file.jpg) — that is metadata only and is not a usable URL.

Upload workflow:
  1. action: "upload_url"  → returns { upload_url, object_id, expires_at }
  2. PUT the file to upload_url with the matching Content-Type header
  3. Persist object_id (e.g. users.avatar_id)
  4. Later: action: "download_url" with that object_id

Set public: true on upload_url to make the file downloadable by any authenticated user
(e.g. post images, avatars). Files are private by default.

publicReadEnabled (update_config):
  - true:  any authenticated user can download any file (uploads/deletes still user-scoped)
  - false (default): users can only download their own files; platform auth (API key) can still access any file

Limits & errors:
  - Files: max 10 MB each (QUOTA_FILE_SIZE_EXCEEDED)
  - QUOTA_STORAGE_EXCEEDED: delete unused files or upgrade plan
  - RESOURCE_NOT_FOUND: app or object doesn't exist (verify object_id, not s3_key)
  - delete is idempotent (no-op if already deleted); upload/download URL generation is not (new URL each call)

Warning: "delete" cannot be undone. Update DB references (e.g. users.avatar_id) first. Endpoint: https://api.butterbase.ai/mcp
- manage_kv (Manage KV) - Manage app KV store: config rules (expose/unexpose namespaces) and data-plane operations (get/set/del/incr etc).

Actions — Config:
  - "list_rules":  List all KV namespace exposure rules for the app
  - "expose":      Expose a key pattern with read/write role access control
  - "unexpose":    Remove an exposure rule by pattern
  - "stats":       Get KV usage stats (key count, memory, etc.)
  - "scan":        Scan keys by prefix (cursor-based, params: prefix, limit, cursor)
  - "flush":       Delete ALL keys in the KV store (requires confirm: true; include_config?: true also wipes rules)

Actions — Data plane:
  - "get":    Get the value of a key (opts: raw?, touch?)
  - "set":    Set a key to a value (opts: ttl?, ephemeral?, raw?)
  - "del":    Delete one key
  - "incr":   Increment a key's integer value (opts: by?)
  - "decr":   Decrement a key's integer value (opts: by?)
  - "setnx":  Set a key only if it does not already exist (opts: value, ttl?)
  - "setex":  Set a key with an explicit TTL in seconds (same as set + ttl)
  - "cas":    Compare-and-swap: atomically set next only if current value matches expected
  - "exists": Check if a key exists
  - "ttl":    Get remaining TTL of a key in seconds
  - "expire": Set a TTL on an existing key
  - "mget":   Get values of multiple keys at once (uses batch op)
  - "mset":   Set multiple key-value pairs at once (uses batch op; entries: {key: value})

Parameters by action:
  list_rules:  { app_id, action: "list_rules" }
  expose:      { app_id, action: "expose", pattern, read, write }
  unexpose:    { app_id, action: "unexpose", pattern }
  stats:       { app_id, action: "stats" }
  scan:        { app_id, action: "scan", prefix?, limit?, cursor? }
  flush:       { app_id, action: "flush", confirm: true, include_config? }
  get:         { app_id, action: "get", key, raw?, touch? }
  set:         { app_id, action: "set", key, value, ttl?, ephemeral?, raw? }
  del:         { app_id, action: "del", key }
  incr:        { app_id, action: "incr", key, by? }
  decr:        { app_id, action: "decr", key, by? }
  setnx:       { app_id, action: "setnx", key, value, ttl? }
  setex:       { app_id, action: "setex", key, value, ttl }
  cas:         { app_id, action: "cas", key, expected, next }
  exists:      { app_id, action: "exists", key }
  ttl:         { app_id, action: "ttl", key }
  expire:      { app_id, action: "expire", key, ttl }
  mget:        { app_id, action: "mget", keys }
  mset:        { app_id, action: "mset", entries }

Warning: "flush" deletes ALL keys and cannot be undone. Always pass confirm: true explicitly. Endpoint: https://api.butterbase.ai/mcp
- query_audit_logs (Query Audit Logs) - Query audit events for an app — authentication, admin mutations, and function invocations.

Returns a unified event stream. Each event has:
  category       'auth' | 'admin' | 'function'
  event_type     e.g. 'login', 'schema.apply', 'function.deploy', 'function.invoke'
  action         'create' | 'update' | 'delete' | 'invoke' | 'enable' | 'disable' | null
  resource_type  which resource the event acted on
  resource_id    the resource identifier (function name, policy name, deployment id, etc.)
  actor_type     'platform_user' | 'app_user' | 'api_key' | 'system' | 'anonymous'
  actor_id       platform user id / app user id / api key id
  event_data     event-specific payload
  success        whether the event succeeded
  correlation_id request id (ties related events together)

Use this to:
  - Investigate who did what and when
  - Debug failing auth / admin / function flows
  - Monitor suspicious activity
  - Trace a request across subsystems via correlation_id

Common filters:
  - category='admin' to see only administrative mutations
  - resource_type='function' + resource_id='my-fn' to see one function's history
  - actor_id=<user-id> to see one actor's activity
  - from / to to narrow to a time window

Idempotency: Safe to call anytime (read-only). Historical auth events predating migration 034 are included (normalized). Endpoint: https://api.butterbase.ai/mcp
- deploy_function (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)
    waitUntil: (promise: Promise) => void,  // Keep alive for background work after response
    idempotency: {                          // Webhook / event dedup primitive
      claim: (key: string, opts?: { scope?: string; ttlSeconds?: number }) => Promise<boolean>
    }
  }): Promise<Response>

Console output: console.log(), console.info(), console.warn(), console.error(), and console.debug() calls are captured and stored with invocation logs. View them via manage_function (action: "get_logs").

IMPORTANT: Handlers MUST return a Response object (Web API standard).
Do NOT return plain objects like { status: 200, body: "..." }.

Idempotent webhook handlers with ctx.idempotency.claim():
  Third-party webhook providers (Stripe, Telegram, GitHub, Slack, Twilio, Discord)
  retry delivery on non-2xx responses with the same event id. Use ctx.idempotency.claim()
  to atomically dedupe — it returns true if you're the first to see this key, false if
  another invocation already claimed it.

  export async function handler(req, ctx) {
    const event = await req.json();
    if (!(await ctx.idempotency.claim(event.id))) {
      // Already processed — ack the retry without re-doing work.
      return new Response('duplicate', { status: 200 });
    }
    await processEvent(event);
    return new Response('ok', { status: 200 });
  }

  Options:
    - scope: 'stripe' | 'telegram' | ... (default: 'default'). Namespace claims so
      keys from different providers can never collide.
    - ttlSeconds: mark the claim with an expiry. Cleanup is your responsibility:
      DELETE FROM _idempotency_keys WHERE expires_at < now();

Background work with ctx.waitUntil():
  Use ctx.waitUntil(promise) to keep the function alive after the response is sent.
  This is useful for fire-and-forget tasks like sending emails or logging.
  Background work has a 30-second timeout. ctx.db is available inside waitUntil promises.

  export async function handler(req, ctx) {
    ctx.waitUntil(fetch("https://api.email.com/send", { method: "POST", body: "..." }));
    return new Response(JSON.stringify({ accepted: true }), {
      headers: { "Content-Type": "application/json" }
    });
  }

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 manage_function (action: "get_logs") to debug. Endpoint: https://api.butterbase.ai/mcp
- invoke_function (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 manage_function (action: "list") to verify
  - Function timeout: Increase timeoutMs in deploy_function
  - Runtime error: Check manage_function (action: "get_logs") for stack trace

Idempotency: Depends on function implementation (may have side effects). Endpoint: https://api.butterbase.ai/mcp
- manage_function (Manage Function) - Manage function lifecycle: list, delete, get logs, and update environment variables.

Actions:
  - "list":       List all deployed functions with status, metrics, and invocation URLs
  - "delete":     Delete a deployed function permanently (IRREVERSIBLE)
  - "get_logs":   Retrieve recent invocation logs for debugging and monitoring
  - "update_env": Update environment variables for a deployed function without redeploying code

Parameters by action:
  list:       { app_id, action: "list" }
  delete:     { app_id, action: "delete", function_name }
  get_logs:   { app_id, action: "get_logs", function_name, limit?, since?, level?, include_deleted? }
  update_env: { app_id, action: "update_env", function_name, env }

Common errors:
  - RESOURCE_NOT_FOUND: Function doesn't exist
  - VALIDATION_INVALID_SCHEMA: Invalid parameter format

Idempotency: Safe to call anytime (list is read-only; delete is idempotent; update_env is safe to call multiple times). Endpoint: https://api.butterbase.ai/mcp
- select_rows (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 manage_schema (action: "get") to verify
  - VALIDATION_INVALID_SCHEMA: Invalid filter format

Idempotency: Safe to call multiple times (read-only operation). Endpoint: https://api.butterbase.ai/mcp
- insert_row (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 manage_schema (action: "get") 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. Endpoint: https://api.butterbase.ai/mcp
- create_frontend_deployment (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 manage_frontend (action: "start_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 manage_frontend (action: "start_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).

Your frontend will be deployed to https://<app-name>.butterbase.dev.

Next steps: Upload your zip to the returned URL, then call manage_frontend (action: "start_deployment"). Endpoint: https://api.butterbase.ai/mcp
- manage_edge_ssr (Manage Edge SSR Deployments) - Manage Edge SSR (Cloudflare Workers) deployments: prebuilt-zip flow, server-side build flow, list history.

Actions:
  - "create":             Create a deployment from a locally-built zip; returns upload URL + deployment_id
  - "start":              Start the deployment after the zip is uploaded; polls until READY/ERROR (≤60s)
  - "create_from_source": Server-side build — Mode 1: create deployment + return upload_url
  - "start_from_source":  Server-side build — Mode 2: kick off the build after source upload
  - "list":               List recent deployments (status, URL, sizes)

Two flows (pick ONE):

  FLOW A — local build (you build with @cloudflare/next-on-pages locally):
    1. Run `npx @cloudflare/next-on-pages` then zip the CONTENTS of `.vercel/output/static/`
       (cd .vercel/output/static && zip -r ../../../edge-ssr.zip .)
       On Windows use Git Bash or WSL; built-in zip tools use backslashes which break uploads.
    2. action: "create"   → { deployment_id, uploadUrl, expiresIn }
    3. PUT zip to uploadUrl with Content-Type: application/zip
    4. action: "start"    → polls; returns { url, status: "READY" }

  FLOW B — server-side build (Butterbase runs the build for you):
    1. action: "create_from_source"  → { deployment_id, upload_url, max_source_bytes }
    2. PUT source zip (≤50 MB) to upload_url with Content-Type: application/zip
    3. action: "start_from_source" with deployment_id + lockfile_hash (sha256 of package-lock.json)
       → { build_id, status, logs_url, status_url }
    4. Stream logs_url for live build output; poll status_url for terminal status

Parameters by action:
  create:             { app_id, action, framework? }
  start:              { app_id, action, deployment_id }
  create_from_source: { app_id, action, framework? }
  start_from_source:  { app_id, action, deployment_id, lockfile_hash, build_command?, output_dir?, package_manager?, user_env? }
  list:               { app_id, action, limit? }

framework: "nextjs-edge" (default) | "remix-edge" | "other-edge"

Status values: WAITING | UPLOADING | BUILDING | READY | ERROR | CANCELED | TIMEOUT
On TIMEOUT: deployment did not reach a terminal state within 60s. Use action: "list" to check
the current status, or call "start" again if it is still BUILDING.

Plan limits: Free = 1 deployment per app (replaces previous). Starter+ = unlimited.

Common errors:
  - INVALID_STATUS / UPLOAD_EXPIRED: zip not uploaded before "start"
  - STATE_PREREQUISITE_MISSING: source zip not uploaded before "start_from_source"
  - QUOTA_FILE_SIZE_EXCEEDED: source zip exceeds 50 MB
  - RESOURCE_NOT_FOUND: app or deployment doesn't exist
  - EXTERNAL_CLOUDFLARE_ERROR: Workers for Platforms not configured

Build caching (start_from_source):
  lockfile_hash is the node_modules cache key — same hash means cached node_modules (faster builds).
  Compute it with: sha256sum package-lock.json | cut -d' ' -f1 Endpoint: https://api.butterbase.ai/mcp
- manage_frontend (Manage Frontend) - Manage frontend deployments, environment variables, and custom domains for a Butterbase app.

Actions:
  - "start_deployment":       Start a frontend deployment after uploading your zip file. Call after uploading zip to the URL returned by create_frontend_deployment. Polls until complete (up to 5 minutes).
  - "list_deployments":       List frontend deployment history for an app (read-only).
  - "create_from_source":     Create a source-based deployment and get a presigned upload URL (Mode 1). Upload your source zip to the URL via HTTP PUT with Content-Type: application/zip (max 50 MB).
  - "start_from_source":      Start the build for a source-based deployment (Mode 2). Requires deployment_id from create_from_source and a lockfile_hash.
  - "set_env":                Set environment variables for frontend builds (upserts).
  - "configure_custom_domain": Manage custom domains. Requires domain_action sub-option.

Parameters by action:
  start_deployment:        { app_id, action: "start_deployment", deployment_id }
  list_deployments:        { app_id, action: "list_deployments" }
  create_from_source:      { app_id, action: "create_from_source" }
  start_from_source:       { app_id, action: "start_from_source", deployment_id, lockfile_hash, build_command?, output_dir?, package_manager?, user_env? }
  set_env:                 { app_id, action: "set_env", vars }
  configure_custom_domain: { app_id, action: "configure_custom_domain", domain_action, hostname?, domain_id? }
    domain_action sub-options:
      "add":    { hostname } — Register a new custom domain
      "list":   {} — List all custom domains for an app
      "status": { domain_id } — Check verification/SSL status of a domain
      "remove": { domain_id } — Remove a custom domain
      "verify": { domain_id } — Trigger re-verification of a pending domain

Common errors:
  - RESOURCE_NOT_FOUND: App or deployment doesn't exist
  - 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
  - STATE_PREREQUISITE_MISSING: Source zip not yet uploaded (PUT to upload_url first)
  - QUOTA_FILE_SIZE_EXCEEDED: Source zip exceeds 50 MB
  - BUILD_FAILED: Build command exited with non-zero status (check logs_url for details)
  - VALIDATION_INVALID_SCHEMA: vars must be a non-empty object
  - feature_not_available: Free plan — upgrade to Pro (custom domains)
  - RESOURCE_ALREADY_EXISTS: Hostname already registered Endpoint: https://api.butterbase.ai/mcp
- submit_suggestion (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. Endpoint: https://api.butterbase.ai/mcp
- manage_realtime (Manage Realtime) - Manage realtime WebSocket notifications for database tables.

Actions:
  - "configure": Enable realtime broadcasts (INSERT/UPDATE/DELETE) on the given tables.
                 Idempotent — already-enabled tables are skipped.
  - "get":       Return current realtime config (which tables, active LISTEN connection, websocket URL).

Parameters by action:
  configure: { app_id, action: "configure", tables: [...] }
  get:       { app_id, action: "get" }

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 manage_schema action: "apply" first)
  - For user-scoped data, enable RLS on the table first (manage_rls action: "enable" / "create_user_isolation") Endpoint: https://api.butterbase.ai/mcp
- manage_auth_users (Manage Auth Users) - Manage end-user auth records for an app.

Actions:
  - "list":   Paginated list of app_users (id, email, provider, provider_uid, email_verified,
              last_sign_in_at, created_at). Pass the next_cursor from a prior response to page.
  - "delete": Hard-delete an app user by id. Cascades to refresh tokens and verification codes.
              Use this to unblock OAuth migrations when an existing email/password row collides.

Parameters by action:
  list:   { app_id, action: "list", limit?, cursor? }
  delete: { app_id, action: "delete", user_id }

Tips:
  - Looking for a user by email? Call list and filter client-side; this tool does not search by email.
  - To switch a user from email/password to Google OAuth without deleting, just have them sign in
    with Google — the OAuth callback now links the existing email row in place automatically. Endpoint: https://api.butterbase.ai/mcp
- seed_database (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. Endpoint: https://api.butterbase.ai/mcp
- manage_rag_content (Manage RAG Content) - Manage RAG (Retrieval-Augmented Generation) collections and documents. Collections are named
containers for documents that are chunked, embedded, and indexed for semantic search.

Actions:
  Collection actions:
  - "create_collection": Create a new collection
  - "list_collections":  List all collections in an app
  - "get_collection":    Get details for a specific collection (includes document counts by status)
  - "delete_collection": Permanently delete a collection and all its documents/embeddings

  Document actions:
  - "ingest_document":     Add a document (raw text or uploaded file) to be chunked, embedded, and indexed
  - "list_documents":      List all documents in a collection with their status
  - "get_document_status": Check the processing status of a specific document
  - "delete_document":     Permanently delete a document and its chunks/embeddings

Parameters by action:
  create_collection:   { app_id, action: "create_collection", name, description?, access_mode?, chunk_size?, chunk_overlap? }
  list_collections:    { app_id, action: "list_collections" }
  get_collection:      { app_id, action: "get_collection", name }
  delete_collection:   { app_id, action: "delete_collection", name }
  ingest_document:     { app_id, collection, action: "ingest_document", text?, storage_object_id?, filename?, metadata? }
  list_documents:      { app_id, collection, action: "list_documents" }
  get_document_status: { app_id, collection, action: "get_document_status", document_id }
  delete_document:     { app_id, collection, action: "delete_document", document_id }

access_mode options (create_collection):
  - "private" (default): Only the app owner can query
  - "shared": All authenticated users can query
  - "custom": Use RLS policies for fine-grained access

Ingestion modes for ingest_document (provide one):
  1. Raw text: provide "text" directly
  2. File-based: upload via manage_storage (action: "upload_url") first, then provide "storage_object_id"

Supported file types: PDF, TXT, Markdown, CSV, HTML, DOCX, XLSX, PPTX.

Document statuses: "pending" → "processing" → "ready" (or "failed")

Workflow: create_collection → ingest_document → poll get_document_status until "ready" → query with rag_query.

Warning: "delete_collection" permanently removes the collection, all documents, and embeddings. Cannot be undone.
Warning: "delete_document" permanently removes the document and its embeddings. To replace, delete then re-ingest.

Common errors:
  - RESOURCE_NOT_FOUND: App, collection, or document doesn't exist
  - VALIDATION_DUPLICATE_NAME: Collection name already exists (create_collection)
  - VALIDATION_ERROR: Neither text nor storage_object_id provided (ingest_document) Endpoint: https://api.butterbase.ai/mcp
- rag_query (Query RAG Collection) - Query a RAG collection using natural language to retrieve relevant document chunks.

Performs semantic search over the collection's indexed documents and returns the most
relevant chunks ranked by similarity. Optionally synthesizes an AI-generated answer
using the retrieved context.

Parameters:
  - query: Natural language question or search phrase
  - top_k: Number of chunks to retrieve (default 5, max 20)
  - threshold: Minimum similarity score 0-1 (only return chunks above this score)
  - synthesize: If true, uses an LLM to generate a natural language answer from the
    retrieved chunks (default false — returns raw chunks only)
  - model: LLM model to use for synthesis (only relevant when synthesize is true,
    default: anthropic/claude-haiku-4.5)
  - filter: Metadata filter to narrow results (e.g. { category: "faq" })

Example — raw retrieval:
  Input: {
    app_id: "app_abc123",
    collection: "knowledge-base",
    query: "How do I reset my password?",
    top_k: 3
  }
  Output: {
    chunks: [
      {
        text: "To reset your password, go to Settings > Security > Reset Password...",
        score: 0.92,
        document_id: "doc_abc",
        metadata: { category: "faq", source: "help-center" }
      },
      ...
    ]
  }

Example — with synthesis:
  Input: {
    app_id: "app_abc123",
    collection: "knowledge-base",
    query: "How do I reset my password?",
    top_k: 5,
    synthesize: true
  }
  Output: {
    answer: "To reset your password, navigate to Settings > Security and click...",
    chunks: [ ... ],
    model: "gpt-4o-mini"
  }

Example — with metadata filter:
  Input: {
    app_id: "app_abc123",
    collection: "knowledge-base",
    query: "pricing plans",
    filter: { category: "billing", version: "2.0" }
  }

Use this to:
  - Search documentation or knowledge bases using natural language
  - Build AI-powered Q&A features for end users
  - Find relevant context for AI assistants
  - Power search bars with semantic understanding

Common errors:
  - RESOURCE_NOT_FOUND: App or collection doesn't exist
  - COLLECTION_EMPTY: No documents have been ingested yet

Idempotency: Safe to call anytime (read-only operation). Endpoint: https://api.butterbase.ai/mcp
- manage_integrations (Manage Integrations) - Manage third-party integrations for a Butterbase app (e.g., Gmail, Slack, Google Calendar).

Actions:
  - "configure":      Enable or manage a third-party integration toolkit for an app
  - "disable":        Disable a configured integration toolkit
  - "list_available": List available integrations that can be enabled (curated or full catalog)
  - "list_connected": List connected integration accounts for an app
  - "list_tools":     List available tool actions for connected integrations
  - "execute_action": Execute a tool action on a connected integration (e.g., send email, create event)

Parameters by action:
  configure:      { app_id, action: "configure", toolkit, scopes?, display_name? }
  disable:        { app_id, action: "disable", toolkit }
  list_available: { app_id, action: "list_available", search? }
  list_connected: { app_id, action: "list_connected" }
  list_tools:     { app_id, action: "list_tools", toolkit? }
  execute_action: { app_id, action: "execute_action", tool_name, params?, user_id? }

Curated toolkits (first-class support):
  gmail, google-calendar, slack, google-sheets, notion, github, hubspot, outlook, google-drive, discord

Example — configure:
  Input:  { app_id: "app_abc123", action: "configure", toolkit: "gmail", scopes: ["gmail.send"] }
  Output: { id: "...", toolkit_slug: "gmail", enabled: true }

Example — list_available:
  Input:  { app_id: "app_abc123", action: "list_available" }
  Output: { integrations: [{ toolkit: "gmail", displayName: "Gmail", curated: true }, ...] }

Example — list_connected:
  Input:  { app_id: "app_abc123", action: "list_connected" }
  Output: { connections: [{ toolkit_slug: "gmail", status: "active", connected_at: "..." }, ...] }

Example — list_tools:
  Input:  { app_id: "app_abc123", action: "list_tools", toolkit: "gmail" }
  Output: { tools: [{ name: "GMAIL_SEND_EMAIL", description: "Send an email", parameters: {...} }, ...] }

Example — execute_action (send email):
  Input:  { app_id: "app_abc123", action: "execute_action", tool_name: "GMAIL_SEND_EMAIL", params: { to: "user@example.com", subject: "Hello", body: "World" } }
  Output: { successful: true, data: { messageId: "..." } }

Common errors:
  - INTEGRATIONS_NOT_CONFIGURED: Integration API key not set
  - INTEGRATIONS_NOT_CONNECTED: User hasn't connected this integration
  - INTEGRATIONS_EXECUTION_FAILED: Integration tool execution failed
  - RESOURCE_NOT_FOUND: App doesn't exist Endpoint: https://api.butterbase.ai/mcp
- manage_auth_config (Manage Auth Config) - Manage authentication configuration for an app.

Actions:
  - "configure_auth_hook": Configure a post-authentication hook function
  - "update_jwt":           Update JWT token expiration times
  - "generate_service_key": Generate a new API key (service key)

Parameters by action:
  configure_auth_hook: { app_id, action: "configure_auth_hook", post_auth_function }
  update_jwt:          { app_id, action: "update_jwt", accessTokenTtl?, refreshTokenTtlDays? }
  generate_service_key: { action: "generate_service_key", name }

---

### configure_auth_hook

Configure a post-authentication hook function for an app.

When set, the specified Butterbase function is invoked (fire-and-forget) after
every successful auth event: OAuth login, email login, and email signup.

The hook function receives a JSON POST body:
  {
    "event": "oauth_login" | "signup" | "login",
    "user": { "id": "uuid", "email": "...", "provider": "google", "display_name": "...", "avatar_url": "..." },
    "isNewUser": true | false,
    "provider": "google" | "github" | "email" | ...
  }

The function runs as butterbase_service (RLS bypassed, ctx.user is null).
Use the payload body to identify the user.

Set post_auth_function to null to remove the hook.

Prerequisites: The function must be deployed first (use deploy_function).

Example — set hook:
  Input: { app_id: "app_abc123", action: "configure_auth_hook", post_auth_function: "on-auth" }
  Output: { auth_hook_function: "on-auth", message: "Post-auth hook set to function \"on-auth\"" }

Example — remove hook:
  Input: { app_id: "app_abc123", action: "configure_auth_hook", post_auth_function: null }
  Output: { auth_hook_function: null, message: "Post-auth hook removed" }

Common errors:
  - Function not found: Deploy the function first before configuring it as a hook.

Idempotency: Safe to call multiple times (overwrites previous setting).

---

### update_jwt

Update JWT token expiration times for access and refresh tokens.

Example:
  Input: {
    app_id: "app_abc123",
    action: "update_jwt",
    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.

---

### 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: {
    action: "generate_service_key",
    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

Example — with substrate access:
  Input: {
    action: "generate_service_key",
    name: "Agent Key",
    substrate_access: true
  }
  Output: {
    key: "bb_sk_a1b2c3d4e5f6...",
    key_id: "uuid-1234",
    prefix: "bb_sk_a1b2c3",
    name: "Agent Key",
    created_at: "2024-01-15T10:00:00Z"
  }
  Note: key works on app endpoints AND on substrate endpoints for this account. Endpoint: https://api.butterbase.ai/mcp
- manage_app (Manage App) - Manage app lifecycle: list, delete, pause/resume, get config, update access mode, secure, update CORS, clone, and find templates.

Actions:
  - "list":               List all backend apps with basic metadata (no app_id needed)
  - "delete":             Delete an app and ALL its resources permanently (IRREVERSIBLE)
  - "pause":              Pause or resume all data-plane traffic for an app (kill-switch)
  - "get_config":         Get detailed configuration for an app including CORS, storage settings, and metadata
  - "set_visibility":     Toggle the app's template visibility between "public" and "private"
  - "update_access_mode": Toggle an app's access mode between "public" and "authenticated"
  - "secure":             Lock down an app: sets access_mode to "authenticated" and optionally enables RLS user isolation
  - "update_cors":        Update CORS allowed origins to control which frontend domains can access your API
  - "clone":              Create a clone of a public app. Returns { job_id }. The dest app is a fresh empty-DB app owned by the caller. Source must be public and have a repo snapshot.
  - "get_clone_job":      Look up the status of a previously-started clone job. Returns { status, dest_app_id?, error_message? }.
  - "find_templates":     Search public templates by name, region, sort order, and pagination. Returns paginated list of public app templates.
  - "set_clone_webhook":  Set or clear a webhook that fires when someone clones this app. Pass webhook_url + webhook_secret to configure, or clear_webhook: true to remove.

Parameters by action:
  list:               { action: "list" }
  delete:             { action: "delete", app_id }
  pause:              { action: "pause", app_id, paused, reason? }
  get_config:         { action: "get_config", app_id }
  set_visibility:     { action: "set_visibility", app_id, visibility, listed? }
  update_access_mode: { action: "update_access_mode", app_id, access_mode }
  secure:             { action: "secure", app_id, tables? }
  update_cors:        { action: "update_cors", app_id, allowed_origins }
  clone:              { action: "clone", source_app_id, name?, region? }
  get_clone_job:      { action: "get_clone_job", job_id }
  find_templates:     { action: "find_templates", q?, region?, sort?, limit?, offset? }
  set_clone_webhook:  { action: "set_clone_webhook", app_id, webhook_url, webhook_secret } or { action: "set_clone_webhook", app_id, clear_webhook: true }

Common errors:
  - RESOURCE_NOT_FOUND: App doesn't exist, verify app_id with action: "list"
  - AUTH_INVALID_API_KEY: Check your API key is set correctly Endpoint: https://api.butterbase.ai/mcp
- manage_repo (Manage Repo) - Manage an app's repo (content-addressed code snapshots).

Actions:
  - "push":             Push a small set of files (≤1 MB total over MCP — for larger repos shell out to `butterbase repo push`). Files are { path, content_base64 } pairs. Server computes sha and runs prepare → upload → commit.
  - "pull_latest":      Fetch the latest snapshot's manifest (does not write files locally). Returns { snapshot_id, files: [{ path, sha256, size, downloadUrl }] } — agents fetch each downloadUrl directly.
  - "status":           Returns { app_id, pinned_snapshot_id?, remote_latest_snapshot_id?, file_count }. No working-tree comparison (the server has no working tree).
  - "list_snapshots":   List snapshot history newest-first.
  - "wipe":             Delete every snapshot and blob, then null repo_latest_snapshot. Irreversible.

Parameters by action:
  push:           { action: "push", app_id, files: [{ path, content_base64 }], message? }
  pull_latest:    { action: "pull_latest", app_id }
  status:         { action: "status", app_id }
  list_snapshots: { action: "list_snapshots", app_id }
  wipe:           { action: "wipe", app_id }

Auth matrix: writes (push, wipe) require app owner. Reads (pull_latest, status, list_snapshots) work for owner or anonymously on a public app; private+non-owner gets 404. Endpoint: https://api.butterbase.ai/mcp
- prep_and_submit_hackathon_entry (Prep and Submit Hackathon Entry) - Prep and submit your project to a Butterbase hackathon. Two-step flow.

The tool resolves which hackathon you mean from your submission_code (or, if
you've already submitted before, your existing binding). You do NOT pass a
slug — that's figured out for you. If resolution is ambiguous (no code, not
yet bound, and multiple hackathons are open), the tool returns the list of
open hackathons; ask the user which one they mean and re-run with a code.

  STEP 1 — action: "prep"
    Resolves the hackathon and returns its field_schema if exactly one is
    identified. Pass submission_code when you have it. Otherwise the tool
    will fall back to "user already bound" or "only one hackathon open".
    Use the schema to:
      • Show the user every field's "label" and "description" (never the internal "key").
      • Propose a value for each field and wait for the user's explicit
        confirmation before STEP 2.
    Do NOT auto-fill values from guesses, prior context, or app metadata without
    showing the user every field value first.

    If resolution returns multiple open hackathons with no match, present
    "open_hackathons" to the user and ask them to provide the submission_code.

  STEP 2 — action: "submit"
    Submits the confirmed "data" object. Pass hackathon_slug = matched.slug from
    the prep response so submit always targets the same hackathon prep resolved
    (matters when multiple are open). Re-running updates the existing submission
    and bumps version. Closes after the hackathon's submission_deadline.

App scoring:
  Always pass app_id on submit when you can. Hackathon scoring awards up to 50
  points for a demo URL on butterbase.dev and up to 50 additional points for
  Butterbase features measured on that specific app (database, functions,
  deployed frontend, auth users, storage, OAuth, realtime, integrations, etc.).
  Without app_id only the demo URL is scored, so entries without it almost
  always rank lower. Including app_id also ties the submission to a real app,
  which is much better for human judges.

Submission code:
  On the FIRST submission you must include the submission_code provided by
  the hackathon organizers. The same code is used to identify the hackathon
  during prep, so pass it on prep too. After the first successful submission
  the code is no longer required (the user is bound by user_id).

Recommended flow:
  1. Get the submission_code from the user (skip if they've already submitted before).
  2. Call with action: "prep", submission_code to resolve + retrieve the schema.
     • If matched is null and open_hackathons has multiple entries, ask the user
       which one and re-run with a code.
  3. Show the user each field's label / description, propose values, and wait
     for confirmation.
  4. Call with action: "submit", hackathon_slug = matched.slug from prep,
     data: {...confirmed values}, app_id, and submission_code (if provided in
     step 1).

Returns:
  prep   → { matched: { slug, name, submission_deadline, ends_at, field_schema } | null,
             match_reason, open_hackathons,
             next_call?: { tool, arguments, instructions } }
           When matched is non-null, next_call contains a fully-formed example
           submit invocation with placeholders for each field. Use it as the
           literal shape for STEP 2: replace each placeholder in arguments.data
           with the user-confirmed value, then call this tool with those args.
  submit → { submission: { id, hackathon_slug, version, data, app_id, ... }, participant_created } Endpoint: https://api.butterbase.ai/mcp
- manage_durable_objects (Manage Durable Objects) - Manage Durable Object (DO) classes for an app: register/update code, list/get/delete, view usage, and manage shared env vars.

DOs are stateful per-key actors that persist state in memory and built-in storage. Use them when
you need state for a single room/user/agent across requests (multiplayer games, chat rooms, rate
limiters, long-running agents). For stateless work, use a Function instead.

Actions:
  - "deploy":     Register or update a DO class (single TypeScript file, one exported class)
  - "list":       List all DO classes for the app
  - "get":        Get a single DO class — includes the source code and current status
  - "delete":     Delete a DO class — Cloudflare immediately deletes all instances and storage; cannot be undone
  - "usage":      Get current-month DO usage (do_requests, do_cpu_ms) — refreshed every 15 min, app-wide totals
  - "list_env":   List all env vars set on the app's DOs
  - "set_env":    Create or overwrite a single env var
  - "delete_env": Remove a single env var

Parameters by action:
  deploy:     { app_id, action: "deploy", name, code, access_mode? }
  list:       { app_id, action: "list" }
  get:        { app_id, action: "get",    name }
  delete:     { app_id, action: "delete", name }
  usage:      { app_id, action: "usage",  name }
  list_env:   { app_id, action: "list_env" }
  set_env:    { app_id, action: "set_env",    key, value }
  delete_env: { app_id, action: "delete_env", key }

Deploy constraints:
  - One TypeScript file, exporting exactly ONE class with fetch(req) and optional state.storage / state.acceptWebSocket
  - No npm imports — only `import { ... } from 'cloudflare:workers'`
  - Max 5 DO classes per app. Bundle (sum of all DO code per app) ≤ 10 MB compressed.
  - Class name in code (PascalCase) is parsed automatically; URL "name" arg is kebab-case.

URL after deploy:
  https://<subdomain>.butterbase.dev/_do/<name>/<instance-id>   (HTTP and WebSocket)

access_mode (v1 — shape check only at the dispatcher; validate inside fetch() for strong auth):
  - "public":         open to anyone
  - "authenticated":  requires Authorization that looks like an end-user JWT (default)
  - "service_key":    requires Authorization starting with "Bearer bb_sk_"

Env vars are key-value pairs injected into every DO class at runtime, scoped to all DO classes within
the app. They are separate from function env vars. After set_env / delete_env, redeploy DOs for the
change to take effect.

Common errors:
  - RESOURCE_NOT_FOUND: app_id or DO class doesn't exist
  - AUTH_INSUFFICIENT_PERMISSIONS: must be app owner or collaborator
  - VALIDATION_ERROR: env key must be alphanumeric + underscores; class code must export exactly one class

Idempotency: deploy/set_env/delete_env are safe to retry. delete is irreversible. Endpoint: https://api.butterbase.ai/mcp
- manage_ai (Manage AI) - Use the app's AI gateway: chat, embeddings, list models, read/update config, read usage.

Actions:
  - chat              { app_id, messages, model?, temperature?, max_tokens? }
                       Synchronous (no streaming). Returns the full assistant response.
                       Default model is the app's configured default, or "openai/gpt-4o-mini".
  - embed             { app_id, input (string | string[]), model?, encoding_format? }
                       Returns OpenAI-shaped embedding response.
  - list_models       { app_id }
                       Returns { models: AiModel[] } — discover what the app can call.
  - get_config        { app_id }
                       Returns { defaultModel, allowedModels, maxTokensPerRequest, ... }
  - update_config     { app_id, config }
                       Set defaultModel, allowedModels, maxTokensPerRequest (1–100000), or rotate BYOK.
  - get_usage         { app_id, startDate?, endDate? }
                       Aggregate token counts and costs over a window.
  - submit_video      { app_id, model, prompt, duration?, resolution?, aspect_ratio?, generate_audio?, seed? }
                       Submits an async video-generation job. Returns { job_id, status, polling_url }.
                       Poll the returned URL until status is "completed".
  - poll_video        { app_id, job_id }
                       Returns current { status, model, content_urls?, error?, created_at }.
                       When status is "completed", content_urls contains absolute URLs (same origin
                       as the polling_url) that the caller can fetch() directly using the same
                       Authorization header. Use this to drive your own polling loop.

This tool wraps the same /v1/:app_id/chat/completions, /embeddings, /ai/config, /ai/models,
/ai/usage routes the SDK uses. The "chat" action sets stream: false; for streamed deltas,
drive the SDK from inside a function or DO. Endpoint: https://api.butterbase.ai/mcp
- manage_billing (Manage Billing) - Manage billing, plans, usage, and spending caps for the Butterbase platform account.

This is a platform-scoped tool — it operates on the authenticated account, not on a specific app.

Actions:
  - "status":    Get current plan, usage summary, and spending cap in one call
  - "portal":    Generate a Stripe billing portal URL for payment method / invoice management
  - "topup":     Add credit to the account balance (prepaid top-up)
  - "cap_get":   Retrieve the current monthly spending cap
  - "cap_raise": Raise the monthly spending cap by a given amount
  - "plans":     List all available subscription plans with pricing
  - "usage":     Query detailed metered usage for a date range and optional meter type

Parameters by action:
  status:    { action: "status" }
  portal:    { action: "portal" }
  topup:     { action: "topup", amount: <number in USD cents> }
  cap_get:   { action: "cap_get" }
  cap_raise: { action: "cap_raise", raise_by: <number in USD cents> }
  plans:     { action: "plans" }
  usage:     { action: "usage", start_date?: "YYYY-MM-DD", end_date?: "YYYY-MM-DD", meter?: "compute" | "storage" | ... }

Examples:

  Check current plan and balance:
    Input:  { action: "status" }
    Output: { plan: "pro", balance_cents: 5000, spending_cap_cents: 20000, usage: { ... } }

  Open billing portal:
    Input:  { action: "portal" }
    Output: { url: "https://billing.stripe.com/session/..." }

  Top up $25:
    Input:  { action: "topup", amount: 2500 }
    Output: { success: true, new_balance_cents: 7500 }

  Get current spending cap:
    Input:  { action: "cap_get" }
    Output: { spending_cap_cents: 20000 }

  Raise spending cap by $50:
    Input:  { action: "cap_raise", raise_by: 5000 }
    Output: { spending_cap_cents: 25000 }

  List available plans:
    Input:  { action: "plans" }
    Output: [{ id: "free", name: "Free", ... }, { id: "pro", name: "Pro", ... }]

  Query compute usage for April 2025:
    Input:  { action: "usage", start_date: "2025-04-01", end_date: "2025-04-30", meter: "compute" }
    Output: { usage: [{ date: "2025-04-01", value: 1234 }, ...] }

Common errors:
  - AUTH_INSUFFICIENT_PERMISSIONS: Must be authenticated as the account owner
  - INSUFFICIENT_BALANCE: Account balance too low for top-up operation
  - INVALID_AMOUNT: amount / raise_by must be a positive integer (cents) Endpoint: https://api.butterbase.ai/mcp
- manage_api_keys (Manage API Keys) - List and revoke API keys (service keys) for the Butterbase platform account.

This is a platform-scoped tool — it operates on the authenticated account, not on a specific app.
To generate a new API key, use manage_auth_config (action: "generate_service_key").

Actions:
  - "list":   List all active API keys on the account (key secrets are NOT returned — only metadata)
  - "revoke": Permanently revoke a specific key by its ID

Parameters by action:
  list:   { action: "list" }
  revoke: { action: "revoke", key_id: "<uuid>" }

Examples:

  List all keys:
    Input:  { action: "list" }
    Output: [
      {
        id: "uuid-1234",
        prefix: "bb_sk_a1b2c3",
        name: "CI/CD Pipeline Key",
        created_at: "2025-01-15T10:00:00Z",
        last_used_at: "2025-04-01T08:30:00Z"
      },
      ...
    ]

  Revoke a key:
    Input:  { action: "revoke", key_id: "uuid-1234" }
    Output: { message: "API key revoked", key_id: "uuid-1234" }

Workflow — rotate a key:
  1. Call "list" to identify the key by name or prefix
  2. Call manage_auth_config (action: "generate_service_key") to create the replacement key (store the new secret immediately)
  3. Update all consumers (CI, scripts, MCP config) with the new key
  4. Call "revoke" with the old key_id to invalidate it

Common errors:
  - AUTH_INSUFFICIENT_PERMISSIONS: Must be authenticated as the account owner
  - RESOURCE_NOT_FOUND: key_id does not exist or belongs to a different account

Security notes:
  - Revocation is immediate and irreversible
  - If a key is compromised, revoke it before generating a replacement to minimise exposure window Endpoint: https://api.butterbase.ai/mcp
- move_app (Move App) - Migrate an app to a different region. Checks eligibility first, then enqueues the migration.

Parameters:
  - app_id:      The app to migrate (e.g. app_abc123)
  - dest_region: Target region slug (e.g. "us-west-2", "eu-central-1")

Returns: migration_id and initial status "queued".

Common errors:
  - 409 ineligible: App already has an in-progress migration, or is already in dest_region.
  - 404: App not found — verify app_id with manage_app (action: "list"). Endpoint: https://api.butterbase.ai/mcp
- move_app_status (Move App Status) - Get the current status of an app migration.

Parameters:
  - app_id:       The app being migrated
  - migration_id: The migration ID returned by move_app

Returns: current_step, source/dest regions, replica state, timing, and progress info.

Steps in order: requested → reserving_dest → blocking_writes → dumping_data → restoring_data →
  copying_blobs → copying_runtime → flipping_routing → setting_up_reverse_replication →
  unblocking_writes → completed Endpoint: https://api.butterbase.ai/mcp
- teardown_source_replica (Teardown Source Replica) - Tear down the source replica kept after a completed app migration.

After a move completes, the source region retains a read replica for rollback safety.
Once you're confident the migration is stable, call this to decommission it and stop
incurring source-region costs.

Parameters:
  - migration_id: The migration ID whose source replica should be torn down

Returns: { status: "torn_down" } on success.

Common errors:
  - 404: Migration not found or not owned by you.
  - 409 teardown_failed: Replica is in a state that prevents teardown. Endpoint: https://api.butterbase.ai/mcp
- list_regions (List Regions) - List the regions an app can be created or moved to.

Returns the live set of supported regions. Use this before calling init_app or
move_app to validate a region slug, or to present region choices to a user.

Returns: { regions: string[] }  e.g. { regions: ["us-east-1", "us-west-2"] }

Idempotency: Safe to call anytime (read-only operation). Endpoint: https://api.butterbase.ai/mcp
- manage_migrations (Manage Migrations) - Read and control in-flight app migrations.

This complements move_app / move_app_status / teardown_source_replica with the four
operational routes those tools don't cover.

Actions:
  - get_active             : { app_id, action: "get_active" }
                             Returns the running migration for this app, or { migration: null }.
  - abort                  : { app_id, migration_id, action: "abort" }
                             Cancel a migration that has NOT yet reached "flipping_routing".
                             Returns 409 if already past cutover; use "reverse" instead.
  - reverse                : { app_id, migration_id, action: "reverse" }
                             Roll a COMPLETED migration back to source. Only works while the
                             source replica is still retained (see list_source_replicas).
  - list_source_replicas   : { action: "list_source_replicas" }
                             Lists active retained source replicas for the caller's apps.
                             Use this before tearing down to discover what's still around.

Use list_regions + move_app to start a move; move_app_status to watch progress;
teardown_source_replica when you're confident the move is stable. Endpoint: https://api.butterbase.ai/mcp
- propose_action - Propose a write to the substrate.

Use to record an entity, decision, commitment, or learning. Phase 0: every action
is auto-approved and executed immediately; the audit ledger captures it either way.
A future phase will gate certain capabilities behind human approval.

Capabilities available in Phase 0:
- upsert_entity     — create or update a person, company, project, etc.
- update_entity     — modify fields on an existing entity by id
- record_decision   — note a decision in institutional memory
- record_commitment — record something owed to/from an entity
- record_learning   — capture what you learned from an outcome

Returns: { action_id, verdict, requires_approval, result? } Endpoint: https://api.butterbase.ai/mcp
- approve_action - Approve a substrate action that is waiting for human review.

Use after a previous propose_action returned requires_approval=true and you
(the substrate owner) want the action to proceed. Executes the action under
the same audit semantics as a normal write — the only difference is the
human-in-the-loop step is recorded.

Returns: { executed: true, result?: any } Endpoint: https://api.butterbase.ai/mcp
- reject_action - Reject a substrate action that is waiting for review.

Use to discard a proposed action without executing it. The ledger row stays
forever (status='rejected') with the supplied reason appended to policy_reason.

Returns: { rejected: true } Endpoint: https://api.butterbase.ai/mcp
- get_entity - Get a substrate entity by id. Returns 404 if not found. Endpoint: https://api.butterbase.ai/mcp
- find_entities - Search substrate entities by type and/or display-name fuzzy match.

Returns a list of matching entities for the authenticated substrate user.
Supports filtering by entity type and fuzzy name search (trigram-based). Endpoint: https://api.butterbase.ai/mcp
- search_memory - Full-text search across substrate institutional memory.

Searches decisions, commitments, and learnings using PostgreSQL full-text search
(tsvector/tsquery). Returns results ordered by relevance (ts_rank) then recency.

Kinds:
- decisions    — strategic and operational decisions recorded in substrate
- commitments  — obligations owed to/from entities
- learnings    — captured insights from outcomes Endpoint: https://api.butterbase.ai/mcp
- list_outbox - List substrate outbox rows for the current user.

Use to inspect pending/failed/dead-letter side-effect dispatches. Each row
corresponds to one capability side_effect (e.g. an email_draft webhook delivery)
queued by an executed action.

Returns: { rows: [{ id, action_id, target, status, attempts, last_error?, ... }] } Endpoint: https://api.butterbase.ai/mcp
- retry_outbox - Manually re-queue a failed or dead-letter substrate outbox row.

Resets scheduled_at to now() and status to pending so the next drainer tick
picks it up. Refuses rows already in succeeded or cancelled state.

Returns: { retried: true } Endpoint: https://api.butterbase.ai/mcp
- cancel_outbox - Cancel a substrate outbox row that has not yet completed.

Sets status to cancelled. Refuses to cancel rows currently in_flight — wait
for the drainer to finish that attempt and then cancel if still applicable.

Returns: { cancelled: true } Endpoint: https://api.butterbase.ai/mcp
- manage_attention_rules (Manage substrate attention rules) - Manage substrate attention rules.

Attention rules define conditions under which the substrate should surface, tag, or
escalate information to the user. Use this tool to list, inspect, create, update,
delete, enable, or disable rules.

Actions:
- list    — Return all attention rules for the current user.
- get     — Return a single rule by id.
- create  — Create a new rule (requires: label, trigger_type, trigger_config, action_type).
- update  — Update fields on an existing rule by id.
- delete  — Permanently delete a rule by id.
- enable  — Enable a previously disabled rule.
- disable — Disable a rule without deleting it.

Returns the API response JSON for the given action. Endpoint: https://api.butterbase.ai/mcp
- list_attention_rule_firings - List firing history for a substrate attention rule.

Returns a paginated list of firing records for the given rule in descending
fired_at order. Firings record each time the rule's condition was evaluated
and an action (or error) resulted.

Supports optional filtering by status and cursor-based pagination via the
before parameter (pass the returned next_before value as the next before
to get the subsequent page).

Returns: { firings: [{ id, rule_id, fired_at, binding_count, proposed_action_ids, status, error_message, error_payload }], next_before: string | null } Endpoint: https://api.butterbase.ai/mcp

## Resources
Not captured

## Prompts
- quickstart - Step-by-step guide to create and configure a Butterbase app Arguments: app_name

## Metadata
- Owner: io.github.butterbase-ai
- Version: 1.0.0
- Runtime: Npm
- Transports: STDIO, HTTP
- License: Not captured
- Language: Not captured
- Stars: Not captured
- Updated: Jun 1, 2026
- Source: https://registry.modelcontextprotocol.io
