Cairn
Provides import/export compatibility with Obsidian vaults: import an Obsidian vault (.zip) as-is and export notes as a zip that can be dropped back into Obsidian, preserving note structure and rewriting links to images and files.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Cairn
Cairn is long-term memory for AI assistants that you run on your own Cloudflare account. Your
memory is a set of markdown notes with typed observations (- [decision] …), relations
(- depends_on [[Other note]]) and tags. Claude and other MCP clients read and write them over
OAuth; you browse, edit, search and export them in a web app. Search uses keywords and meaning
together, and every write returns a receipt saying what was saved, where and by which app.
It is one Cloudflare Worker: passkey sign-in (no passwords, no email), room for several people, one SQLite Durable Object per project, and an export that hands back exactly the markdown you wrote. The note format and tool behaviour follow Basic Memory, so Basic Memory projects and Obsidian vaults import as they are, and the export drops back into either.
(The landing page calls it a revolutionary emotionally intelligent recall paradigm. It is a notes database with good manners.)
Contents
Features · Screenshots · Deploy your own · Connect Claude · Deploy from GitHub Actions · Costs and limits · Security · Backups and export · Upgrading · Troubleshooting · Development · Licence
Related MCP server: MCP Notes Server
Features
Memory and tools. A remote MCP server at /mcp (streamable HTTP, OAuth 2.1 with PKCE,
dynamic client registration and client ID metadata documents, or a personal access token), with
these tools:
Tool | What it does |
| Create or replace a note (by path, or title + folder); returns a receipt and write hints |
| Read by permalink, |
| Append, prepend, find/replace, replace or insert around a section, set frontmatter; checksum-guarded |
| Delete a note, or a folder with |
| Move or rename a note or folder; permalinks stay, so links keep resolving |
| Hybrid search with tags, types, folders, frontmatter filters, observation categories, dates and paging |
| Recursive find by file-name glob or frontmatter, with the fields you choose |
| One folder level |
| A note and its linked notes in both directions, to a depth you choose |
| What changed lately, and which app changed it |
| List or create projects (up to 20 per person) |
| The always-loaded memory: an index note plus every |
| Replace a fact without losing it: the old one moves under |
| Attach a file from a URL or base64 and embed it in a note |
| Validate notes against Picoschema schemas, infer one, or show drift ( |
| The manual, generated from the tools' own schemas |
Every Basic Memory parameter is honoured or refused with a typed INVALID error, never silently
ignored. Set BM_COMPAT=true to also register Basic Memory's bm_* tool names, so skills
written for Basic Memory run unchanged.
Around the notes.
Hybrid search: SQLite FTS5 fused with Workers AI embeddings in Vectorize. The top results are re-ranked by Jev, a small judgement model, which also says when nothing matches well. It falls back to keyword-only search without those bindings.
Write hints: likely duplicates, conflicting facts and unresolved links come back with the receipt.
Supersession and expiry:
(expires 2027-01-01)facts retire themselves nightly. They are moved, never deleted.History for every note, with line diffs, one-click undo, and "undo everything app X did since T".
Nightly gardening (opt-out): a checklist note of contradictions, orphans, unresolved links, near-duplicates and stale notes. A weekly digest on Mondays. Neither edits your notes.
Web app: search as you type, a Ctrl/Cmd-K palette, a CodeMirror editor with
[[completion, a link graph, GitHub-flavoured markdown with code highlighting, mermaid, callouts and footnotes, and light and dark themes.Capture: drag, paste or attach files (stored in R2; images get an AI caption so search finds them), a bookmarklet clipper, sharing from a phone (installable web app), and email in (optional).
Import a Basic Memory project or Obsidian vault (.zip), keeping Basic Memory's permalinks and rewriting links to images and files. Export everything as a zip at any time.
Accounts: passkeys, recovery codes, open or invite-only sign-up, invite links, admins who can disable accounts but cannot read anyone's notes, and deleting your own account (everything, vectors included).
Agent skills (
cairn-notes,cairn-continue,cairn-capture,cairn-gardening,cairn-tasks), served by your instance and packaged as a Claude Code plugin.
Screenshots
(To be added:)
A note: observations, relations, backlinks and the receipt trail
Search with filters, and the Ctrl/Cmd-K palette
The editor with
[[completion and live previewThe project graph
Activity: what each connected app changed, with undo
The consent page an assistant sends you to
Settings: connected apps, tokens, invites
Deploy your own
About 15 minutes. You end up with Cairn at a URL you choose, and Claude connected to it.
Prerequisites
A Cloudflare account. Workers Paid ($5/month) is recommended. Everything Cairn uses exists on the Free plan, but Free allows 10 ms of CPU per request. Cairn hasn't been measured against that limit, and search, imports and exports can exceed it.
Node 24 (or mise, which installs it from
mise.toml), plus git and openssl.Optional: a domain whose DNS zone is on the same Cloudflare account. Without one, Cairn runs at
https://cairn.<your-subdomain>.workers.dev. You need a domain for email capture.
Choose the URL first. Passkeys are bound to the hostname in PUBLIC_URL, and OAuth metadata
is built from it. If you change it after people sign up, every passkey stops working, and people
can only get back in with recovery codes.
1. Get the code and log in
git clone https://github.com/jclement/cairn.git
cd cairn
npm ci
npx wrangler loginIf you will deploy from CI, fork the repository first and clone your fork, so you can commit
your wrangler.jsonc.
2a. One command
npm run setup:cloudflare -- --url https://cairn.example.com # a custom domain on your account
npm run setup:cloudflare # or: your workers.dev URLscripts/setup-cloudflare.mjs runs these steps, and is safe to run again:
It checks the login.
It creates whatever doesn't exist yet: the D1 database
cairn, the KV namespacecairn-oauth, the Vectorize indexcairn-notes(1024 dimensions, cosine) and the R2 bucketcairn-files.It writes their ids,
PUBLIC_URL, the route and the sign-up policy into yourwrangler.jsonc.It builds, applies the D1 migrations and deploys. On workers.dev it reads the URL from the deploy and deploys again with that URL.
It generates
CAIRN_SETUP_TOKENand prints it once.
Options:
--open-signup: anyone can sign up. The default is invite-only; you get in with the setup token.--email cairn@example.com: email capture (see Email capture).--dry-run: show what exists and what would change, without changing anything.
Then go to First sign-in. The manual steps below do the same thing by hand.
2b. By hand: create the resources
npx wrangler d1 create cairn
npx wrangler kv namespace create cairn-oauth
npx wrangler vectorize create cairn-notes --dimensions=1024 --metric=cosine
npx wrangler r2 bucket create cairn-filesKeep the D1 database_id and the KV id they print. The Durable Object, rate limiters, Workers
AI and static assets need no setup. The index must be 1024-dimensional, to match the default
embedding model @cf/qwen/qwen3-embedding-0.6b.
3. By hand: configure and deploy
wrangler.jsonc in this repository holds the maintainer's instance at the top level. In your
copy, change these top-level values (leave env.local alone; it is for local development):
Key | Set it to |
| your D1 id |
| your KV id |
|
|
| the exact URL people will use: |
|
|
| your capture address, or |
To find your workers.dev subdomain, open the Cloudflare dashboard and go to Workers & Pages. The subdomain is shown there.
Set the setup token, then migrate and deploy:
openssl rand -base64 32 | tee /dev/tty | npx wrangler secret put CAIRN_SETUP_TOKEN
npm run deploytee /dev/tty shows the token once: save it in your password manager. npm run deploy builds
the browser bundles and CSS, runs wrangler d1 migrations apply DB --remote, then
wrangler deploy. mise run deploy does the same.
Check the deployment:
curl https://cairn.example.com/health # {"ok":true,"version":"…"}
curl -i -X POST https://cairn.example.com/mcp # 401 with a WWW-Authenticate header4. First sign-in
Open
PUBLIC_URLand choose Sign up. Pick a username and open Setup token (admins only). Paste the token, then create your passkey. The token makes you an admin, and it lets you sign up even when sign-up is invite-only.Save the 10 recovery codes it shows. Each one signs you in once, to add a new passkey.
Add a second passkey (Settings → Account), so losing one device isn't a recovery event.
Invite people from Settings → Invites. Each person may hold
INVITES_PER_USER(default 5) open invite links, and admins have no limit. If you didn't use the token at sign-up, you can become admin later in Settings → Admin.
Email capture (optional)
Set
vars.INBOUND_EMAIL_ADDRESS(for examplecairn@example.com) and deploy.In the Cloudflare dashboard, open your domain and go to Email → Email Routing.
Enable Email Routing. It adds MX and TXT records.
Under Routing rules, create the custom address and set the action to Send to a Worker →
cairn.Optionally, turn on subaddressing in its settings, so
cairn+KEY@example.comworks.
Each person creates a private inbox key in Settings → Capture. Mail sent to cairn+KEY@…, with
[k:KEY] in the subject, or from a trusted sender that passes DMARC/DKIM becomes a note in
inbox/, with its attachments.
AI features and Jev (optional)
Cairn calls Workers AI for four things:
embeddings
Jev (
typesafe/jev): write hints and search re-rankingimage captions
the weekly digest
Jev is a third-party model served through Workers AI and billed to your account like the
others. Each call is time-boxed and charged to a per-user daily token budget
(AI_DAILY_TOKENS, default 1,000,000). When a user's budget runs out, the AI extras stop for the
rest of the day. Search and writes keep working.
To turn features off, set the model variable to "": JEV_MODEL for hints and re-ranking,
VISION_MODEL for captions. Usage per user and per model is shown at /app/stats. Without the
AI and VECTORIZE bindings Cairn still works, with keyword-only search and no extras.
All settings
| In | Meaning |
| (required) | The URL people use; passkeys and OAuth are bound to it |
|
| Shown in the app and on the consent page |
|
|
|
|
| Open invite links a non-admin may hold |
|
| The .zip import in Settings → Import (off unless |
|
| The |
|
| Also register Basic Memory's |
|
| Must match the index's 1024 dimensions; changing it means re-embedding |
|
|
|
| (unset: | Image captions; |
|
| The weekly digest |
|
| Per-user daily AI budget |
| the maintainer's | Email capture address; |
The one secret is CAIRN_SETUP_TOKEN. package.json → cloudflare.bindings describes every
binding. docs/SELF-HOSTING.md has the long version of each step.
Connect Claude and other assistants
Your MCP URL is PUBLIC_URL + /mcp, and Settings in the app shows it. The first connection
sends you to Cairn, where you sign in with your passkey and allow read or read and write.
claude.ai and Claude Desktop: Settings → Connectors → Add custom connector, then paste the URL.
Claude Code:
claude mcp add --transport http cairn https://cairn.example.com/mcpThen run
/mcpin Claude Code, pickcairnand choose Authenticate. Alternatively, the repository is a plugin marketplace that adds the connection, the skills and a session-start reminder:export CAIRN_URL=https://cairn.example.com # before starting Claude Code # in Claude Code: /plugin marketplace add jclement/cairn /plugin install cairn@cairnUse one way or the other, not both.
Codex CLI: run
codex mcp add cairn --url https://cairn.example.com/mcp, thencodex mcp login cairn.Anything else: add a streamable-HTTP MCP server at the URL. A client without OAuth can use a personal access token from Settings: send
Authorization: Bearer cairn_pat_….
Then paste one line into the assistant:
Set up Cairn for me: read https://cairn.example.com/setup.md and follow it./setup.md is generated by your instance. It has the assistant check the connection and ask how
you'll use Cairn. Then it installs the matching skills from /skills/ in the way that suits your
client, or gives you the download links on claude.ai. Settings → Connected apps lists every grant
with a revoke button.
Deploy from GitHub Actions
.github/workflows/deploy.yml runs on every push to main:
A
checkjob runsmise run check(typecheck, lint, tests, probes).If that is green, a
deployjob runs in theproductionenvironment. It runsmise run deploy(migrations, then deploy) and smoke-tests/health, the OAuth metadata and/mcpat thePUBLIC_URLinwrangler.jsonc.
For your fork:
Commit your
wrangler.jsonc(ids, route andPUBLIC_URL; there are no secrets in it).In GitHub, go to Settings → Environments and create
production. Add a secretCLOUDFLARE_API_TOKENand a variableCLOUDFLARE_ACCOUNT_ID. The account id is shown in the dashboard's account home and innpx wrangler whoami.Create the token at My Profile → API Tokens → Create Token → Custom token, with only these permissions:
Scope
Permission
Why
Account
Workers Scripts: Edit
upload the Worker, its assets, Durable Object and cron
Account
D1: Edit
d1 migrations apply --remoteAccount
Workers KV Storage: Edit
the
OAUTH_KVbindingAccount
Workers R2 Storage: Edit
the
FILESbindingAccount
Vectorize: Edit
the
VECTORIZEbindingAccount
Account Settings: Read
wrangler's account lookup
Zone (your zone only)
Workers Routes: Edit
the custom-domain route; omit for workers.dev
User
User Details: Read, Memberships: Read
wrangler's identity check
Scope it to your account (and your zone only). If a deploy fails with an authorization error naming a resource, add that resource's permission.
mise run prod:status(the health report under Troubleshooting) reuses this token: it queries D1, reads the Vectorize index and lists deployments, all covered above.
The secret CAIRN_SETUP_TOKEN is set once with wrangler secret put. It survives deploys, and
CI never needs it.
Costs and limits
From the estimates in docs/research/cloudflare.md §6:
Heavy personal user | 10-person family or team | |
Workers, Durable Objects, D1, KV | within Paid's included amounts | ~$0.15 |
Vectorize | ~$0.45 | ~$3.70 |
Workers AI (embeddings, Jev, captions, digest) | ~$0–2 | ~$2–8 |
R2 (attachments) | pennies | pennies |
Total | ≈ $5/month (the Paid minimum) | ≈ $5–15/month |
These are estimates, not measurements. AI_DAILY_TOKENS caps what any one person can spend on
AI.
Built-in limits per person:
1 MB per note
50 MB in all (notes, history and attachments)
20 projects
10 MB per file and 5,000 files
60 writes and 60 searches a minute
imports of up to 20 MB and 5,000 files per zip
Sign-in and OAuth are rate-limited per IP, and invite changes per person.
Security
The full threat model and how to report a vulnerability are in docs/SECURITY.md.
Passkeys only, bound to
PUBLIC_URL. Recovery codes are hashed and single-use, and are good only for adding a passkey.Sessions are opaque, hashed,
__Host-cookies (HttpOnly, Secure, SameSite=Lax). Every POST must carryPUBLIC_URL's Origin. Exports, account deletion and changes to credentials need a fresh passkey check.Isolation: each project is its own Durable Object with its own SQLite database. It is reached only after D1 confirms you own it. Admins cannot read notes.
OAuth: 1-hour access tokens and rotating refresh tokens. Read or write scope is chosen at consent, and grants can be revoked. Tokens and PATs are stored only as hashes.
Untrusted content: notes render without raw HTML under a strict CSP. Tools return note text as data. Attachments are served by sniffed type, and non-images download. Server-side fetches refuse private addresses.
Your data stays in your account: nothing leaves Cloudflare. The AI calls go to Workers AI on your own account.
Backups and export
Export: Settings → Download everything gives a zip of every project. Notes are byte-for-byte as stored, attachments are included, and there is an index. It drops into Basic Memory or Obsidian. Each note also has a raw
.mddownload.D1 (accounts and the directory):
npx wrangler d1 time-travel restore cairn --timestamp=…restores to any point in the last 30 days (Paid) or 7 days (Free).Notes live in Durable Object SQLite, which Cloudflare keeps with point-in-time recovery. There is no Cairn command for that restore yet, so the export is your portable backup.
Upgrading
git pull # or merge upstream into your fork
npm ci
npm run deploy # applies new D1 migrations (migrations/), then deploysThe Durable Objects upgrade their own SQLite schema on first use. With CI, pushing the merge to
main does all of this. Read the commit log before jumping many versions.
Troubleshooting
mise run prod:status (with CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID set) reports:
the deployed version
migrations applied vs pending
AI failure rates
the last nightly run
stuck imports
email ingestion
the Vectorize index
It exits non-zero on a red flag. Admins see the same at /app/admin/health.
Symptom | Cause |
Every form says Cross-origin request refused | The browser's URL doesn't match |
Passkey prompt fails or finds no passkey | Passkeys belong to |
claude.ai can't connect |
|
| You're on the Free plan; move to Workers Paid. |
Search ignores meaning | No |
daily AI budget reached | That person spent |
Uploads say attachments aren't configured | The |
Email never arrives | Check, in order: the Email Routing rule, |
|
|
Development
mise run setup # npm ci
cp .dev.vars.example .dev.vars # optional: a local setup token, offline semantic search
mise run dev # http://localhost:8787 (local D1 migrations + bundles first)
mise run test # vitest inside workerd, offline
mise run check # lint + tests + parser probes: the gate for every changeOther tasks:
mise run lint(typecheck, biome, plugin-skill drift; read-only)mise run fmtmise run build:clientmise run migrate:localmise run plugin:sync(copiesskills/into the plugin and the Worker)
mise tasks lists them all.
The local environment in wrangler.jsonc has no Workers AI or Vectorize, because both need a
Cloudflare login even under wrangler dev. Search there is keyword-only unless you set
CAIRN_FAKE_EMBEDDINGS=1. Locally only, GET /app/dev-login?u=<name> signs you in without a
passkey, for headless UI checks.
Live checks against your instance. Both scripts create a throwaway account and delete it afterwards, and both need Playwright with Chromium:
npm install --no-save playwright && npx playwright install chromium # once
CAIRN_URL=https://cairn.example.com mise run smoke:live # sign-up, app pages, PAT, MCP, export, deletion
CAIRN_URL=https://cairn.example.com mise run oauth:live # Claude Code, claude.ai and DCR OAuth flowsWith an existing install, point the scripts at it with PLAYWRIGHT=/path/to/playwright/index.mjs
and CHROMIUM_PATH=/path/to/chrome. Your instance must allow sign-up (OPEN_SIGNUP="true") for
the throwaway account.
Layout
src/index.ts Worker entry: OAuth provider, /mcp, email() and scheduled() handlers
src/vault/ VaultDO, one SQLite Durable Object per project: notes, FTS5, revisions, search
src/mcp/ the MCP tools, receipts, instructions, the core_memory prompt
src/memory/ supersession, history, nightly gardening and digest
src/capture/ attachments (R2), email, clipper, SSRF-guarded fetch
src/jev/ Jev hints and re-ranking
src/web/ the Hono app: viewer, editor, settings, consent, export, import
src/client/ browser code, bundled into public/app/dist/
migrations/ D1 migrations
skills/, plugin/ agent skills and the Claude Code plugin
docs/ DESIGN.md, SELF-HOSTING.md, SECURITY.md, research/Licence
MIT © 2026 Jeff Clement. Cairn's note format and tool behaviour follow Basic Memory's published documentation; it contains no Basic Memory (AGPL) code. owg-css is MIT.
This server cannot be deployed
Maintenance
Related MCP Connectors
One memory, every AI. A shared, user-owned markdown memory your AI clients read and write over MCP.
Persistent memory layer for AI tools. Save and recall notes across Claude and other MCP clients.
MCP-native notes and memory for ChatGPT, Claude, and other AI tools.
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceEnables LLM clients to read, search, create, update, and delete notes in a self-hosted shared memory bank over MCP, using hybrid vector and full-text search with optional summarization.4MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP-compatible clients to create, list, search, and delete notes, read all notes as markdown, and use prompts to summarize or clean up notes.MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to read, search, create, edit, move, and version Markdown notes, folders, tags, tasks, and attachments. It also supports generating meeting reports and bulk-importing notes through authenticated MCP connections.MIT
- FlicenseNot gradedqualityAmaintenanceEnables MCP-compatible clients such as Claude to create, link, search, explore, and synthesize atomic notes using a Markdown source of truth with an SQLite index for traversal and querying.-