Skip to main content
Glama

paperpress

一个API,可从公司的实时URL检测其品牌——标志、主色、字体——并将markdown渲染成以该品牌风格排版的PDF,一次HTTP调用即可完成。以纯REST API和MCP服务器两种形式提供。

POST /v1/documents
{ "markdown": "# Q4 report\n...", "brandFromUrl": "stripe.com" }
→ ~1s → signed URL to a PDF in Stripe's brand

关于本项目

这个项目曾被实际构建并短暂作为部署服务运行,之后我仔细审视了它需要竞争的领域:Claude现在原生支持PDF/PPTX/DOCX生成,而Brandfetch已经在销售专门用于为AI代理提供上下文的Brand Context API,并且拥有真实的付费客户。这个项目所做的两件事——检测品牌、渲染文档——如今要么已接近商品化,要么已被有资金支持的竞争对手占据。我不再将其作为产品推进。

它作为作品集/参考实现公开:一个基于Playwright的品牌检测器(CSS自定义属性、CTA按钮颜色采样、评分式logo候选提取、WCAG对比度保护)、一个同步Fastify渲染流水线、一个经过SSRF加固的URL抓取器,以及一个包装它的MCP服务器。可阅读代码、fork、运行——采用MIT许可证。它不作为产品维护:未接入支付处理,mcp/包也未发布到npm。

Related MCP server: Markitdown Universal MCP Server

工作原理

一个Fastify进程完成三件事:

  1. 检测src/render/detect.ts)——使用池化的Playwright/Chromium实例导航目标URL,读取theme-color、品牌CSS自定义属性、CTA按钮颜色、按位置/大小/格式评分的<img>候选元素,以及计算出的字体栈。过滤接近白色/接近黑色/接近灰色的噪声,应用WCAG亮度保护,避免过浅的品牌色破坏文字对比度。

  2. 渲染src/render/)——通过unified/remark/rehypeallowDangerousHtml: false)将markdown转换为HTML,应用五种主题之一以及检测到/提供的品牌套件,并使用Playwright打印为PDF。

  3. 服务——PDF输出到本地磁盘(或挂载的卷),通过HMAC签名、限时URL访问。

无队列、无工作进程、无Redis。渲染为同步操作,Chromium预热后通常耗时100–400ms;检测结果按主机缓存24小时。

项目内容

.
├── src/                       Fastify API (single process)
│   ├── index.ts               Bootstrap, route registration
│   ├── env.ts                 Env validation (zod)
│   ├── lib/                   prisma, auth, billing, storage, email, url-fetch (SSRF guard), inline-image
│   ├── render/                markdown → HTML → PDF (themes/, detect.ts)
│   └── routes/                auth, documents, demo, account, pdf, brand-kits, admin
├── prisma/schema.prisma       5 models: User, ApiKey, Document, CreditTransaction, BrandKit
├── mcp/                       MCP server (unpublished — see mcp/README.md)
├── samples/                   Example output (see Examples below) + input markdown used to generate it
├── scripts/                   preview.ts / detect.ts — regenerate the samples/ output locally
├── Dockerfile                 Single-image deploy (Playwright base)
└── railway.json               Railway config (healthcheck only — start cmd is in Dockerfile)

API接口(v1)

认证

方法

路径

认证

功能

POST

/auth/register

-

通过邮箱请求密钥。始终返回202;密钥发送至收件箱。首次调用 = 新用户 + 免费额度。后续调用 = 轮换(旧密钥有效24小时,之后撤销)。

渲染

方法

路径

认证

说明

POST

/v1/documents

Bearer密钥

Markdown → PDF。接受themebrandKit(保存的名称或内联)、brandFromUrl(检测并应用的快捷方式)、cssformatlandscapetitle。返回签名URL。若渲染页数超过MAX_PAGES_PER_RENDER(默认200)则返回413。

GET

/pdf/:id?exp=&sig=

签名URL

流式传输PDF

品牌套件

方法

路径

认证

说明

POST

/v1/brand-kits

Bearer密钥

按名称创建/更新保存的套件(每个用户每个名称一个)

GET

/v1/brand-kits

Bearer密钥

列出你的套件

GET

/v1/brand-kits/:id

Bearer密钥

读取单个套件

DELETE

/v1/brand-kits/:id

Bearer密钥

删除套件

POST

/v1/brand-kits/detect

Bearer密钥

传入URL,返回primaryColorlogoUrlfaviconfontFamilyfontStyle。缓存24小时。传入{ refresh: true }可绕过缓存。

POST

/v1/brand-kits/detect-batch

Bearer密钥

最多20个URL并行通过现有Playwright池。单项错误内联返回。

账户 / 健康检查

方法

路径

认证

说明

GET

/account

Bearer密钥

邮箱、额度、活动API密钥列表(明文,便于用户找回)

GET

/health

-

{ status: 'ok' }

演示(匿名,受限)

无需认证,不消耗额度。在全局60次/分钟限制之上,另有严格的每IP速率限制(30次/小时)。

方法

路径

说明

POST

/v1/demo

{ url } → 检测品牌(缓存)+ 将内置的samples/demo-q4-review.md渲染为PDF。返回套件 + 签名URL。

管理(只读)

X-Admin-Token请求头控制。当ADMIN_TOKEN未设置时,所有/admin/*路由返回404——无表面、无发现。

方法

路径

说明

GET

/admin/stats

总计:用户、活动密钥、文档、页数、字节数、额度、最近24小时/7天的文档数

GET

/admin/users

分页列表,含每个用户的文档和密钥数量

GET

/admin/documents

分页列表,关联用户邮箱。可按userIdstatus过滤。

GET

/admin/documents/:id/pdf

无需签名URL即可流式传输任意PDF

安全态势

  • API密钥:192位随机,明文存储(以便/account显示);撤销使用revokedAt时间戳,并设有宽限期。

  • 签名URL:HMAC-SHA256,exp + sig查询参数,默认TTL为7天。

  • SSRF防护assertPublicUrl解析DNS,并拒绝RFC1918、环回、链路本地、IPv6 ULA地址。应用于brandKit.logoUrl/v1/brand-kits/detect。提交的主机为公网并不能保证每一跳都是公网——公网主机可重定向到私有地址——因此Playwright导航路径(src/render/detect.ts)和图片内联抓取(src/lib/inline-image.ts)都会在每次重定向跳转前重新验证地址,并在最终导航后再次验证。这缩小了攻击窗口,但并未完全消除:对重定向目标的初始连接发生在重新检查拒绝之前,因此即使被拒绝的目标从未被提取或渲染任何页面内容,有决心的攻击者仍可导致一次盲出站请求(不会向攻击者返回任何响应数据)。完全闭合需要在网络层进行IP固定。

  • CSS注入防护css字段拒绝<style></style><script></script>——否则在<style>${css}</style>内部的原始嵌入会让攻击者逃逸并在Chromium池中运行JS。

  • Markdown净化remark-rehypeallowDangerousHtml: false运行,因此markdown正文中的<script>会被剥离。

  • 限制:markdown ≤ 500KB,css ≤ 50KB,正文 ≤ 2MB,渲染 ≤ 30s,渲染页数 ≤ MAX_PAGES_PER_RENDER(默认200),速率 ≤ 60次/分钟/密钥。

  • 管理端点:恒定时间令牌比较;当令牌错误或未设置时,路由返回404(而非401)。

已知限制

不是产品,因此这些是披露而非作为待办事项跟踪:

  • 无自动化测试套件。 上述所有内容均通过手动验证运行实例;没有回归测试网络。

  • 无已提交的Prisma迁移。 容器启动命令运行prisma db push --skip-generate --accept-data-loss

  • 内存速率限制和检测缓存。 单实例没问题;若需多副本,需将两者迁移到共享存储。

  • 未接入支付处理。 额度系统存在于schema和API中;没有信用卡计费。

  • MCP包(mcp/)未发布到npm,也不打算发布——见mcp/README.md

本地开发

# 1. Postgres running locally on 5432
# 2. Env
cp .env.example .env
# (set SIGNING_SECRET to `openssl rand -base64 32`)

# 3. Install + migrate
npm install
npx prisma migrate dev

# 4. Run
npm run dev

冒烟测试:

# Request a key. Response is { "sent": true } - the key arrives by email.
# In dev (RESEND_API_KEY unset) the server logs the email to stdout; grab the
# key from there.
curl -X POST http://localhost:3000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com"}'

export PP_KEY="pp_live_..."

# Render
curl -X POST http://localhost:3000/v1/documents \
  -H "Authorization: Bearer $PP_KEY" \
  -H "Content-Type: application/json" \
  -d '{"markdown":"# Hello\n\nWorld.","title":"Test"}'

MCP

mcp/README.md。一个基于REST API的轻量MCP客户端。未发布到npm——作为参考代码而非可安装工具。

部署(Railway示例)

实际部署此项目时使用的确切步骤——此处作为文档保留,而非邀请在生产环境中运行。

# 1. Create project with a Postgres database
railway init --name paperpress
railway add --database postgres

# 2. Create the app service. DATABASE_URL is wired via service reference.
railway add --service paperpress \
  --variables "DATABASE_URL=\${{Postgres.DATABASE_URL}}" \
  --variables "SIGNING_SECRET=$(openssl rand -base64 32)" \
  --variables "PUBLIC_BASE_URL=https://your-app.up.railway.app" \
  --variables "STORAGE_DIR=/data/storage" \
  --variables "NODE_ENV=production" \
  --variables "FREE_TIER_CREDITS=100" \
  --variables "PLAYWRIGHT_MAX_CONTEXTS=2" \
  --variables "RENDER_TIMEOUT_MS=30000" \
  --variables "KEY_GRACE_PERIOD_HOURS=24" \
  --variables "MAX_PAGES_PER_RENDER=200" \
  --variables "ADMIN_TOKEN=$(openssl rand -base64 36 | tr -d '\n')"

# 3. Attach a volume so PDFs survive container restarts
railway service paperpress
railway volume add --mount-path /data/storage

# 4. Domain (auto-detects the container port)
railway domain --port 3000

# 5. Ship
railway up --detach -c

注意事项:

  • Dockerfile使用mcr.microsoft.com/playwright:vX.Y-jammy作为基础镜像。保持该版本与playwright npm包同步——不匹配会导致浏览器二进制文件不存在,渲染失败。

  • railway.json中的startCommand有意省略:Railway将其解析为argv(而非shell),因此链式&&命令会失败。Dockerfile中的CMD使用sh -c包装并运行完整启动序列。

  • 邮件:在设置RESEND_API_KEY之前,注册密钥会记录到stdout。搜索[email:console]

示例

所有这些都已提交到samples/——由scripts/preview.tsscripts/detect.ts生成,可使用npx tsx scripts/preview.ts / npx tsx scripts/detect.ts <url>自行重新生成。

同一markdown,五种主题(下方展示clean——完整PDF):

clean主题示例

从实时URL自动检测品牌brandFromUrl: "stripe.com"——完整PDF):

stripe品牌检测示例

来源 URL

检测 + 渲染

github.com

detect-github-com.pdf

railway.com

detect-railway-com.pdf

vercel.com

detect-vercel-com.pdf

内联品牌套件(无 URL,字段直接在请求中传递)— forestmono-coralstripe-colors

上面使用的输入 markdownsample.mddemo-q4-review.md/v1/demo 使用的那份)。

状态

已构建:markdown → PDF,支持 5 种主题;从 URL 自动检测品牌套件(真实的字体族栈,而不仅仅是一个 serif|sans|mono 类别);24 小时检测缓存;brandFromUrl 单次调用快捷方式;批量检测(并行 20 个 URL);WCAG 亮度校验;基于电子邮件的密钥签发,带轮换宽限期;一个 MCP 服务器;只读管理界面;签名分享 URL;预渲染文档。

刻意未构建:支付处理、MCP 包的 npm 发布、自动化测试、真正的 Prisma 迁移。这不是一个活跃的待办事项列表 — 它作为作品集项目已经完成,并不是在朝着 1.0 版本开发。

许可证

MIT — 参见 LICENSE

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Turn markdown into designed PDFs with cover page, table of contents, and code blocks that hold across pages. One command from Claude Desktop, Claude Code, Cursor, Cline, Zed, or any MCP-capable client.
    2
    38
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides a standardized interface for interacting with Markitdown's tools and services through a unified API, compatible with MCP-compliant services.
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

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/rozetyp/paperpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server