Skip to main content
Glama

md-beautifier

An MCP server that turns raw Markdown into a complete, self-contained HTML document rendered with clean typographic styling — serif fonts, syntax highlighting, and automatic light/dark mode. Your LLM agent can hand it the raw markdown it wrote and get back a ready-to-embed <html> string (all CSS inlined, works offline, no CDN).

Packaged as a single Node.js container, run with Docker Compose.

The tool

Name

render_markdown_to_html

Transport

MCP Streamable HTTP at http://<host>:3010/mcp (stdio mode also supported)

Input

markdown (required), title, subtitle (optional)

Output

A URL (${MCP_PUBLIC_BASE_URL}/files/<uuid>.html) to the saved, self-contained HTML document

Example of how an agent calls it:

render_markdown_to_html(markdown="<raw md>", title="Holiday Guide")

Related MCP server: Website Scraper MCP Server

Quick start

docker compose up -d --build

Verify it is healthy and reachable:

curl http://localhost:3010/health

Test a render directly:

curl -s -X POST http://localhost:3010/mcp \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

Connecting Open WebUI (>= 0.6.31)

Open WebUI only supports Streamable HTTP MCP servers natively, so this is the default transport.

  1. Admin Settings → External Tools → + Add Server

  2. Type: MCP (Streamable HTTP)

  3. Server URL: http://host.docker.internal:3010/mcp

    • host.docker.internal works when Open WebUI itself runs in Docker on the same host.

    • Use http://localhost:3010/mcp if Open WebUI runs natively on the host.

    • From another machine, use the host's LAN IP.

  4. Auth: None by default. If you set MCP_API_TOKEN in .env, choose Bearer and enter the token.

  5. Save, then enable the tool for a model and make sure function calling is on for that model so the agent can invoke it.

File serving

Each tool call persists the rendered document as <DATA_DIR>/<uuid>.html and the tool response is a link to it: GET /files/<uuid>.html. The app applies no authentication — put it behind a reverse proxy that enforces access (e.g. an nginx IP allowlist for LAN + VPN) and use https:// in the public base URL.

Persisted files expire: anything older than FILE_TTL_HOURS is unlinked at startup and on a recurring sweep. If the files must survive container recreation, mount a persistent volume at MD_BEAUTIFIER_DATA_DIR (e.g. - beautifier-files:/app/data).

Without MCP_PUBLIC_BASE_URL the tool falls back to returning the raw HTML string.

Configuration

Variable

Default

Purpose

PORT

3010

HTTP port the MCP endpoint listens on

HOST

0.0.0.0

Bind address

MCP_TRANSPORT

http

http (Streamable HTTP) or stdio

MCP_API_TOKEN

(empty)

If set, requests must carry Authorization: Bearer <token>

MCP_PUBLIC_BASE_URL

(empty)

Base URL embedded in returned links, e.g. https://beautifier.example.com

MD_BEAUTIFIER_DATA_DIR

/app/data

Where rendered documents are stored (mount a volume here)

FILE_TTL_HOURS

1

Documents older than this are deleted (fractions allowed)

stdio mode (for other clients)

Some clients (e.g. Claude Desktop, opencode) can launch stdio MCP servers directly:

docker compose run --rm -e MCP_TRANSPORT=stdio md-beautifier

or run natively:

npm install && MCP_TRANSPORT=stdio node src/server.js

Styling

The embedded stylesheet lives in src/styles.js — a single place to tweak fonts, colors, and layout. Current look:

  • Clean minimal article layout, ~46rem reading width

  • Serif type stack: Iowan Old Style / Palatino Linotype / Palatino / Georgia

  • Syntax highlighting via highlight.js (common languages), palette tuned to the theme

  • Automatic light/dark via prefers-color-scheme

  • Styled tables, blockquotes, task lists, kbd, images

Raw HTML in markdown is escaped (not executed), so the tool is safe to run on untrusted input.

Development

npm install
npm test        # unit tests for the renderer
npm start       # run locally on :3010

Layout

├── docker-compose.yml    # runs the service, maps :3010
├── Dockerfile            # node:22-alpine
├── src/
│   ├── server.js         # entry: HTTP/stdio transport + /files serving + TTL sweep
│   ├── mcp.js            # MCP server + tool definition (returns file links)
│   ├── renderer.js       # markdown-it + highlight.js
│   ├── styles.js         # embedded CSS template
│   └── storage.js        # data dir, file persistence, TTL expiry sweep
└── test/                 # renderer.test.js, storage.test.js

Related MCP Connectors

Related MCP Servers