I built an MCP (Model Context Protocol) that gives an LLM five tools for working with a data lake: list tables, describe schema, sample data, row count, run query. I use it every day. Different data sets, different questions.
It supports different workflows. You can paste a screenshot, tell the agent “replicate this data and validate your query.” You can say “hey uhh this data point doesn’t look right. This repo has the code to generate it, can you tell me what’s up?” You can give it a query and tell it “I want to add this column but I need to thread it through all these CTEs. Can you add it?” It explores the database, writes SQL, checks its own results. Iteratively.
I’m proud of the MCP idea. I had the insight on a Sunday that those five tools in that configuration would be useful. The implementation took an afternoon.
It Works
A colleague was the first other person to test this out. He used it to produce an accurate query feeding a tool in a day that would have taken a week. SQL wasn’t his background, but in the process he learned the application, the data, and the business context, along with the relationships with the business users. That was the strategy. SQL isn’t as important as the domain. The MCP handles the SQL. It’s been playing out well.
Not just playing out well. The thing he built has been finding things worth investigating, the kind of things that accumulate in any system over time. It finds stuff because it checks its own work, reasons, asks questions. It’ll run a query, notice the count dropped unexpectedly after a filter, and investigate. It does exactly what a careful data analyst does, just faster. The AI does the mechanical part, but he’s understanding the business.
I’ve shared it with others now. This tool is clearly useful.
Mostly
One example made me pause. I had a query to write and ran the agent alongside my own work. It made its version in 10 seconds and, reading through it, it had done a completely different strategy than I had thought of. I wrote my own as a way to validate whether my internal approach was wrong or suboptimal. I’m okay with being wrong if I can learn. I’m not okay with staying wrong.
Our queries were generally similar. Mine was tighter, 85 lines to its 115. I also caught a bug it couldn’t see, a subtle data integrity issue where the agent’s approach was structurally wrong but by chance didn’t appear in the example we tested. Ultimately it didn’t matter.
I made an unrelated demonstration of the type of issue that can come up so you can understand the questions yourself, to get a sense for the context of problems this tool can solve.
Demo
Like many libraries, a regional library system with 470 branches across ten upper midwest states is launching a tool lending program, drills, saws, ladders, tile cutters. One existing branch per state will be selected as the tool depot for less commonly used tools, with deliveries to other branches when patrons place holds. The task: compute a circulation-weighted geographic center for each state using 2025 data, then select the nearest currently-open branch as the depot.
There are three existing tiers for the libraries on the system, which determine the processing network and priority for new book releases. Demographics and circulation mean that libraries can be switched between tiers.
In the database there are two tables. The branches table is an append-only log: every tier assignment, reassignment, and closure is a separate row. A branch that got reassigned from tier 2 to tier 1 has at least two rows. A branch that closed has a row with status = 'closed'. The circulation table has annual circulation figures by branch.
| Table | Rows | What it is |
|---|---|---|
branches | 575 | Append-only log. branch_id, name, city, state, lat, lng, tier, status, effective_date. Multiple rows per branch. |
circulation | 2,587 | Annual circulation by branch and year. This is the weight. |
The wrinkle: about 40 branches were reassigned between tiers. When a branch moves from tier 2 to tier 1, it gets an active record in the new tier. The old tier’s record gets set to closed, but due to operational lag, the closure is timestamped after the new assignment. Another 25 branches are genuinely closed.
The correct approach takes the last record per branch within each tier. If any tier’s latest record is active, the branch is open. This handles both cases: reassigned branches (closed in old tier, active in new tier) and genuinely closed branches (closed in their only tier, no active record anywhere).
| State | Center Lat | Center Lng | Circulation | Branches | Depot |
|---|---|---|---|---|---|
| CO | 39.5785 | -105.3510 | 5,754,435 | 51 | Lark Community Library, Lakewood |
| IA | 41.9220 | -92.7330 | 4,650,099 | 53 | Buckeye Library, Marshalltown |
| KS | 38.4919 | -97.3810 | 4,625,767 | 49 | Crane Library Branch, Salina |
| MN | 45.2642 | -93.3278 | 7,017,755 | 65 | Summit Branch Library, Maple Grove |
| MO | 38.6307 | -92.9561 | 4,707,750 | 51 | Sassafras Lending Library, Sedalia |
| ND | 47.2868 | -99.9350 | 2,540,057 | 25 | Stone Memorial Library, Bismarck |
| NE | 41.3372 | -99.1227 | 5,247,031 | 46 | Catalpa Library, Broken Bow |
| SD | 44.2348 | -100.6130 | 3,366,758 | 30 | Pine Library, Pierre |
| WI | 44.0521 | -89.0220 | 5,835,967 | 55 | Sumac Library Branch, Oshkosh |
| WY | 42.4943 | -107.2639 | 2,066,239 | 20 | Valley Branch, Casper |
I pointed an LLM at the MCP and gave it the task.

It produced a query that joined the branches table directly to circulation without resolving the append-only log into current state first:

There were two structural problems. First, no dedup: a reassigned branch with three log rows (initial assignment, new tier assignment, old tier closure) gets its circulation counted three times in the ton-center calculation. The weights are inflated and skewed. Second, it filtered WHERE status = 'active' to find open branches, which keeps genuinely closed branches, their original active record is still in the log, and the filter just drops the closed record that superseded it. 445 branches are actually open. The agent’s approach counts 470.
The ton-centers shifted. The depot selections didn’t. All ten states picked the same branch.

The Ideal
The correct query. Resolve the log into current state by taking the last record per branch within each tier. If any tier’s latest record is active, the branch is open:
WITH per_tier AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY branch_id, tier
ORDER BY effective_date DESC
) as rn
FROM branches
),
latest_per_tier AS (
SELECT * FROM per_tier WHERE rn = 1
),
open_branches AS (
SELECT DISTINCT branch_id, branch_name,
city, state, lat, lng
FROM latest_per_tier
WHERE status = 'active'
),
ton_centers AS (
SELECT
bc.state,
SUM(c.annual_circulation * bc.lat)
/ SUM(c.annual_circulation) AS center_lat,
SUM(c.annual_circulation * bc.lng)
/ SUM(c.annual_circulation) AS center_lng,
SUM(c.annual_circulation) AS total_circ,
COUNT(DISTINCT bc.branch_id) AS num_branches
FROM open_branches bc
INNER JOIN circulation c
ON bc.branch_id = c.branch_id
AND c.year = 2025
GROUP BY bc.state
)
The PARTITION BY branch_id, tier is the key. Partitioning by branch alone picks the most recent record overall, which for reassigned branches is the closed record in the old tier, because the closure happened after the new assignment. Partitioning by branch and tier lets you see each tier independently. The old tier’s latest is closed. The new tier’s latest is active. The branch is open.
The agent didn’t know about the operational lag. It didn’t know that some branches have multiple log entries, or that status = 'active' doesn’t mean “currently open” when the table is append-only. It applied standard patterns, join, filter, aggregate, and got the same answer from a structurally wrong query. Pattern filling without contextual awareness.
An LLM is a pattern filler. The agent’s query was a reasonable starting point, and it landed on the same depot selections. I can’t tell you mine mattered.
Is a sandcastle good enough?
A lot of people I talk to have an existential unease about AI. Being good at something, then watching the definition of good shift under you in real time. I think there’s the real risk that people will forget that the part AI does is immediately a commodity. The artifact is ordinary, only as valuable as the tokens used to generate it, while quietly dropping the undocumented context that derisks it.
Is knowing the piece that the AI can do redundancy, or is it dangerous when its 80% contribution, without reflection, can initially pass for 100? As a developer, if you have ever tried to refactor a tangled mess of tightly coupled duplicative code that Claude has written, after it’s tried 10 iterations to implement a feature that constantly breaks another, or seeing its performance degrade from one version to the next, the worry about learned helplessness built on sand becomes unavoidable.
When I ran the query, the agent’s version had two structural bugs and produced the same result. Does it take my ability to write SQL and lived experience to anticipate those problems? Automating a task means nothing if it makes you materially wrong, but how do you know when you’re there? Chicken and egg.
This Post
I’ve been writing SQL since I was a child, and my parents enrolled me in classes at the local community college when I was 11 for programming. I don’t think I’ll lose that ability. It’s a native tongue.
But when I create tools like this, I cycle through “will this cause my skill to atrophy,” to “do they even matter,” settling on “can you evaluate without creation?”
When the tool says there’s something wrong? Often correct. When the tool makes an error? It’s usually slips or failures of a global mental model. The internal validation means the mistakes are edge cases not encountered yet. Sometimes it breaks something visible. But like you see above, sometimes it’s just potential.
I like crafting tight, efficient queries, but I’m also proud of this tool, making something that’s eliminated an entire class of problems. The benefit is not theoretical. I’m not worried about AI taking my job. If all the tasks had been simple enough for an AI to take, then the job wasn’t worth doing in the first place. They aren’t.
What AI does becomes the floor, and it does it without understanding. Patterns. My job isn’t to craft complex SQL queries. That is an effect, an output, evidence from a mental model.
I worry people will equate output with judgment.

Leave a Reply