Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/neo4j-labs/neocarta/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks through building a Text2SQL agent that converts natural language questions into BigQuery SQL. The agent uses two MCP servers in tandem: the Neocarta MCP server for semantic schema discovery (finding the right tables, understanding foreign keys, and resolving business terms) and the BigQuery MCP server for executing the generated queries against live data. The full runnable source is in run_agent.py and agent/agent.py in the Neocarta repository.

Architecture Overview

The agent runs locally. It connects to a Neo4j graph containing your BigQuery schema metadata (ingested by Neocarta) and to the Google-hosted BigQuery MCP endpoint for query execution. An LLM (via LiteLLM) handles the reasoning and SQL generation step. How it works, step by step:
1

User asks a question

The user types a natural language question in the terminal chat loop — for example, “Which customers placed the largest orders last quarter?”
2

Agent retrieves schema context

The agent calls the Neocarta MCP server with the question text. The server performs a hybrid vector + full-text search over the semantic graph and returns the most relevant tables with their columns, data types, sample values, and foreign key references.
3

LLM generates SQL

The agent passes the retrieved schema context and the original question to the LLM. The LLM writes a BigQuery SQL query grounded in the actual table and column names from the graph.
4

Agent executes the query

The agent calls execute_sql on the BigQuery MCP server with the generated SQL and the project ID. BigQuery runs the query and returns the results.
5

Agent returns results

The agent formats the query results into a readable response and prints it to the terminal.

Prerequisites

Before running the agent, ensure the following are in place:

Neocarta installed

Install with the cli and mcp extras:
pip install "neocarta[cli,mcp]"

BigQuery schema ingested

Run the schema connector to load your BigQuery dataset into Neo4j:
neocarta bigquery schema \
  --project-id my-proj \
  --dataset-id sales \
  --embeddings

LangGraph dependencies

Install agent dependencies from the repository:
pip install "neocarta[cli,mcp]" \
  langchain langgraph \
  langchain-openai \
  langchain-mcp-adapters \
  langchain-litellm

Google Cloud credentials

Authenticate with Application Default Credentials:
gcloud auth application-default login

Environment Variables

Add the following to a .env file in the project root:
# Neo4j connection
NEO4J_URI=bolt://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=your-password
NEO4J_DATABASE=neo4j

# Embedding model (used by the Neocarta MCP server for semantic search)
EMBEDDING_MODEL=text-embedding-3-small

# LLM provider key — set the one matching your AGENT_MODEL
OPENAI_API_KEY=sk-...

# LLM model (any LiteLLM model ID; defaults to openai/gpt-4o-mini)
AGENT_MODEL=openai/gpt-4o-mini

# GCP project for BigQuery MCP execution
GCP_PROJECT_ID=my-gcp-project
AGENT_MODEL accepts any LiteLLM model ID, including gemini/gemini-1.5-pro, anthropic/claude-3-5-sonnet-20241022, and Azure OpenAI deployment names.

BigQuery MCP Setup

Enable the BigQuery MCP server on your GCP project:
gcloud beta services mcp enable bigquery.googleapis.com --project=PROJECT_ID
The agent uses Google Cloud Application Default Credentials to authenticate with the BigQuery MCP endpoint. The GoogleAuth class in run_agent.py handles token refresh automatically:
import httpx
from google.auth import default
from google.auth.transport.requests import Request

class GoogleAuth(httpx.Auth):
    def __init__(self):
        self.credentials, _ = default()

    def auth_flow(self, request):
        self.credentials.refresh(Request())
        request.headers["Authorization"] = f"Bearer {self.credentials.token}"
        yield request
To disable the BigQuery MCP server later:
gcloud beta services mcp disable bigquery.googleapis.com --project=PROJECT_ID

Agent Factory

The agent is created with create_text2sql_agent in agent/agent.py. It uses LangGraph’s create_agent with a LiteLLM-backed chat model and in-memory conversation history:
import os
from langchain.agents import create_agent
from langchain.tools import BaseTool
from langchain_litellm import ChatLiteLLM
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph.state import CompiledStateGraph

SYSTEM_PROMPT = """You are a Text2SQL agent and are tasked with answering
questions about our BigQuery dataset on ecommerce.

Use the metadata graph to collect relevant schema to inform your SQL queries.

Rules:
* Always ensure that tables are qualified with project and dataset names
* Always ensure you have the appropriate BigQuery schema from the Metadata Graph before write a query
* Return query results to the user in a readable format"""

def create_text2sql_agent(mcp_tools: list[BaseTool]) -> CompiledStateGraph:
    model = ChatLiteLLM(model=os.getenv("AGENT_MODEL", "gpt-4o-mini"))
    return create_agent(
        model=model,
        tools=mcp_tools,
        system_prompt=SYSTEM_PROMPT,
        checkpointer=InMemorySaver(),
    )

MCP Server Configuration

run_agent.py configures both MCP servers and passes their tools to the agent. Neocarta tools are accepted in full; only the execute_sql tool is allowlisted from BigQuery to prevent the agent from calling administrative operations.
from langchain_mcp_adapters.client import MultiServerMCPClient

sql_metadata_graph_mcp_params = {
    "transport": "stdio",
    "command": "uv",
    "args": ["run", "neocarta-mcp"],
    "env": {
        "NEO4J_URI": os.getenv("NEO4J_URI"),
        "NEO4J_USERNAME": os.getenv("NEO4J_USERNAME"),
        "NEO4J_PASSWORD": os.getenv("NEO4J_PASSWORD"),
        "NEO4J_DATABASE": os.getenv("NEO4J_DATABASE"),
        "EMBEDDING_MODEL": os.getenv("EMBEDDING_MODEL", "text-embedding-3-small"),
        "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY"),
    },
}

bigquery_mcp_params = {
    "transport": "http",
    "url": "https://bigquery.googleapis.com/mcp",
    "auth": GoogleAuth(),
    "headers": {"Content-Type": "application/json"},
}

client = MultiServerMCPClient({
    "sql_metadata_graph": sql_metadata_graph_mcp_params,
    "bigquery": bigquery_mcp_params,
})

Running the Agent

From the repository root, start the interactive chat session:
make agent
# or equivalently:
python run_agent.py
The agent prints a > prompt. Type a question and press Enter. Type exit, quit, or q to stop.

Example Interaction

> Which customers placed the largest orders last quarter?

Calling tools: ['get_context_by_table_hybrid_search']
[{'name': 'get_context_by_table_hybrid_search', 'args': {'text_content': 'customers orders'}, ...}]

Agent: I found the relevant tables. Let me generate the SQL query.

Calling tools: ['execute_sql']

Agent: Here are the customers with the largest orders last quarter:

| customer_id | customer_name      | total_order_value |
|-------------|--------------------|--------------------|
| C-00412     | Northwind Traders  | $284,500.00        |
| C-00089     | Contoso Ltd        | $198,320.00        |
| C-00231     | Fabrikam Inc       | $176,900.00        |
The agent called get_context_by_table_hybrid_search and retrieved both orders and customers tables. The graph showed that orders.customer_id carries a REFERENCES edge to customers.id, so the agent wrote the correct JOIN and applied the quarterly date filter using the column flagged as the time dimension.
The specific MCP tool the agent chooses depends on which indexes are present in your Neo4j graph. If embeddings were generated with --embeddings, the server registers hybrid search tools. Without embeddings, full-text search tools are registered instead. Both produce valid schema context.

Available Neocarta MCP Tools

The MCP server self-configures based on your graph’s indexes. Depending on what’s present, the agent may have access to:
ToolRequires
list_schemasAny ingested schema
list_tables_by_schemaAny ingested schema
get_full_metadata_schemaAny ingested schema
get_context_by_table_full_text_searchFull-text index
get_context_by_column_full_text_searchFull-text index
get_context_by_table_vector_searchVector index (embeddings)
get_context_by_column_vector_searchVector index (embeddings)
get_context_by_table_hybrid_searchBoth full-text and vector indexes
get_context_by_column_hybrid_searchBoth full-text and vector indexes
get_context_by_table_business_term_hybrid_searchBoth indexes + glossary
get_context_by_column_business_term_hybrid_searchBoth indexes + glossary
Run neocarta tool list-schemas --json to verify your Neo4j graph is populated and the MCP tools are reachable before starting the agent.

Build docs developers (and LLMs) love