Purl について
Purlは、有用なURLをまとめて整理・共有したい人向けのリンク管理サービスです。ウェブページ、PDF、YouTube動画、音声を判別し、アプリ、REST API、MCPサーバーから保存したリンクを利用できます。
主な機能
- 01コンテンツ種別を判別してURLを保存
- 02タイトルとプレビュー情報を取得
- 03相対日付でリンクをグループ化
- 04フォルダーでリンクを整理
- 05デバイス間でリスト変更を同期
活用シーン
- 01記事、PDF、動画、音声のリンクを一元管理
- 02アプリやAPIから保存済みリンクを検索
- 03MCPクライアントから保存リンクにアクセス
MCP 対応
Purlは、リンクの保存、一覧表示、取得、移動とフォルダー管理を行うMCPサーバーを提供します。MCPへのアクセスはAPIキーまたはOAuth認証に対応します。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://purl.live/api/mcpclaude mcp add --transport http nublson-purl https://purl.live/api/mcp{"mcpServers":{"nublson-purl":{"type":"http","url":"https://purl.live/api/mcp"}}}Cursor で開く{"servers":{"nublson-purl":{"type":"http","url":"https://purl.live/api/mcp"}}}VS Code で開く{"mcpServers":{"nublson-purl":{"type":"http","url":"https://purl.live/api/mcp"}}}{"mcpServers":{"nublson-purl":{"serverUrl":"https://purl.live/api/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
https://purl.live/api/mcp認証が必要ツール
詳細
Purl
残しておきたいリンクを保存しましょう。人に伝えたいリンクを共有しましょう。
ライブプレビュー: https://purl.live
Purl は、見つけたリンクを保存して共有するための、落ち着いた場所です。あなたの「宝物」を保管できます。URL を貼り付けるだけで、ウェブページ、PDF、YouTube 動画、音声を保存できます。Purl は各アイテムのメタデータ(タイトル、ファビコン、説明、サムネイル)を取得し、アプリ、REST API、MCP サーバーから利用できるよう、すべてを一か所に保管します。
Purl は無料ですが、制限が 1 つあります。各アカウントで保存できるリンクは最大 1,000 件 です(src/lib/limits.ts の MAX_SAVED_LINKS)。
プロダクトの目標は、気に入ったリンクを保管する場所と、人に伝えたいリンクを手軽に共有する方法を 1 か所に用意することです。
現在実装済みの機能
- マーケティングサイト — ライブプレビュー付きのヒーローセクションを備えたランディングページ、REST API と MCP サーバーのドキュメント、プライバシーポリシー、利用規約。
- 認証 — Better Auth を使用した、ランディングページからの Google および GitHub サインイン(環境変数を設定すれば Apple も利用可能)。すべてのアカウントに固有のユーザー名が割り当てられます。ユーザー名は設定で編集でき、サインイン方法の接続や切断も行えます。アバターには OAuth プロバイダーのプロフィール写真が使われます。
- 保存と整理
- URL からアイテムを追加すると、コンテンツタイプが自動検出されます(ウェブ、PDF、YouTube、音声)。
- リンクは相対的な時間(例:今日、今週、先月)ごとにグループ化されます。
- メタデータ(タイトル、説明、ファビコン、利用可能な場合はサムネイル)をプレビューできます。
- セキュリティを強化した外部フェッチ — オプションのプロキシ/DNS 制御を備えたサーバーサイドの
safeFetch(AGENTS.mdを参照)。外部通信プロキシはSAFE_OUTBOUND_HTTP_PROXYで設定できます。 - リアルタイムのリスト同期 — Supabase Realtime により、保存や更新がタブやデバイス間ですばやく反映されます。
- リンク操作 — 元のリンクを開く、URL をコピーする、メタデータを編集する、削除する。
- REST API と MCP —
/api/v1(リンクとフォルダー)および、API キーまたは OAuth 認証に対応した MCP サーバー(save_link、list_saved_items、get_link、move_link、list_folders、create_folder、update_folder、delete_folder)。 - 運用関連の機能 — オプションの Upstash を利用した API レート制限、重要な処理を対象とした Vitest のテストカバレッジ。
- PWA(インストール可能なアプリ) — Web App Manifest と、ビルドすると
public/sw.jsに出力される Serwist のサービスワーカー(src/app/sw.ts)(pnpm build時に生成され、Git の追跡対象外)。プラットフォームが対応している場合、Chrome/Edge などで インストール が可能になります。Serwist の Next.js 向けデフォルト設定によるランタイムキャッシュと、/~offlineにある静的なオフラインシェルを利用できます。開発中にサービスワーカーのキャッシュが予期せぬ問題を引き起こさないよう、pnpm devでは Serwist は無効 になっています。インストール可能かどうかと SW を確認するには、pnpm build && pnpm start(または本番環境の URL)を使用してください。
保存の流れ
リンクの保存は完全に 同期処理 であり、バックグラウンド処理はありません。
- 入力 — URL を指定して
POST /api/linksを呼び出します(/api/v1/linksと MCP のsave_linkツールも利用できます)。 - 分類と情報の付与 — サーバーサイドの
detectContentType(SSRF 対策済みのHEAD/スニッフィング)とscrapeLinkMetadata(Open Graph HTML、PDF のContent-Disposition/サイズ、YouTube oEmbed)を使用します。既存の URL を再度保存すると、メタデータが更新され、リンクがリストの先頭に移動します。 - 永続化 — タイトル、ファビコン、サムネイル、ドメイン、
contentType(WEB、PDF、YOUTUBE、AUDIO)を含むLink行を保存します。 - 同期 —
broadcastLinksChangedが Supabase Realtime 経由で他のタブやデバイスに通知します。
未実装の機能
リポジトリを一般公開するため、以下の点を明示しています。
- 設定の範囲 — 設定ではユーザー名、サインイン方法、アカウント削除に対応しています。通知設定など、より広範なアカウント設定はまだ実装されていません。
マーケティングと製品の違い: ランディングページの文言には、コレクションや週刊ダイジェストなどのアイデアが記載されています。これらは現在のスキーマやアプリには実装されていません。リリース済みの機能ではなく、ロードマップとして扱ってください。
技術スタック
- Web: Next.js(App Router)、React、TypeScript
- UI: Tailwind CSS、shadcn/ui
- 認証: Better Auth
- データベース: PostgreSQL + Prisma
- メール(開発環境では任意): フィードバックメールに Resend を使用
- リアルタイム: Supabase クライアント(サーバーでは anon + service role)
- PWA: Serwist(
@serwist/next)、Web マニフェスト + プリキャッシュ/オフライン時のフォールバック
CI / GitHub Actions
自動化の設定は .github/workflows/ にあります。すべての PR と手動リリースは、これらのパイプラインを通過する必要があります。
PR チェック — pr-checks.yml
develop および main を対象とする pull_request で実行されます:セットアップと検証 → Prisma(クライアントの生成 + 型の修正)→ Lint と 型チェック(並列実行)→ テスト と 本番ビルド(Lint と型チェックの合格後に並列実行)。同時実行は PR ごとに管理され、新しいプッシュがあると古い実行はキャンセルされます。
リリース — release.yml
workflow_dispatch(手動)で実行されます:まず develop を main にマージし、その後 ビルド検証を行います。ビルドが成功した場合にのみ本番環境へ昇格します。
セキュリティ
Purl は信頼できない入力(任意の URL)を前提に構築されています。本番環境では、いくつかの対策が重要です。
- SSRF を考慮した外部フェッチ — ユーザーが指定した URL をそのまま
fetchに渡すことはありません。OG/サムネイルの確認、PDF の取得、コンテンツタイプの判定などはsafeFetchを経由します。HTTP(S) のみを許可し、プライベート、リンクローカル、予約済みの宛先をブロックします。また、リダイレクト時には各ホップでホストを確認し、接続前に DNS 解決結果を固定します(事前チェック後に DNS を再バインドする従来型の攻撃を軽減します)。応答サイズ上限(例:PDF プロキシ)も任意で設定できます。送信プロキシとカスタム DNS サーバーについてはAGENTS.mdに記載されています。 - 認証とルートの制御 — Better Auth のセッションを使用します。Next.js の
proxyは、未認証ユーザーをプライベートルートからリダイレクトします。サインインは OAuth のみ(Google/GitHub、および設定時は Apple)で、パスワードはありません。 - API の認可 — 機密性の高いルート(
/api/links、/api/v1/*、MCP など)では、サーバー側でセッションを取得し、サインイン中のユーザーの範囲内で処理を行います。 - レート制限 —
UPSTASH_REDIS_REST_URLとUPSTASH_REDIS_REST_TOKENが設定されている場合、プロキシは/api/auth/*、POST /api/links、POST /api/feedbackに対して IP ごとの制限を適用します(proxy-rate-limit.tsを参照)。Upstash がない場合、制限は無効です。ローカルでは問題ありませんが、本番環境には適していません。 - シークレットとクライアントへの公開 —
SUPABASE_SECRET_KEY(または旧形式のSUPABASE_SERVICE_ROLE_KEY)などの値はサーバー専用です。ブラウザーでは Supabase の anon キーをリアルタイム機能にのみ使用します。.envは引き続き Git の追跡対象外です。 - 応答サイズの上限 — PDF プロキシのストリーミングにはサイズ上限があります(
safe-outbound-fetchを参照)。
脆弱性の報告: 修正が行われるまで詳細を非公開にするため、このリポジトリの GitHub Security Advisories を使用してください。
セットアップ(ローカル開発)
前提条件
- Node.js: 最近の LTS バージョン
- パッケージマネージャー:
pnpm(このリポジトリにはpnpm-lock.yamlが含まれています) - Postgres: ローカルまたはホステッド環境(Supabase がおすすめです)
1) 依存関係をインストールする
pnpm install
2) 環境変数を設定する
リポジトリのルートに .env ファイルを作成します。全項目は .env.example を参照してください。コア機能に必要な最小限の設定は次のとおりです。
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DBNAME"
# Supabase Realtime — cross-device instant link list sync (same project as Postgres)
SUPABASE_URL="https://YOUR_PROJECT.supabase.co"
SUPABASE_ANON_KEY="eyJ..."
SUPABASE_SECRET_KEY="sb_secret_..."
# 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 <[email protected]>"
注意事項:
DATABASE_URLは必須です(Prisma + Better Auth)。- Supabase の環境変数は、リアルタイムのリンクリスト同期に必要です。Supabase ダッシュボードの Project Settings → API を使用してください。サービスロールキーはサーバー側だけで使用してください。
- 本番環境では Google/GitHub の OAuth 認証情報が必要です。ローカル環境では、環境変数が不足しているプロバイダーは非表示になります。ローカル開発では、コールバック URL に
http://localhost:3000/api/auth/callback/{google,github}を使用してください。Apple は任意で、4 つのAPPLE_*環境変数がすべて設定されている場合にのみ有効になります(.env.exampleを参照)。 - ローカル開発では Resend は任意です。
RESEND_API_KEYがない場合、アプリ内からフィードバックを送信できません。 - Better Auth のシークレットと URL は
.env.exampleにあります。認証を適切に設定するには、該当するキーをコピーしてください。
3) データベースのマイグレーションを実行する
pnpm prisma migrate dev
4) Prisma クライアントを生成する(必要な場合)
pnpm prisma generate
5) 開発サーバーを起動する
pnpm dev
http://localhost:3000 を開きます。
任意: portless を使った名前付き HTTPS URL。 portless をグローバルにインストール済みの場合(npm install -g portless、Node.js 24+)、pnpm dev の代わりに portless run を実行します。ローカルプロキシ経由で dev スクリプトを https://purl.localhost で実行します。.env.local で認証の接続先をこの URL に設定してください。設定しない場合、オリジンが拒否されます。
BETTER_AUTH_URL="https://purl.localhost"
BASE_URL="https://purl.localhost"
Git worktree では、portless がブランチ名を前置します(例:https://my-branch.purl.localhost)。その場合は、両方の変数にこの URL を設定してください。
PWA/インストール: pnpm dev では、サービスワーカーは有効になりません。本番ビルド後は、ローカルに public/sw.js が作成されます。pnpm start を実行して Chromium でアプリを開くと、インストールを利用したり、/~offline へのオフラインナビゲーションをテストしたりできます。
テスト
テストには Vitest を使用し、重要なロジック(フォーマッター、リンクのグループ化、認証ルーティング、API の動作、メタデータのスクレイピング)に重点を置いています。表層的な UI 専用ラッパーは意図的に対象外としています。
pnpm test # run once
pnpm test:watch # watch mode
便利なコマンド
pnpm lint
pnpm build
pnpm start
pnpm test
その他のコントリビューター向け情報(Prisma、アウトバウンドプロキシの環境変数)については、AGENTS.md を参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。