Server · #181 trending MCP repo · ↗ +30 stars this week
Obsidian Local Rest API
coddingtonbear/obsidian-local-rest-api
A secure REST API and Model Context Protocol (MCP) server for your vault.
- Stars
- 3.1k
- This week
- +30
- Forks
- 363
- Open issues
- 5
- Latest release
5.5.2
Get started
Add it to your agent
claude mcp add --transport http obsidian https://127.0.0.1:27124/mcp/Run in your terminal. Add --scope project to share it with your team through .mcp.json.
codex mcp add obsidian-local-rest-api -- npx mcp-remote@latest https://127.0.0.1:27124/mcp/ --header 'Authorization: Bearer <your-api-key>'Saved to ~/.codex/config.toml.
{
"mcpServers": {
"obsidian-local-rest-api": {
"command": "npx",
"args": [
"mcp-remote@latest",
"https://127.0.0.1:27124/mcp/",
"--header",
"Authorization: Bearer <your-api-key>"
]
}
}
}Add to ~/.cursor/mcp.json (or .cursor/mcp.json in a project).
{
"servers": {
"obsidian-local-rest-api": {
"type": "stdio",
"command": "npx",
"args": [
"mcp-remote@latest",
"https://127.0.0.1:27124/mcp/",
"--header",
"Authorization: Bearer <your-api-key>"
]
}
}
}Add to .vscode/mcp.json, then start it from the MCP view in Copilot Chat.
gemini mcp add obsidian-local-rest-api npx mcp-remote@latest https://127.0.0.1:27124/mcp/ --header 'Authorization: Bearer <your-api-key>'Saved to .gemini/settings.json.
Documentation
README
Local REST API with MCP
Give your scripts, browser extensions, and AI agents a direct line into your Obsidian vault via a secure, authenticated REST API.
- Interactive API docs: https://coddingtonbear.github.io/obsidian-local-rest-api/
- Obsidian Community page: https://community.obsidian.md/plugins/obsidian-local-rest-api/
- What you can do
- Quick start
- API overview
- Patching notes
- Targeting specific sections
- Searching
- Event streams
- Server state
- MCP (Model Context Protocol)
- API Extensions
- Contributing
- Credits
What you can do
Access your vault through the REST API or the built-in MCP server — both interfaces expose the same core capabilities, so scripts, browser extensions, and AI agents all speak the same language.
- Read, create, update, or delete notes — full CRUD on any file in your vault, including binary files
- Surgically patch specific sections — target a heading, block reference, or frontmatter key and append, prepend, replace, delete, or move just that section without touching the rest of the file
- Search your vault — simple full-text search or structured JsonLogic queries against note metadata (frontmatter, tags, path, content)
- Follow vault events — subscribe to Obsidian events (a note created, its frontmatter changed, a file opened) as a filtered Server-Sent Events stream
- Access the active file — read or write whatever note is currently open in Obsidian
- List and execute commands — trigger any Obsidian command as if you'd used the command palette
- Query tags — list all tags across your vault with usage counts
- Open files in Obsidian — tell Obsidian to open a specific note in its UI
- Extend the API — other plugins can register their own routes via the API extension interface
All requests are served over HTTPS with a locally generated certificate and gated behind API key authentication.
Quick start
After installing and enabling the plugin, open Settings → Local REST API to find your API key and certificate.
REST API
# Check the server is running (no auth required)
curl -k https://127.0.0.1:27124/
# List files at the root of your vault
curl -k -H "Authorization: Bearer <your-api-key>" \
https://127.0.0.1:27124/vault/
# Read a note
curl -k -H "Authorization: Bearer <your-api-key>" \
https://127.0.0.1:27124/vault/path/to/note.md
# Read a specific heading (URL-embedded target)
curl -k -H "Authorization: Bearer <your-api-key>" \
https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section
# Append a line to a specific heading (PATCH with a JSON instruction)
curl -k -X PATCH \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
--data '{"targetType":"heading","target":["My Section"],"operation":"append","content":"New line of content"}' \
https://127.0.0.1:27124/vault/path/to/note.md
To avoid certificate warnings, you can download the plugin's certificate authority from https://127.0.0.1:27124/obsidian-local-rest-api.crt and trust it in your OS or browser, or point your HTTP client at it directly (for example curl --cacert obsidian-local-rest-api.crt ...). The plugin generates its own certificate authority on first run and serves a server certificate signed by it, so the download is a CA certificate rather than the server certificate itself. That CA is name-constrained: it can only vouch for 127.0.0.1, localhost, your configured binding host, and the hostnames you list under Subject alternative names, so trusting it does not let it (or anyone who obtains its key) impersonate other sites.
MCP clients
The MCP server runs at https://127.0.0.1:27124/mcp/ and requires that you provide your bearer token for authentication via an Authorization header (i.e. Authorization: Bearer <your-api-key>). Because the plugin uses a locally generated certificate authority, you may need to either trust that certificate in your OS/client, or use the plain HTTP endpoint at http://127.0.0.1:27123/mcp/ (enable it under Settings → Local REST API → Enable HTTP server).
Claude Code
Claude Code has native HTTP MCP support. The quickest way to add the server is via the CLI:
claude mcp add --transport http obsidian https://127.0.0.1:27124/mcp/ \
--header "Authorization: Bearer <your-api-key>"
Or add it manually to .mcp.json in your project root (project-scoped) or configure it user-wide via claude mcp add --scope user:
{
"mcpServers": {
"obsidian": {
"type": "http",
"url": "https://127.0.0.1:27124/mcp/",
"headers": {
"Authorization": "Bearer <your-api-key>"
}
}
}
}
Claude Desktop
Claude Desktop does not natively support remote HTTP MCP servers, but you can bridge it with mcp-remote (requires Node.js). Add the following to claude_desktop_config.json:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"obsidian": {
"command": "npx",
"args": [
"mcp-remote@latest",
"https://127.0.0.1:27124/mcp/",
"--header",
"Authorization: Bearer <your-api-key>"
]
}
}
}
Restart Claude Desktop after saving the file.
Cursor
Cursor supports the Streamable HTTP MCP transport. Add the following to ~/.cursor/mcp.json (global) or .cursor/mcp.json (project-specific):
{
"mcpServers": {
"obsidian": {
"url": "https://127.0.0.1:27124/mcp/",
"headers": {
"Authorization": "Bearer <your-api-key>"
}
}
}
}
Other clients
Any MCP client that supports the Streamable HTTP transport can connect to https://127.0.0.1:27124/mcp/ with an Authorization: Bearer <your-api-key> header. Consult your client's documentation for the exact configuration format.
API overview
| Endpoint | Methods | Description |
|---|---|---|
/vault/{path} |
GET PUT PATCH POST DELETE | Read, write, or delete any file in your vault |
/active/ |
GET PUT PATCH POST DELETE | Operate on the currently open file |
/search/simple/ |
POST | Full-text search across all notes |
/search/ |
POST | Structured search via JsonLogic |
/commands/ |
GET | List available Obsidian commands |
/commands/{commandId}/ |
POST | Execute a command |
/tags/ |
GET | List all tags with usage counts |
/open/{path} |
POST | Open a file in the Obsidian UI |
/ |
GET | Server status and authentication check |
/mcp/ |
GET POST | MCP (Model Context Protocol) server — connect AI agents directly to your vault |
For full request/response details, see the interactive docs.
The configuration directory is off-limits
The API refuses to read or write any file inside Obsidian's configuration directory (app.vault.configDir, normally .obsidian) — for both REST and MCP, reads as well as writes. A request for such a path is rejected with 403 (error code 40321) at the REST layer or a path error from an MCP tool.
That directory holds plugin code and each plugin's data.json — including this plugin's own, where your API key is stored. Writing into it is effectively remote code execution, since Obsidian runs an enabled plugin's main.js; reading from it leaks those secrets. Blocking it keeps a credential documented as "vault file access" from silently granting more (see GHSA-66m9-r757-qvq7).
The check is against where a path lands on disk, not just how it is spelled: a Windows 8.3 short name such as OBSIDI~1, a differently cased spelling on a case-insensitive filesystem, or a symlink that points into the configuration directory is refused the same way. Search and the tag listing skip any indexed note that lives there too, so a symlinked folder cannot hand the contents out by another route.
If you deliberately manage your Obsidian configuration through the API, turn on Settings → Local REST API → Advanced settings → Allow access to the configuration directory. It is off by default, and turning it on grants every holder of your API key that access.
Failed authentication is throttled
A source that presents a wrong credential -- an incorrect API key, or an invalid, expired or spent signed URL -- ten times within a minute has its further wrong credentials refused with 429 (error code 42901) and a Retry-After header until the minute is up. This applies to every route the plugin authenticates, including /mcp/ and GET / (routes an extension plugin publishes without authentication are not behind it): the root route answers 200 with authenticated: false to a wrong key, so it would otherwise be the cheapest place to guess from, and a wrong key sent there counts like a wrong key anywhere else.
Nothing else is slowed down. A request carrying the correct key or a valid signed URL is never counted or delayed, so MCP clients and scripts that make many requests in quick bursts are unaffected, and so is a client with the right key while another process on the same machine is being refused. A request that presents no credential at all is not counted either -- it has made no guess -- and is answered 401, or served on the routes that need no authentication, however many of them arrive. The counter is kept in memory per source address and is cleared by a plugin reload.
Conditional writes
Two clients that read the same note and then each write their edited copy back would otherwise lose the first write without a word. Every write to a file can carry a precondition instead, so it fails rather than overwriting a change it never saw:
# Read the note; the ETag response header is its current version
curl -i -H "Authorization: Bearer $KEY" https://127.0.0.1:27124/vault/notes/todo.md
# => ETag: "a1b2c3"
# Write it back only if nobody has changed it since
curl -X PUT -H "Authorization: Bearer $KEY" -H 'If-Match: "a1b2c3"' \
-H "Content-Type: text/markdown" --data-binary @todo.md \
https://127.0.0.1:27124/vault/notes/todo.md
# => 204, with the new version in ETag -- or 412 if the note changed, and nothing written
# Create a note only if it does not exist yet
curl -X PUT -H "Authorization: Bearer $KEY" -H "If-None-Match: *" \
-H "Content-Type: text/markdown" --data-binary @new.md \
https://127.0.0.1:27124/vault/notes/new.md
If-Match works on every write: PUT, POST, PATCH and DELETE on /vault/{path} and /active/, MOVE and COPY on /vault/{path}, and writes aimed at a heading, block or frontmatter field. It takes the ETag from a GET, the version from a document map or note JSON — all three are the same token — or the ETag a previous write answered with. A list matches if any entry does, and * matches any file that exists. A failed precondition is 412 (error code 41200), and the message names the file's current version. Writes made through the API are queued per file, so two clients holding the same version cannot both pass the check.
The ETag sent with a note's HTML rendering or its note JSON is not this token, because those also depend on other files; read note JSON's version field instead. MCP's write tools take the same precondition as an ifMatch argument (and vault_write/vault_append an ifNoneMatch: "*"), vault_read returns the version, and the write tools return the new one.
Browser clients and response headers
Several endpoints answer in a response header rather than in the body: ETag carries a file's version for conditional writes, Content-Location tells you which file a targeted or /active/ request actually resolved to, Markdown-Patch-Warnings reports what a PATCH had to work around, Deprecation warns that a format is sunsetting, and Mcp-Session-Id carries the session for a sessionful MCP connection.
Browsers hide response headers from JavaScript unless the server opts them in, so the API sends Access-Control-Expose-Headers: * and all of them are readable with response.headers.get(...). Safari honours the wildcard from 15.4 onward; older browsers see only the CORS-safelisted headers. Requests made with credentials: "include" are not supported — the API authenticates with a bearer token and sends Access-Control-Allow-Origin: *, which browsers reject for credentialed requests.
Patching notes
The PATCH method is one of the most useful features of this API. It lets you make targeted edits without rewriting entire files.
Send a JSON instruction: an operation (replace, prepend, append, or delete) applied to a scope (content, marker, markerAndContent, or parent) of a target — a heading (addressed as an array of heading texts from the top level down), a block reference, or a frontmatter key. The payload rides in content (a string), value (JSON, for frontmatter values), or destination (a heading move):
# Replace the value of a frontmatter field
curl -k -X PATCH \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
--data '{"targetType":"frontmatter","target":"status","operation":"replace","value":"done"}' \
https://127.0.0.1:27124/vault/path/to/note.md
Heading levels inside a content string are relative to the target (a leading # becomes a direct child). Advisory warnings (e.g. a heading rebased past level 6) come back as percent-encoded JSON in the Markdown-Patch-Warnings response header — decode with decodeURIComponent before parsing. Pass ifMatch (the version from a document map) for optimistic concurrency, or send an If-Match header as with any other conditional write.
Note: Whitespace is library-owned — your content is reduced to trimmed, canonical form (leading and trailing blank lines are meaningless), and the API itself supplies the blank line wherever inserted content faces body text, so an
appendorprependalways lands as its own block and never merges into an existing paragraph. Heading lines, existing blank lines, and each document's spacing style are preserved as-is. See the interactive docs for worked examples.
To continue an existing block instead of starting a new one — say, extending a list — add within to a heading instruction: an index selecting one of the section's top-level body blocks (0-based in document order, negative counting from the end, so -1 is the last block). A within edit splices literally, so you own the joint:
# Add an item to the last list under "Log" (the leading \n continues the block)
curl -k -X PATCH \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
--data '{"targetType":"heading","target":["Log"],"within":-1,"operation":"append","content":"\n- new item"}' \
https://127.0.0.1:27124/vault/path/to/note.md
With markerAndContent scope, prepend/append instead insert a new block beside the indexed one. Indices are positional, so read the document map first and pair the edit with ifMatch.
Raw-content mode
If your client templates markdown into the request body (Shortcuts, Tasker, curl from a template), JSON-escaping that content into an instruction is fragile. Raw-content mode moves the instruction's fields out of the body — target in the URL (or in Target-Type/Target headers with an explicit Markdown-Patch-Version: 2), operation and options in headers — and the body is the raw payload, no JSON escaping required:
# Append a templated line under a heading — no JSON escaping anywhere
curl -k -X PATCH \
-H "Authorization: Bearer <your-api-key>" \
-H "Operation: append" \
-H "Content-Type: text/markdown" \
--data "- $TEMPLATED_CONTENT" \
https://127.0.0.1:27124/vault/notes/daily.md/heading/Log
A text/* body is the content carrier, an application/json body the value carrier, and no body at all carries nothing (a delete, or a move via a Destination header). Target-Scope, Within (the instruction's within index as a plain integer, e.g. -1), Create-Target-If-Missing, Reject-If-Content-Preexists, and If-Match headers round out the instruction. See the interactive docs for the header encodings and the full details.
Already using the older header-driven PATCH format? It spread the instruction across request headers instead of a JSON body, and is deprecated and will be removed in 6.0. It still works — send
Markdown-Patch-Version: 1to opt back into it (the same header also selects the legacy::-joined document map on GET), and responses served by it carry aDeprecation: true; sunset-version="6.0"header. To upgrade, drop that header and move each header into the JSON body; the interactive docs have the field-by-field mapping table. If your content contains headings, adjust their levels too: 1.x wrote them literally, and 2.x treats them as relative to the target (see the migration guide there).
See the interactive docs for the full instruction schema and options.
Targeting specific sections
You can read or write a specific part of a note — a heading, block reference, or frontmatter field — without fetching or replacing the whole file. This works on GET, PUT, POST, and PATCH requests (for PATCH this is raw-content mode — add an Operation header).
Append /<target-type>/<target> after the filename. Each nested heading level is its own path segment, so a heading whose text contains :: needs no escaping:
# Read the content under a specific heading
curl -k -H "Authorization: Bearer <your-api-key>" \
https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section
# Read a nested heading (one path segment per level)
curl -k -H "Authorization: Bearer <your-api-key>" \
https://127.0.0.1:27124/vault/path/to/note.md/heading/Work/Meetings
# Read a frontmatter field
curl -k -H "Authorization: Bearer <your-api-key>" \
https://127.0.0.1:27124/vault/path/to/note.md/frontmatter/status
# Replace the content of a heading via PUT (heading levels are normalized for you)
curl -k -X PUT \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: text/markdown" \
--data "Updated content" \
https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section
# Append to a heading via POST
curl -k -X POST \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: text/markdown" \
--data "Appended content" \
https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section
Supported target types: heading, block, frontmatter.
A targeted URL is ambiguous on its face — /vault/notes/log.md/heading/Today could name the Today section of notes/log.md or a file literally called notes/log.md/heading/Today. The server walks backwards down the path until it finds a real file and reports which one it settled on in a Content-Location response header, with each path component percent-encoded on its own (non-ASCII characters, and reserved characters like #, ? and ,) so it can be pasted straight back into a request URL. A request whose URL names the file outright gets no such header.
On a GET, a Target-Scope header selects which part of the target comes back, mirroring the PATCH scopes: content (the default), marker (the label — a heading's raw text, a block's bare id, a frontmatter key), or markerAndContent (the whole node, in exactly the shape a PATCH replace at that scope consumes — a heading subtree reads back with its own line as # Title, levels relative to its parent):
# Read a whole section — heading line included — ready to edit and write back
curl -k -H "Authorization: Bearer <your-api-key>" \
-H "Target-Scope: markerAndContent" \
https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section
Deprecated: header-based targeting. Earlier releases targeted a section with
Target-Type,Target, andTarget-Delimiterheaders (plusTarget-Scope/Trim-Target-Whitespace). That form is deprecated and will be removed in 6.0; it is only processed when you also sendMarkdown-Patch-Version: 1(responses then carry aDeprecationheader). Without it, supplying those targeting headers is rejected with400. Supplying both URL-path targeting and the header form on one request returns422 Unprocessable Entity.
Searching
POST /search/simple/?query=your+terms runs Obsidian's built-in fuzzy search and returns matching filenames with scored context snippets. Obsidian matches each word of the query separately, so a query containing a common word can match thousands of times; &maxResults= caps how many files come back (keeping the highest-scoring ones) and &maxMatchesPerFile= caps the matches within each. Each result's totalMatches and the Total-Results response header say how much was left out.
POST /search/ accepts a JsonLogic expression (content type application/vnd.olrapi.jsonlogic+json) and evaluates it against each note's metadata (frontmatter, tags, path, content).
Event streams
You can follow what happens in the vault as a Server-Sent Events stream. There are two steps. First, register a subscription to one Obsidian event, with an optional JsonLogic filter. Then open the URL that comes back:
# 1. Subscribe to notes under journal/ being modified
curl -X POST -H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/vnd.olrapi.jsonlogic+json" \
-d '{"glob": ["journal/*", {"var": "path"}]}' \
https://127.0.0.1:27124/events/vault/modify/
# => {"id": "…", "url": "https://127.0.0.1:27124/events/vault/modify/…/?sig=…&exp=…&n=…", …}
# 2. Follow the stream; with signed URLs on, the URL needs no API key
curl -N "<url>"
It takes two steps because a browser's EventSource can only make GET requests, which have no body to carry a filter.
The events are Obsidian's own, and only these can be streamed:
| Emitter | Events |
|---|---|
vault |
create, modify, delete, rename |
metadataCache |
changed, deleted, resolve, resolved |
workspace |
file-open, active-leaf-change, layout-change |
Each event is serialized by code written for it. That code decides exactly what is sent: the path, the file's NoteJson (the same shape /search/ evaluates), and a few event-specific fields such as oldPath on a rename. Note content is sent only when the filter reads file.content. Events whose payloads are keystrokes, clipboard data, or UI objects (editor-change, quick-preview, editor-paste, the menu events, …) can't be streamed. To react to frontmatter changes, use metadataCache changed: vault modify fires before Obsidian has re-read the file's metadata.
Each message's id is <epoch>-<counter>. A new epoch, or a gap in the counter, means events were missed. Nothing is replayed. A stream URL expires after the signed-URL lifetime (or ?ttl=<seconds>), but a stream opened before then stays open. At most 16 streams can be open at once. Anyone holding a signed stream URL sees the paths and metadata of every event its filter matches, so treat it like the notes themselves. See the API docs for the full message format.
Server state
GET / answers without an API key, but an authenticated request gets more: certificateInfo, the list of installed API extensions, and a state section.
state holds observations, by namespace, that a client reads to decide whether to proceed. The server attaches no verdict. The plugin's own namespace is metadataCache:
{
"status": "OK",
"authenticated": true,
"state": {
"metadataCache": {
"listeningSince": "2026-10-03T14:02:11.408Z",
"lastResolvedAt": null,
"lastActivityAt": "2026-10-03T14:02:13.951Z"
}
}
}
It exists because a note's links, backlinks, and unresolvedLinks come from Obsidian's vault-wide link graph, and while Obsidian is still indexing after startup they can be incomplete with no sign in the note itself. Obsidian gives no documented signal for the end of that indexing, so the plugin reports what it can observe: when it started listening (listeningSince, which is when it loaded), when it last heard the cache's resolved event (lastResolvedAt, fired each time Obsidian's resolver queue drains, or null if never since loading), and when it last heard any indexing activity at all (lastActivityAt: changed, resolve, or resolved). Indexing in progress looks like recent activity; done looks like silence. A plugin enabled into an already-indexed vault hears nothing, which is why listeningSince is there: "never resolved, but listening for a while now" is as good as done. A workable rule is to wait while lastResolvedAt is null and listeningSince is recent, or while lastActivityAt is recent, with "recent" set to whatever your vault's indexing takes. A client that would rather not poll can watch the metadataCache event stream instead, where resolve fires per file and resolved on each drain.
Every other key of state is an extension plugin's id, holding whatever that extension publishes (see Extension state), or null when it could not be read in time. The budget is Settings → Local REST API → Advanced settings → Extension state read budget, 100 ms by default; extensions are read concurrently, so it bounds the whole wait. MCP clients get the same document from the obsidian://local-rest-api/status resource.
MCP (Model Context Protocol)
[!NOTE] Several third-party MCP servers for Obsidian exist, but they are no longer necessary — this plugin ships a built-in MCP server that runs inside Obsidian and has direct access to your vault's live metadata, active file, and command palette. If you are currently using a third-party server, switching to this one is likely to give you better results.
The plugin includes a built-in MCP server at /mcp/ so AI agents and MCP-compatible clients can interact with your vault without hand-crafting HTTP requests.
Transport: Streamable HTTP — API key authentication required.
Protocol revisions
The endpoint serves the 2026-07-28 revision plus the sessionful revisions from 2024-10-07 through 2025-11-25, choosing per request, so clients on either can share it.
The 2026-07-28 revision is stateless: there is no initialize handshake and no session, so the plugin neither issues nor reads the Mcp-Session-Id header. Each request carries its own protocol version and client identity in params._meta, repeats them in the MCP-Protocol-Version, Mcp-Method, and Mcp-Name headers, and is answered on its own. Clients can call server/discover to learn the supported revisions and capabilities up front.
Clients that open with an initialize request are served the sessionful revision they negotiate: the handshake returns an Mcp-Session-Id, GET /mcp/ opens that session's notification stream, and DELETE /mcp/ ends it. Sessions exist only on this path, and they are what keeps the handshake's listChanged capabilities honest: when another plugin registers or removes an MCP tool, every live session is notified, while 2026-07-28 clients hear about it on a subscriptions/listen stream.
Connecting a client
Connect your MCP client to https://127.0.0.1:27124/mcp/. Authentication uses a bearer token — find your API key under Settings → Local REST API, then pass it as:
Authorization: Bearer <your-api-key>
The exact config syntax varies by client; see the Quick start examples above or consult your client's documentation for Streamable HTTP remote MCP servers.
[!WARNING] To connect to the MCP server securely, your client must trust the plugin's locally generated certificate authority. You can download and trust it from
https://127.0.0.1:27124/obsidian-local-rest-api.crt, or configure your client to skip TLS verification for127.0.0.1.If trusting a locally generated certificate is not possible in your environment, you can connect insecurely using
http://127.0.0.1:27123/mcp/instead ofhttps://127.0.0.1:27124/mcp/if you have enabled the HTTP endpoint under Settings → Local REST API → Enable HTTP server.
Available tools
| Tool | Description |
|---|---|
vault_list |
List files and subdirectories inside a vault directory |
vault_read |
Read a text file's content, frontmatter, tags, and stat; refuses anything that is not valid UTF-8 |
vault_read_binary |
Read an attachment: images as an image block the model can see, anything else as a download link or embedded bytes |
vault_get_download_url |
Mint a signed, expiring link to a file that works without the API key (only when signed URLs are enabled) |
vault_get_upload_url |
Mint a signed, single-use link for uploading a file over PUT (only when signed URLs are enabled) |
events_get_listener_url |
Subscribe to an Obsidian event and mint a signed link to its Server-Sent Events stream (only when signed URLs are enabled) |
vault_write |
Create or overwrite a text file; refuses paths whose extension names a binary type |
vault_append |
Append content to the end of a vault file |
vault_patch |
Patch a specific heading, block reference, or frontmatter field |
vault_delete |
Delete a vault file (moves to trash by default) |
vault_move |
Move (rename) a vault file to a new path |
vault_copy |
Copy a vault file to a new path |
vault_get_document_map |
List the headings, block references, and frontmatter fields in a file |
active_file_get_path |
Return the vault path of the file currently open in Obsidian |
search_query |
Search using a JsonLogic query against note metadata |
search_simple |
Full-text search using Obsidian's built-in search |
tag_list |
List all tags across the vault with usage counts |
command_list |
List all registered Obsidian commands |
command_execute |
Execute an Obsidian command by ID |
open_file |
Open a file in the Obsidian UI |
search_simple returns at most the 20 highest-scoring files and the first 10 matches in each unless the call sets maxResults or maxMatchesPerFile. Obsidian's simple search matches each word of a query separately, so an unbounded search containing a common word can return megabytes, and an MCP result usually goes straight into the model's context. When anything was left out, a second text block beginning [Search results limited: says how much and how to see more. Its contextLength is at most 500 characters. POST /search/simple/ takes the same two parameters but has no limit by default, and does not bound contextLength.
Binary files and attachments
The REST API has always handled binary content: GET /vault/<path> returns raw bytes with a Content-Type
…
Releases