The short version
- Connect your warehouse to an AI assistant once, on the web, and you can ask it questions about your business from your phone, in plain English, against live numbers.
- With Claude, a custom connector added on claude.ai or Claude Desktop carries over to Claude's iOS and Android apps.
- Good answers come from good questions (say the dates and the comparison) and from a warehouse whose names explain themselves.
- Connect your phone read-only. The assistant can then look but never change anything, and you can see every query it ran.
It is a Saturday in late September, a little after half past ten. Pondside's owner is at the farmers' market, behind a table of coffee bags, while the café runs its Saturday rush across town. Between customers, out comes the phone.
In part one we promised this moment: a question asked from a phone, answered from real numbers, in the time it takes to hand over a bag of coffee. Everything since has been building toward it. Pondside has a warehouse (part two), its sources sync on a schedule (part four), the raw tables have a clean, well-named layer on top (part five), and an assistant can reach it through the Model Context Protocol (part seven). This part puts those pieces in a pocket.
Set it up once, on the web
Phones are good at asking and bad at configuring, so the setup happens once on a computer. With Claude, it takes about two minutes:
Add DuckHouse as a custom connector
On claude.ai, or in Claude Desktop, open Claude's connector settings and choose Add custom connector. Name it DuckHouse and paste the server address:
https://duckhouse.co/api/mcpNothing else: no key, no client id. Anthropic's help article has the current menu path. On a Team or Enterprise plan, an owner adds the connector for everyone and each person then connects their own account. The Free plan allows one custom connector, which is all this needs.
Approve it, read-only
Claude sends you to DuckHouse's consent screen. Sign in, pick the organization that holds the warehouse, and choose read access. A phone in a pocket at a market is exactly where you want an assistant that can look but not touch. You can change it later under Settings → Profile → Connected applications.
Open Claude on your phone
Connectors you add on the web or desktop are available in Claude for iOS and Android the next time you sign in there. In a conversation, make sure DuckHouse is switched on among the connectors, and ask away.
Every other client (Claude Code, Cursor, VS Code and the rest) is covered in the MCP documentation. On a phone, though, the hosted apps are the ones that matter.
What about ChatGPT?
ChatGPT can connect to custom MCP servers too, and DuckHouse's server follows the same standard, so the connection itself works the same way: you give it the address and approve on DuckHouse's consent screen. Two caveats, both OpenAI's to change. Custom MCP servers are connected on ChatGPT's website, on the plans its help article lists. And at the time of writing, that article does not say whether a server connected on the web can then be used from ChatGPT's phone apps. Before you plan your Saturday mornings around it, check OpenAI's current help page.
The setup is a laptop job. The questions can come from anywhere.
The conversation
Back at the market. Here is the exchange, and the queries behind it. Every number below is what the SQL returns on Pondside's sample data, which you can download and check yourself.
query · clean.cafe_sales
query · clean.cafe_sales
- 1The questionTyped the way you would ask a colleague, with the comparison spelled out.
- 2The tool callThe assistant read the schema, wrote SQL and ran it against the warehouse, read-only.
- 3The answerReal numbers from the last sync, worked into a sentence, with a follow-up already welcome.
The first question is about today, so the assistant compares the same stretch of the morning on two days: this Saturday, September 26, 2026, and the Saturday 52 weeks earlier. It works from clean.cafe_sales, which already has the café's own clock in sold_at, so “before 10:30” means 10:30 at the café, not in UTC.
SELECT
sold_on,
count(*) AS orders,
round(sum(net_sales), 2) AS sales
FROM clean.cafe_sales
WHERE sold_on IN (DATE '2026-09-26', DATE '2025-09-27')
AND sold_at::TIME < TIME '10:30'
GROUP BY sold_on
ORDER BY sold_on;Result, 2 rows
| sold_on | orders | sales |
|---|---|---|
| 2025-09-27 | 138 | 1,334.18 |
| 2026-09-26 | 145 | 1,475.44 |
One good morning proves nothing, which is what the second question is really asking. The assistant compares the average Saturday in each month of this year with the same month last year:
WITH saturdays AS (
SELECT sold_on, count(*) AS orders
FROM clean.cafe_sales
WHERE dayofweek(sold_on) = 6
GROUP BY sold_on
)
SELECT
monthname(sold_on) AS month,
round(avg(orders) FILTER (WHERE year(sold_on) = 2025)) AS saturdays_2025,
round(avg(orders) FILTER (WHERE year(sold_on) = 2026)) AS saturdays_2026,
round(100 * (avg(orders) FILTER (WHERE year(sold_on) = 2026)
/ avg(orders) FILTER (WHERE year(sold_on) = 2025) - 1)) AS growth_pct
FROM saturdays
WHERE month(sold_on) <= 9
GROUP BY month(sold_on), monthname(sold_on)
ORDER BY month(sold_on);Result, 9 rows
| month | saturdays_2025 | saturdays_2026 | growth_pct |
|---|---|---|---|
| January | 153 | 182 | 19 |
| February | 161 | 199 | 24 |
| March | 160 | 202 | 26 |
| April | 186 | 214 | 15 |
| May | 185 | 231 | 25 |
| June | 205 | 226 | 10 |
| July | 205 | 227 | 11 |
| August | 208 | 242 | 16 |
| September | 198 | 211 | 7 |
Summer Saturdays are busier in both years, so a summer-against-winter comparison would have flattered the trend. Month against the same month, Saturday is up in every one of them. The rush is growing.
Then the question that started the series. Do Pond Club subscribers still come into the café, or did the subscription replace their visits? Part one found 227 subscribers who also buy in the café. The assistant now compares how often they visited before they subscribed with how often they have visited since, per month of each:
WITH subscribers AS (
SELECT email, min(started_on) AS subscribed_on
FROM clean.subscriptions
GROUP BY email
),
regulars AS (
SELECT
s.email,
s.subscribed_on,
count(*) FILTER (WHERE v.sold_on < s.subscribed_on) AS visits_before,
count(*) FILTER (WHERE v.sold_on >= s.subscribed_on) AS visits_after
FROM subscribers s
JOIN clean.cafe_sales v USING (email)
GROUP BY s.email, s.subscribed_on
)
SELECT
count(*) AS subscribers_who_visit,
round(30 * sum(visits_before)
/ sum(date_diff('day', DATE '2025-01-01', subscribed_on)), 2) AS visits_a_month_before,
round(30 * sum(visits_after)
/ sum(date_diff('day', subscribed_on, DATE '2026-10-01')), 2) AS visits_a_month_after
FROM regulars;Result
| subscribers_who_visit | visits_a_month_before | visits_a_month_after |
|---|---|---|
| 227 | 0.3 | 0.33 |
No sign of cannibalization: if anything, subscribers visit slightly more often after joining. A careful assistant adds the caveat that matters, and you should expect it to: these are only the visits where Square recognized the customer, so the absolute numbers are low. The comparison between before and after is the useful part.
And the third, asked on the drive home:
SELECT bean, bags_sold, margin, round(margin / bags_sold, 2) AS margin_per_bag
FROM marts.bean_margins
ORDER BY margin DESC;Result, 5 rows
| bean | bags_sold | margin | margin_per_bag |
|---|---|---|---|
| Mallard Blend | 14,947 | 221,251.19 | 14.8 |
| Heron Ethiopia | 7,903 | 129,925.32 | 16.44 |
| Lily Pad Decaf | 4,419 | 61,802.11 | 13.99 |
| Morning Mist Guatemala | 2,721 | 41,566.51 | 15.28 |
| Kingfisher Kenya | 2,297 | 38,957.12 | 16.96 |
Two answers in one table, which is why it is worth looking at rather than just reading the summary. The house blend earns the most in total, by a distance, because it sells the most. Bag for bag, the Kenya earns the most, and the Ethiopia is close behind. The decaf earns the least per bag. Both readings are true, and which one matters depends on the decision: what to feature at the market stall is a per-bag question.
Which suggests a follow-up, typed with one thumb: just the web shop, last month, which coffee sold the most bags? The assistant keeps the context, swaps to the web shop's line items and narrows the dates:
SELECT product, sum(quantity) AS bags, round(sum(revenue)) AS revenue
FROM clean.web_items
WHERE ordered_on BETWEEN DATE '2026-09-01' AND DATE '2026-09-30'
AND product IN (SELECT bean FROM clean.bean_costs)
GROUP BY product
ORDER BY bags DESC;Result, 5 rows
| product | bags | revenue |
|---|---|---|
| Mallard Blend | 427 | 7,686 |
| Heron Ethiopia | 221 | 4,641 |
| Morning Mist Guatemala | 169 | 3,211 |
| Kingfisher Kenya | 148 | 3,256 |
| Lily Pad Decaf | 134 | 2,412 |
The Kenya earns the most per bag and sells about a third as many bags online as the house blend. That is not an answer, it is a better question for Monday: would featuring it sell more? Four questions, a follow-up and a decision worth testing, all between customers, and not one export.
Questions that work well from a phone
A phone suits short questions with short answers: a number, a comparison, a list of five. These are the kind Pondside asks most, and the view each one lands on. Yours will differ in the details and not much in the shape.
| Question | Where the answer lives | Why it works |
|---|---|---|
| How did yesterday compare with the same day last week? | marts.daily_sales | One view, two dates, one comparison. |
| Which channel grew most this quarter against last? | marts.daily_sales | Already split by channel, already in dollars. |
| Who are our ten best customers this year, across everything? | marts.customers | One row per person, matched across all three tools. |
| How many people subscribed in September, and how many left? | clean.subscriptions | Start and end dates are already on the café’s calendar. |
| What is our busiest hour on Saturdays lately? | clean.cafe_sales | sold_at is already local time, so the hour is the café’s hour. |
| Which coffee should we feature at the market? | marts.bean_margins | Margin per bag, after the cost of the beans. |
What does not work as well on a phone is anything whose answer is a big table. The assistant can only bring back 500 rows at a time, and you can only read about five on a small screen anyway. Ask for the top few, a total or a trend, and save the long lists for a laptop.
How to ask questions that get good answers
The assistant is writing SQL on your behalf, so the same things that make a question clear to an analyst make it clear to a model.
- Say the dates. “This September against last September” beats “recently”. If you mean the café's Saturday, not a calendar week, say so.
- Name the comparison. “Is it growing?” invites a guess about the baseline. “Compared with the same month last year” does not.
- Say which channel. Pondside sells in three places. “Café only” or “all channels” is one word that removes a whole class of wrong answer.
- Ask for the SQL when a number matters. It is right there in the tool call. Check what it filtered and what it summed.
- Follow up in the same conversation. “Now just the web shop” or “split that by hour” builds on the query it already wrote.
- Ask what it left out. “What would make this number wrong?” is a surprisingly good question, and the honest answer (only recognized customers, list prices, green-coffee cost only) is often the most useful sentence in the reply.
Vague
“How are sales doing?”
Clear
“Café sales for each week of September, against the same weeks last year, as a table.”
Write it down where the assistant looks
When an assistant connects, it learns your warehouse from describe_schema: every table and view, every column and its type. Names are most of what it has to go on, which is why part five spent so long on them. But some things a name cannot say. Revenue is before tips. Café visits only count recognized customers. Margin is after green coffee and nothing else.
DuckDB lets you attach a comment to a table or view with COMMENT ON, and that is good practice, but describe_schema returns names and types, not comments. So put the notes somewhere the assistant will see them: a view whose name says what it is.
CREATE OR REPLACE VIEW marts.readme AS
SELECT * FROM (VALUES
('marts.daily_sales',
'Start here for sales. One row per day per channel: cafe, web or subscription. Revenue is in US dollars, after refunds and before tips. Days are on the cafe clock (America/Chicago).'),
('marts.customers',
'One row per person, matched by lowercase email across the cafe, the web shop and Pond Club. Cafe visits only count when Square recognized the customer, so they undercount.'),
('marts.bean_margins',
'One row per coffee: 12 oz bags sold in the cafe and online, revenue at list price, and margin after the green coffee cost only, not roasting, packaging or labor.'),
('clean.cafe_sales',
'One row per completed cafe payment, in dollars, with sold_at on the cafe clock.'),
('clean.web_orders',
'One row per real web order. Test orders and cancelled orders are already excluded.'),
('clean.subscriptions',
'One row per Pond Club subscription, with its plan, monthly price, and start and end dates.')
) AS t(name, description);Now marts.readme shows up in the schema beside everything else, and a view called readme is hard to miss. To be sure, you can simply say: “Read marts.readme first.” Keep it short and keep it current; a stale note is worse than none.
What the assistant sees, and how to stay in charge
Asking from a phone changes nothing about the safety model from part seven, but a few points are worth repeating where they apply most.
- It sees what its queries return. Schemas, and the rows of each result, up to 500 at a time. Those land in the conversation, and the conversation is held by the assistant's provider under its own terms. Keep data you would not want there out of the database it can reach.
- Read access means read. On a read connection the server refuses anything but a plain query, whatever the conversation says. For a phone, that is the right default.
- A sleeping warehouse wakes up. If the database stops when idle, the first question after a quiet spell starts it again and DuckHouse waits for it to come up, which the docs put at about five seconds. Later questions are quick.
- You can cut it off from anywhere. Under Settings → Profile → Connected applications, disconnecting ends every token that connection holds, at once. A lost phone is a two-click problem.
- Everyone gets their own. On a team, each person connects their own account and approves their own consent screen, so access follows people: when someone leaves the organization, their connection stops working with them.
That is the pocket warehouse: a copy of everything the business knows, joined up and named well, behind a consent screen, answering in sentences. The rest of the series is about keeping it that way as it grows.
Pondside's data is small, and will be for years. Some businesses' is not, or will not stay that way. In part nine (part 9, out October 27) we look at DuckLake, which keeps a warehouse's tables as Parquet files in object storage with a catalog in an ordinary database, and adds time travel along the way: what it buys you, and when one DuckDB file stops being enough.
Further reading
- About custom connectors using remote MCP — Anthropic: plans, limits and setup
- Use connectors to extend Claude’s capabilities — Anthropic: connectors on iOS and Android
- Connecting custom MCP servers in ChatGPT — OpenAI: plans and where it works
- COMMENT ON — DuckDB documentation
- DuckHouse MCP documentation — setup for every client, the tools and the safety model
- Stop when idle — how a sleeping database wakes, and what syncs do to it