print-mcp
Click on "Install 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., "@print-mcpPrint report.md to the default printer double-sided"
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.
print-mcp
A Dockerized MCP server that turns Markdown into print-ready PDFs and submits them to printers managed by CUPS. The MCP endpoint uses a static Bearer token and can be published through the included Cloudflare Tunnel service.
Architecture
Remote MCP client -> Cloudflare Tunnel -> print-mcp -> CUPS -> USB or IPP printer
^
Tailscale clients -> host Tailscale IP:631 ----------+CUPS and MCP communicate on a private Compose network. Cloudflare can reach only MCP. CUPS port 631 is published on the exact host address configured by CUPS_BIND_IP; it defaults to localhost.
Related MCP server: md-mermaid-to-pdf-mcp
Requirements
A Linux Docker host with Docker Compose.
For remote CUPS access, Tailscale or another trusted private interface on that host.
For the public MCP URL, a Cloudflare account, domain, named tunnel, and tunnel token.
USB printers require access to
/dev/bus/usb; Docker Desktop hosts are not supported for USB passthrough.
Quick start
Clone the repo, create the environment file, and replace both secrets:
cp .env.example .env
openssl rand -hex 32 # paste into MCP_BEARER_TOKEN
openssl rand -hex 32 # paste into CUPS_ADMIN_PASSWORD
docker compose up -d --buildAdd your printer
Most modern network printers speak IPP Everywhere / driverless, so this is the recommended path. Point the queue at the printer's IPP URI and enable it in one shot — no web UI, no driver PPD:
# adjust the IP and URI to your printer
docker compose exec cups lpadmin -p brother -E -v ipp://192.168.1.50/ipp/print -m everywhere
docker compose exec cups lpadmin -d brother # make it the default queue-m everywhere asks CUPS to use its built-in driverless/IPP Everywhere model, which auto-detects media, duplex, and color capabilities. Find the IPP URI on the printer's network report or config page; it is usually ipp://PRINTER_IP:631/ipp/print.
If you prefer the web UI, open http://127.0.0.1:631/admin, sign in with CUPS_ADMIN_USER and CUPS_ADMIN_PASSWORD, choose Internet Printing Protocol, and enter the ipp:// URI.
USB printers need extra setup: stop the stack and restart with the USB overlay.
docker compose down
docker compose -f compose.yaml -f compose.usb.yaml up -d --buildThe overlay exposes the Linux USB bus only to CUPS. Replugging a device may require restarting the CUPS service. Add the discovered printer in the same CUPS admin page.
Set DEFAULT_PRINTER to the queue name if calls should not have to select a printer. Queue configuration and job history persist in Docker volumes.
Quick test (docker exec)
The CLI is baked into the mcp image, so a quick print needs no special mounts. Pipe Markdown to it and give the printer a moment:
echo 'This is a quick test of the Print MCP stack.' | docker compose exec -T mcp python /app/cli/print_file.py - --page-size a4Expected output: rendered /tmp/stdin.pdf (1 pages) then submitted job N to printer '...'. The job should complete on the printer within a few seconds.
CLI
The easiest way to print a file from the host is to run the same script inside the running mcp container. docker exec bypasses the server entrypoint and inherits the container's environment (CUPS server, default queue, secrets), so no extra configuration is needed.
# Markdown from a pipe (no file inside the container required)
cat notes.md | docker compose exec -T mcp python /app/cli/print_file.py -
# Options
docker compose exec -T mcp python /app/cli/print_file.py notes.md \
--page-size a4 --orientation portrait \
--margins-mm 12 --copies 2 \
--sides two-sided-long-edge --color monofile accepts a path inside the container or - to read from standard input. If the source is a real path, the rendered PDF is written next to it; for stdin input it is written to the container temp dir. Flags:
Flag | Default | Meaning |
| settings/default | CUPS queue name |
| file name or | job title |
|
|
|
|
|
|
|
| uniform margins in mm |
|
| number of copies |
|
|
|
|
|
|
For host-friendly use there is also a thin bin/print-md.sh wrapper that mounts the source directory so you can pass any file by absolute path:
./bin/print-md.sh --page-size a4 ./docs/report.mdTailscale CUPS access
Find the host's Tailscale IPv4 address and set it as the only published CUPS address:
tailscale ip -4CUPS_BIND_IP=100.100.20.30
CUPS_ALLOWED_NETWORKS=100.64.0.0/10Restart with docker compose up -d. Other tailnet machines can browse the web interface at http://100.100.20.30:631 and add a queue using:
ipp://100.100.20.30:631/printers/QUEUE_NAMECUPS_ALLOWED_NETWORKS accepts a comma-separated list of IP addresses or CIDRs. Keep CUPS_BIND_IP on localhost or a private interface; never use 0.0.0.0 unless a host firewall independently limits port 631. CUPS administration always requires the configured administrator credentials. Printing and printer/job queries are allowed from the configured networks so operating-system print clients work normally; job cancellation and configuration require authentication.
Cloudflare Tunnel
Everything ships in compose.yaml; only the two secrets need filling in .env:
MCP_BEARER_TOKEN— the static token every/mcprequest must send. Generate withopenssl rand -hex 32.CLOUDFLARE_TUNNEL_TOKEN— the connector token from a named tunnel (Cloudflare Zero Trust → Networks → Tunnels).
In Cloudflare Zero Trust, create a named tunnel and a public hostname whose service URL is:
http://mcp:8000Put the connector token in .env, then start the tunnel profile:
CLOUDFLARE_TUNNEL_TOKEN=your-named-tunnel-token
MCP_ALLOWED_HOSTS=your-hostname.exampledocker compose --profile tunnel up -d --buildBecause mcp is also on the tunnel-edge network, cloudflared reaches it at http://mcp:8000 even though port 8000 is otherwise bound only to localhost.
MCP URL and auth
The MCP endpoint is https://your-hostname.example/mcp, reached through the tunnel. Configure the client to send the bearer token on every request:
Authorization: Bearer YOUR_MCP_BEARER_TOKENThis mirrors the CLI: the server binary (print-mcp, started by the mcp service) enforces the same MCP_BEARER_TOKEN from .env, so the token is defined in one place for both the tunnel-facing MCP server and the local CLI. The Cloudflare tunnel token and MCP bearer token are separate secrets; rotate either by editing .env and recreating only the affected service.
MCP tools
list_printers()returns every configured CUPS queue and its current capabilities.print_markdown(markdown, title?, printer?, page_size?, orientation?, margins?, copies?, sides?, color_mode?)renders and submits a document. Standard paper sizes areletter,legal, anda4.get_job_status(job_id)returns the state and timestamps retained by CUPS.
Raw HTML is disabled. Markdown images may use public HTTP/HTTPS URLs or data URIs. Private, loopback, link-local, multicast, and reserved destinations are rejected, including after redirects. External images are downloaded and validated before PDF rendering; a failed image prevents submission.
Health and troubleshooting
curl http://127.0.0.1:8000/healthz
curl http://127.0.0.1:8000/readyz
docker compose logs cups mcp/healthz checks the MCP process. /readyz returns 503 until CUPS has at least one configured queue. Neither endpoint exposes document or credential data.
If CUPS cannot discover a network printer, add it by explicit IPP URI; multicast discovery does not reliably cross Docker or Tailscale networks.
Reserve the printer's LAN address in DHCP so its URI remains stable.
For an
ipps://printer with a private certificate, install its CA in the CUPS image instead of disabling TLS validation.If a requested duplex, color, or media option is unsupported, MCP returns
UNSUPPORTED_OPTIONwithout submitting the job.
Development
uv sync --extra dev
uv run ruff check .
uv run pytest
docker compose configThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityCmaintenanceConverts Markdown documents to PDF files with support for syntax highlighting, custom styling, Mermaid diagrams, optional page numbers, and configurable watermarks.176MIT
- Flicense-qualityDmaintenanceConverts Markdown files and raw content into professionally styled PDFs with full support for Mermaid diagrams and syntax highlighting. It offers customizable page formats, margins, and modern typography for high-quality document generation.11
- AlicenseBqualityDmaintenanceConverts Markdown files and content to styled PDFs with S3 integration, Mermaid diagrams, and ApexCharts support, supporting stdio, HTTP, and SSE transport modes.378MIT
- Alicense-qualityDmaintenanceEnables AI assistants to convert Markdown files and content to beautifully formatted PDF documents using Playwright's Chromium engine.50MIT
Related MCP Connectors
Turn HTML or Markdown into a clean, styled PDF and get a download link.
Generate PDFs from templates via AI chat. Works with Claude, ChatGPT, Cursor, and any MCP client.
Convert files, HTML, and Markdown to PDF via the FileToPDF API. Bring your own API key.
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/mdp/print-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server