Thanks, you're subscribed!

← Blog

No SQL, No Problem

Agents used to be an enterprise feature. Even today, it's not uncommon for some tools to charge an extra monthly fee for AI add ons. But savvy users expect natural language interfaces as a default, not an upsell.

ACP is so exciting because it lets you reuse your Claude & OpenAI subscriptions with other tools beyond just the standard harness.

Introducing duckdb-acp, a DuckDB extension for natural language queries:

INSTALL acp FROM community;
LOAD acp;

CLAUDE which customers have not ordered in 90 days?

-- or as a table function
SELECT * FROM claude('which customers have not ordered in 90 days?');

Works with Claude Code, Gemini CLI, Codex & other agents that support Agent Client Protocol.

A Brief History of Text-to-SQL

After the ChatGPT explosion, every data tool rushed to add "Ask AI" buttons. The standard approach was RAG: embed your schema, column comments, and example queries in a vector store, retrieve what seems relevant, and prompt the model to generate SQL. Results were hit or miss. Teams spent weeks tuning retrieval, adding example queries & building up in house evaluations for their text to SQL pipeline.

Tool-calling changed everything. Instead of indexing context up front and praying, the model has tools to run queries & explore the database directly. When a query fails, it reads the error and tries again. The model learns the schema the same way you would: by poking around and running things. AI labs like Anthropic have been codifying sequential tool calling and improving the models' ability to incorporate feedback & self-correct.

Just Two Tools

duckdb-acp is intentionally very simple in comparison with most text to SQL tools. We simply append a paragraph the underlying agents' system prompt to give some context about the execution environment. We also disable all default tools so the agent cannot read & write files outside of the context of DuckDB.

The entire agent interface is comprised of the following tools:

run_sql     - Execute a SELECT query, return results
final_query - Submit the answer

Claude figures out the schema by running queries, builds the SQL, tests it, iterates when something breaks.

Architecture

C++ is mostly a necessity today for ambitious DuckDB extensions (DuckDB's C extension API is still maturing; for anything beyond simple scalar functions, you'll need C++). We register statements as a parser extension, along with a new table function.

A Rust layer provides both the ACP client & MCP HTTP server that exposes the internal DuckDB connection that is provided to the ACP client.

Today, ACP adapters are external processes— we check for presence of npm or bun to run ACP adapters via npx or bunx.

Example

Here's a real session analyzing Foursquare's places dataset, comparing Peet's Coffee vs Starbucks locations:

D SET acp_show_sql=true;
D attach '~/Desktop/dbs/foursquare.db' as fs;
D CLAUDE look at tables and analyze peets vs starbucks;

--- Generated SQL ---
WITH brand_summary AS (
    SELECT
        CASE
            WHEN lower(name) LIKE '%peet%' THEN 'Peets'
            WHEN lower(name) LIKE '%starbucks%' THEN 'Starbucks'
        END as brand,
        country,
        region,
        COUNT(*) as location_count
    FROM fs.places
    WHERE lower(name) LIKE '%peet%' OR lower(name) LIKE '%starbucks%'
    GROUP BY 1, 2, 3
)
SELECT
    brand,
    SUM(location_count) as total_locations,
    COUNT(DISTINCT country) as countries,
    SUM(CASE WHEN country = 'US' THEN location_count ELSE 0 END) as us_locations,
    SUM(CASE WHEN country = 'US' AND region = 'CA' THEN location_count ELSE 0 END)
        as california_locations,
    ROUND(SUM(CASE WHEN country = 'US' AND region = 'CA' THEN location_count ELSE 0 END)
        * 100.0 / NULLIF(SUM(CASE WHEN country = 'US' THEN location_count ELSE 0 END), 0), 1)
        as pct_us_in_california
FROM brand_summary
GROUP BY brand
ORDER BY total_locations DESC;
---------------------

┌───────────┬─────────────────┬───────────┬──────────────┬──────────────────────┬──────────────────────┐
│   brand   │ total_locations │ countries │ us_locations │ california_locations │ pct_us_in_california │
├───────────┼─────────────────┼───────────┼──────────────┼──────────────────────┼──────────────────────┤
│ Starbucks │           53490 │       146 │        24361 │                 4283 │                 17.6 │
│ Peets     │            4188 │        54 │         1632 │                  612 │                 37.5 │
└───────────┴─────────────────┴───────────┴──────────────┴──────────────────────┴──────────────────────┘

From a simple English prompt, Claude explored the schema, built a complex CTE with geographic breakdowns, and surfaced an insight: Peet's is much more California-concentrated (37.5% of US locations) compared to Starbucks (17.6%).

Summary & Datasources

You can also enable summary and datasource output to get Claude's interpretation alongside the data:

D SET acp_show_summary = true;
D SET acp_show_datasources = true;
D attach '~/Desktop/dbs/foursquare.db' as fs;
D CLAUDE look at tables and analyze peets vs starbucks vs dutch bros year over year;

[Summary]
Year-over-year comparison of Peets, Starbucks, and Dutch Bros new location openings (2018-2024):

**Key Findings:**

1. **Dutch Bros** is the growth story:
   - Consistent upward trajectory: 63 locations (2018) → 163 locations (2024)
   - Strong growth in 2020 (+66%), 2022 (+67%)
   - Total footprint now ~1,037 locations

2. **Starbucks** dominates but slowing:
   - Massive scale: ~53,490 total locations
   - COVID hit hard: -34% in 2020
   - Recovered in 2022 (+40%) but declining again (-21% in 2024)

3. **Peets** is contracting:
   - Peaked earlier (2015 was their biggest year)
   - Steady decline since: 135 (2018) → 64 (2024)
   - 2024 down 23% YoY
   - ~4,188 total locations

**Market Share Shift (new locations only, 2018-2024):**
- Starbucks: 92.9% → 91.8%
- Dutch Bros: 2.3% → 4.5% (doubled share)
- Peets: 4.8% → 3.7% (lost ground)

[Datasources]
fs.places table containing Foursquare POI data. Filtered by name matching
'starbucks', 'peet', and 'dutch bros'. Used date_created field to determine
when locations opened. Calculated YoY growth rates and cumulative locations
using window functions.

100% ▕██████████████████████████████████████▏ (00:00:02.55 elapsed)
┌────────────┬───────┬───────────────┬─────────────────────┬────────────────┬──────────────────────┐
│   brand    │ year  │ new_locations │ prev_year_locations │ yoy_growth_pct │ cumulative_locations │
│  varchar   │ int64 │     int64     │        int64        │     double     │        int128        │
├────────────┼───────┼───────────────┼─────────────────────┼────────────────┼──────────────────────┤
│ Dutch Bros │  2018 │            63 │                NULL │           NULL │                   63 │
│ Dutch Bros │  2019 │            47 │                  63 │          -25.4 │                  110 │
│ Dutch Bros │  2020 │            78 │                  47 │           66.0 │                  188 │
│ Dutch Bros │  2021 │            87 │                  78 │           11.5 │                  275 │
│ Dutch Bros │  2022 │           145 │                  87 │           66.7 │                  420 │
│ Dutch Bros │  2023 │           157 │                 145 │            8.3 │                  577 │
│ Dutch Bros │  2024 │           163 │                 157 │            3.8 │                  740 │
│ Peets      │  2018 │           135 │                NULL │           NULL │                  135 │
│ Peets      │  2019 │           109 │                 135 │          -19.3 │                  244 │
│ Peets      │  2020 │            72 │                 109 │          -33.9 │                  316 │
│ Peets      │  2021 │            63 │                  72 │          -12.5 │                  379 │
│ Peets      │  2022 │            86 │                  63 │           36.5 │                  465 │
│ Peets      │  2023 │            83 │                  86 │           -3.5 │                  548 │
│ Peets      │  2024 │            64 │                  83 │          -22.9 │                  612 │
│ Starbucks  │  2018 │          2600 │                NULL │           NULL │                 2600 │
│ Starbucks  │  2019 │          2425 │                2600 │           -6.7 │                 5025 │
│ Starbucks  │  2020 │          1611 │                2425 │          -33.6 │                 6636 │
│ Starbucks  │  2021 │          1775 │                1611 │           10.2 │                 8411 │
│ Starbucks  │  2022 │          2492 │                1775 │           40.4 │                10903 │
│ Starbucks  │  2023 │          2385 │                2492 │           -4.3 │                13288 │
│ Starbucks  │  2024 │          1892 │                2385 │          -20.7 │                15180 │
├────────────┴───────┴───────────────┴─────────────────────┴────────────────┴──────────────────────┤
│ 21 rows                                                                                6 columns │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

The summary gives you Claude's interpretation of the data, while datasources explains where the data came from and how it was processed.

Try It

INSTALL acp FROM community;
LOAD acp;

CLAUDE whats the average order value by month?

github.com/sidequery/duckdb-acp