obsidian-cli-mcp
An MCP server that wraps the official Obsidian CLI (obsidian, bundled with the Obsidian desktop app) instead of the Local REST API plugin. Each server instance is pinned to a single vault, so you run one entry per vault.
Why the CLI instead of the REST API?
The REST-API MCP (mcp-obsidian) needs the Local REST API community plugin, an API key, and a dedicated port per vault. This server drives the Obsidian app through its built-in CLI, which needs no plugin and no API key, and exposes a much richer surface: tasks, frontmatter properties, backlinks, search-with-context, vault metadata, and more.
Requirement: the Obsidian desktop app must be running with the target vault open. The CLI talks to the live app, not the files on disk.
Install
The server is a uv project. No global install is needed — MCP clients launch it on demand with uv run.
git clone <this repo> && cd obsidian-cli-mcp
uv sync # install dependencies
uv run pytest # run the test suite (no live Obsidian needed)
Configure (per vault)
Add one entry per vault to your MCP client config (e.g. Claude Desktop's claude_desktop_config.json). The vault is selected with the OBSIDIAN_VAULT environment variable — run obsidian vaults to see the exact names.
{
"mcpServers": {
"obsidian-cli-void": {
"command": "uv",
"args": ["run", "--directory", "/ABSOLUTE/PATH/TO/obsidian-cli-mcp", "obsidian-cli-mcp"],
"env": { "OBSIDIAN_VAULT": "void" }
},
"obsidian-cli-notebook": {
"command": "uv",
"args": ["run", "--directory", "/ABSOLUTE/PATH/TO/obsidian-cli-mcp", "obsidian-cli-mcp"],
"env": { "OBSIDIAN_VAULT": "notebook" }
}
}
}
Environment variables
| Variable | Required | Default | Purpose | | --- | --- | --- | --- | | OBSIDIAN_VAULT | yes | — | Vault name this instance targets (injected as vault= on every call). | | OBSIDIAN_CLI_PATH | no | obsidian | Path to the CLI binary if it isn't on PATH. | | OBSIDIAN_VERIFY_READS | no | true | Run the vault guard before reads too (not just writes). | | OBSIDIAN_ALLOW_PERMANENT_DELETE | no | false | Allow delete_note(permanent=True) to bypass trash. | | OBSIDIAN_TIMEOUT | no | 30 | Per-command subprocess timeout (seconds). | | OBSIDIAN_GUARD_TTL | no | 5 | Seconds to cache a successful vault-guard check. | | OBSIDIAN_LOG_LEVEL | no | INFO | Log level (logs go to stderr). |
Tools
Read / navigate: read_note, search_vault, list_files, list_folders, note_info, list_backlinks, list_tags, list_tasks, list_properties, read_property, vault_info, list_vaults
Write / organise: create_note, add_to_note, set_property, remove_property, move_note, rename_note, delete_note, update_task
Escape hatch: run_obsidian — run any other CLI command (e.g. bookmarks, wordcount, history). Disruptive commands (restart, reload, eval, devtools, plugins:restrict, and all dev:*) are blocked.
Notes are addressed by file (resolve by name, like a wikilink) or path (exact folder/note.md). Most read tools fall back to the active note when both are omitted; write tools require an explicit target.
Safety: the vault guard
The Obsidian CLI silently falls back to the active vault when vault=<name> names a vault that isn't currently open. To prevent acting on the wrong vault, this server verifies — via vault info=name — that the configured vault is the one actually responding before every write (and before reads, by default). If it isn't, the tool fails with a clear message instead of touching another vault.
This also means the CLI reports failures on stdout with exit code 0; the server detects them by inspecting the output, so genuine errors surface as MCP tool errors rather than being mistaken for success.
License
MIT © digster












