Back to MCP Servers

Obsidian Server

Access and interact with your Obsidian vault for personal knowledge management and note-taking

obsidiannotesknowledge-basemarkdowncommunity
By Community
51675Updated 2 weeks agoTypeScriptApache-2.0

Installation

npm install -g obsidian-mcp

Configuration

{
  "mcpServers": {
    "obsidian": {
      "command": "npx",
      "args": ["-y", "obsidian-mcp"],
      "env": {
        "OBSIDIAN_VAULT_PATH": "/path/to/your/vault"
      }
    }
  }
}

How to use

  1. Run the installation command above (if needed)
  2. Open your Claude Code settings file (~/.claude/settings.json)
  3. Add the configuration to the mcpServers section
  4. Restart Claude Code to apply changes
<div align="center"> <h1>obsidian-mcp-server</h1> <p><b>MCP server for Obsidian vaults — read, write, search, and surgically edit notes, tags, and frontmatter via the Local REST API plugin. STDIO or Streamable HTTP.</b> <div>14 Tools • 3 Resources</div> </p> </div> <div align="center">

npm Version Framework MCP SDK

License TypeScript Bun

</div>

Tools

Fourteen tools grouped by shape — readers fetch notes and metadata, writers create or surgically edit content, managers reconcile tags and frontmatter, and a guarded escape hatch dispatches Obsidian command-palette commands.

Tool NameDescription
obsidian_get_noteRead a note as raw content, full structured form (content + frontmatter + tags + stat, with optional outgoing links), structural document map, or a single section.
obsidian_list_notesList notes and subdirectories at a vault path with a recursive walk (default depth 2 — structural overview; max 20) bounded by a 1000-entry cap. Optional extension and nameRegex filters apply across the tree; regex-filtered directories are skipped without recursing into them. Returns flat entries[] plus a box-drawing tree in the rendered output; per-directory truncated: true flags where the depth limit cut off recursion.
obsidian_list_tagsList every tag found across the vault with usage counts, including hierarchical parents.
obsidian_list_commandsList Obsidian command-palette commands available for execution. Opt-in via OBSIDIAN_ENABLE_COMMANDS=true (paired with obsidian_execute_command).
obsidian_search_notesSearch the vault by text, Dataview DQL, or JSONLogic. Text-mode matches return surrounding context windows (contextLength) — capped at 100 hits with overflow indicator.
obsidian_write_noteCreate a note, replace a single section in place, or — with overwrite: true — clobber an existing file. Refuses whole-file writes against an existing path by default.
obsidian_append_to_noteAppend content to a note. Without section it creates the file if missing — your content becomes the entire file. With section, appends to that heading/block/frontmatter (PATCH; the file must exist).
obsidian_patch_noteSurgical append / prepend / replace against a heading, block reference, or frontmatter field.
obsidian_replace_in_noteBody-wide search-replace inside a single note. Literal or regex matching, with wholeWord, flexibleWhitespace, caseSensitive, replaceAll, and $1/$& capture groups.
obsidian_manage_frontmatterAtomic get / set / delete on a single frontmatter key.
obsidian_manage_tagsAdd, remove, or list tags — reconciles frontmatter tags: and inline #tag syntax.
obsidian_delete_notePermanently delete a note. Elicits human confirmation when the client supports it.
obsidian_open_in_uiOpen a file in the Obsidian app UI, with failIfMissing and newLeaf toggles.
obsidian_execute_commandExecute an Obsidian command-palette command by ID. Opt-in via OBSIDIAN_ENABLE_COMMANDS=true.

obsidian_get_note

Read a note in one of four projections, addressed by vault path, the active file, or a periodic note (daily, weekly, monthly, quarterly, yearly).

  • format: "content" — raw markdown body
  • format: "full" — content, frontmatter, tags, and file metadata; pass includeLinks: true to also parse outgoing wiki and markdown link references from the body (vault-internal only — external URLs are filtered)
  • format: "document-map" — catalog of headings, block references, and frontmatter fields
  • format: "section" — single heading/block/frontmatter section value (requires section); heading sections include the full subtree under that heading

Pair the document-map projection with obsidian_patch_note to discover edit targets before patching.


obsidian_search_notes

Three search modes selected by mode:

  • text — substring match with surrounding context windows. contextLength controls characters of context per side of each match (default 100; bump it for more context per hit). Optional pathPrefix filter (text mode only — passing pathPrefix in dataview or jsonlogic mode is rejected with path_prefix_invalid_mode).
  • dataview — Dataview DQL (TABLE …) for path/date/metadata queries; file.mtime, file.path, etc. are queryable
  • jsonlogic — JSONLogic tree evaluated against path, content, frontmatter.<key>, tags, and stat.{ctime,mtime,size}; custom glob and regexp operators

Results are capped at 100 hits. When the upstream returns more, an excluded indicator surfaces the overflow count and a hint to narrow the query. Text-mode hits are additionally clipped per file at maxMatchesPerHit (default 10) so a single match-heavy note can't blow the response budget — clipped hits carry truncated: true and totalMatches.


obsidian_write_note

Create or surgically replace, with a protective default against accidental whole-file overwrites.

  • Without section — full-file PUT. Refuses to clobber an existing file unless overwrite: true is set. The file_exists (Conflict) error suggests obsidian_patch_note / obsidian_append_to_note / obsidian_replace_in_note for in-place edits.
  • With sectionPATCH-with-replace against the named heading/block/frontmatter field, leaving the rest of the file untouched. The overwrite flag is ignored in section mode.

The output reports created: true when the call brought a new file into existence; false when it replaced an existing one or targeted a section. Every mutating tool also returns previousSizeInBytes and currentSizeInBytes so an agent can spot accidental clobbers, unexpected upstream behavior, or a typo path that landed at the wrong file.


obsidian_append_to_note

A combined upsert + section-append primitive that mirrors the upstream Local REST API behavior:

  • Without sectionPOST to /vault/{path}. Appends when the file exists, creates the file with your content as the entire body when it doesn't. The output's created: true flags the second branch so the agent can notice when a typo path or a not-yet-created daily note silently turned into a brand-new file.
  • With sectionPATCH-with-append against the named heading, block reference, or frontmatter field. The file must exist (PATCH preflight throws note_missing otherwise). Pass createTargetIfMissing: true to bring the section itself into existence inside an existing file. Block-reference targets concatenate adjacent to the block line without a separator — include a leading newline in content if you want one.

previousSizeInBytes is 0 on the upsert-create branch and the actual file size otherwise; currentSizeInBytes is the post-write size read from the upstream after the operation. Compare deltas against Buffer.byteLength(content) to detect auto-newline injection or concurrent writers.


obsidian_patch_note

Surgical edits at a single document target.

  • operation: "append" adds after the section
  • operation: "prepend" adds before the section
  • operation: "replace" swaps it out
  • Targets: heading path, block reference ID, or frontmatter field

Use obsidian_get_note with format: "document-map" to discover what targets exist before patching.


obsidian_replace_in_note

Body-wide search-replace for edits that don't fit obsidian_patch_note's structural targets. The note is fetched, replacements are applied sequentially (each sees the previous output), and the result is written back in a single PUT.

Per-replacement options:

  • useRegex — treat search as an ECMAScript regex. With useRegex: true, the replacement honors $1 / $& capture-group references.
  • caseSensitive — when false, match case-insensitively
  • wholeWord — wrap the pattern in \b…\b; works in both literal and regex modes
  • flexibleWhitespace — substitute any run of whitespace in search with \s+. Literal mode only — has no effect when useRegex: true (express it directly).
  • replaceAll — when false, only the first match is replaced

Literal mode preserves $1 / $& in the replacement verbatim — only useRegex: true expands capture-group references.


obsidian_manage_tags

Add, remove, or list tags on a note. Reconciles both representations:

  • Frontmatter tags: array
  • Inline #tag syntax in the body

add ensures the tag is present in the requested location(s); remove strips it. Inline #tag occurrences inside fenced code blocks are intentionally left alone.


obsidian_delete_note

Permanently delete a note. When the client supports elicit, the server requests human confirmation before issuing the DELETE and the prompt includes the file's byte size — destructive blast radius visible before the user confirms. Without elicitation, the destructiveHint annotation surfaces the operation in the host's approval flow. The output reports previousSizeInBytes (size at the moment of deletion) and currentSizeInBytes: 0.


obsidian_execute_command

Dispatch an Obsidian command-palette command by ID (discoverable via obsidian_list_commands). Behavior is command-dependent — some commands open UI, others delete files or close the vault.

Off by default. When OBSIDIAN_ENABLE_COMMANDS is unset, both obsidian_execute_command and its discovery partner obsidian_list_commands are wrapped with disabledTool() — absent from tools/list (the LLM can't invoke them) but still visible in the operator-facing manifest with a hint to enable them.


Path policy (folder-scoped permissions)

Three optional env vars gate which vault paths each tool can target. Default unset = full vault for both reads and writes — backwards compatible.

GoalConfig
Default (current behavior)all unset
Read everywhere, write only in projects/ and scratch/OBSIDIAN_WRITE_PATHS=projects/,scratch/
Read only public/, write only public/inbox/OBSIDIAN_READ_PATHS=public/, OBSIDIAN_WRITE_PATHS=public/inbox/
Read-only deployment — no writes anywhereOBSIDIAN_READ_ONLY=true

Matching is prefix-based with implicit recursion, case-insensitive, with trailing slashes normalized. projects/ matches projects/a.md, projects/sub/b.md, etc.

Write paths are implicitly readable — you can't sanely edit what you can't see. So a read passes when the target matches READ_PATHS or WRITE_PATHS.

OBSIDIAN_READ_ONLY=true short-circuits before the path checks — every write tool and the command-palette pair are wrapped with disabledTool() at startup (absent from tools/list), and any write that still reaches the service is denied at runtime regardless of WRITE_PATHS.

Denies are typed path_forbidden (JSON-RPC code Forbidden) with the active scope echoed back in data.recovery.hint and data.activeScope, so the LLM can self-correct without inspecting server logs. Search results from obsidian_search_notes are filtered against READ_PATHS silentl

View source on GitHub