
Keepfor.me
io.github.melcutz由 GitHub 帳號 melcutz 發布v0.2.0更新於 Nov 6, 2026
Your personal library, readable by AI agents: durable snapshots, hybrid search, 46 MCP tools.
概覽
Keepfor.me 的遠端 MCP 伺服器,讓助理儲存、搜尋、閱讀、標記並整理你的稍後閱讀與個人藏書庫。
- 可以做什麼
- 儲存網頁、批次網址與個人筆記到藏書庫,可附上來源說明欄位
- 依關鍵字與語意搜尋藏書庫,支援條目層級與段落層級檢索,並找出相關條目
- 分頁閱讀全文、瀏覽條目清單,查看閱讀紀錄與藏書庫摘要
- 管理標籤:建立、重新命名、合併、刪除、建議、搜尋,並批次標記,預設先預覽
- 建立與管理摘錄和集合,釘選或移到垃圾桶,查看方案與配額
- 提供條目與標籤的唯讀資源,以及研究簡報與每週回顧提示範本
- 適用情境
- 適合讓助理以你自己收藏的內容而非公開網路為基礎來工作:研究時順手收藏連結、找回某個主題下已儲存的資料,或整理個人藏書庫中的標籤與重複條目。
- 執行需求
- 遠端 Streamable HTTP 端點,不需本機安裝。需要 OAuth 2.1 登入,或在 Keepfor.me 設定中建立的個人存取權杖,透過 Authorization Bearer 標頭送出。唯讀與讀寫權限分開發行。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 Keepfor.me,將其新增到工作區。
- 連接 所需的 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
- 為需要使用其工具的對話啟用該服務。
Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。
交給代理程式安裝
把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。
閱讀 https://sourceweft.com/mcp/io.github.melcutz%2Fkeepfor.me/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。其他 MCP 客戶端
把它新增到你客戶端的 mcpServers 設定中。 客戶端會在首次連接時完成 OAuth 登入。
{
"mcpServers": {
"keepfor-me": {
"type": "http",
"url": "https://app.keepfor.me/api/mcp"
}
}
}README
Keepfor.me
[Release] [Tests] [Security] [Runtime] [FastAPI] [Protocol] [License] [Support this project]
A thin, fast read-it-later and personal library application running entirely on Cloudflare Python Workers, D1 SQLite with FTS5, Vectorize, R2, and the Model Context Protocol (MCP).
Features
- Four Core Jobs: Capture, Store, Search, and Share with AI Agents.
- Python Edge Runtime: Powered by FastAPI on Cloudflare Python Workers (Pyodide).
- Installable PWA & Native Sharing: Add to Home screen on Android and iOS. On Android, native Web Share Target integrates directly into the system share sheet. On iOS, 1-tap Apple Shortcuts integrate with the system share sheet using your Safari session, paired with smart clipboard detection in the PWA.
- Distraction-Free Reader: Customizable themes (Light, Sepia, Dark), fonts (Sans, Serif, Mono), and font sizes.
- Hybrid Search: Reciprocal Rank Fusion (RRF) combining D1 FTS5 BM25 keyword matching and Vectorize semantic embeddings (
bge-base-en-v1.5). FTS and embedding calls run concurrently, and the vector path is time-bounded so a slow AI binding degrades to keyword-only instead of hanging. - Resilient Content Extraction: Automated body parsing via
trafilaturawith OpenGraph metadata fallback so bookmarks are never lost. Browser-identical request headers plus an optional reader-proxy fallback (configurable viaREADER_PROXY_BASE, off by default) recover many bot-walled (HTTP 403) origins. - Visible Extraction State: Every item shows
Extracting…, content, or a failed reason (e.g.HTTP 403) rather than silently stalling. Bulk imports fan out through the queue, never blocking the request. - Dual Snapshots in R2: Raw original HTML snapshot (
raw.html) and sanitized reader HTML (clean.html). - Trash, Not Data Loss: Deleting (from the UI or from an agent) moves an item to the Trash instead of destroying it. The row, the snapshots and the vectors stay, so Restore is instant; Delete forever in the Trash view is the only permanent path, and a scheduled purge clears the Trash after 30 days.
- Zero-Config Single-Tenant Lock: First registration automatically claims admin ownership and locks out external signups.
- Model Context Protocol (MCP): Remote Streamable HTTP server at
/api/mcp(stateless, Bearer PAT auth) for opencode, Claude Desktop, and Cursor — no local install needed. - Instant Browser Capture: Drag-and-drop JavaScript bookmarklet and unpacked Manifest V3 extension.
- Import & Export: Ingest Pocket, Instapaper, Omnivore, and browser bookmark exports (Netscape HTML & CSV). Chrome folder names are preserved as tags. Notes import from Google Keep Takeout zips, Evernote
.enexfiles, and zips of Markdown/text files (Obsidian vaults, Apple Notes via Exporter, Simplenote): tags and original dates are kept, links inside notes become regular saves, attachments are listed but not stored, and re-imports never duplicate (50 MB cap for zips and.enex, 10 MB for HTML/CSV; inside a zip: 2 MB per file, 200 MB total and 20,000 files, so decompression bombs are refused). Every notes upload keeps a status record with its progress and final counts (notes imported, duplicates skipped, links queued, attachments not imported, parse errors, plan-limit skips) and a capped list of error messages, so the result survives a refresh. - Mobile-First UI: Bottom tab bar and tag chip strip on phones, ≥32px touch targets, no horizontal overflow down to 360px.
Architecture Overview
Extraction lifecycle
Saving a URL inserts the row with status='queued' and enqueues a job; the queue
consumer fetches, extracts, stores snapshots, and flips it to ok or failed with
a reason. Failures are always visible in the UI and reader.
Two deliberate design choices:
- No queue binding → extract inline. Local dev and tests have no queue
consumer, so
save_itemextracts synchronously rather than leaving items stuck inqueuedforever. - Bulk imports never run inline. A 900-bookmark import would issue thousands of D1 writes inside one request; instead it fans out as queue messages of 25 bookmarks, or a background task when no queue is bound.
If messages are ever lost, Settings → Re-queue stuck items re-sends jobs for rows
stuck in queued (skips fresh rows so live messages aren't duplicated).
Trash and restore
Deleting never destroys. MCP delete_item and DELETE /api/items/{id} set
items.deleted_at and stop there: the row, the R2 snapshots and the vector
embeddings stay, so an agent cannot destroy library data. Trashed items are
invisible everywhere (library, search, MCP reads, counts, exports) until they
are restored.
The Trash view (/trash) lists them with two actions: Restore brings
an item back into the library, Delete forever is the only permanent
delete. Hosts can also run the scheduled cleanup themselves:
Nothing is deleted behind the user's back: without a scheduler the Trash just grows.
Reader Proxy Fallback (READER_PROXY_BASE)
By default, Keepfor.me does not route failed fetches to any third-party service. When an origin rejects a direct fetch (HTTP 403, 429, 530), the item is recorded as failed.
To opt in to reader proxy fallback (e.g. using Jina Reader or a self-hosted readability proxy):
- Configure
READER_PROXY_BASEinwrangler.jsonc(vars.READER_PROXY_BASE = "https://r.jina.ai/") or in.dev.vars. - Privacy Notice: When enabled, blocked article URLs will be transmitted to the configured proxy service. Private IP ranges (e.g.,
127.0.0.1,10.0.0.0/8,192.168.0.0/16) and local domains are strictly blocked by SSRF egress policy and are never forwarded to any reader proxy.
For full guidance on private / offline deployments and egress policies, see docs/self-hosting.md.
Deployment
1. Prerequisites
- Node.js 20 or newer and npm
- uv 0.12.3 or newer
- Cloudflare account with Workers, D1, R2, Vectorize, and Queues enabled
- Custom domain:
keepfor.me(or standard*.workers.devsubdomain)
2. Provision Cloudflare Resources
Run the following commands once to create your D1 database, R2 bucket, Vectorize index, and Queue:
3. Apply Database Migrations
Apply the initial schema (tables, FTS5 virtual table, indexes):
For local testing:
4. Deploy
Deploys are manual: self-hosters deploy to their own Cloudflare account via pywrangler deploy or GitHub Actions workflow_dispatch (with input confirm: "deploy"). Pushing to main does not auto-deploy.
[!WARNING]
wrangler.jsonchas"remote": trueon D1, R2, and Vectorize bindings. Never run local dev againstwrangler.jsoncas it interacts directly with remote resources. For local simulation, copywrangler.local.example.jsonctowrangler.local.jsoncand runuvx --from workers-py pywrangler dev --config wrangler.local.jsonc.
PyWrangler bundles the Python dependencies declared in pyproject.toml.
A healthy bundle is roughly 52,000 KiB / ~4,500 modules. If .venv-workers/
paths appear in the module table, the build venv is being bundled and the deploy is
one dependency bump away from failing.
Once deployed, visit your domain (e.g., https://app.keepfor.me or
https://keepfor-me.<your-subdomain>.workers.dev). The first user to register
automatically becomes the admin and locks public registration.
5. Custom Domain
wrangler.jsonc declares a route for app.keepfor.me. Create the DNS record once
(Cloudflare dashboard → DNS → Add record):
The Worker route matches before the IP is ever contacted, so the placeholder
address is fine. workers_dev is left enabled so the *.workers.dev URL keeps
working.
Connecting AI Agents (Remote MCP)
Keepfor.me hosts a remote Streamable HTTP MCP server at https://app.keepfor.me/api/mcp,
exposing 42 tools for capturing, finding, reading and tagging your library:
save_url: Save any webpage to your library.save_urls: Save many webpages to your library in one call, with a status for each URL.save_note: Save a note, snippet or prompt template.search_library: Hybrid search across text and meaning.search_passages: Passage-level search with permanent snapshot URLs.find_item: Look up an item id by title or url (no search, no embeddings).get_item: Read metadata and excerpt by id, url or exact title.read_item: Paginated full-text reading.list_items: Compact paginated browse with sorting and filters.reading_history: What you saved and opened, with date filters.related_items: Items similar to a given item or text.list_tags/create_tag/describe_tag/update_tag/rename_tag/merge_tags/delete_tag: Full tag management.tag_item/tag_items/set_item_tags/retag_by_query: Tagging, in bulk and safely (dry-run by default).bulk_update: Update status, pin and tags on every item matching a filter (dry-run by default).suggest_tags/search_tags/tag_stats/related_tags/tag_cleanup: Tag vocabulary tools.find_cleanup: Read-only cleanup report (duplicate urls, dead links, never-opened old items, untagged items, near-duplicate tags).delete_item/pin_item: Remove or pin an item.library_digest: A compact recap — saves, opens, top tags, unread highlights.create_highlight/list_highlights/delete_highlight: Quote a passage of an item, anchored by character offsets.create_collection/list_collections/add_to_collection/remove_from_collection/rename_collection/delete_collection: Named buckets of library items.get_account: Your plan, the end of the trial and the quota left — free, so it still answers when the quota is exhausted.
The server is stateless (no sessions, plain JSON responses, no SSE stream) and authenticates with the same Personal Access Tokens as the REST API.
Setup
- Go to Settings & API in your Keepfor.me UI.
- Click Generate Token to create a Personal Access Token (
kfm_live_...). Pick Read-only in the scope picker if the agent should only be able to browse and search the library. - Point your client at the endpoint with the token as a Bearer header:
opencode (opencode.json):
Then export KEEPFOR_ME_TOKEN=kfm_live_... before launching opencode, and
restart opencode after changing the config.
Cursor / Claude Desktop: add a custom remote MCP server with URL
https://app.keepfor.me/api/mcp and header
Authorization: Bearer kfm_live_YOUR_TOKEN_HERE.
Handshake check without any client:
Available MCP Tools (Server 0.2.0)
The MCP server exposes 42 tools. The tools that change many items at once take a dry_run flag: it is only accepted as a JSON boolean (omitted means true, a preview), and anything else is rejected rather than applied.
save_url— Save any webpage to your library. Accepts optionalagent_summary,why_saved,source_context,saved_byfor agent research provenance.save_urls— Save up to 50 webpages to your library in one call, with a status per URL (queued,exists,invalid,over_limit) and the totals. Needs the write scope. The URLs are deduped by canonical URL, every new URL goes through the same save check assave_url(the ones the save limit refuses come back asover_limit), and the extraction is queued, so the call does not wait for the pages.save_note— Save a personal note, code snippet, or prompt template. Accepts the same optional provenance fields.search_library— Hybrid search across text and meaning (item-level results with snippets).search_passages— Passage-level hybrid search over semantic chunks withmax_charsbudget. Returnschunk_id, char offsets, and permanentsnapshot_url(R2 archive) that survives link rot.read_item— Token-efficient paginated reading of full content. Returnstext,next_cursor,total_chunks,outline.get_item— Retrieve metadata and excerpt, addressed byitem_id,urlor exacttitle. By default capscontent_textto excerpt and reportstruncatedandcontent_length; useinclude_content=truefor full text.list_items— Compact paginated browse (id,title,url,saved_at,tags— no body). Sorts bysaved,openedortitle; filters bysince/until,domain,pinned,typeand tags.find_item— Find item ids by exact/prefix title or exact/normalized url. No search, no embeddings: the cheap path from a title you know to anitem_idyou can read.reading_history— Reading history with date filtering (since/until), event types (saved/opened), and optional query/tag filters.related_items— Nearest neighbors by vector similarity from anitem_idor rawtext, with one-line reasons.list_tags— Every tag with its item count and last-used date. Sorts bycount,nameorrecent; filters byprefixandmin_count.create_tag— Create a tag with optionaldescription,colorandparent(hierarchy, e.g.work/research).describe_tag— A tag's metadata, parent, children and item count.update_tag— Edit a tag's description, color or parent.rename_tag— Rename a tag; every item carrying it is updated.merge_tags— Fold several tags into one target tag (at most 20 source tags per call).delete_tag— Remove a tag from items. Items are never deleted.dry_run=trueby default: returns the affected count and a sample;dry_run=falseapplies.tag_item— Add or remove tags on a single item.tag_items— Add/remove tags across many items.dry_run=trueby default. At most 100 items per call; the result reportstotal_matchedandtruncated.retag_by_query— Tag items matching a title/url query (plus tag filters).dry_run=trueby default. At most 100 items per call; the result reportstotal_matchedandtruncated.bulk_update— Update the status, the pin state and the tags of every item matching a filter (status group, saved date range, domain, pinned, type, tags).dry_run=trueby default: it reportstotal_matched,affectedandtruncatedwithout writing anything. At most 200 items are updated per call, and an empty filter or an empty change is rejected. The whole update is applied in one transaction, and tag names follow thecreate_tagrules (2-64 characters ofa-z,0-9, space,_or-).set_item_tags— Replace the whole tag set of one item.suggest_tags— Suggest tags for an item or for free text, drawn from your existing vocabulary first.search_tags— Fuzzy search over tag names and descriptions (machinelearningfindsmachine-learning).tag_stats— Tag usage statistics for the library.related_tags— Tags that co-occur with a tag on the same items.tag_cleanup— Near-duplicate tags, unused tags and untagged items worth a look.find_cleanup— Read-only cleanup report: duplicate items (same normalized url), dead links (recorded extraction failures — no network requests), never-opened old items, untagged items and near-duplicate tags. Takescategories,older_than_daysand a per-categorylimit(max 100); each category reports the candidates, a count and atruncatedflag. The item categories list one id with a reason; the group categories list one entry per group —duplicatesgives the group's ids (capped at 10), its size and whether the id list was truncated,near_duplicate_tagsgives the group's tag names — solimitcaps groups and one url saved 300 times stays a small response.delete_item— Move an item to the Trash. The item leaves the library but is not destroyed: it stays restorable for 30 days, and only the user can delete it for good from the Trash view.pin_item— Pin or unpin an item to/from the top shelf.library_digest— Read-only recap of the library for a period: saves, opens, top tags and unread highlights. Takes optionalsince,untilandtag; every list in the response is capped.create_highlight— Quote a passage of an item and anchor it with the character offsets it covers insidecontent_text.start_offsetdefaults to0,end_offsettostart_offset + len(text); an end offset past the end of the item's content is refused.list_highlights— The highlights of one item, ordered by their position in the text, with the quoted text, offsets, optional note and color.delete_highlight— Remove one highlight. The item it belongs to is never deleted.create_collection— Create a named bucket of library items, with an optional description. Creating one with an existing name returns it unchanged.list_collections— Your collections with their item count and item ids (at most 20 per collection, withitems_truncatedwhen there are more).add_to_collection— Put items in a collection, addressed by name or id. At most 100 items per call; the result reportsadded,skippedandtruncated.remove_from_collection— Take items out of a collection. The items themselves are never deleted.rename_collection— Rename a collection. Renaming onto a name that is already taken is refused, never merged.delete_collection— Delete a collection and its links. The items it held are never deleted; useremove_from_collectionto empty it.get_account— Read-only report of your account: the plan,trial_ends_atand the quota of every metered action (limit,used,remaining,resets_at). The call is free — it costs no quota unit, so it still answers when the quota is exhausted.
All tools declare MCP annotations (readOnlyHint, destructiveHint, idempotentHint). They are advisory hints for the client, and the server uses them to enforce the scopes of a token (see below). A token is never rejected for missing scope information: no scope information means full access. Each description says when to use a tool versus its siblings. List-type responses include limit, offset/cursor, and a fields projection; the default list_items page stays under 4 KB.
Tag filters (tags_all, tags_any, tags_none, untagged) work on list_items, search_library, search_passages, find_item, reading_history and bulk_update, and count child tags: filtering on work also matches work/research.
MCP Resources and Prompts
The server also speaks the resources and prompts parts of the MCP spec, with the same auth and tenant scoping as the tools.
Resources — read-only views of your own library:
resources/list returns your most recent items and most used tags, resources/templates/list returns the two URI templates, and resources/read returns the payload as JSON.
Prompts — reusable templates that read your own data:
Token scopes (read-only tokens)
A Personal Access Token can be limited to read-only. Create it on Settings & API with the Scope picker next to the token name (Full access is the default):
*(all) — the default. Every tool works, exactly as before.read— over MCP the token only sees the read tools intools/list, and any write or destructive tool (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) is refused with a JSON-RPC error (error.data.type=insufficient_scope) before it runs — nothing is written.
Tokens created before scopes existed keep their full access: their stored
scope is NULL and means "all". Scopes are enforced everywhere a token works:
over MCP each tool needs the scope its annotations declare, and over REST a
read-only token gets 403 on every write route. The exception is the
read-only POST routes /api/search and /search: a search is a read, so a
read-only token may use them. /api/mcp enforces scopes per tool. PATs are
refused on /settings/* entirely, so a token can never mint a broader one.
Self-hosters: the scope column comes from migration 0012_pat_scopes.sql, so
apply the migrations (step 3 above) before deploying.
Browser & Mobile Capture
1. Android (PWA + System Share Sheet)
- Open your domain in Chrome and tap Add to Home screen (or Install app).
- To save from anywhere, tap Share → Keepfor.me in any app — the native Web Share Target opens a prefilled save sheet, and swiping back returns you seamlessly to your app.
2. iPhone & iPad (Apple Shortcut & Smart Clipboard)
iOS WebKit sandboxes PWAs from the system share sheet. Keepfor.me provides two zero-friction capture methods for iOS:
- 1-Tap Apple Shortcut (Share Sheet): Go to Settings & API → iPhone & iPad Quick Sharing and tap Install Keepfor.me Shortcut. This adds a native action to your iOS Share Sheet that sends URLs directly to Keepfor.me using your existing Safari login session (no API tokens or technical configuration required).
- PWA Smart Clipboard: Add the app to your Home Screen from Safari (Share → Add to Home Screen). When you copy a link in any app and switch to the Keepfor.me PWA, a floating toast detects the clipboard URL and lets you save it with a single tap.
3. Drag-and-Drop Bookmarklet (desktop)
Go to Settings and drag the Keepfor.me button to your browser's bookmarks bar. Click it on any page to open a quick-save dialog.
Bookmarklets always show a generic globe icon. To get the app icon: bookmark any page on your domain, edit that bookmark, paste the copied code (Settings → Copy code) as its URL, and name it
Keepfor.me.
4. Browser Extension (Manifest V3)
- Open Chrome/Brave/Edge and navigate to
chrome://extensions/. - Enable Developer mode (top right).
- Click Load unpacked and select the
keepfor.me/browser-extensionfolder. - Click the extension icon, enter your Worker URL and PAT token once in Settings
(
⚙️), then save any tab withAlt+S.
Local Development & Testing
Install the project and its test dependencies, then run the pytest suite:
Run pytest from the repo root — keepfor resolves as a namespace package only
when the root is on sys.path.
Lint and format exactly as CI does (bare ruff check . uses different rules and
will pass on things CI rejects):
To run the local Worker preview with its Python dependencies:
Local dev has no queue consumer, so saved links extract inline and show content immediately.
Performance notes
Cold starts dominate perceived latency on Python Workers: page loads sit around 250ms warm but can spike past 1.5s when an isolate boots.
- Import graph hygiene: Heavy article parsers (
trafilatura,bs4,lxml) are imported lazily inside the extraction code. A regression test asserts they stay out of the request import path. - Concurrent search & I/O: Hybrid search runs D1 FTS and Workers AI embedding
lookups concurrently (
asyncio.gather), bounded by a 5s timeout. - D1 query optimization: Composite indexes (
(user_id, created_at DESC)and(user_id, status, created_at DESC)) eliminate SQLite in-memory temporary B-tree filesorts (USE TEMP B-TREE FOR ORDER BY). Status counts, feed items, and tag aggregations are parallelized viaasyncio.gather, and multi-statement tag/deletion mutations are batched viaexecute_batchto minimize RPC roundtrips.
Support this project
If you find Keepfor useful, you can support its development:
Support Keepfor with a donation on Stripe (preset $5, or choose your amount).
Contributing
Contributions are welcome! Please read CONTRIBUTING.md for details on our development workflow, testing, and pull request process.
All contributors must sign our Contributor License Agreement (CLA). You retain copyright ownership of your work; the agreement grants us the license necessary to operate the hosted service and offer commercial licenses.
License
Keepfor.me is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-only).
- Self-Hosting: You can self-host freely for personal or organizational use without restriction.
- Network Services: If you modify Keepfor.me and offer it as a service over a network, you must publish your modified source code under the AGPL-3.0.
- Commercial Licensing: Commercial licenses and alternative licensing agreements are available upon request to support proprietary, enterprise, or closed-source deployments. See LICENSING.md for details.
- Prior Releases: Previous releases prior to this license change remain under the MIT License for anyone who already has them.
- Trademark: The project name and marks are governed by TRADEMARK.md.
來源:README.md,提交 da6a1b8
工具
0版本歷史
1- v0.2.0最新Nov 6, 2026
評分
已滿足適用檢查項的 85%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通必需25 分
- 已宣告認證方式必需10 分
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分
