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_SECRETMCP Servers overview
<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.
**PWWhat people ask about purl
What is nublson/purl?
+
nublson/purl is mcp servers for the Claude AI ecosystem. AI read-it-later & personal knowledge base It has 2 GitHub stars and its last recorded update is dated 2026-10-01.
How do I install purl?
+
You can install purl by cloning the repository (https://github.com/nublson/purl) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is nublson/purl safe to use?
+
Our security agent has analyzed nublson/purl and assigned a Trust Score of 62/100 (tier: OK). See the full breakdown of passed checks and flags on this page.
Who maintains nublson/purl?
+
nublson/purl is maintained by nublson. The last recorded GitHub activity is dated 2026-10-01, with 1 open issues.
Are there alternatives to purl?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy purl to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
More 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.