Your notes, on your own server. Open to every device — and your AI.
mdnest keeps your notes as plain Markdown files on a server you run. Open them in your browser, your terminal, or straight from Claude and Cursor. One place for everything you (and your AI) want to remember.
No cloud. No lock-in. Your files stay yours.
- 🤖 Made for AI. Claude, Cursor, and your scripts read and write your notes over MCP and a CLI. Your AI finally has a memory that sticks between runs.
- 🛟 Never lose a note. Every change auto-saves to your own Git repo. Browse the full history and restore any version in one click.
- 🌐 Everywhere at once. Browser, phone, terminal, API — the same notes, always in sync. Your laptop sleeps; your notes don't.
- 📝 Nice to write in. A live editor where Markdown becomes rich text as you type — tables, diagrams, math, and drag-and-drop images. Or switch to plain text.
- 🗂️ Just files. Every note is a
.mdfile on disk.cat,grep, andgitstill work. Walk away anytime. - 👥 A wiki when you grow. Add SSO, roles, and comment threads for your team — a Confluence you can actually afford.
- You run AI agents and want them to remember things across runs and projects.
- You use more than one device and want one set of notes everywhere, never lost.
- You're a small team that wants a shared wiki without the Confluence bill.
Just want a local vault on one Mac? Obsidian is great. Pick mdnest when you want your notes on every device, backed up, and open to your AI.
Handles 1,000–5,000 notes out of the box. Bigger? Tune the search settings — config only. Full feature list in More features.
- Docker and Docker Compose
- Git
- Optional: Tailscale — free, only if you want encrypted remote access to your own devices later. Not needed to install or run mdnest.
git clone https://github.com/mahsanamin/mdnest.git
cd mdnest
./mdnest-server setupThis creates mdnest.conf from the sample. Open it and set:
- Credentials -- change
MDNEST_USER,MDNEST_PASSWORD, andMDNEST_JWT_SECRET - Mounts -- add at least one
MOUNT_<name>=<path>pointing to a directory on your machine
Example:
MDNEST_USER=ahsan
MDNEST_PASSWORD=mysecurepassword
MDNEST_JWT_SECRET=some-random-string
MOUNT_personal=/home/ahsan/notes
MOUNT_work=/home/ahsan/work-notes
Then build and start:
./mdnest-server rebuildInstall Tailscale on the host and on your phone/laptop, then:
tailscale serve --bg --https 3236 http://127.0.0.1:3236Access from any of your devices: https://your-server.tailnet.ts.net:3236
Encrypted, private, no ports opened to the internet. See Remote Access for more options.
Once the server is running, there are three ways to access your notes:
Open http://localhost:3236 (or your Tailscale URL) in any browser. Works on desktop and mobile.
Install the mdnest CLI on any machine (macOS / Linux):
curl -fsSL https://raw.githubusercontent.com/mahsanamin/mdnest/main/install-cli.sh | bashThen authenticate with a token from the web UI (Settings > API Tokens):
mdnest login https://your-server:3236 <your-api-token>Now use it from anywhere:
mdnest note list # list namespaces
mdnest note list personal # list files in a namespace
mdnest note read personal ideas.md # read a note
mdnest note write personal ideas.md "New content" # overwrite a note
mdnest note append personal log.md "New entry" # append to a note
mdnest note prepend personal log.md "Top entry" # prepend to a note
mdnest note create personal new-note.md # create a new note
mdnest note delete personal old-note.md # delete a note
mdnest note search personal "search query" # search notes
echo "piped content" | mdnest note write personal draft.md - # pipe from stdinRun mdnest note help for the full list.
AI agents can read, write, search, and organize your notes via the bundled MCP server.
cd mcp-server && npm installCreate an API token in Settings (gear icon) > API Tokens, then configure your MCP client:
{
"mcpServers": {
"mdnest": {
"command": "node",
"args": ["/path/to/mdnest/mcp-server/index.js"],
"env": {
"MDNEST_URL": "http://localhost:8286",
"MDNEST_TOKEN": "<your API token>"
}
}
}
}Available tools: list_namespaces, list_tree, read_note, write_note, append_note, prepend_note, create_note, create_folder, delete_item, move_item, search_notes
All server commands use mdnest-server and must be run from the project directory:
./mdnest-server start # start all services
./mdnest-server stop # stop all services
./mdnest-server restart # restart all services
./mdnest-server rebuild # rebuild after code or config changes
./mdnest-server logs # view logs (all services)
./mdnest-server logs backend # view backend logs only
./mdnest-server sync-logs # view git-sync logs
./mdnest-server status # show running containers
./mdnest-server migrate # run database migrations (multi mode only)After editing mdnest.conf, always re-run:
./mdnest-server rebuild./mdnest-server updateEverything is driven by mdnest.conf. Run ./mdnest-server rebuild after any change.
| Setting | Description | Default |
|---|---|---|
MDNEST_USER |
Login username (single mode) / initial admin (multi mode) | admin |
MDNEST_PASSWORD |
Login password (single mode) / initial admin password (multi mode) | changeme |
MDNEST_JWT_SECRET |
JWT signing secret | changeme |
BACKEND_PORT |
Backend API port | 8286 |
FRONTEND_PORT |
Frontend UI port | 3236 |
BIND_ADDRESS |
Host IP(s) to bind to. Comma-separated for multi-IP (e.g. 127.0.0.1,100.73.118.115) — useful for localhost + a Tailscale / VPN address without 0.0.0.0. |
127.0.0.1 |
MOUNT_<name> |
Map a host directory as a namespace | -- |
AUTH_MODE |
single (file-based, no DB) or multi (Postgres-backed users & permissions) |
single |
POSTGRES_PASSWORD |
PostgreSQL password (required when AUTH_MODE=multi) |
-- |
Filename filtering is instant (client-side). Content search runs server-side with concurrent file reads, a cached file index, and early termination.
| Setting | Description | Default |
|---|---|---|
SEARCH_MAX_RESULTS |
Max results per query | 30 |
SEARCH_MAX_FILE_SIZE |
Skip files larger than this (bytes) | 1048576 (1 MB) |
SEARCH_WORKERS |
Parallel file readers | 8 |
SEARCH_CACHE_TTL |
File list cache lifetime (seconds) | 30 |
For 10,000+ notes: set SEARCH_WORKERS=16 and SEARCH_CACHE_TTL=60.
By default, mdnest runs in single-user mode — one user, file-based auth, no database. Ideal for personal use.
Multi-user mode adds a three-tier role hierarchy, per-namespace grants, optional 2FA, and your choice of three identity providers (corporate SSO, Firebase Auth, or local username/password). Set AUTH_MODE=multi in mdnest.conf and setup.sh adds a Postgres container automatically.
AUTH_MODE=multi
POSTGRES_PASSWORD=a-secure-password
# Pick an identity provider:
USER_PROVIDER=local # username/password + per-user TOTP (default)
# or
USER_PROVIDER=sso # corporate OIDC — see docs/sso-setup.md
# or
USER_PROVIDER=firebase # Firebase Auth + Firestore TOTP — see docs/firebase-setup.mdThen rebuild:
./mdnest-server rebuild- SuperAdmin — global. Manages everyone, every namespace, every grant. Promote with
ADMIN_EMAILS=ops@example.cominmdnest.conf(auto-promoted on every startup). - Admin — namespace-scoped. Manages users, grants, and git-sync for just the namespaces in their
namespace_adminsrows. Cannot reset 2FA or change global roles. - Collaborator — per-grant access only. Sees only the namespaces / paths assigned to them.
Assign namespace admins via the admin panel's Namespace Admins tab, or POST /api/admin/namespace-admins. See docs/security.md for the full authorization model and docs/api.md for the API.
For a small company deploying mdnest as a shared knowledge base:
AUTH_MODE=multi
USER_PROVIDER=sso
SSO_ISSUER_URL=https://accounts.google.com # or your IdP
SSO_CLIENT_ID=...
SSO_CLIENT_SECRET=...
SSO_ALLOWED_DOMAINS=example.com # optional but recommended
ADMIN_EMAILS=ops@example.com,you@example.com # auto-promoted to superadmin
POSTGRES_PASSWORD=a-secure-passwordPlus a TLS reverse proxy (Caddy, nginx + certbot, or Cloudflare Tunnel) so the backend stays loopback-only and only the proxy is exposed.
Upgrading an existing single-user instance to multi-user:
- Edit
mdnest.conf: addAUTH_MODE=multi,POSTGRES_PASSWORD=<secure>, and yourUSER_PROVIDERsettings. ./mdnest-server rebuild— regenerates docker-compose.yml with a Postgres service, runs migrations on first start, seeds the initial admin.
Existing notes on disk are untouched. The database stores only user accounts and access grants.
See docs/setup.md for the full multi-user walkthrough, docs/sso-setup.md for IdP-specific (Google / Okta / Entra / Keycloak / Auth0) instructions, and docs/firebase-setup.md for the Firebase peer-mode setup.
Each MOUNT_<name>=<host_path> entry in mdnest.conf mounts a host directory as a namespace. Namespaces are isolated -- separate trees, separate files. Add or remove by editing mdnest.conf and running ./mdnest-server rebuild.
Your notes are private by default. Nothing leaves your machine unless you choose to enable sync.
To back up to a private GitHub repo:
- Initialize each namespace directory as a git repo with a remote
- Add an SSH key (passphrase-protected keys won't work inside Docker):
mkdir -p git-sync/keys # Option A: single key for all repos ssh-keygen -t ed25519 -f git-sync/keys/default -N "" -C "mdnest-sync" # Option B: one key per namespace (required for GitHub deploy keys) ssh-keygen -t ed25519 -f git-sync/keys/<namespace> -N "" -C "mdnest-sync"
- Add the
.pubkey to your Git provider (GitHub: Settings > Deploy Keys, enable write access) - Rebuild:
./mdnest-server rebuild
Git sync starts automatically when keys are found in git-sync/keys/. No keys = no sync.
The sync interval is configurable (default: every 10 minutes):
GIT_SYNC_INTERVAL=900 # sync every 15 minutes
The git remote is a backup destination — let mdnest be the only thing pushing to it. Don't commit to the same repo from other tools. See docs/setup.md for full setup details.
All ports bind to 127.0.0.1 by default. To access from other devices:
Tailscale creates an encrypted private network between your devices. No ports opened, no public IP needed.
Option A: Dedicated port (multiple services on the host)
tailscale serve --bg --https 3236 http://127.0.0.1:3236Access: https://<your-hostname>.tailnet-name.ts.net:3236
Option B: Default HTTPS (host dedicated to mdnest)
tailscale serve --bg http://127.0.0.1:3236Access: https://<your-hostname>.tailnet-name.ts.net
Manage:
tailscale serve status # see active rules
tailscale serve off # remove all rules- Nginx + Certbot -- traditional reverse proxy with free TLS. See docs/setup.md.
- Cloudflare Tunnel -- no open ports, works behind NAT. See docs/setup.md.
Beyond the core editor and the three access interfaces, mdnest includes:
- Obsidian
[[wikilinks]]. Bring an Obsidian vault over as-is:[[note]],[[note|alias]],[[note#heading]], and[[#heading]]resolve against your notes and open in-app — in both the preview and the Live editor. Broken links show muted so you can spot missing notes, and the markdown on disk stays literal[[...]]. - Inline comments with threads. Highlight any text and leave a comment; commented passages stay highlighted in yellow, and reviewers reply in a thread. Click a highlight to jump to the conversation. Comments are anchored to invisible UUIDs, so moving or renaming files keeps them attached.
- Live collaboration. Multiple people editing the same note see each other's cursors and changes in real time over WebSocket. Toggle with
ENABLE_LIVE_COLLAB=true. - Interactive mermaid diagrams. Diagrams render in-place with a Source/Preview/zoom toolbar and click-to-edit labels — edit node text directly on the diagram without touching the code.
- In-app version history. With git-sync enabled, right-click any note → History to browse past versions, compare any two as a diff, and one-click restore. Restoration is itself versioned and undoable through the same modal.
- Namespace-scoped admins. One or two SuperAdmins overall, plus per-team Admins who manage just their own namespace — invite users, manage grants, trigger git-sync — without touching other teams' data. See docs/security.md.
- Corporate SSO + 2FA. Point mdnest at any OIDC provider (Google Workspace, Okta, Microsoft Entra, Keycloak, Auth0); the IdP owns MFA, mdnest owns per-namespace authorization. Or use local username/password with TOTP. One
USER_PROVIDERflag — no code changes. See docs/sso-setup.md. - API-first. Full REST API + CLI with multi-server support (
mdnest read @work/eng/spec.md). Build scripts, automations, or integrations on top of your notes. - Git backup on your terms. Optional sidecar auto-commits and pushes to a private repo on a schedule you control. See Git Sync.
- Update-aware. The backend polls GitHub for newer releases; when one drops, a badge in the sidebar footer surfaces the release notes inline. Opt out with
DISABLE_UPDATE_CHECK=truefor air-gapped installs.
- docs/setup.md — Setup, configuration, env vars
- docs/user-guide.md — End-user walkthrough
- docs/security.md — Threat model, identity, authorization, role hierarchy
- docs/architecture.md — Backend / frontend / database layout
- docs/api.md — Full REST API reference with curl examples
- docs/cli.md —
mdnestCLI for terminal access (multi-server) - docs/sso-setup.md — Corporate SSO (Google / Okta / Entra / Keycloak / Auth0)
- docs/firebase-setup.md — Firebase Auth peer mode
MIT. See LICENSE.
