paperpress
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进程完成三件事:
检测(
src/render/detect.ts)——使用池化的Playwright/Chromium实例导航目标URL,读取theme-color、品牌CSS自定义属性、CTA按钮颜色、按位置/大小/格式评分的<img>候选元素,以及计算出的字体栈。过滤接近白色/接近黑色/接近灰色的噪声,应用WCAG亮度保护,避免过浅的品牌色破坏文字对比度。渲染(
src/render/)——通过unified/remark/rehype(allowDangerousHtml: false)将markdown转换为HTML,应用五种主题之一以及检测到/提供的品牌套件,并使用Playwright打印为PDF。服务——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 |
| - | 通过邮箱请求密钥。始终返回202;密钥发送至收件箱。首次调用 = 新用户 + 免费额度。后续调用 = 轮换(旧密钥有效24小时,之后撤销)。 |
渲染
方法 | 路径 | 认证 | 说明 |
POST |
| Bearer密钥 | Markdown → PDF。接受 |
GET |
| 签名URL | 流式传输PDF |
品牌套件
方法 | 路径 | 认证 | 说明 |
POST |
| Bearer密钥 | 按名称创建/更新保存的套件(每个用户每个名称一个) |
GET |
| Bearer密钥 | 列出你的套件 |
GET |
| Bearer密钥 | 读取单个套件 |
DELETE |
| Bearer密钥 | 删除套件 |
POST |
| Bearer密钥 | 传入URL,返回 |
POST |
| Bearer密钥 | 最多20个URL并行通过现有Playwright池。单项错误内联返回。 |
账户 / 健康检查
方法 | 路径 | 认证 | 说明 |
GET |
| Bearer密钥 | 邮箱、额度、活动API密钥列表(明文,便于用户找回) |
GET |
| - |
|
演示(匿名,受限)
无需认证,不消耗额度。在全局60次/分钟限制之上,另有严格的每IP速率限制(30次/小时)。
方法 | 路径 | 说明 |
POST |
|
|
管理(只读)
由X-Admin-Token请求头控制。当ADMIN_TOKEN未设置时,所有/admin/*路由返回404——无表面、无发现。
方法 | 路径 | 说明 |
GET |
| 总计:用户、活动密钥、文档、页数、字节数、额度、最近24小时/7天的文档数 |
GET |
| 分页列表,含每个用户的文档和密钥数量 |
GET |
| 分页列表,关联用户邮箱。可按 |
GET |
| 无需签名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-rehype以allowDangerousHtml: 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作为基础镜像。保持该版本与playwrightnpm包同步——不匹配会导致浏览器二进制文件不存在,渲染失败。railway.json中的startCommand有意省略:Railway将其解析为argv(而非shell),因此链式&&命令会失败。Dockerfile中的CMD使用sh -c包装并运行完整启动序列。邮件:在设置
RESEND_API_KEY之前,注册密钥会记录到stdout。搜索[email:console]。
示例
所有这些都已提交到samples/——由scripts/preview.ts和scripts/detect.ts生成,可使用npx tsx scripts/preview.ts / npx tsx scripts/detect.ts <url>自行重新生成。
同一markdown,五种主题(下方展示clean——完整PDF):

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

来源 URL | 检测 + 渲染 |
github.com | |
railway.com | |
vercel.com |
内联品牌套件(无 URL,字段直接在请求中传递)— forest、mono-coral、stripe-colors。
上面使用的输入 markdown:sample.md、demo-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。
This server cannot be installed
Maintenance
Related MCP Servers
- AlicenseAqualityAmaintenanceTurn 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.2381MIT
- AlicenseNot gradedqualityDmaintenanceProvides a standardized interface for interacting with Markitdown's tools and services through a unified API, compatible with MCP-compliant services.MIT

docjet-mcpofficial
AlicenseAqualityAmaintenanceRender branded PDF documents (invoices, reports, certificates) and PNG social images directly from any MCP client using DocJet templates or raw HTML with JSON data.340MIT- AlicenseNot gradedqualityBmaintenanceMCP server that exposes brand identity guidelines (visual look and voice) as markdown, enabling LLMs to produce on-brand content.13MIT
Related MCP Connectors
Render HTML, Markdown, or URLs to images, PDF, or branded artifacts; extract and watch pages.
Turn HTML or Markdown into a clean, styled PDF and get a download link.
Markdown in, any format out. PDFs merged, split, watermarked. Runs on our own doc engines.
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/rozetyp/paperpress'
If you have feedback or need assistance with the MCP directory API, please join our Discord server