tesserae-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| TESSERAE_URL | No | The URL of the Tesserae instance (default: http://127.0.0.1:8765) | http://127.0.0.1:8765 |
| TESSERAE_MCP_TOKEN | No | The MCP API token from Tesserae settings. Omit if running on the same machine (loopback trusted). |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| list_widgetsA | List every widget that can be placed on a canvas (with its fragments) and the available theme/style/font appearance options. |
| get_widget_optionsA | Get the configurable options for one widget, so you can fill an element's "options" correctly (e.g. a weather widget's location). Each option carries a "format" hint for its type. Big choice lists (HA entity pickers) are omitted by default (the option shows "choices_count" + a "choices_endpoint"); pass include_choices=True to inline them, or call get_widget_choices() to page. |
| get_widget_choicesA | Page through the choice rows for one of a widget's options (kept out of get_widget_options so a picker with hundreds of entries doesn't bloat the schema). "q" filters by case-insensitive substring on value/label. |
| probe_widget_dataA | Return a widget's data as JSON to pick "field" paths before binding a data primitive. Returns {data, data_source, reason, fields}: "data_source" is "live" (real fetch), "sample" (demo fallback because nothing was configured), or "error" (fetch failed) so you never mistake a placeholder for a real result; "fields" lists the bindable dot-paths with sample values (a wrong key simply isn't in the list; an empty payload has fields with null values). |
| list_devicesA | List registered display devices with panel dims AND colour capability: "color_mode" (e.g. "6-colour (Spectra 6)"), "colors" (the renderable palette as hex), "gamut", "orientation", and a "mono" flag. Match a canvas's w/h to the target panel, and design within its palette so colours don't quantise away on the hardware. |
| list_pagesA | List existing canvas (freeform) dashboards. |
| create_canvas_pageA | Create a new, empty canvas dashboard and return its id. Then set_canvas() to lay it out. Size it to your target panel (see list_devices). |
| get_canvasA | Get the full canvas document (size, appearance, and every element) for a page, plus "rev" / "updated_at" / "updated_by". Keep the "rev" and pass it as base_rev on your next write to be warned (HTTP 409) if the page drifted. |
| update_elementA | Change ONE element in place without re-sending the whole document. "patch" is a partial element ({field: value, ...}) merged over the existing one (a provided "options"/"parts" replaces wholesale). The cheap edit path for a big canvas: change a precision, a colour, a location, one box. Returns the ack. |
| delete_elementB | Remove ONE element from a canvas by id. Returns the compact ack. |
| patch_canvasA | Change document-level fields (any of name, w, h, theme, style, font, bg, bg_image, bg_fit) without touching the elements. Use update_element / set_canvas for elements. Returns the ack. |
| arrangeA | Compute "count" aligned child boxes inside "box" ({x,y,w,h}) for a "grid" / "row" / "column" layout, so you place cells by intent instead of hand-computing pixels. "gap" is the space between cells, "pad" the inset from the box edge, "cols" forces a grid column count (default ~sqrt). Returns {boxes:[{x,y,w,h}, ...]}; spread them across your elements' geometry (bake them in as normal elements — they stay individually editable). |
| measure_textA | Measure how wide/tall text renders in a widget font, so a box fits its content (prevents clipping). "items" is a list of {text, font?, size?, weight?, max_width?}. Returns {items:[{text,width,height,fits}]} where "fits" is whether the text is within max_width. Font names come from list_widgets().appearance. |
| render_reportA | Read back what a canvas actually rendered, as JSON (a companion to render_preview's image). Per element: the resolved box, the text that rendered, overflow/clip flags (overflow_x when content is wider than its box), "data_source" (live | sample | error | static), and computed colours; plus the board's resolved background / theme. Use it to verify a render — catch clipping, confirm live data, read the real colours — without parsing a PNG. (Widget cells render into shadow DOM, so their "text" may be empty; data primitives and decorations report their text.) |
| render_previewA | Render the canvas to a PNG at its authored size and return the image, so you can visually check the layout and iterate. This is your feedback loop: place → render_preview → adjust → set_canvas → render_preview again. For a machine-readable check (values, overflow, colours), use render_report(). |
| push_to_deviceA | Render the canvas and push it to the given devices (ids from list_devices). device_ids is required — pushing is always explicit. |
| set_canvasA | Replace a canvas dashboard's document. Returns a compact {ok,id,rev,elements} ack (not the full document), or an error with field-level "details" (HTTP 422) if the document is invalid, so you can correct it and retry. Pass base_rev (the rev from get_canvas) to be warned with HTTP 409 if the page changed under you. For a one-field change prefer update_element / patch_canvas. After setting, call render_preview() (or render_report()) to check the result. A canvas document is JSON: { "w": int, "h": int, # artboard size in px (match the target panel) "theme": str, "style": str, # appearance ids from list_widgets().appearance "font": str, "bg": str, # optional font id and background colour override "els": [ , ... ] # painted in list order: first = back, last = front } Elements may sit partly off the panel (it clips at the edge). Each element has a unique "id" and a box "x","y","w","h" (px, top-left origin; x/y may be negative), plus optional "opacity" (0-100) and "rotate" (degrees). By "kind":
LIVE BINDINGS ("bind" on ANY element -- makes a SHAPE reflect data): Data elements auto-update, but shapes (rect/ellipse/icon/line/text) are static geometry. Add "bind": [ , ... ] to drive a shape's props from data each render (in lockstep with data elements, no polling). A binding is: {"source":"","options":{...},"field":"","transform":"","params":{...}} transforms: position -- scalar to a coordinate: {"axis":"x"|"y","in":[lo,hi],"out":[p0,p1],"center":} lo/hi may be numbers OR other field paths (e.g. "sun.riseMin"). length -- scalar to a size: {"dim":"w"|"h","in":[lo,hi],"out":[minPx,maxPx],"anchorMax":?} pick -- integer field indexes arrays: {"set":{"x":[...],"color":[...],...},"center":?} color -- scalar to a colour by ascending thresholds: {"stops":[[max,"#hex"],...],"else":"#hex"} gradient -- scalar interpolated smoothly along colour stops: {"stops":[[value,"#hex"],...]} (quantised to the panel palette on e-ink; a value-driven gradient, not animation) icon -- code/string to a Phosphor glyph: {"table":{"":"ph-name"},"default":"ph-name"} Several bindings combine (e.g. bind x by position AND colour by threshold). A binding that can't resolve its value is skipped, so the element keeps its authored props. FIELD PATHS ("field" on data elements): dotted -- "current.temp" array index -- "hourly.0.temp" (or "hourly[0].temp") pluck (for charts) -- "series.*.total" (or "series[].total") maps .total over every item of the array "series", yielding an array of numbers. Charts (line/bar/sparkline) need a field that resolves to an array of numbers -- use pluck to get one from an array-of-objects. probe_widget_data() returns a "fields" list of the bindable paths (with sample values), so you don't have to reverse-engineer the shape. EDITING WITHOUT RESENDING EVERYTHING: update_element / delete_element / patch_canvas change one thing without re-sending the whole document (cheaper, fewer errors on a big canvas). add_element appends one. AVOID CLOBBERING A CONCURRENT EDIT: get_canvas() returns a "rev". Pass it as base_rev to a write; if the page changed since (someone edited it in the UI, or another agent), the write returns HTTP 409 with the current rev, so you re-read instead of overwriting. LAY OUT BY INTENT, NOT PIXELS: arrange(box, layout, count) returns aligned child boxes (grid/row/column) to spread across your elements -- no hand-computed x/y. measure_text() tells you how wide text renders so a box fits its content (prevents clipping). render_report() reads back what actually rendered (per-element value, overflow flags, live-vs-sample, colours) so you verify without eyeballing the PNG. MATCH THE HARDWARE: list_devices() reports each panel's colour capability (color_mode, the renderable palette as hex, and a "mono" flag). Design within that palette so colours don't quantise away on the panel. |
| add_elementA | Append ONE element to a canvas and save (each call is a separate save, so an open editor updates live as you build). 'element' is a single element object; returns {ok,id,rev,elements,element_id}. Use set_canvas to replace the whole layout at once. A canvas document is JSON: { "w": int, "h": int, # artboard size in px (match the target panel) "theme": str, "style": str, # appearance ids from list_widgets().appearance "font": str, "bg": str, # optional font id and background colour override "els": [ , ... ] # painted in list order: first = back, last = front } Elements may sit partly off the panel (it clips at the edge). Each element has a unique "id" and a box "x","y","w","h" (px, top-left origin; x/y may be negative), plus optional "opacity" (0-100) and "rotate" (degrees). By "kind":
LIVE BINDINGS ("bind" on ANY element -- makes a SHAPE reflect data): Data elements auto-update, but shapes (rect/ellipse/icon/line/text) are static geometry. Add "bind": [ , ... ] to drive a shape's props from data each render (in lockstep with data elements, no polling). A binding is: {"source":"","options":{...},"field":"","transform":"","params":{...}} transforms: position -- scalar to a coordinate: {"axis":"x"|"y","in":[lo,hi],"out":[p0,p1],"center":} lo/hi may be numbers OR other field paths (e.g. "sun.riseMin"). length -- scalar to a size: {"dim":"w"|"h","in":[lo,hi],"out":[minPx,maxPx],"anchorMax":?} pick -- integer field indexes arrays: {"set":{"x":[...],"color":[...],...},"center":?} color -- scalar to a colour by ascending thresholds: {"stops":[[max,"#hex"],...],"else":"#hex"} gradient -- scalar interpolated smoothly along colour stops: {"stops":[[value,"#hex"],...]} (quantised to the panel palette on e-ink; a value-driven gradient, not animation) icon -- code/string to a Phosphor glyph: {"table":{"":"ph-name"},"default":"ph-name"} Several bindings combine (e.g. bind x by position AND colour by threshold). A binding that can't resolve its value is skipped, so the element keeps its authored props. FIELD PATHS ("field" on data elements): dotted -- "current.temp" array index -- "hourly.0.temp" (or "hourly[0].temp") pluck (for charts) -- "series.*.total" (or "series[].total") maps .total over every item of the array "series", yielding an array of numbers. Charts (line/bar/sparkline) need a field that resolves to an array of numbers -- use pluck to get one from an array-of-objects. probe_widget_data() returns a "fields" list of the bindable paths (with sample values), so you don't have to reverse-engineer the shape. EDITING WITHOUT RESENDING EVERYTHING: update_element / delete_element / patch_canvas change one thing without re-sending the whole document (cheaper, fewer errors on a big canvas). add_element appends one. AVOID CLOBBERING A CONCURRENT EDIT: get_canvas() returns a "rev". Pass it as base_rev to a write; if the page changed since (someone edited it in the UI, or another agent), the write returns HTTP 409 with the current rev, so you re-read instead of overwriting. LAY OUT BY INTENT, NOT PIXELS: arrange(box, layout, count) returns aligned child boxes (grid/row/column) to spread across your elements -- no hand-computed x/y. measure_text() tells you how wide text renders so a box fits its content (prevents clipping). render_report() reads back what actually rendered (per-element value, overflow flags, live-vs-sample, colours) so you verify without eyeballing the PNG. MATCH THE HARDWARE: list_devices() reports each panel's colour capability (color_mode, the renderable palette as hex, and a "mono" flag). Design within that palette so colours don't quantise away on the panel. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 18 tools
Each tool targets a distinct purpose: listing resources, measuring, rendering, editing elements vs documents, and querying widget metadata. The close cousins (render_preview vs render_report, set_canvas vs patch_canvas vs update_element) are clearly separated by their input/output and scope, leaving no real ambiguity.
Tool names follow a consistent verb_noun snake_case pattern (list_widgets, get_canvas, update_element, push_to_device). The only outlier is 'arrange', a single verb, but it is still a clear imperative and fits the overall style.
18 tools is well-scoped for a canvas dashboard editor covering widget discovery, layout, rendering, device management, and document editing. Each tool addresses a distinct need without superfluous additions, and the count is appropriate for the complexity of the domain.
The set covers the full lifecycle for canvases and elements: create, read, update, delete, render, and push. The only notable gap is the lack of a delete_page tool (or explicit page deletion/archival), which is a minor oversight given list_pages and create_canvas_page cover the rest of page lifecycle.