Build a Supabase Database Chatbot
Build a multi-agent chatbot that queries your Supabase database in plain English.
Introduction
30 Second Summary
You've got data sitting in a database, but getting answers out means writing SQL queries every time. What if you could just ask your database a question in plain English instead?
In this project, you will build a chatbot that connects to your real Supabase database using Google ADK. You'll ask it plain English questions like "What tables do I have?" and it will query your database and return the results.
What You'll Build
A multi-agent chatbot powered by Gemini that queries your Supabase database through MCP, translating natural language into SQL.
By the end of this project, you'll have:
- A configured Google ADK project with all dependencies installed.
- A database sub-agent connected to your Supabase database via MCPToolset.
- A multi-agent architecture where a root coordinator agent delegates database questions to the specialist sub-agent.
- Secret Mission: add an Express HTTP endpoint so you can call your agent via API.
Are there any prerequisites?
This project assumes basic JavaScript or TypeScript knowledge. You'll also need a Supabase account with at least one database table and a free Gemini API key from Google AI Studio.
Set Up the ADK Project
Before building your chatbot, you need a solid project foundation. Google ADK is a TypeScript framework for building AI agents that handles orchestration, tool management, and conversation flow.
In this step, you'll scaffold the project, install the ADK libraries, and configure your credentials for connecting to Supabase.
In this step, get ready to:
- Create and initialize the project folder with ADK dependencies.
- Gather your Supabase credentials.
- Create the environment file with your API keys.
Create the project and install dependencies
ADK requires Node.js 24.13.0 or higher. Let's confirm your version before setting up the project.
- Confirm Node.js is installed by running:
node --version
✔️ I see a version number
You're all set. Make sure the version is 24.13.0 or higher. If it's lower, head to nodejs.org to download the latest LTS version.
ⓧ Command not found
Node.js isn't installed yet. Follow these steps to install it.
- Go to nodejs.org and download the latest LTS version.
- Run the installer and accept all the default options.
- Close and reopen your terminal, then run the version check again.
- Move to your Desktop and create the project folder by running these commands:
cd ~/Desktop
mkdir supabase-chatbot
cd supabase-chatbot
Your terminal prompt should now show supabase-chatbot as your current directory.
- Initialize a Node.js project and configure it for ADK by running these commands:
npm init --yes
npm pkg set type="module"
npm pkg set main="agent.ts"
What do these commands do?
- npm init --yes creates a package.json file with default settings.
- type="module" enables ES module imports (the import syntax instead of require).
- main="agent.ts" tells ADK which file contains your agent definition.
- Install the project dependencies by running:
npm install @google/adk@1.1.0 dotenv
npm install -D @google/adk-devtools
What's being installed?
- @google/adk is the Google Agent Development Kit. This is the core framework for building your AI agent.
- dotenv loads environment variables from a .env file so your credentials stay out of your code.
- @google/adk-devtools is a dev-only dependency that powers the ADK web UI for testing your agent in the browser.
You should see a message like added X packages in your terminal output. A node_modules folder now exists in your project.
Seeing install errors?
- Check your Node.js version with node --version. ADK requires 24.13.0 or higher.
- If you see permission errors on macOS or Linux, try prefixing the command with sudo.
- Make sure you're connected to the internet. npm needs to download packages from the registry.
Help me troubleshoot my npm install error
Gather your Supabase credentials
Your agent needs two values from Supabase to connect to your database. Let's track them down so they're ready for the environment file.
First, grab your Project Reference ID.
- Open your Supabase dashboard and click on your project.
- Click Project Settings in the left sidebar.
- Click General.
- Copy the Reference ID value. It looks like a short alphanumeric string (e.g. abcdefghijklmnop). Save it somewhere handy.
Check your project URL
You can also find the Reference ID in your project URL. It's the string between https:// and .supabase.co.
Next, grab your Personal Access Token (PAT).
- Go to your Supabase Access Tokens page.
- Click Generate new token.
- Enter a name like ADK Chatbot and click Generate token.
- Copy the token immediately and save it somewhere secure. You will not be able to see it again.
Keep your token safe
Your Personal Access Token grants full access to your Supabase account. Never share it publicly or commit it to a repository.
Create the environment file
ADK and the Supabase connection both read credentials from environment variables. A .env file keeps these values in one place without hardcoding them into your source code.
- Create a new file called .env inside your supabase-chatbot folder.
- Add the following content to your .env file:
GEMINI_API_KEY=your-gemini-api-key-here
SUPABASE_PROJECT_REF=your-project-ref-here
SUPABASE_ACCESS_TOKEN=your-access-token-here
What does each variable do?
- GEMINI_API_KEY authenticates your agent with the Gemini LLM. This is the key you got from Google AI Studio.
- SUPABASE_PROJECT_REF tells the MCP server which Supabase project to connect to.
- SUPABASE_ACCESS_TOKEN is the Personal Access Token that authorizes the MCP server to read your database.
- Replace your-gemini-api-key-here with your actual Gemini API key from Google AI Studio.
- Replace your-project-ref-here with the Reference ID you copied from your Supabase project settings.
- Replace your-access-token-here with the Personal Access Token you generated.
- Save the .env file.
Common .env file mistakes
- Make sure the file is named exactly .env (with the leading dot). Some editors may hide dotfiles by default.
- Do not add spaces around the = signs. KEY=value is correct. KEY = value will break.
- Do not wrap values in quotes. Paste the raw key and token directly after the equals sign.
Help me fix my .env file
Never commit your .env file
If you plan to push this project to GitHub, add .env to your .gitignore file. Committing API keys to a public repo is a common security mistake.
✔️ Awesome, I've got everything!
Make sure you've saved your .env file with all three values replaced.
ⓧ I'd like to double check the full code
Here's exactly what your .env file should look like (with your real values instead of the placeholders).
GEMINI_API_KEY=your-gemini-api-key-here
SUPABASE_PROJECT_REF=your-project-ref-here
SUPABASE_ACCESS_TOKEN=your-access-token-here
Your project is scaffolded and your credentials are in place. Next up, you'll build the database agent that connects to Supabase through MCP and translates your questions into SQL queries.
Build the Database Agent
Your project directory is ready with all the dependencies and credentials it needs. Now it's time to build the core of your chatbot.
ADK agents use tools to interact with external services. In this step, you'll wire up the Supabase MCP server as a tool and give it to a database specialist agent that translates plain English into SQL queries.
In this step, get ready to:
- Connect to your Supabase database through the Model Context Protocol.
- Build a database specialist agent that runs read-only SQL queries.
- Test the agent in the ADK web dev UI with real data from your database.
Create agent.ts with the MCPToolset
The MCPToolset class lets your ADK agent connect to any MCP server. It handles tool discovery and proxies tool calls between the agent and the server automatically.
- Create a new file called agent.ts in your supabase-chatbot/ folder.
- Add the following code to import ADK and configure the Supabase MCP connection:
import 'dotenv/config';
import { LlmAgent, MCPToolset } from '@google/adk';
// Configure the Supabase MCP connection
const supabaseMcp = new MCPToolset(
{
// Use HTTP transport for Supabase's hosted MCP server
type: 'StreamableHTTPConnectionParams',
// Connect to your specific project in read-only mode
url: `https://mcp.supabase.com/mcp?project_ref=${process.env.SUPABASE_PROJECT_REF}&read_only=true&features=database`,
transportOptions: {
requestInit: {
headers: {
// Authenticate with your Personal Access Token
Authorization: `Bearer ${process.env.SUPABASE_ACCESS_TOKEN}`,
},
},
},
},
// Only expose the two database tools the agent needs
['list_tables', 'execute_sql'],
);
What does this code do?
MCPToolset creates a live connection to Supabase's hosted MCP server at mcp.supabase.com. The URL includes three important parameters:
- project_ref scopes the connection to your specific Supabase project.
- read_only=true ensures all SQL runs as a read-only PostgreSQL user. No accidental writes.
- features=database limits the exposed tools to database operations only.
The second argument (['list_tables', 'execute_sql']) filters the available MCP tools down to just the two your agent needs. Fewer tools means less noise for the LLM when deciding which tool to call.
- Save agent.ts.
You won't see a change yet. The next section adds the agent that uses this connection.
Define the database agent and export it
An LlmAgent is the building block of any ADK application. You give it a name, a model, instructions on how to behave, and tools it can use.
- In agent.ts, add the following code below your supabaseMcp declaration:
// Define the database specialist agent
const databaseAgent = new LlmAgent({
name: 'database_agent',
model: 'gemini-flash-latest',
description:
'Handles database questions. Can list tables and run read-only SQL queries on the Supabase database.',
instruction: `You are a database specialist connected to a Supabase PostgreSQL database.
When a user asks about their data:
1. If they want to know what tables exist, use the list_tables tool.
2. If they want to query specific data, use the execute_sql tool with a valid read-only SQL SELECT statement.
3. Format results clearly. Use bullet points or simple formatting for readability.
4. Never attempt INSERT, UPDATE, DELETE, or any DDL operations. You only have read access.
5. If a query fails, explain the error and suggest a corrected query.`,
// Give the agent access to Supabase MCP tools
tools: [supabaseMcp],
});
// Export as rootAgent so ADK can discover it
export const rootAgent = databaseAgent;
What does this code do?
- name gives this agent a unique identity (database_agent) that other agents can reference later.
- model sets the Gemini model that powers the agent's reasoning.
- description tells other agents what this agent is good at. This becomes important for multi-agent delegation.
- instruction is the system prompt that shapes the agent's behavior. It tells the agent which tools to call and when.
- tools passes in your supabaseMcp connection so the agent can call list_tables and execute_sql.
Exporting as rootAgent is how ADK discovers which agent to run. The ADK dev tools look for this named export automatically.
- Save agent.ts.
You'll test the full agent in the next section.
✔️ Awesome, I've got everything!
Double check you've saved agent.ts before moving on.
ⓧ I'd like to double check the full code
Here is the complete agent.ts file so far. Make sure yours matches:
import 'dotenv/config';
import { LlmAgent, MCPToolset } from '@google/adk';
const supabaseMcp = new MCPToolset(
{
type: 'StreamableHTTPConnectionParams',
url: `https://mcp.supabase.com/mcp?project_ref=${process.env.SUPABASE_PROJECT_REF}&read_only=true&features=database`,
transportOptions: {
requestInit: {
headers: {
Authorization: `Bearer ${process.env.SUPABASE_ACCESS_TOKEN}`,
},
},
},
},
['list_tables', 'execute_sql'],
);
const databaseAgent = new LlmAgent({
name: 'database_agent',
model: 'gemini-flash-latest',
description:
'Handles database questions. Can list tables and run read-only SQL queries on the Supabase database.',
instruction: `You are a database specialist connected to a Supabase PostgreSQL database.
When a user asks about their data:
1. If they want to know what tables exist, use the list_tables tool.
2. If they want to query specific data, use the execute_sql tool with a valid read-only SQL SELECT statement.
3. Format results clearly. Use bullet points or simple formatting for readability.
4. Never attempt INSERT, UPDATE, DELETE, or any DDL operations. You only have read access.
5. If a query fails, explain the error and suggest a corrected query.`,
tools: [supabaseMcp],
});
export const rootAgent = databaseAgent;
Test the database agent
ADK ships with a web dev UI that lets you chat with your agent and inspect every tool call it makes. This is the fastest way to verify your MCP connection is working.
- Start the ADK web UI by running this command in your terminal:
npx adk web
- Open http://localhost:8000 in your browser.
- Select your agent from the dropdown in the top left corner.
Try these prompts in the chat to test different capabilities:
- Type What tables do I have? and press Enter.
- Try Describe the schema of the [table-name] table using a real table name from your database.
- Try Show me the last 5 rows from [table-name] using a table you know has data.
You should see the agent call the Supabase MCP tools and return real data from your database. The right-side trace panel shows each tool call the agent makes.
What should I see?
When you ask about tables, the agent calls list_tables through the MCP server and returns a list of your real Supabase tables. When you ask for data, it generates a SQL SELECT query, sends it through execute_sql, and formats the results for you.
Getting a connection error or empty response?
- Double check your .env file. Make sure SUPABASE_PROJECT_REF matches your project's Reference ID and SUPABASE_ACCESS_TOKEN is a valid Personal Access Token (not an anon key or service role key).
- Make sure your GEMINI_API_KEY is set correctly. The agent needs a working Gemini key to process your prompt before it can call any tools.
- If you see a timeout, your Supabase project may be paused. Open your Supabase dashboard and check the project status.
Still stuck? Ask for help here.
Why MCP instead of a direct Supabase client?
You could query Supabase directly with a database client. MCP is a standardized protocol for connecting LLMs to external services.
Using MCPToolset means your agent automatically discovers available tools, understands their schemas, and handles all the request/response plumbing. The same pattern works for any MCP-compatible service.
Your database agent is live and pulling real data from Supabase. Next up, you'll wrap it inside a coordinator agent that handles general conversation and delegates database questions to this specialist.
Add the Coordinator Agent
Your database agent is live and returning real data from Supabase. Right now though, every message goes straight to the database specialist, even a simple "Hi, how are you?"
In this step, you'll add a coordinator agent that handles general conversation on its own. When the user asks a database question, the coordinator delegates to the database agent automatically.
In this step, get ready to:
- Refactor agent.ts to use a root coordinator with sub-agent delegation.
- Test multi-agent routing in the ADK web UI.
- Refine agent instructions based on edge case testing.
Refactor into a multi-agent architecture
A multi-agent architecture splits responsibilities across specialized agents. Instead of one agent doing everything, a root coordinator decides which specialist to call based on the user's message.
- In agent.ts, scroll to the bottom of the file.
- Find this line:
export const rootAgent = databaseAgent;
- Delete that line and replace it with the following coordinator agent code:
// Root coordinator agent - delegates database questions to the specialist
export const rootAgent = new LlmAgent({
name: 'chatbot',
model: 'gemini-flash-latest',
description:
'A chatbot that can have general conversations and query a Supabase database.',
instruction: `You are a helpful chatbot assistant. You can have general conversations and help users explore their Supabase database.
When the user asks about database tables, schemas, data, or wants to run queries, delegate to the database_agent.
For general questions or conversation, respond directly.
Be concise and helpful in all responses.`,
// Register the database agent as a sub-agent for delegation
subAgents: [databaseAgent],
});
What does this code do?
This creates a new root agent called chatbot that acts as the front door for all conversations.
- subAgents: [databaseAgent] tells ADK that this coordinator can delegate work to the database specialist.
- Gemini reads the description of each sub-agent to decide when to delegate. A clear description is what makes the routing work.
- General questions ("Hi, how are you?") stay with the coordinator. Database questions get routed to database_agent.
- Save agent.ts.
Seeing a syntax error?
- Make sure you deleted the entire old line export const rootAgent = databaseAgent; before pasting the new code. Having two export const rootAgent declarations causes a duplicate identifier error.
- Check that the backtick template literal for instruction uses backticks (`) and not regular quotes.
Help me fix a syntax error in my agent.ts coordinator agent code.
✔️ Awesome, I've got everything!
Double check you've saved agent.ts before moving on.
ⓧ I'd like to double check the full code
Here is the complete agent.ts file. Compare it with yours and make sure everything matches.
import 'dotenv/config';
import { LlmAgent, MCPToolset } from '@google/adk';
const supabaseMcp = new MCPToolset(
{
type: 'StreamableHTTPConnectionParams',
url: `https://mcp.supabase.com/mcp?project_ref=${process.env.SUPABASE_PROJECT_REF}&read_only=true&features=database`,
transportOptions: {
requestInit: {
headers: {
Authorization: `Bearer ${process.env.SUPABASE_ACCESS_TOKEN}`,
},
},
},
},
['list_tables', 'execute_sql'],
);
const databaseAgent = new LlmAgent({
name: 'database_agent',
model: 'gemini-flash-latest',
description:
'Handles database questions. Can list tables and run read-only SQL queries on the Supabase database.',
instruction: `You are a database specialist connected to a Supabase PostgreSQL database.
When a user asks about their data:
1. If they want to know what tables exist, use the list_tables tool.
2. If they want to query specific data, use the execute_sql tool with a valid read-only SQL SELECT statement.
3. Format results clearly. Use bullet points or simple formatting for readability.
4. Never attempt INSERT, UPDATE, DELETE, or any DDL operations. You only have read access.
5. If a query fails, explain the error and suggest a corrected query.`,
tools: [supabaseMcp],
});
export const rootAgent = new LlmAgent({
name: 'chatbot',
model: 'gemini-flash-latest',
description:
'A chatbot that can have general conversations and query a Supabase database.',
instruction: `You are a helpful chatbot assistant. You can have general conversations and help users explore their Supabase database.
When the user asks about database tables, schemas, data, or wants to run queries, delegate to the database_agent.
For general questions or conversation, respond directly.
Be concise and helpful in all responses.`,
subAgents: [databaseAgent],
});
Test multi-agent delegation
Time to see the coordinator in action. You'll restart the ADK dev UI and test both general conversation and database queries to confirm the routing works.
- In your terminal, stop the running server by pressing Ctrl+C.
- Restart the ADK web UI by running:
npx adk web
- Open http://localhost:8000 in your browser.
- Select your agent from the dropdown in the top corner.
First, test a general conversation message that the coordinator should handle directly.
- Type Hi, how are you? and send it.
- Check the trace panel on the right side of the UI. You should see the chatbot agent responding directly without involving database_agent.
Now test a database question that should be delegated to the specialist.
- Type What tables are in my database? and send it.
- Check the trace panel again. This time you should see the chatbot agent route the message to database_agent, which then calls the Supabase MCP tools.
What should I see in the trace panel?
The trace panel shows the full chain of events for each message. For a database question, you should see the chatbot agent receive the message, decide to delegate, and then the database_agent call list_tables or execute_sql.
For a general conversation message, you should only see the chatbot agent respond. No delegation happens.
Database questions not being delegated?
- Make sure you stopped and restarted the server. The ADK dev UI does not hot-reload code changes.
- Check that subAgents: [databaseAgent] is inside your new rootAgent definition, not outside it.
- Verify that the old export const rootAgent = databaseAgent; line is fully removed. If both exports exist, only the first one takes effect.
Help me debug why my coordinator agent isn't delegating to the database agent.
Refine the agent instructions
Your multi-agent system is working. Now stress-test it with trickier prompts to see how well the routing and SQL generation hold up.
- Try a vague prompt like Tell me about my data and see if the coordinator delegates correctly.
- Ask for something that requires a JOIN across multiple tables (if your database has related tables).
- Ask the agent to delete a row and confirm it refuses. The read-only mode should prevent any mutations.
Improving delegation accuracy
If the coordinator is not delegating when it should, make the database_agent's description more specific. Gemini reads the sub-agent descriptions to decide when to delegate.
If the database agent is generating bad SQL, add more examples or constraints to its instruction field. Specific guidance like "always use LIMIT 10 for large tables" helps the LLM produce better queries.
Secret mission
Add an Express HTTP Endpoint
Your chatbot works in the ADK dev UI, but what if you wanted to call it from your own frontend or another service? In this secret mission, you will wrap your agent in an Express server so it can respond to HTTP requests. This turns your chatbot into an API that any application can talk to.
Before you go
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally with free-tier services, so there are no ongoing costs.
Resources you used:
- The supabase-chatbot/ project directory with agent.ts, .env, and node_modules/.
- The ADK web dev UI running on http://localhost:8000.
- A Supabase Personal Access Token.
- A Gemini API key from Google AI Studio.
Keep everything running
No action needed. Choose this if you are still actively building or want to keep experimenting with your chatbot.
- Your project files stay in the supabase-chatbot/ folder on your Desktop.
- Your .env file keeps your credentials ready for the next session.
- Your Supabase Personal Access Token and Gemini API key remain active.
Pause - I'll come back to this later
Shut down the running dev server to free up your terminal. Your project files and credentials stay intact for next time.
- Stop the ADK web dev UI by pressing Ctrl+C in the terminal where it is running.
- Your project files in supabase-chatbot/ remain on your Desktop.
- When you come back, open a terminal, navigate to your supabase-chatbot/ folder, and run npx adk web to start the dev UI again.
Delete - I don't want to use this again
Remove all project files and revoke your credentials so nothing is left behind.
- Stop the ADK web dev UI by pressing Ctrl+C in the terminal.
- Delete the supabase-chatbot/ folder from your Desktop.
- Revoke your Supabase Personal Access Token by going to your Supabase dashboard, clicking your profile icon, selecting Access Tokens, and deleting the token you created.
- Optionally, revoke your Gemini API key in Google AI Studio if you no longer need it.
Mission accomplished
Nice Work!
You just built a multi-agent chatbot that queries your real Supabase database through plain English. That is a production-grade pattern used by teams building AI agents that interact with live data.
Here's what you learned:
- Connected a Google ADK agent to a live Supabase database using MCPToolset with read-only access and token authentication.
- Built a database specialist agent that translates natural language into SQL queries and returns real results.
- Created a multi-agent architecture where a coordinator agent delegates database questions to a specialist sub-agent.
- Secret Mission: Added an Express HTTP endpoint to serve your chatbot as an API.
Ready to quiz yourself?