sandbox_schema

動手做之前先排好要存的資料,用文字、不是圖:資料表、它的欄位,每個欄位寫型別、一句它放什麼的註解(每個欄位都必填),以及關聯到哪張表。Schema 以文字回到工具結果,人在 app 裡這份設計的頁面看到同一版的文字表格,在上面圈線、講話、打字回報。不需要箱子。

輸入 輸出
topic(最多 80 字,每一版都一樣)、tables(1–30 張,每個欄位都要有 note)、選填 note(最多 300 字:這版改了什麼)、選填 id(箱子,讓設計也出現在那個箱子頁) ok、designId(dsg_ 開頭,這份設計)、reviewId(dsn_ 開頭,這一版)、kind(schema)、topic、version、appUrl、有傳 id 時有 boxId、next,有提醒時有 warnings,人在 app 留話給這個對話時有 fromHuman;Schema 的文字以一段 text 接在 JSON 後面。沒有圖,也沒有 imageUrls

工具標題(MCP 的 title,有的 client 用它顯示這個工具)是 Lay out a data schema;註記為 readOnlyHint: false(成功一次就存一版)、destructiveHint: false、openWorldHint: false。

什麼時候用

新做或要改的東西,開發順序是:sandbox_draft(畫面)→ sandbox_architecture(服務)→ sandbox_schema(存的資料)→ 在箱子裡做 → sandbox_review。只有這次工作碰不到的那一步才跳過:沒有多存東西的改動就沒有 Schema。

這次工作會加或改要存的資料(新的表、新的欄位、某個欄位的意思改了)、或人要看 Schema 時,用 sandbox_schema。不要改寫 HTML、SVG、圖片、圖表或 Markdown 檔來代替:這個工具會排好、會留下每一版,也給人回報用的工具。

除了回來的是文字,其他都跟 sandbox_draft 一樣:不需要箱子,設計屬於帳號和這段對話,箱子停掉也還在。id 選填:傳這段工作的箱子,設計也會出現在那個箱子頁;呼叫在伺服器上做(只檢查、存起來,不畫圖),從不叫醒凍結的箱子,id 指的是還沒停掉的箱子時,成功就算對它的一次使用,呼叫的對話就是拿著它的對話(sandbox_draft)。

寫什麼

只列這次工作需要的表和欄位。new: true 標的是跟現在存的資料比起來新的或不一樣的表和欄位:文字裡標 [new],app 裡標「新增」。比的是現在存的資料,不是上一版:跟上一版比改了什麼寫在 note。

每個欄位都要有 note:一句簡短註解,說這個欄位放什麼(「候補第幾位;已預約就是空的」),人不看程式也能判斷這個欄位。有欄位沒給 note、給空字串或只有空白,整個呼叫都會失敗(見錯誤)。label 和每個 note 都用人讀的語言寫:照原樣顯示,不會翻譯。

tables:

欄位 必填 上限 說明
name 必填 64 字,不能重複 資料表名稱(bookings)
label 40 字 這張表存什麼,用人讀的語言,寫在表名後面(「預約」)
new 這次工作加的表
columns 40 欄 欄位,由上往下

columns:

欄位 必填 上限 說明
name 必填 64 字 欄位名
note 必填 1–120 字 一句簡短註解,說這個欄位放什麼(「已預約/候補/取消」)
type 建議寫 40 字 型別(uuid、text、int、timestamptz)
ref 64 字 這個欄位指到哪張表,顯示成 → 表名;指到這份 Schema 沒有的表照樣收,warnings 會提醒
new 跟現在比起來新的或不一樣的欄位

拼錯或多給的欄位一律報錯,錯誤會列出允許的欄位;不會默默忽略。上限算的是字元數(中文一個字算 1)。整個呼叫除了 id、topic、note 以外的部分是 Schema 的規格,JSON 最大 64 KB。

範例

{
  "topic": "預約資料",
  "tables": [
    {
      "name": "classes",
      "label": "課程",
      "columns": [
        { "name": "id", "type": "uuid", "note": "課程編號" },
        { "name": "starts_at", "type": "timestamptz", "note": "開課時間" }
      ]
    },
    {
      "name": "bookings",
      "label": "預約",
      "columns": [
        { "name": "id", "type": "uuid", "note": "預約編號" },
        { "name": "class_id", "type": "uuid", "ref": "classes", "note": "哪一堂課" },
        { "name": "status", "type": "text", "note": "已預約/候補/取消" },
        { "name": "waitlist_rank", "type": "int", "note": "候補第幾位;已預約就是空的", "new": true }
      ]
    }
  ]
}

回傳

Schema 的文字以一段 text 接在 JSON 後面。先讀過,再用一句話告訴人要看哪裡;next 也這樣寫。上面的範例當第一版送出時是:

Schema "預約資料" v1
[new] = added or changed in this version; "→ table" = references that table; "# …" = what the field holds

classes  (課程)
  id         uuid         # 課程編號
  starts_at  timestamptz  # 開課時間

bookings  (預約)
  id             uuid            # 預約編號
  class_id       uuid → classes  # 哪一堂課
  status         text            # 已預約/候補/取消
  waitlist_rank  int             # 候補第幾位;已預約就是空的  [new]
  • 一張表一段:name、括號裡的 label,new: true 的表後面加 [new]。接著一個欄位一行:欄位名、型別、有 ref 時 → 表名、# 和它的 note,new: true 的欄位後面加 [new]。沒有 columns 的表寫 (no columns)。呼叫有帶 note 時,第一行下面多一行 Changed in this version: …。
  • 這些固定字不管帳號是什麼語言,一律是英文;你寫的字照原樣出現,emoji 也一樣。
  • designId(dsg_ 開頭)是這份設計,這個 topic 的每一版都一樣,reviewId(dsn_ 開頭)是這一版的 id,version 照帳號、種類、topic 各自編號(不管傳哪個箱子或沒傳),appUrl 是 app 裡這份設計的頁面 /design/<designId>(拿到連結就打得開,只有帳號本人能回報),boxId 是你傳的 id 那個箱子(沒傳就沒有)。
  • warnings 會說 ref 指到這份 Schema 沒有的表,例如 table "bookings" column "member_id": ref "members" is not one of the tables in this diagram。

人在 app 看到的

人在 app 裡這份設計的頁面和「設計」清單看到它,有傳箱子 id 時也在那個箱子頁的「Schema」那一格。在 app 裡也是文字,不是圖:一張表一張卡片,上面是表名、label,新的表旁邊有「新增」小標籤;底下一個欄位一列:欄位名、型別、→ 表名、註解。不是新表裡的新欄位或改過的欄位,欄位名旁邊有「新增」小標籤;整張表都是新的就只在表名標一次,不再一個欄位一個欄位標。手機上一列是欄位名和型別一行、註解在下一行;寬的畫面排成「欄位|型別|說明」三欄。app 自己的字(「新增」、欄名)跟 app 的語言。

每張表的卡片底下有一個「回報這裡」:拿那張表的卡片截一張圖,人在上面圈線、講話、打字,然後送出。一次回報可以包含好幾張表的回報。

人的回報與下一版

  • 人對某一版的回報會回到寫那一版的對話,照驗收回饋的方式回來,有沒有箱子都一樣:叫醒設定好接收 app 回饋的對話,或在這個對話下一次呼叫設計工具或對任何箱子呼叫工具時以 fromHuman 回來,或經由 複製給 AI 的訊息;用 sandbox_report 只帶 reportId 讀,不帶 id。訊息會寫 Feedback on schema "<topic>" v<n>: …。每張圈過的圖都是人回報的那張表的卡片,那則回報的 source.label 寫著哪一版、哪張表,例如 Schema「預約資料」v1 · bookings(跟 app 的語言)。先看圈過的圖、語音逐字稿和人在圖上打的字(notes[].labels)。訊息最後是 Change it as asked, then call sandbox_schema again with the same topic for the next version.。要自動回到這個對話,轉接器要 0.6.0 以上(有傳箱子 id 時 0.5.4 以上),Claude Code 與 Gemini CLI 的回饋 hook 也要是 0.5.4 以上裝的(自動回到對話的條件)。細節見 sandbox_draft。
  • 沒有核准或定案的步驟。照人說的改,再用同一個 topic(傳過 id 就帶同一個 id)呼叫一次 sandbox_schema,規格整份重給(每個欄位都有 note),加一句 note 說跟上一版比改了什麼:這就是下一版(v2、v3…),不管從哪個箱子或沒有箱子。

錯誤

呼叫失敗時訊息會指出是哪個欄位;什麼都不會存,也不算使用任何箱子:box not found (it must be one of yours; see sandbox_status or GET /v1/boxes)(你傳的 id 不是這個帳號的箱子;不帶 id 就能不用箱子寫)、topic is required: …、topic is at most 80 characters、note is at most 300 characters、spec is at most 64 KB,或 Schema 檢查不接受的東西,格式是 <路徑>: <問題>,路徑就是你呼叫裡的參數:

  • tables[0].columns[2].note: required (a short comment on every field):那個欄位沒有 note 或是空的。每個欄位都給 note;工具的 input schema 也把它標成必填。
  • tables[1].name: duplicate table name "bookings" (also used by spec.tables[0]):括號裡的 spec.tables[0] 就是你呼叫裡的 tables[0]
  • tables[0].columns[2]: unknown field "comment" (allowed: name, type, note, ref, new)
  • spec: unknown field "table" (allowed: tables):多給或拼錯的最上層參數(除了 id、topic、note 以外的參數都會交給 Schema 檢查,它把這些合稱 spec)

改好那個欄位再呼叫一次。


所有工具與主題都列在工具參考。