Building a MCP-Powered Task Manager Agent with Agno and Supabase: A Step-by-Step Guide

How I built a stateful, persistent AI agent to manage tasks using Agno and Supabase's Model Context Protocol Server

Why Connect an AI Agent to Supabase?

After building a basic task manager with Agno and SQLite, I wanted something more powerful - an agent that could directly interface with my cloud database. By connecting Agno to Supabase, I created an agent that not only understands natural language but can also perform real database operations, all while maintaining a clean separation between my data and the agent's logic.

In this post, I'll walk you through how I built a Supabase-connected task manager using Agno's MCP (Model Context Protocol) integration. We'll cover everything from setting up the environment to executing real SQL operations through natural language. By the end, you'll understand how to create AI agents that can safely interact with your production databases.

Step 1: Understanding the Architecture

Before diving into code, let's understand the architecture:

This architecture allows us to:
  • Take natural language input from users
  • Process it with an AI agent
  • Connect to Supabase via MCP tools
  • Execute database operations
  • Return formatted results to users

The key innovation here is the MCP layer, which standardizes how AI models interact with external services like Supabase.

Step 2: Setting Up Your Environment

First, we need to install the necessary packages and set up our environment variables:

pip install agno openai mcp
We'll need two essential environment variables:
  • SUPABASE_ACCESS_TOKEN: For authenticating with Supabase
  • OPENAI_API_KEY: For accessing the OpenAI models

Let's make sure they're available by adding validation at the start of our script:
import os
import asyncio
from textwrap import dedent
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.mcp import MCPTools

# Check if required environment variables are available
if "SUPABASE_ACCESS_TOKEN" not in os.environ:
    raise ValueError("SUPABASE_ACCESS_TOKEN environment variable is not set. Please set it and try again.")

if "OPENAI_API_KEY" not in os.environ:
    raise ValueError("OPENAI_API_KEY environment variable is not set. Please set it and try again.")

Step 3: Understanding the Model Context Protocol (MCP)

 

The heart of our implementation is the Model Context Protocol, which allows AI agents to interact with external services through a standardized interface. MCP defines a set of tools that an agent can use to connect to services like Supabase.

Here's how we initialize the MCP tools for Supabase:
async def main():
    # Using async context manager as specified in the docs
    command = f"npx -y @supabase/mcp-server-supabase@latest --access-token {os.environ['SUPABASE_ACCESS_TOKEN']}"
    async with MCPTools(command) as mcp_tools:
        # Create agent with these tools
        # ...
Key Technical Insight: Notice how we're using the async context manager pattern (async with). This is crucial because:
  1. It properly initializes the MCP session at the beginning
  2. It ensures resources are properly cleaned up when we're done
  3. It maintains the connection throughout the agent's lifetime

The context manager pattern avoids common pitfalls like dangling connections and resource leaks.

Step 4: Creating an Intelligent Agent with Clear Instructions

Now let's create our agent with detailed instructions on how to use Supabase:

agent = Agent(
    name="Agno Supabase Task Manager",
    role="Task manager agent that stores tasks in Supabase via MCP.",
    model=OpenAIChat(id="gpt-4o-mini"),
    tools=[mcp_tools],
    instructions=dedent("""
        You are a task manager agent that uses Supabase MCP to store tasks. Each task has:
        - description (text)
        - completion (percent)
        - responsible (person)
        - due_date (YYYY-MM-DD)
        
        Follow these exact steps to interact with Supabase:
        
        1. First, use `list_projects` to find all available Supabase projects
        
        2. Look for the project named "AgnoTaskList" and get its project ID (ref)
        
        3. For all database operations, specify the project ID explicitly:
           - To check tables: use `list_tables` with the project ID and schema="public"
           - To create table: use `apply_migration` with the project ID
           - To manipulate data: use `execute_sql` with the project ID
        
        4. If the TaskList table doesn't exist, create it with `apply_migration`:
           ```sql
           CREATE TABLE IF NOT EXISTS TaskList (
               id SERIAL PRIMARY KEY,
               description TEXT NOT NULL,
               completion INTEGER NOT NULL,
               responsible TEXT NOT NULL,
               due_date DATE NOT NULL
           );
           ```
           
        5. For regular operations, use `execute_sql`:
           - To add tasks: Use the current date (CURRENT_DATE) as the default due date if none is specified
           - To list tasks: `SELECT * FROM TaskList;`
           - To update tasks: `UPDATE TaskList SET column_name = new_value WHERE condition RETURNING *;`
           - To delete tasks: `DELETE FROM TaskList WHERE condition RETURNING *;`
        
        IMPORTANT RULES:
        - Always include the project ID in ALL database operations
        - If no due date is specified when adding a task, use the current date (CURRENT_DATE)
        - Show the actual results returned from database operations
        - When listing tasks, present them in a clear, formatted table
        - If any tool call fails, show the error message and try to troubleshoot it
    """),
    show_tool_calls=True,
    add_state_in_messages=True,
    markdown=True,
)
Why These Instructions Matter: The instructions are a crucial part of our implementation for several reasons:
  1. Sequential Workflow: The numbered steps guide the agent through a logical sequence of operations, ensuring it doesn't try to execute SQL before finding the project ID.
  2. Database Schema Definition: We explicitly define our table structure, making sure the agent creates a consistent schema.
  3. Operation Templates: We provide templates for common operations, helping the agent generate valid SQL.
  4. Default Values: We specify defaults like using the current date when no due date is provided.
  5. Error Handling: We instruct the agent to show and troubleshoot errors, making debugging easier.

These detailed instructions illustrate how agents can be programmed with structured workflows despite using a natural language interface.

Step 5: Creating a Clean User Interface

The user interface is simple but effective:

def print_welcome():
    print(f'''
Agno Task Manager Agent (Supabase MCP)
--------------------------------------
You are now chatting with an AI agent that can manage your tasks in Supabase.

You can:
- Add new tasks (e.g. "Add a task for John to finish the report by Friday, 80% done.")
- Update tasks (e.g. "Change the due date for the report task to next Monday.")
- Remove tasks (e.g. "Delete the task about the report.")
- List all tasks (e.g. "Show me all my tasks.")

Just type your requests in natural language. Type 'exit' or 'quit' to stop.
''')

# In the main loop:
while True:
    user_input = input("\nYou: ").strip()
    if user_input.lower() in {"exit", "quit"}:
        print("Exiting. Your tasks are saved in Supabase!")
        break
    
    try:
        await agent.aprint_response(user_input, stream=True)
    except Exception as e:
        print(f"\n❌ Error during agent response: {str(e)}")

Notice that we use await agent.aprint_response() instead of the synchronous version. This is because our entire application uses the asyncio pattern, which is essential when working with MCP tools.

Step 6: The Complete Async Implementation

Here's the complete implementation, with special attention to async patterns:

import os
import asyncio
from textwrap import dedent
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.mcp import MCPTools

# Check if required environment variables are available
if "SUPABASE_ACCESS_TOKEN" not in os.environ:
    raise ValueError("SUPABASE_ACCESS_TOKEN environment variable is not set. Please set it and try again.")

if "OPENAI_API_KEY" not in os.environ:
    raise ValueError("OPENAI_API_KEY environment variable is not set. Please set it and try again.")

def print_welcome():
    print(f'''
Agno Task Manager Agent (Supabase MCP)
--------------------------------------
You are now chatting with an AI agent that can manage your tasks in Supabase.

You can:
- Add new tasks (e.g. "Add a task for John to finish the report by Friday, 80% done.")
- Update tasks (e.g. "Change the due date for the report task to next Monday.")
- Remove tasks (e.g. "Delete the task about the report.")
- List all tasks (e.g. "Show me all my tasks.")

Just type your requests in natural language. Type 'exit' or 'quit' to stop.
''')

async def main():
    """Main async function to run the agent."""
    print_welcome()
    
    try:
        # Initialize MCP tools using the context manager pattern
        command = f"npx -y @supabase/mcp-server-supabase@latest --access-token {os.environ['SUPABASE_ACCESS_TOKEN']}"
        async with MCPTools(command) as mcp_tools:
            # Create the agent with initialized tools
            agent = Agent(
                name="Agno Supabase Task Manager",
                role="Task manager agent that stores tasks in Supabase via MCP.",
                model=OpenAIChat(id="gpt-4o-mini"),
                tools=[mcp_tools],
                instructions=dedent("""
                    You are a task manager agent that uses Supabase MCP to store tasks. Each task has:
                    - description (text)
                    - completion (percent)
                    - responsible (person)
                    - due_date (YYYY-MM-DD)
                    
                    Follow these exact steps to interact with Supabase:
                    
                    1. First, use `list_projects` to find all available Supabase projects
                    
                    2. Look for the project named "AgnoTaskList" and get its project ID (ref)
                    
                    3. For all database operations, specify the project ID explicitly:
                       - To check tables: use `list_tables` with the project ID and schema="public"
                       - To create table: use `apply_migration` with the project ID
                       - To manipulate data: use `execute_sql` with the project ID
                    
                    4. If the TaskList table doesn't exist, create it with `apply_migration`:
                       ```sql
                       CREATE TABLE IF NOT EXISTS TaskList (
                           id SERIAL PRIMARY KEY,
                           description TEXT NOT NULL,
                           completion INTEGER NOT NULL,
                           responsible TEXT NOT NULL,
                           due_date DATE NOT NULL
                       );
                       ```
                       
                    5. For regular operations, use `execute_sql`:
                       - To add tasks: Use the current date (CURRENT_DATE) as the default due date if none is specified
                       - To list tasks: `SELECT * FROM TaskList;`
                       - To update tasks: `UPDATE TaskList SET column_name = new_value WHERE condition RETURNING *;`
                       - To delete tasks: `DELETE FROM TaskList WHERE condition RETURNING *;`
                    
                    IMPORTANT RULES:
                    - Always include the project ID in ALL database operations
                    - If no due date is specified when adding a task, use the current date (CURRENT_DATE)
                    - Show the actual results returned from database operations
                    - When listing tasks, present them in a clear, formatted table
                    - If any tool call fails, show the error message and try to troubleshoot it
                """),
                show_tool_calls=True,
                add_state_in_messages=True,
                markdown=True,
            )
            
            # Interact with the agent
            while True:
                user_input = input("\nYou: ").strip()
                if user_input.lower() in {"exit", "quit"}:
                    print("Exiting. Your tasks are saved in Supabase!")
                    break
                
                try:
                    await agent.aprint_response(user_input, stream=True)
                except Exception as e:
                    print(f"\n❌ Error during agent response: {str(e)}")
    
    except Exception as e:
        print(f"\n❌ ERROR: {str(e)}")

if __name__ == "__main__":
    try:
        # Run the async main function
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nOperation interrupted by user. Exiting...")
    except Exception as e:
        print(f"\n❌ ERROR: {str(e)}")
Deep Dive: Async Execution Model
Let's analyze the async execution pattern in our code:
  1. Entry Pointasyncio.run(main()) is our entry point, which sets up the event loop.
  2. Async Context Managerasync with MCPTools(...) as mcp_tools: ensures proper initialization and cleanup of resources.
  3. Agent Responseawait agent.aprint_response(...) pauses execution while waiting for the agent to process and respond.

This async structure is crucial for several reasons:
  • It allows non-blocking I/O operations when communicating with Supabase
  • It enables streaming responses from the agent
  • It ensures proper resource cleanup even if exceptions occur

By using Python's asyncio, we create a responsive application that can handle multiple tasks without blocking.

What's Happening Behind the Scenes?

 

When a user enters a natural language command like "Add a task for John to finish the report by Friday, 80% done", here's the exact sequence of operations:
  1. Language Understanding: The agent parses the request using GPT-4o-mini.
  2. Project Discovery: The agent calls list_projects using MCP to find all Supabase projects.
  3. Project Selection: It identifies the "AgnoTaskList" project and gets its ID.
  4. Table Verification: It calls list_tables to check if the TaskList table exists.
  5. Table Creation: If needed, it uses apply_migration to create the table with the schema we defined.
  6. SQL Generation: It generates an INSERT SQL statement with the task details.
  7. SQL Execution: It calls execute_sql to run the INSERT statement in Supabase.
  8. Result Formatting: It formats the database response into a user-friendly message.

This entire sequence happens automatically thanks to our carefully crafted instructions.

Final Thoughts: What Makes This Implementation Special

 

This Supabase-powered AI agent task manager demonstrates several powerful concepts:
  1. Database-Backed Agents with MCP: Your agent can directly interact with production databases.
  2. Structured Workflows: Despite using natural language, the agent follows a structured process.
  3. Protocol-Based Integration: MCP provides a standard way for AI to interact with external services.
  4. Error Resilience: The implementation includes proper error handling at all levels.

By connecting Agno to Supabase via MCP, we've created a truly persistent task manager that can safely operate on cloud databases through natural language. This opens up endless possibilities for AI-powered applications that interact with your existing data infrastructure.
I hope this guide helps you build your own Supabase-connected AI agents! If you have questions or want to share your own creations, let me know in the comments.