Back to MCP Servers

Obsidian Server

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

obsidiannotesknowledge-basemarkdowncommunity
By Community
668104Updated 2 days 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>Read, write, search, and surgically edit Obsidian vault notes, tags, and frontmatter via MCP. STDIO or Streamable HTTP.</b> <div>14 Tools • 3 Resources</div> </p> </div> <div align="center">

Version License Docker MCP SDK npm TypeScript Bun

</div> <div align="center">

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

</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 under a vault path. Recursive walk (default depth 2, max depth 20; 1000-entry cap) with optional extension and nameRegex filters.
obsidian_list_tagsList vault tags with usage counts, including hierarchical parents. Ordered by count descending and capped at limit (default 200, max 10000), with the withheld remainder disclosed. Optional nameRegex and minCount narrow the set first.
obsidian_list_commandsList Obsidian command-palette commands, optionally filtered by nameRegex on display name. Opt-in via OBSIDIAN_ENABLE_COMMANDS=true (paired with obsidian_execute_command).
obsidian_search_notesSearch the vault by text, JSONLogic, or BM25-ranked Omnisearch (when the plugin is reachable). Results paginate via opaque cursors.
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, creates the file if missing. With section, appends to a specific heading, block, or frontmatter field (file must exist).
obsidian_patch_noteSurgical append / prepend / replace against a heading, block reference, or frontmatter field.
obsidian_replace_in_noteSearch-replace inside a single note, scoped to the body by default. Literal or regex matching with whole-word, whitespace-flexible, and case-sensitivity options; supports capture-group replacement.
obsidian_manage_frontmatterAtomic get / set / delete on a single frontmatter key.
obsidian_manage_tagsAdd, remove, or list tags. Defaults to the frontmatter tags: array; location: 'inline' or 'both' opts into mutating the note body.
obsidian_delete_notePermanently delete a note. Always asks the user to confirm first — the call is answered with a confirmation request and retried with the answer.
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

Up to 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 any other mode is rejected with path_prefix_invalid_mode).
  • jsonlogic — JSONLogic tree evaluated against path, content, frontmatter.<key>, tags, and stat.{ctime,mtime,size}; custom glob and regexp operators, both taking [PATTERN, VALUE] — pattern first, then the field reference: {"glob": ["Projects/*.md", {"var": "path"}]}. The reverse order compiles the note's own field as the pattern: glob then matches nothing, and regexp fails outright on whatever the field parses as. This is also how backlinks are expressed, since there is no dedicated tool or upstream endpoint for them: {"regexp": ["\\[\\[Target Note(\\||#|\\]\\])", {"var": "content"}]} returns every note whose body wikilinks Target Note.
  • omnisearch — BM25-ranked search via the community Omnisearch plugin. Supports quoted phrases, -exclusion, path: / ext: filters, typo tolerance, and PDF + OCR coverage (via Text Extractor). Only present in the mode enum when the plugin's HTTP server is reachable at startup; the upstream hard-caps results at 50 — narrow the query to surface more (the response carries truncated: true when the cap was likely hit).

Results paginate via opaque cursors per the MCP 2025-11-25 spec: omit cursor for the first page, then pass nextCursor from the prior response. Every result carries totalCount (post-path-policy, pre-pagination); nextCursor is omitted on the last page. 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

Heading targets accept either the full Parent::Child path or a bare leaf name. A bare leaf that matches exactly one heading is expanded to its full path before the write, and the response echoes the locator the edit landed on; a leaf matching several headings is rejected with ambiguous_section, whose error data lists the candidate paths. The same resolution applies to obsidian_write_note and obsidian_append_to_note with section.

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


obsidian_replace_in_note

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.

scope selects what the replacements run over:

  • body (default) — the text after the YAML frontmatter block. The block is re-attached from the original bytes, so it comes back byte-identical.
  • frontmatter — only the YAML between the --- fences. The fences themselves are never matched.
  • both — each replacement runs over the frontmatter and then the body; perReplacement[] reports bodyCount and frontmatterCount separately.

With the frontmatter in scope, the rewritten YAML is re-parsed before anything is written: if it no longer parses as a mapping of properties, the call fails with frontmatter_invalid and the note keeps its original bytes. That check catches YAML that breaks — an unquoted : in a scalar, a list marker rewritten into an alias, a stray quote. It cannot catch an edit that stays well-formed while meaning something else, such as a substring collisio

View source on GitHub