label-printer-mcp
README.md
# label-printer-mcp
Print a 4x6 shipping label from a PDF — as a CLI, a Python library, or an MCP server.
A return label turns up as a 4x6 page, or as a US Letter sheet with the label in one corner and a packing slip below
it, or sideways. This finds the label, crops to it **losslessly**, checks it really is 4x6, and prints it at exactly
100% on a 4x6 thermal printer.
## Why it crops instead of scaling
A carrier's barcode encodes the tracking number in the **width of its bars**. Scale the label — or rasterise and
resample it — and you can get a parcel whose barcode a scanner won't read, with nothing wrong on screen to warn you.
It gets rejected at the counter.
So the crop here is a **CropBox change**: the page keeps every object it had and simply shows a smaller window onto
them. Vector artwork stays vector and prints at whatever resolution the printer has. Nothing is resampled, ever, and
printing passes `print-scaling=none` so CUPS doesn't helpfully fit the label to the media.
A test pins this: it counts the barcode's vector drawings after the crop. A rasterising crop would leave one image and
zero drawings, and would pass a size check while producing a label a scanner may refuse.
## Why it refuses
Finding the label on a letter sheet is a heuristic. Candidates are scored on **aspect ratio alone** — "biggest block"
and "most ink" both pick the packing slip on a busy sheet, while a 4x6 label is the only thing on it with a 1.5 ratio
and a short side over three inches.
When nothing scores well it refuses and says what it saw, with measurements, rather than cropping to the wrong thing:
```
no region looks like a 4x6 label. Closest: 3.41x8.01in (aspect 2.35), 8.5x11.0in (aspect 1.29).
A 4x6 has aspect 1.50 and a short side of 4.0in; refusing rather than cropping to the wrong thing.
```
```
that region is 3.2x4.8in, not 4.0x6.0in (aspect is right, size is not). Scaling it would corrupt the
barcode, so nothing was written. Print it as-is only if you mean to.
```
Paper is cheap; a mislabelled return is not.
## Install
```sh
python3 -m venv .venv
.venv/bin/pip install -e . # or: pip install pymupdf mcp
```
## CLI
```sh
python -m labelprint inspect label.pdf # what it is, and where the label looks to be
python -m labelprint normalise label.pdf out.4x6.pdf # crop to the label; --page N for a later page
python -m labelprint preview label.pdf out.png # look before you print
python -m labelprint queues # the print queues this machine has
python -m labelprint print out.4x6.pdf MyPrinter # DRY RUN: prints the command, sends nothing
python -m labelprint print out.4x6.pdf MyPrinter 1 --live
```
`print` is a dry run unless you pass `--live`. Everything returns JSON.
## MCP server
```json
{
"mcpServers": {
"label": {
"command": "/path/to/label-printer-mcp/.venv/bin/python",
"args": ["/path/to/label-printer-mcp/server.py"]
}
}
}
```
Tools: `inspect`, `prepare`, `preview`, `printers`, `send`. `send` is a dry run unless called with `live=True`, so an
assistant has to say explicitly that it means to move paper — and should show you the preview first.
## Library
```python
from pathlib import Path
import labelprint
r = labelprint.normalise(Path("return.pdf"), Path("out.4x6.pdf"))
if r.ok:
print(r.size_in) # [4.0, 6.0]
labelprint.print_label(Path(r.out), "MyPrinter", dry_run=False)
else:
print(r.reason) # why it refused, with measurements
```
## Printer setup
Add the printer in **System Settings → Printers & Scanners**, then:
```sh
lpstat -p # the queue name
lpoptions -p <queue> -l # what options it actually supports
```
That second command matters. A CUPS queue **silently ignores options it doesn't advertise**, which looks exactly like
them working — so check that your printer really takes `media=Custom.4x6in` and `print-scaling` before trusting a
label that came out looking fine.
## Tests
```sh
.venv/bin/python test_labelprint.py
```
15 tests on synthetic fixtures shaped like the real thing: a 4x6 page, a label on a letter sheet, a label **above a
packing slip** (the case the finder exists for), a landscape label, an undersized label, a packing slip alone, a blank
page. The fixtures are synthetic on purpose — a real return label carries somebody's address and tracking number.
## Requirements
macOS or Linux with CUPS (`lp`, `lpstat`), Python 3.10+, and [PyMuPDF](https://pymupdf.readthedocs.io/).
The MCP server additionally needs the [MCP SDK](https://github.com/modelcontextprotocol/python-sdk) (v2+).
## Licence
MIT.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues