Obsidian Native MCP
LLM-optimized MCP server for Obsidian vaults with surgical edits, hash-based concurrency safety, and no whole-file rewrites.
README
<div align="center">
<img src="assets/banner.png" alt="Obsidian Native MCP banner" width="800" />
LLM-optimized MCP server for Obsidian vaults Surgical edits, hash-based concurrency safety, no whole-file rewrites.
</div>
A Model Context Protocol server that gives AI assistants (Claude Desktop, Cursor, Rovo Dev, etc.) direct, safe, context-efficient access to your Obsidian vaults.
Two ways to use it:
- Obsidian plugin — 1-click install, auto-discovers vaults, settings UI for per-tool toggles, runs inside Obsidian over HTTP/SSE with a bearer token.
- CLI — standalone Node binary, configured via env var or config file, speaks JSON-RPC over stdio with Content-Length framing.
Why Obsidian Native MCP
The defining design goal is minimize how many bytes the LLM has to push around per edit. Every read returns content plus cryptographic hashes; every write declares the precondition hash it expects. The result: most edits become tiny str_replaces or unified-diff patches instead of full file rewrites.
| Feature | Obsidian Native MCP | Typical Obsidian MCP server |
|---|---|---|
| Edit model | str_replace, apply_patch, apply_edits — surgical by default |
Read whole file → write whole file |
| Concurrency safety | Cryptographic preconditions (expected_*_hash) on every write |
None — silent clobbering |
| Structural awareness | mdast-AST: code-fenced "headings" never treated as headings | Regex hacks that corrupt code blocks |
| Frontmatter | Real YAML parser with nested key paths | Hand-rolled line matching |
| Atomicity | Multi-file bulk.apply with rollback |
None |
| Permissions | Read-only mode + per-tool toggle + per-vault subdir allow/deny | All-or-nothing |
| Audit trail | JSONL log with content hashes before/after every mutation | None |
| Multi-vault | First-class | Usually one vault |
Installation
Obsidian plugin (recommended)
- Open Obsidian → Settings → Community Plugins → Browse
- Search for "Native MCP" and install
- Enable in Community Plugins
- Open plugin settings: select which vaults to expose, optionally toggle per-tool permissions, copy the MCP URL
CLI (standalone)
npm install -g obsidian-native-mcp
Build from source
git clone https://github.com/usrivastava92/obsidian-native-mcp.git
cd obsidian-native-mcp
npm install
npm run build
Configuration
Plugin
Auto-discovers all your Obsidian vaults from Obsidian's own config. Pick which to expose in plugin settings. Plugin also surfaces a bearer token and the MCP URL.
A Performance budgets section in plugin settings lets you cap long-running operations. All limits default to 0 = unlimited — raise or lower them freely without restriction.
| Setting | Description |
|---|---|
| Max files scanned | Max .md files scanned per search.content / vault.info call |
| Max bytes read | Max raw bytes of file content read per call |
| Max bulk ops | Max ops accepted by a single bulk.apply call |
| Deadline (ms) | Wall-clock time limit for long-walk tools (best-effort; checked once per file) |
CLI
Either an env var or a config file.
# Single vault
export OBSIDIAN_VAULT_PATHS=/Users/me/my-obsidian-vault
# Multiple vaults (semicolons on all platforms)
export OBSIDIAN_VAULT_PATHS=/Users/me/personal;/Users/me/work
Config file at ~/.config/obsidian-native-mcp/vaults.json:
{
"vaults": {
"personal": "/Users/me/personal-notes",
"work": "/Users/me/work-vault"
}
}
Optional flags:
obsidian-native-mcp --read-only # all write tools disabled
obsidian-native-mcp --vault notes=/path # ad-hoc named vault
obsidian-native-mcp --config ./my.json # explicit config file
Performance budget env vars
All default to 0 (unlimited). Set any to a positive integer to cap that resource:
MCP_MAX_FILES_SCANNED=500 # files per search.content / vault.info call
MCP_MAX_BYTES_READ=10000000 # raw bytes per call (~10 MB)
MCP_MAX_BULK_OPS=50 # ops per bulk.apply call
MCP_DEADLINE_MS=30000 # wall-clock ms ceiling for long-walk tools
These are defaults — they are never enforced as hard system limits. Set them to whatever makes sense for your vault and workflow.
Usage
Obsidian plugin
Add the URL from plugin settings to your claude_desktop_config.json:
{
"mcpServers": {
"obsidian-native-mcp": {
"url": "http://127.0.0.1:9789/sse?token=YOUR_TOKEN"
}
}
}
CLI
{
"mcpServers": {
"obsidian-native-mcp": {
"command": "obsidian-native-mcp",
"env": {
"OBSIDIAN_VAULT_PATHS": "/Users/me/my-obsidian-vault"
}
}
}
}
Tools
All tools accept an optional vault parameter; with a single vault configured, it's inferred. Every read returns hashes used by writes as preconditions.
Read tools
| Tool | What it returns | Notes |
|---|---|---|
vault.list |
All configured vaults | — |
vault.info |
Stats per vault | _budget supported |
file.list |
Paged file listing | recursive, pattern (glob), limit, offset |
file.find |
Find files by name | exact / substring / glob / regex |
file.read |
Full file content + contentHash + totalLines |
Use freely — guidelines/AGENTS.md/etc. |
file.read_range |
Line range + rangeHash |
Cheaper for big files |
outline |
Heading skeleton + sectionHash per heading |
Sub-KB even for 5000-line files |
heading.find |
All matches (line, level, sectionHash) |
Returns all — caller disambiguates |
block.find |
Block ref location + blockHash |
Structural-type aware (list/table/paragraph) |
frontmatter.get |
Whole frontmatter or single nested key | YAML-aware |
tags.list |
Tags from frontmatter + body | Code-fence aware |
links.get |
Outlinks, backlinks, or both | Typed: wiki/embed/header/block/markdown |
metadata.read |
Frontmatter + headings + tags + links + hashes | One-shot context dump |
search.content |
Paged full-text matches with per-line hashes | _budget supported; pre-filters before parse |
Write tools — surgical primaries
| Tool | Shape | Why |
|---|---|---|
str_replace |
{file, find, replace, occurrence?, expected_content_hash?} |
The default editing verb — quote what you see |
apply_patch |
Unified diff | Multi-hunk edits in one shot; context lines act as preconditions |
apply_edits |
[{find, replace, occurrence?}, ...] |
Multi-edit, atomic per file |
Write tools — structural (when you have the address)
| Tool | Notes |
|---|---|
heading.replace_body |
Requires expected_section_hash |
heading.rename |
Optionally update wiki-link references |
block.replace |
Requires expected_block_hash; preserves list/table prefix |
block.rename |
Renames a ^id and updates references |
frontmatter.set |
Nested key path; YAML-safe round-trip |
frontmatter.delete |
Nested key path |
lines.replace |
Requires expected_range_hash |
lines.insert |
Insert at line N |
Write tools — whole-file & metadata
| Tool | Notes |
|---|---|
file.create |
Create-only — errors if file exists |
file.replace |
Whole-file rewrite — heavy, requires expected_content_hash |
file.append |
Cheap, no read needed |
file.move |
Default on_conflict: error; alternatives: overwrite, rename |
file.delete |
Defaults to .obsidian/trash; hard delete requires expected_content_hash |
Power & batch
| Tool | Notes |
|---|---|
bulk.apply |
Multi-file, multi-op batch. atomic: true → snapshot + rollback. _budget supported |
regex.replace |
Two-step: server returns proposal token + diff → caller confirms |
file.diff |
Diff between two contentHash versions (when cache has them) |
Per-call budget overrides (_budget)
vault.info, search.content, and bulk.apply all accept an optional _budget object that overrides the server-level config for that single call only. This lets an AI agent tighten or relax limits based on what it knows about the task:
{
"tool": "search.content",
"arguments": {
"query": "important term",
"directory": "Projects/",
"_budget": {
"maxFilesScanned": 200,
"maxBytesRead": 5000000,
"deadlineMs": 10000
}
}
}
| Field | Applies to | Description |
|---|---|---|
maxFilesScanned |
vault.info, search.content |
Max .md files to scan this call (0 = unlimited) |
maxBytesRead |
vault.info, search.content |
Max raw bytes to read this call (0 = unlimited) |
deadlineMs |
vault.info, search.content |
Wall-clock limit in ms for this call (0 = no limit) |
maxBulkOps |
bulk.apply |
Max ops for this batch (0 = unlimited) |
When a budget is hit, the tool returns truncated: true with a hint and (for search.content) a nextOffset the agent can use to resume pagination. No error is thrown — the agent gets partial results and can decide what to do next.
Prompts
Place markdown in any vault's Prompts/ folder with mcp-tools-prompt in the frontmatter; Templater-style <% tp.mcpTools.prompt(name, hint) %> placeholders become MCP prompt arguments automatically.
Concurrency safety
Every read returns one or more hashes. Every write that operates on an existing range requires the matching expected_*_hash. If the file changed underneath you (a human edit in Obsidian, a parallel tool call, etc.), the write returns:
{
"ok": false,
"error": {
"code": "STALE_PRECONDITION",
"current_content_hash": "sha256:…",
"current_section_hash": "sha256:…"
}
}
The model refreshes from the new hash and retries. No silent clobbering.
Permissions
- Read-only mode — plugin toggle or CLI
--read-onlyflag disables every write tool. - Per-tool toggle — disable individual tools (e.g., turn off
file.deletefor less-trusted clients). - Per-vault subdir allow/deny — limit a client to a vault subtree.
Audit log
Every mutating call appends one JSONL line to <vault>/.obsidian/plugins/native-mcp/audit.log:
{
"ts": "2026-05-21T13:00:00Z",
"tool": "str_replace",
"vault": "notes",
"file": "Daily/2026-05-21.md",
"args_hash": "sha256:…",
"before_hash": "sha256:…",
"after_hash": "sha256:…",
"dry_run": false,
"ok": true
}
Long-walk tools (search.content, vault.info) also emit telemetry fields:
{
"ts": "2026-05-21T13:00:01Z",
"tool": "search.content",
"vault": "notes",
"duration_ms": 412,
"files_scanned": 347,
"bytes_read": 2891024,
"truncated": true,
"abort_reason": "budget"
}
| Field | Description |
|---|---|
duration_ms |
Wall-clock time for the operation in milliseconds |
files_scanned |
Number of .md files read (after pre-filter; excludes cache hits on misses) |
bytes_read |
Raw bytes of file content read before mdast parsing |
truncated |
true if the operation was cut short by a budget or deadline |
abort_reason |
"budget" · "deadline" · "cancelled" — why it stopped early |
Rotates at 5 MB by default.
Security
- Runs locally only — loopback (
127.0.0.1) for HTTP, stdio for CLI. - HTTP transport requires a startup-generated bearer token in the SSE URL.
- Origin header allowlist enforced; CORS is not
*. - Request bodies capped at 5 MB; max-sessions and idle TTL applied.
- Path-traversal protection on every vault-relative path.
- Only vaults you explicitly select are accessible.
License
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。