obsidian-remote-mcp
Self-hosted MCP server that provides remote AI clients with read and write access to Obsidian vaults over HTTPS without needing the Obsidian desktop app running.
README
obsidian-remote-mcp
A self-hosted MCP server for headless Obsidian vaults. It gives remote AI clients read and write access to a vault over HTTPS without requiring the Obsidian desktop app to be running on the same machine.
Features
This is meant for home servers, NAS boxes, VPSes, and other setups where your vault lives on disk and you want to expose it through a remote MCP endpoint for apps like Claude.ai.
- OAuth (browser sign-in) or a fixed bearer token, depending on what the client supports.
vault_contextserves your vault guide note (defaults toAGENTS.md) plus a shallow folder tree so agents can see your vault structure.- Daily notes from a path template you configure.
- Create, edit, update, and trash notes; wikilinks and YAML frontmatter work as usual.
- Edit just one section under a heading, without replacing the whole note.
- Full-note updates can check the version you last read, so another agent's edit is not overwritten by accident.
- Set or read individual frontmatter properties.
- Read one section under a heading, or list headings first, instead of pulling the whole note every time.
- Search by filename or regex in note text; content search can be scoped to a folder.
- Block paths with
.mcpignore; setVAULT_READ_ONLYto turn off writes. - Optional JSONL logs of tool calls (note bodies redacted) if you want to review what agents did.
Security and scope
Built-in safeguards:
- OAuth 2.1 + PKCE with optional
MCP_CLIENT_SECRETon token exchange - Fixed bearer token (
MCP_STATIC_BEARER_TOKEN) for clients that skip browser auth - CORS allowlisting (
CORS_ALLOWED_ORIGINS) for browser-based clients - Vault path sandboxing — all paths validated against the vault root
.mcpignoreto block specific paths from MCP accessVAULT_READ_ONLYmode to prevent all writes
What it includes
The server currently exposes these tools:
| Tool | Description |
|---|---|
vault_context |
Read the vault guidance note configured by VAULT_CONTEXT_PATH, or fall back to AGENTS.md / CLAUDE.md |
vault_read |
Full note text (mode full, default) with a version for safe updates, or list one folder level (mode list; path "" = vault root) |
vault_outline |
All # headings in a note (one per line); use before vault_read_section |
vault_read_section |
Body under a single heading (heading = text without #, case-insensitive) |
vault_frontmatter |
Read YAML frontmatter from a note; optional property for a single key |
vault_links |
Read outgoing wikilinks and optional backlinks |
vault_create |
Create a new note |
vault_update |
Replace a note's full contents; optional base_version rejects stale writes |
vault_set_frontmatter_property |
Set one frontmatter property without rewriting the note body |
vault_edit |
Append, prepend, or replace exact text within a note |
vault_edit_section |
Append, prepend, or replace the body under one heading |
vault_trash |
Move a note to .trash |
vault_search_title |
Find notes by filename (partial or exact); returns paths for vault_read |
vault_search_content |
Regex search in note bodies; optional folder to scope large vaults |
vault_daily_note |
Read or create a daily note using a configurable path template |
Quick start
Runtime. The server is TypeScript on Bun. There is no separate build step for normal use: Bun runs src/server.ts directly. Install dependencies with bun install (Express, the MCP SDK, and a few libraries—see package.json).
Vault on disk. Point the server at your vault root with VAULT_PATH, or leave it unset and use Obsidian’s obsidian.json discovery (details under Vault path below).
git clone https://github.com/nweii/obsidian-remote-mcp.git
cd obsidian-remote-mcp
bun install
export VAULT_PATH=/absolute/path/to/your/vault
export MCP_CLIENT_ID=dev
bun run src/server.ts
bun start runs the same entrypoint (package.json maps it to bun run src/server.ts).
The process listens on port 3456 by default. The MCP endpoint is POST /mcp on that port; OAuth metadata is served under /.well-known/oauth-authorization-server.
To make the server reachable from the internet (required by Claude.ai and other remote clients), see Deployment below.
Deployment
Docker
Save this as docker-compose.yml in the cloned repo directory:
services:
obsidian-remote-mcp:
image: oven/bun:1 # pre-built Bun runtime — no local Bun install needed
working_dir: /app
restart: unless-stopped
environment:
MCP_CLIENT_ID: your-client-id
MCP_CLIENT_SECRET: your-client-secret # optional
MCP_BASE_URL: https://mcp.example.com
VAULT_PATH: /vault # path inside the container (mapped by volumes below)
CORS_ALLOWED_ORIGINS: https://claude.ai
PORT: 3456
# TOKEN_STORE_PATH: /app/data/tokens.json # uncomment to persist OAuth sign-ins across restarts
volumes:
- ./:/app # mounts the repo into the container
- /path/to/your/vault:/vault # left = path on your machine, right = path inside container
# - ./data:/app/data # uncomment to persist TOKEN_STORE_PATH on the host
command: ["bun", "run", "src/server.ts"]
ports:
- "3456:3456" # host:container — access on http://localhost:3456
docker compose up -d # start in background
docker compose logs -f # watch output
HTTPS and MCP_BASE_URL
Remote MCP and OAuth require HTTPS. Use a reverse proxy (Caddy, nginx, Cloudflare Tunnel, etc.) to handle TLS in front of the app and expose a public URL like https://mcp.example.com. Set MCP_BASE_URL on the server to that origin without the /mcp path — it must match what users see in the browser bar.
If you use Cloudflare Zero Trust, a practical pattern is to put the identity gate only on /authorize, so users log in to approve access while /.well-known/*, /oauth/token, and /mcp stay reachable for the protocol.
Environment variables
Set these on the server process (Compose environment:, Portainer, shell export, etc.):
MCP_CLIENT_ID=your-client-id
MCP_CLIENT_SECRET=your-client-secret # optional
MCP_BASE_URL=https://mcp.example.com
MCP_ALLOWED_REDIRECT_URIS=https://claude.ai/api/mcp/auth_callback # optional
VAULT_PATH=/path/to/your/vault # optional if obsidian.json is available
OBSIDIAN_VAULT_ID=personal # optional when obsidian.json contains multiple vaults
VAULT_DISPLAY_NAME=Personal # optional; defaults to the vault directory name
VAULT_CONTEXT_PATH=AGENTS.md # optional; defaults to AGENTS.md, then CLAUDE.md
DAILY_NOTE_PATH_TEMPLATE=Daily/{YYYY}-{MM}-{DD}.md
CORS_ALLOWED_ORIGINS=https://claude.ai # optional; defaults to *
TOKEN_STORE_PATH=./tokens.json # optional
MCP_STATIC_BEARER_TOKEN= # optional; fixed Bearer for /mcp (e.g. mcp-remote + Antigravity)
VAULT_READ_ONLY=true # optional
PORT=3456
VAULT_PATH, VAULT_CONTEXT_PATH, and DAILY_NOTE_PATH_TEMPLATE are also documented under Vault path, Vault context note, and Daily note paths later in this readme.
Authenticating to /mcp
Every POST /mcp request must send Authorization: Bearer …. You can offer OAuth, fixed bearer, or both; each client then uses whichever path it supports.
OAuth (browser)
For clients where the user can open a browser. The server uses OAuth 2.1: approve on /authorize, exchange the short-lived code at POST /oauth/token, then send the issued access token in Authorization on /mcp.
Server: MCP_CLIENT_ID is required. MCP_CLIENT_SECRET is optional; if you set it on the server, configure the same value in each OAuth client so they send it at POST /oauth/token. If you leave it unset, that step does not use a shared secret—use HTTPS and limit who can reach /authorize. MCP_BASE_URL must match the public site origin (no /mcp). MCP_ALLOWED_REDIRECT_URIS is an optional comma-separated list; Claude’s callback is allowed by default.
Persisted sign-in: TOKEN_STORE_PATH (default ./tokens.json) stores OAuth-issued tokens after login so clients survive server restarts.
Add as a remote MCP connector to Claude (paid plans only): Use your base URL with /mcp included. Under advanced settings, set OAuth client ID to your MCP_CLIENT_ID, optionally OAuth client secret to your MCP_CLIENT_SECRET (required at token exchange if the server has a secret configured). On the server, set MCP_BASE_URL to the same origin as the connector URL, without /mcp.
Cursor mcp.json:
{
"mcpServers": {
"obsidian-vault": {
"url": "https://your-host/mcp",
"auth": {
"CLIENT_ID": "your-mcp-client-id",
"CLIENT_SECRET": "your-mcp-client-secret (optional)"
}
}
}
}
Cursor uses the redirect URI cursor://anysphere.cursor-mcp/oauth/callback. Add it to MCP_ALLOWED_REDIRECT_URIS on the server (comma-separated alongside any other clients, e.g. Claude’s callback).
Fixed bearer (non-browser)
For scripts, Antigravity + mcp-remote, or any client that cannot run the browser flow. Do not set both auth and headers on the same mcp.json entry—pick OAuth or fixed bearer.
Set MCP_STATIC_BEARER_TOKEN on the server; the client sends that exact string as Authorization: Bearer … on every /mcp request. This skips /authorize, POST /oauth/token, TOKEN_STORE_PATH for that client.
Antigravity mcp_config.json:
"obsidian-vault": {
"serverUrl": "https://your-host/mcp",
"headers": {
"Authorization": "Bearer YOUR_MCP_STATIC_BEARER_TOKEN"
}
}
Cursor mcp.json:
{
"mcpServers": {
"obsidian-vault": {
"url": "https://your-host/mcp",
"headers": {
"Authorization": "Bearer YOUR_MCP_STATIC_BEARER_TOKEN"
}
}
}
}
Bridging over stdio with mcp-remote:
"obsidian-vault": {
"command": "bunx",
"args": ["-y", "mcp-remote", "https://your-host/mcp", "--transport", "http-only", "--header", "Authorization: Bearer YOUR_MCP_STATIC_BEARER_TOKEN"]
}
CORS
CORS_ALLOWED_ORIGINS limits which browser origins may call the API from JavaScript. It is separate from OAuth and from MCP_STATIC_BEARER_TOKEN. Default is * (allow all). To restrict:
CORS_ALLOWED_ORIGINS=https://claude.ai
CORS_ALLOWED_ORIGINS=https://claude.ai,http://localhost:3000
When set, only listed origins get a reflected Access-Control-Allow-Origin; other browser preflights fail.
Vault path
The server resolves the vault root in this order:
VAULT_PATH, if set.config/obsidian/obsidian.json, found by walking up from the current working directory or from the package directory
If obsidian.json contains multiple vaults, set OBSIDIAN_VAULT_ID to the vault entry you want to use.
The display name used in the OAuth approval page defaults to the resolved vault directory name. You can override that with VAULT_DISPLAY_NAME.
Headless / no Obsidian installed
Most users should just set VAULT_PATH and skip this. If you prefer the automatic discovery path on a machine without Obsidian, create the config file yourself:
mkdir -p ~/.config/obsidian
~/.config/obsidian/obsidian.json:
{
"vaults": {
"personal": {
"path": "/home/user/vaults/personal"
}
}
}
With multiple vaults, add more entries and set OBSIDIAN_VAULT_ID to the one you want. Use absolute paths — ~ is not expanded inside obsidian.json.
Vault context note
vault_context is meant to help agents learn your vault structure before they start writing. By default it looks for AGENTS.md or CLAUDE.md.
If your vault uses a different bootstrap file, set VAULT_CONTEXT_PATH to the relative path you want the tool to read.
If you do not want to maintain one, the server still works without it.
Daily note paths
vault_daily_note uses DAILY_NOTE_PATH_TEMPLATE, which defaults to:
Daily/{YYYY}-{MM}-{DD}.md
This is only a convenience tool. Many vaults use different daily note layouts, so you will likely want to override it.
Supported tokens:
{YYYY}: 4-digit year{YY}: 2-digit year{MM}: 2-digit month{M}: month without zero padding{DD}: 2-digit day{D}: day without zero padding{MMM}: short month name likeMar{MMMM}: full month name likeMarch{dd}: short weekday name likeTh{ddd}: short weekday name likeMon{dddd}: full weekday name likeMonday
Examples:
DAILY_NOTE_PATH_TEMPLATE=Daily/{YYYY}/{YYYY}-{MM}-{DD}.md
DAILY_NOTE_PATH_TEMPLATE=Journal/{YYYY}-{MM}-{DD}-{dddd}.md
DAILY_NOTE_PATH_TEMPLATE=Journal/{YYYY}/{MMM}/{D}-{ddd}.md
Notes
MCP and HTTP
GET /mcpreturns405, not404, so streamable HTTP clients know the server only accepts MCP overPOST.
Vault access and tool defaults
- All vault paths are validated against the resolved vault root to prevent directory traversal.
.mcpignorein the vault root can block paths from all MCP access.VAULT_READ_ONLY=trueblocks all write operations.vault_search_titledefaults tolimit=50;vault_search_contentdefaults tolimit=20. Limits are adjustable;0means no limit.vault_frontmatterandvault_set_frontmatter_propertylet agents work with frontmatter properties without reading or rewriting the whole note body.vault_readreturns a version block. Pass it tovault_updateasbase_versionif you want stale full-note updates to fail instead of overwriting another edit.
Tests
bun test
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.