CCM
/MCP
SkillsMCPMarketplacesDigestLearnAdvertise

This week in Claude

Every Monday: Claude Code, Agent SDK, MCP, and the Anthropic platform moves worth your time.

Skills by Category
Frontend DevelopmentBackend & APIsTesting & QASecurityDevOps & CI/CDGit & Pull RequestsDocumentationCode Review & QualityAI & Agent BuildingSkill Development
MCP Servers by Category
Sales & MarketingWeb & Browser AutomationDatabasesAI & LLM ToolsCloud & InfrastructureCommunication & MessagingDeveloper ToolsDesign & CreativeDocuments & KnowledgeSearch & Web Crawling
Marketplaces by Category
AI Agents & OrchestrationLLM IntegrationDevelopment ToolsFrontend & UIBackend & APIsDatabasesTesting & Code QualityDevOps & CloudSecurity & ComplianceGit & Version Control

Claude Code Marketplaces

Discover Claude Code plugins, extensions, and tools. Automatically updated directory of Anthropic Claude AI marketplaces with development tools, productivity plugins, and integrations.

Resources

  • Browse Skills
  • Browse MCP Servers
  • Browse Marketplaces
  • Plugins Reference

Community

  • About
  • Learn
  • Feedback
  • Privacy Policy
  • Advertise

Built for the Claude Code community with Claude Code by @mertduzgun

Independent project, not affiliated with Anthropic

Obsidian Mcp Server

cyanheads/obsidian-mcp-server
58212 toolsauthSTDIO, HTTPregistry active
Summary

The Obsidian MCP Server provides AI agents and LLMs with comprehensive access to Obsidian vaults through the Model Context Protocol, enabling read, write, search, and management operations on notes and files via the Obsidian Local REST API plugin. It exposes specialized tools including obsidian_read_note, obsidian_update_note, obsidian_search_replace, and obsidian_global_search, with capabilities such as flexible content formatting, regex-based search-and-replace, case-insensitive path fallback, and metadata retrieval. The server solves the problem of integrating AI assistants and development tools with Obsidian knowledge bases by providing a standardized, secure interface for programmatic vault interaction.

Install to Claude Code

verified
claude mcp add obsidian --env OBSIDIAN_API_KEY=YOUR_OBSIDIAN_API_KEY --env OBSIDIAN_BASE_URL=http://127.0.0.1:27123 --env OBSIDIAN_VERIFY_SSL=false --env OBSIDIAN_REQUEST_TIMEOUT_MS=30000 --env OBSIDIAN_ENABLE_COMMANDS=false --env OBSIDIAN_READ_PATHS=YOUR_OBSIDIAN_READ_PATHS --env OBSIDIAN_WRITE_PATHS=YOUR_OBSIDIAN_WRITE_PATHS --env OBSIDIAN_READ_ONLY=false --env MCP_LOG_LEVEL=info -- node -y obsidian-mcp-server run start:stdio

Run in your terminal. Replace YOUR_* placeholders with real values; add --scope user to install for every project.

Review the command, arguments, and environment values before installing — MCP servers run with your local permissions.

CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Vibe Prospecting MCPVibe Prospecting MCP
Vibe Prospecting MCP
Connect Claude to +800M contacts, +150M companies. Find & Enrich leads in chat.
Try For Free →
Context.devContext.dev
Context.dev
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Vibe Prospecting MCPVibe Prospecting MCP
Vibe Prospecting MCP
Connect Claude to +800M contacts, +150M companies. Find & Enrich leads in chat.
Try For Free →
Context.devContext.dev
Context.dev
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →

Tools

Verified live against the running server on Jun 10, 2026.

verified live12 tools
obsidian_get_noteRead a note from the vault — by path, the active file, or a periodic note. Choose a `format` projection: raw body, full object, structural document map, or a single section.4 params

Read a note from the vault — by path, the active file, or a periodic note. Choose a `format` projection: raw body, full object, structural document map, or a single section.

Parameters* required
format*string
Which projection to return. `content` — raw markdown body. `full` — content plus parsed frontmatter, tags, and file metadata. `document-map` — catalog of headings, block IDs, and frontmatter field names (use to discover patch targets). `section` — a single heading, block, or frontmatter section (requires `section`); heading sections include the full subtree under that heading and use `Parent::Child` syntax for nesting.one of content · full · document-map · section
target*value
Where the note lives.
sectionobject
Required when `format` is `"section"`. Identifies the heading/block/frontmatter to extract.
includeLinksboolean
When true with `format: "full"`, parses outgoing wiki and markdown link references from the note body. Skipped for other formats.default: false
obsidian_list_notesList notes and subdirectories at a vault path. Defaults to the vault root when `path` is omitted. Tune recursion with `depth`, or filter the walk with `extension` / `nameRegex`. Capped at 1000 entries per call — when reached, walking stops and `excluded` is set; narrow `path`...4 params

List notes and subdirectories at a vault path. Defaults to the vault root when `path` is omitted. Tune recursion with `depth`, or filter the walk with `extension` / `nameRegex`. Capped at 1000 entries per call — when reached, walking stops and `excluded` is set; narrow `path`...

Parameters* required
pathstring
Vault-relative directory path. Omit for the vault root.
depthinteger
How many directory levels to walk. `1` = target directory only (no recursion); `2` = target plus its immediate children — a structural overview; bump higher to drill in. Prefer narrowing `path` to a subdirectory over a high `depth` on the vault root.default: 2
extensionstring
Only include files matching this extension, with or without leading dot. Applies to files only — directories are returned regardless.
nameRegexstring
Optional ECMAScript regex (no flags) applied to entry names. Matches both files and directories; directories that fail the regex are skipped without recursing into them.
obsidian_list_tagsList every tag found across the vault, with usage counts. Includes hierarchical parents — `work/tasks` contributes to both `work` and `work/tasks`. Filter to a subset with the optional `nameRegex`. To find notes by tag, use `obsidian_search_notes` in jsonlogic mode (e.g. `{"in...1 params

List every tag found across the vault, with usage counts. Includes hierarchical parents — `work/tasks` contributes to both `work` and `work/tasks`. Filter to a subset with the optional `nameRegex`. To find notes by tag, use `obsidian_search_notes` in jsonlogic mode (e.g. `{"in...

Parameters* required
nameRegexstring
Optional ECMAScript regex (no flags, ≤256 chars, no nested quantifiers like `(a+)+`) matched against the bare tag name (no leading `#`). Hierarchical tags like `work/tasks` are matched as the full string. Use character classes (`[Mm]cp`) for case-insensitivity.
obsidian_open_in_uiOpen a file in the Obsidian app UI. By default fails when the path does not exist; the `failIfMissing` flag controls the open-or-create behavior.3 params

Open a file in the Obsidian app UI. By default fails when the path does not exist; the `failIfMissing` flag controls the open-or-create behavior.

Parameters* required
path*string
Vault-relative path of the file to open.
newLeafboolean
Open in a new leaf (split pane) instead of the active one.default: false
failIfMissingboolean
When true (default), fails if the file does not exist. When false, allows Obsidian to create the file on open.default: true
obsidian_search_notesSearch the vault by text substring or JSONLogic predicate. Pick the mode that matches the query shape. Results paginate via opaque cursors: omit `cursor` for the first page, then pass `nextCursor` from the prior response. Text-mode hits additionally clip per file at `maxMatche...7 params

Search the vault by text substring or JSONLogic predicate. Pick the mode that matches the query shape. Results paginate via opaque cursors: omit `cursor` for the first page, then pass `nextCursor` from the prior response. Text-mode hits additionally clip per file at `maxMatche...

Parameters* required
mode*string
Which search algorithm to run. `text` matches a substring case-insensitively across filenames and note bodies, returning surrounding context windows. `jsonlogic` evaluates a JSONLogic tree against each note, with `var` paths into `path`, `content`, `frontmatter.<key>`, `tags`, and `stat.{ctime,mtime,size}`, plus `glob` and `regexp` operators.one of text · jsonlogic
logicobject
JSONLogic tree. Required for `jsonlogic` mode; ignored in `text` and `omnisearch` modes (use `query` instead — passing a string here will fail Zod validation since this field must be an object).
querystring
The query string. Required for `text` and `omnisearch` modes; ignored in `jsonlogic` mode (use `logic` instead — passing a JSONLogic tree here will fail Zod validation since this field must be a string).
cursorstring
Opaque cursor from a prior response. Omit for the first page. Page size is server-determined; do not assume a fixed value.
pathPrefixstring
Filter returned filenames by prefix (text mode only, applied client-side).
contextLengthinteger
Characters of context on each side of the match (text mode only).default: 100
maxMatchesPerHitinteger
Cap on match contexts returned per file in text mode. When clipped, the hit carries `truncated: true` and `totalMatches`.default: 10
obsidian_write_noteCreate or overwrite a note. With `section`, replaces just that heading/block/frontmatter section in place; nested headings need `Parent::Child` syntax — use `obsidian_get_note` with `format: "document-map"` to discover available targets. Whole-file writes fail with `file_exist...5 params

Create or overwrite a note. With `section`, replaces just that heading/block/frontmatter section in place; nested headings need `Parent::Child` syntax — use `obsidian_get_note` with `format: "document-map"` to discover available targets. Whole-file writes fail with `file_exist...

Parameters* required
target*value
Where the note lives.
content*string
Body to write. For heading sections, the new section body — do not repeat the heading line (it stays in place). Markdown unless `contentType` is `json`.
sectionobject
Optional sub-document target. When set, only this section is replaced; rest of the note is untouched.
overwriteboolean
Whole-file mode only (ignored when `section` is set). When `false` (default), the call fails with `file_exists` if the target note already exists — read it first and use `obsidian_patch_note` / `obsidian_append_to_note` / `obsidian_replace_in_note` for in-place edits, or retry with `overwrite: true` for a deliberate full replacement.default: false
contentTypestring
Content body format. Use "json" for typed frontmatter values or block-targeted table rows. JSON values must be valid JSON literals — strings need quoting (`"\"draft\""`, not `"draft"`), and numbers/booleans/arrays/objects pass through as-is.one of markdown · jsondefault: markdown
obsidian_append_to_noteAppend content to a note. **Without `section`: appends to the end of the file, or creates the file if it does not exist (your content becomes the full file).** With `section`: appends to the end of that heading/block/frontmatter; nested headings need `Parent::Child` syntax — u...5 params

Append content to a note. **Without `section`: appends to the end of the file, or creates the file if it does not exist (your content becomes the full file).** With `section`: appends to the end of that heading/block/frontmatter; nested headings need `Parent::Child` syntax — u...

Parameters* required
target*value
Where the note lives.
content*string
Body to append. Markdown unless `contentType` is `json`.
sectionobject
Optional sub-document target. When set, content is appended to that section instead of the file.
contentTypestring
Content body format. Use "json" for typed frontmatter values or block-targeted table rows. JSON values must be valid JSON literals — strings need quoting (`"\"draft\""`, not `"draft"`), and numbers/booleans/arrays/objects pass through as-is.one of markdown · jsondefault: markdown
createTargetIfMissingboolean
When `section` is provided, create the section if it does not already exist (otherwise the call fails when the section is missing).default: false
obsidian_patch_noteEdit a heading, block reference, or frontmatter field in place — append to, prepend to, or replace the target's body. Use `obsidian_get_note` with `format: "document-map"` to discover available targets first; nested headings need `Parent::Child` syntax.6 params

Edit a heading, block reference, or frontmatter field in place — append to, prepend to, or replace the target's body. Use `obsidian_get_note` with `format: "document-map"` to discover available targets first; nested headings need `Parent::Child` syntax.

Parameters* required
target*value
Where the note lives.
content*string
Body to insert/replace. Markdown unless `contentType` is `json`.
section*object
Which heading/block/frontmatter field to edit.
operation*string
How to apply `content` relative to the targeted section. `append` — at the end of the target's body (for headings, before the next sibling/parent heading; for frontmatter array fields, as a new array item). `prepend` — at the start. `replace` — swaps the target's body.one of append · prepend · replace
contentTypestring
Content body format. Use "json" for typed frontmatter values or block-targeted table rows. JSON values must be valid JSON literals — strings need quoting (`"\"draft\""`, not `"draft"`), and numbers/booleans/arrays/objects pass through as-is.one of markdown · jsondefault: markdown
patchOptionsobject
Optional flags: createTargetIfMissing, applyIfContentPreexists, trimTargetWhitespace.
obsidian_replace_in_noteSearch and replace inside a single note, literally or by regex. Replacements run in array order, each over the previous one's output. Use for edits that don't fit `obsidian_patch_note`'s structural targets — e.g., body-wide find-and-replace.2 params

Search and replace inside a single note, literally or by regex. Replacements run in array order, each over the previous one's output. Use for edits that don't fit `obsidian_patch_note`'s structural targets — e.g., body-wide find-and-replace.

Parameters* required
target*value
Where the note lives.
replacements*array
Replacements to apply in array order over the evolving content.
obsidian_manage_frontmatterGet, set, or delete a single frontmatter key on a note, atomically. `set` requires a JSON-typed `value` (string, number, boolean, array, or object).4 params

Get, set, or delete a single frontmatter key on a note, atomically. `set` requires a JSON-typed `value` (string, number, boolean, array, or object).

Parameters* required
key*string
Frontmatter field name.
valuevalue
Required when `operation` is `"set"`. JSON-typed value to write — strings, numbers, booleans, arrays, and objects all accepted.
target*value
Where the note lives.
operation*string
Operation to perform on `key`. `get` — read the current value. `set` — write `value`, creating the key if absent. `delete` — remove the key from frontmatter.one of get · set · delete
obsidian_manage_tagsAdd, remove, or list a note's tags. Defaults to the frontmatter `tags:` array — set `location` to `inline` or `both` to mutate the note body. `add` ensures the tag is present in the requested location(s); `remove` strips it; `both` reconciles across both representations. Inlin...4 params

Add, remove, or list a note's tags. Defaults to the frontmatter `tags:` array — set `location` to `inline` or `both` to mutate the note body. `add` ensures the tag is present in the requested location(s); `remove` strips it; `both` reconciles across both representations. Inlin...

Parameters* required
tagsarray
Tags to add or remove. Omit the leading `#`. Required for add/remove.
target*value
Where the note lives.
locationstring
Where to apply the change. Defaults to `frontmatter` (the canonical Obsidian tag location, leaves the body untouched). `inline` mutates the note body — `add` appends `#tag` at end-of-file. `both` is opt-in reconciliation of frontmatter and inline tag locations.one of frontmatter · inline · bothdefault: frontmatter
operation*string
`add` and `remove` mutate the note; `list` reads the current tag set.one of add · remove · list
obsidian_delete_notePermanently delete a note from the vault. Confirms with the user before deleting when the client supports interactive confirmation. Recovery requires the local trash in Obsidian — there is no API-level undo.1 params

Permanently delete a note from the vault. Confirms with the user before deleting when the client supports interactive confirmation. Recovery requires the local trash in Obsidian — there is no API-level undo.

Parameters* required
target*value
Which note to delete.

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

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework


Tools

Fourteen tools grouped by shape — readers fetch notes and metadata, writers create or surgically edit content, managers reconcile tags and frontmatter, and a guarded escape hatch dispatches Obsidian command-palette commands.

Tool NameDescription
obsidian_get_noteRead a note as raw content, full structured form (content + frontmatter + tags + stat, with optional outgoing links), structural document map, or a single section.
obsidian_list_notesList notes and subdirectories under a vault path. Recursive walk (default depth 2, max depth 20; 1000-entry cap) with optional extension and nameRegex filters.
obsidian_list_tagsList every tag found across the vault with usage counts, including hierarchical parents. Optional nameRegex post-filters the result set.
obsidian_list_commandsList Obsidian command-palette commands, optionally filtered by nameRegex on display name. Opt-in via OBSIDIAN_ENABLE_COMMANDS=true (paired with obsidian_execute_command).
obsidian_search_notesSearch the vault by text, JSONLogic, or BM25-ranked Omnisearch (when the plugin is reachable). Results paginate via opaque cursors.
obsidian_write_noteCreate a note, replace a single section in place, or — with overwrite: true — clobber an existing file. Refuses whole-file writes against an existing path by default.
obsidian_append_to_noteAppend content to a note. Without section, creates the file if missing. With section, appends to a specific heading, block, or frontmatter field (file must exist).
obsidian_patch_noteSurgical append / prepend / replace against a heading, block reference, or frontmatter field.
obsidian_replace_in_noteBody-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_frontmatterAtomic get / set / delete on a single frontmatter key.
obsidian_manage_tagsAdd, remove, or list tags. Defaults to the frontmatter tags: array; location: 'inline' or 'both' opts into mutating the note body.
obsidian_delete_notePermanently delete a note. Elicits human confirmation when the client supports it.
obsidian_open_in_uiOpen a file in the Obsidian app UI, with failIfMissing and newLeaf toggles.
obsidian_execute_commandExecute an Obsidian command-palette command by ID. Opt-in via OBSIDIAN_ENABLE_COMMANDS=true.

obsidian_get_note

Read a note in one of four projections, addressed by vault path, the active file, or a periodic note (daily, weekly, monthly, quarterly, yearly).

  • format: "content" — raw markdown body
  • format: "full" — content, frontmatter, tags, and file metadata; pass includeLinks: true to also parse outgoing wiki and markdown link references from the body (vault-internal only — external URLs are filtered)
  • format: "document-map" — catalog of headings, block references, and frontmatter fields
  • format: "section" — single heading/block/frontmatter section value (requires section); heading sections include the full subtree under that heading

Pair the document-map projection with obsidian_patch_note to discover edit targets before patching.


obsidian_search_notes

Up to three search modes selected by mode:

  • text — substring match with surrounding context windows. contextLength controls characters of context per side of each match (default 100; bump it for more context per hit). Optional pathPrefix filter (text mode only — passing pathPrefix in any other mode is rejected with path_prefix_invalid_mode).
  • jsonlogic — JSONLogic tree evaluated against path, content, frontmatter.<key>, tags, and stat.{ctime,mtime,size}; custom glob and regexp operators
  • omnisearch — BM25-ranked search via the community Omnisearch plugin. Supports quoted phrases, -exclusion, path: / ext: filters, typo tolerance, and PDF + OCR coverage (via Text Extractor). Only present in the mode enum when the plugin's HTTP server is reachable at startup; the upstream hard-caps results at 50 — narrow the query to surface more (the response carries truncated: true when the cap was likely hit).

Results paginate via opaque cursors per the MCP 2025-11-25 spec: omit cursor for the first page, then pass nextCursor from the prior response. Every result carries totalCount (post-path-policy, pre-pagination); nextCursor is omitted on the last page. Text-mode hits are additionally clipped per file at maxMatchesPerHit (default 10) so a single match-heavy note can't blow the response budget — clipped hits carry truncated: true and totalMatches.


obsidian_write_note

Create or surgically replace, with a protective default against accidental whole-file overwrites.

  • Without section — full-file PUT. Refuses to clobber an existing file unless overwrite: true is set. The file_exists (Conflict) error suggests obsidian_patch_note / obsidian_append_to_note / obsidian_replace_in_note for in-place edits.
  • With section — PATCH-with-replace against the named heading/block/frontmatter field, leaving the rest of the file untouched. The overwrite flag is ignored in section mode.

The output reports created: true when the call brought a new file into existence; false when it replaced an existing one or targeted a section. Every mutating tool also returns previousSizeInBytes and currentSizeInBytes so an agent can spot accidental clobbers, unexpected upstream behavior, or a typo path that landed at the wrong file.


obsidian_append_to_note

A combined upsert + section-append primitive that mirrors the upstream Local REST API behavior:

  • Without section — POST to /vault/{path}. Appends when the file exists, creates the file with your content as the entire body when it doesn't. The output's created: true flags the second branch so the agent can notice when a typo path or a not-yet-created daily note silently turned into a brand-new file.
  • With section — PATCH-with-append against the named heading, block reference, or frontmatter field. The file must exist (PATCH preflight throws note_missing otherwise). Pass createTargetIfMissing: true to bring the section itself into existence inside an existing file. Block-reference targets concatenate adjacent to the block line without a separator — include a leading newline in content if you want one.

previousSizeInBytes is 0 on the upsert-create branch and the actual file size otherwise; currentSizeInBytes is the post-write size read from the upstream after the operation. Compare deltas against Buffer.byteLength(content) to detect auto-newline injection or concurrent writers.


obsidian_patch_note

Surgical edits at a single document target.

  • operation: "append" adds after the section
  • operation: "prepend" adds before the section
  • operation: "replace" swaps it out
  • Targets: heading path, block reference ID, or frontmatter field

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 — treat search as an ECMAScript regex. With useRegex: true, the replacement honors $1 / $& capture-group references.
  • caseSensitive — when false, match case-insensitively
  • wholeWord — wrap the pattern in \b…\b; works in both literal and regex modes
  • flexibleWhitespace — substitute any run of whitespace in search with \s+. Literal mode only — has no effect when useRegex: true (express it directly).
  • replaceAll — when false, 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 frontmatter tags: array; the note body is left untouched
  • location: 'inline' — only inline #tag syntax in the body; add appends #tag at end-of-file
  • location: '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.

GoalConfig
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 anywhereOBSIDIAN_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

TypeURIDescription
Resourceobsidian://vault/{+path}A note in the vault — content, frontmatter, tags, and file metadata.
Resourceobsidian://tagsAll tags found across the vault, with usage counts.
Resourceobsidian://statusServer 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 instructions on initialize — 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 #tag syntax (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_PATHS and a global OBSIDIAN_READ_ONLY kill switch — denies are typed path_forbidden with the active scope echoed back in the error data
  • Opt-in command-palette pair (obsidian_list_commands + obsidian_execute_command) — registered only when OBSIDIAN_ENABLE_COMMANDS=true
  • Forgiving path resolution on obsidian_get_note and obsidian_open_in_ui — silently retries case-mismatched paths against the canonical filename, throws Conflict on ambiguous case matches, and enriches NotFound with Did you mean: …? suggestions when only near-matches exist. obsidian_delete_note is deliberately excluded — a destructive op shouldn't silently rewrite the target path.

Getting started

Add the following to your MCP client configuration file. The Obsidian Local REST API plugin must be installed and enabled in your vault — see Prerequisites.

{
  "mcpServers": {
    "obsidian-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["obsidian-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info",
        "OBSIDIAN_API_KEY": "your-local-rest-api-key"
      }
    }
  }
}

Or with npx (no Bun required):

{
  "mcpServers": {
    "obsidian-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "obsidian-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info",
        "OBSIDIAN_API_KEY": "your-local-rest-api-key"
      }
    }
  }
}

For Streamable HTTP, set the transport and start the server. Inline env vars work for one-off runs; for repeated use, copy values into .env (see .env.example) and run bun run start:http.

MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
# Server listens at http://127.0.0.1:3010/mcp by default

Prerequisites

  • Bun v1.3.11 or higher (or Node.js v24+).
  • The Obsidian Local REST API plugin v4.0.0 or later installed and enabled in your vault. Generate an API key in Settings → Community Plugins → Local REST API and copy it into OBSIDIAN_API_KEY.
  • This server defaults to http://127.0.0.1:27123 for simplicity. Enable "Non-encrypted (HTTP) Server" in the plugin settings to use it. To use the always-on HTTPS port instead, set OBSIDIAN_BASE_URL=https://127.0.0.1:27124; the plugin's self-signed cert is handled by OBSIDIAN_VERIFY_SSL=false (the default).

Installation

  1. Clone the repository:

    git clone https://github.com/cyanheads/obsidian-mcp-server.git
    
  2. Navigate into the directory:

    cd obsidian-mcp-server
    
  3. Install dependencies:

    bun install
    
  4. Configure environment:

    cp .env.example .env
    # edit .env and set OBSIDIAN_API_KEY
    

Configuration

VariableDescriptionDefault
OBSIDIAN_API_KEYRequired. Bearer token for the Obsidian Local REST API plugin.—
OBSIDIAN_BASE_URLBase URL of the Local REST API plugin. Use https://127.0.0.1:27124 for the always-on HTTPS port (self-signed cert).http://127.0.0.1:27123
OBSIDIAN_VERIFY_SSLVerify the TLS certificate. Default false because the plugin uses a self-signed cert. On Node, the dispatcher's rejectUnauthorized option handles this without any process-wide change. On Bun, the runtime ignores that option, so the service additionally sets NODE_TLS_REJECT_UNAUTHORIZED=0 — that fallback is scoped to Bun only.false
OBSIDIAN_REQUEST_TIMEOUT_MSPer-request timeout in milliseconds.30000
OBSIDIAN_ENABLE_COMMANDSOpt-in flag for the command-palette pair (obsidian_list_commands + obsidian_execute_command). Off by default — Obsidian commands are opaque and can be destructive.false
OBSIDIAN_READ_PATHSComma-separated vault-relative folder allowlist for read operations. Prefix-based with implicit recursion; case-insensitive; trailing slashes normalized. Unset = full vault. Write paths are implicitly readable.unset
OBSIDIAN_WRITE_PATHSComma-separated vault-relative folder allowlist for write operations. Same syntax as OBSIDIAN_READ_PATHS. Unset = full vault.unset
OBSIDIAN_READ_ONLYGlobal kill switch. When true, denies every write regardless of OBSIDIAN_WRITE_PATHS, and suppresses the OBSIDIAN_ENABLE_COMMANDS pair (commands can mutate).false
OBSIDIAN_OMNISEARCH_URLOverride URL for the Omnisearch plugin's HTTP server. When unset, derives from OBSIDIAN_BASE_URL host with port 51361 (falling back to http://localhost:51361). Probed once at startup — if reachable, the omnisearch mode is added to obsidian_search_notes; otherwise it's omitted from the tool schema. Restart the server to re-probe.derived
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_HOSTHost for the HTTP server.127.0.0.1
MCP_HTTP_PORTPort for the HTTP server.3010
MCP_HTTP_ENDPOINT_PATHEndpoint path for the JSON-RPC handler./mcp
MCP_PUBLIC_URLPublic origin override for TLS-terminating reverse-proxy deployments (landing page, Server Card, RFC 9728 metadata).unset
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_AUTH_SECRET_KEYRequired when MCP_AUTH_MODE=jwt. ≥32-char shared secret used to verify incoming JWTs.—
MCP_AUTH_DISABLE_SCOPE_CHECKSWhen true, bypasses per-tool scope enforcement after the auth-context presence check. Token signature, audience, issuer, and expiry validation remain intact. Use only when a custom claim can't be injected and combine with OBSIDIAN_READ_PATHS / OBSIDIAN_WRITE_PATHS / OBSIDIAN_READ_ONLY for access control. A WARNING is logged at startup whenever the bypass is active.false
MCP_LOG_LEVELLog level (RFC 5424).info
LOGS_DIRDirectory for log files (Node.js only).<project-root>/logs
OTEL_ENABLEDEnable OpenTelemetry instrumentation (spans, metrics, completion logs).false

See .env.example for the full list of optional overrides.

Running the server

Local development

  • Build and run the production version:

    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
    
  • Run checks and tests:

    bun run devcheck   # Lint, format, typecheck, security, changelog sync
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec
    

Docker

docker build -t obsidian-mcp-server .
docker run --rm -e OBSIDIAN_API_KEY=your-key -p 3010:3010 obsidian-mcp-server

The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/obsidian-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.

The image binds to 0.0.0.0 inside the container (required for Docker port mapping). For any deployment reachable beyond your own machine, set MCP_AUTH_MODE=jwt (with MCP_AUTH_SECRET_KEY) or oauth — otherwise the listener forwards your OBSIDIAN_API_KEY to the vault on behalf of every caller.

Project structure

DirectoryPurpose
src/index.tscreateApp() entry point — registers tools/resources and inits the Obsidian service.
src/configServer-specific environment variable parsing (OBSIDIAN_*) with Zod.
src/services/obsidianLocal REST API client, frontmatter operations, section extractor, domain types.
src/mcp-server/toolsTool definitions (*.tool.ts) and shared input schemas.
src/mcp-server/resourcesResource definitions (*.resource.ts).
src/mcp-server/promptsPrompt definitions (currently empty — CRUD/search shape doesn't benefit from a structured template).
tests/Vitest tests mirroring src/.
docs/Upstream OpenAPI spec for the Local REST API plugin and the generated tree.md.
changelog/Per-version release notes; CHANGELOG.md is the regenerated rollup.

Development guide

See CLAUDE.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
  • Register new tools and resources via the barrels in src/mcp-server/*/definitions/index.ts
  • Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields

Contributing

Issues and pull requests are welcome. Run checks and tests before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Featured
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Vibe Prospecting MCPVibe Prospecting MCP
Vibe Prospecting MCP
Connect Claude to +800M contacts, +150M companies. Find & Enrich leads in chat.
Try For Free →
Context.devContext.dev
Context.dev
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →

Configuration

OBSIDIAN_API_KEY*

Bearer token for the Obsidian Local REST API plugin (Settings → Community Plugins → Local REST API).

OBSIDIAN_BASE_URLdefault: http://127.0.0.1:27123

Base URL of the Obsidian Local REST API. Default: http://127.0.0.1:27123 (enable "Non-encrypted (HTTP) Server" in plugin settings). Use https://127.0.0.1:27124 for the always-on HTTPS port (self-signed cert; pair with OBSIDIAN_VERIFY_SSL=false).

OBSIDIAN_VERIFY_SSLdefault: false

Whether to verify the TLS certificate on the Obsidian endpoint. Default false because the plugin uses a self-signed cert.

OBSIDIAN_REQUEST_TIMEOUT_MSdefault: 30000

Per-request timeout in milliseconds.

OBSIDIAN_ENABLE_COMMANDSdefault: false

Opt-in flag for the command-palette pair (obsidian_list_commands + obsidian_execute_command). Off by default — Obsidian commands are opaque and can be destructive.

OBSIDIAN_READ_PATHS

Optional comma-separated vault-relative folder allowlist for reads. Prefix-based with implicit recursion; case-insensitive; trailing slashes normalized. Unset = full vault. Write paths are implicitly readable. Example: 'public/,projects/'.

OBSIDIAN_WRITE_PATHS

Optional comma-separated vault-relative folder allowlist for writes. Same syntax as OBSIDIAN_READ_PATHS. Unset = full vault. Example: 'projects/,scratch/'.

OBSIDIAN_READ_ONLYdefault: false

Global read-only kill switch. When true, every write is denied regardless of OBSIDIAN_WRITE_PATHS, and the command-palette pair is suppressed (commands can mutate). Useful for shared or public-facing deployments.

MCP_LOG_LEVELdefault: info

Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').

MCP_HTTP_HOSTdefault: 127.0.0.1

The hostname for the HTTP server.

MCP_HTTP_PORTdefault: 3010

The port to run the HTTP server on.

MCP_HTTP_ENDPOINT_PATHdefault: /mcp

The endpoint path for the MCP server.

MCP_AUTH_MODEdefault: none

Authentication mode to use: 'none', 'jwt', or 'oauth'.

Categories
Documents & KnowledgeSearch & Web Crawling
Registryactive
Packageobsidian-mcp-server
TransportSTDIO, HTTP
AuthRequired
Resources2
Tools verifiedJun 10, 2026
UpdatedJun 2, 2026
View on GitHub

Related Documents & Knowledge MCP Servers

View all →
Pdf Document Mcp

csoai-org/pdf-document-mcp

pdf-document-mcp MCP server by MEOK AI Labs
Mcp Document Converter

xt765/mcp-document-converter

Convert PDF, DOCX, HTML, Markdown, and Text for AI assistant context injection.
10
Markdown Formatter

io.github.xjtlumedia/markdown-formatter

AI Answer Copier — Convert Markdown to PDF, DOCX, HTML, LaTeX, CSV, JSON, XML, XLSX, RTF, PNG
3
Better Notion

io.github.ai-aviate/better-notion

Operate Notion with a single Markdown document — read, create, and update pages in one call.
2
Notion

suekou/mcp-notion-server

Notion MCP Server enables LLMs to access Notion workspaces with optional Markdown conversion to save tokens.
892
Docx

meterlong/mcp-doc

A powerful Word document processing service based on FastMCP, enabling AI assistants to create, edit, and manage docx files with full formatting support. Preserves original styles when editing content. 基于FastMCP的强大Word文档处理服务,使AI助手能够创建、编辑和管理docx文件,支持完整的格式设置功能。在编辑内容时能够保留原始样式和格式,实现精确的文档操作。
185