obsidian-mcp-server: Surgical Vault Edits for MCP Clients
Read, write, search, and surgically edit Obsidian vault notes, tags, and frontmatter via MCP. STDIO or Streamable HTTP.
At a glance
- What is it?
- cyanheads/obsidian-mcp-server exposes 14 tools and 3 resources over MCP so Claude, Cursor, VS Code and Codex can read, search and patch an Obsidian vault without rewriting whole files. The design bet is locality: patch a heading, a block reference or one frontmatter key instead of clobbering a note.
- Who is it for?
- Adopt it if your MCP client already edits files and you want those edits scoped to a heading, a block reference or a single frontmatter key rather than a full-file rewrite, and if you can run an Obsidian Local REST API endpoint with a key. Skip it if you only need to read notes, or if you cannot expose an HTTP API on the vault.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: agents rewrite notes they should only touch
A language model with filesystem access treats a markdown note as a string. Ask it to add a tag and it may return the entire file, reformatted, with your callout syntax and trailing whitespace quietly changed. Obsidian vaults are hostile to that: wiki links, block references, frontmatter arrays and periodic note naming are all conventions a generic writer does not preserve. cyanheads/obsidian-mcp-server is built for people who already run an MCP client against a vault and want the write path constrained. Its fourteen tools are grouped by shape rather than by feature: readers fetch notes and metadata, writers create or surgically edit content, managers reconcile tags and frontmatter, and one guarded escape hatch dispatches Obsidian command-palette commands. The intended user is a single operator pointing Claude Desktop, Cursor, VS Code or Codex at their own vault, not a team sharing a knowledge base. The README frames the whole thing in one line: read, write, search, and surgically edit notes, tags and frontmatter. The word doing the work there is surgically.
How the tools split reads from writes
The read side is projection-based. obsidian_get_note returns one of four shapes: raw markdown content, a full structured form with content plus frontmatter plus tags plus file stat, a document map of headings, block references and frontmatter fields, or a single section. The document-map projection is the one that matters for editing, because the README explicitly suggests pairing it with obsidian_patch_note to discover edit targets before patching. That is a two-call pattern: map first, then patch the locator the map returned.
The write side is deliberately narrow. obsidian_write_note creates a note, replaces a single section in place, or clobbers an existing file only when overwrite: true is passed, and it refuses whole-file writes against an existing path by default. obsidian_append_to_note appends to a note, or to a specific heading, block or frontmatter field when section is given, and in that second case the file must already exist. obsidian_patch_note does append, prepend or replace against a heading, block reference or frontmatter field. obsidian_replace_in_note does search-replace inside one note, scoped to the body by default, with literal or regex matching and capture-group replacement. obsidian_manage_frontmatter does atomic get, set or delete on a single key. Each of these has a blast radius of one note or less.
Search has three modes selected by mode. text is substring matching with context windows, contextLength controls characters per side (default 100), and pathPrefix is accepted in text mode only; passing it in any other mode is rejected with path_prefix_invalid_mode. jsonlogic evaluates a JSONLogic tree against path, content, frontmatter.<key>, tags and stat fields, with custom glob and regexp operators that take a pattern first and a field reference second. That argument order is not cosmetic: the README states that reversing it compiles the note's own field as the pattern, so glob then matches nothing and regexp fails outright. The same regexp construction is how backlinks are expressed, since there is no dedicated backlink tool or upstream endpoint for them. The third mode is BM25-ranked Omnisearch, available when that plugin is reachable.
Installing obsidian-mcp-server and running a first patch
There is no build step for normal use. The package is published to npm and the README offers one-click install badges for Claude Desktop, Cursor and VS Code, all of which pass OBSIDIAN_API_KEY in the client's env block. The underlying command is npx, so a manual client configuration looks like this:
{
"mcpServers": {
"obsidian-mcp-server": {
"command": "npx",
"args": ["-y", "obsidian-mcp-server"],
"env": { "OBSIDIAN_API_KEY": "your-api-key" }
}
}
}Set OBSIDIAN_API_KEY to the key from the Obsidian Local REST API plugin before starting the client. If the key is missing or wrong, the server starts but vault calls fail.
For a container, the repository ships a Dockerfile and publishes to ghcr.io. The image is built with Bun and runs as a slim production stage. Transport is chosen with environment variables, and the defaults come from .env.example:
MCP_TRANSPORT_TYPE=http bun ./dist/index.js
MCP_HTTP_PORT=3010
MCP_HTTP_HOST=127.0.0.1
MCP_HTTP_ENDPOINT_PATH=/mcpThat starts a Streamable HTTP listener on 127.0.0.1:3010 with the MCP endpoint at /mcp. Leave MCP_TRANSPORT_TYPE unset and it defaults to stdio, which is what a desktop client wants.
A first real use is the map-then-patch loop. Call obsidian_get_note with format: "document-map" on a note path to get its headings, block references and frontmatter keys. Then call obsidian_patch_note with that locator and an operation of append, prepend or replace. If a heading name is ambiguous, the section read echoes the locator it resolved to in sectionTarget as a full Parent::Child path, and when a bare leaf name matches several headings it lists every colliding path in candidates. The read still returns the first match, so check candidates before you patch.
Two tools are off by default. obsidian_list_commands and obsidian_execute_command require OBSIDIAN_ENABLE_COMMANDS=true, which the README describes as opt-in and pairs them together.
Where the surgical model breaks down
Ambiguity resolution is the sharpest edge. When a bare leaf heading name matches several headings, the read returns the first match and merely reports the collisions in candidates. A patch built on that read can land in the wrong section, and the tool will not stop you. The document-map step exists precisely to avoid this, but nothing enforces the two-call sequence.
The second constraint is the dependency on the Obsidian Local REST API. This server is a client of that API, not a parser of the vault directory, so the plugin has to be present and reachable. The README does not document rollback, and obsidian_delete_note is permanent: it always asks the user to confirm first, answering the call with a confirmation request that is retried with the answer, but there is no trash or undo path described.
Scale is bounded too. obsidian_list_notes does a recursive walk with a default depth of 2 and a maximum of 20, capped at 1000 entries. obsidian_list_tags is ordered by count descending and capped at limit, default 200 and maximum 10000, with the withheld remainder disclosed. Neither cap is adjustable beyond those numbers, so a very large vault needs narrower path prefixes rather than bigger limits.
Finally, this is the wrong tool if you want a general filesystem agent. It gives you no shell, no arbitrary file read outside the vault, and no bulk rewrite primitive. obsidian_replace_in_note is scoped to a single note. If your workflow is "restructure 400 notes," you will be issuing 400 calls.
How it differs from a plain filesystem MCP server
The obvious alternative is an MCP server that just exposes read_file and write_file over a directory, or the filesystem server that ships with most MCP clients. The difference is where the safety lives. A filesystem server moves bytes and trusts the model to preserve structure; it has no concept of a heading, a block reference or a frontmatter key, so every edit is a whole-file write and every write is a chance to reformat. This project puts the vault's structure in the tool schema. obsidian_patch_note cannot address a heading that does not exist, obsidian_manage_frontmatter touches one key atomically, and obsidian_write_note refuses to clobber an existing path unless you explicitly pass overwrite: true. That is a different failure profile: fewer catastrophic rewrites, more calls that return a locator error.
The cost is coupling. A filesystem server works on any directory and needs no plugin. This one needs Obsidian running with the Local REST API reachable, and it inherits that API's surface. If you want an agent to work on markdown that is not in a vault, or a vault you cannot expose over HTTP, the filesystem route is the one that works.
Maintenance, transport and licence
The repository is not archived, and the last push was on 2026-09-09, the same day v3.5.2 landed with periodic-route detection and tag/section resolution fixes. Recent releases have been frequent and small: v3.5.1 on 2026-09-04 widened 5xx classification and pinned session mode, and v3.5.0 on 2026-08-22 made tag removal and note replacement frontmatter-safe. The changelog directory is in the published files list, so upgrade notes travel with the package. Note that package.json on the default branch reads version 3.5.3 while the latest tagged release is v3.5.2, so the branch is ahead of the release.
The upgrade cost is mostly configuration drift, not code. Transport and auth are environment variables, and .env.example documents a mode switch worth reading before you expose the HTTP transport: MCP_AUTH_MODE accepts none, jwt or oauth, with none as the default, and MCP_AUTH_SECRET_KEY is required for jwt. In oauth mode the server reads granted scopes from a union of the scp, scope and mcp_tool_scopes JWT claims. The file states that standard OIDC providers compute scope from the client's requested scopes in authorization_code flow and ignore property mappings that try to override it, so per-tool scopes should be injected through mcp_tool_scopes instead. If no claim-injection path exists, MCP_AUTH_DISABLE_SCOPE_CHECKS=true falls back to path policy, and the README notes a WARNING is logged at startup. Also relevant to exposure: MCP_PUBLIC_URL is for a public origin behind a TLS-terminating proxy, and MCP_HTTP_MAX_BODY_BYTES defaults to 1 MiB, which the file says should be raised for large note writes.
Licensing is Apache-2.0, which permits commercial and private use and includes a patent grant. That is the extent of what the repository states; questions about your own distribution or attribution obligations belong with your legal team, not with this review. The Docker build has one caveat worth knowing before you attempt a multi-arch image: the Dockerfile pins the build stage to BUILDPLATFORM and its comments explain that under QEMU, Bun's JavaScriptCore aborts with a MemoryExhaustion assertion at roughly 21 MB peak, so a cross-arch build of that stage fails on an arm64 host.
Editorial conclusion
Adopt it if your MCP client already edits files and you want those edits scoped to a heading, a block reference or a single frontmatter key rather than a full-file rewrite, and if you can run an Obsidian Local REST API endpoint with a key. Skip it if you only need to read notes, or if you cannot expose an HTTP API on the vault. Verify first that your Obsidian build ships the Local REST API plugin, that OBSIDIAN_API_KEY is set in the client's env block, and that your client can reach the transport you chose: stdio for a local process, or the default MCP_HTTP_PORT=3010 endpoint if you run it as a service.
Frequently asked questions
What is obsidian-mcp-server?
It is a TypeScript MCP server that reads, writes, searches and surgically edits Obsidian vault notes, tags and frontmatter. It exposes 14 tools and 3 resources over STDIO or Streamable HTTP, and it talks to the vault through the Obsidian Local REST API.
Is there an obsidian mcp server?
Yes. cyanheads/obsidian-mcp-server is published on npm as obsidian-mcp-server and is also published as a container image on ghcr.io, with one-click install links for Claude Desktop, Cursor and VS Code in the README.
how to install obsidian mcp server
For a desktop client, point the MCP config at npx with args ["-y", "obsidian-mcp-server"] and set OBSIDIAN_API_KEY in the env block. For a container, the repository ships a Dockerfile and a ghcr.io image, and transport is selected with MCP_TRANSPORT_TYPE.
how to setup obsidian mcp server
Setup means supplying OBSIDIAN_API_KEY from the Obsidian Local REST API plugin, then choosing a transport. The default is stdio; setting MCP_TRANSPORT_TYPE=http starts a listener on MCP_HTTP_HOST 127.0.0.1 and MCP_HTTP_PORT 3010 with the endpoint at MCP_HTTP_ENDPOINT_PATH /mcp.
obsidian mcp server vscode
The README includes a VS Code install badge that registers the server with command npx and args ["-y", "obsidian-mcp-server"], passing OBSIDIAN_API_KEY through the env block. The same pattern applies to Cursor and Claude Desktop.
Does obsidian-mcp-server work with Claude Code and Cursor?
The README provides install links for Claude Desktop, Cursor and VS Code, and the repository carries .claude-plugin and .codex-plugin directories at the top level. All of them configure the same npx command with OBSIDIAN_API_KEY in the environment.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cyanheads-obsidian-mcp-server)