Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
TESSERAE_URLNoThe URL of the Tesserae instance (default: http://127.0.0.1:8765)http://127.0.0.1:8765
TESSERAE_MCP_TOKENNoThe MCP API token from Tesserae settings. Omit if running on the same machine (loopback trusted).

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
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":

  • widget: {"kind":"widget","widget":"","fragment":"full","options":{...}} from list_widgets(); "fragment" from that widget's fragments (or "full"); "options" per get_widget_options().

  • text: {"kind":"text","text":"...","color":"<css or var(--accent-1)>","size":<px, 0=auto>,"align":"left|center|right"}

  • rect: {"kind":"rect","color":"...","fill":true,"stroke":,"radius":}

  • ellipse: {"kind":"ellipse","color":"...","fill":true,"stroke":}

  • line: {"kind":"line","color":"...","stroke":}

  • icon: {"kind":"icon","icon":"","color":"...","weight":"thin|light|regular|bold|fill|duotone"}

  • data: {"kind":"data","source":"","options":{...},"field":"", "display":"text|number|line|bar|sparkline","format":"","unit":"","precision":0, "label":"","color":"...","size":<px, 0=auto>,"align":"..."} Binds a widget's data field to a scalable value or graph. "source" is a widget key from list_widgets(); configure it via "options" (get_widget_options). Use probe_widget_data(source, options) to see the real data shape before choosing "field". "format" (text/number only) is a date pattern ("HH:mm","MMM d","ddd HH:mm"), "relative", or a number pattern ("0.0").

  • html: {"kind":"html","html":"","css":"div{…}"} A mini widget from static HTML + CSS in a sandboxed iframe (no scripts, no network).

  • svg: {"kind":"svg","html":"<svg …>…","css":""} -- raw SVG, scaled to fill the box.

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":

  • widget: {"kind":"widget","widget":"","fragment":"full","options":{...}} from list_widgets(); "fragment" from that widget's fragments (or "full"); "options" per get_widget_options().

  • text: {"kind":"text","text":"...","color":"<css or var(--accent-1)>","size":<px, 0=auto>,"align":"left|center|right"}

  • rect: {"kind":"rect","color":"...","fill":true,"stroke":,"radius":}

  • ellipse: {"kind":"ellipse","color":"...","fill":true,"stroke":}

  • line: {"kind":"line","color":"...","stroke":}

  • icon: {"kind":"icon","icon":"","color":"...","weight":"thin|light|regular|bold|fill|duotone"}

  • data: {"kind":"data","source":"","options":{...},"field":"", "display":"text|number|line|bar|sparkline","format":"","unit":"","precision":0, "label":"","color":"...","size":<px, 0=auto>,"align":"..."} Binds a widget's data field to a scalable value or graph. "source" is a widget key from list_widgets(); configure it via "options" (get_widget_options). Use probe_widget_data(source, options) to see the real data shape before choosing "field". "format" (text/number only) is a date pattern ("HH:mm","MMM d","ddd HH:mm"), "relative", or a number pattern ("0.0").

  • html: {"kind":"html","html":"","css":"div{…}"} A mini widget from static HTML + CSS in a sandboxed iframe (no scripts, no network).

  • svg: {"kind":"svg","html":"<svg …>…","css":""} -- raw SVG, scaled to fill the box.

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

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dmellok/tesserae-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server