obsidian-mcp-lite
Provides tools for interacting with an Obsidian vault, enabling AI agents to list, read, search, and modify notes with per-agent folder permissions, revision checks, atomic writes, and audit logging.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@obsidian-mcp-litesearch my notes for anything about the Q3 planning meeting"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
obsidian-mcp-lite
A small MCP server that gives several AI agents access to one Obsidian vault, with per-agent folder permissions enforced by the server. Each agent gets its own bearer token; the token decides what it can see and change.
Streamable HTTP at
/mcp(stateless, JSON responses);/healthzfor health checks.Nine tools:
list_dir,read_file,search,stat, and the write toolswrite_file,edit_file,append_file,move_file, plusdelete_fileif enabled (off by default).Folders an agent can't read are invisible: listings and search results never show them.
Built for a vault that the Obsidian sync client and the owner edit at the same time: revision checks, atomic writes, per-file locks, and an audit log.
How it works
agent ──► MCPHub ──(Bearer <agent token>)──► obsidian-mcp-lite ──► /vault
│ Host allowlist → 403
│ token → identity → 401
│ ACL (acl.yaml) per request
└► /data/audit.jsonl, /data/locks/Each agent is a separate MCPHub upstream pointing at http://obsidian-mcp-lite:8000/mcp with its
own Authorization: Bearer … header.
Related MCP server: Obsidian MCP Server
Tools
Tool | What it does |
| Name, type, size and mtime per entry. Returns |
| UTF-8 text only. Line-numbered content, |
| Case-insensitive search of file names and note contents, with line snippets. Uses ripgrep when installed (it is in the image). |
|
|
| Creates a file, or replaces one. Replacing needs |
| Exact string replacement. Fails with |
| Appends text, creating the file if needed. Adds a newline first if the file doesn't end with one. |
| Needs write access to both. Never overwrites. Does not rewrite |
| Only when |
tools/list shows the write tools only to identities that have at least one write rule, and
calling a hidden tool fails with Unknown tool.
Errors are prefixed with a stable code an LLM can act on, for example
path_forbidden: 'Private/x.md' is outside your read scope, conflict: …, no_match: …,
unsupported_extension: …, too_large: …, revision_required: ….
Permissions (acl.yaml)
# /config/acl.yaml: the deployed ACL (decided by admin, 2026-09-29)
always_deny: [".obsidian/", ".trash/", ".git/", "Private/"] # every identity, incl. future ones
identities:
claude-rc:
token_env: OBSIDIAN_MCP_TOKEN_CLAUDE_RC
read: ["/"] # "/" = whole vault
write: ["LLM_Data/"] # trailing slash = folder + everything under it
deny: ["Private/"]
hermes:
token_env: OBSIDIAN_MCP_TOKEN_HERMES
read: ["/"]
write: ["LLM_Data/"]
deny: ["Private/"]See deploy/acl.example.yaml for a commented version.
Vault-wide denials go in
always_deny. It applies to every identity, including any you add later (say, a new DeepSeek worker), so nobody has to remember a per-identitydenyforPrivate/.Allow rules (
read,write):"/"means the whole vault."Folder/"means that folder and everything under it."Note.md"(no trailing slash) means exactly that path."LLM_Data/"does not matchLLM_Data_old/.Deny rules (
always_deny,deny) always cover the path and everything under it, with or without a trailing slash.Precedence:
always_deny>deny>write>read. Write implies read.Any path segment starting with
.is always denied..obsidian/,.trash/and.git/are denied even if the file leaves them out.Deny rules match on a folded "skeleton": case, accents, full-width letters and the Turkish dotted/dotless i are all folded away.
Private/also blocksPRIVATE/,Prıvate/andPrivate/, which matters on a case-insensitive dataset. Allow rules are case-sensitive.An agent with read access only below some folder can still list the folders that lead there, and sees nothing else in them.
Tokens never go in this file.
token_envnames the environment variable that holds the token. Tokens must be at least 32 characters and unique. An identity whose variable is empty is disabled and logged at startup. When you add an identity, add its variable to the composeenvironment:block too.Reload: file changes are picked up within about a second, or immediately after
SIGHUP(docker kill -s HUP obsidian-mcp-lite). If the new file is invalid, the error is logged and the last good ACL stays in force.
Safety model
Paths: everything is relative to
/vault. The server rejects absolute paths,..segments, NUL bytes and drive letters. The ACL is checked on the path the agent asked for before touching the disk, so a denied file's existence is never revealed.Symlinks: every path is resolved with
realpath(for a new file, via its parent). The result must stay inside the vault and pass the same ACL check, so a symlink can't reach/etcor a denied folder. Listings never follow symlinked folders. Write tools refuse a symlink as the file itself.Atomic writes: the new content goes to a hidden temp file in the same folder, is fsynced, and then renamed over the target. Existing file modes are kept. New files are
0644and new folders0755.Concurrency:
edit_file,append_fileand the other write tools hold a per-file lock for the whole read-modify-write. Locks live in/data/locks/as a fixed set of 256 bucket files, so they can't pile up. They also re-check the file just before the rename, so a sync client that writes in the meantime causes aconflict, not a lost edit.Search: user regexes never run on Python's
re. Filenames and the fallback content search use theregexmodule, which has a timeout and releases the interpreter lock, and ripgrep's engine runs in linear time. Every search has a 30-second budget, so a pathological pattern returns partial results markedTIMED OUTinstead of freezing the server.Limits: only
.md .txt .canvas .base .jsonfiles can be written. Reads and writes are capped at 5 MiB by default.Audit: every successful change appends one JSON line to
/data/audit.jsonl(ts, identity, tool, path, old_rev, new_rev, plusdst/trashed_tofor moves and deletes).HTTP: the Host header is checked against
OBSIDIAN_MCP_ALLOWED_HOSTS(403 otherwise)./mcprequiresAuthorization: Bearer <token>(401 otherwise), compared in constant time./healthzneeds no token.
App state lives in /data, which must not be inside the vault; the server refuses to start if
it is.
Configuration
Variable | Default | Meaning |
| — | One per identity; names come from |
|
| Comma-separated Host header allowlist; |
| unset (off) |
|
|
| Vault root. |
|
| ACL file. |
|
| Audit log and lock files. Must not be inside the vault. |
|
| Largest file |
|
| Largest resulting file a write tool will produce. |
|
| Listen address. |
Deploy (TrueNAS, mcp-servers stack)
Image:
ghcr.io/realbeepmcjeep/obsidian-mcp-lite:latest, or:sha-<commit>to pin a version.Service block:
deploy/compose.yaml. It uses themcp_backendnetwork only, publishes no ports, and runs asuser: 568:568with a read-only root filesystem andno-new-privileges. Mounts:/mnt/tank3/apps/obsidian/vault:/vault,…/obsidian-mcp/config:/config:roand…/obsidian-mcp/data:/data.Stack
.env:deploy/.env.example.Smoke test from the TrueNAS shell:
sh deploy/smoke-truenas.sh(source). It usescurlimages/curlinsidemcp_backendand reads the tokens from the running container. For each identity it runsinitializeandtools/list, checks thatread_file Private/README.mdis refused and thatlist_dir /andsearchdon't revealPrivate, then makes one allowed write intoLLM_Data/. It prints PASS/FAIL per check and exits non-zero on any failure.
Development
uv sync
uv run pytest # 90+ tests: traversal, symlinks, ACL, revisions, locking, HTTP auth
uv run ruff check src tests scripts
# Run locally against a scratch vault:
OBSIDIAN_MCP_VAULT_DIR=/tmp/vault OBSIDIAN_MCP_DATA_DIR=/tmp/data \
OBSIDIAN_MCP_ACL_FILE=deploy/ci-acl.yaml SMOKE_WRITER_TOKEN=$(openssl rand -hex 32) \
SMOKE_READER_TOKEN=$(openssl rand -hex 32) uv run obsidian-mcp-lite serve --port 18000CI (.github/workflows/ci.yml) runs lint and tests, then builds the image and smoke-tests it
over HTTP as a non-default uid. publish.yml pushes to GHCR only after CI passes on main, then
checks that the image can be pulled anonymously.
Limitations
move_filedoesn't update links in other notes.append_fileisn't revision-checked by design. If the sync client writes the same file at the same moment, the append returnsconflictand can simply be retried.A symlink swapped in by something other than this server, between the check and the open, is not fully covered: the final open uses
O_NOFOLLOW, but parent folders aren't re-checked. No tool can create symlinks.Content search covers text files only (the extensions above), not PDFs or images.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Agent-native notes, tasks, dev-docs, vaults, sync & handoffs. MCP + OpenAPI dual surface.
Persistent docs and memory for AI agents — read, write, organize & search a shared workspace.
Self-hostable shared brain for you and your AI agents — docs, flows, meetings, decisions, rationale
Search, read, and safely update Markdown notes in your connected Phasoric knowledge vaults.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI assistants to interact with Obsidian vaults for creating, reading, searching, and managing notes, daily notes, TODOs, session reports, and backlinks through both stdio and HTTP/SSE transports.103,445 npm4MIT
- AlicenseNot gradedqualityDmaintenanceExposes a folder of Markdown notes (Obsidian vault or plain .md files) to LLM clients like Claude and ChatGPT via Streamable HTTP, with tools for searching, reading, writing, and managing notes directly on the filesystem.10,054 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to securely read and write to an Obsidian-compatible Markdown vault with per-agent access control, audit logging, and conflict resolution.Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to list, read, search, create, update, rename, and delete markdown notes in a local Obsidian vault via an HTTP MCP endpoint.6 npmMIT