obsidian-mcp
Enables interaction with multiple Obsidian vaults through natural language, supporting note operations, search, and vault management with both REST and filesystem backends.
README
obsidian-mcp
A FastMCP server for multiple Obsidian vaults. It talks to the Obsidian Local REST API plugin when Obsidian is running, and falls back to direct filesystem access when it isn't — so your notes stay reachable whether or not the app is open.
Every tool takes an optional vault argument, and obsidian_switch_vault sets the default for unqualified calls.
Why both backends
| REST (Obsidian running) | Filesystem (always) | |
|---|---|---|
| Search | Obsidian's own index and ranking | substring scan, all terms must match |
| Tags | resolved by Obsidian, with counts | parsed from frontmatter + inline #tags |
| Backlinks | ✅ from the link graph | ❌ not available |
| Active note / open in UI / commands | ✅ | ❌ |
| Works with Obsidian closed | ❌ | ✅ |
| Works on a headless box or over SSH | ❌ | ✅ |
The router probes REST health once and caches the result for OBSIDIAN_HEALTH_TTL seconds, so a closed Obsidian doesn't cost a failed connection on every call. If REST drops mid-call it retries on the filesystem automatically. Errors that mean "what you asked for doesn't exist" (missing note, bad path) are not retried — falling back would just fail again, slower.
Every response reports which backend served it, so a caller can tell whether it's seeing Obsidian's view or the raw files.
Setup
git clone <your-repo> obsidian-mcp && cd obsidian-mcp
uv venv && uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
pytest
Requires Python 3.11+. Works with MCP Python SDK 1.x (FastMCP) and 2.x (MCPServer) — _sdk.py papers over the rename.
Configuring vaults
Define vaults using whichever of these fits; the first one present wins.
A TOML file (best for 3+ vaults) — OBSIDIAN_VAULTS_FILE=~/.config/obsidian-mcp/vaults.toml:
default = "personal"
[vaults.personal]
path = "/Users/you/Vaults/Personal"
description = "Homelab, hobbies"
daily_folder = "Journal"
[vaults.work]
path = "/Users/you/Vaults/Work"
description = "Employer notes"
rest_base_url = "https://127.0.0.1:27125" # note: NOT the default port
api_key = "this-vault's-own-key"
Inline JSON — OBSIDIAN_VAULTS='{"default":"work","vaults":{...}}'. Accepts port/protocol/host shorthand instead of rest_base_url, and camelCase keys, so configs from other Obsidian MCP servers mostly paste straight in.
Per-vault env vars — OBSIDIAN_VAULT_WORK_PATH, OBSIDIAN_VAULT_WORK_API_KEY, OBSIDIAN_VAULT_WORK_REST_BASE_URL, etc. The segment between OBSIDIAN_VAULT_ and the field name is the vault name.
Legacy single-vault vars — OBSIDIAN_VAULT_PATH / OBSIDIAN_REST_BASE_URL / OBSIDIAN_API_KEY still work and define one vault named default.
⚠️ One port per vault
Every vault's Local REST API plugin defaults to port 27124, and only one process can bind a port. If you run two vaults at once, open each vault's Settings → Local REST API → Advanced and give it a unique port (27124, 27125, 27126…), then toggle the plugin off and on.
Each vault also generates its own API key, so a mismatched port usually shows up as a 401 rather than silently reading the wrong vault. To check positively:
obsidian_backend_status(verify=true)
That cross-checks each vault's REST root listing against its configured path and warns if they disagree.
Per-vault settings
path, rest_base_url, api_key, verify_ssl, ca_cert, daily_folder, daily_format, description
Global settings
| Variable | Default | Notes |
|---|---|---|
OBSIDIAN_DEFAULT_VAULT |
first defined | Overrides default in the config file. |
OBSIDIAN_PREFER_REST |
true |
Set false to always use the filesystem. |
OBSIDIAN_READ_ONLY |
false |
Disables every mutating tool, across all vaults. |
OBSIDIAN_MAX_FILE_BYTES |
2000000 |
Refuse to read anything larger. |
OBSIDIAN_HEALTH_TTL |
20 |
Seconds to cache each vault's REST health probe. |
OBSIDIAN_REQUEST_TIMEOUT |
15 |
Per-request timeout in seconds. |
To trust the plugin's cert rather than disabling verification, per vault:
curl -k https://127.0.0.1:27125/obsidian-local-rest-api.crt -o ~/.config/obsidian-mcp/work.crt
# then in vaults.toml: ca_cert = "~/.config/obsidian-mcp/work.crt" and verify_ssl = true
Register it
Claude Code:
claude mcp add obsidian -- \
uv --directory /path/to/obsidian-mcp run obsidian-mcp
Then set the env vars in .mcp.json or your shell profile.
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"obsidian": {
"command": "uv",
"args": ["--directory", "/path/to/obsidian-mcp", "run", "obsidian-mcp"],
"env": {
"OBSIDIAN_VAULTS_FILE": "/Users/you/.config/obsidian-mcp/vaults.toml"
}
}
}
}
Tools
Vaults — obsidian_list_vaults, obsidian_switch_vault
Reads — obsidian_list_notes, obsidian_read_note, obsidian_search_notes, obsidian_list_tags, obsidian_get_active_note*, obsidian_backend_status
Writes — obsidian_write_note, obsidian_append_to_note, obsidian_patch_note, obsidian_append_to_daily_note, obsidian_move_note, obsidian_delete_note
App control — obsidian_open_note, obsidian_list_commands, obsidian_run_command*
* requires Obsidian to be running. These deliberately do not fall back — they return a message telling you to start Obsidian, rather than silently doing something different from what you asked.
All 17 tools accept an optional vault. Targeting works two ways, on purpose:
- Per-call —
obsidian_search_notes(vault="work", query="..."). Explicit and stateless; the right choice when a task spans vaults. - Session default —
obsidian_switch_vault(vault="work")changes where unqualified calls go, so a long stretch of work in one vault doesn't repeat the argument.
A per-call vault always overrides the session default. Switching affects this server session only — it doesn't touch any vault's contents or change which vault Obsidian has open.
obsidian_backend_status is the one to call first when something fails with a connectivity error; with no vault named it probes all of them and reports which backends are live.
Safety choices
- Deletes are recoverable by default.
obsidian_delete_notemoves the note to the vault's.trash, restorable from inside Obsidian.permanent=trueis opt-in. Because the plugin'sDELETEis unconditional, trashing is routed through the filesystem backend even when REST is live. - Absolute paths and
..traversal are rejected, not silently rewritten. Resolved paths are checked against the vault root. obsidian_write_notewon't clobber an existing note withoutoverwrite=true.OBSIDIAN_READ_ONLY=truedisables every mutating tool in one switch — useful for a research-only session.
Known limitations
- Moving a note does not rewrite wikilinks. Only Obsidian does that, and only for moves made inside the app. Search for the old name afterwards if it matters.
- Frontmatter patches reformat the YAML block. Values round-trip through PyYAML, so
tags: [a, b]comes back as a block list. Content is preserved; formatting isn't. - Filesystem search has no fuzzy matching or ranking beyond a title-match boost. When Obsidian is running you get its real index instead.
- The filesystem backend doesn't see unsaved editor buffers. A note being actively edited may be stale on disk by a few seconds.
- Cross-vault operations aren't atomic.
obsidian_move_noteworks within one vault only; moving between vaults means read, write, delete as separate calls. obsidian_get_active_noteis per-vault, and only answers for a vault whose Obsidian window is open with the plugin bound to that vault's configured port.
Worth knowing before you build on this
As of v3+, the Local REST API plugin ships its own built-in MCP server at https://127.0.0.1:27124/mcp/ (streamable HTTP, bearer auth). If all you want is Obsidian access while Obsidian is running, point your client at that and skip this project entirely.
This server earns its place when you want the things that one can't do: working with the vault while Obsidian is closed, running on a headless machine, read-only enforcement, trash-by-default deletes, or custom workflow tools shaped around your own vault conventions.
Development
pytest # 40 tests, no Obsidian required
ruff check obsidian_mcp tests
npx @modelcontextprotocol/inspector uv --directory . run obsidian-mcp
The test suite fakes the REST backend, so fallback and multi-vault routing are covered without a running Obsidian.
推荐服务器
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 模型以安全和受控的方式获取实时的网络信息。