Query Your Database in Plain English

Serve a CSV through Datasette's MCP endpoint and query it with an AI client.

Introduction

30 Second Summary

Somewhere on your laptop is a spreadsheet with answers buried in it. Finding them means scrolling for ages or writing a query you half remember.

In this project, you will load that spreadsheet into a database and connect an AI client to it. You will ask questions in plain English and watch the exact query it runs to get each answer.

What You'll Build

You ask your AI client a question about your own data in plain English, and it writes the query, runs it, and shows you both the query and the rows that came back.

By the end of this project, you'll have:

  • A Datasette server running over a SQLite file built from your own CSV, with a web UI where you can browse your table and check any answer by hand.
  • An MCP endpoint your AI client connects to, with the execute_sql tool showing up in its list of available tools.
  • A question loop where you ask in plain English, read the SQL the client wrote, and see the rows it got back.
  • Secret Mission: Load a second CSV into the same database file and ask a question that spans both tables.

Are there any prerequisites?

You need Python with pip, a CSV file you actually care about, and any AI client that speaks MCP.

Datasette and its plugin are free, so the only spend is whatever your AI client already costs you.

Before We Start

An AI client can only answer questions about your data if something gives it a way to run queries. That something is a database with a query endpoint sitting in front of it.

Over the next few steps you'll put Datasette in front of your own CSV and open it up to an AI client over MCP. None of that runs until the tooling is on your machine and your data is somewhere you can point at.

In this step, get ready to:
  • Check that Python 3 and pip are on your machine.
  • Install Datasette and sqlite-utils.
  • Save a CSV you care about as data.csv in a new project folder.
Check Python and pip are on your machine

Datasette is a Python tool. You install it with pip, the package installer that ships with Python 3.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows), then press Enter to open it.
  • Check which Python you have by running this command:
python3 --version

✔️ I see a version number

Python 3 is ready to go.

  • Check that pip came along with it by running this command:
pip --version

You should see a pip version number, followed by the Python it belongs to.

Python works but pip does not?

Your Python install has almost certainly bundled pip. The command is just not on your PATH, so your shell cannot find it.

My terminal finds python3 but says pip is not found. How do I get pip working?

ⓧ Command not found

On Windows the command is usually spelled python rather than python3.

  • Try the Windows spelling by running this command:
python --version

If that prints a version number, you are set. Use python anywhere this project says python3.

If both spellings come up empty, Python is not installed yet.

  • Open the official Python downloads page.
  • Download the latest Python 3 installer for your operating system.
  • Run the installer.
  • Tick the installer option that adds Python to your PATH before you continue.
  • Close your terminal window.
  • Open your terminal again from the search bar.
  • Confirm Python is now installed by running this command:
python3 --version

You should see a version number starting with 3.

Install Datasette and sqlite-utils

What do these two tools do?

Datasette takes a SQLite file and serves it as a browsable web UI plus a query API. That API is what the MCP plugin will expose to your AI client later.

sqlite-utils is the companion command line tool that builds that SQLite file for you, straight from a CSV.

  • Install Datasette by running this command:
pip install datasette
  • Confirm it landed by running this command:
datasette --version

You should see a Datasette version number printed back.

Install ran but the command is not found?

pip put the package somewhere your shell does not look for commands. This is the most common outcome when several Pythons are installed side by side.

A permissions error instead means pip tried to write to a folder your user account cannot touch.

pip installed datasette but my terminal cannot find the command. What should I do?

  • Install sqlite-utils by running this command:
pip install sqlite-utils
  • Confirm it landed by running this command:
sqlite-utils --version

You should see a second version number, this time for sqlite-utils.

Seeing an error about the package name?

The package name uses a hyphen, so sqlite_utils and sqliteutils both fail. Check the spelling matches the command above exactly.

My pip install of sqlite-utils failed. Can you read the error with me?

Save a CSV into your project folder

This project is only interesting when the AI is answering questions about data you actually care about. Any spreadsheet with a few hundred rows will do.

  • Move to your home folder by running this command:
cd ~
  • Create your project folder and move into it by running these commands:
mkdir datasette-project
cd datasette-project
  • Confirm you are inside the new folder by running this command:
pwd

You should see a path ending in datasette-project.

What makes a good first dataset

Pick something you already have opinions about. A bank statement export, a workout log, a reading list, or a spreadsheet from work all work well.

Aim for a file with clear column headers and a mix of text and numbers. That gives the AI something to group and count later.

  • Pick a CSV you care about and download it to your machine.
  • Move that file into your datasette-project folder.
  • Rename the file to data.csv so every command later in this project matches yours.

Before you run the next command, what do you expect the folder to contain right now?

  • List the folder contents by running this command:
ls

You should see data.csv listed, and nothing else. That is your whole project so far.

Nothing listed, or a different filename?

An empty listing means your terminal is in a different folder from the one you dropped the file into. Run pwd again and check the path ends in datasette-project.

A name like data.csv.txt means your operating system is hiding file extensions and the rename did not take. Turn extensions on, then rename it again.

My CSV is not showing up in the folder I created. Help me find it.

Your machine has the tooling and your data is sitting in one known place. Next up, you'll turn that CSV into a database and see your rows rendered in a browser.

Load Your Data Into Datasette

You've got Datasette installed and a CSV file sitting in your project folder. Neither one does anything useful on its own yet.

Datasette serves SQLite databases, and a spreadsheet is not a database. In this step you'll convert your CSV into a SQLite file, serve it, and see your own rows in the browser.

In this step, get ready to:
  • Convert data.csv into a SQLite database file.
  • Start the Datasette server on your own machine.
  • Confirm your table's columns and row count in the web UI.
Turn your CSV into a SQLite database

A CSV is a flat list of text lines with no types, no indexes, and no way to run a query against it.

The sqlite-utils tool reads that file and writes a real database out of it in one command.

  • Switch back to the terminal you used to install Datasette.
  • Move into your project folder by running this command:
cd ~/datasette-project
  • Load your CSV into a new database file by running this command:
sqlite-utils insert data.db data data.csv --csv

What does this command do?

  • data.db is the database file to write to. It gets created if it doesn't exist.
  • The second data is the name of the table your rows land in.
  • data.csv is the file being read.
  • --csv tells the tool to parse the file as comma-separated values and use the header row as column names.
  • Confirm the database file landed by running this command:
ls

You should now see data.db listed alongside data.csv.

No data.db in the listing?

Check that ls shows data.csv too. If it doesn't, you're in the wrong folder and the insert command had nothing to read.

A file whose first line is not a header row also breaks the import, because the values in that line become your column names.

Why did my CSV import fail?

Start Datasette and open the web UI

The database file on disk is inert. Datasette wraps it in a web server so you can browse the tables and run queries from a browser.

This command takes over your terminal

Datasette is a long-running server, so it keeps the terminal busy until you stop it. Press Ctrl+C to shut it down.

Leave it running for the rest of this project. When you need to run another command, open a second terminal window.

  • Serve your database by running this command:
datasette data.db

Datasette prints a local URL and then sits there waiting for requests.

  • Open http://127.0.0.1:8001 in your browser.

You should see a Datasette homepage listing a single database called data, with your table underneath it.

Page won't load?

Check the terminal running Datasette. If it exited straight back to your prompt, the server never started and there's an error message above the prompt.

Another process already using the port will also stop it starting. Shut down anything else you have serving on 8001 and try again.

Why won't Datasette start?

Check your row count against the CSV

A table that renders is not the same as a table that imported everything. Comparing counts is how you know nothing was silently dropped.

  • Open a second terminal window, leaving Datasette running in the first.
  • Move into your project folder by running this command:
cd ~/datasette-project
  • Count the lines in your original file by running this command:
wc -l data.csv

That number counts the header line as well as your data rows.

Before you open the table, do you expect Datasette's count to match that number exactly, or come out different?

  • Open http://127.0.0.1:8001/data/data in your browser.

You'll see your columns across the top of the table and a row count near the heading.

It should be exactly one less than the wc -l figure, because the header line became your column names instead of a row.

Counts don't line up?

A gap of more than one usually means some lines had a different number of fields to the header, so they were skipped.

Column names that look like real values are the other tell. That means your file had no header row.

Why are rows missing from my table?

Your own rows are live in the browser and you've proved the import was clean. Next up, you'll install the datasette-mcp plugin so an AI client can reach this same database over MCP.

Install the MCP Plugin

Your CSV is a real database now, and Datasette is serving it at http://127.0.0.1:8001 where you can click through every row. That only helps a human with a browser.

An AI client has no way in. It cannot see your table, and it has no way to run a query against it.

MCP is the protocol that gives a client that way in. The datasette-mcp plugin adds an MCP endpoint to the server you already have running.

In this step, get ready to:
  • Stop the Datasette server that is currently running.
  • Install the datasette-mcp plugin with pip.
  • Restart Datasette and find the MCP endpoint in the startup output.
Stop the running Datasette server

Datasette loads its plugins once, at startup. A plugin installed while the server is running has no effect until you restart it.

  • Switch back to the terminal window running Datasette.
  • Press Ctrl+C to stop the server.
  • Return to your browser tab and refresh http://127.0.0.1:8001/data/data.

The page fails to load. That is your confirmation the server has actually stopped and the port is free.

Install the datasette-mcp plugin
  • Install the plugin by running this command:
pip install datasette-mcp

What does this plugin add?

The plugin bolts an MCP endpoint onto the Datasette server you already have. Your database file and your rows stay exactly as they are.

That endpoint is what an AI client connects to. Through it, the client can run SQL against data.db instead of guessing at your data.

pip prints its progress, then finishes with a line naming datasette-mcp and the version it installed.

Install didn't finish?

  • The plugin needs the mcp package at 2.1.1 or higher. If pip reports a conflict on that package, upgrade it to 2.1.1 or higher and run the install again.
  • Use the same terminal and the same Python you used for pip install datasette earlier. A plugin installed into a different Python is invisible to the server.
  • My pip install of datasette-mcp failed. What should I check?
Restart Datasette and find the MCP endpoint

Before you run this, what do you expect to show up in the startup output that was not there the first time?

  • Start Datasette again by running this command:
datasette data.db
  • Read the lines Datasette prints as it starts.

✔️ I see the MCP endpoint listed

The startup output lists an MCP endpoint at http://127.0.0.1:8001/-/mcp alongside the usual server URL. The plugin loaded.

Datasette has taken over this terminal again. Leave it running for the rest of the project.

ⓧ I don't see an MCP endpoint

Datasette started, but it never loaded the plugin. The usual cause is pip putting the package somewhere the datasette command cannot reach.

  • Press Ctrl+C to stop the server.
  • Install the plugin again from this same terminal by running this command:
pip install datasette-mcp
  • Start Datasette again by running this command:
datasette data.db

Still no MCP endpoint?

Your pip and your Datasette are probably coming from two different Python installs. That mismatch is worth confirming before you go further.

Why isn't Datasette picking up my datasette-mcp plugin?

ⓧ Datasette won't start

Datasette reports it cannot use the port. The earlier server is still running somewhere and holding 8001.

  • Look through your open terminal windows for the one still showing Datasette's log lines.
  • Press Ctrl+C in that window.
  • Start Datasette again by running this command:
datasette data.db

Port still taken?

A Datasette process can survive a closed terminal window, which leaves the port occupied with nothing visible to stop.

How do I find and stop the process holding port 8001?

  • Return to your browser tab and refresh http://127.0.0.1:8001/data/data.

Your table renders exactly as before, with the same row count. The plugin added an endpoint without touching a single row.

Your database now speaks a protocol an AI client understands. Next up, you'll point a client at that endpoint and watch its query tool appear.

Connect an MCP Client

Your Datasette server is already advertising an endpoint at http://127.0.0.1:8001/-/mcp. Nothing is on the other end of it yet.

An MCP client keeps a list of servers it is allowed to talk to, and yours has never heard of Datasette. Add one entry, and the client discovers what your database can do.

In this step, get ready to:
  • Register your Datasette server with an MCP client.
  • Open the client and connect to that server.
  • Find the execute_sql tool in your client's tool list.
Register the Datasette endpoint with your MCP client

Datasette is holding onto the terminal it is running in. You need a second terminal to type anything else without shutting the server down.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open a second terminal window.
  • Choose the tab below that matches the client you want to use.

Claude Code

  • Register your Datasette server by running this command:
claude mcp add --transport http datasette http://127.0.0.1:8001/-/mcp

What does this command do?

  • --transport http tells the client to reach the server over the network instead of launching it as a background process.
  • datasette is the name this server will appear under in your client.
  • The URL is the same endpoint Datasette printed when you restarted it.
  • Confirm the entry was saved by running:
claude mcp list

You should see datasette in the output, alongside its URL and a connection status.

Another MCP client

Every MCP client stores its servers somewhere, either a settings screen or a JSON config file. The wording differs between clients, the two values you supply do not.

  • Open your client's MCP server settings or its MCP config file.
  • Add a server named datasette.
  • Set its transport to HTTP.
  • Set its URL to http://127.0.0.1:8001/-/mcp.
  • Save the settings screen or the config file.

Your client's server list should now include datasette pointing at that URL.

Why point the client at a URL?

Most MCP servers are a command the client launches for itself and talks to over a pipe.

Datasette is already running and already serving MCP over HTTP, so the client only needs an address. Stop the Datasette process and the connection goes with it.

Find the execute_sql tool in your client

Registering a server is not the same as talking to one. Your client only learns what tools exist when it opens a connection and asks.

Before you look, take a guess: does a whole database expose one tool, or one tool per table?

  • Start your MCP client in the second terminal by running this command:
claude
  • Type /mcp and press Enter to open the panel of configured servers.
  • Select datasette from the list to see what it offers.

One tool comes back: execute_sql. That is the entire surface area of your database.

Why only one tool?

Datasette does not generate a tool per table or per column. It hands the client one way in and lets the model write the query.

That is why your data table needed no configuration to become askable. Every question you ask from here turns into SQL going through this one door.

Your server shows zero tools?

  • A client that was already open caches its tool list. Select Reconnect in the /mcp panel so it asks Datasette again.
  • Check your first terminal is still running Datasette. If you closed it, the endpoint at http://127.0.0.1:8001/-/mcp is gone.
  • Restart the client with claude --debug=mcp to watch the connection errors as they happen.

Why is my Datasette MCP server showing no tools?

Your client can now run queries against your own rows. Next up, you'll ask it a plain English question and read the SQL it writes to answer you.

Ask a Simple Question and Verify the SQL

The last step ended with execute_sql sitting in your client's tool list. A listed tool proves the connection works, not that the answers are right.

Now you get to ask your first question in plain English. The client picks the tool, writes the SQL, and hands you back a sentence.

That middle part is where trust is won or lost. If you never read the SQL, you are taking a confident sentence on faith, so your first question is one you already know the answer to.

In this step, get ready to:
  • Ask your MCP client a plain English question about your table.
  • Read the SQL it wrote and the rows it got back.
  • Check both against the Datasette web UI.
Ask a question you already know the answer to

Your first question is not about learning something new. Pick one you can confirm by eye, so a wrong answer is obvious the moment it lands.

Before you send anything, predict the SQL. Which table do you think the client will name, and how will it count the rows?

  • Switch back to the MCP client you connected in the last step.
  • Ask your first question by sending this message:
How many rows are in the data table?
  • Read the answer your client gives you.
  • Confirm the answer arrived with a call to the execute_sql tool attached to it.

What just happened?

  • Your client read the question, decided it needed real data, and picked execute_sql from the tools Datasette advertises over MCP.
  • Datasette ran that SQL against data.db and sent the rows straight back.
  • The client turned those rows into the sentence you are reading.

No tool call in the answer?

  • Vague questions get answered from the model's own head. Name the data table in your question so the client has a reason to query.
  • Check the Datasette process is still running in the terminal from earlier. If it stopped, http://127.0.0.1:8001/-/mcp is dead and the client has nothing to call.

Why did my client answer without calling the tool?

Read the SQL behind the answer

The sentence your client wrote is a claim. The SQL and the rows are the evidence, and both are sitting inside the tool call.

  • Expand the execute_sql tool call in your client to see its arguments.
  • Read the SQL string the client sent to Datasette.
  • Read the rows Datasette sent back underneath it.

For a row count you should see one row holding one number. The SQL should name your data table and nothing else.

Why the rows come back labelled

Each row arrives as an object, with the column name attached to every value. Older builds of the plugin returned bare arrays instead.

Bare arrays made weaker models lose track of which value belonged to which column. Labelled rows mean you can read the result yourself without counting positions.

Check the answer against the Datasette web UI

Reading the SQL tells you what was asked. Running it yourself tells you whether the answer is true.

The database page in Datasette includes a SQL editor for any select query. That gives you a second opinion from the same file the client just queried.

Before you run it, do you expect the same number your client reported, or something different?

  • Switch to the browser tab showing your Datasette web UI.
  • Open the database page at http://127.0.0.1:8001/data.
  • Paste the exact SQL from your client's tool call into the SQL editor on that page.
  • Run the query.

A row count against your table looks like this.

select count(*) from data
  • Compare the number on the page against the number your client reported.

You should see a single row with a single number, and it should be the number your client gave you. That match is the whole point of this step.

Numbers don't match?

  • Look for a where or limit clause you never asked for. A filtered query returns a smaller number that is still wrong.
  • Check you pasted the whole SQL string, including everything after the table name.

Why do my two row counts disagree?

You now have an answer you can prove, not just one you were handed. Next up, you'll ask something harder and watch what the client does when a single table is not enough.

Debug a Harder Query

Your first question landed because there was only one sensible way to answer it. One column, one number, one row back.

Real questions are messier. Ask for a total per category and the client has to work out which column holds the category, which holds the number, and how to add them up.

Every one of those decisions rests on your column names. In this step you'll ask a question hard enough to expose a bad guess, then close the gap two different ways.

In this step, get ready to:
  • Ask a grouping question and read the SQL the client writes for it.
  • Hand the client your exact column names when it guesses wrong.
  • Rename the column that tripped it up so the guess stops coming back.
Ask a question that needs a group by

A grouping question makes the client pick two columns and an aggregate function in one go. That is three chances to guess wrong, and your column names are all it has to go on.

  • Return to the MCP client you connected earlier.

Before you send anything, make a call. Will the client name your columns correctly on the first try?

  • Ask your grouping question by pasting this into the client:
Which [[GROUP_COLUMN="the column you want to group by"]] has the highest total [[VALUE_COLUMN="the numeric column you want to add up"]]?
Show me the SQL you ran.
  • Read the SQL in the tool call before you look at the numbers it returned.

What to check in the SQL

  • The names after select and group by match the headers in your table exactly.
  • The aggregate wraps a column that really does hold numbers.
  • The number of rows coming back looks sane for your data.

Two outcomes are common here. Either the query errors because the column does not exist, or it runs against a column you never meant and hands you a confident wrong number.

That gap is exactly what the rest of this step closes.

Client answered without running a query?

Some clients answer from memory rather than reaching for a tool. Tell it to run the query against the data table with execute_sql and show you the query it used.

Still no tool call? Why is my MCP client ignoring the execute_sql tool?

Give the client your exact column names

The client is guessing because nothing in your question told it what your columns are called. Handing it the real names removes the guess.

  • Switch back to the Datasette web UI at http://127.0.0.1:8001/data/data.
  • Copy the column headers across the top of the table exactly, capitals and spaces included.
  • Return to your client and paste this, with your own column names in place of the placeholder:
The table is called data. Its columns are exactly: [[COLUMN_LIST="your column names, separated by commas"]]
Use only these names. Answer my last question again and show me the SQL you ran.
  • Compare the new SQL against the first attempt.

The names in the group by now match your headers character for character.

Why the client guesses at all

The client writes SQL from your words alone unless something hands it the schema. A header reading Total Amount becomes total in its head, and total does not exist.

Give it the real names and the guessing stops.

  • Copy the SQL the client just ran.
  • Open http://127.0.0.1:8001/data in your browser.
  • Paste the client's SQL into the SQL editor on that page and run it.

You should see exactly the rows the client reported back to you. Same values, same order.

Rename the column that tripped it up

Pasting the schema works, and you have to do it again in every new conversation. Renaming the column fixes the guess in the database itself.

  • Note the exact name of the column the client got wrong: the column name the client got wrong.
  • Note a clearer name for it, lowercase with underscores instead of spaces: a clearer name, lowercase with underscores.
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open a second terminal window.

Your first terminal stays busy serving your database. This second window is where you work.

  • Move into your project folder by running this command:
cd ~/datasette-project
  • Rename the column by running this command:
sqlite-utils transform data.db data --rename [[TRICKY_COLUMN="the column name the client got wrong"]] [[CLEAN_COLUMN="a clearer name, lowercase with underscores"]]

What does this command do?

  • transform rebuilds the table, which lets it go beyond the capabilities of ALTER TABLE.
  • data.db data picks the database file first, then the table inside it.
  • --rename takes the current column name first and the new name second.
  • Switch back to your browser and refresh the table page at http://127.0.0.1:8001/data/data.

The old header is gone and your new name sits in its place. Every row still carries its values.

Rename did not go through?

  • A column name containing spaces needs quotes around it, like "Total Amount".
  • If the database is locked, stop the server in your first terminal with Ctrl+C, run the rename again, then start it back up by running datasette data.db.
  • Reconnect your client after a restart if it drops the connection to the endpoint.

Still stuck? Why did my sqlite-utils column rename fail?

Before you ask again, predict it. With the column renamed and no schema pasted in, does the client get the query right this time?

  • Ask your grouping question in the client again, worded exactly as you first asked it, with no column list this time.

The SQL comes back naming your renamed column. The answer matches the rows you ran by hand in the SQL editor.

You can now steer the client when its guesses go wrong. Next up, you'll ask it to change your data and find out exactly what this endpoint lets through.

Test the Endpoint's Write Boundaries

Your client can now answer a real question about your own rows, joins and all. Every one of those questions only read data back.

The same execute_sql tool that runs a read could, in principle, run a write. Nothing you have seen so far tells you which, so this step asks for a change and watches what comes back.

In this step, get ready to:
  • Ask your client to change a row through the execute_sql tool.
  • Check your table in the web UI to see whether the change landed.
  • Record exactly what the endpoint allowed and what it refused.
Ask the client to write to your database

Every question so far has only read rows back. Whether the same tool can change them decides how safely you can point a client at real data.

  • Return to the browser tab showing your table at http://127.0.0.1:8001/data/data.
  • Pick a text column you can spot at a glance: a column from your table.
  • Switch back to your MCP client.

Before you send the next prompt, make a prediction. Does the tool run the change, refuse it, or fail with an error?

  • Ask the client to change a row by sending this prompt:
Use execute_sql to update the data table. Set [[COLUMN_NAME="a column from your table"]] to 'nextwork-test' for the first row.

What does this prompt actually test?

The client turns plain English into SQL and sends it through the same execute_sql tool it uses for every read.

Two outcomes are both useful. The tool refuses and tells you why, or the change lands and your file is now different.

  • Read the client's full response, including any SQL it shows you.
  • Copy that response into a note, word for word.

Client answered without calling the tool?

Some clients turn down write-shaped requests before they ever reach your endpoint, so you learn nothing about the endpoint itself.

Tell it explicitly to call execute_sql and to show you the statement it sends. Why won't my client call the tool for this request?

Check the row in the web UI

The client's summary of what happened is not proof. Your data.db file is the only thing that settles it.

  • Return to your Datasette tab at http://127.0.0.1:8001/data/data.
  • Refresh the page.
  • Read your chosen column on the first row.

If the value reads exactly what it did before, the write never reached your file. If it now reads nextwork-test, the endpoint let a change through.

Why this answer matters more than the response text

A client can report success on a statement that quietly did nothing. It can also report an error after the row already changed.

Reading the row yourself is the only check that does not depend on the model's account of events.

Confirm the client and the database agree

You now have two views of the same row, one from the browser and one from the client. Making them match is the last check that your setup is telling you the truth.

Before you send this, predict what the client will report for that first row.

  • Ask the client for the current value by sending this prompt:
Use execute_sql to show me the first row of the data table, and show me the SQL you ran.

The value the client reports should match what your browser shows. Your read path and your file agree, whatever the write attempt did.

Client reporting a different value?

Some clients reuse a tool result from earlier in the conversation instead of running the query again. Start a fresh message and ask for the row once more.

Check that the row you are reading in the browser is the same row the client returned. Why is my client showing a stale row?

  • Write one sentence in your notes naming what this endpoint allowed and what it refused.

You now know both halves of what this endpoint does. Reads work, and the write side is something you tested yourself instead of assumed.

Secret mission

Answer Questions That Span Two Tables

Your client can query one table. Give it a second table that shares a column, and find out whether it works out the join on its own. This is where a plain English question turns into real SQL across your whole database.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Everything in this project runs locally on your machine with Datasette, so there are no ongoing costs.

Resources you used:

  • The ~/datasette-project folder, holding data.csv and your SQLite file data.db.
  • The Datasette server running at http://127.0.0.1:8001.
  • The MCP server entry in your client config pointing at http://127.0.0.1:8001/-/mcp.
  • The datasette, datasette-mcp and sqlite-utils packages installed with pip.

Keep everything running

No action needed. Choose this if you're still actively building on top of your database.

  • Leave the Datasette server running at http://127.0.0.1:8001 so your client keeps its connection to the endpoint.
  • Open a new terminal window for any other work, since the Datasette process holds on to the terminal it started in.

Swap in a different dataset

Load a new CSV into its own database file with sqlite-utils, then restart Datasette against that file.

Your client config stays exactly as it is, because the MCP endpoint lives at the same address.

Pause - I'll come back to this later

Shut down the running server to free up the port and the terminal, and keep every file exactly where it is.

  • Stop the Datasette server by pressing Ctrl+C in the terminal window where it is running.
  • Leave the ~/datasette-project folder and your MCP client config untouched.
  • Bring the whole setup back later by running these commands:
cd ~/datasette-project
datasette data.db

What happens to the tool in your client?

With the server stopped, your client can no longer reach http://127.0.0.1:8001/-/mcp, so the execute_sql tool stops working.

Restart Datasette and the tool works again. Some clients cache their tool list, so restart the client too if the tool stays greyed out.

Delete - I don't want to use this again

Remove every resource this project created and start fresh. Your original CSV goes with the folder, so move it elsewhere first if you still want it.

  • Stop the Datasette server by pressing Ctrl+C in the terminal window where it is running.
  • Open your MCP client's config file and delete the server entry pointing at http://127.0.0.1:8001/-/mcp.
  • Restart your client so it drops execute_sql from its list of available tools.
  • Delete the project folder, along with data.csv and data.db inside it, by running this command on macOS or Linux:
rm -rf ~/datasette-project
  • On Windows, delete the same folder from Command Prompt by running this command:
rmdir /s /q %USERPROFILE%\datasette-project
  • Confirm the folder is gone by running this command:
ls ~/datasette-project

You should see an error saying the folder does not exist. That is the result you want here.

  • Uninstall the three packages you installed for this project by running this command:
pip uninstall datasette datasette-mcp sqlite-utils
  • Enter y when pip asks you to confirm each removal.

Still seeing the tool in your client?

Some clients cache their tool list between sessions, so a config edit alone does not clear it. Quit the client fully and reopen it.

Ask NextWork AI: Why does my MCP client still list a tool after I removed the server entry from its config?

Nice Work!

Nice Work

Nice work. You've built a working natural language query loop over your own data, with every SQL statement visible as it runs.

Your data.db file and client config are still there, so a new dataset is a one line change away.

You've learned how to:

  • Turn a CSV into a queryable SQLite database and serve it as a browsable web UI with Datasette.
  • Expose that database over MCP and connect an AI client that can see and call the execute_sql tool.
  • Ask questions in plain English, then read the SQL the client wrote and check its answers against the web UI.
  • Debug a wrong answer by giving the model better schema clues, then find out what the endpoint does when you ask it to change data.
  • Secret Mission: an optional challenge to push your skills further.

Ready to quiz yourself?