paperpress
paperpress
ライブURLから企業のブランド(ロゴ、プライマリカラー、フォント)を検出し、そのブランドでスタイルされたPDFにマークダウンをレンダリングするAPIです。1回の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ボタンの色サンプリング、スコアリングされたロゴ候補の抽出、WCAGコントラストガード)、同期Fastifyレンダリングパイプライン、SSRF対策済みURLフェッチャー、そしてそれをラップするMCPサーバーで構成されています。コードを読んだり、フォークしたり、実行したり自由です。MITライセンスです。製品としてメンテナンスされているわけではありません。決済処理は組み込まれておらず、MCPパッケージ(mcp/)はnpmに公開されていません。
Related MCP server: Markitdown Universal MCP Server
仕組み
単一のFastifyプロセスが3つのことを行います。
検出(
src/render/detect.ts)— プールされたPlaywright/Chromiumインスタンスで対象URLにアクセスし、theme-color、ブランドCSSカスタムプロパティ、CTAボタンの色、位置・サイズ・形式でスコアリングされたヘッダー<img>候補、計算済みフォントスタックを読み取ります。白に近い・黒に近い・グレーに近いノイズを除去し、WCAGの輝度ガードを適用して、明るすぎるブランドカラーがテキストのコントラストを壊さないようにします。レンダリング(
src/render/)— マークダウンをunified/remark/rehype(allowDangerousHtml: false)でHTMLに変換し、5つのテーマと検出・指定されたブランドキットを適用して、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キー | マークダウン→PDF。 |
GET |
| 署名付きURL | PDFをストリーミングします |
ブランドキット
メソッド | パス | 認証 | 説明 |
POST |
| Bearerキー | 名前を付けてキットを保存・更新します(ユーザーごとに名前で1つ) |
GET |
| Bearerキー | 自分のキットを一覧表示します |
GET |
| Bearerキー | 1つのキットを読み取ります |
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を解決し、呼び出し元が送信したURLの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を実行できてしまいます。マークダウンのサニタイズ:
remark-rehypeはallowDangerousHtml: falseで実行されるため、マークダウン本文内の<script>は削除されます。制限: マークダウン≤500KB、CSS≤50KB、本文≤2MB、レンダリング≤30秒、レンダリングページ数≤
MAX_PAGES_PER_RENDER(デフォルト200)、レート≤毎分60リクエスト/キー。管理者エンドポイント: 定数時間のトークン比較。トークンが間違っているか未設定の場合、ルートは404(401ではなく)を返します。
既知の制限事項
製品ではないため、これらはバックログとして追跡するのではなく開示しています。
自動テストスイートはありません。 上記はすべて実行中のインスタンスに対して手動で検証されたもので、リグレッションネットはありません。
Prismaマイグレーションはコミットされていません。 コンテナの起動コマンドは
prisma db push --skip-generate --accept-data-lossを実行します。インメモリのレート制限と検出キャッシュ。 シングルインスタンスでは問題ありませんが、共有ストレージに移行しない限り、複数レプリカでは機能しません。
決済処理は組み込まれていません。 クレジットシステムはスキーマと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(シェルではなく)として解析するため、&&で連結されたコマンドは失敗します。DockerfileのCMDはsh -cでラップされ、完全な起動シーケンスを実行します。メール:
RESEND_API_KEYが設定されるまで、登録キーは標準出力にログ出力されます。[email:console]をgrepしてください。
例
これらはすべてsamples/にチェックインされています。scripts/preview.tsとscripts/detect.tsによって生成され、npx tsx scripts/preview.ts / npx tsx scripts/detect.ts <url>で自分で再生成できます。
同じマークダウン、5つのテーマ(以下は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 で使用)。
ステータス
実装済み: 5つのテーマでのmarkdown → PDF変換、URLからのブランドキット自動検出(serif|sans|mono の単純な分類ではなく、実際のfont-familyスタックを使用)、24時間の検出キャッシュ、brandFromUrl によるワンコールショートカット、バッチ検出(20 URLを並列処理)、WCAG輝度比ガード、メールベースのキー発行とローテーション猶予期間、MCPサーバー、読み取り専用の管理画面、署名付き共有URL、事前レンダリング済みドキュメント。
意図的に未実装: 決済処理、MCPパッケージのnpm公開、自動テスト、本番用Prismaマイグレーション。これはライブ運用中のバックログではなく、ポートフォリオ作品として完成したものであり、v1.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