Skip to main content
Glama

Write a canvas

well_upsert_canvas
Destructive

Write one canvas: create a board, or replace the arrangement of a board that already exists.

Writes an ARRANGEMENT — the blocks a canvas holds and where they sit. It never computes a figure and never stores one: a figure block names a data feed and stores the question rather than the answer, and a note carries its own words.

CREATE — omit canvas_view_id. name, layout_mode and tiles are required, and layout_mode is "free": every board is drawn on the infinite board.

WHO EDITS — anyone in the workspace can create a board and becomes its author. Only a board's author, a workspace admin or the owner can change it afterwards; every other member reads it. well_show_canvas returns can_edit for this caller. When can_edit is false, do not call this: tell the user the board is read-only for them, name who can change it, and offer to ask its author. A write by someone else is refused with can_edit false and author_name, and changes nothing.

UPDATE — pass canvas_view_id AND the version well_show_canvas returned for it. The write is compare-and-set on that version: if the canvas moved since you read it, the call is refused and comes back carrying the canvas as stored now, so you re-apply your one change instead of overwriting somebody else's. Never guess a version and never resend a rejected one — re-read with well_show_canvas first.

STORE CARD ANSWERS — pass canvas_view_id, the version you hold, and store_card_answers, and nothing else. Each entry names a block by tile_id and a card: "exemptions" or "cash_scope". The server reads the reader's answer to that card in this conversation, and every option that card offered. It writes both into the block's binding.scope and changes no other key of any block. The write is compare-and-set on the version and returns the new version. It is refused when the reader has not answered that card in this conversation, or when a newer card is on screen than the one the reader answered.

TILES — place them yourself. h is whole cells, and x, y and w may carry a fraction of a cell, to a hundredth; send back the position and width well_show_canvas returned. A figure block may send a size instead of w and h: "S" (4 × 3), "M" (7 × 6), "L" (11 × 8), the smallest rectangle that draws that size on the board a conversation draws. S draws the figure, M and L its chart. When the user asks for a size, send it rather than guessing a rectangle. A canvas takes any non-overlapping layout at or above each block's minimum size, negative coordinates included. A layout that is not already legal is REPORTED, never repaired — nothing here moves a block whose position you stated. Use the geometry helpers in @wellapp/shared/canvas-tiles to compute the rectangles. Every block that names a feed is type "figure" with mark null: the feed decides which chart it draws. A figure block stores no params. Params sent on one are dropped, not stored, because a stored figure would stop being true the day after it is written, and the board is measured again each time it is opened. A note (type "text") carries its words as params { content }.

Do NOT use this to answer a question about a figure (ask well_render_cash_position, well_render_burn, well_render_runway), to read a board (well_show_canvas), or to change a canvas's origin template — create a new canvas for that.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameNoThe canvas's label. Required on create.
tilesNoThe blocks the canvas holds, in full. Required on create; on update it REPLACES the stored set.
versionNoThe version well_show_canvas returned. Required when canvas_view_id is set; refused without it.
parametersNoCanvas-wide values stored beside the blocks. No block reads them: what a block asks its feed is its own `binding`, and a window named here reaches nothing.
layout_modeNoCreate only: "free", which places blocks anywhere on the infinite board.
workspace_idNoTarget workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which.
canvas_view_idNoThe canvas to write. Omit to create one; pass it with `version` to change one that exists.
conversation_idNoThe conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.
idempotency_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
workspace_scopeNoRestrict this call to the family: the token's primary workspace plus the company workspaces under it that you are a member of. Unrelated authorized workspaces drop out of the read; row reads merge across the family, while a per-workspace figure still reports exactly one workspace.
origin_template_idNoCreate only: which template this canvas came from. Provenance, not identity.
store_card_answersNoUpdate only, with no tiles, name or parameters: the blocks that took a card answer the reader gave in this conversation, and the card each one took. The server writes the answer and the options that card offered into each block's binding.scope.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
cardNo
nameNo
errorNo
tilesNo
createdNo
refusalNo
successYes
versionNo
can_editNo
conflictNo
parametersNo
author_nameNo
layout_modeNo
grid_columnsNo
canvas_view_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
newly_connectedNoTools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. The connected-line rule says when to tell an entry; when it does, one line per entry, in the person's language: "<name> is connected.". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later.
origin_template_idNo
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed3 schema fields changed
    • addedOutput schema / properties / author_name
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ]
      +}
    • addedOutput schema / properties / can_edit
      Added value: +{
      +  "type": "boolean"
      +}
    • changedOutput schema / properties / newly_connected / description
      Previous value: -"Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. Start the reply with one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later."New value: +"Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. The connected-line rule says when to tell an entry; when it does, one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later."
  2. Changed1 schema field changed
    • addedInput schema / properties / workspace_scope
      Added value: +{
      +  "description": "Restrict this call to the family: the token's primary workspace plus the company workspaces under it that you are a member of. Unrelated authorized workspaces drop out of the read; row reads merge across the family, while a per-workspace figure still reports exactly one workspace.",
      +  "enum": [
      +    "family"
      +  ],
      +  "type": "string"
      +}
  3. Changed1 schema field changed
    • addedOutput schema / properties / newly_connected
      Added value: +{
      +  "description": "Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. Start the reply with one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "name": {
      +        "type": "string"
      +      },
      +      "service_id": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "name",
      +      "service_id"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
  4. Changed12 schema fields changed
    • changedInput schema / properties / tiles / items / properties / binding / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": {},
      -    "properties": {
      -      "period": {
      -        "description": "The window this block reads. Omit it for a block that reads as of now; a \"mrr\", \"runway\" or \"cash-forecast\" block takes no period, and a \"cost-structure\" block takes one whole month, first day to last day.",
      -        "properties": {
      -          "from": {
      -            "description": "First day of the window, YYYY-MM-DD.",
      -            "type": "string"
      -          },
      -          "to": {
      -            "description": "Last day of the window, inclusive, YYYY-MM-DD.",
      -            "type": "string"
      -          }
      -        },
      -        "required": [
      -          "from",
      -          "to"
      -        ],
      -        "type": "object"
      -      },
      -      "scope": {
      -        "additionalProperties": {},
      -        "description": "The reader's answers to this block's choice cards, each beside the options its card offered, so a later run of the block reuses them. Never a figure. Send it back as well_show_canvas returned it. A new answer is stored with store_card_answers, not written here.",
      -        "properties": {
      -          "counted_account_types": {
      -            "description": "The account types the reader counted as cash on the cash-scope card.",
      -            "items": {
      -              "maxLength": 200,
      -              "type": "string"
      -            },
      -            "maxItems": 200,
      -            "type": "array"
      -          },
      -          "exempt_categories": {
      -            "description": "The category keys the reader exempted from burn on the exemption card.",
      -            "items": {
      -              "maxLength": 200,
      -              "type": "string"
      -            },
      -            "maxItems": 200,
      -            "type": "array"
      -          },
      -          "offered_account_types": {
      -            "description": "Every account type that card offered when the reader answered.",
      -            "items": {
      -              "maxLength": 200,
      -              "type": "string"
      -            },
      -            "maxItems": 200,
      -            "type": "array"
      -          },
      -          "offered_categories": {
      -            "description": "Every category key that card offered when the reader answered.",
      -            "items": {
      -              "maxLength": 200,
      -              "type": "string"
      -            },
      -            "maxItems": 200,
      -            "type": "array"
      -          }
      -        },
      -        "type": "object"
      -      }
      -    },
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": {},
      +    "properties": {
      +      "period": {
      +        "description": "The window this block reads. Omit it for a block that reads as of now; a \"mrr\", \"runway\" or \"cash-forecast\" block takes no period, and a \"cost-structure\" block takes one whole month, first day to last day.",
      +        "properties": {
      +          "from": {
      +            "description": "First day of the window, YYYY-MM-DD.",
      +            "type": "string"
      +          },
      +          "to": {
      +            "description": "Last day of the window, inclusive, YYYY-MM-DD.",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "from",
      +          "to"
      +        ],
      +        "type": "object"
      +      },
      +      "scope": {
      +        "additionalProperties": {},
      +        "description": "The reader's answers to this block's choice cards, each beside the options its card offered, so a later run of the block reuses them. Never a figure. Send it back as well_show_canvas returned it, or leave it out: a block that keeps its tile_id and its feed keeps the answers it holds. A new answer is stored with store_card_answers, not written here.",
      +        "properties": {
      +          "counted_account_types": {
      +            "description": "The account types the reader counted as cash on the cash-scope card.",
      +            "items": {
      +              "maxLength": 200,
      +              "type": "string"
      +            },
      +            "maxItems": 200,
      +            "type": "array"
      +          },
      +          "exempt_categories": {
      +            "description": "The category keys the reader exempted from burn on the exemption card.",
      +            "items": {
      +              "maxLength": 200,
      +              "type": "string"
      +            },
      +            "maxItems": 200,
      +            "type": "array"
      +          },
      +          "offered_account_types": {
      +            "description": "Every account type that card offered when the reader answered.",
      +            "items": {
      +              "maxLength": 200,
      +              "type": "string"
      +            },
      +            "maxItems": 200,
      +            "type": "array"
      +          },
      +          "offered_categories": {
      +            "description": "Every category key that card offered when the reader answered.",
      +            "items": {
      +              "maxLength": 200,
      +              "type": "string"
      +            },
      +            "maxItems": 200,
      +            "type": "array"
      +          }
      +        },
      +        "type": "object"
      +      }
      +    },
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedInput schema / properties / tiles / items / properties / layout / description
      Previous value: -"The block's rectangle in cells: top-left cell and spans. x, y and h are whole cells; w may carry a fraction of a cell, to a hundredth."New value: +"The block's rectangle in cells: top-left corner and spans. h is whole cells; x, y and w may carry a fraction of a cell, to a hundredth. w and h come from `size` when the block sends one, and are required otherwise."
    • removedInput schema / properties / tiles / items / properties / layout / properties / x / maximum
      Removed value: -9007199254740991
    • removedInput schema / properties / tiles / items / properties / layout / properties / x / minimum
      Removed value: --9007199254740991
    • addedInput schema / properties / tiles / items / properties / layout / properties / x / multipleOf
      Added value: +0.01
    • changedInput schema / properties / tiles / items / properties / layout / properties / x / type
      Previous value: -"integer"New value: +"number"
    • removedInput schema / properties / tiles / items / properties / layout / properties / y / maximum
      Removed value: -9007199254740991
    • removedInput schema / properties / tiles / items / properties / layout / properties / y / minimum
      Removed value: --9007199254740991
    • addedInput schema / properties / tiles / items / properties / layout / properties / y / multipleOf
      Added value: +0.01
    • changedInput schema / properties / tiles / items / properties / layout / properties / y / type
      Previous value: -"integer"New value: +"number"
    • changedInput schema / properties / tiles / items / properties / layout / required
      Previous value: -[
      -  "x",
      -  "y",
      -  "w",
      -  "h"
      -]New value: +[
      +  "x",
      +  "y"
      +]
    • addedInput schema / properties / tiles / items / properties / size
      Added value: +{
      +  "description": "A figure block's size: \"S\" is 4 × 3, \"M\" is 7 × 6, \"L\" is 11 × 8. S draws the figure, M and L its chart. The write takes the block's w and h from it; a w or h you also send wins.",
      +  "enum": [
      +    "S",
      +    "M",
      +    "L"
      +  ],
      +  "type": "string"
      +}
  5. Changed9 schema fields changed
    • removedInput schema / properties / grid_columns
      Removed value: -{
      -  "anyOf": [
      -    {
      -      "maximum": 9007199254740991,
      -      "minimum": -9007199254740991,
      -      "type": "integer"
      -    },
      -    {
      -      "type": "null"
      -    }
      -  ],
      -  "description": "Create only: the column count a grid canvas clamps against. A free canvas has no referent for one."
      -}
    • addedInput schema / properties / layout_mode / const
      Added value: +"free"
    • changedInput schema / properties / layout_mode / description
      Previous value: -"Create only, and fixed afterwards: \"grid\" clamps blocks into columns, \"free\" places them anywhere."New value: +"Create only: \"free\", which places blocks anywhere on the infinite board."
    • removedInput schema / properties / layout_mode / enum
      Removed value: -[
      -  "grid",
      -  "free"
      -]
    • addedInput schema / properties / store_card_answers
      Added value: +{
      +  "description": "Update only, with no tiles, name or parameters: the blocks that took a card answer the reader gave in this conversation, and the card each one took. The server writes the answer and the options that card offered into each block's binding.scope.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "card": {
      +        "description": "The choice card the reader answered.",
      +        "enum": [
      +          "exemptions",
      +          "cash_scope"
      +        ],
      +        "type": "string"
      +      },
      +      "tile_id": {
      +        "description": "The block that took the reader's answer.",
      +        "minLength": 1,
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "tile_id",
      +      "card"
      +    ],
      +    "type": "object"
      +  },
      +  "maxItems": 120,
      +  "minItems": 1,
      +  "type": "array"
      +}
    • changedInput schema / properties / tiles / items / properties / binding / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": {},
      -    "properties": {
      -      "period": {
      -        "description": "The window this block reads. Omit it for a block that reads as of now; a \"mrr\", \"runway\" or \"cash-forecast\" block takes no period, and a \"cost-structure\" block takes one whole month, first day to last day.",
      -        "properties": {
      -          "from": {
      -            "description": "First day of the window, YYYY-MM-DD.",
      -            "type": "string"
      -          },
      -          "to": {
      -            "description": "Last day of the window, inclusive, YYYY-MM-DD.",
      -            "type": "string"
      -          }
      -        },
      -        "required": [
      -          "from",
      -          "to"
      -        ],
      -        "type": "object"
      -      },
      -      "scope": {
      -        "additionalProperties": {},
      -        "description": "The feed's own narrowing arguments.",
      -        "propertyNames": {
      -          "type": "string"
      -        },
      -        "type": "object"
      -      }
      -    },
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": {},
      +    "properties": {
      +      "period": {
      +        "description": "The window this block reads. Omit it for a block that reads as of now; a \"mrr\", \"runway\" or \"cash-forecast\" block takes no period, and a \"cost-structure\" block takes one whole month, first day to last day.",
      +        "properties": {
      +          "from": {
      +            "description": "First day of the window, YYYY-MM-DD.",
      +            "type": "string"
      +          },
      +          "to": {
      +            "description": "Last day of the window, inclusive, YYYY-MM-DD.",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "from",
      +          "to"
      +        ],
      +        "type": "object"
      +      },
      +      "scope": {
      +        "additionalProperties": {},
      +        "description": "The reader's answers to this block's choice cards, each beside the options its card offered, so a later run of the block reuses them. Never a figure. Send it back as well_show_canvas returned it. A new answer is stored with store_card_answers, not written here.",
      +        "properties": {
      +          "counted_account_types": {
      +            "description": "The account types the reader counted as cash on the cash-scope card.",
      +            "items": {
      +              "maxLength": 200,
      +              "type": "string"
      +            },
      +            "maxItems": 200,
      +            "type": "array"
      +          },
      +          "exempt_categories": {
      +            "description": "The category keys the reader exempted from burn on the exemption card.",
      +            "items": {
      +              "maxLength": 200,
      +              "type": "string"
      +            },
      +            "maxItems": 200,
      +            "type": "array"
      +          },
      +          "offered_account_types": {
      +            "description": "Every account type that card offered when the reader answered.",
      +            "items": {
      +              "maxLength": 200,
      +              "type": "string"
      +            },
      +            "maxItems": 200,
      +            "type": "array"
      +          },
      +          "offered_categories": {
      +            "description": "Every category key that card offered when the reader answered.",
      +            "items": {
      +              "maxLength": 200,
      +              "type": "string"
      +            },
      +            "maxItems": 200,
      +            "type": "array"
      +          }
      +        },
      +        "type": "object"
      +      }
      +    },
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedInput schema / properties / tiles / items / properties / layout / description
      Previous value: -"The block's rectangle in grid cells: top-left cell and spans. x, y and h are whole cells; w is whole cells on a grid canvas and may carry a fraction of a cell, to a hundredth, on a free one."New value: +"The block's rectangle in cells: top-left cell and spans. x, y and h are whole cells; w may carry a fraction of a cell, to a hundredth."
    • addedOutput schema / properties / card
      Added value: +{
      +  "enum": [
      +    "exemptions",
      +    "cash_scope"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / refusal
      Added value: +{
      +  "enum": [
      +    "no_conversation",
      +    "no_answer",
      +    "card_unanswered",
      +    "other_workspace",
      +    "unstorable_answer"
      +  ],
      +  "type": "string"
      +}
  6. Added

TDQS

Score is being calculated.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources