Skip to main content
Glama

display-image-mcp

An MCP server that renders images for small displays (e-paper, OLED, TFT) and hosts them over HTTP.

It deliberately does one thing: turn a description of a screen into a pixel-exact image for a given display, and serve it. It knows nothing about where the image is shown or when to refresh it, so any agent, automation or device can use it. Whatever drives your display (Home Assistant, Node-RED, an LLM agent) decides when to push, and the display fetches the URL.

Concepts

  • Profile: a kind of display: resolution, colour depth and output format. mono, gray4, gray16, tricolor_red, tricolor_yellow or rgb. Output is BMP or PNG. Drawing colours snap to what the profile can show.

  • Label: a name for an image within a profile. Rendering to an existing label overwrites it, so a display can keep polling one stable URL. A profile can hold many labels (e.g. main, alert, calendar).

  • Digest: GET /images/<profile> lists what is available, with sha256, size and last-changed time.

Built-in profiles (see list_profiles): waveshare-2in9-v2 (296×128 mono), waveshare-2in9-b (tricolor), waveshare-2in13-v3, waveshare-4in2, waveshare-7in5-v2, ssd1306-128x64, st7789-240x240 (RGB). Add your own with a TOML file:

# profiles.toml  (pass with --profiles or DISPLAY_MCP_PROFILES)
[profiles.kitchen-lcd]
width = 320
height = 240
color_mode = "rgb"      # mono | gray4 | gray16 | tricolor_red | tricolor_yellow | rgb
format = "png"          # bmp | png
description = "Kitchen 320x240 TFT"

Profile and label names are lowercase letters, digits, - and _.

Related MCP server: hyperweave

MCP tools

Tool

What it does

list_profiles

Display profiles available

render_layout(profile, label, root, background?)

Preferred. Nested row/column layout with flex sizing, padding, auto-fitting text and warnings. No pixel coordinates

render_card(profile, label, header, lines, footer?, invert_header?)

Quick header/lines/footer layout, sized to the display

render_image(profile, label, elements, background?)

Exact pixel placement from text, rect, line and progress elements, for when you need coordinates

list_images(profile?)

Stored images with URL, sha256 and last-changed time

delete_image(profile, label)

Remove an image

Render tools return the image url, sha256 and changed, which is false when the pixels are identical to what was already stored. A caller can use that to skip a needless e-paper refresh.

Layouts

render_layout takes a tree. Containers split the space between their children, so you say how things relate (a title row, a big number filling the left, a list on the right) and the server does the measuring. A 296×128 display, for example:

{"type": "column", "padding": 6, "gap": 4, "children": [
  {"type": "row", "h": 22, "children": [
    {"type": "text", "text": "TUESDAY", "size": 16, "bold": true, "flex": 1},
    {"type": "text", "text": "14:05", "size": 16, "align": "right"}]},
  {"type": "rect", "h": 2, "fill": "black"},
  {"type": "row", "flex": 1, "gap": 8, "children": [
    {"type": "text", "text": "21°", "size": "fit", "bold": true, "flex": 1, "align": "center", "valign": "middle"},
    {"type": "column", "flex": 2, "gap": 3, "children": [
      {"type": "text", "text": "Standup in 12 min", "size": 14},
      {"type": "text", "text": "Front door open for 20 minutes", "size": 12, "max_lines": 2},
      {"type": "progress", "h": 8, "value": 0.6}]}]},
  {"type": "text", "text": "updated 14:05", "size": 10}]}

Node

Properties

every node

w, h (px), flex (share of leftover space along the parent's direction)

column / row

children, gap, padding (number, [v, h] or [t, r, b, l]), align (cross axis: stretch default, start, center, end), justify (main axis when nothing has flex: start, center, end, space-between), fill, border, border_color

text

text, size (px, or "fit"), bold, color, align, valign, max_lines, min_size, max_size, fit: "shrink"

rect

fill, outline: a bar or rule such as {"type": "rect", "h": 2, "fill": "black"}

progress

value (0 to 1), h

spacer

empty space; give it flex or w/h

size: "fit" picks the largest font size that fits the text's box, so give it flex or w/h. Text longer than its box is cut with an ellipsis; raise max_lines to wrap instead.

Warnings. The result always includes warnings, such as truncated text, children that need more room than the container has, or elements that got no space or fall off the canvas. They name the offending node (root.children[1].children[0]), so a caller can correct the layout and render again to the same label. Layouts are limited to 8 levels and 120 nodes.

Ready-made layouts are in examples/ (dashboard, alert, agenda) and are covered by the tests. They are written for a 296×128 display; the same trees adapt to other sizes because sizes are relative.

HTTP

Endpoint

GET /images/<profile>/<label>.<bmp|png>

The image (ETag, Cache-Control: no-cache; extension optional)

GET /images/<profile>

Digest for one profile

GET /images

Digest for every profile that has images

GET /profiles

Profile definitions

The image host has no authentication, because displays like an ESP32 can't easily send headers. Run it on a trusted network and be careful what you render. The MCP endpoint can be protected with a bearer token (see below).

Run

uv sync
uv run display-image-mcp                                  # MCP over stdio + image host on :8099
uv run display-image-mcp --transport streamable-http      # MCP at http://127.0.0.1:8767/mcp

Option

Env

Default

--data-dir

DISPLAY_MCP_DATA

./data

--profiles

DISPLAY_MCP_PROFILES

none

--http-host / --http-port

DISPLAY_MCP_HTTP_HOST / _PORT

0.0.0.0 / 8099

--base-url

DISPLAY_MCP_BASE_URL

http://<http-host>:<http-port>. Set this to the address your displays can reach, as it is used in returned URLs

--font

DISPLAY_MCP_FONT

Pillow's built-in vector font

--transport

DISPLAY_MCP_TRANSPORT

stdio

--token

DISPLAY_MCP_TOKEN

none. If set, the MCP endpoint requires Authorization: Bearer <token> (streamable-http only)

--mcp-host / --mcp-port

DISPLAY_MCP_MCP_HOST / _PORT

127.0.0.1 / 8767

Run as a standalone service

The server is meant to run on its own, independent of whichever agent uses it: the agent just connects to a URL and never starts or stops the process. Use the streamable-http transport and let your OS service manager own the lifecycle.

Install it as a uv tool (no checkout needed):

uv tool install git+https://github.com/frizzy/display-image-mcp   # puts display-image-mcp in ~/.local/bin
uv tool upgrade display-image-mcp                                 # later, to update

Check it by hand first (replace the IP with this machine's LAN address):

display-image-mcp --transport streamable-http --mcp-host 0.0.0.0 \
  --data-dir ~/display-images --base-url http://192.168.1.10:8099
  • MCP endpoint: http://192.168.1.10:8767/mcp

  • Image host: http://192.168.1.10:8099

--mcp-host 0.0.0.0 lets an agent on another machine connect. Protect the MCP endpoint with a token: generate one with openssl rand -hex 32 and pass it as --token or DISPLAY_MCP_TOKEN; clients then send Authorization: Bearer <token>. Without a token the server prints a warning when bound to a non-local address. Keep both ports on a trusted network either way, or leave --mcp-host at its 127.0.0.1 default if the agent runs on the same machine.

Linux (systemd)

/etc/systemd/system/display-image-mcp.service. Adjust User and the paths. uv tool installs into that user's ~/.local/bin:

[Unit]
Description=display-image-mcp
After=network-online.target
Wants=network-online.target

[Service]
User=pi
ExecStart=/home/pi/.local/bin/display-image-mcp --transport streamable-http --mcp-host 0.0.0.0
Environment=DISPLAY_MCP_DATA=/home/pi/display-images
Environment=DISPLAY_MCP_BASE_URL=http://192.168.1.10:8099
Environment=DISPLAY_MCP_TOKEN=put-a-long-random-token-here
# Environment=DISPLAY_MCP_PROFILES=/home/pi/display-profiles.toml
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now display-image-mcp
journalctl -u display-image-mcp -f

macOS (launchd)

~/Library/LaunchAgents/com.example.display-image-mcp.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.example.display-image-mcp</string>
  <key>ProgramArguments</key><array>
    <string>/Users/you/.local/bin/display-image-mcp</string>
    <string>--transport</string><string>streamable-http</string>
    <string>--mcp-host</string><string>0.0.0.0</string>
  </array>
  <key>EnvironmentVariables</key><dict>
    <key>DISPLAY_MCP_DATA</key><string>/Users/you/display-images</string>
    <key>DISPLAY_MCP_BASE_URL</key><string>http://192.168.1.10:8099</string>
    <key>DISPLAY_MCP_TOKEN</key><string>put-a-long-random-token-here</string>
  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardErrorPath</key><string>/tmp/display-image-mcp.log</string>
</dict></plist>
launchctl load ~/Library/LaunchAgents/com.example.display-image-mcp.plist

The unit files above follow standard systemd and launchd usage. I have not run them myself: only the uv tool install command and the LAN-bound streamable-http endpoint were tested.

Running with Docker

A multi-arch image (amd64 and arm64, so a Raspberry Pi works too) is published to GitHub Container Registry as ghcr.io/frizzy/display-image-mcp, built by CI from main (:latest) and from v* tags (:X.Y.Z, :X.Y). To build it yourself, run docker build -t display-image-mcp . in a checkout and use that name instead.

Always on, with Docker Compose

docker-compose.yml runs the streamable-HTTP server with a restart policy, a health check, a read-only filesystem, no capabilities and a volume for the images:

DISPLAY_MCP_BASE_URL=http://192.168.1.10:8099 docker compose up -d   # or put that line in a .env file
docker compose logs -f
docker compose pull && docker compose up -d                          # update

Port

What

Published to

8099

Image host. Displays fetch from here

your network

8767

MCP endpoint, http://127.0.0.1:8767/mcp

this machine only

The MCP endpoint is bound to localhost by default. To let an agent on another machine connect, set DISPLAY_MCP_TOKEN (in .env or the environment) and change the mapping to "8767:8767". To add your own profiles, mount a TOML file and set DISPLAY_MCP_PROFILES (there is a commented example in the compose file).

Plain docker run

docker run -d --name display-image-mcp --restart unless-stopped \
  -p 8099:8099 -p 127.0.0.1:8767:8767 -v display-images:/data \
  -e DISPLAY_MCP_BASE_URL=http://192.168.1.10:8099 -e DISPLAY_MCP_TOKEN=<token> \
  ghcr.io/frizzy/display-image-mcp --transport streamable-http --mcp-host 0.0.0.0

Without --transport the container speaks MCP over stdio (docker run -i --rm ...), but then whatever launches it owns its lifecycle.

Connecting an agent

Point any MCP client that supports streamable HTTP at the URL, e.g. http://192.168.1.10:8767/mcp, with the header Authorization: Bearer <token> if you set a token. Check your client's docs for how it takes a remote server URL and headers. If a client can only launch local stdio servers, you can still use display-image-mcp (no flags) as its command, but then that client owns the process lifecycle, which is what this setup avoids.

Example: ESPHome e-paper

The display side just downloads a URL:

online_image:
  - platform: online_image
    id: dashboard_image
    url: "http://192.168.1.10:8099/images/waveshare-2in9-v2/main.bmp"
    format: BMP
    type: BINARY
    on_download_finished:
      - component.update: eink_display

To change the source without reflashing, expose an API action that calls online_image.set_url with a URL argument.

Development

uv run pytest

License

MIT

Available Tools

6 tools
delete_imageC

Delete the image stored for (profile, label).

ParametersJSON Schema
NameRequiredDescriptionDefault
labelYes
profileYes

TDQS

C2.7/5.0
Behavior2/5

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

No annotations are supplied, so the description carries the entire behavioral burden. It conveys that this is a mutating, key-scoped operation, but says nothing about permanence, idempotency, error behavior when the key is absent, or required permissions — significant gaps for a destructive tool.

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?

A single tight sentence with the resource and key front-loaded and no filler. It is efficient, though so terse that it omits information the agent actually needs.

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

Completeness2/5

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

For a destructive two-parameter tool with no annotations, no output schema, and zero parameter documentation, the description is too thin. Nothing tells the agent about failure modes, reversibility, or argument sourcing.

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

Parameters2/5

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

Schema description coverage is 0%, so the description must compensate for both parameters. It only shows '(profile, label)' as a composite key, adding the relationship between the two params but no explanation of what a profile or label is, their format, or their source.

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?

States a specific verb+resource (delete the image) and pins down the exact key it operates on, '(profile, label)', which lets an agent distinguish it from list_images and render_image. It stops short of explicitly naming a sibling or contrasting scope, so it is clear but not fully differentiated.

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

Usage Guidelines2/5

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

There is no guidance on when to delete versus when to use the sibling tools, no preconditions, and no mention of what happens if no image exists for the given key. The agent must infer all usage context.

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

list_imagesA

List stored images (all profiles, or one) with URL, sha256, size and last-changed time.

ParametersJSON Schema
NameRequiredDescriptionDefault
profileNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

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

With no annotations, the description carries the full load; 'List stored images' implies a non-destructive read, which is useful. It discloses the returned attributes but says nothing about ordering, pagination, or limits for a potentially large collection.

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?

One sentence, front-loaded with the verb and resource, containing the scope and return fields with no filler.

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

Completeness4/5

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

An output schema exists, so the field list need not be the description's job, and with one optional parameter the surface area is small. Ordering and volume behavior are the only real gaps.

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 0% for the single 'profile' parameter, so the description must compensate. '(all profiles, or one)' explains that omitting or nulling the parameter returns everything, which the schema's bare anyOf/string/null does not convey; only the expected identifier format is left unspecified.

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?

States a specific verb (List) and resource (stored images), and adds scope (all profiles, or one) plus the returned fields. An agent can separate it from render_image and delete_image, though no sibling is named explicitly.

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 only implied: the tool exists to enumerate images, and the parenthetical hints at the optional profile filter. There is no statement of when to prefer this over render_image or list_profiles, and no prerequisites are given.

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

list_profilesA

List display profiles (resolution, colour mode, output format).

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

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

No annotations are supplied, so the description carries the full behavioral burden, yet it discloses nothing about read-only semantics, pagination, ordering, or result volume. 'List' strongly implies a safe read, which keeps this from being a serious gap, but no real behavioral context is added.

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?

A single front-loaded sentence with zero waste; the parenthetical earns its place by telling the agent what a profile actually is.

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

Completeness4/5

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

With an output schema present, return values need not be described, and a zero-parameter list tool needs little else. The only shortfall is the absence of any behavioral note, which for this low-risk operation is minor.

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?

The tool takes no parameters, so the baseline is 4; the schema has nothing to document and the description appropriately does not invent parameter detail.

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?

States a specific verb ('List') and resource ('display profiles') and clarifies what a profile represents (resolution, colour mode, output format). It is clearly distinct from the render_* and *_image siblings, though it never explicitly contrasts itself with them.

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 rather than stated: a zero-parameter enumerator is self-evidently a 'list everything' call, and no alternative or precondition is named. There is no explicit when-to-use or when-not-to-use guidance, so it lands at minimum-viable.

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

render_cardA

Render a simple card (header bar, body lines, footer) and store it as (profile, label).

Text that is too long is truncated with an ellipsis and extra lines beyond what fits are dropped, so keep it short. Returns the image URL, its sha256 and changed (false when the pixels are identical to what was already stored, so a caller can skip a display refresh).

ParametersJSON Schema
NameRequiredDescriptionDefault
labelYes
linesYes
footerNo
headerYes
profileYes
descriptionNo
invert_headerNo

TDQS

A3.5/5.0
Behavior4/5

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

With no annotations to lean on, the description carries the full burden and does well: it discloses truncation-with-ellipsis, silent dropping of overflow lines, the persistence key (profile, label), and a non-obvious `changed=false` idempotency signal that lets callers skip a refresh. What it omits is where the image is stored/served from and any auth or profile-existence requirement.

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?

Roughly three tight sentences with zero filler: purpose first, then the behavioral caveat, then the return contract. The `changed` flag's semantics are tucked into a parenthetical, which is exactly the right weight for it.

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

Completeness3/5

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

With no annotations and no output schema, the description correctly covers the return payload and truncation behavior, which is the important half. It remains incomplete for a 7-parameter, 0%-documented tool: two parameters are unexplained and there is no statement of the storage target or what happens if the profile does not exist.

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

Parameters3/5

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

Schema description coverage is 0% across 7 parameters, so the description must compensate. It meaningfully covers header, lines and footer, and gives (profile, label) a role as the storage key, but `description` and `invert_header` are never mentioned and no parameter's format or length limits are specified.

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?

States a specific verb (render) and resource (card) and even enumerates the card's structure (header bar, body lines, footer), which is more concrete than `render_image` or `render_layout`. However, it never explicitly contrasts itself with those siblings, so the agent must infer the card-vs-image-vs-layout boundary from the names alone.

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

Usage Guidelines2/5

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

There is no statement of when to choose render_card over render_image or render_layout, and no prerequisites or exclusions. The only guidance ('keep it short') is a constraint on input content rather than a routing rule, leaving selection entirely to inference.

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

render_imageB

Render a custom layout from drawing elements and store it as (profile, label).

Origin (0,0) is top-left; sizes are in pixels. Colours are names (black, white, red, yellow, gray, dark_gray, light_gray) or #rrggbb; they snap to what the profile can show. Elements: {"type":"text","text":..,"x":..,"y":..,"size":12,"color":"black","bold":false, "anchor":"la","max_width":px,"max_lines":1} (anchor: Pillow anchors, e.g. "la","mm","ra") {"type":"rect","x":..,"y":..,"w":..,"h":..,"fill":colour|null,"outline":colour|null,"width":1} {"type":"line","x1":..,"y1":..,"x2":..,"y2":..,"color":"black","width":1} {"type":"progress","x":..,"y":..,"w":..,"h":..,"value":0.0-1.0} Returns the image URL, sha256 and changed (see render_card).

ParametersJSON Schema
NameRequiredDescriptionDefault
labelYes
profileYes
elementsYes
backgroundNowhite
descriptionNo

TDQS

B3.4/5.0
Behavior4/5

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

With no annotations the description carries the full burden, and it delivers real behavioral context: the top-left origin convention, pixel units, colours that 'snap to what the profile can show', and a `changed` return value implying cached/no-op semantics. It omits what happens when (profile, label) already exists and whether the store is overwritten, which keeps it short of a 5.

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?

The purpose sentence is front-loaded, followed by terse coordinate/colour rules and a compact element-type spec block. The spec block is large but earns its space given the free-form schema; the only soft spot is the dangling '(see render_card)' reference for `changed`.

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

Completeness3/5

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

Return values (URL, sha256, changed) are named despite there being no output schema, and the element format is fully specified. What is missing is anything about the profile concept (a sibling list_profiles exists), label uniqueness/overwrite behavior, and the purpose of the description and background parameters.

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

Parameters3/5

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

Schema description coverage is 0%, so the description must compensate; it does so thoroughly for the critical `elements` parameter by enumerating element types and their fields. However, profile, label, background (only defaulted in the schema) and description are never explained, leaving 4 of 5 parameters semantically undocumented.

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?

Specific verb+resource: 'Render a custom layout from drawing elements and store it as (profile, label)' tells an agent exactly what is produced and where it lands. It does not, however, explicitly differentiate the tool from its close siblings render_card and render_layout, which an agent must distinguish by name inference only.

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

Usage Guidelines2/5

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

There is no statement of when to use this tool versus render_card or render_layout, nor any prerequisites or exclusions. The phrase 'custom layout from drawing elements' implies a low-level primitive, but the agent is left to infer the choice entirely from sibling tool names.

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

render_layoutA

Lay out a screen from nested rows and columns (no pixel coordinates) and store it as (profile, label).

root fills the whole display. Containers split space between children; text can fit itself. Always check warnings in the result: it reports truncated text, overflow and elements that got no room, so you can fix the layout and render again (same label overwrites).

Nodes (every node accepts w, h in px and flex as a share of leftover space): {"type":"column"|"row","children":[...],"gap":4,"padding":6,"align":"stretch|start|center|end", "justify":"start|center|end|space-between","fill":colour,"border":px,"border_color":colour} padding is a number, [vertical, horizontal] or [top, right, bottom, left]. align = cross axis (default stretch); justify = main axis when no child has flex. {"type":"text","text":"..","size":12|"fit","bold":false,"color":"black","align":"left|center|right", "valign":"top|middle|bottom","max_lines":1,"min_size":8,"max_size":96,"fit":"shrink"} size "fit" (or fit "shrink") picks the largest size that fits the box: give it flex or w/h. Text longer than its box is cut with an ellipsis (and warned): raise max_lines to wrap. {"type":"rect","fill":colour,"outline":colour} (a bar or rule: {"type":"rect","h":2,"fill":"black"}) {"type":"progress","value":0.0-1.0,"h":8} {"type":"spacer"} (give it flex or w/h) Colours: black, white, red, yellow, gray, dark_gray, light_gray or #rrggbb (snapped to the profile). Limits: 8 levels deep, 120 nodes.

ParametersJSON Schema
NameRequiredDescriptionDefault
rootYes
labelYes
profileYes
backgroundNowhite
descriptionNo

TDQS

A3.7/5.0
Behavior4/5

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

With no annotations the description carries the full burden, and it delivers: overwrite-by-label semantics, truncation/overflow/no-room warning behavior, hard limits (8 levels, 120 nodes), and colour snapping to the profile. It omits any auth/permission notes and doesn't say what the full return payload is beyond `warnings`.

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?

Purpose and the critical warning-feedback loop are front-loaded, then the node vocabulary is laid out in a scan-friendly list with no filler. Given the complexity, the length is justified, though a few lines pack several rules together.

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

Completeness4/5

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

For a deeply nested, 0%-schema-coverage tool with no output schema, the description supplies nearly everything an agent needs to build a valid tree: node types, accepted keys, defaults, limits and colours. Remaining gaps are the undocumented non-root parameters and what the stored render returns besides warnings.

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

Parameters3/5

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

Schema coverage is 0% and `root` is only "object, additionalProperties true", so the description's node grammar is doing essential work that the schema cannot. However, the other four parameters (`profile`, `label`, `background`, `description`) get no explanation — `background` and `description` are never mentioned at all, and valid `profile` values are left to the list_profiles sibling.

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?

States a specific verb+resource ("Lay out a screen from nested rows and columns") plus the storage model ("store it as (profile, label)") and explicitly excludes pixel coordinates. It does not, however, name or contrast itself with the sibling render_card, so an agent must infer the screen-vs-card distinction.

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?

Gives a genuine workflow hint — check `warnings`, fix the layout, render again, and that "same label overwrites" — which is real usage context. But it never states when to pick this tool over render_card, nor any prerequisites or when-not-to-use conditions.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 6 tool updatesv0.1.0
    • First observeddelete_image
    • First observedlist_images
    • First observedlist_profiles
    • First observedrender_card
    • First observedrender_image
    • First observedrender_layout

TDQS

A3.6/5.0

Scored across 6 tools

Disambiguation4/5

The three render tools are distinguishable: render_card is a fixed header/body/footer template, render_image takes pixel-coordinate drawing elements, and render_layout uses nested rows/columns without coordinates. They could be confused at a glance (card output is achievable via the other two), but the descriptions clarify the boundaries well. list_profiles vs list_images is also clearly separated by resource.

Naming Consistency5/5

All six tools follow a strict verb_noun snake_case pattern: list_profiles, list_images, render_card, render_image, render_layout, delete_image. Verb families are internally consistent (render_*, list_*). No mixed conventions.

Tool Count5/5

Six tools is well-scoped for a display image rendering/storage server. Each tool earns its place: profile discovery, three rendering variants, listing, and deletion. No filler or redundancy.

Completeness4/5

The image lifecycle is mostly covered: render (create), list (read), delete. However, there is no single-image get/inspect tool to fetch metadata for one (profile, label) without listing everything, and profiles can only be listed, not defined. These are minor gaps agents can work around via list_images.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    HyperWeave gives agents a visual output surface: structured data in, branded SVG out. No JavaScript, no runtime. Works anywhere an image can be embedded. Badges, cards, charts, dashboards, diagrams, and more.
    147 PyPI
    50
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Hosted, SSRF-safe, cached screenshots and Open Graph images for AI agents - no headless Chrome to run.
    9 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to create high-resolution marketing images from simple JSON configs without design skills or API keys. Provides presets, themes, and layouts to render deterministic PNGs locally.
    MIT