AI read-it-later & personal knowledge base
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Documented (README)
- !No standard license detected
git clone https://github.com/nublson/purl{
"mcpServers": {
"purl": {
"command": "node",
"args": ["/path/to/purl/dist/index.js"],
"env": {
"DATABASE_URL": "<database_url>",
"NEXT_PUBLIC_SUPABASE_URL": "<next_public_supabase_url>",
"NEXT_PUBLIC_SUPABASE_ANON_KEY": "<next_public_supabase_anon_key>",
"SUPABASE_SERVICE_ROLE_KEY": "<supabase_service_role_key>",
"GOOGLE_CLIENT_SECRET": "<google_client_secret>",
"GITHUB_CLIENT_SECRET": "<github_client_secret>"
}
}
}
}DATABASE_URLNEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYGOOGLE_CLIENT_SECRETGITHUB_CLIENT_SECRETResumen de MCP Servers
<p align="center">
<img src="thumbnail.jpeg" alt="Purl — Save Anything. Keep what matters. A home for your links, PDFs, video, and audio." width="920" />
</p>
# Purl
**Save anything. Keep it in one place.**
**Live preview:** [https://purl.nublson.com](https://purl.nublson.com)
Purl is a read-it-later app — a home for your "pearls". You paste URLs: web pages, PDFs, YouTube videos, and audio. Purl resolves each item's metadata (title, favicon, description, thumbnail) and keeps everything in one place, available from the app, a REST API, and an MCP server.
Purl is free, with one limit: each account can save up to **1,000 links** (`MAX_SAVED_LINKS` in [`src/lib/limits.ts`](src/lib/limits.ts)).
The product goal: one place to stash material you care about.
## Implemented today
- **Marketing site** — Landing page (hero with a live preview), docs for the REST API and MCP server, privacy and terms.
- **Authentication** — Google and GitHub sign-in on the landing page via [Better Auth](https://www.better-auth.com/) (Apple too when its env vars are set). Every account gets a unique username, editable in Settings, where you can also connect or disconnect sign-in methods; avatars come from the OAuth provider's profile photo.
- **Save & organize**
- Add items by URL with automatic content-type detection (web, PDF, YouTube, audio).
- Links grouped by relative time (e.g. Today, This week, Last month).
- Preview metadata (title, description, favicon, thumbnail where available).
- **Hardened outbound fetch** — Server-side `safeFetch` with optional proxy/DNS controls (see `AGENTS.md`). An egress proxy can be configured via [`SAFE_OUTBOUND_HTTP_PROXY`](docs/production-outbound-proxy.md).
- **Realtime list sync** — Supabase Realtime so saves and updates propagate across tabs/devices quickly.
- **Link actions** — Open original, copy URL, edit metadata, delete.
- **REST API & MCP** — `/api/v1` and an MCP server (`save_link`, `list_saved_items`, `get_link`) with API-key or OAuth auth.
- **Operational extras** — Optional Upstash-backed API rate limiting, Vitest coverage for critical paths.
- **PWA (installable app)** — [Web App Manifest](public/manifest.json) plus a [Serwist](https://serwist.pages.dev/) service worker ([`src/app/sw.ts`](src/app/sw.ts)) that builds to **`public/sw.js`** (generated on `pnpm build`, gitignored). Enables **Install** in Chrome/Edge and similar where the platform supports it, with runtime caching via Serwist's Next.js defaults and a static offline shell at [`/~offline`](src/app/~offline/page.tsx). **Serwist is disabled in `pnpm dev`** to avoid service-worker cache surprises during development — use **`pnpm build && pnpm start`** (or your production URL) to exercise installability and the SW.
## Save flow
Saving a link is fully **synchronous** — there is no background processing.
1. **Input** — `POST /api/links` with a URL (also `/api/v1/links` and the MCP `save_link` tool).
2. **Classify & decorate** — Server-side [`detectContentType`](src/lib/server-detect-content-type.ts) (SSRF-safe `HEAD` / sniff) plus [`scrapeLinkMetadata`](src/lib/links.ts) (Open Graph HTML, PDF `Content-Disposition` / size, YouTube oEmbed). Saving an existing URL again refreshes its metadata and moves it to the top.
3. **Persist** — A `Link` row with title, favicon, thumbnail, domain, and `contentType` (`WEB`, `PDF`, `YOUTUBE`, or `AUDIO`).
4. **Sync** — [`broadcastLinksChanged`](src/lib/realtime-broadcast.ts) notifies other tabs/devices via Supabase Realtime.
## Not implemented yet
These are called out explicitly because the repo is going public:
- **Settings breadth** — Settings cover username, sign-in methods, and account deletion; broader account preferences (notification settings, etc.) are not implemented yet.
**Marketing vs. product:** The landing page copy mentions ideas such as **collections** and a **weekly digest**. Those are **not** built in the current schema or app — treat them as roadmap, not shipped features.
## Tech stack
- **Web:** Next.js (App Router), React, TypeScript
- **UI:** Tailwind CSS, shadcn/ui
- **Auth:** Better Auth
- **Database:** PostgreSQL + Prisma
- **Email (optional in dev):** Resend for feedback emails
- **Realtime:** Supabase client (anon + service role on server)
- **PWA:** [Serwist](https://serwist.pages.dev/) (`@serwist/next`), web manifest + precache / offline fallback
## CI / GitHub Actions
Automation lives under [`.github/workflows/`](.github/workflows/). Every PR and manual release is gated by these pipelines.
### PR checks — [`pr-checks.yml`](.github/workflows/pr-checks.yml)
Runs on **`pull_request`** to **`develop`** and **`main`**: **Setup & validation** → **Prisma** (generate client + type fixes) → **Lint** and **type check** (in parallel) → **Tests** and **production build** (in parallel, after lint and type check pass). Concurrency is per-PR so new pushes cancel stale runs.
<p align="center">
<img src="prCheckPipeline.png" alt="GitHub Actions graph for pr-checks.yml: Setup, Prisma, Lint & Type Check, Test & Build" width="920" />
</p>
### Release — [`release.yml`](.github/workflows/release.yml)
Runs on **`workflow_dispatch`** (manual): **Merge `develop` into `main`**, then **build validation** so production is only promoted after a green build.
<p align="center">
<img src="releasePipeline.png" alt="GitHub Actions graph for release.yml: merge develop into main, then build validation" width="920" />
</p>
## Security
Purl is built around **untrusted input** (arbitrary URLs). A few layers matter in production:
- **SSRF-aware outbound fetches** — User-supplied URLs are not passed to raw `fetch`. OG/thumbnail probes, PDF fetch, content-type sniffing, and similar paths go through [`safeFetch`](src/lib/safe-outbound-fetch.ts): HTTP(S) only, blocked private/link-local/reserved targets, redirect handling with per-hop host checks, DNS resolution pinned before connect (mitigates classic DNS rebinding against the pre-check), optional response size caps (e.g. PDF proxy). Optional **egress proxy** and custom DNS servers are documented in [`AGENTS.md`](AGENTS.md).
- **Authentication & route gating** — [Better Auth](https://www.better-auth.com/) sessions; Next.js [`proxy`](src/proxy.ts) redirects unauthenticated users away from private routes. Sign-in is OAuth-only (Google/GitHub, plus Apple when configured); there are no passwords.
- **API authorization** — Sensitive routes (`/api/links`, `/api/v1/*`, MCP, etc.) resolve the session server-side and scope work to the signed-in user.
- **Rate limiting** — When `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` are set, the proxy applies per-IP limits to **`/api/auth/*`**, **`POST /api/links`**, and **`POST /api/feedback`** (see [`proxy-rate-limit.ts`](src/lib/proxy-rate-limit.ts)). Without Upstash, limits are disabled — fine locally, not ideal for production.
- **Secrets & client exposure** — `SUPABASE_SERVICE_ROLE_KEY` and similar values are server-only. The browser uses the Supabase **anon** key for Realtime only; `.env` stays gitignored.
- **Response bounds** — PDF proxy streaming is size-capped (see `safe-outbound-fetch`).
**Reporting a vulnerability:** use [GitHub Security Advisories](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) for this repository so details stay private until patched.
## Setup (local development)
### Prerequisites
- **Node.js:** recent LTS
- **Package manager:** `pnpm` (this repo includes `pnpm-lock.yaml`)
- **Postgres:** local or hosted (Supabase works well)
### 1) Install dependencies
```bash
pnpm install
```
### 2) Configure environment variables
Create a `.env` file in the repo root. See `.env.example` for the full list; minimum for core behavior:
```bash
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DBNAME"
# Supabase Realtime — cross-device instant link list sync (same project as Postgres)
NEXT_PUBLIC_SUPABASE_URL="https://YOUR_PROJECT.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="eyJ..."
SUPABASE_SERVICE_ROLE_KEY="eyJ..."
# Sign-in (OAuth apps with callback {BETTER_AUTH_URL}/api/auth/callback/{google,github})
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."
GITHUB_CLIENT_ID="..."
GITHUB_CLIENT_SECRET="..."
# Optional (used to send in-app feedback emails)
RESEND_API_KEY="re_..."
RESEND_FROM="Purl <onboarding@resend.dev>"
```
Notes:
- **`DATABASE_URL`** is required (Prisma + Better Auth).
- **Supabase** env vars are required for realtime link list sync. Use **Project Settings → API** in the Supabase dashboard. The service role key must stay server-only.
- **Google/GitHub** OAuth credentials are required in production; locally, a provider whose vars are missing is simply hidden. For local dev, use callback URLs `http://localhost:3000/api/auth/callback/{google,github}`. Apple is optional and enabled only when all four `APPLE_*` vars are set (see `.env.example`).
- **Resend** is optional for local dev: without `RESEND_API_KEY`, in-app feedback can't be sent.
- **Better Auth** secrets and URLs are in `.env.example` — copy those keys for a working auth setup.
### 3) Run database migrations
```bash
pnpm prisma migrate dev
```
### 4) Generate Prisma client (if needed)
```bash
pnpm prisma generate
```
### 5) Start the dev server
```bash
pnpm dev
```
Open `http://localhost:3000`.
**Optional: a named HTTPS URL with [portless](https://portless.sh/).** With portless installed globally (`npm install -g portless`, Node.js 24+), run `portless run` instead of `pnpm dev`. It runs the `dev` script behind a local proxy at `https://purl.localhost`. Point auth at that URL in `.env.local`, or sign-in rejects the origin:
```bash
BETTER_AUTH_URL="https://purl.localhost"
BASE_URL="https://purl.localhost"
```
In a git worktree, portless prefixes the branch name (for example `https://my-branch.purl.localhost`); set both variables to that URL there.
**PWLo que la gente pregunta sobre purl
¿Qué es nublson/purl?
+
nublson/purl es mcp servers para el ecosistema de Claude AI. AI read-it-later & personal knowledge base Tiene 2 estrellas en GitHub y su última actualización registrada es del 2026-10-01.
¿Cómo se instala purl?
+
Puedes instalar purl clonando el repositorio (https://github.com/nublson/purl) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.
¿Es seguro usar nublson/purl?
+
Nuestro agente de seguridad ha analizado nublson/purl y le ha asignado un Trust Score de 62/100 (tier: OK). Revisa el desglose completo de comprobaciones superadas y flags en esta página.
¿Quién mantiene nublson/purl?
+
nublson/purl es mantenido por nublson. La última actividad registrada en GitHub es del 2026-10-01, con 1 issues abiertos.
¿Hay alternativas a purl?
+
Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.
Despliega purl en tu cloud
Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.
¿Mantienes este repo? Añade un badge a tu README
Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.
Más MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
The fastest path to AI-powered full stack observability, even for lean teams.