md-beautifier
by JayKid
README.md
# 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")
```
## Quick start
```bash
docker compose up -d --build
```
Verify it is healthy and reachable:
```bash
curl http://localhost:3010/health
```
Test a render directly:
```bash
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:
```bash
docker compose run --rm -e MCP_TRANSPORT=stdio md-beautifier
```
or run natively:
```bash
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
```bash
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
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues