---
title: "save_look"
description: "Persist a composed look on a project as a new version, so the next strip starts there."
canonical: "https://shotops.dev/docs/mcp/reference/save_look"
last_updated: "2026-09-07"
shotops_mcp_version: "0.9.6"
result_contract_version: "2.0.0"
---
# save_look

Persist a composed look on a project as a new version, so the next strip starts there.

## What it does

- Writes
- Account required

Make a design reusable. Once the user is happy with how a strip looks, this stores the styling on a project as a new version — so their next render, here or in the web app, starts from what they approved instead of a default. Styling only: caption words are never stored, so pass those per render.

You can ask: “Save the Look I just approved as a new project version.”

## Input

- `project`stringoptionalat least 1 character Which ShotOps project to target: its id (as returned by read_look/save_project), OR its NAME as the user says it — matched exactly, ignoring case and surrounding spaces. Omit = your most recently edited project. A name that matches none or several is refused with the account's projects listed, never resolved by guessing.
- `look`objectrequired The look JSON to save (styling only — the shape read_look returns / describe_look documents). Caption text is NOT part of a look; pass it per render instead.
  - `schema`numberoptional Look schema version. The server stamps it on every save (save_look); read_look returns it. Any input value is ignored — never set it yourself.
  - `panelPresetId`stringoptional Canvas size preset, e.g. r69 = 1290×2796. Default r69.
  - `bgMode`stringoptional One gradient across the strip, or a flat colour per panel. Default gradient. one of`gradient``perPanel`
  - `gradientStops`arrayoptionalat least 1 item Canonical gradient stops — { color (hex), at (0–1 position) } — wins over gradientFrom/gradientTo/gradientDir when present. Read this back for the full stop list a Studio-authored look may carry; the trio below is only ever a two-stop summary. each item object - `color`stringrequired - `at`numberrequiredmin 0max 1
  - `gradientAngle`numberoptional Canonical gradient angle in degrees, clockwise from "to top" (CSS convention: 90 = right, 180 = down, the gradientDir:"vertical" equivalent). Paired with gradientStops.
  - `gradientFrom`stringoptional Gradient start (hex) — a DERIVED mirror of gradientStops[0], re-derived from the stops on every render and reload. Setting it ALONE changes nothing: set gradientStops to change the backdrop. Default #1b1b2e.
  - `gradientTo`stringoptional Gradient end (hex) — a DERIVED mirror of the last gradientStops entry, re-derived from the stops on every render and reload. Setting it ALONE changes nothing: set gradientStops to change the backdrop. Default #0a0a14.
  - `gradientDir`stringoptional A DERIVED mirror of gradientAngle (nearest axis), re-derived on every render and reload. Setting it ALONE changes nothing: set gradientAngle to turn the gradient. Default vertical. one of`horizontal``vertical`
  - `panelColors`objectoptional Per-panel flat colours (mode perPanel), keyed by panel id.
  - `shadow`booleanoptional Drop shadow under the phones. Default true.
  - `floorReflection`booleanoptional Flipped, faded floor reflection under each phone. Default false.
  - `panelBackgrounds`objectoptional Per-frame background overrides keyed by panel id (web-authored; agents rarely set these).
  - `captionStyles`objectoptional Per-panel caption STYLE arrays keyed by panel id (styling only, no words — for caption text pass style.captions).
  - `shots`arrayoptionalat most 60 items One entry per SCREENSHOT/DEVICE, flattened in panel order. Each entry has its OWN `look`. For screenshots:[[left,right]], pass two shots with the SAME panelId ("panel-1") to give the two devices different colours/materials/angles. This is how a look varies devices; style.shotLook cannot. each item object - `panelId`stringoptional Panel containing this screenshot/device. Panels are panel-1…panel-N in slot order; REPEAT the same panelId for multiple devices in one panel. A saved look’s own ids are remapped by ordinal. - `look`objectoptional This individual screenshot/device’s styling — the SAME fields as style.shotLook. - `angle`stringoptional Camera preset. Default front. one of`front``left``right` - `cameraPos`one of 2 shapesoptional Manual camera position override; null/omit = use the angle preset. 1`x` - `x`numberrequired - `y`numberrequired - `z`numberrequired 2`null` - `roll`string or numberoptional -45–45° clock-hand tilt. Default 0. - `phoneHeight`string or numberoptional -45–45° clock-hand tilt. Default 0. - `hOffset`string or numberoptional -45–45° clock-hand tilt. Default 0. - `vOffset`string or numberoptional -45–45° clock-hand tilt. Default 0. - `material`stringoptional Device body. Default real. one of`real``clay` - `colorway`stringoptional Body colour (material "real"). Default silver. These are finish SLOTS — each device binds its own real finish (silver = iPhone Silver / Pixel Moonstone). "green" is Pixel-only (Jade); other devices fall back to their first finish. one of`orange``blue``silver``green``custom` - `customColor`stringoptional Hex body colour when colorway is "custom". - `finish`string or numberoptional -45–45° clock-hand tilt. Default 0. - `clearcoat`string or numberoptional -45–45° clock-hand tilt. Default 0. - `clayTone`stringoptional Clay tone (material "clay"). Default grey. one of`grey``white``charcoal``custom` - `clayCustom`stringoptional Hex clay colour when clayTone is "custom". - `flatScreen`booleanoptional Render the screen flat (no curvature). Default false. - `glare`booleanoptional Screen glare. Default false. - `lighting`booleanoptional Scene lighting. Default true. - `reflections`booleanoptional Body reflections. Default false. - `clipToFrame`booleanoptional Clip this complete device composite to its owning frame. Default false (overflow allowed).
- `sourceName`stringoptionalat most 120 characters Where this styling came from (shown in the studio UI).

## Result

- `ok`booleanoptional
- `project`objectoptional
  - `id`stringrequired
  - `name`stringrequired
- `version`numberoptional
- `message`stringoptional

## Example

That request maps to this call:

```
{
  "name": "save_look",
  "arguments": {
    "project": "Tidebook 2.4 launch",
    "look": {
      "bgMode": "gradient",
      "gradientStops": [
        {
          "color": "#0d2b3d",
          "at": 0
        },
        {
          "color": "#6aa9c9",
          "at": 1
        }
      ]
    }
  }
}
```

The version number it was saved as. Earlier versions stay readable through read_look.

## Access and cost

- Needs an account. Not available to an anonymous hosted caller.
- Free. Saving a look spends no cloud credits and renders nothing.

## When it refuses

- **A field is rejected as out of range.**
  The reply names the field, its range and the fix in prose. This is the one door where an out-of-range pose would get persisted, which is why it is checked here rather than at render time.

- **Nothing you render afterwards picks the new version up.**
  Something is held. A held version is the project default until release_look clears it — saving a newer one does not override it.

## Where this fits

- [Versions, holding and following latest](https://shotops.dev/docs/mcp/workflows/saved-project)

## What the agent is told

The title and description the server publishes on `tools/list` — this is the copy a model chooses between, reproduced verbatim.

Save look

Make a design the user liked reusable: persists a composed ShotOps look (styling only) on a project as a new version, so their next strip — rendered here or opened in the web app — starts from the design they just approved instead of a default. Offer it once they are happy with how a strip looks. Use describe_look for the field catalog; caption words are never stored, so pass those per render. No store credential is involved.
