Skip to main content
Glama
yuchi-chang

obsidian-mcp

by yuchi-chang

obsidian-mcp

MCP server that wraps the official Obsidian CLI so an LLM agent can drive a running Obsidian instance — read/write notes, search, manage frontmatter, navigate links, run plugins, and more.

This server is a thin, comprehensive wrapper. Every tool maps 1:1 to an obsidian CLI command.

Prerequisites

  1. Obsidian must be running. The CLI talks to the live app over IPC; it does not read the vault on disk directly.

  2. Register the CLI binary. In Obsidian: Settings → General → Command line interface → Register CLI. Obsidian will add obsidian to your PATH.

  3. Verify: obsidian version prints the CLI version.

Install

Two paths depending on whether you want to build it yourself or grab a pre-published version from npm.

Option A — Clone & build (works today)

Clone the repo and build locally, then point Claude Code at the built file:

git clone https://github.com/yuchichang/obsidian-mcp.git
cd obsidian-mcp
npm install
npm run build

Register it with Claude Code (one command):

# Add (user scope — available across all projects)
claude mcp add -s user obsidian -- node /absolute/path/to/obsidian-mcp/dist/index.js

# Remove
claude mcp remove obsidian

# List configured servers
claude mcp list

-s user registers it for your whole user account. Use -s project to commit it to the repo's .mcp.json instead, or -s local for the current project only (default).

Or write it into .mcp.json manually:

{
  "mcpServers": {
    "obsidian": {
      "command": "node",
      "args": ["/absolute/path/to/obsidian-mcp/dist/index.js"]
    }
  }
}

Option B — Install from npm (zero-build)

Prerequisite: the package must already be published to npm. The maintainer publishes once via npm publish; all subsequent users get it via npx automatically. If you forked this repo and want this flow under your own scope, change name in package.json to @<your-npm-username>/obsidian-mcp, then npm publish.

Once published, no clone or build needed:

claude mcp add -s user obsidian -- npx -y @yuchichang/obsidian-mcp

Or in .mcp.json / Claude Desktop's claude_desktop_config.json:

{
  "mcpServers": {
    "obsidian": {
      "command": "npx",
      "args": ["-y", "@yuchichang/obsidian-mcp"]
    }
  }
}

Override the CLI path

If obsidian isn't on PATH, set the OBSIDIAN_CLI env var. Works with either install path:

{
  "mcpServers": {
    "obsidian": {
      "command": "node",
      "args": ["/absolute/path/to/obsidian-mcp/dist/index.js"],
      "env": {
        "OBSIDIAN_CLI": "C:/Users/you/AppData/Local/Obsidian/obsidian.cmd"
      }
    }
  }
}

Tools

Vault & files

Tool

Wraps

obsidian_list_files

obsidian files

obsidian_list_folders

obsidian folders

obsidian_read_note

obsidian read

obsidian_get_metadata

obsidian file

obsidian_create_note

obsidian create

obsidian_append_note

obsidian append

obsidian_prepend_note

obsidian prepend

obsidian_move_note

obsidian move

obsidian_delete_note

obsidian delete (permanent flag supported)

Frontmatter properties

Tool

Wraps

obsidian_get_properties

obsidian properties

obsidian_set_property

obsidian property:set

obsidian_remove_property

obsidian property:remove

Tool

Wraps

obsidian_search

obsidian search

obsidian_search_context

obsidian search:context

Tool

Wraps

obsidian_list_tags

obsidian tags

obsidian_files_with_tag

obsidian tag

obsidian_rename_tag

obsidian tags:rename

obsidian_get_links

obsidian links

obsidian_get_backlinks

obsidian backlinks

obsidian_list_unresolved

obsidian unresolved

obsidian_list_orphans

obsidian orphans

Daily notes

Tool

Wraps

obsidian_daily_read

obsidian daily:read

obsidian_daily_append

obsidian daily:append

obsidian_daily_path

obsidian daily:path

Plugins

Tool

Wraps

obsidian_list_plugins

obsidian plugins

obsidian_enable_plugin

obsidian plugin:enable

obsidian_disable_plugin

obsidian plugin:disable

obsidian_reload_plugin

obsidian plugin:reload

Developer / advanced

Tool

Wraps

Notes

obsidian_eval

obsidian eval

⚠️ Runs arbitrary JS inside Obsidian. Treat as destructive.

obsidian_dev_screenshot

obsidian dev:screenshot

Returns base64 PNG.

obsidian_dev_errors

obsidian dev:errors

obsidian_dev_console

obsidian dev:console

Meta

Tool

Wraps

obsidian_topic_stats

reports the persistent topic → folder map for a vault

obsidian_register_topic

binds a topic to a folder (no prompt)

obsidian_remove_topic

removes a topic from the persistent store

obsidian_scan_root

lists root-level notes with preview for bulk-organize

obsidian_organize_apply

validates + applies a routing plan (dry-run supported)

obsidian_version

obsidian version

obsidian_help

obsidian help

Conventions

  • Targeting a note — file-targeting tools accept either:

    • file — wikilink-style note name (e.g. "My Note"), or

    • path — vault-relative file path (e.g. "Folder/My Note.md").

  • Multi-vault setups — every tool accepts an optional vault parameter. When omitted, the most recently focused vault is used.

  • Output format — list/search/metadata tools default to JSON for easy machine parsing.

Sensitive operations & user confirmation

The following tools are gated behind a user-confirmation step:

Tool

Reason

obsidian_delete_note

Removes data (especially with permanent: true).

obsidian_move_note

Renames + rewrites wikilinks across the vault.

obsidian_remove_property

Removes frontmatter data.

obsidian_rename_tag

Bulk-rewrites tags across every note.

obsidian_enable_plugin

Grants a community plugin code execution.

obsidian_eval

Runs arbitrary JavaScript inside Obsidian.

How the gate works:

  1. MCP elicitation (preferred). If the connected client supports the elicitation capability (Claude Code does), the server sends an elicitation/create request and the client shows the user a Proceed? prompt with the action and target spelled out. Only accept + confirm: true proceeds.

  2. Explicit confirm: true parameter. Every sensitive tool's input schema includes an optional confirm: boolean. Passing confirm: true skips the elicitation prompt — use this only when the caller has already obtained user approval.

  3. Refusal fallback. If the client doesn't support elicitation and confirm: true was not provided, the tool returns an isError result that names the action and instructs the caller to retry with confirm: true.

Bypass for batch / automation

OBSIDIAN_MCP_AUTO_CONFIRM=1

Set this env var (in your MCP client's env block) to skip every confirmation prompt. Use only in fully-trusted automation contexts.

Topic → folder routing (vault-aware, persistent)

The MCP runs a per-vault topic store at ~/.obsidian-mcp/<vault>/topic-map.json. It learns where each topic of note belongs and reuses that decision next time.

Agent: create_note(path="kungpao.md", topic="recipe-chinese", vault="MyVault")
   ↓
MCP: is "recipe-chinese" already in the store?
   ├── yes → use the stored folder, increment usage, write
   └── no  → scan vault for similar folders ("Recipes/Chinese", "食譜/中式" …)
            → MCP elicits the user: "Where should 'recipe-chinese' notes live?
              Suggestions: ..."
            → user types or picks a folder
            → MCP records the route, then writes

What lives where, and why:

  • The persistent store, not env vars, is the source of truth — the MCP is the only thing that sees vault state across sessions, so storing the conventions there is what gives this layer its leverage.

  • The user is asked once per topic; subsequent notes for the same topic land silently.

  • Folders are auto-created by the Obsidian CLI as deep as needed — no mkdir from the MCP.

Resolution order

Condition

Action

1

path contains /

Used as-is, topic ignored.

2

topic present in store

Reuse stored folder, increment usage.

3

folder arg passed alongside topic

Treat as pre-decided; record in store.

4

topic unknown, client supports elicitation

Scan vault, prompt user, record answer.

5

topic unknown, no elicitation

Auto-create <topic>/ folder, record, hint at similar existing folders in the response.

6

No topic, no folder

Write at vault root.

Topic-store tools

Tool

What it does

obsidian_topic_stats

Show the learned map for a vault, sorted by usage.

obsidian_register_topic

Bind topic → folder programmatically (no elicitation).

obsidian_remove_topic

Forget a topic from the store (existing notes untouched).

Theory pointers

Faceted folder routing here is the simplest slice of a much larger idea. Worth reading if you want to push further:

  • Ranganathan, S.R. (1933) Colon Classification — PMEST facets

  • Ranganathan, S.R. (1931) Five Laws of Library Science

  • Tiago Forte (2022) Building a Second Brain — PARA method (actionability axis)

  • Niklas Luhmann (1981) "Kommunikation mit Zettelkästen" — graph-over-tree

  • Bates, M.J. (1989) "The design of browsing and berrypicking"

Bulk organize root notes

When the vault root accumulates loose .md files, a caller LLM can sweep them into the right subfolders in three steps:

  1. Scan — list root notes with metadata + body preview:

    // tool: obsidian_scan_root
    { "ignore": ["Daily/*", "*.excalidraw.md"] }
  2. Classify (caller side) — the LLM reads each preview and proposes a routing plan:

    [
      { "path": "WebRTC 連線建立流程.md", "target_folder": "webrtc", "topic": "webrtc", "reason": "covers signaling/SDP/ICE" },
      { "path": "舊筆記.md", "target_folder": "Notes", "topic": "misc" }
    ]
  3. Apply — dry-run first to preview, then call again with dry_run: false:

    // tool: obsidian_organize_apply
    { "plan": [...], "dry_run": true }
    // → { "summary": { "will_move": 2, "will_create_folders": 1, ... }, "items": [...] }
    
    { "plan": [...], "dry_run": false, "confirm": true }
    // → moves files, creates new folders as needed, registers topic→folder mappings

Per-entry failure isolation: a single move failure marks that entry status: "failed" without aborting the rest of the batch. Successful moves with a topic field are recorded in the persistent topic store, so future single-note writes for that topic auto-route.

Long content & argv limits

The Obsidian CLI does not (yet) support reading parameter values from stdin or from files — every value travels on the command line. That collides with platform limits:

Platform

Practical command-line limit

Windows (cmd.exe)

~8,191 chars total

macOS / Linux

ARG_MAX (typically 128 KB – 2 MB)

To stay safe, the server automatically chunks long writes:

Tool

Chunking strategy

obsidian_create_note

First chunk via create, remaining chunks via append.

obsidian_append_note

Sequential append calls.

obsidian_prepend_note

prepend calls in reverse order so final order is preserved.

obsidian_daily_append

Resolves the daily note path, then chunked append.

obsidian_eval

Not chunked — JS can't be split. Returns an error suggesting the script-via-note workaround.

Splits happen at line boundaries when possible; oversized single lines fall back to UTF-8-safe character boundaries. Reassembled content is byte-identical to the original.

Configure the per-call byte threshold (defaults: 6,000 on Windows, 100,000 elsewhere):

OBSIDIAN_MCP_MAX_ARG_BYTES=4000

If a chunk in the middle of a multi-chunk write fails, the server returns isError with a clear message stating which chunks made it to disk so the caller can recover.

Develop

npm run dev      # tsc --watch
npm run inspect  # launch MCP Inspector against the built server
node scripts/smoke-test.mjs   # initialize + tools/list smoke test

How it works

runObsidian() (src/exec.ts) shell-quotes arguments, invokes the obsidian binary via child_process.exec, and parses stdout. Most read-style tools request format=json; results are parsed to structuredContent for clients that consume structured tool output, while still returning a text representation in content.

Tool registry lives in src/tools.ts — adding a new wrapped command is a single entry there.

Reference

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/yuchi-chang/obsidian-mcp'

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