sandbox_schema
Lays out the stored data before you build it, as text, not a picture: tables with their fields, each field with its type, a short note on what it holds (required on every field) and the table it references, if any. The schema comes back as text in the result, and the person sees the same version as a table of text on the design's page in their app, where they circle, talk and write on it. No box is needed.
| Input | Output |
|---|---|
topic (at most 80 characters, the same for every version), tables (1–30, with a note on every field), optional note (at most 300 characters: what this version changed), optional id (a box, to show the design on that box's page too) |
ok, designId (dsg_…, the design), reviewId (dsn_…, this version), kind (schema), topic, version, appUrl, boxId when you passed id, next, warnings when there are any, fromHuman when the person left notes for this conversation; the schema follows the JSON as a text block. There is no picture and no imageUrls |
Its tool title (the MCP title, which some clients show for the tool) is Lay out a data schema; it is annotated readOnlyHint: false (each successful call saves a version), destructiveHint: false and openWorldHint: false.
When to use it
The order of work for anything new or changed is: sandbox_draft (screens) → sandbox_architecture (services) → sandbox_schema (stored data) → build in the box → sandbox_review. Skip a step only when the task does not touch it: a change that stores nothing new has no schema.
Use sandbox_schema when the work adds or changes what is stored (a new table, a new column, a field whose meaning changes), or the person asks for the schema. Do not make HTML, SVG, image, diagram or Markdown files for this instead: this tool lays it out, keeps every version, and gives the person the tools to answer.
It works like sandbox_draft in everything but the output: it needs no box, and the design belongs to the account and this conversation, staying when a box stops. id is optional: pass the box of this piece of work to show the design on that box's page too; the call runs on the server (the schema is checked and saved, nothing is drawn) and never wakes a frozen box, and with id naming a box that has not stopped, a successful call counts as use of it and makes your conversation its holder (sandbox_draft).
What to write
List only the tables and fields this work needs. new: true marks the tables and fields that are new or different compared with what is stored now: they get [new] in the text and a New tag in the app. It compares with what is stored now, not with the previous version: what changed since the previous version goes in note.
Every field needs a note: one short comment on what it holds ("place on the waitlist; empty once booked"), so the person can judge the field without reading code. A field without a note, or with one that is empty or only spaces, fails the whole call (Errors). Write label and every note in the language the person reads: they are shown as given, never translated.
tables:
| Field | Required | Limit | What it is |
|---|---|---|---|
name |
yes | 64 characters, unique | the table's name (bookings) |
label |
40 characters | what the table holds, in the person's language, shown after the name ("Bookings") | |
new |
a table this work adds | ||
columns |
40 | its fields, top to bottom |
columns:
| Field | Required | Limit | What it is |
|---|---|---|---|
name |
yes | 64 characters | the field's name |
note |
yes | 1–120 characters | a short comment on what the field holds ("booked, waitlisted or cancelled") |
type |
recommended | 40 characters | its type (uuid, text, int, timestamptz) |
ref |
64 characters | the table this field points to, shown as → table; a table that is not in this schema is accepted and named in warnings |
|
new |
a field that is new or different compared with now |
A misspelled or extra field is an error that lists what is allowed; nothing is ignored silently. Limits count characters (a CJK character is one). Everything in the call except id, topic and note is the schema's spec, at most 64 KB of JSON.
Example
{
"topic": "Booking data",
"tables": [
{
"name": "classes",
"label": "Classes",
"columns": [
{ "name": "id", "type": "uuid", "note": "class id" },
{ "name": "starts_at", "type": "timestamptz", "note": "when the class starts" }
]
},
{
"name": "bookings",
"label": "Bookings",
"columns": [
{ "name": "id", "type": "uuid", "note": "booking id" },
{ "name": "class_id", "type": "uuid", "ref": "classes", "note": "which class" },
{ "name": "status", "type": "text", "note": "booked, waitlisted or cancelled" },
{ "name": "waitlist_rank", "type": "int", "note": "place on the waitlist; empty once booked", "new": true }
]
}
]
}
The result
The schema follows the JSON as a text block. Read it over, then tell the person in one line what to look at; next says the same. For the example above, as its first version:
Schema "Booking data" v1
[new] = added or changed in this version; "→ table" = references that table; "# …" = what the field holds
classes (Classes)
id uuid # class id
starts_at timestamptz # when the class starts
bookings (Bookings)
id uuid # booking id
class_id uuid → classes # which class
status text # booked, waitlisted or cancelled
waitlist_rank int # place on the waitlist; empty once booked [new]
- One block per table: its
name, thelabelin parentheses, and[new]on a table withnew: true. Then one line per field: its name, its type,→ tablefor aref,#and itsnote, and[new]on a field withnew: true. A table withoutcolumnsshows(no columns). When the call has anote, a lineChanged in this version: …follows the first line. - These fixed words are always in English, whatever the account's language; what you wrote is shown as given, emoji included.
designId(dsg_…) is the design, the same for every version of thistopic,reviewId(dsn_…) is this version's id,versioncounts per account, kind andtopic(whichever box you pass or none),appUrlis the design's page in the app,/design/<designId>(anyone with the link can open it; only the account sends feedback), andboxIdis the box you passed asid(missing without one).warningsnames arefto a table that is not in this schema, such astable "bookings" column "member_id": ref "members" is not one of the tables in this diagram.
In the person's app
The person sees the schema on the design's page in their app and in the Designs list, and on the box's page under Schema when you passed a box's id. It is text there too, not a picture: each table is a card with its name, its label, and a New tag when the table is new; under it, one row per field with its name, type, → table and note. A new or changed field in a table that is not new has a New tag beside its name; a new table is tagged once, not field by field. On a phone a row shows the name and type on one line and the note under them; on a wider screen the rows line up in three columns, Column, Type and Notes. The app's own words (New, the headings) follow the app's language.
Under each table's card is Report here. It takes a picture of that table's card, and the person circles on it, talks and writes text on it, then sends. One report can hold notes on several tables.
Their feedback and the next version
- Their report on a version comes back to the conversation that wrote it, like review feedback, with or without a box: it wakes a conversation set up for app feedback, or arrives as
fromHumanon this conversation's next call to a design tool or to any tool on a box, or through Copy message for AI; read it withsandbox_reportandreportIdalone, noid. The message saysFeedback on schema "<topic>" v<n>: …. Each annotated image is the card of the table the person reported on, and that note'ssource.labelnames the version and the table, such asSchema “Booking data” v1 · bookings(in the app's language). Read the circled pictures, the voice transcript and the text written on the picture (notes[].labels). The message ends withChange it as asked, then call sandbox_schema again with the same topic for the next version.For it to return to this conversation on its own, the adapter must be 0.6.0 or later (0.5.4 or later when you passed a box'sid), and Claude Code's and Gemini CLI's feedback hooks must have been installed with 0.5.4 or later (What automatic return needs). Details:sandbox_draft. - There is no approval step. Change what they asked, then call
sandbox_schemaagain with the sametopic(and the sameidif you passed one), the whole spec again (anoteon every field) and a one-sentencenoteon what changed since the previous version: that is the next version (v2, v3…), from any box or none.
Errors
The call fails with a message that names the field; nothing is saved, and it does not count as use of a box: box not found (it must be one of yours; see sandbox_status or GET /v1/boxes) (the id you passed is not a box of this account; leave id out to write it without a box), topic is required: …, topic is at most 80 characters, note is at most 300 characters, spec is at most 64 KB, or what the schema check does not accept, as <path>: <problem>, the path being the arguments of your call:
tables[0].columns[2].note: required (a short comment on every field): that field has nonote, or an empty one. Give every field anote; the tool's input schema marks it as required too.tables[1].name: duplicate table name "bookings" (also used by spec.tables[0]): thespec.tables[0]in parentheses istables[0]in your calltables[0].columns[2]: unknown field "comment" (allowed: name, type, note, ref, new)spec: unknown field "table" (allowed: tables): a stray or misspelled top-level argument (every argument other thanid,topicandnotegoes to the schema check, which calls themspectogether)
Fix that field and call again.
Every tool and topic is listed in the tool reference.