obsidian-mcp-server
Provides tools for reading, writing, searching, and managing notes in an Obsidian vault, including frontmatter, tags, backlinks, periodic notes, and vault statistics.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@obsidian-mcp-serversearch my vault for notes about project Alpha"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
obsidian-mcp-server
A Go Model Context Protocol (MCP) server for Obsidian vaults. It gives AI agents and development tools direct filesystem access to your vault — no running Obsidian instance required.
Features
Read, write, and list notes and directories via MCP tools
Frontmatter — parse and update YAML frontmatter with format-preserving rewrites
Tags — extract inline
#tags, aggregate vault-wide tag counts, add/remove tagsBacklinks — on-demand reverse link graph (wikilinks and markdown links)
Mutations — heading-anchored patch, safe delete, and move with confirmation guards
Full-text search — BM25 Okapi ranked search with match snippets
Regex/glob search — RE2 regex or filepath glob search across paths and content
Path security — 4-layer validation: lexical checks, ignore/extension filters, case-insensitive existence lookup, and symlink escape prevention
Stdio transport — works with any MCP client (Claude Code, Claude Desktop, etc.)
Streamable HTTP transport (optional) — TLS 1.3 + bearer token auth by default, loopback-only unless explicitly widened; see
--transport httpin Configuration andSECURITY.md§ HTTP transportZero Obsidian dependency — operates on the vault directory directly
Token counting — responses include approximate token counts (cl100k_base)
Related MCP server: Obsidian MCP Server
MCP Tools
Tool | Description | Params |
| Read a note's content and metadata |
|
| Create or update a note |
|
| List files and subdirectories |
|
| Read YAML frontmatter from a note |
|
| Set or remove frontmatter keys (format-preserving) |
|
| Add or remove a tag on a note |
|
| Aggregate all tags across the vault with counts |
|
| Find all notes that link to a target note |
|
| Apply a heading-anchored patch to a note |
|
| Move a note to |
|
| Move or rename a note within the vault (requires confirm); rewrites unambiguous inbound links by default |
|
| BM25 full-text search with ranked results and match snippets |
|
| Search using RE2 regex or glob pattern |
|
| Read the content of multiple notes in a single request |
|
| Get metadata for multiple notes without reading full content |
|
| Get aggregate statistics about the entire vault |
|
| Get a periodic note (daily, weekly, monthly, quarterly, or yearly) |
|
| Get the N most recent periodic notes |
|
| List notes most recently modified in the vault |
|
| Audit the vault for hygiene issues: orphans, dangling links, untagged notes, duplicate titles |
|
| Return a note's heading tree (level, text, line number) without its body |
|
| Read a bounded range of lines from a note |
|
| Rename a tag vault-wide, across frontmatter and inline occurrences |
|
| Scoped search-and-replace within a single note, literal or regex |
|
search_notes, search_regex, list_directory, and get_recent_changes results also include an
obsidian://open deepLink field per note, built from --vault-name (see Configuration below).
Notes
patch_note semantics: position controls where content is inserted relative to the heading:
before— inserted immediately before the heading lineafter— inserted after the heading's body (before the next same-level or higher heading)replace_body— replaces everything between the heading line and the next same-level heading
search_notes parameters:
Parameter | Type | Default | Description |
| string | required | Search query. Multi-term queries use OR logic; the full phrase contributes a bonus score. |
| integer | 20 | Maximum number of results |
| integer | 3 | Maximum match snippets per result |
| boolean | false | Case-sensitive matching |
| boolean | true | Include note body in scoring |
| boolean | true | Include frontmatter values in scoring |
| string | — | Glob pattern to restrict search scope (e.g. |
| boolean | false | Format JSON with indentation |
Returns: { query, results: [{ path, score, matchCount, matches: [{line, snippet, term}], tokenCount, reason }], total }
search_regex parameters:
Parameter | Type | Default | Description |
| string | required | RE2 regex or glob pattern |
| boolean | false | Treat pattern as a filepath glob ( |
| string | content |
|
| integer | 20 | Maximum number of results |
| integer | 5 | Maximum match snippets per result |
| boolean | false | Format JSON with indentation |
Returns: { pattern, scope, results: [{ path, matches: [{line, snippet}] }], total }
Batch tools (read_multiple_notes, get_notes_info): The paths parameter is a JSON array string — e.g. '["Notes/foo.md","Notes/bar.md"]'. summary:true returns headOf (first N runes from headChars, default 200) instead of full content, which is useful for large notes to stay within context limits. Both tools enforce --max-batch (default 10); requests with more paths are silently truncated and the response includes "truncated": true.
Optimistic concurrency (if_match / etag): read_note, read_multiple_notes, and get_notes_info return a SHA-256 etag of the note's content. Pass that value as if_match on write_note, patch_note, update_frontmatter, manage_tags, delete_note, or move_note to make the write conditional — if the note has changed since you read it, the call fails with a REVISION_CONFLICT error instead of silently overwriting someone else's edit. if_match is optional everywhere; omitting it writes unconditionally, as before. Two edge cases: passing if_match for a note that doesn't exist yet is always a conflict (it never creates the note), and move_note's dryRun:true preview does not enforce if_match.
Periodic notes (get_periodic_note, get_recent_periodic_notes): Configuration (folder and date format per granularity) is read from .obsidian/plugins/periodic-notes/data.json inside the vault. If that file is missing, built-in defaults are used: daily notes use YYYY-MM-DD in Daily Notes/, weekly notes use gggg-[W]ww in Weekly Notes/, and so on. offset=0 resolves to the current period, offset=-1 to the previous period (yesterday, last week, etc.), and offset=+1 to the next period. createIfMissing=true creates an empty note at the resolved path if it does not already exist.
get_vault_stats: Returns noteCount, totalBytes, totalLinks, totalTags, topTags (top 20 by count), oldestNote, newestNote, and vaultRoot. Setting includeTokenCounts:true runs token counting across every note — this is expensive for large vaults and is disabled by default.
audit_notes classes:
orphans— notes that have no tags AND no incoming wikilinks or markdown links (completely isolated notes)dangling-links— notes containing links to vault paths that do not exist (broken references)untagged— notes with no frontmatter tags and no inline#tagsduplicate-titles— multiple notes sharing the same filename stem, which causes wikilink ambiguity
Each class result is capped at limit entries (default 20). When results are truncated, the response includes "truncated": true.
MCP Prompts
Prompts are server-defined conversation starters that the host (Claude Code, Claude Desktop) exposes in its UI. Each prompt pulls live vault data and constructs a ready-to-use message for the LLM.
Prompt | Description | Arguments |
| Summarize a note: 3 key bullets, entities, open questions |
|
| Review a daily note: carryover TODOs, link suggestions, missing tags |
|
| Weekly retrospective from the last 7 daily notes |
|
| Suggest related notes worth linking, grouped by relationship type |
|
| Audit orphans, dangling links, untagged notes, duplicate titles; prioritize fixes | (none) |
Prompts are invoked from the host's prompt picker (e.g. / in Claude Code). They never modify the vault.
MCP Resources
Resources are read-only vault data that the host can attach directly to a conversation context window — no explicit tool call required.
Resource / Template | URI | MIME | Description |
Vault statistics |
|
| Note count, total size, top 10 tags, vault root |
Tag index |
|
| All tags with note counts, sorted by frequency |
Note content |
|
| Raw markdown (frontmatter + body) for any vault note |
Periodic note |
|
| Current daily / weekly / monthly / quarterly / yearly note |
Backlinks |
|
| All notes linking to the target, with line numbers and snippets |
Static resources (obsidian://vault/*) are always available in the resource picker. Template resources are resolved when the host reads them — if the note does not exist, the resource returns an explanatory empty body instead of an error.
Installation
Install script
Downloads the right binary for your OS/architecture, verifies its checksum, and installs it to
~/.local/bin (override with a second argument):
curl -fsSL https://raw.githubusercontent.com/tylern91/obsidian-mcp-server/main/install.sh | shPin a version instead of latest:
curl -fsSL https://raw.githubusercontent.com/tylern91/obsidian-mcp-server/main/install.sh | sh -s -- v0.2.0Claude Desktop (.mcpb bundle)
Download obsidian-mcp-<version>.mcpb from the latest release and double-click it — Claude Desktop installs the extension and prompts for your vault path. No manual JSON editing.
Release binary
Download a prebuilt binary from the latest release, verify its checksum, and install:
# macOS (Apple Silicon) — swap the asset name for your platform (darwin-amd64, linux-amd64, linux-arm64)
curl -fLO https://github.com/tylern91/obsidian-mcp-server/releases/latest/download/obsidian-mcp-<version>-darwin-arm64.tar.gz
curl -fLO https://github.com/tylern91/obsidian-mcp-server/releases/latest/download/obsidian-mcp-<version>-darwin-arm64.tar.gz.sha256
shasum -a 256 -c obsidian-mcp-<version>-darwin-arm64.tar.gz.sha256
tar -xf obsidian-mcp-<version>-darwin-arm64.tar.gz
install -m 0755 obsidian-mcp-<version>-darwin-arm64/obsidian-mcp ~/.local/bin/obsidian-mcpHomebrew (macOS/Linux)
brew tap tylern91/obsidian-mcp
brew install obsidian-mcpgo install
Requires Go 1.27+ to build from source. Building with GOTOOLCHAIN=local on an older Go
requires upgrading first — GOTOOLCHAIN=auto (the default since Go 1.21) downloads a matching
toolchain automatically.
go install github.com/tylern91/obsidian-mcp-server/cmd/obsidian-mcp@latestBuild from source
Requires Go 1.27+. Building with GOTOOLCHAIN=local on an older Go
requires upgrading first — GOTOOLCHAIN=auto (the default since Go 1.21) downloads a matching
toolchain automatically.
git clone https://github.com/tylern91/obsidian-mcp-server.git
cd obsidian-mcp-server
make buildUsage
Claude Code
claude mcp add obsidian -s user \
-e OBSIDIAN_VAULT_PATH="/path/to/your/vault" \
-- obsidian-mcpClaude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp",
"args": ["--vault", "/path/to/your/vault"],
"env": {}
}
}
}Cursor
Edit the vault path after install — the deeplink can't know it in advance.
VS Code
Edit the vault path after install — the deeplink can't know it in advance.
Direct
obsidian-mcp --vault /path/to/your/vaultPrint the version and exit:
obsidian-mcp --versionConfiguration
Configuration follows CLI flag > environment variable > default precedence.
Flag | Env Var | Default | Valid values |
|
| (required) | Absolute or relative path to an existing directory. Validated at startup — non-existent paths or files (not dirs) cause an immediate error. Surrounding whitespace is trimmed. |
| — | — | Prints the binary version to stdout and exits. Does not require |
|
|
| Comma-separated list. Each entry should start with |
|
|
| Comma-separated list of file/directory names to skip during traversal. Match is by name (not glob). Whitespace trimmed; empties discarded. |
|
|
| CLI: bare |
|
|
| Integer ≥ |
|
|
| Integer ≥ |
|
|
| One of: |
|
|
| CLI: bare |
|
|
| Integer ≥ |
|
| the vault directory's basename | Used to build the |
|
|
|
|
|
|
| Bind address for |
|
|
| Port for |
|
|
| Allows |
|
| (empty) | Comma-separated Host header allowlist for |
|
| (empty) | Comma-separated Origin header allowlist for |
|
| (none) | Path to a PEM file of trusted client CAs. Enables mandatory mutual TLS for |
Examples
# Override extensions to include Excalidraw drawings
obsidian-mcp --vault ./my-vault --extensions ".md,.canvas,.excalidraw"
# Add a custom ignore pattern alongside defaults (you must repeat the defaults
# you want to keep — values fully replace, not merge)
obsidian-mcp --vault ./my-vault \
--ignore ".obsidian,.git,node_modules,.DS_Store,.trash,Archive,Templates"
# Enable pretty JSON via env var (any ParseBool-compatible truthy value works)
OBSIDIAN_PRETTY=1 obsidian-mcp --vault ./my-vault
OBSIDIAN_PRETTY=true obsidian-mcp --vault ./my-vault
# Verbose logging while debugging an integration
OBSIDIAN_LOG_LEVEL=debug obsidian-mcp --vault ./my-vault
# Streamable HTTP transport instead of stdio (loopback only, TLS + bearer
# token auto-generated on first run — see SECURITY.md § HTTP transport)
obsidian-mcp --vault ./my-vault --transport http --http-port 8443Precedence in action: with OBSIDIAN_LOG_LEVEL=debug exported, obsidian-mcp --vault ... --log-level info runs at info — the explicit flag wins. Unset flags inherit the env var; if neither is set, the default applies.
Security
All paths are validated through a 4-layer security model before any filesystem operation:
Lexical — rejects absolute paths,
..traversal, and null bytesFilter — blocks ignored patterns (
.git,.obsidian, etc.) and unapproved extensionsExistence — verifies the file exists with a case-insensitive fallback; rejects ambiguous matches
Symlink — resolves symlinks and verifies the target remains inside the vault root
The optional --transport http listener has its own security posture (TLS, bearer auth,
loopback-only default, session binding) — see SECURITY.md § HTTP transport.
Project Structure
cmd/obsidian-mcp/ Entry point, transport selection (stdio/http)
internal/
config/ CLI flags, env vars, defaults
vault/ Path security, CRUD, frontmatter, tags, links, mutations
tools/ MCP tool registrations and handlers
response/ Token counting, JSON formatting
search/ BM25 ranked search, regex/glob
periodic/ Periodic note resolution (Phase 4)
prompts/ MCP Prompt templates
resources/ MCP Resource registrations
httptransport/ Streamable HTTP transport: TLS, bearer auth, session binding
testdata/vault/ Fixture vault for testsDevelopment
make build # compile binary
make test # go test -race ./...
make vet # go vet ./...
make fmt # gofmt + goimports
make run ARGS="--vault /path/to/vault"
make help # list all targetsLicense
This server cannot be deployed
Maintenance
Related MCP Connectors
Agent-native notes, tasks, dev-docs, vaults, sync & handoffs. MCP + OpenAPI dual surface.
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
OCR, transcription, file extraction, and image generation for AI agents via MCP.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables MCP clients to interact with Obsidian vaults via filesystem operations and optional REST API integration for advanced UI commands. It features multi-vault auto-discovery, concurrent-safe file handling, and comprehensive tools for searching, reading, and managing vault content.125,145 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to search, read, and append content to notes in an Obsidian vault via the MCP protocol.5,145 npmBSD Zero Clause
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to remotely access and interact with Obsidian vaults via MCP, supporting note operations, tag management, graph queries, and command execution.4GPL 3.0
- FlicenseAqualityBmaintenanceA filesystem-based MCP server for Obsidian vaults that enables LLMs to browse, search, read, write, and edit Markdown notes directly on disk without requiring Obsidian to be running.6803 npm1-