Skip to main content
Glama

obsidian-mcp-pro

The most feature-complete MCP server for Obsidian vaults.

⭐ Please star us on GitHub, it helps us reach more users!

Patreon Ko-fi

šŸ’™ Support this project. obsidian-mcp-pro is free and open-source. If it saves you time, consider becoming a patron for ongoing support, or tipping on Ko-fi for a one-time thanks. Patrons get release notes early, priority on bug reports, and (optionally) their name in the README.

obsidian-mcp-pro MCP server

npm version npm downloads GitHub stars License: MIT Node >= 24 CI Tool Quality

Give AI assistants deep, structured access to your Obsidian knowledge base. Read, write, search, tag, analyze links, traverse graphs, manipulate canvases, query Bases, edit by heading or block reference, run semantic search, and pull binary attachments. All through the Model Context Protocol.

41 tools, 5 prompts, 3 resources. Every tool ships with rich descriptions, typed schemas, human-readable titles, and safety annotations (readOnlyHint, destructiveHint, idempotentHint) so your agent picks the right tool, passes the right arguments, and handles results correctly. The original 23 tools earned an average 4.40/5 score and all-A grades on Glama's quality index; the 18 newer ones follow the same authoring conventions documented in docs/TOOL_AUTHORING.md.


Contents


Related MCP server: Vault Cortex Obsidian MCP Server

Features

  • Focus-ranked full-text search across all vault notes, with query-centered snippets (cached: re-runs only re-read changed files)

  • Read individual notes whole, or as a fragment by heading path, block id, or line range

  • List and filter notes by folder, date, or pattern

  • Search by frontmatter fields and values

  • Retrieve daily notes automatically using the vault's configured filename format

  • get_recent_notes orders by mtime; get_vault_stats reports counts, words, tag coverage; resolve_alias translates a display name to a real note path

Write & Modify

  • Create new notes with frontmatter and content

  • Append or prepend content to existing notes

  • Update frontmatter properties programmatically (merge: unlisted keys are preserved)

  • Move and rename notes (rewrites every wikilink, markdown link, and canvas reference across the vault by default)

  • Delete notes safely; moved to the vault's .trash folder by default, with an optional permanent flag and elicitation-based confirmation

  • Surgical edits by heading: update_section, insert_at_section, list_sections, plus single-note replace_in_note (regex with match-count guard) and edit_block for paragraphs tagged with ^id

Tags

  • Build and query a complete tag index (incremental: cached across runs)

  • Search notes by single or multiple tags

  • rename_tag rewrites both inline #tag occurrences and frontmatter tags: arrays vault-wide; hierarchical mode also rebases nested sub-tags (project/alpha follows project)

  • Get backlinks (what links to a note)

  • Get outlinks (what a note links to)

  • Find orphan notes with no inbound or outbound links

  • Detect broken links pointing to non-existent notes

  • Traverse graph neighbors to a configurable depth

Canvas

  • Read .canvas files with full node and edge data

  • Add new nodes (text, file, link, group) to canvases

  • Add edges between canvas nodes

  • List all canvases in the vault

Bases (Obsidian 1.10+)

  • list_bases enumerates .base files

  • read_base returns the parsed YAML (filters, properties, views)

  • query_base runs the filter DSL against the vault and returns matching notes; supports taggedWith(), file.hasTag(), file.inFolder(), comparison operators, and and/or/not combinators. Unsupported filters warn and fail closed instead of broadening results.

Attachments

  • list_attachments enumerates every non-md/canvas/base file with a per-extension count summary

  • find_unused_attachments flags assets no note references via embeds or markdown links; optional reclaimable-bytes report

  • get_attachment returns image / audio / blob bytes inline as MCP content blocks (5 MB default cap, 50 MB hard cap)

Semantic Search (optional, Ollama or OpenAI)

  • index_vault chunks each note (heading-aware), embeds via the configured provider, persists vectors to <vault>/.obsidian/cache/, and incrementally re-embeds only changed notes

  • Because indexing sends readable note chunks to the configured embedding provider, index_vault requires confirm: "send-vault-text-to-embedding-provider" on each call

  • search_semantic ranks notes by cosine similarity against an embedded query

  • find_similar_notes reuses an existing note's embeddings, with source-topic anchoring, to surface neighbors without a live API call

  • Stored snippets are returned only while the current note content hash still matches the indexed text; stale chunks are pruned on search

MCP Resources

  • obsidian://note/{path} reads any note by its vault-relative path

  • obsidian://tags retrieves the full tag index as JSON

  • obsidian://daily gets today's daily note content

MCP Prompts

The server exposes five starter prompts that clients (Claude Desktop, Cursor) surface in their slash-command palette:

  • daily-review walks today's daily note, surfaces unchecked tasks, and proposes follow-ups

  • weekly-rollup aggregates the last seven daily notes into themes / decisions / open tasks

  • find-stale-notes locates untouched notes and clusters them as orphaned vs. broken-linked vs. still-linked

  • extract-action-items pulls all - [ ] … lines from a note (or every note matching a tag) into a checklist

  • build-moc generates a Map of Content (MOC) for a tag or folder

Operational features

  • Folder-scoped permissions: OBSIDIAN_READ_PATHS / OBSIDIAN_WRITE_PATHS allowlists gate every tool at the path-resolution choke point

  • In-memory note cache speeds repeated vault scans within a running process without persisting note bodies to disk

  • Progress notifications (notifications/progress) on rename_tag, find_unused_attachments, and index_vault when the client subscribes via _meta.progressToken

  • Hard bulk-write latches require confirmPath for default move_note reference rewrites, confirmTag for non-dry-run rename_tag, and confirm=true for permanent deletion; clients with elicitation support also get typed prompts


Quick Start

npx installs the latest published npm release. This README follows the source branch and may describe merged changes that have not been published. See the 4.1.0 release notes for verified publication and consumer dependency requirements.

Using Obsidian? There's also an Obsidian plugin that runs this server inside the app with a ribbon toggle and settings UI — no config-file editing. Recommended for most users.

One-Command Install (Claude Desktop / Cursor)

npx -y obsidian-mcp-pro install

This merges an entry into your claude_desktop_config.json (or ~/.cursor/mcp.json with --client=cursor), backs up the previous file, and prints next steps. Works on macOS, Windows, and Linux.

Pin a specific vault:

npx -y obsidian-mcp-pro install --vault /path/to/your/vault

Manual Claude Desktop Config

Add this to your Claude Desktop configuration file (claude_desktop_config.json):

{
  "mcpServers": {
    "obsidian": {
      "command": "npx",
      "args": ["-y", "obsidian-mcp-pro"]
    }
  }
}

If you have multiple vaults, specify which one:

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

Claude Code

claude mcp add obsidian-mcp-pro -- npx -y obsidian-mcp-pro

HTTP Transport (Remote Clients, Cursor, ChatGPT, Web)

MCP_HTTP_TOKEN=your-secret npx -y obsidian-mcp-pro --transport=http --port=3333

Endpoint: http://127.0.0.1:3333/mcp (Streamable HTTP). HTTP transport requires a bearer token:

MCP_HTTP_TOKEN=your-secret npx -y obsidian-mcp-pro --transport=http

The HTTP server binds to 127.0.0.1 by default with DNS rebinding protection enabled. IPv6 bind literals use canonical URL spelling and brackets in returned and logged URLs and the derived Host allowlist entry. For example, host: "::1" returns http://[::1]:port/mcp, and host: "::ffff:127.0.0.1" uses [::ffff:7f00:1]; handle.host remains the original bind address. Explicit allowedHosts entries retain their exact configured spelling. Scoped IPv6 bind addresses such as fe80::1%eth0 are unsupported by the HTTP URL parser and reject before the server starts listening. Startup always requires MCP_HTTP_TOKEN, including loopback-only local servers. The old --token flag was removed because command-line secrets can be exposed through OS process listings.

The following embedding option is available in core 4.1.0 and newer. When embedding the library, set allowedHosts to accept additional destination addresses, such as the hostname forwarded by an HTTPS reverse proxy:

import { buildMcpServer, startHttpServer } from "obsidian-mcp-pro";

const vaultPath = process.env.OBSIDIAN_VAULT_PATH!;
const server = await startHttpServer({
  host: "127.0.0.1",
  port: 3333,
  bearerToken: process.env.MCP_HTTP_TOKEN!,
  allowedHosts: ["vault.example.com", "192.0.2.10:3333"],
  buildMcpServer: () => buildMcpServer(vaultPath),
  installSignalHandlers: false,
});

// On application shutdown:
await server.stop();

startHttpServer() resolves after the socket starts listening. Validation errors, invalid listen arguments, and bind failures such as EADDRINUSE reject its Promise, so an embedder can catch the error and retry. These startup failures create no session sweeper or signal handlers. A successful handle supports repeated stop() calls.

  • Entries match the HTTP Host header exactly and case-sensitively, including a port when present. Use vault.example.com:443 if the proxy forwards that value instead of vault.example.com. IPv6 must keep brackets ([::1]:port). There is no IDN / punycode normalization. Do not include schemes, paths, or wildcards; "*" is rejected at startup (it is not a wildcard).

  • Configured entries extend the bound-address and loopback defaults. Omitting allowedHosts or passing [] preserves the defaults.

  • The server copies and validates the list at startup. Restart it to apply configuration changes. Listen-time logs include the effective Host allowlist (never the bearer token).

  • /health and /version skip Host validation (pre-existing). /mcp rejects a disallowed Host with 403 and a warn log, including when the bearer is valid.

  • This controls destination addresses, not client identity. Bearer authentication and Origin validation still apply; DNS-rebinding protection remains enabled.

  • This is an embedding API option, not a CLI flag.

The Role API below is available in core 4.1.0 and newer. HTTP embedders can assign each token a named Role containing an explicit Tool list. Roles filter tool, resource, and prompt discovery and calls. Permission changes close affected sessions immediately. See HTTP Roles for configuration, legacy-token migration, audit events, and the remaining plugin integration work.

WARNING

Never bind --host=0.0.0.0 directly to the public internet. Doing so exposes your entire Obsidian vault to anyone who can reach the port. The server refuses HTTP startup without a bearer token, but if you need remote access:

  • Put the server behind a reverse proxy (nginx, Caddy, Cloudflare Tunnel) that terminates TLS, and

  • Set MCP_HTTP_TOKEN, and

  • Restrict --allow-origin to the specific origins you trust, and

  • Set --rate-limit to cap request volume per IP.

For local-only setups (same machine / VPN / SSH tunnel), keep the default 127.0.0.1 bind.

Additional hardening flags:

Flag

Purpose

--allow-origin=<csv>

Restrict CORS to an allowlist (e.g. https://claude.ai,https://chat.openai.com). Default is localhost-only; * requires bearer auth.

--rate-limit=<n>

Cap requests per minute per client IP. /health and /version are exempt. Default is unlimited.

Operational endpoints (no auth required):

Endpoint

Returns

GET /health

{ status: "ok", version: <string> } — liveness for monitoring.

GET /version

{ version: <string> } — package version, for rollout auditing.

Structured logging is controlled by LOG_LEVEL (debug/info/warn/error/silent, default info) and LOG_FORMAT (text/json, default text). All logs go to stderr so the stdio transport on stdout is never polluted, with absolute paths, vault-relative path fields, secret-bearing URLs, and control characters redacted before write.


Usage

Once installed, the server exposes 41 tools, 5 prompts, and 3 resources to whichever MCP client you configured. Below is a transcript of a Claude Desktop conversation showing what day-to-day use looks like.

You: Find notes tagged #project I touched this week, then summarize the status of each and append today's date under a "Last reviewed" heading in each one.

Claude picks the right tools in order, with no guidance beyond the prompt:

šŸ”§ search_by_tag(tag="project", includeContent=false)
  → 7 notes: "Atlas/overview.md", "Atlas/milestones.md", "Beacon/spec.md", ...

šŸ”§ list_notes(folder="Atlas", limit=100)
  → filters to recent modifications via get_note stats

šŸ”§ get_note(path="Atlas/overview.md")
  → reads content + frontmatter + tags

šŸ”§ get_note(path="Atlas/milestones.md")
  → ...

šŸ”§ append_to_note(
    path="Atlas/overview.md",
    content="\n## Last reviewed\n2026-04-21 — on track; waiting on design review."
  )
  → ok

šŸ”§ append_to_note(
    path="Atlas/milestones.md",
    content="\n## Last reviewed\n2026-04-21 — Q2 milestone at risk, see spec.md."
  )
  → ok

Claude: I reviewed 7 project notes tagged #project. Atlas is on track pending design review; the Q2 milestone is at risk — details in milestones.md. Beacon's spec is complete but has 3 open questions in the frontmatter. I appended a "Last reviewed" section to each.

Other things the tool set lets agents do without hand-holding:

  • Research a topic across the vault. search_notes → get_outlinks → get_graph_neighbors walks the graph to depth=2 and surfaces related notes the user may have forgotten.

  • Clean up dangling references after a rename. move_note → find_broken_links returns every wikilink that now points nowhere, with source note and line number.

  • Maintain a daily log. get_daily_note reads today's note (using the vault's configured date format) and append_to_note adds the new entry — daily-note plugin config is honored, no manual date formatting.

  • Canvas editing. read_canvas → agent reasons about the node graph → add_canvas_node + add_canvas_edge lays out new ideas on an existing board.

With updateLinks=true, move_note also preserves self-links and outgoing targets inside the moved note. Explicit relative Markdown links and embeds retain their original targets after a folder change. Move link behavior describes resolution and partial failures.

Tool descriptions + typed schemas + safety hints (readOnlyHint, destructiveHint) are what make this work reliably — the agent knows delete_note is destructive and asks first, knows search_notes is free to call speculatively, and knows the expected shape of every argument.


Configuration

The server locates your vault using the following priority:

Priority

Method

Description

1

OBSIDIAN_VAULT_PATH

Environment variable with the absolute path to your vault

2

OBSIDIAN_VAULT_NAME

Environment variable to select a vault by folder name when multiple vaults exist

3

Auto-detection

Reads Obsidian's global config (obsidian.json) and uses the first valid vault found

Auto-detection works on macOS, Windows, and Linux by reading the platform-specific Obsidian configuration directory.

Environment Variables

Every setting is optional unless noted. The sections below explain the grouped ones in depth.

Variable

Purpose

Default

OBSIDIAN_VAULT_PATH

Absolute path to the vault (highest-priority vault selector).

auto-detect

OBSIDIAN_VAULT_NAME

Select a vault by folder name when several exist.

first valid vault

OBSIDIAN_READ_PATHS

Comma/colon list of folders read tools may access (see Folder-Scoped Permissions).

unrestricted

OBSIDIAN_WRITE_PATHS

Same shape, for mutating tools.

unrestricted

OBSIDIAN_CACHE_DISABLED

Set 1, true, or yes to disable semantic embedding persistence (see Caches). The in-memory note cache remains active.

enabled

OBSIDIAN_EMBEDDING_PROVIDER

Semantic-search provider, e.g. openai (see Semantic Search Provider).

unset (disabled)

OBSIDIAN_EMBEDDING_MODEL

Embedding model name for the chosen provider.

provider default

OBSIDIAN_EMBEDDING_URL

Override the embedding endpoint base URL.

provider default

OBSIDIAN_EMBEDDING_API_KEY

API key for the embedding provider.

falls back to OPENAI_API_KEY

OPENAI_API_KEY

Used as the embedding key when OBSIDIAN_EMBEDDING_API_KEY is unset.

unset

MCP_HTTP_TOKEN

Bearer token required by the HTTP transport (see HTTP Transport).

unset

LOG_LEVEL

Logger verbosity: debug, info, warn, error.

info

LOG_FORMAT

Log output format: text or json.

text

Daily-Note Filename Format

get_daily_note, create_daily_note, and the obsidian://daily resource render the note path using your vault's .obsidian/daily-notes.json format string. Moment.js-style tokens are supported:

Token

Example

Token

Example

YYYY

2026

dddd

Thursday

YY

26

ddd

Thu

MMMM

April

dd

Th

MMM

Apr

HH / H

05 / 5

MM / M

04 / 4

hh / h

05 / 5

DD / D

09 / 9

mm / m

07 / 7

Do

9th

ss / s

03 / 3

DDDD / DDD

099 / 99

Q

2

[literal]

renders the bracket contents verbatim, e.g. YYYY-[Q]Q → 2026-Q2

Unrecognized tokens pass through unchanged. Local time is used (matching Obsidian's rendering).

Folder-Scoped Permissions

Restrict the tools' read/write surface to specific folders without exposing the rest of the vault:

Env var

Purpose

OBSIDIAN_READ_PATHS

Comma- or colon-separated list of folders that read tools may access. Unset means unrestricted. Use . to mean the vault root.

OBSIDIAN_WRITE_PATHS

Same shape, but for mutations (create / append / update / delete / move / surgical edits).

Read and write are independent, so an audit account can be read-only on most of the vault but write-only to a Drafts/ folder. Moving a note requires read access to the source and write access to the destination, because the move carries the source content into its new folder. Surgical edits (replace_in_note, update_section, insert_at_section, and edit_block) require read access to the target plus write access because they inspect existing content before writing. In-vault symlinks are checked against their real target before the allowlist decision is finalized. The startup log line and --help advertise the active scope. The allowlist is enforced at a single path-resolution choke point so every tool inherits it.

append_to_note, prepend_to_note, and update_frontmatter can modify write-only notes without granting retrieval access. Their internal read preserves existing content. Invalid YAML errors report a fixed category and a one-based line and column in the note, without source excerpts or parser-provided reasons. Oversized frontmatter errors report the size limit. These errors leave the note unchanged and do not include existing note text in responses or logs.

Caches

The note-content cache is in-memory only. It speeds repeated vault-wide scans inside the running process, but it does not persist note bodies or absolute paths to disk. Any legacy <vault>/.obsidian/cache/mcp-pro-index-cache.json snapshot is ignored and removed on first cache use.

Batch reads reuse content only when the validated file's size, mtime, ctime, device, and inode match the cached signature at the same requested path. A size change or replacement therefore invalidates an entry even when mtime is restored. Filesystems can reuse identities or preserve metadata, and a file can change after validation. This signature is not content validation. Direct point reads remain uncached; use a direct read and validate the bytes when exact content freshness is required.

Semantic embeddings live under <vault>/.obsidian/cache/:

File

Purpose

mcp-pro-embeddings.json

Persisted embeddings for semantic search (only present once index_vault has run). Vault-relocation safe via an embedded vaultRoot check; switching providers/models or stale note content invalidates entries automatically.

The embedding cache is vault-local, excluded from vault scans (.obsidian/ is pruned), and can be deleted at any time. Persistence can be turned off with OBSIDIAN_CACHE_DISABLED=1.

Semantic Search Provider

The semantic-search tools (index_vault, search_semantic, find_similar_notes) need an embedding provider. Configure via env:

Env var

Default

Notes

OBSIDIAN_EMBEDDING_PROVIDER

ollama if unset or blank

ollama, openai, or none to disable.

OBSIDIAN_EMBEDDING_MODEL

nomic-embed-text (Ollama), text-embedding-3-small (OpenAI) if unset or blank

Provider-specific model identifier.

OBSIDIAN_EMBEDDING_URL

http://localhost:11434 (Ollama), https://api.openai.com/v1 (OpenAI) if unset or blank

Base URL without credentials, query strings, or fragments.

OBSIDIAN_EMBEDDING_API_KEY

OPENAI_API_KEY falls back if unset or blank

Required for hosted providers.

For local Ollama: install Ollama, then ollama pull nomic-embed-text. The semantic tools register even when no provider is configured, so they're discoverable; calls return a configuration hint until set up.

index_vault sends readable note chunks to that provider. Calls must include:

{ "confirm": "send-vault-text-to-embedding-provider" }

This latch is per call so scripted index refreshes have to keep the privacy decision visible.

Observability

Logs stream to stderr as either plain text (default) or single-line JSON, controlled by LOG_LEVEL (debug/info/warn/error/silent) and LOG_FORMAT (text/json). Local stderr and MCP-forwarded log payloads both redact absolute paths, vault-relative path fields, secret-bearing URLs, and control characters.

The server also declares the MCP logging capability, so every log line is forwarded to the connected client as a notifications/message frame alongside tool responses. Clients that honor logging/setLevel can filter server-side logs at runtime without restarting. Claude Desktop surfaces these in its MCP DevTools pane; most other clients currently ignore them, so this is useful primarily for self-hosters and tooling authors.


Security

Tool calls over HTTP and stdio are limited to 16 object keys and array entries, counted recursively before validation. The largest current tool takes eight scalar fields. String length does not consume this element budget, so existing content and frontmatter string limits still apply. Over-budget calls return a tool error before dispatch. HTTP requests also remain subject to the 4 MiB body cap, including JSON framing and escaping; schema-valid text may need smaller chunks. See the dependency security review for the budget rationale and separate transport limits.

  • Vault boundary — every tool and resource routes through a single path resolver that rejects .. traversal, null-byte injection, and symlinks pointing outside the vault (ancestor-realpath check). On Windows it also rejects reserved device names, alternate data stream syntax, and trailing-dot/space filename aliases.

  • Note/file boundary — note read and edit surfaces only target .md files; attachments, Canvas files, and Bases stay on their dedicated tools with their own caps and parsers.

  • Full-note cap — full .md note reads and read-modify-write helpers refuse notes over 5 MiB before materializing them; get_note line fragments still stream from large notes for targeted inspection.

  • Excluded directories — .obsidian, .git, and .trash are pruned at traversal time and denied on both requested paths and canonical targets. File symlinks and directory aliases into these directories are denied, including nested occurrences. Public note reads, including read-modify-write operations, require canonical file targets ending in .md. Directory aliases remain valid for folder operations even when their names end in .md. Reads through aliases to permitted note targets remain supported. Internal reads of configuration and cache files use a separate vault-boundary check.

  • HTTP transport — requires a bearer token at startup and binds to 127.0.0.1 by default with DNS rebinding protection (host-header allowlist). /mcp requests must send Authorization: Bearer <secret>, compared in constant time. Malformed request URL or Host data is rejected before routing.

  • Attachment safety — executable extensions are blocked, SVGs are returned as text/plain, and active text formats such as HTML/XML/CSS are served with text/plain resource metadata.

  • Canvas link safety — Canvas link nodes added through the server accept only absolute http:// and https:// URLs, preventing local file and application-protocol links from being persisted by tool calls.

  • Regex edit safety — replace_in_note executes compilation, counting, and replacement in a terminable worker with a 1,000 ms deadline that includes worker startup. It retains input caps and early pattern rejection, limits output to 5 MiB of UTF-8, and leaves the note unchanged on timeout or worker failure. Literal mode uses the same worker while preserving literal dollar tokens. See Bounded note replacement.

  • Error and log sanitization — filesystem error messages are stripped of absolute host paths before being returned to MCP clients, local stderr and MCP-forwarded logs redact paths and secret-bearing URLs, ASCII control bytes and Unicode bidi controls are escaped in displayed values, and note-derived duplicate-alias text is not copied into graph warnings. Uncaught HTTP errors respond with a generic Internal server error body while server logs keep sanitized diagnostics.

  • Untrusted vault text boundaries — tool outputs that include note bodies, read, search result, semantic result, semantic index failure, write rewrite-warning, link-graph, attachment, Base, and Canvas path summaries, attachment extension summaries, search snippets, semantic snippets and heading paths, tag lists, tag search result paths and previews, tag rename skipped-note rows, frontmatter values, Base data, wikilink targets in link-analysis output, SVG attachment text, section headings, Canvas node identities, colors, previews, edge endpoints, and edge labels, or backlink context wrap those vault-authored portions in [BEGIN UNTRUSTED VAULT CONTENT: ...] / [END UNTRUSTED VAULT CONTENT: ...] markers before they enter an MCP client's model context. Note and daily resources use the same visible boundaries for markdown bodies and carry _meta["obsidian-mcp-pro/contentTrust"] = "untrusted-vault-content".

  • YAML parser boundaries — Obsidian properties are parsed only from --- YAML delimiter lines; non-YAML gray-matter language blocks stay as body text, oversized frontmatter is skipped on reads and refused for metadata updates, and note/Base YAML containing anchors or aliases is not parsed.

  • Semantic index freshness — search_semantic and find_similar_notes re-check current note content hashes before returning stored snippets or source-note embeddings, pruning stale cache entries instead of surfacing old note text.

  • Semantic indexing confirmation — index_vault refuses to read and embed vault notes unless the call includes confirm: "send-vault-text-to-embedding-provider", making provider-bound note transfer explicit.

  • Bulk-write confirmations — move_note reference rewrites require confirmPath to match the destination path, and non-dry-run rename_tag requires confirmTag to match the new tag. Clients that support MCP elicitation are also asked to re-type the destination path or new tag before the rewrite runs.

  • Interactive confirmation failures — permanent deletion, link-rewriting moves, and tag renames abort if an attempted form confirmation fails, times out, or is cancelled. A late response cannot authorize a cancelled tool request. Clients without form elicitation, including URL-only clients, use the required caller-supplied confirm, confirmPath, or confirmTag latch as the explicit noninteractive fallback. See Destructive confirmation policy.

  • Atomic writes — every note write (create_note, append, prepend, update_frontmatter, canvas mutations) stages content to a sibling temp file then renames onto the target, so a crash or kill mid-write never leaves a truncated file. Per-path locks serialize cooperating callers in this process. Note transforms, appends, and prepends also reject observed external changes before replacement. This is optimistic conflict detection with a remaining check-to-rename window. See Note update conflicts. Existing POSIX file modes survive replacement; new notes use 0666 & ~umask, and staging starts without group or other access. See File permissions for the policy and platform limits. The install subcommand uses the same staging pattern and keeps a backup of the previous config.

  • Rate limiting + CORS allowlist — optional --rate-limit caps per-IP request volume; --allow-origin restricts browser-facing CORS and refuses * unless bearer auth is enabled. /health and /version stay reachable under load for monitoring.

  • Request timeout — HTTP POST requests are capped at 2 minutes of wall-clock time. Long-lived SSE GET streams are exempt so idle clients aren't reaped.

  • Process supervision — uncaughtException exits cleanly so systemd/Docker/npx supervisors can restart; unhandledRejection logs but doesn't kill the process.


[[Target]] resolves in the same order Obsidian does:

  1. Exact relative-path match (case-insensitive).

  2. Path-suffix match (e.g. [[projects/foo]] picks work/projects/foo.md).

  3. Basename match. When multiple notes share a basename, the one that shares the deepest directory prefix with the linking note wins; ties break on shortest overall path.

  4. Frontmatter aliases — [[Display Name]] resolves to a note whose frontmatter declares that alias. aliases, Aliases, and ALIASES are all recognized.

Tag extraction is similarly case-tolerant: tags, Tags, TAGS, tag, and Tag frontmatter keys are all read.


Tool Reference

Read

Tool

Description

Key Parameters

search_notes

Focus-ranked full-text search with query-centered snippets (cached)

query, caseSensitive, maxResults, folder

get_note

Read a note whole, or by section / block / lines

path, section, block, lines

list_notes

List notes in the vault or a folder

folder, limit

get_daily_note

Get today's (or a specific date's) daily note

date

search_by_frontmatter

Find notes by frontmatter property values with case-insensitive key/value matching

property, value, folder

get_recent_notes

Notes sorted by mtime; optional ISO-or-relative since filter

limit, since, folder

get_vault_stats

Vault counts, bytes, words, tag coverage, most-recent note

folder

resolve_alias

Translate frontmatter alias (or basename) to note path

name, includeBasename

Write

Tool

Description

Key Parameters

create_note

Create a new note with content and frontmatter

path, content, frontmatter

append_to_note

Append content to an existing note

path, content, ensureNewline

prepend_to_note

Prepend content after frontmatter

path, content

update_frontmatter

Update frontmatter properties on a note

path, properties

create_daily_note

Create today's daily note from template

date, content, templatePath

move_note

Move or rename a note; rewrites references across the vault

oldPath, newPath, updateLinks, confirmPath

delete_note

Delete a note (trash by default); optional elicitation on permanent

path, permanent, removeReferences

Section-level edits

Tool

Description

Key Parameters

update_section

Replace the body under a heading path (heading kept)

path, section, newBody

insert_at_section

Insert at before / after-heading / append of a section

path, section, content, position

list_sections

Return the heading outline of a note as an indented tree

path

replace_in_note

Find/replace within one note (literal or regex, with match-count guard)

path, find, replace, regex, flags, expectedCount

edit_block

Replace content of a paragraph tagged ^id (anchor preserved)

path, block, newContent

Tags

Tool

Description

Key Parameters

get_tags

Get all tags and their usage counts

sortBy

search_by_tag

Find all notes with a specific tag

tag, includeContent

rename_tag

Rewrite inline + frontmatter occurrences vault-wide; hierarchical

oldName, newName, hierarchical, dryRun, confirmTag

Tool

Description

Key Parameters

get_backlinks

Get all notes that link to a given note

path

get_outlinks

Get all links from a given note

path

find_orphans

Find notes with no links in or out

includeOutlinksCheck

find_broken_links

Detect links pointing to non-existent notes

folder

get_graph_neighbors

Get notes connected within N link hops

path, depth, direction

Canvas

Tool

Description

Key Parameters

list_canvases

List all .canvas files in the vault

(none)

read_canvas

Read a bounded .canvas node/edge summary

path

add_canvas_node

Add a node to a canvas

canvasPath, type, content, x, y

add_canvas_edge

Add an edge between two canvas nodes

canvasPath, fromNode, toNode

Bases

Tool

Description

Key Parameters

list_bases

Enumerate .base files in the vault

(none)

read_base

Parse a Base file (filters, properties, views)

path

query_base

Run a Base's filter DSL against the vault; unsupported filters warn and fail closed

path, view, limit, includeFrontmatter

Attachments

Tool

Description

Key Parameters

list_attachments

Enumerate every non-md/canvas/base file

extension, limit

find_unused_attachments

Attachments not referenced via embeds or markdown links

limit, includeBytes

get_attachment

Return image / audio / blob content (5 MB default cap)

path, maxBytes

Tool

Description

Key Parameters

index_vault

Build / refresh the embedding index (incremental, progress events)

force, folder, confirm

search_semantic

Cosine search the embedding index for a natural-language query

query, limit, folder, includeSnippet

find_similar_notes

Surface notes most similar to a source note, anchored to its opening topic (no live API call)

path, limit


MCP Resources

Resources provide a URI-based way to access vault data:

Resource URI

Description

obsidian://note/{path}

Read any note by its vault-relative path

obsidian://tags

Full tag index with file lists (JSON)

obsidian://daily

Today's daily note content


Troubleshooting

Tools Don't Show Up in Claude Desktop

MCP clients only re-read their config on startup. After editing claude_desktop_config.json (or running npx obsidian-mcp-pro install), fully quit Claude Desktop (⌘Q on macOS, tray → Quit on Windows) and relaunch. Hot-reloading the window is not enough.

"No Obsidian vault configured" on Startup

The server couldn't locate a vault. Resolution order is:

  1. OBSIDIAN_VAULT_PATH env var (absolute path) — always wins if set. An invalid selection fails startup instead of falling back to another vault.

  2. OBSIDIAN_VAULT_NAME env var — picks a named vault from Obsidian's global config.

  3. Auto-detection — reads obsidian.json (platform-specific) and uses the first valid vault found.

Fastest fix: set OBSIDIAN_VAULT_PATH in the env block of your MCP client's config. Auto-detection fails when Obsidian has never been launched, obsidian.json is missing/corrupt, or all registered vaults resolve to paths that no longer exist.

Both startup and install --vault require an accessible vault directory containing an accessible .obsidian directory. An ordinary folder or a file named .obsidian does not qualify. Correct an invalid explicit path, or unset OBSIDIAN_VAULT_PATH to enable auto-detection.

"Path traversal detected" Error on Tool Calls

All tool paths must be vault-relative (e.g. notes/hello.md), never absolute (/Users/me/vault/notes/hello.md) or containing ... The agent normally gets this right — if you see this error, check whether a custom instruction is asking it to use absolute paths.

HTTP Transport Returns 401 Unauthorized

The server was started with MCP_HTTP_TOKEN but the client isn't sending a matching Authorization: Bearer <secret> header. Verify the token value and that the header is present — comparison is case-sensitive and constant-time.

HTTP Transport Returns 429 Too Many Requests

--rate-limit=<n> is set and the client exceeded N requests in the last 60 seconds from that IP. Either raise the limit, drop it, or wait 60 seconds. /health and /version are exempt if you need to check liveness under load.

Daily-Note Path Is Wrong or Unresolved

The server reads .obsidian/daily-notes.json from the vault for the filename format and folder. If that file doesn't exist (the Daily Notes core plugin has never been configured), the server falls back to YYYY-MM-DD.md in the vault root. Configure the plugin once inside Obsidian and the server picks it up automatically.

npx obsidian-mcp-pro Silently Exits With Code 0

This was a bug in versions < 1.4.1 where the npx-symlinked CLI entry failed to detect itself as the entrypoint. Upgrade: npx -y obsidian-mcp-pro@latest install.

Windows: "EPERM: operation not permitted" During Writes

The server retries these transparently (Windows holds stricter file-sharing locks than POSIX) — if you still see the error, it usually means antivirus or a sync client (OneDrive, Dropbox) is holding the file. Exclude the vault folder from real-time antivirus scanning, or pause the sync client during heavy agent sessions.


Development

# Clone the repository
git clone https://github.com/rps321321/obsidian-mcp-pro.git
cd obsidian-mcp-pro

# Install dependencies
npm install

# Build
npm run build

# Run the full local maintenance gate
npm run verify

# Run in development (watch mode)
npm run dev

# Start the server locally
OBSIDIAN_VAULT_PATH=/path/to/vault npm start

GitHub Actions workflows are manual-only in this repository. Use npm run verify as the required local gate before merging or publishing.

Commits run a local Husky hook that rejects maintainer-only paths, formats staged files, then runs the type-checker and test suite. Run npm ci after cloning to install the hook.

Project Structure

src/
  index.ts                # Server entry, CLI parser, resource + prompt registration
  config.ts               # Vault detection, daily-notes config loader
  http-server.ts          # Streamable HTTP transport, Bearer auth, session TTL
  install.ts              # `install` subcommand (Claude Desktop / Cursor)
  types.ts                # Shared TypeScript interfaces
  lib/
    vault.ts              # Core vault ops (read, search, list, per-file locks,
                          # symlink boundary, canvas + base + attachment round-trip)
    permissions.ts        # OBSIDIAN_READ_PATHS / OBSIDIAN_WRITE_PATHS allowlist
    markdown.ts           # Frontmatter, wikilinks, tags, alias-aware resolver
    sections.ts           # Heading parser, block-id parser, section bounds
    tag-rewriter.ts       # Vault-wide tag rewriting (inline + frontmatter)
    link-rewriter.ts      # Plan/apply edit pipeline used by move + delete
    bases.ts              # Bases YAML parser + filter DSL evaluator
    chunker.ts            # Heading-aware chunking for embeddings
    embedding-providers.ts# Ollama + OpenAI providers
    embedding-store.ts    # Persistent vector index, cosine search
    index-cache.ts        # file-signature content cache (in-memory)
    progress.ts           # MCP progress-notification helper
    mime.ts               # extension -> MIME map for attachments
    dates.ts              # Moment-style date format for daily-note filenames
    errors.ts             # sanitizeError: strips absolute paths from fs errors
    concurrency.ts        # Bounded-concurrency fan-out helper
    logger.ts             # Leveled stderr logger (text + JSON modes)
  tools/
    read.ts               # search, get, list, daily, frontmatter, recent, stats, alias
    write.ts              # create, append, prepend, update_frontmatter, move, delete
    sections.ts           # update_section, insert_at_section, list_sections,
                          # replace_in_note, edit_block
    tags.ts               # get_tags, search_by_tag, rename_tag
    links.ts              # backlinks, outlinks, orphans, broken, graph_neighbors
    canvas.ts             # list, read, add_node, add_edge
    bases.ts              # list_bases, read_base, query_base
    attachments.ts        # list_attachments, find_unused_attachments, get_attachment
    semantic.ts           # index_vault, search_semantic, find_similar_notes
    prompts.ts            # daily-review, weekly-rollup, find-stale-notes,
                          # extract-action-items, build-moc
  __tests__/
    vault.test.ts            markdown.test.ts            tools.test.ts
    security.test.ts         http-server.test.ts         semantics.test.ts
    logger.test.ts           sections.test.ts            tag-rewriter.test.ts
    bases.test.ts            permissions.test.ts         index-cache.test.ts
    chunker.test.ts          embedding-store.test.ts     errors.test.ts
    link-rewriter.test.ts
    handlers/
      read.test.ts          write.test.ts          tags.test.ts
      links.test.ts         canvas.test.ts         attachments.test.ts
      semantic.test.ts      harness.ts

Testing

npm test

Run node scripts/check-packed-consumer.mjs to build, pack and install the core into a fresh consumer, then exercise its installed CLI, library and HTTP sessions against a temporary vault. See installed package acceptance for platform coverage and limits.

The suite covers vault operations, concurrent writes, Markdown parsing, link and tag rewriting, attachment classification, semantic search and embedding persistence, cache freshness, HTTP transport, and security regressions. Handler tests exercise tools through a real MCP client/server pair via InMemoryTransport. See CI runs for results tied to a commit; the badge above reports the master workflow state.

npm run lint       # eslint v9 + typescript-eslint v8 (flat config)
npm run lint:fix   # auto-fix

What's New

v1.8.2 rolls in a deeper-dive audit pass on top of 1.8.1:

  • rename_tag and other vault-wide bulk writers now hold the same rewrite lock as move_note / delete_note. Closes a cross-tool TOCTOU where running tag-rename concurrently with a move could surface "content changed during move" failures and leave stale links.

  • applyRewrites retries failed edits via content search. When bytes shift between plan and apply (Obsidian sync, text editor, concurrent tool), the apply step now finds the unique expected substring at its new position and splices there. If ambiguous or missing, the failure is still surfaced rather than corrupting the file.

  • planMoveRewrites and planDeleteRewrites now read each note exactly once. The previous two-pass implementation doubled I/O on rename / delete operations across large vaults.

  • CommonMark fenced-code indentation now matches the spec. Lines with more than 3 leading spaces no longer falsely close a fenced block (and don't expose subsequent content to wikilink rewriting).

  • /health no longer leaks the live session count when a Bearer token is configured. Status + version stay public for monitoring; sessions is dropped in authenticated deployments. In 4.0.0 and later, HTTP always starts with bearer auth, so the field stays hidden.

  • constantTimeEqual is now fully length-safe (pads both inputs to a fixed width before comparing), and the regex / parser hardening list also closed: resolveWikilink proximity tie-break for path-suffix matches, escaped ] in markdown link labels, control- character validation on runInstall.vaultName, backup-path hint on install write failure, and mapConcurrent return-value usage in the canvas planner.

  • npm audit clean. Resolved 4 moderate-severity advisories in transitive devDependencies. Production deps were already clean.

v1.8.1 was a security and correctness patch on top of 1.8.0:

  • CRITICAL: permission allowlist bypass via .. segments closed. v1.8.0 evaluated OBSIDIAN_READ_PATHS / OBSIDIAN_WRITE_PATHS against the raw user-supplied path before path.resolve collapsed ... An input like Allowed/../OtherFolder/note.md slipped past the prefix check. assertAllowed now collapses .. via path.posix.normalize and rejects any path that climbs above its starting point. Six regression tests cover the bypass classes.

  • HIGH: HTTP timeout on every embedding-provider fetch (AbortSignal.timeout(30s)), TOCTOU race in rename_tag closed by moving the rewrite inside updateNote's transform, and search_semantic / find_similar_notes now invalidate stale vectors when the active provider/model differs from what produced the index.

  • MEDIUM: depth guard on Bases filter recursion, updateNote skips the disk write when the transform returns unchanged content (no more spurious mtime bumps on no-op tools), and embedding-provider error bodies are truncated to 200 chars before being interpolated into thrown errors.

  • LOW: empty accept elicitation responses are now cancellations, not errors, and the in-memory mtime cache snapshot orders by content length so small entries fill the budget first.

v1.8.0 was the largest feature drop since v1.0:

  • Surgical edits by heading and block id. update_section, insert_at_section, list_sections, replace_in_note, edit_block, plus fragment retrieval modes on get_note (section, block, lines).

  • Bases support. list_bases, read_base, query_base for Obsidian's database-view files. First filesystem-only MCP server to ship native Bases.

  • Semantic search. index_vault, search_semantic, find_similar_notes backed by Ollama (default) or OpenAI. Persistent vector index with content-hash incremental updates.

  • Attachments. list_attachments, find_unused_attachments, get_attachment (returns image / audio / blob bytes inline).

  • Tag renames vault-wide. rename_tag rewrites both inline #tag and frontmatter tags: (hierarchical mode rebases nested sub-tags).

  • Folder-scoped permissions. OBSIDIAN_READ_PATHS / OBSIDIAN_WRITE_PATHS allowlists.

  • In-memory note cache. Vault-wide scans (get_tags, search_notes, search_by_tag) reuse note content when its file signature matches, without persisting note bodies to disk.

  • Quick wins. get_recent_notes, get_vault_stats, resolve_alias.

  • MCP prompts. daily-review, weekly-rollup, find-stale-notes, extract-action-items, build-moc.

  • Progress notifications on rename_tag, find_unused_attachments, index_vault.

  • Bulk-write confirmation latches on delete_note(permanent: true), move_note reference rewrites, and non-dry-run rename_tag, plus elicitation prompts for clients that support them.

  • eslint wired up with typescript-eslint flat config; npm run lint and lint:fix.

v1.7.0 — delete_note reference handling:

  • delete_note can strip references vault-wide when permanent: true is paired with removeReferences: true. Wikilinks fall back to alias-or-basename, markdown links fall back to visible text, embeds drop entirely, fragments are discarded. Trash-mode (default) leaves references intact since trashed files stay recoverable.

  • Concurrent-safe rewrites — move_note (with updateLinks: true) and delete_note (with removeReferences: true) serialize per vault, removing the partial-failure mode for parallel rewrite-bearing operations.

v1.6.0 — Obsidian-parity link maintenance:

  • move_note rewrites references across the vault by default, matching Obsidian's "Automatically update internal links" behavior. Wikilinks (with aliases / fragments preserved), markdown links, and canvas nodes[].file fields all follow the moved file. Output form is preserved when possible.

  • TOCTOU correctness — every edit's pre-edit content is verified before splicing, so a parallel write_note between plan and apply is surfaced in failedReferrers rather than corrupting referrers silently.

  • Control-char injection defense — sanitizeError and the new escapeControlChars strip newlines/control bytes from any caller-controlled string before it reaches LLM context. Closes a prompt-injection vector via attacker-named filenames.

Full version history in CHANGELOG.md.


License

MIT


Contributing

Contributions welcome! Please open an issue first to discuss what you'd like to change. Pull requests without a corresponding issue may be closed.

If you're adding or editing a tool, read docs/TOOL_AUTHORING.md first — it documents the description, schema, and annotation conventions that keep every tool at A-grade quality.


Acknowledgments

  • Vault-wide link rewriting on move_note (#3, #4) and the sanitizeError defense-in-depth hardening contributed by @brentkearney.

For the full list of everyone who's contributed, see the contributors page.

Available Tools

41 tools
add_canvas_edgeAdd Canvas EdgeA

Create a directed edge connecting two existing canvas nodes. Both fromNode and toNode must already exist on the canvas (use read_canvas to list node ids, or capture the id returned by add_canvas_node). Optional fromSide/toSide control which face of each node the edge anchors to. Returns the generated edge UUID.

Inputs: toNode (required), fromNode (required), canvasPath (required), label, +2 more.

add_canvas_nodeAdd Canvas NodeA

Add a new node to an Obsidian canvas and persist the updated file. Supports four node types: 'text' (markdown block), 'file' (embedded vault note reference), 'link' (external URL), and 'group' (labeled container). Returns the generated node UUID, needed to connect nodes via add_canvas_edge. When neither x nor y is supplied, the new node is auto-positioned at (50 * existing_count, 50 * existing_count) to avoid stacking multiple defaulted nodes at the origin. Supplying an explicit x or y always overrides this stagger.

Inputs: type (required), content (required), canvasPath (required), x, +4 more.

append_to_noteAppend to NoteA

Append text to the end of an existing note without altering prior content. By default, inserts a leading newline if the file does not already end in one, so appended content starts on its own line. Use for log entries, running lists, or adding new sections. Fails if the note does not exist — use create_note to make a new note first.

Inputs: path (required), content (required).

create_daily_noteCreate Daily NoteA

Create a daily note for today (or a specific date) in the vault's configured daily-note folder using its configured filename format. Optionally seed the note from a template file with Obsidian-style placeholder substitution: {{date}} and {{title}} → the formatted date; {{time}} → local HH:mm; {{date:FORMAT}} / {{time:FORMAT}} → custom moment-style format. Fails if the daily note already exists.

Inputs: date, content, templatePath.

create_noteCreate NoteA

Create a new markdown note at the given path with body content and optional YAML frontmatter. Fails (does not overwrite) if a note already exists at that path — use append_to_note, prepend_to_note, or update_frontmatter for existing notes. Missing directories are created automatically, and a .md extension is appended if omitted.

Inputs: path (required), content (required), frontmatter.

delete_noteDelete NoteA
Destructive

Delete a note. By default the file is moved to the vault's .trash folder (recoverable inside Obsidian); pass permanent=true to unlink it from disk immediately. When permanent=true, you can additionally pass removeReferences=true to strip wikilinks and markdown links to the deleted file across the vault (embeds are removed entirely; plain links fall back to their visible text). References are never rewritten when the file moves to .trash, since trashed files are recoverable.

Inputs: path (required), confirm, permanent, removeReferences.

edit_blockEdit BlockA
DestructiveIdempotent

Replace the content of a block tagged with ^id. The trailing ^id anchor is preserved on the last line of the new content so existing transclusions (![[note#^id]]) keep working. Use to update a single paragraph or list item that other notes reference.

Inputs: path (required), block (required), newContent (required).

find_orphansFind Orphan NotesA
Read-onlyIdempotent

Identify disconnected notes in the vault's link graph, classified into three groups: fully isolated (no links in or out), no-backlinks (nothing links to them), and no-outlinks (they link to nothing). Returns counts per category and an example list per category, capped by maxResults. Use to surface abandoned notes, missing hub pages, or candidates for archiving.

Inputs: maxResults, includeOutlinksCheck.

find_similar_notesFind Similar NotesA
Read-onlyIdempotent

Given a note path, return the K most semantically similar notes from the index (excluding the source note). Uses the source note's existing chunk embeddings and anchors the source query to chunks aligned with the note's opening topic — no live API call to the embedding provider, so this is fast and free. Run index_vault first to populate embeddings for both the source and the candidates.

Inputs: path (required), limit.

find_unused_attachmentsFind Unused AttachmentsA
Read-onlyIdempotent

Locate attachments that no note references — neither via ![[file]] embeds nor [text](file) markdown links. Useful for vault hygiene before archiving or before running a sync. Pair the output with delete operations from your shell, since this tool deliberately doesn't unlink files.

Inputs: limit, includeBytes.

get_attachmentGet AttachmentA
Read-onlyIdempotent

Read an attachment file and return its bytes to the client. Images come back as image content blocks (rendered inline by Claude / Cursor), audio as audio blocks, everything else as a base64 resource block with a vault:// URI. Caps at 5 MB by default to keep token usage sane; raise via maxBytes up to 50 MB. The attachment must be inside the vault — markdown notes (.md), canvases (.canvas), and Bases (.base) are deliberately rejected so callers don't accidentally pull text-format files through this binary path; use get_note / read_canvas / read_base instead.

Inputs: path (required), maxBytes.

get_daily_noteGet Daily NoteA
Read-onlyIdempotent

Read the daily note for today or for a specific date, resolved via the vault's configured daily-note folder and filename format. Returns the note path, parsed frontmatter (as a labeled header block), and body. Errors if no daily note exists for that date — use create_daily_note to create one.

Inputs: date.

get_graph_neighborsGet Graph NeighborsA
Read-onlyIdempotent

Traverse the wikilink graph outward from a starting note and return every note reachable within N hops, grouped by depth level with an indented tree visualization. Each neighbor is tagged with its hop distance and direction (inbound = reached via backlink, outbound = reached via outlink). Use to explore a topic cluster, map a note's local neighborhood, or find related notes beyond direct links. Accepts paths with or without .md extension.

Inputs: path (required), depth, direction, maxResults.

get_noteGet NoteA
Read-onlyIdempotent

Read a note in full or as a fragment. With no fragment options, returns parsed frontmatter (as a labeled header), a flat list of inline #tags, and the body. With section, returns just the body under that heading (path-form like 'Tasks/Today' is supported). With block, returns the paragraph or block tagged ^id. With lines, returns the inclusive 1-indexed line range. Fragment modes skip the frontmatter/tag header and return raw text — use them to keep token usage tight on long notes.

Inputs: path (required), block, lines, section.

get_recent_notesGet Recent NotesA
Read-onlyIdempotent

List notes ordered by most-recently-modified first. Optional since filter accepts an ISO date (e.g. '2026-04-01') or a relative span ('7d', '24h', '2w'); only notes modified at or after that time are returned. Use to surface what you've been working on, build a 'what changed this week' digest, or pick targets for review.

Inputs: limit, since, folder.

get_vault_statsGet Vault StatsA
Read-onlyIdempotent

Return a quick health snapshot of the vault: note count, total bytes, total words, unique tag count, untagged-note count, and the most-recently-modified note. Useful for dashboards and 'is this vault healthy?' checks. Reads through the mtime cache so repeat calls are cheap.

Inputs: folder.

index_vaultIndex Vault for Semantic SearchA
Idempotent

Build or refresh the embedding index used by search_semantic and find_similar_notes. Splits readable notes into heading-aware chunks, sends those chunks to the configured embedding provider (Ollama by default, OpenAI optional), and persists the index to <vault>/.obsidian/cache/mcp-pro-embeddings.json. Requires confirm: "send-vault-text-to-embedding-provider" so callers explicitly acknowledge that vault text will leave this tool boundary. Incremental: notes whose content hash matches the prior pass are skipped. Use force: true to re-embed everything (e.g., after switching models). Emits progress notifications when the client subscribes.

Inputs: force, folder, confirm.

insert_at_sectionInsert at SectionA

Insert content into a specific section without replacing it. position controls where: 'before' inserts above the heading, 'after-heading' inserts immediately under the heading line (at the top of the section body), 'append' inserts at the end of the section's body just before the next heading. Use to add a new bullet or paragraph without rewriting the section.

Inputs: path (required), content (required), section (required), position.

list_attachmentsList AttachmentsA
Read-onlyIdempotent

Enumerate every non-markdown file in the vault — images, PDFs, audio/video clips, anything pasted in beyond notes/canvases/Bases. Returns a sorted list of relative paths plus a per-extension count summary. Use to audit assets, find duplicates by name, or pick targets for find_unused_attachments.

Inputs: limit, extension.

list_basesList BasesA
Read-onlyIdempotent

Enumerate every Obsidian Bases (.base) file in the vault. Bases are YAML-defined database views over notes (filters, properties, table/calendar/kanban views). Returns a sorted list of relative paths plus the total count. Pair with read_base or query_base.

Inputs: no named parameters.

list_canvasesList CanvasesA
Read-onlyIdempotent

Enumerate every Obsidian canvas file (.canvas) anywhere in the vault, returning a numbered list of relative paths and the total count. Takes no parameters — scans the entire vault. Use to discover available canvases before calling read_canvas, add_canvas_node, or add_canvas_edge.

Inputs: no named parameters.

list_notesList NotesA
Read-onlyIdempotent

Enumerate every markdown note in the vault (or a single folder), returning a sorted list of relative paths along with the total count. Truncates output to limit entries but still reports the total. Use to browse vault structure, build a file picker, or enumerate targets for batch processing.

Inputs: limit, folder.

list_sectionsList SectionsA
Read-onlyIdempotent

List all headings in a note as a tree of paths (with depth). Useful for discovering valid section arguments before calling get_note, update_section, or insert_at_section.

Inputs: path (required).

list_tagsList All TagsA
Read-onlyIdempotent

Enumerate every unique tag used across the vault along with the number of notes each tag appears in. Detects tags from both inline #hashtags and YAML frontmatter, normalizes them case-insensitively, and returns a sorted list plus the total unique tag count. Use to build a tag cloud, pick categories, audit taxonomy, or discover available tags before calling search_by_tag.

Inputs: sortBy.

move_noteMove/Rename NoteA
Destructive

Move or rename a note within the vault, preserving its full content. Parent folders at the destination are created as needed. By default, wikilinks and file references are updated, matching Obsidian's "Automatically update internal links" behavior; this rewrite requires confirmPath to match the destination path after .md normalization. Pass updateLinks: false to skip the rewrite scan (faster on large vaults; pair with find_broken_links if you need to audit afterward). A .md extension is added automatically if omitted from either path.

Inputs: newPath (required), oldPath (required), confirmPath, updateLinks.

prepend_to_notePrepend to NoteA

Insert content at the top of an existing note's body, immediately after the YAML frontmatter block if one is present (so metadata stays at the top of the file). Use for adding new items to the front of a running list, pinning context, or inserting TL;DR sections. Fails if the note does not exist.

Inputs: path (required), content (required).

query_baseQuery BaseA
Read-onlyIdempotent

Run a Base file's filters against the vault and return matching note paths. Optionally pick a named view to apply that view's filters and ordering on top of the base-level filters. Supported filter syntax (subset of Obsidian's full DSL): chained methods file.hasTag("tag"), file.hasProperty("key"), file.inFolder("path"), file.linksTo("target"), file.name.contains("x")/.startsWith/.endsWith/.equals, plus .isEmpty/.isNotEmpty on any value; legacy function form taggedWith(file, "tag"); comparisons key == "val", key != x, key contains x, >=, <=, >, <; combinators and:, or:, not:. Recognized file properties: file.name, file.basename, file.folder, file.ext, file.path, file.size, file.ctime, file.mtime, file.tags, file.properties, file.links, file.embeds, file.backlinks. Unsupported clauses are reported as warnings and treated as no-match.

Inputs: path (required), view, limit, includeFrontmatter.

read_baseRead BaseA
Read-onlyIdempotent

Return the parsed contents of a Base file: filters, properties, view definitions, and any unrecognized fields. Use to discover what queries a Base supports before calling query_base.

Inputs: path (required).

read_canvasRead CanvasA
Read-onlyIdempotent

Read an Obsidian canvas file (.canvas, JSON format) and return a bounded human-readable summary of its structure: total node/edge counts, up to 200 nodes with id/type/position/size/content preview, and up to 200 edges with source/target node ids plus optional label. Use to inspect or navigate a canvas before calling add_canvas_node or add_canvas_edge.

Inputs: path (required).

rename_tagRename TagA
Destructive

Rename a tag everywhere it appears across the vault, in both inline #tags and frontmatter tags: fields. Non-dry-run rewrites require confirmTag to match the new tag name. With hierarchical: true (default), nested tags also rebase: renaming project to client also renames project/alpha → client/alpha. With dryRun: true, returns the planned counts without writing. Strip the leading # from oldName/newName — they're tag names, not tag tokens.

Inputs: newName (required), oldName (required), dryRun, confirmTag, +1 more.

replace_in_noteReplace in NoteA
Destructive

Search-and-replace within a single note. Supports literal strings or regex patterns. With expectedCount, the operation refuses to commit unless that many matches are present, guarding against accidental over-replacement when an LLM drafts a pattern that's too broad. Returns the count of replacements made.

Inputs: find (required), path (required), replace (required), flags, +2 more.

resolve_aliasResolve AliasA
Read-onlyIdempotent

Find every note whose frontmatter aliases: field contains the given name (case-insensitive). With includeBasename: true, also matches notes whose filename (without .md) equals the name — Obsidian's resolution fallback when no alias matches. Use to translate a human-friendly title like 'My Project' into the actual note path before calling get_note.

Inputs: name (required), includeBasename.

search_by_frontmatterSearch by FrontmatterA
Read-onlyIdempotent

Find notes whose YAML frontmatter contains a given property/value pair. Property names and values are matched case-insensitively; for array-valued properties, a match is declared if any element matches. Returns matching note paths with their full frontmatter. Use to filter notes by metadata like status, type, or tags stored in frontmatter.

Inputs: value (required), property (required), folder, maxResults.

search_by_tagSearch by TagA
Read-onlyIdempotent

Find all notes tagged with a specific tag, including nested sub-tags (searching 'project' matches both #project and #project/alpha). Detects tags from both inline #hashtags and YAML frontmatter. Returns matching note paths with optional content previews. Use to collect notes belonging to a topic, area, or workflow stage.

Inputs: tag (required), maxResults, includeContent.

search_notesSearch NotesA
Read-onlyIdempotent

Full-text search across all notes in the vault. Ranks literal matches with title/path focus and repeated-line dampening, then returns matching note paths grouped with the line numbers and query-centered snippet content of each matching line. Use to locate notes containing a phrase, keyword, or code fragment; pair with get_note to retrieve full bodies.

Inputs: query (required), folder, maxResults, caseSensitive.

search_semanticSemantic SearchA
Read-onlyIdempotent

Search notes by meaning rather than keywords. Embeds the query with the configured provider, scores every chunk in the persisted index by cosine similarity, ranks one result per note using the best chunk plus a small top-chunk focus signal, and returns the best chunk as the snippet source. Run index_vault first to populate the index — this tool does not auto-index because the user should know they're paying the embedding cost. Pair with get_note to retrieve full bodies after picking a hit.

Inputs: query (required), limit, folder, includeSnippet.

update_frontmatterUpdate FrontmatterA
DestructiveIdempotent

Merge new key-value pairs into a note's YAML frontmatter, preserving any keys not mentioned and leaving the body content untouched. Keys in the payload overwrite existing values. Creates a frontmatter block if the note has none. Returns a count of properties written. Use to set status fields, tags arrays, or other metadata without rewriting the body.

Note: The YAML block is regenerated on each update — comments, custom quoting, multi-line scalar style, blank lines, and key ordering inside the block are normalized. Key presence and values are preserved; formatting is not.

Inputs: path (required), properties (required).

update_sectionUpdate SectionA
DestructiveIdempotent

Replace the body of a specific section (everything between a heading and the next heading at any level). The heading line itself is preserved. section is a heading path: 'Tasks', 'Project A/Status', etc. - case-insensitive and whitespace-tolerant. Use this instead of rewriting the whole file when you only need to update one section.

Inputs: path (required), newBody (required), section (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 41 tools

    Disambiguation4/5

    Most tools target a clearly distinct resource+action (read_base vs query_base, get_backlinks vs get_outlinks, list_tags vs search_by_tag), and descriptions explicitly cross-reference when to use which. The one genuinely crowded area is content mutation — append_to_note, prepend_to_note, update_section, insert_at_section, edit_block, and replace_in_note all write into a note — plus four separate search modalities (search_notes, search_semantic, search_by_frontmatter, search_by_tag), which are well-described but still require careful reading to pick correctly.

    Naming Consistency5/5

    Every name is snake_case and verb-led with a clear object (get_note, list_canvases, find_broken_links, add_canvas_edge, search_by_tag). Family prefixes (list_*, get_*, find_*, search_*, create_*, add_*, *_canvas_*) form a predictable and readable taxonomy with no camelCase or casing drift.

    Tool Count2/5

    41 tools is well past the point where an agent can hold the surface in view comfortably, and several clusters (six note-mutation tools, four search tools, three attachment tools) could be consolidated behind parameters. The domains are genuinely broad (notes, sections, blocks, tags, links, graph, canvas, Bases, attachments, embeddings), so it is not arbitrary bloat — but the selection cost on every turn is real.

    Completeness4/5

    Note lifecycle is essentially full (create/read/append/prepend/move/delete, frontmatter, sections, blocks, regex replace) and tags, links, graph traversal, Bases, attachments, and semantic search all have coherent mini-surfaces. Gaps are around the peripheral formats: canvas support is add-only (no node/edge update or deletion, no canvas creation), Bases are read/query-only, and there is no attachment deletion tool — the description even defers that to the shell.

    Maintenance

    ActivityMaintained
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      Local-first MCP server for Obsidian vaults with 66 tools for reading, writing, searching, and managing notes, tasks, graphs, and more. Works without Obsidian running and requires no plugins.
      66
      MIT
    • A
      license
      A
      quality
      A
      maintenance
      Standalone MCP server for Obsidian vaults - hybrid search (FTS5 + vector + cross-encoder reranking), images and PDFs in agent-readable form, Kanban-aware tasks (Tasks-plugin + Dataview formats), structured memory with topic recall, fine-grained read/write tools for optimal token efficiency, and link graph support. Run locally, self-host, or one-click deploy for remote access. OAuth 2.1.
      16
      33
      429 npm
      38
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP server for Obsidian that exposes tools for reading/writing notes, managing frontmatter and tags, querying Tasks, semantic search, and interacting with Obsidian Bases, with shared local caching and support for various runtime modes.
      40
      Apache 2.0