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, the label in parentheses, and [new] on a table with new: true. Then one line per field: its name, its type, → table for a ref, # and its note, and [new] on a field with new: true. A table without columns shows (no columns). When the call has a note, a line Changed 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 this topic, reviewId (dsn_…) is this version's id, version counts per account, kind and topic (whichever box you pass or none), appUrl is the design's page in the app, /design/<designId> (anyone with the link can open it; only the account sends feedback), and boxId is the box you passed as id (missing without one).
  • warnings names a ref to a table that is not in this schema, such as table "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 fromHuman on this conversation's next call to a design tool or to any tool on a box, or through Copy message for AI; read it with sandbox_report and reportId alone, no id. The message says Feedback on schema "<topic>" v<n>: …. Each annotated image is the card of the table the person reported on, and that note's source.label names the version and the table, such as Schema “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 with Change 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's id), 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_schema again with the same topic (and the same id if you passed one), the whole spec again (a note on every field) and a one-sentence note on 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 no note, or an empty one. Give every field a note; the tool's input schema marks it as required too.
  • tables[1].name: duplicate table name "bookings" (also used by spec.tables[0]): the spec.tables[0] in parentheses is tables[0] in your call
  • tables[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 than id, topic and note goes to the schema check, which calls them spec together)

Fix that field and call again.


Every tool and topic is listed in the tool reference.