Skip to main content
Glama

PLMHub boards

Draw on a doodle board

draw

Draw on a doodle board. The user sees each call appear live in the editor.

Doodle ops

A doodle is a board (default 1280 x 720 px, origin top-left, y grows down). Send ops as {"ops": [...]}; they apply in order, all or nothing. The answer lists the ids each op created, in order. Address elements by id; read the board for the ids it already has.

Positions: x, y is the CENTER of a shape or text, the top-left of a comment, the midpoint of a line. Omit them to place at the board's center. Later elements draw on top.

Create

  • {"op": "add", "role": "text" | "box" | "button" | "input" | "card" | "ellipse" | "line" | "image" | "comment" | "draw" | "triangle" | "diamond" | "star" | "frame", "x", "y", "w"?, "h"?, "id"?, ...style} Defaults (w x h): text 160 wide, box 180x110, card 240x150 (radius 12), button 150x46, input 220x44, ellipse 140x90, line 180 long, triangle 120x110, diamond/star 120x120, image 320x200 (needs "src": URL or data: URL), frame 360x240, draw needs "path". Pass your own "id" to address the element without reading back (it must be unused). Buttons and inputs are plain shapes: give them text with label.

  • {"op": "draw", "path": "M 10 10 L 90 90", "stroke"?, "strokeWidth"?} freehand; path coordinates are board coordinates.

  • {"op": "comment", "text", "x"?, "y"?, "w"?} a sticky note for the human.

  • {"op": "label", "id": shapeId, "text", "fontSize"? = 16, "fill"?, "labelId"?} text centered inside a shape that follows it when the shape moves. Empty text removes it.

  • {"op": "duplicate", "id", "newId"?, "dx"? = 20, "dy"? = 20}

Change

  • {"op": "update", "id", ...style} (fields below; "x"/"y"/"w"/"h" set position and size)

  • {"op": "move", "id", "dx", "dy"} or {"op": "move", "id", "x", "y"}; moving a frame moves its children too.

  • {"op": "reorder", "id", "to": "front" | "back" | "forward" | "backward"}

  • {"op": "delete", "id"} · {"op": "clear"} removes everything.

Style fields (add and update)

fill, stroke, backgroundColor (CSS colors) · strokeWidth · opacity 0..1 · angle (degrees) · radius (corner radius; or rx, ry) · borderStyle "solid" | "dashed" | "dotted" | "dashdot" or strokeDashArray [numbers] · shadow "none" | "soft" | "medium" | "hard", {color, blur, offsetX, offsetY} or null · gradient {"type": "linear" | "radial", "stops": [{"offset": 0, "color": "#6366f1"}, {"offset": 1, "color": "#ec4899"}], "coords"?: 0..1 units} (replaces fill) · flipX, flipY, skewX, skewY · text, fontSize, fontFamily, fontWeight, fontStyle ("italic"), textAlign, underline, linethrough, lineHeight, charSpacing, padding · name (layer name), visible, locked (true/false).

Structure

  • {"op": "group", "ids": [2 or more], "name"?, "groupId"?} returns the group id. {"op": "ungroup", "group": groupId} or {"op": "ungroup", "ids": [...]}.

  • {"op": "wrap", "ids": [...], "direction"?, "justify"?, "align"?, "gap"?, "padding"?, "frameId"?} puts the elements in a new auto-layout frame and returns its id. {"op": "unwrap", "id": frameId} removes the frame and keeps its children.

  • {"op": "reparent", "ids": [...], "frame": frameId} moves elements into a frame; "frame": null takes them out.

  • {"op": "layout", "id": frameId, "mode"?: "block" | "flex", "direction"?: "row" | "col", "justify"?: "start" | "center" | "end" | "between", "align"?: "start" | "center" | "end" | "stretch", "gap"? = 16, "padding"? = 16, "wMode"?, "hMode"?: "hug" | "fixed", "w"?, "h"?} A flex frame lines its children up (row = left to right, col = top to bottom) and hugs them, or keeps the fixed w/h. A block frame leaves children where they are.

  • {"op": "size", "id", "w"?, "h"?: "fixed" | "hug" | "fill", "minW"?, "maxW"?, "minH"?, "maxH"?} how a child of a flex frame sizes; fill shares the frame's spare room; null clears a min/max.

Board

  • {"op": "bg", "color": "#f8fafc"} or {"op": "bg", "src": imageUrl} (an image behind everything).

  • {"op": "board", "w"?, "h"?, "autoH"?: true, "padding"?, "minH"?, "maxH"?} autoH grows the board to fit its content plus padding, within minH..maxH.

Errors name the failing op (ops[2] (move): ...): 400 for a bad field, 404 for an unknown id, 409 for an id already in use. Fix that op and send the batch again.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
opsYesThe ops, in order
boardYesThe board's 22-character code, or its URL

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.4/5.0
Behavior5/5

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

With no annotations, the description carries the full behavioral burden and does so thoroughly: ops apply in order atomically, the response lists created ids, elements are addressed by id, positioning semantics are defined, and errors include failing op indices with HTTP statuses. It also discloses that the user sees each call appear live in the editor.

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

Conciseness5/5

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

The description is long, but the complexity of the ops language justifies the length. It is front-loaded with purpose and live-visibility context and then organized into clear sections for create, change, style, structure, board, and errors.

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?

For a high-complexity batch-drawing tool with no output schema and no annotations, the description covers the critical operational details an agent needs: atomicity, id addressing, positioning defaults, op syntax, style fields, structural operations, and error behavior.

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

Parameters5/5

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

Although the schema coverage is 100%, the schema only defines board and an opaque ops array. The description adds extensive meaning for ops: op types, required fields, defaults, style fields, group/frame semantics, layout behavior, and error handling.

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

Purpose4/5

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

The description states a specific verb and resource: draw on a doodle board by sending ops. It is clear what the tool does, but it does not explicitly distinguish itself from sibling tools such as write_board or edit_text.

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

Usage Guidelines3/5

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

Usage is implied through the detailed ops model and the note that calls appear live in the editor. However, the description never states when to choose draw over write_board, edit_text, or read_board, nor does it provide exclusions.

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