Obsidian Vault MCP Server
by JonHollander
README.md
# Obsidian + Claude via Cloudflare
Access your Obsidian vault from Claude (web, desktop, Code) using an MCP server
on Cloudflare Workers + Containers.
No NAS, no Docker Compose, no tunnels. Just Cloudflare infrastructure with the
Agents SDK for a proper MCP server.
## Architecture
```
Obsidian (phone, desktop)
│
│ Obsidian Sync (your existing subscription)
▼
Cloudflare Container (Node.js 22)
runs `ob sync --continuous`
serves vault files over HTTP API
▲
│ container fetch (native)
│
Cloudflare Worker (MCP server via Agents SDK)
tools: list, read, search, write, append, delete
auth via bearer token (or OAuth / Cloudflare Access)
▲
│ MCP over Streamable HTTP
│
Claude (web, desktop, Code)
```
The Container is the single source of truth. It runs `obsidian-headless` to sync
with Obsidian Sync and exposes an HTTP API for file operations. The Worker proxies
all MCP tool calls to the Container's API.
## MCP Tools
| Tool | Description |
|------|-------------|
| `list_notes` | List all markdown notes with paths, sizes, and dates |
| `read_note` | Read the full content of a note by path |
| `search_notes` | Full-text search across all notes with snippets |
| `write_note` | Create or overwrite a note |
| `append_to_note` | Append to an existing note (or create it) |
| `delete_note` | Delete a note |
| `create_folder` | Create a folder (with intermediate directories) |
| `delete_folder` | Delete a folder (empty or recursive) |
| `list_folders` | List immediate subfolders at a path |
## Prerequisites
- Cloudflare account with Workers Paid plan ($5/month)
- Active [Obsidian Sync](https://obsidian.md/sync) subscription
- Node.js 22+ on your workstation
- `wrangler` CLI: `npm install -g wrangler`
## Setup
### 0. Wrangler Login
```bash
wrangler login
```
All required scopes are granted by default.
### 1. Generate Obsidian Auth Token
One-time step on your workstation:
```bash
npm install -g obsidian-headless
ob login
# Enter email, password, MFA code if enabled
ob sync-list-remote
# Note your vault name
```
### 2. Configure Environment
Copy the example env file and fill in your values:
```bash
cp .dev.vars.example .dev.vars
```
Edit `.dev.vars` with your Obsidian credentials and optional MCP auth token.
This file is used by `wrangler dev` for local development and by the setup
script to push secrets to Cloudflare. It's already in `.gitignore`.
### 3. Deploy
Run the setup script to push all secrets and deploy:
```bash
./scripts/setup.sh
```
Or run steps individually:
```bash
./scripts/setup.sh secrets # Push secrets to Cloudflare
./scripts/setup.sh validate # Check prerequisites
./scripts/setup.sh deploy # Validate + install deps + deploy + restart container
./scripts/setup.sh status # Check sync container health
./scripts/setup.sh restart # Restart sync container
./scripts/setup.sh container-logs # View sync container logs
```
Your MCP server is live at:
`https://obsidian-mcp.<your-subdomain>.workers.dev/mcp`
### 4. Connect Claude
**Claude.ai (web)**
Settings → Connectors → Add custom connector:
- URL: `https://obsidian-mcp.<your-subdomain>.workers.dev/mcp?token=YOUR_MCP_AUTH_TOKEN`
- Leave OAuth fields blank — the token in the URL handles auth
**Claude Code**
```bash
claude mcp add \
--transport http \
--scope user \
obsidian-vault \
https://obsidian-mcp.<your-subdomain>.workers.dev/mcp
```
**Claude Desktop**
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"obsidian-vault": {
"url": "https://obsidian-mcp.<your-subdomain>.workers.dev/mcp"
}
}
}
```
## How Data Flows
### You edit a note on your phone:
1. Obsidian Sync pushes the change
2. Container's `ob sync --continuous` pulls it to `/vault`
3. Next time Claude reads or searches, the Worker proxies the request to the
Container's HTTP API which reads directly from `/vault`
### Claude creates a note:
1. Worker receives MCP `write_note` call
2. Worker proxies it to the Container's HTTP API
3. Container writes the file to `/vault`
4. `ob sync` detects the new file and pushes it via Obsidian Sync
5. It appears on your phone and desktop
## Development
```bash
# Local dev (MCP server only, no container)
npm run dev
# Deploy
npm run deploy
```
## Cost
| Service | Usage | Cost |
|---|---|---|
| Workers Paid Plan | Already paying | $5/month (covers everything) |
| Container | 1 instance, mostly idle | Included in Workers plan |
| **Total additional** | | **$0** |
## Project Structure
```
obsidian-mcp/
├── src/
│ └── index.ts # MCP server (Agents SDK, proxies to container)
├── sync-container/
│ ├── Dockerfile # Headless sync container image
│ ├── entrypoint.sh # Auth, sync startup
│ └── server.js # HTTP API for vault file operations
├── scripts/
│ └── setup.sh # Push secrets, deploy
├── .dev.vars.example # Template for env vars / secrets
├── wrangler.jsonc # Worker + Container config
└── package.json
```
## Next Steps
These are left as exercises to harden the setup for your needs:
### Auth Hardening
The included auth (`MCP_AUTH_TOKEN` secret) supports both `Authorization: Bearer`
headers and `?token=` query params. The URL token approach is convenient for
Claude.ai connectors where custom headers aren't always available.
For shared or public deployments, consider stronger options:
- **Cloudflare Access**: Put [Zero Trust Access](https://developers.cloudflare.com/cloudflare-one/policies/access/)
in front of the Worker for identity-based SSO with audit logs and no code changes
- **OAuth**: Integrate [`workers-oauth-provider`](https://github.com/cloudflare/workers-oauth-provider)
for GitHub/Google OAuth flows
### Container Auth
Check whether `obsidian-headless` supports `--token` or env-var-based auth for
`ob login` to avoid interactive prompts. If not, persist the auth session from a
one-time interactive login and restore it on container start.
### Container Restart Resilience
The `ob` sqlite state file lives on ephemeral container disk. A restart triggers
a full re-sync. To fix: add a SIGTERM trap in `entrypoint.sh` that persists the
state file, and restore it on startup.
### Search Performance
The brute-force search reads every `.md` file per query — fine for <500 files.
For larger vaults, build a search index in [D1](https://developers.cloudflare.com/d1/)
or [Workers KV](https://developers.cloudflare.com/kv/).
### Attachments
Currently filters to `.md` only. Extend to support images, PDFs, and other
vault attachments with additional tools.
## Troubleshooting
**Docker must be running** — The sync container requires Docker. Run `docker info`
to verify. The `validate` subcommand checks this automatically.
**Two passwords** — `OBSIDIAN_PASSWORD` is your Obsidian account password (used
to log in at obsidian.md). `VAULT_PASSWORD` is the separate end-to-end encryption
password set in Obsidian → Sync → Encryption. Leave `VAULT_PASSWORD` empty if
your vault doesn't use E2EE.
**Deploy doesn't restart containers** — `wrangler deploy` does not restart running
containers. The setup script handles this automatically. If deploying manually,
restart with `./scripts/setup.sh restart`.
**Container logs not in wrangler tail** — Container stdout is not streamed through
`wrangler tail`. Use `./scripts/setup.sh container-logs` instead.
## Component Reference
| Component | What it does |
|---|---|
| [`obsidian-headless`](https://www.npmjs.com/package/obsidian-headless) | Official Obsidian CLI, syncs vault headlessly |
| [`McpAgent`](https://developers.cloudflare.com/agents/model-context-protocol/mcp-agent-api/) (Agents SDK) | Handles MCP transport, sessions, auth |
| [`McpServer`](https://github.com/modelcontextprotocol/typescript-sdk) (MCP SDK) | Tool registration, JSON-RPC protocol |
| [Cloudflare Containers](https://developers.cloudflare.com/containers/) | Runs the sync process alongside the Worker |
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues