Obsidian MCP Server
The Obsidian MCP Server enables AI models to interact with Obsidian vaults via secure API communication, providing:
File Operations: List, retrieve, append, and update files in the vault, including hierarchical directory structures.
Search Capabilities: Perform full-text searches with context, execute complex JsonLogic queries, and filter files based on content or metadata.
Property Management: Retrieve and update YAML frontmatter properties, manage tags, titles, and custom fields.
Advanced Tools: Use JsonLogic for complex filtering, manage timestamps, and intelligently merge or replace properties.
Integration: Works with Obsidian's Local REST API plugin and provides a standardized interface via the Model Context Protocol.
Security: Offers API key authentication, rate limiting, and SSL options for secure operations.
Enables AI models to interact with Obsidian vaults through a standardized interface, providing file operations, search capabilities, and property management for knowledge bases in Obsidian
Click on "Install 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 for notes about machine learning projects"
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.
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 |
| Read a note as raw content, full structured form (content + frontmatter + tags + stat), structural document map, or a single section. |
| List notes and subdirectories at a vault path with a recursive walk (default depth 2 — structural overview; max 20) bounded by a 1000-entry cap. Optional |
| List every tag found across the vault with usage counts, including hierarchical parents. |
| List Obsidian command-palette commands available for execution. (paired with |
| Search the vault by text, Dataview DQL, or JSONLogic — capped at 100 hits with overflow indicator. |
| Create a note, replace a single section in place, or — with |
| Append content to a note, or to a specific heading/block/frontmatter section. |
| Surgical |
| Body-wide search-replace inside a single note. Literal or regex matching, with |
| Atomic |
| Add, remove, or list tags — reconciles frontmatter |
| Permanently delete a note. Elicits human confirmation when the client supports it. |
| Open a file in the Obsidian app UI, with |
| Execute an Obsidian command-palette command by ID. |
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 metadataformat: "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
Three search modes selected by mode:
text— substring match with surrounding context windows; optionalpathPrefixfilter (text mode only — passingpathPrefixindatavieworjsonlogicmode is rejected withpath_prefix_invalid_mode)dataview— Dataview DQL (TABLE …) for path/date/metadata queries;file.mtime,file.path, etc. are queryablejsonlogic— JSONLogic tree evaluated againstpath,content,frontmatter.<key>,tags, andstat.{ctime,mtime,size}; customglobandregexpoperators
Results are capped at 100 hits. When the upstream returns more, an excluded indicator surfaces the overflow count and a hint to narrow the query. 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.
obsidian_patch_note
Surgical edits at a single document target.
operation: "append"adds after the sectionoperation: "prepend"adds before the sectionoperation: "replace"swaps it outTargets: 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. Reconciles both representations:
Frontmatter
tags:arrayInline
#tagsyntax in the body
add ensures the tag is present in the requested location(s); remove strips it. 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. Without elicitation, the destructiveHint annotation surfaces the operation in the host's approval flow.
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. Both obsidian_execute_command and its discovery partner obsidian_list_commands register only when the operator sets OBSIDIAN_ENABLE_COMMANDS=true; both are omitted from the surface otherwise.
Related MCP server: Obsidian MCP
Resources
Type | URI | Description |
Resource |
| A note in the vault — content, frontmatter, tags, and file metadata. |
Resource |
| All tags found across the vault, with usage counts. |
Resource |
| Server reachability, plugin version, and auth status of the Obsidian Local REST API. |
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.Pluggable auth on the HTTP transport:
none,jwt,oauthStructured 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 operationsTag reconciliation across both representations: frontmatter
tags:array and inline#tagsyntax (skipping fenced code blocks)Search across three modes: text, Dataview DQL, JSONLogic — with overflow indicator when results exceed the 100-hit cap
Optional human-in-the-loop confirmation for destructive deletes via
ctx.elicitOpt-in command-palette pair (
obsidian_list_commands+obsidian_execute_command) — registered only whenOBSIDIAN_ENABLE_COMMANDS=trueForgiving 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-matches exist.obsidian_delete_noteis 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": {
"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": {
"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 defaultPrerequisites
Bun v1.3.11 or higher (or Node.js v22+).
The Obsidian Local REST API plugin 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:27123for simplicity. Enable "Non-encrypted (HTTP) Server" in the plugin settings to use it. To use the always-on HTTPS port instead, setOBSIDIAN_BASE_URL=https://127.0.0.1:27124; the plugin's self-signed cert is handled byOBSIDIAN_VERIFY_SSL=false(the default).
Installation
Clone the repository:
git clone https://github.com/cyanheads/obsidian-mcp-server.gitNavigate into the directory:
cd obsidian-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env # edit .env and set OBSIDIAN_API_KEY
Configuration
Variable | Description | Default |
| Required. Bearer token for the Obsidian Local REST API plugin. | — |
| Base URL of the Local REST API plugin. Use |
|
| Verify the TLS certificate. Default |
|
| Per-request timeout in milliseconds. |
|
| Opt-in flag for the command-palette pair ( |
|
| Transport: |
|
| Host for the HTTP server. |
|
| Port for the HTTP server. |
|
| Endpoint path for the JSON-RPC handler. |
|
| Auth mode: |
|
| ≥32-char shared secret used to verify incoming JWTs. | — |
| Log level (RFC 5424). |
|
| Enable OpenTelemetry. |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Hot-reload dev mode:
bun run dev:stdio bun run dev:httpBuild and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:httpRun 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-serverThe 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.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing ( |
| Local REST API client, frontmatter operations, section extractor, domain types. |
| Tool definitions ( |
| Resource definitions ( |
| Prompt definitions (currently empty — CRUD/search shape doesn't benefit from a structured template). |
| Vitest tests mirroring |
| Upstream OpenAPI spec for the Local REST API plugin and the generated |
| Per-version release notes; |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools and resources via the barrels in
src/mcp-server/*/definitions/index.tsWrap 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 testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be installed
Maintenance
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/obsidian-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server