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 | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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