label-printer-plugin

July 31, 2026 · View on GitHub

Print labels on a Brother P-touch label printer from Claude Code — single labels, N copies, batch runs, and QR-coded templates for inventory work.

A Claude Code plugin plus the streamable-HTTP MCP server behind it. The MCP renders labels with PIL and qrcode and hands the bitmap to a small HTTP print bridge on the Linux host the printer is wired to; the bridge drives ptouch-print over USB.

Claude Code ──MCP──▶ label-printer ──HTTP──▶ print bridge ──USB──▶ Brother P-touch
                     (renders PNG)           (:9180)               (24mm tape)

The bridge, the ptouch-print patch some models need, and the hardware notes live in ptouch-cube-print-bridge. Set that up first — this repo is useless without it.

Tools

ToolWhat it does
printer_statusBridge reachability, loaded tape width, printer error word
printer_capabilitiesWhat the driver does and does not expose — check before promising a setting
preview_labelRender a label and return the image without printing
print_labelGeneral-purpose: heading, optional subtext, optional QR, N copies
print_text_labelText only, no QR, up to 3 stacked lines
print_asset_labelInventory asset template — A- prefix, name underneath
print_storage_labelStorage unit template — S- prefix, contents underneath
print_batchUp to 50 different labels in one run
label_canvasThe pixel canvas to render your own artwork onto
preview_image_labelShow submitted artwork as it would reach the printer, without printing
print_image_labelPrint arbitrary PNG/JPEG artwork you rendered yourself

Skills

SkillPurpose
label-an-assetTag one item — asset number, name, QR to its page
label-a-storage-unitTag a box, bin or shelf
batch-label-runLabel a whole shelf, or generate labels from a CSV
printer-troubleshootingNothing printed — work out why

Label layout

QR labels are a uniform 62 mm along the tape: QR square on the left, heading in large bold type, optional second line beneath it. Long names wrap to two lines rather than stretching the tape. A heading too wide to shrink legibly widens the label instead, up to a 120 mm cap.

Example asset label

Tape narrower than 18 mm drops the second line — there is no room for it.

Hebrew and other RTL text

Hebrew works in either field, on its own:

print_text_label(text="נקודת מסירת משלוחים")

Two things are handled for you. Font fallback: Inter carries no Hebrew glyphs at all, so any string containing Hebrew or Arabic switches to the vendored Noto Sans Hebrew faces — otherwise you get blank tape, not an error. Bidi: Pillow is normally built without libraqm, so it lays glyphs out left-to-right and will not reorder RTL text itself. The server applies the bidi algorithm explicitly and pins the layout engine to BASIC, so the result is identical whether or not your Pillow has raqm.

Arabic will pick the same fallback font and be reordered correctly, but contextual letterforms are not shapedBASIC layout does no glyph joining. Hebrew needs no joining, so it is unaffected. If you need correct Arabic, install a raqm-enabled Pillow, drop the _shape() call and the BASIC pin, and let raqm do both.

Mixing scripts on one label does not work — use artwork instead

The font is chosen per label, not per run: one Hebrew character anywhere sends the whole label to Noto Sans Hebrew, and that face has no Latin glyphs, so the Latin half comes out as tofu boxes (□□□□). Splitting across heading and subtext does not help — the choice is made once for both.

print_label(heading="Rosehill | רוזהיל")     # -> "□□□□□□□□ | רוזהיל"

Rather than a per-run font fallback, render the label yourself and submit it — see Custom artwork below. That also fixes bidi properly, since a raqm-enabled Pillow on the rendering side does real mixed-direction layout.

Custom artwork

When the built-in layouts can't express the design — mixed scripts, a logo, custom typography, columns — render your own bitmap and print it:

label_canvas()
# {'tapeMm': 24, 'heightPx': 288, 'maxWidthPx': 3600, 'maxLengthMm': 300,
#  'scalePxPerMm': 12, ...}

print_image_label(image="<base64 PNG>", copies=1)

The tape width is the label's height; the label runs as long as you like along the tape. Render at heightPx for the loaded tape (12 px/mm — the bridge downscales to the 180 dpi head and trims the blank leader, so no end margins are needed).

  • image — PNG or JPEG, as a data: URL or a bare base64 string. Wrapped base64 is fine. 8 MiB decoded cap.
  • fitscale (default) resizes proportionally to the tape height; pad keeps the artwork's own scale and centres it across the tape; exact refuses anything whose height isn't already the tape height.
  • Transparency is composited onto white. Submitting RGBA without this would print a solid black slab, since transparent converts to black rather than to bare tape.
  • Length is capped at 300 mm, looser than the 120 mm auto-layout cap — that one guards against a long string stretching tape, which doesn't apply to artwork composed deliberately.

Use preview_image_label to see the exact bitmap that would be sent, before spending tape.

Install

claude plugins install label-printer@danielrosehill

Point it at your deployed MCP:

export LABEL_PRINTER_MCP_URL=http://<host>:<port>/mcp

Running the MCP server

Source is in mcp-server/. It speaks streamable HTTP on :3000 by default, or stdio with MCP_TRANSPORT=stdio.

docker build -t label-printer-mcp mcp-server/
docker run -d --name label-printer -p 3000:3000 \
  -e PRINT_BRIDGE_URL=http://<bridge-host>:9180 \
  label-printer-mcp

Configuration

VariableDefaultMeaning
PRINT_BRIDGE_URLhttp://127.0.0.1:9180Where the print bridge listens
LABEL_WIDTH_MM62Preferred QR-label length along the tape
LABEL_TAPE_MM24Fallback tape width when the printer is off
ASSET_PREFIXA-Prefix applied by print_asset_label
STORAGE_PREFIXS-Prefix applied by print_storage_label
FONT_BOLD / FONT_REGULARvendored InterLatin font paths
FONT_HEBREW_BOLD / FONT_HEBREW_REGULARvendored Noto Sans HebrewUsed automatically for Hebrew/Arabic text
MCP_TRANSPORTstreamable_httpOr stdio
MCP_HTTP_HOST / MCP_HTTP_PORT0.0.0.0 / 3000HTTP bind

The MCP has no authentication — bind it to a trusted LAN or VPN, or put it behind a gateway that authenticates.

Linux users: read this before hunting for settings

ptouch-print is a print-only driver. It exposes no device settings at all, so auto power-off cannot be changed from Linux — not through the driver, not over Bluetooth, not in the mobile apps, and not under Wine. It is a one-time change in Brother's Windows/macOS Printer Setting Tool, stored in the printer's NVRAM.

The printer also cannot be woken over USB once it sleeps. Someone has to press the button.

Full details, the complete option list, and the measurements behind the 60-minute figure: docs/linux-printer-settings.md.

Tested on

Brother PT-P710BT (P-Touch Cube Plus), 24 mm laminated tape, ptouch-print v1.8, Ubuntu. ptouch-print also supports PT-P700, PT-P750W, PT-D460BT, PT-D610BT, PT-E550W, PT-P900Wc and others — the rendering here is model-independent, but only the P710BT has been exercised end to end.

License

MIT — see LICENSE. Vendored fonts are SIL OFL 1.1: Inter (mcp-server/fonts/OFL.txt) and Noto Sans Hebrew (mcp-server/fonts/OFL-NotoSansHebrew.txt). ptouch-print is a separate GPL-3.0 project.