Install
$ agentstack add mcp-cyanheads-obsidian-mcp-server ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
obsidian-mcp-server Read, write, search, and surgically edit Obsidian vault notes, tags, and frontmatter via MCP. STDIO or Streamable HTTP. 14 Tools • 3 Resources
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/obsidian-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/obsidian-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
[](https://github.com/cyanheads/obsidian-mcp-server/releases/latest/download/obsidian-mcp-server.mcpb) [](https://cursor.com/en/install-mcp?name=obsidian-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm9ic2lkaWFuLW1jcC1zZXJ2ZXIiXSwiZW52Ijp7Ik9CU0lESUFOX0FQSV9LRVkiOiJ5b3VyLWFwaS1rZXkifX0=) [](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22obsidian-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22obsidian-mcp-server%22%5D%2C%22env%22%3A%7B%22OBSIDIANAPIKEY%22%3A%22your-api-key%22%7D%7D)
[](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
Tools
Fourteen tools grouped by shape — readers fetch notes and metadata, writers create or surgically edit content, managers reconcile tags and frontmatter, and a guarded escape hatch dispatches Obsidian command-palette commands.
| Tool Name | Description | |:----------|:------------| | obsidian_get_note | Read a note as raw content, full structured form (content + frontmatter + tags + stat, with optional outgoing links), structural document map, or a single section. | | obsidian_list_notes | List notes and subdirectories under a vault path. Recursive walk (default depth 2, max depth 20; 1000-entry cap) with optional extension and nameRegex filters. | | obsidian_list_tags | List every tag found across the vault with usage counts, including hierarchical parents. Optional nameRegex post-filters the result set. | | obsidian_list_commands | List Obsidian command-palette commands, optionally filtered by nameRegex on display name. Opt-in via OBSIDIAN_ENABLE_COMMANDS=true (paired with obsidian_execute_command). | | obsidian_search_notes | Search the vault by text, JSONLogic, or BM25-ranked Omnisearch (when the plugin is reachable). Results paginate via opaque cursors. | | obsidian_write_note | Create a note, replace a single section in place, or — with overwrite: true — clobber an existing file. Refuses whole-file writes against an existing path by default. | | obsidian_append_to_note | Append content to a note. Without section, creates the file if missing. With section, appends to a specific heading, block, or frontmatter field (file must exist). | | obsidian_patch_note | Surgical append / prepend / replace against a heading, block reference, or frontmatter field. | | obsidian_replace_in_note | Body-wide search-replace inside a single note. Literal or regex matching with whole-word, whitespace-flexible, and case-sensitivity options; supports capture-group replacement. | | obsidian_manage_frontmatter | Atomic get / set / delete on a single frontmatter key. | | obsidian_manage_tags | Add, remove, or list tags. Defaults to the frontmatter tags: array; location: 'inline' or 'both' opts into mutating the note body. | | obsidian_delete_note | Permanently delete a note. Elicits human confirmation when the client supports it. | | obsidian_open_in_ui | Open a file in the Obsidian app UI, with failIfMissing and newLeaf toggles. | | obsidian_execute_command | Execute an Obsidian command-palette command by ID. Opt-in via OBSIDIAN_ENABLE_COMMANDS=true. |
obsidian_get_note
Read a note in one of four projections, addressed by vault path, the active file, or a periodic note (daily, weekly, monthly, quarterly, yearly).
format: "content"— raw markdown bodyformat: "full"— content, frontmatter, tags, and file metadata; passincludeLinks: trueto also parse outgoing wiki and markdown link references from the body (vault-internal only — external URLs are filtered)format: "document-map"— catalog of headings, block references, and frontmatter fieldsformat: "section"— single heading/block/frontmatter section value (requiressection); heading sections include the full subtree under that heading
Pair the document-map projection with obsidian_patch_note to discover edit targets before patching.
obsidian_search_notes
Up to three search modes selected by mode:
text— substring match with surrounding context windows.contextLengthcontrols characters of context per side of each match (default 100; bump it for more context per hit). OptionalpathPrefixfilter (text mode only — passingpathPrefixin any other mode is rejected withpath_prefix_invalid_mode).jsonlogic— JSONLogic tree evaluated againstpath,content,frontmatter.,tags, andstat.{ctime,mtime,size}; customglobandregexpoperatorsomnisearch— BM25-ranked search via the community Omnisearch plugin. Supports quoted phrases,-exclusion,path:/ext:filters, typo tolerance, and PDF + OCR coverage (via Text Extractor). Only present in the mode enum when the plugin's HTTP server is reachable at startup; the upstream hard-caps results at 50 — narrow the query to surface more (the response carriestruncated: truewhen the cap was likely hit).
Results paginate via opaque cursors per the MCP 2025-11-25 spec: omit cursor for the first page, then pass nextCursor from the prior response. Every result carries totalCount (post-path-policy, pre-pagination); nextCursor is omitted on the last page. Text-mode hits are additionally clipped per file at maxMatchesPerHit (default 10) so a single match-heavy note can't blow the response budget — clipped hits carry truncated: true and totalMatches.
obsidian_write_note
Create or surgically replace, with a protective default against accidental whole-file overwrites.
- Without
section— full-filePUT. Refuses to clobber an existing file unlessoverwrite: trueis set. Thefile_exists(Conflict) error suggestsobsidian_patch_note/obsidian_append_to_note/obsidian_replace_in_notefor in-place edits. - With
section—PATCH-with-replace against the named heading/block/frontmatter field, leaving the rest of the file untouched. Theoverwriteflag is ignored in section mode.
The output reports created: true when the call brought a new file into existence; false when it replaced an existing one or targeted a section. Every mutating tool also returns previousSizeInBytes and currentSizeInBytes so an agent can spot accidental clobbers, unexpected upstream behavior, or a typo path that landed at the wrong file.
obsidian_append_to_note
A combined upsert + section-append primitive that mirrors the upstream Local REST API behavior:
- Without
section—POSTto/vault/{path}. Appends when the file exists, creates the file with your content as the entire body when it doesn't. The output'screated: trueflags the second branch so the agent can notice when a typo path or a not-yet-created daily note silently turned into a brand-new file. - With
section—PATCH-with-append against the named heading, block reference, or frontmatter field. The file must exist (PATCH preflight throwsnote_missingotherwise). PasscreateTargetIfMissing: trueto bring the section itself into existence inside an existing file. Block-reference targets concatenate adjacent to the block line without a separator — include a leading newline incontentif you want one.
previousSizeInBytes is 0 on the upsert-create branch and the actual file size otherwise; currentSizeInBytes is the post-write size read from the upstream after the operation. Compare deltas against Buffer.byteLength(content) to detect auto-newline injection or concurrent writers.
obsidian_patch_note
Surgical edits at a single document target.
operation: "append"adds after the sectionoperation: "prepend"adds before the sectionoperation: "replace"swaps it out- Targets: heading path, block reference ID, or frontmatter field
Use obsidian_get_note with format: "document-map" to discover what targets exist before patching.
obsidian_replace_in_note
Body-wide search-replace for edits that don't fit obsidian_patch_note's structural targets. The note is fetched, replacements are applied sequentially (each sees the previous output), and the result is written back in a single PUT.
Per-replacement options:
useRegex— treatsearchas an ECMAScript regex. WithuseRegex: true, the replacement honors$1/$&capture-group references.caseSensitive— whenfalse, match case-insensitivelywholeWord— wrap the pattern in\b…\b; works in both literal and regex modesflexibleWhitespace— substitute any run of whitespace insearchwith\s+. Literal mode only — has no effect whenuseRegex: true(express it directly).replaceAll— whenfalse, only the first match is replaced
Literal mode preserves $1 / $& in the replacement verbatim — only useRegex: true expands capture-group references.
obsidian_manage_tags
Add, remove, or list tags on a note. Operates on one of two representations, defaulting to the canonical Obsidian frontmatter location:
location: 'frontmatter'(default) — only the frontmattertags:array; the note body is left untouchedlocation: 'inline'— only inline#tagsyntax in the body;addappends#tagat end-of-filelocation: 'both'— opt-in reconciliation across both representations
add ensures the tag is present in the requested location(s); remove strips it; list ignores the input tags array. Inline #tag occurrences inside fenced code blocks are intentionally left alone.
obsidian_delete_note
Permanently delete a note. When the client supports elicit, the server requests human confirmation before issuing the DELETE and the prompt includes the file's byte size — destructive blast radius visible before the user confirms. Without elicitation, the destructiveHint annotation surfaces the operation in the host's approval flow. The output reports previousSizeInBytes (size at the moment of deletion) and currentSizeInBytes: 0.
obsidian_execute_command
Dispatch an Obsidian command-palette command by ID (discoverable via obsidian_list_commands). Behavior is command-dependent — some commands open UI, others delete files or close the vault.
Off by default. When OBSIDIAN_ENABLE_COMMANDS is unset, both obsidian_execute_command and its discovery partner obsidian_list_commands are wrapped with disabledTool() — absent from tools/list (the LLM can't invoke them) but still visible in the operator-facing manifest with a hint to enable them.
Path policy (folder-scoped permissions)
Three optional env vars gate which vault paths each tool can target. Default unset = full vault for both reads and writes — backwards compatible.
| Goal | Config | |:---|:---| | Default (current behavior) | all unset | | Read everywhere, write only in projects/ and scratch/ | OBSIDIAN_WRITE_PATHS=projects/,scratch/ | | Read only public/, write only public/inbox/ | OBSIDIAN_READ_PATHS=public/, OBSIDIAN_WRITE_PATHS=public/inbox/ | | Read-only deployment — no writes anywhere | OBSIDIAN_READ_ONLY=true |
Matching is prefix-based with implicit recursion, case-insensitive, with trailing slashes normalized. projects/ matches projects/a.md, projects/sub/b.md, etc.
Write paths are implicitly readable — you can't sanely edit what you can't see. So a read passes when the target matches READ_PATHS or WRITE_PATHS.
OBSIDIAN_READ_ONLY=true short-circuits before the path checks — every write tool and the command-palette pair are wrapped with disabledTool() at startup (absent from tools/list), and any write that still reaches the service is denied at runtime regardless of WRITE_PATHS.
Denies are typed path_forbidden (JSON-RPC code Forbidden) with the active scope echoed back in data.recovery.hint and data.activeScope, so the LLM can self-correct without inspecting server logs. Search results from obsidian_search_notes are filtered against READ_PATHS silently — surfacing a "we hid N hits" indicator would defeat the gate.
Tag listing is vault-wide. obsidian_list_tags and the obsidian://tags resource aggregate tag names across the whole vault and are not narrowed by OBSIDIAN_READ_PATHS — they take no path to gate, so tag names (never note contents) from outside the read scope can surface.
The startup banner logs the active scope so operators can verify their config at boot.
Resources
| Type | URI | Description | |:---|:---|:---| | Resource | obsidian://vault/{+path} | A note in the vault — content, frontmatter, tags, and file metadata. | | Resource | obsidian://tags | All tags found across the vault, with usage counts. | | Resource | obsidian://status | Server reachability, auth status, plugin/Obsidian version info, and the plugin manifest. |
All resource data is also reachable via tools — obsidian_get_note for obsidian://vault/{+path}, obsidian_list_tags for obsidian://tags. Resources exist for clients that prefer attaching a specific note or vault snapshot to a conversation.
Features
Built on @cyanheads/mcp-ts-core:
- Declarative tool and resource definitions — single file per primitive, framework handles registration and validation
- Unified error handling — handlers throw, framework catches, classifies, and formats. Tools advertise their failure surface via typed
errors[]contracts. - Server-level
instructionsoninitialize— surfaces deployment-specific orientation (active path policy, read-only mode, command-palette toggle) to spec-compliant clients alongside the static tool/resource catalog - Pluggable auth on the HTTP transport:
none,jwt,oauth - Structured logging with optional OpenTelemetry tracing
- STDIO and Streamable HTTP transports
The server itself is stateless — every tool call hits the Local REST API directly. The framework's storage backends, request-state KV, and progress streams aren't used here; Obsidian is single-vault and there's nothing to persist between calls.
Obsidian-specific:
- Wraps the Obsidian Local REST API plugin — typed client, deterministic error mapping
- Section-aware editing across headings, block references, and frontmatter fields via
PATCH-with-target operations - Tag reconciliation across both representations: frontmatter
tags:array and inline#tagsyntax (skipping fenced code blocks) - Search across up to three modes: text, JSONLogic, and (when the plugin is reachable) BM25-ranked Omnisearch — cursor-paginated per the MCP 2025-11-25 spec, with per-file match clipping in text mode
- Optional human-in-the-loop confirmation for destructive deletes via
ctx.elicit - Folder-scoped read/write permissions via
OBSIDIAN_READ_PATHS/OBSIDIAN_WRITE_PATHSand a globalOBSIDIAN_READ_ONLYkill switch — denies are typedpath_forbiddenwith the active scope echoed back in the error data - Opt-in command-palette pair (
obsidian_list_commands+obsidian_execute_command) — registered only whenOBSIDIAN_ENABLE_COMMANDS=true - Forgiving path resolution on
obsidian_get_noteandobsidian_open_in_ui— silently retries case-mismatched paths against the canonical filename, throwsConflicton ambiguous case matches, and enrichesNotFoundwithDid you mean: …?suggestions when only near-match
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: cyanheads
- Source: cyanheads/obsidian-mcp-server
- License: Apache-2.0
- Homepage: https://www.npmjs.com/package/obsidian-mcp-server
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.