paperpress
paperpress
회사의 브랜드를 라이브 URL에서 감지하는 API — 로고, 기본 색상, 폰트 — 그리고 마크다운을 해당 브랜드 스타일의 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 색상 샘플링, 점수 기반 로고 후보 추출, WCAG 대비 가드), 동기식 Fastify 렌더 파이프라인, SSRF 강화 URL 페처, 그리고 이를 감싸는 MCP 서버. 코드를 읽고, 포크하고, 실행하세요 — MIT 라이선스입니다. 제품으로 유지보수되지는 않습니다: 결제 처리는 연결되어 있지 않으며 MCP 패키지(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사용)로 마크다운을 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 키 | 마크다운 → 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를 해석하고 호출자가 제출한 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 요청/분/키.관리자 엔드포인트: 상수 시간 토큰 비교; 토큰이 잘못되었거나 설정되지 않은 경우 경로는 401이 아닌 404를 반환합니다.
알려진 제한 사항
제품이 아니므로 백로그로 추적하기보다 공개합니다:
자동화된 테스트 스위트 없음. 위의 모든 것은 실행 중인 인스턴스에 대해 수동으로 검증되었습니다. 회귀 테스트 네트가 없습니다.
커밋된 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가 설정될 때까지 등록 키는 stdout에 기록됩니다.[email:console]을 검색하세요.
예시
이 모든 것은 samples/에 체크인되어 있습니다 — scripts/preview.ts와 scripts/detect.ts로 생성되었으며, npx tsx scripts/preview.ts / npx tsx scripts/detect.ts <url>로 직접 재생성할 수 있습니다.
동일한 마크다운, 다섯 가지 테마 (아래는 clean — 전체 PDF):

테마 | |
| |
| |
| |
|
라이브 URL에서 자동 감지된 브랜드 (brandFromUrl: "stripe.com" — 전체 PDF):

소스 URL | 감지 + 렌더링됨 |
github.com | |
railway.com | |
vercel.com |
인라인 브랜드 키트(URL 없음, 요청에 필드가 직접 전달됨) — forest, mono-coral, stripe-colors.
위에서 사용된 입력 마크다운: 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 마이그레이션. 이것은 라이브 백로그가 아닙니다 — 포트폴리오 작품으로 완성된 것이며, 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