display-image-mcp
Allows ESPHome e-paper displays to fetch rendered images over HTTP using the online_image component, enabling display updates without reflashing.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@display-image-mcprender a 296×128 card on my e-paper showing today's calendar"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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_yelloworrgb. 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 |
| Display profiles available |
| Quick header/lines/footer layout, sized to the display |
| Custom layout from |
| Stored images with URL, sha256 and last-changed time |
| 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.
HTTP
Endpoint | |
| The image ( |
| Digest for one profile |
| Digest for every profile that has images |
| 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/mcpOption | Env | Default |
|
|
|
|
| none |
|
|
|
|
|
|
|
| Pillow's built-in vector font |
|
|
|
|
| none. If set, the MCP endpoint requires |
|
|
|
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 updateCheck 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:8099MCP endpoint:
http://192.168.1.10:8767/mcpImage 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.targetsudo systemctl daemon-reload
sudo systemctl enable --now display-image-mcp
journalctl -u display-image-mcp -fmacOS (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.plistThe 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 # updatePort | What | Published to |
| Image host. Displays fetch from here | your network |
| MCP endpoint, | 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.0Without --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_displayTo change the source without reflashing, expose an API action that calls online_image.set_url with a URL argument.
Development
uv run pytestLicense
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Deterministic image rendering for agents: JSON template in, on-brand PNG out.
Display delivery platform for AI agents. Push HTML, dashboards and live data to screens.
Generate images, GIFs, videos, and PDFs from HTML, URLs, or templates — from your AI agent.
Diagrams, badges, charts and QR codes as plain image URLs you can paste into Markdown.
Related MCP Servers
- AlicenseAqualityCmaintenanceCapture screenshots, generate PDFs, and render HTML to images via AI agents. Supports batch capture, geo-targeting, async webhooks, and CSS/JS injection.1175 npm7MIT
- AlicenseNot gradedqualityBmaintenanceHyperWeave 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.46Apache 2.0
- AlicenseNot gradedqualityCmaintenanceHosted, SSRF-safe, cached screenshots and Open Graph images for AI agents - no headless Chrome to run.29 npmMIT
- AlicenseNot gradedqualityCmaintenanceDeterministic social image rendering for AI agents that converts specs into PNGs with exact typography, paid per call in USDC on Base via x402.43 npmMIT