obsidian-mcp-pro
You can use obsidian-mcp-pro as an MCP server to give AI assistants structured access to an Obsidian vault for reading, writing, searching, linking, and analyzing notes and assets.
Search and read notes: full-text search, frontmatter search, tag search, recent notes, vault stats, alias resolution, daily notes, and fragments by section, block ID, or line range.
Write and organize: create notes, append/prepend content, update frontmatter, create daily notes from templates, move/rename with link rewrites, and delete to trash or permanently with optional reference cleanup.
Make surgical edits: update or insert into sections, list headings, regex/literal replace with match guards, and edit
^blockcontent while preserving anchors.Manage tags: list tag counts, find notes by tag, and rename tags vault-wide, including nested tags and frontmatter arrays.
Analyze links and graph structure: get backlinks, outlinks, orphan notes, broken links, and traverse graph neighbors up to 3 hops.
Work with Obsidian canvases: list/read
.canvasfiles, add text/file/link/group nodes, and add edges between nodes.Query Obsidian Bases: list/read
.basefiles and run supported filter DSL queries against the vault.Handle attachments: list non-note files, find unused attachments, and retrieve image/audio/binary bytes with size caps.
Use semantic search: index the vault for embeddings with explicit confirmation, search by meaning, and find notes similar to a source note.
Use MCP resources and prompts: read notes/tags/daily content by URI and run prompts for daily review, weekly rollup, stale-note discovery, action-item extraction, and MOC building.
Allows for structured manipulation of Markdown documents within the vault, including parsing and updating frontmatter properties, as well as appending or prepending content to existing files.
Provides comprehensive access to Obsidian vaults, enabling AI agents to search notes, read and write content with frontmatter support, manage tags, analyze backlinks and outlinks, and traverse the link graph.
obsidian-mcp-pro
The most feature-complete MCP server for Obsidian vaults.
ā Please star us on GitHub, it helps us reach more users!
š 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.
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
Read & Search
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_notesorders by mtime;get_vault_statsreports counts, words, tag coverage;resolve_aliastranslates 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
.trashfolder by default, with an optional permanent flag and elicitation-based confirmationSurgical edits by heading:
update_section,insert_at_section,list_sections, plus single-notereplace_in_note(regex with match-count guard) andedit_blockfor paragraphs tagged with^id
Tags
Build and query a complete tag index (incremental: cached across runs)
Search notes by single or multiple tags
rename_tagrewrites both inline#tagoccurrences and frontmattertags:arrays vault-wide; hierarchical mode also rebases nested sub-tags (project/alphafollowsproject)
Links & Graph
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
.canvasfiles with full node and edge dataAdd new nodes (text, file, link, group) to canvases
Add edges between canvas nodes
List all canvases in the vault
Bases (Obsidian 1.10+)
list_basesenumerates.basefilesread_basereturns the parsed YAML (filters, properties, views)query_baseruns the filter DSL against the vault and returns matching notes; supportstaggedWith(),file.hasTag(),file.inFolder(), comparison operators, andand/or/notcombinators. Unsupported filters warn and fail closed instead of broadening results.
Attachments
list_attachmentsenumerates every non-md/canvas/base file with a per-extension count summaryfind_unused_attachmentsflags assets no note references via embeds or markdown links; optional reclaimable-bytes reportget_attachmentreturns image / audio / blob bytes inline as MCP content blocks (5 MB default cap, 50 MB hard cap)
Semantic Search (optional, Ollama or OpenAI)
index_vaultchunks each note (heading-aware), embeds via the configured provider, persists vectors to<vault>/.obsidian/cache/, and incrementally re-embeds only changed notesBecause indexing sends readable note chunks to the configured embedding provider,
index_vaultrequiresconfirm: "send-vault-text-to-embedding-provider"on each callsearch_semanticranks notes by cosine similarity against an embedded queryfind_similar_notesreuses an existing note's embeddings, with source-topic anchoring, to surface neighbors without a live API callStored 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 pathobsidian://tagsretrieves the full tag index as JSONobsidian://dailygets 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-reviewwalks today's daily note, surfaces unchecked tasks, and proposes follow-upsweekly-rollupaggregates the last seven daily notes into themes / decisions / open tasksfind-stale-noteslocates untouched notes and clusters them as orphaned vs. broken-linked vs. still-linkedextract-action-itemspulls all- [ ] ā¦lines from a note (or every note matching a tag) into a checklistbuild-mocgenerates a Map of Content (MOC) for a tag or folder
Operational features
Folder-scoped permissions:
OBSIDIAN_READ_PATHS/OBSIDIAN_WRITE_PATHSallowlists gate every tool at the path-resolution choke pointIn-memory note cache speeds repeated vault scans within a running process without persisting note bodies to disk
Progress notifications (
notifications/progress) onrename_tag,find_unused_attachments, andindex_vaultwhen the client subscribes via_meta.progressTokenHard bulk-write latches require
confirmPathfor defaultmove_notereference rewrites,confirmTagfor non-dry-runrename_tag, andconfirm=truefor 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 installThis 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/vaultManual 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-proHTTP Transport (Remote Clients, Cursor, ChatGPT, Web)
MCP_HTTP_TOKEN=your-secret npx -y obsidian-mcp-pro --transport=http --port=3333Endpoint: 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=httpThe 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
Hostheader exactly and case-sensitively, including a port when present. Usevault.example.com:443if the proxy forwards that value instead ofvault.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
allowedHostsor 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).
/healthand/versionskip Host validation (pre-existing)./mcprejects 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.
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, andRestrict
--allow-originto the specific origins you trust, andSet
--rate-limitto 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 |
| Restrict CORS to an allowlist (e.g. |
| Cap requests per minute per client IP. |
Operational endpoints (no auth required):
Endpoint | Returns |
|
|
|
|
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
#projectI 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."
)
ā okClaude: I reviewed 7 project notes tagged
#project. Atlas is on track pending design review; the Q2 milestone is at risk ā details inmilestones.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_neighborswalks the graph todepth=2and surfaces related notes the user may have forgotten.Clean up dangling references after a rename.
move_noteāfind_broken_linksreturns every wikilink that now points nowhere, with source note and line number.Maintain a daily log.
get_daily_notereads today's note (using the vault's configured date format) andappend_to_noteadds 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_edgelays 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 |
| Environment variable with the absolute path to your vault |
2 |
| Environment variable to select a vault by folder name when multiple vaults exist |
3 | Auto-detection | Reads Obsidian's global config ( |
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 |
| Absolute path to the vault (highest-priority vault selector). | auto-detect |
| Select a vault by folder name when several exist. | first valid vault |
| Comma/colon list of folders read tools may access (see Folder-Scoped Permissions). | unrestricted |
| Same shape, for mutating tools. | unrestricted |
| Set | enabled |
| Semantic-search provider, e.g. | unset (disabled) |
| Embedding model name for the chosen provider. | provider default |
| Override the embedding endpoint base URL. | provider default |
| API key for the embedding provider. | falls back to |
| Used as the embedding key when | unset |
| Bearer token required by the HTTP transport (see HTTP Transport). | unset |
| Logger verbosity: |
|
| Log output format: |
|
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 | |
|
|
|
| |
|
|
|
| |
|
|
|
| |
|
|
|
| |
|
|
|
| |
|
|
|
| |
|
|
|
| |
|
|
|
| |
| renders the bracket contents verbatim, e.g. |
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 |
| Comma- or colon-separated list of folders that read tools may access. Unset means unrestricted. Use |
| 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 |
| Persisted embeddings for semantic search (only present once |
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 |
|
|
|
|
| Provider-specific model identifier. |
|
| Base URL without credentials, query strings, or fragments. |
|
| 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
.mdfiles; attachments, Canvas files, and Bases stay on their dedicated tools with their own caps and parsers.Full-note cap ā full
.mdnote reads and read-modify-write helpers refuse notes over 5 MiB before materializing them;get_noteline fragments still stream from large notes for targeted inspection.Excluded directories ā
.obsidian,.git, and.trashare 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.1by default with DNS rebinding protection (host-header allowlist)./mcprequests must sendAuthorization: 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 withtext/plainresource metadata.Canvas link safety ā Canvas link nodes added through the server accept only absolute
http://andhttps://URLs, preventing local file and application-protocol links from being persisted by tool calls.Regex edit safety ā
replace_in_noteexecutes 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 errorbody 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_semanticandfind_similar_notesre-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_vaultrefuses to read and embed vault notes unless the call includesconfirm: "send-vault-text-to-embedding-provider", making provider-bound note transfer explicit.Bulk-write confirmations ā
move_notereference rewrites requireconfirmPathto match the destination path, and non-dry-runrename_tagrequiresconfirmTagto 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, orconfirmTaglatch 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 use0666 & ~umask, and staging starts without group or other access. See File permissions for the policy and platform limits. Theinstallsubcommand uses the same staging pattern and keeps a backup of the previous config.Rate limiting + CORS allowlist ā optional
--rate-limitcaps per-IP request volume;--allow-originrestricts browser-facing CORS and refuses*unless bearer auth is enabled./healthand/versionstay 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 ā
uncaughtExceptionexits cleanly so systemd/Docker/npx supervisors can restart;unhandledRejectionlogs but doesn't kill the process.
Wikilink Resolution
[[Target]] resolves in the same order Obsidian does:
Exact relative-path match (case-insensitive).
Path-suffix match (e.g.
[[projects/foo]]pickswork/projects/foo.md).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.
Frontmatter
aliasesā[[Display Name]]resolves to a note whose frontmatter declares that alias.aliases,Aliases, andALIASESare 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 |
| Focus-ranked full-text search with query-centered snippets (cached) |
|
| Read a note whole, or by |
|
| List notes in the vault or a folder |
|
| Get today's (or a specific date's) daily note |
|
| Find notes by frontmatter property values with case-insensitive key/value matching |
|
| Notes sorted by mtime; optional ISO-or-relative |
|
| Vault counts, bytes, words, tag coverage, most-recent note |
|
| Translate frontmatter alias (or basename) to note path |
|
Write
Tool | Description | Key Parameters |
| Create a new note with content and frontmatter |
|
| Append content to an existing note |
|
| Prepend content after frontmatter |
|
| Update frontmatter properties on a note |
|
| Create today's daily note from template |
|
| Move or rename a note; rewrites references across the vault |
|
| Delete a note (trash by default); optional elicitation on permanent |
|
Section-level edits
Tool | Description | Key Parameters |
| Replace the body under a heading path (heading kept) |
|
| Insert at |
|
| Return the heading outline of a note as an indented tree |
|
| Find/replace within one note (literal or regex, with match-count guard) |
|
| Replace content of a paragraph tagged |
|
Tags
Tool | Description | Key Parameters |
| Get all tags and their usage counts |
|
| Find all notes with a specific tag |
|
| Rewrite inline + frontmatter occurrences vault-wide; hierarchical |
|
Links & graph
Tool | Description | Key Parameters |
| Get all notes that link to a given note |
|
| Get all links from a given note |
|
| Find notes with no links in or out |
|
| Detect links pointing to non-existent notes |
|
| Get notes connected within N link hops |
|
Canvas
Tool | Description | Key Parameters |
| List all | (none) |
| Read a bounded |
|
| Add a node to a canvas |
|
| Add an edge between two canvas nodes |
|
Bases
Tool | Description | Key Parameters |
| Enumerate | (none) |
| Parse a Base file (filters, properties, views) |
|
| Run a Base's filter DSL against the vault; unsupported filters warn and fail closed |
|
Attachments
Tool | Description | Key Parameters |
| Enumerate every non-md/canvas/base file |
|
| Attachments not referenced via embeds or markdown links |
|
| Return image / audio / blob content (5 MB default cap) |
|
Semantic search
Tool | Description | Key Parameters |
| Build / refresh the embedding index (incremental, progress events) |
|
| Cosine search the embedding index for a natural-language query |
|
| Surface notes most similar to a source note, anchored to its opening topic (no live API call) |
|
MCP Resources
Resources provide a URI-based way to access vault data:
Resource URI | Description |
| Read any note by its vault-relative path |
| Full tag index with file lists (JSON) |
| 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:
OBSIDIAN_VAULT_PATHenv var (absolute path) ā always wins if set. An invalid selection fails startup instead of falling back to another vault.OBSIDIAN_VAULT_NAMEenv var ā picks a named vault from Obsidian's global config.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 startGitHub 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.tsTesting
npm testRun 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-fixWhat's New
v1.8.2 rolls in a deeper-dive audit pass on top of 1.8.1:
rename_tagand other vault-wide bulk writers now hold the same rewrite lock asmove_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.applyRewritesretries failed edits via content search. When bytes shift between plan and apply (Obsidian sync, text editor, concurrent tool), the apply step now finds the uniqueexpectedsubstring at its new position and splices there. If ambiguous or missing, the failure is still surfaced rather than corrupting the file.planMoveRewritesandplanDeleteRewritesnow 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).
/healthno longer leaks the live session count when a Bearer token is configured. Status + version stay public for monitoring;sessionsis dropped in authenticated deployments. In 4.0.0 and later, HTTP always starts with bearer auth, so the field stays hidden.constantTimeEqualis now fully length-safe (pads both inputs to a fixed width before comparing), and the regex / parser hardening list also closed:resolveWikilinkproximity tie-break for path-suffix matches, escaped]in markdown link labels, control- character validation onrunInstall.vaultName, backup-path hint on install write failure, andmapConcurrentreturn-value usage in the canvas planner.npm auditclean. 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 evaluatedOBSIDIAN_READ_PATHS/OBSIDIAN_WRITE_PATHSagainst the raw user-supplied path beforepath.resolvecollapsed... An input likeAllowed/../OtherFolder/note.mdslipped past the prefix check.assertAllowednow collapses..viapath.posix.normalizeand 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 inrename_tagclosed by moving the rewrite insideupdateNote's transform, andsearch_semantic/find_similar_notesnow invalidate stale vectors when the active provider/model differs from what produced the index.MEDIUM: depth guard on Bases filter recursion,
updateNoteskips 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
acceptelicitation 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 onget_note(section,block,lines).Bases support.
list_bases,read_base,query_basefor Obsidian's database-view files. First filesystem-only MCP server to ship native Bases.Semantic search.
index_vault,search_semantic,find_similar_notesbacked 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_tagrewrites both inline#tagand frontmattertags:(hierarchical mode rebases nested sub-tags).Folder-scoped permissions.
OBSIDIAN_READ_PATHS/OBSIDIAN_WRITE_PATHSallowlists.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_notereference rewrites, and non-dry-runrename_tag, plus elicitation prompts for clients that support them.eslint wired up with typescript-eslint flat config;
npm run lintandlint:fix.
v1.7.0 ā delete_note reference handling:
delete_notecan strip references vault-wide whenpermanent: trueis paired withremoveReferences: 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(withupdateLinks: true) anddelete_note(withremoveReferences: true) serialize per vault, removing the partial-failure mode for parallel rewrite-bearing operations.
v1.6.0 ā Obsidian-parity link maintenance:
move_noterewrites references across the vault by default, matching Obsidian's "Automatically update internal links" behavior. Wikilinks (with aliases / fragments preserved), markdown links, and canvasnodes[].filefields 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_notebetween plan and apply is surfaced infailedReferrersrather than corrupting referrers silently.Control-char injection defense ā
sanitizeErrorand the newescapeControlCharsstrip 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 thesanitizeErrordefense-in-depth hardening contributed by @brentkearney.
For the full list of everyone who's contributed, see the contributors page.
Available Tools
41 toolsadd_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 NoteADestructive
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 BlockADestructiveIdempotent
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_broken_linksFind Broken LinksARead-onlyIdempotent
Scan notes for wikilinks ([[target]]) whose target does not resolve to any existing note in the vault. Returns a per-source report grouping each note with its broken link text and line numbers, plus a total count. Use after renaming, moving, or deleting notes to catch dangling references. Resolution uses the whole vault even when scanning a single folder, so only truly unresolvable links are reported.
Inputs: folder, maxResults.
find_orphansFind Orphan NotesARead-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 NotesARead-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 AttachmentsARead-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 AttachmentARead-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_backlinksGet BacklinksARead-onlyIdempotent
List all notes that contain a wikilink pointing to the target note. Each result includes the source note path, line number, and the surrounding line text for context. Use to understand which notes reference a topic, or to assess the impact of renaming or deleting a note. Accepts paths with or without .md extension; falls back to basename matching if exact match fails.
Inputs: path (required).
get_daily_noteGet Daily NoteARead-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 NeighborsARead-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 NoteARead-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_outlinksGet OutlinksARead-onlyIdempotent
List every outgoing wikilink from a note, partitioned into valid links (resolve to an existing note), broken links (target not found), and file embeds (![[...]]). Returns the raw link text and resolved paths. Use to audit a note's references, detect broken links, or follow downstream dependencies.
Inputs: path (required).
get_recent_notesGet Recent NotesARead-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 StatsARead-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 SearchAIdempotent
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 AttachmentsARead-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 BasesARead-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 CanvasesARead-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 NotesARead-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 SectionsARead-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 TagsARead-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 NoteADestructive
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 BaseARead-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 BaseARead-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 CanvasARead-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 TagADestructive
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 NoteADestructive
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 AliasARead-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 FrontmatterARead-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 TagARead-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 NotesARead-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 SearchARead-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 FrontmatterADestructiveIdempotent
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 SectionADestructiveIdempotent
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
Scored across 41 tools
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.
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.
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.
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
Related MCP Connectors
Hosted MCP server for Obsidian Sync vaults: read, search, edit notes. Not affiliated with Obsidian.
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
MCP-native open-source Notion alternative: read & write pages, databases and kanban boards.
Nifty's MCP server ā exposes tasks, projects, messages, and files as tools for AI agents.
Related MCP Servers
- AlicenseBqualityDmaintenanceLocal-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.66MIT
- AlicenseAqualityAmaintenanceStandalone 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.1633429 npm38MIT
- AlicenseNot gradedqualityBmaintenanceMCP 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.40Apache 2.0
- AlicenseBqualityDmaintenanceA comprehensive MCP server for Obsidian with 45 tools across hybrid filesystem-native and API-based architectures, enabling note management, backlinks, tag management, and analytics.638MIT