Skip to main content
Glama
jamessturges

Obsidian MCP Server

by jamessturges

Obsidian MCP Server

A remote MCP server that exposes a folder of Markdown notes — an Obsidian vault, or just a plain folder of .md files — to LLM clients like Claude and ChatGPT. It talks Streamable HTTP, operates directly on the filesystem, and has no dependency on the Obsidian app itself.

MCP client --HTTP--> server.mjs (127.0.0.1:MCP_PORT) --node:fs--> VAULT_ROOT

The server only binds to 127.0.0.1. It does not do TLS termination, authentication, or expose itself to the network — that's a separate concern, left to whatever you put in front of it (see Exposing it remotely).

What it can do

Tool

Scope

obsidian_search

Full-text search across the vault (or a folder), with context, folder scoping, case sensitivity, and per-file match counts

obsidian_list_notes

List files/folders at a path

obsidian_read_note

Read any note; include_metadata adds frontmatter, tags, headings, word count

obsidian_read_notes

Batch-read up to 20 notes in one call; a missing note errors per-path, not the whole batch

obsidian_recent_notes

List the most recently modified notes, newest first

obsidian_vault_tree

Folder tree overview, to orient without many list calls

obsidian_query_notes

Structured metadata search: filter by folder, tags, frontmatter fields, or modified date

obsidian_list_tags

All tags in use (frontmatter + inline #tag) with usage counts, to avoid inventing near-duplicate tags

obsidian_list_tasks

Checkbox inventory (- [ ] / - [x]) across the vault, with folder/status filters

obsidian_get_backlinks

Outgoing links + backlinks for a note, resolved the way Obsidian resolves [[wikilinks]]

obsidian_create_inbox_note

Create a note in Inbox/ (fails if it exists)

obsidian_append_inbox_note

Append to a note in Inbox/ (creates if absent)

obsidian_append_daily_note

Append to today's (or a given date's) daily note, creating it if absent

obsidian_capture_inbox

Save a standardized capture (conversation, excerpt, decision, todo, etc.) into Inbox/

obsidian_update_note

Edit an existing note anywhere in the vault — full replace, exact-string replace, or insert under a heading — fails if the note doesn't exist

obsidian_move_note

Move/rename a note anywhere in the vault, rewriting every other note's [[wikilink]] to it (alias/heading/embed preserved) — fails if the destination exists

obsidian_delete_note

Soft-delete a note into .trash/ (reversible); reports notes that link to it so you know what's now dangling

Most of the metadata tools (query_notes, list_tags, list_tasks, get_backlinks) ride on one internal vault index that walks the vault once and caches parsed frontmatter/tags/headings/links/tasks per file, keyed by (path, mtime), so repeat calls only re-parse notes that actually changed.

Related MCP server: Obsidian MCP Server

Write model

  • New notes can only be created inside Inbox/ (configurable). The one exception is obsidian_append_daily_note, which may create today's daily note outside Inbox/ because that path is fully deterministic and the write is append-only.

  • Editing, moving/renaming, and deleting existing notes is allowed anywhere in the vault.

  • Delete is soft: obsidian_delete_note moves the file into .trash/ instead of unlinking it, so it's always recoverable by hand. .trash/ is excluded from every listing/search tool.

  • Move is wikilink-safe: obsidian_move_note rewrites every other note's [[wikilink]]/![[embed]] that resolves to the moved note (bare filename if that's still unique, otherwise the full path), preserving aliases and #heading anchors.

  • Path safety: every tool resolves paths through one canonical helper that rejects absolute paths, .. traversal, and symlinks pointing outside VAULT_ROOT.

  • Writes are atomic: temp file + rename in the same directory, so a client watching the folder (Obsidian, a sync client) never sees a partial write.

Configuration

Copy .env.example to .env and fill in:

Variable

Default

Meaning

VAULT_ROOT

— (required)

Absolute path to the vault folder.

MCP_PORT

3001

Port the server listens on (loopback only).

INBOX_FOLDER

Inbox

Where new-note creation is confined to.

CAPTURE_WRITE_JSON_SIDECAR

true

Whether obsidian_capture_inbox also writes a .json sidecar next to the Markdown.

CAPTURE_EMBED_RAW_JSON

false

Whether to embed the raw capture payload as a JSON block in the Markdown.

CAPTURE_COLLISION_BEHAVIOR

suffix

suffix | overwrite | error — what to do if a capture's generated filename already exists.

DAILY_NOTE_FOLDER / DAILY_NOTE_FORMAT

Fallback for obsidian_append_daily_note only if .obsidian/daily-notes.json doesn't exist (the server reads your real Daily Notes plugin settings first).

Running it

npm install
cp .env.example .env   # set VAULT_ROOT
node server.mjs
curl -s http://127.0.0.1:3001/health   # {"ok":true}

That's the whole server: one Node process, no database, no build step. npm run start does the same thing.

Exposing it remotely

Claude web/mobile and ChatGPT connect to MCP servers over the network, not over stdio like Claude Desktop — so using this from those clients means putting a real HTTPS URL with authentication in front of 127.0.0.1:MCP_PORT. This server has no authentication of its own; that's intentionally left to whatever sits in front of it. /mcp should be behind that auth layer; /health is safe to leave open, it returns nothing but {"ok":true}.

Once the client can reach the URL, add it as a custom connector (Claude) or a developer-mode connector (ChatGPT — standard ChatGPT connectors only invoke tools literally named search/fetch and won't show any actions here, since this server intentionally doesn't add those aliases).

A note on cloud-synced vaults

If VAULT_ROOT lives on a cloud-synced drive (iCloud Drive, Dropbox, OneDrive, etc.), a file can be an evicted placeholder that isn't actually on disk yet. readFileSync on one either blocks while it downloads or errors. The search/read tools handle this per-file — an unreadable note is reported individually rather than failing the whole call — but if it becomes a real problem, opening the file once in the desktop app (or your sync client's "keep local copy" option) forces it to materialize.

Development

The server is split into server.mjs (tool registration + Streamable HTTP transport) and lib/:

  • lib/config.mjs — env loading and validated config constants

  • lib/vault-fs.mjsresolveInVault, atomic writes, vault walking, ignore rules

  • lib/markdown.mjs — frontmatter/heading/task/wikilink/tag parsing (fence-aware)

  • lib/vault-index.mjs — the cached vault-wide index and link resolution/backlink graph

  • lib/daily-notes.mjs — reads .obsidian/daily-notes.json, Moment-token date formatting

  • lib/capture.mjs — the obsidian_capture_inbox payload normalizer and Markdown renderer

scripts/smoke-test.mjs drives the MCP surface end-to-end (initializetools/listtools/call) against a scratch vault, without needing a real client:

VAULT_ROOT=/tmp/fake-vault node server.mjs &
node scripts/smoke-test.mjs

It exercises every tool above, including path-traversal rejection, wikilink rewriting on move, and dangling-backlink reporting on delete.

License

MIT — see LICENSE.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jamessturges/obsidian-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server