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 layout write stores no figures: params sent on a figure block are dropped. A board's figures are measured in a chat when a person presses its Refresh button, and Well keeps them apart from the layout, as the board's dated snapshot. 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

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.
origin_sourceNoCreate only: how this board was made. Unstated, it is "template" when origin_template_id is set, else "scratch". Send "photo" when the board comes from reading an image of a dashboard.
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

NameRequiredDescriptionDefault
cardNo
nameNo
errorNo
tilesNo
createdNo
refusalNo
successYes
versionNo
can_editNo
conflictNo
parametersNo
author_nameNo
layout_modeNo
grid_columnsNo
origin_sourceNo
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. Changed4 schema fields changed
    • addedInput schema / properties / origin_source
      Added value: +{
      +  "description": "Create only: how this board was made. Unstated, it is \"template\" when origin_template_id is set, else \"scratch\". Send \"photo\" when the board comes from reading an image of a dashboard.",
      +  "enum": [
      +    "scratch",
      +    "template",
      +    "photo",
      +    "copy"
      +  ],
      +  "type": "string"
      +}
    • changedInput schema / properties / tiles / items / properties / source / description
      Previous value: -"The data feed this block draws (\"cash-position\", \"avg-burn\", \"mrr\", \"runway\", \"cost-structure\", \"cash-forecast\", \"cash-flow-waterfall\"). Required on a figure block and refused on a note."New value: +"The data feed this block draws (\"cash-position\", \"avg-burn\", \"mrr\", \"runway\", \"cost-structure\", \"cash-forecast\", \"cash-flow-waterfall\", \"logged-time\"). Required on a figure block and refused on a note."
    • changedInput schema / properties / tiles / items / properties / type / description
      Previous value: -"\"figure\" for every block that names a feed; \"text\" for a note. \"kpi\", \"composition\", \"trend\", \"flow-waterfall\" are older names for a figure block and are stored as \"figure\"."New value: +"\"figure\" for every finance block that names a feed; \"text\" for a note; \"logged-time\" is a \"kpi\" block. \"kpi\", \"composition\", \"trend\", \"flow-waterfall\" are older names for a figure block on a finance feed and are stored as \"figure\"."
    • addedOutput schema / properties / origin_source
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
  2. Changed1 schema field changed
    • changedInput schema / properties / origin_template_id / anyOf
      Previous value: -[
      -  {
      -    "enum": [
      -      "investor-report"
      -    ],
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "maxLength": 64,
      +    "type": "string"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
  3. Changed18 schema fields changed
    • removedInput schema / properties / tiles / items / properties / mark / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / tiles / items / properties / mark / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedInput schema / properties / tiles / items / properties / source / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / tiles / items / properties / source / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedInput schema / properties / tiles / items / properties / title / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / tiles / items / properties / title / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / author_name / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / author_name / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / grid_columns / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / grid_columns / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / origin_template_id / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / origin_template_id / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / tiles / items / properties / mark / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / tiles / items / properties / mark / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / tiles / items / properties / source / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / tiles / items / properties / source / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / tiles / items / properties / title / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / tiles / items / properties / title / type
      Added value: +[
      +  "string",
      +  "null"
      +]
  4. Changed1 schema field changed
    • changedInput schema / properties / tiles / items / properties / params / description
      Previous value: -"A note's words, as { content }. Send nothing on a figure block: its figures are measured when the board is opened, and params sent on one are dropped."New value: +"A note's words, as { content }. Send nothing on a figure block: a layout write stores no figures, and params sent on one are dropped."
  5. 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."
  6. 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"
      +}
  7. 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"
      +}
  8. 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"
      +}
  9. 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"
      +}
  10. Added

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructiveHint=true and readOnlyHint=false; the description goes well beyond by disclosing the permission model (author, admin, owner), the compare-and-set refusal protocol and what a refusal returns (the canvas as stored now, plus can_edit and author_name), that illegal layouts are REPORTED and never repaired, and that params on figure blocks are silently dropped. These are non-obvious failure and side-effect behaviors an agent cannot infer from the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the one-line purpose before any detail, and the CREATE / UPDATE / WHO EDITS / TILES / STORE CARD ANSWERS sections make a long description scannable. It is verbose for its length, with some restatement (the no-figures point appears in both the arrangement paragraph and TILES), but the density is largely justified by a 13-parameter tool with three distinct write modes.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and annotations cover the safety profile. Given create/update branching, nested tiles and card-answer writes, the description covers everything an agent needs: required fields per mode, versioning, permissions, geometry units, and misrouting alternatives.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents each parameter; the description still adds operational meaning beyond it, notably the version discipline ('Never guess a version and never resend a rejected one — re-read with well_show_canvas first') and the S/M/L versus explicit w/h precedence. Marginal gap: workspace_id and conversation_id handling are left entirely to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource plus the two modes it covers: 'Write one canvas: create a board, or replace the arrangement of a board that already exists.' It further narrows scope by declaring it writes an ARRANGEMENT and never a figure, which separates it cleanly from well_render_cash_position, well_show_canvas and well_delete_canvas named later.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit mode-selection rules: 'CREATE — omit canvas_view_id' versus 'UPDATE — pass canvas_view_id AND the version', plus a third distinct path for store_card_answers with 'and nothing else'. It also states an exclusion condition ('When can_edit is false, do not call this') and a closing do-not-use list naming the specific alternative tools for each wrong use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources