- A plugin installed in your harness — see Get Started.
mariadb-shell -- mcp setuprun once, with the directory you will work in on the allowed-paths list.- Nothing else — the sandbox brings its own MariaDB Server if the machine has none.
This is the tutorial everything else builds on. By the end you will have a
notes_app.sql script on disk, a running MariaDB server on port 3310 with that
schema in it, and a clear picture of which part of the work came from the
skills and which from the MCP tools.
Nothing here touches a database you care about. The sandbox is a throwaway server that lives in one folder, and the last step deletes it.
Ask for the schema
Open your agent in an empty directory — one that is on the MCP server’s allowed-paths list — and start with the schema itself. No database is involved yet; this step produces a file:
Create a MariaDB database schema named notes_app for a note-taking app and
store it in a notes_app.sql file.
Before it writes a single CREATE, the agent reads the
mariadb-schema-create-script skill, and from there the mariadb-create-table
and mariadb-create-index skills. That is what makes the output MariaDB SQL
rather than generic SQL.
Three tells that the skills were actually used:
The script opens and closes with a settings block. Every script the skill
produces saves the session’s checks and SQL mode, switches to utf8mb4, and
restores everything at the end:
-- Save current session settings and disable checks for faster, safer bulk import
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES';
SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0;
SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT, @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;
SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION;
SET NAMES utf8mb4;
Primary keys are native UUID, not CHAR(36). MariaDB has had a real
UUID column type since 10.7, and UUID_v7() generates time-ordered values, so
you get the index locality of an auto-increment without exposing a row count to
the outside world:
-- A person who writes notes.
CREATE OR REPLACE TABLE `notes_app`.`user` (
`id` UUID NOT NULL DEFAULT UUID_v7(),
`email` VARCHAR(255) NOT NULL,
`display_name` VARCHAR(120) NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_user_email` (`email`)
) ENGINE=InnoDB;
CREATE SCHEMA IF NOT EXISTS, and CREATE OR REPLACE TABLE. The skill
prefers SCHEMA over DATABASE, and MariaDB’s atomic CREATE OR REPLACE over
a DROP-then-CREATE pair — so re-running the script is safe and never leaves
you with the table gone and the create failed.
A reasonable Notes App comes out as five tables — user, notebook, note,
tag and the note_tag junction — plus a view or two. The exact shape will vary;
what should not vary is the MariaDB-specific detail above.
Push back and watch it use the skills again. Ask “make note.body full-text
searchable” or “add row history to note so I can see what it looked like last
week”. The second one should produce a system-versioned table
(WITH SYSTEM VERSIONING) — a MariaDB feature with no MySQL equivalent, and one
the agent only reaches for because a skill told it about it.
Deploy the sandbox
Now the MCP tools take over, and a real server appears:
Spin up a MariaDB sandbox instance on port 3310.
That is everything you type. What the agent runs in response is the call below — shown so you can recognise it going past:
sandbox.deploy(port=3310, password="…", ssl=False)
A port is all you have to give. You do not need to invent a root password — the agent sets one and hands it to the MCP server with the connection, so nothing downstream ever asks you for it.
Three things happen that are worth knowing about:
- A real
mariadbdstarts, with its data directory under~/.mariadb-shell/sandboxes/3310/. No container runtime is involved. The server itself comes from yourPATHif one there fits; otherwise the tool downloads a version, verifies its checksum and unpacks it — so this works on a machine with no MariaDB Server at all. The deploy’s message says which of the two happened. - The connection
root@127.0.0.1:3310is registered with the MCP server automatically, password and all. You do not have to re-runmcp setupto let the agent connect to it. - TLS is off (
ssl: False), which is why a command-line client may need--skip-ssllater. Turning it on requiresopensslon the machine.
When you do want to choose the password
Name one in the prompt whenever you intend to reach the server from outside the agent — a command-line client, a GUI, an application you are pointing at it:
Spin up a MariaDB sandbox instance on port 3310 with root password demo-pw.
The same call the agent made before, now carrying the password you chose:
sandbox.deploy(port=3310, password="demo-pw", ssl=False)
The rest of this tutorial works either way; the later steps go through the agent, which already has the credentials.
Do not ask for a blank password. It is the one value that is accepted and then breaks everything: the connection gets registered, nothing can open it, and the failure reads like a permissions problem rather than a password problem. Either say nothing and let the agent choose, or give it a real one.
If the deploy hangs rather than failing, the sandbox directory is not on the
allowed-paths list — the path guard is waiting for a confirmation a headless
agent cannot give. Run mariadb-shell -- mcp setup and add it.
Connect and run the script
With the sandbox up, point the agent at the file you made in step 1:
Connect to that sandbox and run notes_app.sql against it.
Two calls this time by the agent — one to open the connection, one to run the file down it:
db.connect(uri="root@127.0.0.1:3310")
db.execute_sql_script(file_path="/abs/path/notes_app.sql", connection_id="…")
Two behaviours here explain most of the surprises people hit later:
- The script file has to be somewhere the MCP server was given access to. If your working directory was never allowed, the agent simply sends the same SQL inline instead — which works just as well, and is why you will sometimes see it paste a script rather than point at one.
- A script is not a transaction. The statements run in order on one session, so the settings block at the top really does apply to everything below it — but a failure stops the script and leaves whatever already ran in place. The agent reports which statement failed; Tutorial 2 covers what to do about it.
Read the result back
The last step is the one that turns “the agent said it worked” into “it worked”, and it is worth asking for explicitly. The difference is between the agent summarizing what it just did and the agent going back to the server to look:
Don’t tell me what you ran — go and read it back off the server. Which schemas
exist, which tables are in notes_app, and what are note’s columns?
You should see notes_app in the schema list, your five tables in the object
list, and note’s columns — including the UUID primary key and the foreign
key to user — in the details.
Now insert three sample users and five notes, then show me each user with their note count.
That last query is a good sanity check on the foreign keys. It is also a question rather than a script, which is a distinction worth knowing about — Tutorial 2 covers when it matters.
Clean up
The sandbox is a real server process. Stop it and delete it when you are done:
Stop and delete the sandbox on port 3310.
Which the agent carries out as two calls, in this order:
sandbox.stop(port=3310, password="…")
sandbox.delete(port=3310)
A sandbox cannot be deleted while it is running, so the stop has to land first. If a stop fails — a wedged server, a forgotten password — the agent forces it down and then deletes it. Asking for both in one sentence, as above, means you never have to think about the order.
The whole thing in one prompt
You have now seen each step on its own. In practice you would not type five prompts — you would type one, because agents do this better as a single instruction than as five: the later steps constrain the earlier ones, so an agent that knows it will have to run the script on a real server writes a more careful script.
This is the same tutorial as one prompt, and it is the shape to use once you trust what it does:
Work in the current directory and complete every step in order.
1. Create a MariaDB database schema named notes_app for a note-taking app and
store it in a notes_app.sql file.
2. Spin up a sandbox instance on port 3310, connect to it and run
notes_app.sql against it.
3. Don’t tell me what you ran — read it back off the server. List the tables you
created and show me the columns of the note table.
4. Then stop and delete the sandbox, even if an earlier step failed.
Step 4 is the habit worth forming early: ask for the teardown in the same breath as the creation, so a run that times out halfway does not leave a server listening and a directory behind.
What you built
A notes_app.sql script whose MariaDB-specific decisions came from the skills,
run against a real server that the agent created, verified and then removed —
without you ever typing a connection string or a password into a config file.
The two halves you just used are the whole product. Everything else on this site is a deeper version of one of them.
Where to go next
- Run SQL and SQL scripts — a statement versus a whole file, and why the difference keeps biting people.
- Explore an existing database — the same tools pointed at a schema you did not write.
- Version the schema with MSM — turn this one-off script into something you can ship version 2 of.