Keepfor.me について
Keepfor.meは、ウェブページやメモを保存・整理し、AIエージェントから利用できるようにする「あとで読む」ライブラリです。永続的なページスナップショットと、キーワード・セマンティック検索を組み合わせています。
主な機能
- 01ウェブページ、メモ、断片の保存
- 02キーワード・セマンティック検索
- 03オリジナルと整形済みスナップショットの保存
- 04タグ、ハイライト、コレクションの管理
- 05ゴミ箱からのアイテム復元
活用シーン
- 01記事をあとで読むために保存
- 02テーマ別に個人ライブラリを検索
- 03エージェントによる保存内容の閲覧と整理
- 04誤って削除したアイテムの復元
MCP 対応
`/api/mcp` でリモートのステートレスな Streamable HTTP MCP サーバーを提供し、Bearer Personal Access Token で認証します。ツールからライブラリへの保存、検索、閲覧、タグ付け、管理ができます。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://app.keepfor.me/api/mcpclaude mcp add --transport http melcutz-keepfor-me https://app.keepfor.me/api/mcp{"mcpServers":{"melcutz-keepfor-me":{"type":"http","url":"https://app.keepfor.me/api/mcp"}}}Cursor で開く{"servers":{"melcutz-keepfor-me":{"type":"http","url":"https://app.keepfor.me/api/mcp"}}}VS Code で開く{"mcpServers":{"melcutz-keepfor-me":{"type":"http","url":"https://app.keepfor.me/api/mcp"}}}{"mcpServers":{"melcutz-keepfor-me":{"serverUrl":"https://app.keepfor.me/api/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
https://app.keepfor.me/api/mcp認証が必要ツール
詳細
Keepfor.me
Cloudflare Python Workers、FTS5 対応の D1 SQLite、Vectorize、R2、Model Context Protocol(MCP)上で完全に動作する、シンプルで高速な「あとで読む」アプリ兼パーソナルライブラリです。
機能
- 4つの主要機能:AI エージェントを使って、キャプチャ、保存、検索、共有できます。
- Python エッジランタイム:Cloudflare Python Workers(Pyodide)上の FastAPI を利用しています。
- インストール可能な PWA とネイティブ共有:Android と iOS でホーム画面に追加できます。Android では、ネイティブの Web Share Target がシステムの共有シートに直接統合されます。iOS では、Safari セッションを使用するワンタップの Apple Shortcuts がシステムの共有シートと連携し、PWA ではクリップボードをスマートに検出します。
- 集中できるリーダー:テーマ(ライト、セピア、ダーク)、フォント(サンセリフ、セリフ、等幅)、フォントサイズをカスタマイズできます。
- ハイブリッド検索:D1 FTS5 の BM25 キーワードマッチングと Vectorize のセマンティック埋め込み(
bge-base-en-v1.5)を Reciprocal Rank Fusion(RRF)で組み合わせます。FTS と埋め込みの呼び出しは並行して実行され、ベクトル検索には時間制限が設けられているため、AI バインディングの応答が遅い場合でも処理が停止せず、キーワード検索のみで結果を返します。 - 堅牢なコンテンツ抽出:
trafilaturaによる本文の自動解析と OpenGraph メタデータへのフォールバックにより、ブックマークを失いません。ブラウザーと同一のリクエストヘッダーに加えて、任意のリーダープロキシへのフォールバック(READER_PROXY_BASEで設定可能。デフォルトでは無効)を使い、多くのボット対策された(HTTP 403)サイトからコンテンツを取得します。 - 抽出状態の表示:各アイテムには、処理が停止したままになるのではなく、「抽出中…」、コンテンツ、または失敗理由(例:
HTTP 403)が表示されます。一括インポートはキューを通じて並行処理され、リクエストをブロックしません。 - R2 への二重スナップショット保存:元の HTML スナップショット(
raw.html)と、サニタイズ済みのリーダー用 HTML(clean.html)を保存します。 - データを失わないゴミ箱機能:UI またはエージェントから削除すると、アイテムは消去されずゴミ箱に移動します。行、スナップショット、ベクトルは保持されるため、すぐに復元できます。完全に削除できるのはゴミ箱ビューの完全に削除のみで、スケジュールされた消去処理により30日後にゴミ箱が空になります。
- 設定不要のシングルテナントロック:最初の登録で自動的に管理者権限が設定され、外部からの新規登録がロックされます。
- Model Context Protocol(MCP):opencode、Claude Desktop、Cursor 向けの、
/api/mcpにあるリモート Streamable HTTP サーバー(ステートレス、Bearer PAT 認証)です。ローカルへのインストールは不要です。 - ブラウザーからの即時キャプチャ:ドラッグ&ドロップ対応の JavaScript ブックマークレットと、展開済みの Manifest V3 拡張機能を利用できます。
- インポートとエクスポート:Pocket、Instapaper、Omnivore、ブラウザーのブックマーク(Netscape HTML と CSV)を取り込めます。Chrome のフォルダー名はタグとして保持されます。ノートはGoogle Keep Takeout の zip ファイル、Evernote の
.enexファイル、Markdown/テキストファイルの zip ファイル(Obsidian の保管庫、Exporter 経由の Apple Notes、Simplenote)からインポートできます。タグと元の日付は保持され、ノート内のリンクは通常の保存アイテムになり、添付ファイルは一覧表示されますが保存されません。同じデータを再インポートしても重複は作成されません(zip ファイルと.enexは最大50 MB、HTML/CSV は最大10 MB。zip 内はファイルごとに最大2 MB、合計200 MB、20,000ファイルまで。これを超えると、展開爆弾として拒否されます)。ノートのアップロードごとに、進捗と最終件数(インポートしたノート、スキップした重複、キューに追加したリンク、インポートしなかった添付ファイル、解析エラー、プラン制限によるスキップ)を含むステータスレコードが保存され、上限付きのエラーメッセージ一覧も記録されます。そのため、結果はページを再読み込みしても保持されます。 - モバイルファースト UI:スマートフォンでは下部タブバーとタグチップの帯が表示され、タップ領域は32px以上で、幅360pxまで横方向のはみ出しはありません。
アーキテクチャ概要
flowchart LR
Browser["Web UI / PWA / Share Sheet"] -->|FastAPI| Worker["Cloudflare Python Worker"]
Claude["Claude Desktop / MCP Agents"] -->|Streamable HTTP + Bearer PAT| Worker
Worker -->|Queries & FTS5| D1[("D1 Database")]
Worker -->|Enqueue job| Queue["Cloudflare Queue"]
Queue -->|Process batch| Worker
Worker -->|Raw & Clean HTML| R2[("R2 Storage")]
Worker -->|Embeddings| WAI["Workers AI"]
Worker -->|Upsert vectors| Vec[("Vectorize")]
抽出のライフサイクル
URL を保存すると、status='queued' の行が挿入され、ジョブがキューに登録されます。キューのコンシューマーが取得、抽出、スナップショットの保存を行い、理由とともにステータスを ok または failed に変更します。失敗は常に UI とリーダーに表示されます。
意図的に採用している設計上の選択肢は 2 つです。
- キューのバインディングがない場合はインラインで抽出します。 ローカル開発環境やテスト環境にはキューコンシューマーがないため、
save_itemは同期的に抽出を行います。これにより、アイテムがqueuedのまま永久に残ることを防ぎます。 - 一括インポートはインラインで実行しません。 900 件のブックマークをインポートすると、1 回のリクエスト内で何千回もの D1 書き込みが発生します。そのため、25 件のブックマークを含むキューメッセージに分割するか、キューのバインディングがない場合はバックグラウンドタスクとして実行します。
メッセージが失われた場合は、設定 → 停滞したアイテムを再キューで、queued のまま停滞している行のジョブを再送信できます(新しい行はスキップされるため、処理中のメッセージが重複することはありません)。
ゴミ箱と復元
削除しても、データが破棄されることはありません。MCP の delete_item と DELETE /api/items/{id} は items.deleted_at を設定するだけです。行、R2 のスナップショット、ベクトル埋め込みは保持されるため、エージェントがライブラリのデータを破壊することはありません。ゴミ箱に入ったアイテムは、復元されるまで、ライブラリ、検索、MCP の読み取り、件数、エクスポートのすべてで表示されません。
ゴミ箱ビュー(/trash)にはアイテムが一覧表示され、2 つの操作を行えます。復元するとアイテムがライブラリに戻り、完全に削除は唯一の永久削除方法です。ホストは、次のスケジュール済みクリーンアップを自分で実行することもできます。
from keepfor.models.items import purge_trash
# Hard-deletes trashed items older than 30 days (row, snapshots, vectors).
await purge_trash(db, env, user_id=None, older_than_days=30)
# A multi-tenant host passes the tenant scope: it carries the R2 prefix
# and the Vectorize namespace of the tenant. An unscoped call is refused.
await purge_trash(db, env, user_id=None, older_than_days=30, scope=scope)
ユーザーに気づかれないまま削除されることはありません。スケジューラーがなければ、ゴミ箱は増え続けます。
リーダープロキシへのフォールバック(READER_PROXY_BASE)
デフォルトでは、Keepfor.me は失敗した取得リクエストをサードパーティーサービスに転送しません。オリジンが直接の取得を拒否した場合(HTTP 403、429、530)、アイテムは失敗として記録されます。
リーダープロキシへのフォールバックを有効にする場合(Jina Reader やセルフホストの可読性プロキシを使用する場合など):
wrangler.jsonc(vars.READER_PROXY_BASE = "https://r.jina.ai/")または.dev.varsでREADER_PROXY_BASEを設定します。- プライバシーに関するお知らせ:有効にすると、ブロックされた記事の URL が設定したプロキシサービスに送信されます。プライベート IP 範囲(例:
127.0.0.1、10.0.0.0/8、192.168.0.0/16)とローカルドメインは、SSRF の送信トラフィックポリシーによって厳格にブロックされ、リーダープロキシに転送されることはありません。
プライベート環境/オフライン環境へのデプロイと送信トラフィックポリシーについて詳しくは、docs/self-hosting.md を参照してください。
デプロイ
1. 前提条件
- Node.js 20 以降と npm
- uv 0.12.3 以降
- Workers、D1、R2、Vectorize、Queues が有効になっている Cloudflare アカウント
- カスタムドメイン:
keepfor.me(または標準の*.workers.devサブドメイン)
2. Cloudflare リソースのプロビジョニング
次のコマンドを一度実行して、D1 データベース、R2 バケット、Vectorize インデックス、キューを作成します。
# 1. Create D1 Database
npx wrangler d1 create keepfor-me-db
# Copy the returned database_id into wrangler.jsonc
# 2. Create R2 Bucket
npx wrangler r2 bucket create keepfor-me-bucket
# 3. Create Vectorize Index (768 dimensions for bge-base-en-v1.5)
npx wrangler vectorize create keepfor-me-index --dimensions=768 --metric=cosine
# 4. Create Queue
npx wrangler queues create keepfor-me-queue
3. データベースのマイグレーションを適用
初期スキーマ(テーブル、FTS5 仮想テーブル、インデックス)を適用します。
npx wrangler d1 migrations apply keepfor-me-db --remote
ローカルでテストする場合:
npx wrangler d1 migrations apply keepfor-me-db --local
4. デプロイ
デプロイは手動で行います。セルフホストの場合は、pywrangler deploy または GitHub Actions の workflow_dispatch(入力値は confirm: "deploy")を使用して、自分の Cloudflare アカウントにデプロイします。main へのプッシュによって自動デプロイされることはありません。
警告
wrangler.jsoncでは、D1、R2、Vectorize のバインディングに"remote": trueが設定されています。リモートリソースと直接やり取りするため、wrangler.jsoncを使ってローカル開発を実行しないでください。ローカルでシミュレーションするには、wrangler.local.example.jsoncをwrangler.local.jsoncにコピーし、uvx --from workers-py pywrangler dev --config wrangler.local.jsoncを実行してください。
PyWrangler は pyproject.toml で宣言された Python 依存関係をバンドルします。
# Always dry-run first and check the "Total (N modules)" row: the deploy
# hard-fails if the bundle exceeds 64 MiB.
uvx --from workers-py pywrangler deploy --dry-run
uvx --from workers-py pywrangler deploy
正常なバンドルのサイズは、おおよそ 52,000 KiB/約 4,500 モジュールです。モジュール一覧に .venv-workers/ のパスが表示される場合は、ビルド用の仮想環境がバンドルされています。この状態では、依存関係を一度更新するだけでデプロイが失敗する可能性があります。
デプロイ後、ドメイン(例:https://app.keepfor.me または https://keepfor-me.<your-subdomain>.workers.dev)にアクセスしてください。最初に登録したユーザーが自動的に管理者となり、公開登録がロックされます。
5. カスタムドメイン
wrangler.jsonc では、app.keepfor.me のルートを宣言しています。DNS レコードを一度作成してください
(Cloudflare ダッシュボード → DNS → Add record):
| タイプ | 名前 | コンテンツ | プロキシ |
|---|---|---|---|
A | app | 192.0.2.1 | プロキシ済み(オレンジ色の雲) |
Worker のルートは IP に接続される前に一致するため、プレースホルダーの
アドレスで問題ありません。workers_dev は有効のままなので、*.workers.dev URL も引き続き
利用できます。
AI エージェントの接続(リモート MCP)
Keepfor.me は、https://app.keepfor.me/api/mcp でリモート Streamable HTTP MCP サーバーをホストし、
ライブラリの保存、検索、閲覧、タグ付けを行う 42 個のツールを公開しています:
save_url:任意のウェブページをライブラリに保存します。save_urls:1 回の呼び出しで複数のウェブページをライブラリに保存し、URL ごとにステータスを返します。save_note:メモ、スニペット、プロンプトテンプレートを保存します。search_library:テキストと意味にわたるハイブリッド検索を行います。search_passages:永続的なスナップショット URL を使って、パッセージ単位で検索します。find_item:タイトルまたは URL からアイテム ID を検索します(検索も埋め込みも行いません)。get_item:ID、URL、または完全一致するタイトルからメタデータと抜粋を読み取ります。read_item:ページネーション対応で全文を読み取ります。list_items:並べ替えとフィルターに対応した、簡潔なページネーション付き一覧を表示します。reading_history:日付フィルターを使って、保存したアイテムと開いたアイテムを確認します。related_items:指定したアイテムまたはテキストに類似するアイテムを表示します。list_tags/create_tag/describe_tag/update_tag/rename_tag/merge_tags/delete_tag:タグをすべて管理します。tag_item/tag_items/set_item_tags/retag_by_query:一括かつ安全にタグ付けします(デフォルトではドライラン)。bulk_update:フィルターに一致するすべてのアイテムのステータス、ピン留め、タグを更新します(デフォルトではドライラン)。suggest_tags/search_tags/tag_stats/related_tags/tag_cleanup:タグ語彙の管理ツールです。find_cleanup:読み取り専用のクリーンアップレポートを表示します(重複 URL、リンク切れ、古く一度も開かれていないアイテム、タグのないアイテム、類似したタグ)。delete_item/pin_item:アイテムを削除またはピン留めします。library_digest:保存、閲覧、よく使われるタグ、未読の注目アイテムを簡潔にまとめます。create_highlight/list_highlights/delete_highlight:文字オフセットを基準に、アイテム内のパッセージを引用します。create_collection/list_collections/add_to_collection/remove_from_collection/rename_collection/delete_collection:ライブラリアイテムを名前付きのグループにまとめます。get_account:プラン、トライアル終了日、残りのクォータを確認します。無料で利用でき、クォータを使い切った後も応答します。
サーバーはステートレス(セッションなし、プレーンな JSON 応答、SSE ストリームなし)で、 REST API と同じ Personal Access Token で認証します。
セットアップ
- Keepfor.me の UI で Settings & API を開きます。
- Generate Token をクリックして Personal Access Token(
kfm_live_...)を作成します。 エージェントがライブラリの閲覧と検索のみを行う場合は、スコープ選択で Read-only を選択してください。 - Bearer ヘッダーにトークンを指定して、クライアントをエンドポイントに接続します:
opencode(opencode.json):
{
"mcp": {
"keepfor-me": {
"type": "remote",
"url": "https://app.keepfor.me/api/mcp",
"headers": { "Authorization": "Bearer {env:KEEPFOR_ME_TOKEN}" }
}
}
}
opencode を起動する前に export KEEPFOR_ME_TOKEN=kfm_live_... を実行し、設定を変更した後は
opencode を再起動してください。
Cursor / Claude Desktop: URL
https://app.keepfor.me/api/mcp とヘッダー
Authorization: Bearer kfm_live_YOUR_TOKEN_HERE を指定して、カスタムのリモート MCP サーバーを追加します。
クライアントを使わずにハンドシェイクを確認するには:
curl https://app.keepfor.me/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer kfm_live_YOUR_TOKEN_HERE" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
利用可能な MCP ツール(サーバー 0.2.0)
MCP サーバーは 42 個のツールを公開しています。一度に多数のアイテムを変更するツールは dry_run フラグを受け取ります。このフラグに指定できるのは JSON の真偽値のみです(省略時は true となり、プレビューが実行されます)。それ以外の値は適用されず、拒否されます。
save_url— 任意のウェブページをライブラリに保存します。エージェントによる調査の出典情報として、任意でagent_summary、why_saved、source_context、saved_byを指定できます。save_urls— 1 回の呼び出しで最大 50 件のウェブページをライブラリに保存します。URL ごとのステータス(queued、exists、invalid、over_limit)と合計数が返されます。書き込みスコープが必要です。URL は正規化 URL で重複排除され、新しい URL はすべてsave_urlと同じ保存チェックを通ります(保存上限により拒否された URL はover_limitとして返されます)。抽出はキューに登録されるため、この呼び出しはページの処理を待ちません。save_note— 個人的なメモ、コードスニペット、プロンプトテンプレートを保存します。同じ出典情報フィールドを任意で指定できます。search_library— テキストと意味の両方を対象にしたハイブリッド検索を行います(スニペット付きのアイテム単位の結果)。search_passages—max_charsの上限内で、意味的なチャンクを対象にしたチャンク単位のハイブリッド検索を行います。chunk_id、文字オフセット、リンク切れ後も利用できる永続的なsnapshot_url(R2 アーカイブ)が返されます。read_item— トークン効率のよいページネーション形式で、コンテンツ全体を読み取ります。text、next_cursor、total_chunks、outlineが返されます。get_item—item_id、url、または完全一致のtitleを指定して、メタデータと抜粋を取得します。デフォルトではcontent_textは抜粋に制限され、truncatedとcontent_lengthが報告されます。全文を取得するにはinclude_content=trueを指定します。list_items— コンパクトなページネーション形式で一覧を表示します(id、title、url、saved_at、tags。本文は含まれません)。saved、opened、titleで並べ替え、since/until、domain、pinned、type、タグで絞り込めます。find_item— 完全一致または前方一致のタイトル、もしくは完全一致または正規化された URL からアイテム ID を検索します。検索や埋め込みは行わず、既知のタイトルから読み取り可能なitem_idを低コストで取得します。reading_history— 日付(since/until)、イベントの種類(saved/opened)、任意のクエリ/タグフィルターで絞り込んだ閲覧履歴を取得します。related_items—item_idまたはテキストを指定し、ベクトル類似度で最も近いアイテムを、1 行の理由とともに取得します。list_tags— すべてのタグについて、アイテム数と最終使用日を表示します。count、name、recentで並べ替え、prefixとmin_countで絞り込めます。create_tag— 任意でdescription、color、parent(階層構造。例:work/research)を指定してタグを作成します。describe_tag— タグのメタデータ、親、子、アイテム数を取得します。update_tag— タグの説明、色、親を編集します。rename_tag— タグの名前を変更します。そのタグが付いているすべてのアイテムも更新されます。merge_tags— 複数のタグを 1 つの対象タグに統合します(1 回の呼び出しで指定できる元タグは最大 20 個です)。delete_tag— アイテムからタグを削除します。アイテム自体は削除されません。デフォルトではdry_run=trueで、影響を受ける件数とサンプルを返します。dry_run=falseにすると変更が適用されます。tag_item— 1 つのアイテムにタグを追加または削除します。tag_items— 複数のアイテムにタグを追加または削除します。デフォルトではdry_run=trueです。1 回の呼び出しで指定できるアイテムは最大 100 件で、結果にはtotal_matchedとtruncatedが含まれます。retag_by_query— タイトル/URL クエリ(およびタグフィルター)に一致するアイテムにタグを付けます。デフォルトではdry_run=trueです。1 回の呼び出しで指定できるアイテムは最大 100 件で、結果にはtotal_matchedとtruncatedが含まれます。bulk_update— フィルター(ステータスグループ、保存日付範囲、ドメイン、ピン留め状態、種類、タグ)に一致するすべてのアイテムのステータス、ピン留め状態、タグを更新します。デフォルトではdry_run=trueで、書き込みは行わずにtotal_matched、affected、truncatedを報告します。1 回の呼び出しで更新できるアイテムは最大 200 件です。フィルターが空の場合、または変更内容が空の場合は拒否されます。更新全体は 1 つのトランザクションで適用され、タグ名にはcreate_tagのルール(a-z、0-9、スペース、_、-の 2〜64 文字)が適用されます。set_item_tags— 1 つのアイテムのタグ一式を置き換えます。suggest_tags— アイテムまたは自由入力テキストに対して、まず既存の語彙からタグを提案します。search_tags— タグ名と説明をあいまい検索します(machinelearningでmachine-learningが見つかります)。tag_stats— ライブラリのタグ使用統計を取得します。related_tags— 同じアイテムに付けられているタグのうち、指定したタグと共起するものを取得します。tag_cleanup— 類似した重複タグ、未使用のタグ、確認が必要なタグなしアイテムを表示します。find_cleanup— 読み取り専用の整理レポートです。重複アイテム(正規化 URL が同じもの)、リンク切れ(記録された抽出失敗。ネットワークリクエストは行いません)、一度も開かれていない古いアイテム、タグなしアイテム、類似した重複タグを表示します。categories、older_than_days、カテゴリごとのlimit(最大 100)を指定できます。各カテゴリには候補、件数、truncatedフラグが表示されます。アイテムカテゴリでは理由付きで ID が 1 つずつ表示され、グループカテゴリではグループごとに 1 件表示されます。duplicatesにはグループの ID(最大 10 件)、グループのサイズ、ID リストが切り詰められたかどうかが含まれ、near_duplicate_tagsにはグループのタグ名が含まれます。そのため、limitはグループ数に上限を設け、同じ URL が 300 回保存されていても応答は小さく保たれます。delete_item— アイテムをゴミ箱に移動します。アイテムはライブラリから移動されますが、完全には削除されません。30 日間は復元可能で、完全に削除できるのはユーザー本人だけです。pin_item— アイテムを上部の棚にピン留め、またはピン留め解除します。library_digest— 指定期間のライブラリの要約を読み取り専用で取得します。保存、閲覧、上位タグ、未読の注目アイテムが含まれます。since、until、tagは任意で指定でき、応答内の各リストには上限があります。create_highlight— アイテム内の文章を引用し、content_text内で引用範囲を示す文字オフセットを指定して固定します。start_offsetのデフォルトは0、end_offsetのデフォルトはstart_offset + len(text)です。アイテムのコンテンツの末尾を超える終了オフセットは拒否されます。list_highlights— 1 つのアイテムのハイライトを、テキスト内の位置順に表示します。引用文、オフセット、任意のメモと色が含まれます。delete_highlight— 1 つのハイライトを削除します。対象のアイテム自体は削除されません。create_collection— 任意で説明を指定して、ライブラリのアイテムをまとめる名前付きのコレクションを作成します。既存の名前で作成した場合は、変更せずにそのコレクションを返します。list_collections— アイテム数とアイテム ID を含むコレクション一覧を取得します(コレクションごとに最大 20 件。さらにアイテムがある場合はitems_truncatedが表示されます)。add_to_collection— 名前または ID を指定して、アイテムをコレクションに追加します。1 回の呼び出しで追加できるアイテムは最大 100 件で、結果にはadded、skipped、truncatedが含まれます。remove_from_collection— アイテムをコレクションから削除します。アイテム自体は削除されません。rename_collection— コレクションの名前を変更します。すでに使われている名前への変更は拒否され、統合は行われません。delete_collection— コレクションとそのリンクを削除します。格納されていたアイテムは削除されません。空にするにはremove_from_collectionを使用します。get_account— アカウント情報を読み取り専用で取得します。プラン、trial_ends_at、すべての従量制アクションのクォータ(limit、used、remaining、resets_at)が含まれます。この呼び出しは無料で、クォータを消費しないため、クォータを使い切った後でも応答が返されます。 すべてのツールで MCP アノテーション(readOnlyHint、destructiveHint、idempotentHint)を宣言しています。これらはクライアント向けの参考情報であり、サーバーはトークンのスコープを適用する際に使用します(下記参照)。スコープ情報がないことを理由にトークンが拒否されることはありません。スコープ情報がない場合は、すべてにアクセスできます。各ツールの説明には、類似する他のツールではなく、そのツールを使用する場面が記載されています。リスト型のレスポンスにはlimit、offset/cursor、fieldsの投影が含まれます。list_itemsのデフォルトページは 4 KB 未満です。
タグフィルター(tags_all、tags_any、tags_none、untagged)は list_items、search_library、search_passages、find_item、reading_history、bulk_update で使用でき、子タグも対象に含まれます。たとえば work でフィルタリングすると、work/research も一致します。
MCP リソースとプロンプト
サーバーは MCP 仕様のリソースとプロンプトの機能にも対応しており、ツールと同じ認証およびテナントスコープが適用されます。
リソース — 自分のライブラリを読み取り専用で参照できます。
| URI | 内容 |
|---|---|
keepfor://item/{item_id} | 保存した記事またはメモの 1 件分です。メタデータ、タグ、保存されたテキストが含まれます(20,000 文字を上限とし、切り詰められた場合は truncated で示します)。 |
keepfor://tag/{name} | 自分のタグの 1 つと、そのタグが付いたアイテムです(最大 50 件。さらにアイテムがある場合は items_truncated で示します)。 |
resources/list は最近のアイテムと使用頻度の高いタグを返し、resources/templates/list は 2 つの URI テンプレートを返し、resources/read はペイロードを JSON で返します。
プロンプト — 自分のデータを読み取る再利用可能なテンプレートです。
| プロンプト | 引数 | 内容 |
|---|---|---|
research_brief | topic(必須)、max_items | あるトピックについてすでに保存した内容をアシスタントに簡潔に伝え、ライブラリの情報をもとに調査セッションを開始できるようにします。 |
weekly_review | days、tag | 過去数日間に保存した内容を確認し、要約と次に読むものを尋ねます。 |
トークンのスコープ(読み取り専用トークン)
Personal Access Token は読み取り専用に制限できます。トークン名の横にあるScopeセレクターを使って、Settings & APIで作成してください(デフォルトはFull accessです)。
*(すべて) — デフォルトです。以前とまったく同じように、すべてのツールを使用できます。read— MCP では、トークンからtools/list内の読み取りツールのみを参照できます。また、書き込みまたは破壊的なツール(save_url、save_note、tag_item、create_tag、update_tag、rename_tag、merge_tags、delete_tag、tag_items、retag_by_query、bulk_update、set_item_tags、delete_item、pin_item、create_highlight、delete_highlight、create_collection、add_to_collection、remove_from_collection、rename_collection、delete_collection)は実行前に JSON-RPC エラー(error.data.type=insufficient_scope)で拒否されます。書き込みは一切行われません。
スコープが導入される前に作成されたトークンは、引き続きフルアクセスを保持します。保存されたスコープは NULL で、「すべて」を意味します。スコープはトークンが使用されるすべての場所で適用されます。MCP では各ツールに、そのアノテーションで宣言されたスコープが必要です。REST では読み取り専用トークンを使うと、すべての書き込みルートで 403 が返されます。例外は読み取り専用の POST ルート /api/search と /search です。検索は読み取り操作なので、読み取り専用トークンでも使用できます。/api/mcp ではツールごとにスコープが適用されます。PAT は /settings/* では全面的に拒否されるため、トークンからより広い権限を持つトークンを作成することはできません。
セルフホストする場合:スコープのカラムはマイグレーション 0012_pat_scopes.sql で追加されます。デプロイ前に、上記の手順 3 に従ってマイグレーションを適用してください。
ブラウザーとモバイルからの保存
1. Android(PWA + システムの共有シート)
- Chrome でドメインを開き、Add to Home screen(またはInstall app)をタップします。
- どこからでも保存するには、アプリでShare → Keepfor.meをタップします。ネイティブの Web Share Target が、入力済みの保存シートを開き、スワイプして戻るとアプリにシームレスに戻ります。
2. iPhone & iPad(Apple Shortcut とスマートクリップボード)
iOS の WebKit は、PWA をシステムの共有シートから隔離します。Keepfor.me では、iOS で手間なく保存できる方法を 2 つ提供しています。
- ワンタップの Apple Shortcut(共有シート):Settings & API → iPhone & iPad Quick Sharing に移動し、Install Keepfor.me Shortcut をタップします。これにより、既存の Safari ログインセッションを使って URL を Keepfor.me に直接送信するネイティブアクションが iOS の共有シートに追加されます(API トークンや技術的な設定は必要ありません)。
- PWA スマートクリップボード:Safari からアプリをホーム画面に追加します(Share → Add to Home Screen)。任意のアプリでリンクをコピーして Keepfor.me PWA に切り替えると、フローティングトーストがクリップボードの URL を検出し、ワンタップで保存できます。
3. ドラッグ&ドロップのブックマークレット(デスクトップ)
Settings に移動し、Keepfor.me ボタンをブラウザーのブックマークバーにドラッグします。任意のページでクリックすると、クイック保存ダイアログが開きます。
ブックマークレットには常に汎用の地球儀アイコンが表示されます。アプリアイコンを使うには、ドメイン内のページをブックマークしてそのブックマークを編集し、コピーしたコード(Settings → Copy code)を URL として貼り付け、名前を
Keepfor.meにします。
4. ブラウザー拡張機能(Manifest V3)
- Chrome/Brave/Edge を開き、
chrome://extensions/に移動します。 - Developer mode(右上)を有効にします。
- Load unpacked をクリックし、
keepfor.me/browser-extensionフォルダーを選択します。 - 拡張機能のアイコンをクリックし、Settings(
⚙️)で Worker URL と PAT トークンを一度入力して保存すると、Alt+Sで任意のタブを保存できます。
ローカル開発とテスト
プロジェクトとテスト依存関係をインストールしてから、pytest スイートを実行します。
python3 -m pip install -e . pytest pytest-asyncio pytest-cov httpx
python3 -m pytest tests/ -q
pytest はリポジトリのルートから実行してください。ルートが sys.path にある場合にのみ、keepfor は名前空間パッケージとして解決されます。
CI とまったく同じ方法で Lint とフォーマットを実行します(引数なしの ruff check . は異なるルールを使うため、CI で拒否されるコードも通過します)。
ruff check keepfor/ tests/ --select=E,W,F,I,N
ruff format --check keepfor/ tests/
Python 依存関係を使ってローカル Worker のプレビューを実行するには、次のとおりです。
uvx --from workers-py pywrangler dev
ローカル開発環境にはキューコンシューマーがないため、保存したリンクはインラインで抽出され、コンテンツがすぐに表示されます。
パフォーマンスに関する注意事項
Python Workers ではコールドスタートが体感レイテンシの大部分を占めます。ページの読み込みはウォーム状態で約 250ms ですが、isolate の起動時には 1.5s を超えることがあります。
- インポートグラフの整理:記事パーサー(
trafilatura、bs4、lxml)などの重いライブラリは、抽出コード内で遅延インポートされます。回帰テストにより、これらがリクエストのインポートパスに含まれないことを確認しています。 - 検索と I/O の並行処理:ハイブリッド検索では、D1 FTS と Workers AI の埋め込み検索を並行して実行し(
asyncio.gather)、タイムアウトを 5s に制限しています。 - D1 クエリの最適化:複合インデックス(
(user_id, created_at DESC)および(user_id, status, created_at DESC))により、SQLite のインメモリ一時 B-tree によるファイルソート(USE TEMP B-TREE FOR ORDER BY)をなくします。ステータス件数、フィード項目、タグの集計はasyncio.gatherで並列化し、複数ステートメントからなるタグや削除の更新はexecute_batchでまとめて、RPC の往復を最小限に抑えています。
このプロジェクトを支援する
Keepfor が役に立った場合は、開発を支援できます。
Stripe で寄付して Keepfor を支援できます(設定額は $5 です。金額は自由に選択できます)。
コントリビューション
コントリビューションを歓迎します。開発ワークフロー、テスト、プルリクエストの手順については、CONTRIBUTING.md をお読みください。
すべてのコントリビューターは、Contributor License Agreement (CLA) に署名する必要があります。作品の著作権は保持されます。この契約により、ホステッドサービスの運用と商用ライセンスの提供に必要なライセンスが当方に付与されます。
ライセンス
Keepfor.me は、GNU Affero General Public License v3.0(AGPL-3.0-only)の下でライセンスされています。
- セルフホスト:個人または組織での利用に限り、制限なく自由にセルフホストできます。
- ネットワークサービス:Keepfor.me を改変し、ネットワーク経由でサービスとして提供する場合は、改変したソースコードを AGPL-3.0 の下で公開する必要があります。
- 商用ライセンス:プロプライエタリ、エンタープライズ、またはクローズドソースでの導入を支援するため、商用ライセンスや別のライセンス契約をご要望に応じて提供しています。詳細については、LICENSING.md をご覧ください。
- 過去のリリース:このライセンス変更より前のリリースは、すでにお持ちの方に対して引き続き MIT License の下で提供されます。
- 商標:プロジェクト名とマークは、TRADEMARK.md の規定に従います。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。