14. MCP Toolbox for Databases - PostgreSQL

This blog is part of the ADK Masterclass - Hands-On Series. We'll explore MCP Toolbox for Databases - Google's open-source enterprise-grade solution for building AI tools with database access, enhanced security, and observability.

MCP Toolbox acts as an intermediary layer between our ADK agents and databases. It manages connection pooling, authentication, and tool definitions centrally - so we can update database tools without redeploying our agents.

View Code on GitHub

Table of Contents

1. What is MCP Toolbox?

graph LR User[User Query] --> Agent[ADK Agent] Agent --> |ToolboxSyncClient| Toolbox[MCP Toolbox Server] subgraph ControlPlane["Control Plane"] Toolbox Tools[tools.yaml] Tools --> Toolbox end Toolbox --> |Connection Pool| PostgreSQL[(PostgreSQL)] PostgreSQL --> |Results| Toolbox Toolbox --> Agent Agent --> Response[Response] style Agent fill:#e3f2fd,stroke:#1565c0,stroke-width:2px style Toolbox fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style PostgreSQL fill:#fff9c4,stroke:#fbc02d,stroke-width:2px style ControlPlane fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1px

MCP Toolbox sits between our ADK agent and database, managing connections, security, and tool definitions in one place.

MCP Toolbox for Databases is Google's open-source MCP server designed for database access. It handles the complexity of database connections so our agents can focus on answering questions.

Unlike direct database connections or basic MCP servers, MCP Toolbox handles complex enterprise requirements:

  • Connection pooling and management - Efficient connection handling with automatic reuse
  • Authentication and security - Multiple auth patterns including authenticated parameters
  • Centralized tool management - Store and update tools without redeploying agents
  • Tool sharing - Share tools between multiple agents and applications
  • Observability - Built-in monitoring and tracing via OpenTelemetry
  • Performance optimization - Dynamic reloading and efficient query execution

This architecture enables building production-ready data-aware agents for business intelligence, reporting, data analysis, and automated database management with enterprise-grade security and observability.

Supported Data Sources

While this tutorial focuses on PostgreSQL, MCP Toolbox supports a wide range of databases:

  • Google Cloud: BigQuery, AlloyDB, Spanner, Cloud SQL, Firestore, Bigtable, Dataplex
  • Relational: PostgreSQL, MySQL, SQL Server, ClickHouse, SQLite, TiDB
  • NoSQL: MongoDB, Couchbase, Redis, Cassandra
  • Graph: Neo4j, Dgraph
  • Data Platforms: Looker, Trino

See the full list of supported data sources in the official documentation.

2. Tutorial

We'll build an ADK agent that interacts with a PostgreSQL database using MCP Toolbox. The agent will query data, analyze trends, and generate reports from database content.

Prerequisites

  • Python 3.11 or later
  • Google ADK installed (see Getting Started)
  • Google API key from Google AI Studio
  • PostgreSQL 12+ installed locally or remote access to a PostgreSQL instance
  • MCP Toolbox for Databases installed and running (see configuration section)
  • psql or a PostgreSQL client for initial setup
💡 Tip: For local development, you can use Docker to quickly set up PostgreSQL: docker run --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:15

2.1. Setting up PostgreSQL

First, let's create a sample database for our agent to work with. We'll create an e-commerce database with products, customers, and orders.

Step 1: Create Database and Tables

Connect to PostgreSQL and create our sample database:

-- Connect to PostgreSQL
psql -U postgres

-- Create database
CREATE DATABASE ecommerce_db;
\c ecommerce_db

-- Create tables
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    category VARCHAR(100),
    price DECIMAL(10,2),
    stock_quantity INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE,
    join_date DATE
);

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER REFERENCES customers(id),
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    total_amount DECIMAL(10,2),
    status VARCHAR(50)
);

CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id),
    product_id INTEGER REFERENCES products(id),
    quantity INTEGER,
    unit_price DECIMAL(10,2)
);

-- Insert sample data
INSERT INTO products (name, category, price, stock_quantity) VALUES
    ('Laptop Pro', 'Electronics', 1299.99, 50),
    ('Wireless Mouse', 'Electronics', 29.99, 200),
    ('Office Chair', 'Furniture', 399.99, 30),
    ('USB-C Hub', 'Electronics', 49.99, 150),
    ('Monitor 4K', 'Electronics', 599.99, 40);

INSERT INTO customers (name, email, join_date) VALUES
    ('Alice Johnson', '[email protected]', '2024-01-15'),
    ('Bob Smith', '[email protected]', '2024-02-20'),
    ('Carol Davis', '[email protected]', '2024-03-10');

INSERT INTO orders (customer_id, total_amount, status) VALUES
    (1, 1329.98, 'completed'),
    (2, 429.99, 'pending'),
    (1, 649.98, 'completed'),
    (3, 399.99, 'completed');

INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES
    (1, 1, 1, 1299.99),
    (1, 2, 1, 29.99),
    (2, 3, 1, 399.99),
    (3, 4, 1, 49.99),
    (3, 2, 2, 29.99),
    (4, 3, 1, 399.99);

Step 2: Create Database User (Optional)

For security best practices, create a dedicated user for our agent:

-- Create user with limited privileges
CREATE USER adk_agent WITH PASSWORD 'secure_password';

-- Grant read access to our tables
GRANT CONNECT ON DATABASE ecommerce_db TO adk_agent;
GRANT USAGE ON SCHEMA public TO adk_agent;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO adk_agent;

-- For write operations (optional, uncomment if needed)
-- GRANT INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO adk_agent;

2.2. Installing and Configuring MCP Toolbox

Step 1: Install the Python SDK

pip install toolbox-core

Step 2: Install the Toolbox Server

Download and install the Toolbox server. Check the releases page for the latest version:

# Option A: Homebrew (macOS/Linux)
brew install mcp-toolbox

# Option B: Binary download (Linux AMD64)
export VERSION=0.21.0  # Check releases page for latest
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox

# Option C: Binary download (macOS Apple Silicon)
export VERSION=0.21.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/arm64/toolbox
chmod +x toolbox

# Option D: Binary download (macOS Intel)
export VERSION=0.21.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/amd64/toolbox
chmod +x toolbox

Step 3: Configure Database Tools

Create a tools.yaml file to define our PostgreSQL connection and tools:

# MCP Toolbox for Databases - PostgreSQL Configuration
# See: https://googleapis.github.io/genai-toolbox/getting-started/configure/

sources:
  ecommerce_db:
    kind: postgres
    host: ${DB_HOST:localhost}
    port: ${DB_PORT:5432}
    database: ${DB_NAME:ecommerce_db}
    user: ${DB_USER:postgres}
    password: ${DB_PASSWORD:postgres}

tools:
  get_products:
    kind: postgres-sql
    source: ecommerce_db
    description: "Retrieve all products from the catalog with pricing and inventory"
    statement: SELECT * FROM products ORDER BY name

  search_products:
    kind: postgres-sql
    source: ecommerce_db
    description: "Search products by category"
    parameters:
      - name: category
        type: string
        description: "Product category to filter by (e.g., 'Electronics', 'Furniture')"
    statement: SELECT * FROM products WHERE category ILIKE '%' || $1 || '%'

  get_customers:
    kind: postgres-sql
    source: ecommerce_db
    description: "Get all customers"
    statement: SELECT id, name, email FROM customers ORDER BY name

  get_inventory_value:
    kind: postgres-sql
    source: ecommerce_db
    description: "Calculate total inventory value"
    statement: |
      SELECT 
        COUNT(*) as total_products,
        SUM(stock_quantity) as total_items,
        SUM(price * stock_quantity) as total_value
      FROM products

# Toolsets group related tools together
toolsets:
  ecommerce:
    - get_products
    - search_products
    - get_customers
    - get_inventory_value

Step 4: Start the Toolbox Server

# Run the Toolbox server with our configuration
./toolbox --tools-file "tools.yaml" --port 5050

# With the UI enabled (recommended for development):
./toolbox --tools-file "tools.yaml" --port 5050 --ui

# If installed via Homebrew, toolbox is in the PATH:
toolbox --tools-file "tools.yaml" --port 5050 --ui

# The server will start at http://localhost:5050
# With --ui flag, access the web interface at http://localhost:5050/ui

# Note: Use port 5050 or higher on macOS (port 5000 is used by AirPlay)

â„šī¸ Dynamic Reloading

Toolbox enables dynamic reloading by default - changes to the tools.yaml are automatically picked up without restarting the server. To disable this behavior, use the --disable-reload flag.

💡 Toolbox UI

The --ui flag enables a built-in web interface at http://localhost:5050/ui for testing our tools. We can:

  • View all available tools and their descriptions
  • Test tools with different parameters
  • Verify configurations before using them in agents

Learn more in the Toolbox UI documentation.

2.3. Building a Data Query Agent

Now let's create our agent using the toolbox-core SDK. ADK provides a synchronous client (ToolboxSyncClient) that makes integration straightforward:

Step 1: Create the Agent File

Create agent.py:

import os
from google.adk.agents import Agent
from toolbox_core import ToolboxSyncClient
from dotenv import load_dotenv

load_dotenv()

# Connect to the MCP Toolbox server
TOOLBOX_URL = os.getenv("TOOLBOX_URL", "http://localhost:5050")

try:
    toolbox_client = ToolboxSyncClient(TOOLBOX_URL)
    db_tools = toolbox_client.load_toolset("ecommerce")
    print(f"Loaded {len(db_tools)} tools from MCP Toolbox")
except Exception as e:
    print(f"WARNING: Could not connect to MCP Toolbox at {TOOLBOX_URL}")
    print(f"Error: {e}")
    print("Make sure the Toolbox server is running: toolbox --tools-file ../tools.yaml --port 5050")
    db_tools = []


root_agent = Agent(
    model="gemini-2.5-flash",
    name="postgres_data_agent",
    description="A data analysis agent for PostgreSQL e-commerce database",
    instruction="""You are a database analyst assistant with access to an e-commerce PostgreSQL database.

Help users:
- Query product information and inventory
- Look up customer data
- Calculate inventory metrics and values
- Search products by category

Use the database tools to fetch real data. Present results clearly.""",
    tools=db_tools,
)

💡 Sync vs Async Client

The ToolboxSyncClient is recommended for ADK agents as shown in the official ADK documentation. For async applications, use ToolboxClient with async with pattern.

Step 2: Alternative - Async Client (for programmatic use)

For async control, use the ToolboxClient:

import asyncio
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
from toolbox_core import ToolboxClient

async def main():
    # Load tools using async context manager
    async with ToolboxClient("http://127.0.0.1:5050") as client:
        db_tools = await client.load_toolset("ecommerce")
        
        # Create the agent with loaded tools
        agent = Agent(
            model="gemini-2.5-flash",
            name="postgres_data_agent",
            instruction="You are a database analyst assistant...",
            tools=db_tools,
        )
        
        # Set up runner
        session_service = InMemorySessionService()
        runner = Runner(
            agent=agent,
            app_name="data_agent_app",
            session_service=session_service,
        )
        
        # Create session and run query
        session = await session_service.create_session(
            app_name="data_agent_app",
            user_id="analyst",
        )
        
        query = "What products do we have in inventory?"
        content = types.Content(role="user", parts=[types.Part(text=query)])
        
        async for event in runner.run_async(
            user_id="analyst",
            session_id=session.id,
            new_message=content
        ):
            if hasattr(event, 'content') and event.content and event.content.parts:
                for part in event.content.parts:
                    if hasattr(part, 'text') and part.text:
                        print(part.text)

if __name__ == "__main__":
    asyncio.run(main())

Step 3: Run the Agent

Make sure the Toolbox server is running first, then start the ADK agent:

# Terminal 1: Start the Toolbox server
toolbox --tools-file tools.yaml --port 5050

# Terminal 2: Set API key, activate venv, and start the ADK agent
export GOOGLE_API_KEY=your_api_key_here
source .venv/bin/activate
adk web

âš ī¸ macOS Port 5000 Conflict

On macOS, port 5000 is used by AirPlay Receiver. Always use port 5050 or higher to avoid conflicts. AirPlay Receiver can be disabled in System Settings → General → AirDrop & Handoff if port 5000 is needed.

Open http://127.0.0.1:8000 and try these example queries:

  • "List all products in the database" - Basic product catalog query
  • "Show me all electronics products" - Category filtering
  • "What's the total value of our inventory?" - Inventory metrics
  • "Search for products in the Furniture category" - Product search

Example Query and Response

User: "List all products in the database"

Agent: Here are all the products in the database:

Product ID: 1
- Name: Laptop Pro
- Category: Electronics
- Price: $1,299.99
- Stock Quantity: 50

Product ID: 2
- Name: Wireless Mouse
- Category: Electronics
- Price: $29.99
- Stock Quantity: 200

Product ID: 3
- Name: Office Chair
- Category: Furniture
- Price: $399.99
- Stock Quantity: 30

---

User: "What's the total value of our inventory?"

Agent: Here are the inventory metrics:

- Total Products: 3
- Total Items in Stock: 280
- Total Inventory Value: $82,999.20

The Electronics category holds the highest inventory value with 
the Laptop Pro contributing significantly at $64,999.50 (50 units × $1,299.99).

3. Understanding MCP Toolbox Architecture

MCP Toolbox provides a layered architecture that separates concerns between the agent, tool definitions, and database connections:

flowchart LR subgraph Agent["1. ADK Agent"] A1["User Query"] --> A2["ToolboxSyncClient"] end subgraph Toolbox["2. MCP Toolbox Server"] T1["Load Tools"] --> T2["Execute Query"] end subgraph Config["3. tools.yaml"] C1["sources"] --> C2["tools"] --> C3["toolsets"] end subgraph DB["4. PostgreSQL"] D1[("ecommerce_db")] end A2 --> T1 Config -.-> Toolbox T2 --> D1 D1 --> T2 T2 --> A2 style Agent fill:#e3f2fd,stroke:#1565c0,stroke-width:2px style Toolbox fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style Config fill:#fff3e0,stroke:#ef6c00,stroke-width:2px style DB fill:#fff9c4,stroke:#fbc02d,stroke-width:2px

How It Works

  1. ADK Agent: User sends a query. The agent uses ToolboxSyncClient to connect to the Toolbox server
  2. MCP Toolbox Server: Loads available tools and executes queries against the database using connection pooling
  3. tools.yaml: Defines database sources, SQL tool definitions, and toolsets that group related tools
  4. PostgreSQL: Receives parameterized SQL queries, returns results back through the Toolbox to the agent

Key Benefits

  • Centralized Tool Management: Update SQL queries in tools.yaml without redeploying agents
  • Connection Efficiency: Connection pooling reduces database load and latency
  • Security: Parameterized queries prevent SQL injection; credentials stay on the server
  • Observability: Built-in OpenTelemetry support for tracing and monitoring

4. Security Best Practices

When connecting agents to databases, security is paramount:

1. Use Read-Only Access When Possible

-- Create read-only user
CREATE USER agent_readonly WITH PASSWORD 'strong_password';
GRANT CONNECT ON DATABASE ecommerce_db TO agent_readonly;
GRANT USAGE ON SCHEMA public TO agent_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_readonly;

2. Use Connection Strings Securely

  • Store credentials in environment variables, never in code
  • Use .env files with proper .gitignore rules
  • Consider using secret management services in production

3. Implement Query Allow Lists

For production deployments, consider implementing an allow-list of approved queries to prevent unauthorized data access.

4. Monitor and Log Activity

Enable query logging to track what data our agents are accessing:

# Enable logging in the agent
def log_query(user_query: str, sql_query: str, results: dict):
    """Log database queries for audit purposes"""
    logging.info(f"User: {user_query} | SQL: {sql_query} | Rows: {results.get('row_count', 0)}")

Next Steps

Now that we've connected our agent to PostgreSQL using MCP Toolbox, we can:

  • Build advanced analytics agents - Create agents that generate reports and insights
  • Implement data visualization - Combine query results with chart generation tools
  • Create data management agents - Build agents that can clean and transform data
  • Multi-database integrations - Connect to multiple databases for cross-system analysis
  • Add business logic - Implement custom tools for domain-specific calculations

In the next module, we'll explore the Model Context Protocol in more depth, understanding its architecture and building custom MCP servers.

Resources

Comments