# An AI agent for your Supabase app

To add an AI agent to an app whose users sign in with Supabase Auth, create the agent from a chat with the Vatio MCP server, then build it into the code with `npx @vatio-ai/skills` and `/vatio-init` ([steps below](#add-the-agent-in-two-steps)). A Supabase Edge Function reads the user's Supabase session and signs a short token with your own key, which the widget passes to Vatio. The agent's private tools call other Edge Functions of yours, which verify that token and read only the signed-in user's rows. Nothing reaches your users until you publish.

The example is a dog-walking app where signed-in owners ask about their pets' walks.

## Add the agent in two steps

**Step 1, from any chat.** Add the remote MCP server `https://vatio.ai/mcp` as a connector in Claude, ChatGPT or Cursor, sign in with a Vatio account and ask for the agent. It is created in a preview you can try; nothing reaches your users until you publish ([MCP](https://vatio.ai/docs/mcp)).

**Step 2, build it into the Supabase app.** In the project, run:

```text
npx @vatio-ai/skills
/vatio-init
```

If you built the app in Lovable or Bolt, connect it to GitHub and open it in Claude Code or Cursor first. Run the first line in a terminal at the root of your app's project, and the second in Claude Code (in Cursor or Codex, ask it to use the vatio-init skill). `/vatio-init` looks at what already exists, then writes the agent and pushes a preview. The first time it pushes, your browser opens to sign in with email, Google or GitHub. Then ask your coding agent to put the widget where the app starts. You can say what you want after `/vatio-init`:

```text
/vatio-init an agent that answers from our FAQ and tells signed-in owners when their pets' next walks are, from our Supabase tables
```

After launch, `/vatio-improve` is the daily routine ([After launch](#after-launch)).

## What vatio-init finds in a Supabase project

vatio-init reads the project and tells you what it found and what it recommends before writing anything. Then it asks only what the code doesn't answer, such as what the agent must never promise and when a person takes over.

- **How your users sign in.** A Supabase client and the project URL beside it: the project URL in a `.env`, or the `src/integrations/supabase/client.ts` Lovable generates. That means an Edge Function that signs the Vatio token.
- **What the agent could call.** Your Edge Functions as `/functions/v1/<name>`, or routes of your own server, which read your tables and database functions for the signed-in user.
- **The docs it could use.** FAQ and help pages, policies and your README, as knowledge entries the agent searches before it answers. Vatio doesn't crawl your site.

## Put the agent in your app

Ask your coding agent to add this code; it is shown here so you can review it.

Mount the widget once, where your app starts. The user's token comes from your backend (next section):

```ts
// src/main.tsx
import { mountVatioWidget } from "@vatio-ai/widget";

mountVatioWidget({ workspace: "pawpal", token: import.meta.env.VITE_VATIO_TOKEN });
```

The script tag works the same way, before `</body>` in `index.html`, with `data-workspace` and `data-token` ([Web widget](https://vatio.ai/docs/channels/widget)). To tell the agent who is signed in, pass the visitor token as `data-visitor-token` or with `identify()` ([Authentication](https://vatio.ai/docs/authentication/)).

The publishable key (`vatpub_…`) is meant for page source, and it picks the environment: preview's goes in `.env.development`, live's in `.env.production`. Each works only from its own origins, exact `scheme://host[:port]` values with no wildcards:

```bash
vatio keys create publishable --env preview --label dev --origin http://localhost:5173
vatio keys create publishable --env live --label web --origin https://pawpal.example
```

Lovable, Replit, Bolt, v0 and Base44 domains, preview and published, are accepted without listing them; once the app has a domain of its own, list it. Change a key's origins with `vatio keys origins PREFIX URL...` or in **Settings → API keys** in the inbox. A page whose origin isn't on the key gets `origin_not_allowed`, and the browser console names the origin to add. Origins live on the key, never in `vatio.yml`.

## Let it act for signed-in users

Vatio holds only your public key, so it can check a token but never mint one. Your coding agent sets it up with `vatio auth --new-key`, which writes `identity.pub` into the workspace (`auth: public_key: identity.pub` in `vatio.yml`) and `identity.pem`, the private key. Store `identity.pem` as a Supabase secret for your Edge Functions, never in `vatio secrets` ([Authentication](https://vatio.ai/docs/authentication/)).

**Signing the token.** Add an Edge Function, say `vatio-token`, that reads the user's Supabase session (the `Authorization` header supabase-js sends), and signs a short RS256 token with `sub` set to the user id and `aud` set to your workspace slug. The app calls it once the user is signed in and hands the result to the widget. Mechanics, claims and refresh are in [Authentication](https://vatio.ai/docs/authentication/) and [Sessions and channels](https://vatio.ai/docs/authentication/sessions).

**Reading the user's rows.** A tool marked `access: private` forwards that token as `Authorization: Bearer $auth.token` to another Edge Function. The function verifies it against `identity.pub` and queries your tables for `$auth.subject`, the user id claim. The token is not a Supabase session, so turn Verify JWT off on the functions Vatio calls and do the check yourself. The check that matters now lives in the function: filter every query by the verified `sub`.

```ts
// supabase/functions/my-pets/index.ts: after verifying the token
const { data } = await admin
  .from("pets")
  .select("name,breed,next_walk")
  .eq("owner_id", sub);
```

Keep the service-role key inside the function, never in a tool. A private tool cannot take a user id as a parameter either, and the deploy fails if one tries, because the model fills parameters and could be talked into filling in someone else's.

## What your coding agent writes

These files live in your project, so you can review them like the rest of your code: an excerpt of `vatio.yml`, and the tool.

```yaml
# vatio.yml
workspace: pawpal
auth:
  public_key: identity.pub
agent:
  instructions: |
    You help owners who use Pawpal. Answer questions about walks, prices and
    cancellations from knowledge_lookup. For an owner's own pets and walks,
    call my_pets. Never confirm a walk my_pets did not return.
  tools: [knowledge_lookup, my_pets]
  knowledge: [faq]

# tools/my_pets.yml
description: The signed-in owner's pets, with each one's next walk.
when_to_use: The owner asks about their own pets or their walks.
access: private
request:
  method: GET
  base_url: https://abcdefghijklmnopqrst.supabase.co
  path: /functions/v1/my-pets
  headers:
    Authorization: "Bearer $auth.token"
respond:
  data: { pets: "$" }
```

To act as well as read, the same kind of file calls `POST /functions/v1/book-walk` with the same header. The tool has no owner id: the function reads it from the verified token, and returns nobody else's rows.

## Try it, then publish

vatio-init gives you a link to the inbox, with **Try the agent** open, where you try the preview on WhatsApp, Instagram or a web chat. The web chat is a visitor who isn't signed in, so it only reaches public tools. To try `my_pets`, run your dev build with preview's widget token and sign in as a test user. Preview can call a staging project's functions while live calls production; sign in to the one preview calls. Ask "when is Luna's next walk?", then for the pets of another owner by email: it should find nothing but your own. In the inbox, each reply shows the tool calls behind it, with their arguments and what your function answered.

When the agent does what you want, tell your coding agent to publish. Nothing reaches your users until you do.

## After launch

Your team reads real conversations in the inbox. On a wrong reply, **Report this reply** asks what should have happened, and that note becomes a test. Once a day, run `/vatio-improve` in your project: your coding agent fixes the cause where it lives (the instructions, a knowledge entry, a tool or an Edge Function) and pushes it to preview, and you review the results and approve the update ([Improve](https://vatio.ai/docs/improve)). The same agent answers on WhatsApp and Instagram ([Pricing](https://vatio.ai/pricing)). There, a `sign_in_url` under `auth:` gives private tools a **Sign in** button that opens your login page, with the widget on it, and the conversation picks up once the user has signed in ([Sessions and channels](https://vatio.ai/docs/authentication/sessions)).

## Frequently asked questions

### How do I add an AI agent to an app built on Supabase?

Step 1: add the MCP server https://vatio.ai/mcp as a connector in Claude, ChatGPT or Cursor, sign in and ask for the agent; it is created in a preview. Step 2: in the app's project run npx @vatio-ai/skills, then /vatio-init in Claude Code (in Cursor or Codex, ask it to use that skill). Your coding agent adds an Edge Function that signs the user's token and puts the widget in your app, with a publishable key that allows its origins.

### How does the agent know who is signed in?

A Supabase Edge Function reads the user's Supabase session and signs a short RS256 token with the private key from vatio auth --new-key; the app passes it to the widget as the visitor token. Vatio holds only the public key, so it can check the token but never create one. Details are in Vatio's Authentication docs.

### Can the agent read another user's rows in my Supabase database?

Not when its tools are private and your Edge Functions filter by the verified token. A private tool forwards the token, your function verifies it and reads the user from its sub, and a private tool cannot take a user id as a parameter. Keep the service-role key inside the function, never in a tool.

### Does this work for an app I built in Lovable?

Yes. Connect the Lovable project to GitHub and open it in Claude Code or Cursor. Your coding agent adds the Edge Function that signs the token, and every publishable key accepts Lovable's preview and published domains without listing them.

## Related guides

- [Build an AI agent for your app](https://vatio.ai/docs/guides/build-an-ai-agent): The two-step path with your coding agent, from the first chat to publishing.
- [An AI chat for your web app or site](https://vatio.ai/docs/guides/ai-chat-widget-for-your-website): One script tag puts the agent in your web app or site, themed to your brand and able to recognize signed-in users.
- [React apps](https://vatio.ai/docs/guides/ai-agent-for-react-apps): The widget mounted in an effect; your backend signs the user's token and your API verifies it.
- [Give your Lovable app an agent](https://vatio.ai/docs/guides/ai-agent-for-lovable-apps): Open your Lovable project in Claude Code or Cursor, run the two lines and connect your users.
