active_note
Return the note currently open in Obsidian, including cursor position and selection. Requires the obsidian-brain companion plugin installed and Obsidian running against the same vault.
Semantic search, graph analytics and safe editing over an Obsidian vault — reading the .md files straight off disk, with Obsidian closed if you like.
LABSCO SUMMARY
Hybrid retrieval is the day-to-day win: embeddings at markdown-heading granularity fused with keyword ranking, so you get the passage rather than the document. The graph side is what you cannot do by hand — rank the hubs, find the note bridging two topics, trace a link path between two ideas. When an agent edits, run edit_note with dryRun and apply the preview by ID; that is the version of write access worth granting.
A standalone local server over a folder of markdown. It keeps a SQLite index with chunk-level embeddings and a link graph, so it can answer both 'find the passage about X' and 'which notes hold this vault together'.
Node 20 or higher, and VAULT_PATH pointing at the vault — or any folder of .md files, since it reads from disk and Obsidian itself is optional. Everything else has a default: DATA_DIR for the SQLite index and embedding cache, EMBEDDING_PRESET (english, on bge-small-en-v1.5) or EMBEDDING_MODEL for a specific checkpoint, and EMBEDDING_PROVIDER choosing local transformers or a running Ollama at OLLAMA_BASE_URL. Run it with npx -y obsidian-brain@latest server. First boot downloads a ~34 MB embedding model and indexes the vault, so tools can take 30-60 seconds to appear; later boots are immediate. The three Obsidian-live tools additionally need the companion plugin and Obsidian running against the same vault.
One command — /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/sweir1/obsidian-brain/main/scripts/install.sh)"
USE CASES
Search in hybrid mode, then read the top hit in full.
Rank by influence and bridging, then drill into a theme cluster.
Find the link paths between the two notes, and widen to the neighbourhood around them.
Generate the edit as a preview first, look at it, then apply it by ID.
Move it; inbound wiki-links, aliases, embeds and heading suffixes are rewritten in place.
Read the note currently open in Obsidian, and run a Dataview query against the vault.
Counted once with only startup setup. Other versions, modes or optional credentials may expose a different list.
active_noteReturn the note currently open in Obsidian, including cursor position and selection. Requires the obsidian-brain companion plugin installed and Obsidian running against the same vault.
apply_edit_previewApply a previously previewed edit. Pass the previewId returned by edit_note with dryRun: true. Previews expire after 5 minutes. Fails with a descriptive error if the target file changed since the preview was generated — in that case, regenerate the preview and try again.
base_queryEvaluate an Obsidian Bases .base file and return its rows. Requires the obsidian-brain companion plugin installed, a recent Obsidian (1.10.0+) running against the same vault, and the Bases core plugin enabled (Obsidian → Settings → Core plugins → Bases). Obsidian does not yet expose a public API for headless Bases query execution (Plugin.registerBasesView() is a view-factory hook only). The plugin uses its own YAML parser + a whitelisted expression subset (Path B). See docs/plugin.md#bases for the full supported subset. Supported subset: tree ops (and/or/not), comparisons (==, !=, >, >=, <, <=), leaf boolean (&&, ||, !), file.{name, path, folder, ext, size, mtime, ctime, tags}, file.hasTag("x"), file.inFolder("x"), frontmatter dot-paths. Arithmetic (+, -, *, /, %), method calls other than hasTag/inFolder, function calls (today(), now(), etc.), regex literals, formulas:, summaries:, and this context references all return 400 unsupported_construct errors — they ship in subsequent plugin patches as users hit them. Provide either file (vault-relative path to a .base file) or yaml (inline .base YAML source); view names which view inside the file to execute. Returns { view, rows, total, executedAt } — rows contain {file: {name, path}, ...projected columns}, total is the pre-limit count. Default 30s timeout (override with timeoutMs). Timeout only cancels the HTTP wait; the plugin has no cancellation API, so a running evaluation keeps going inside Obsidian. Prefer a limit: in the view for open-ended queries over large vaults.
create_noteCreate a new note in the vault with a title, body, and optional YAML frontmatter. The new note is indexed immediately so semantic search and graph tools can find it. Auto-injects a title: field into frontmatter matching the note title unless frontmatter already has one.
dataview_queryRun a Dataview DQL query against the vault. Requires the obsidian-brain companion plugin v0.2.0+ installed, Obsidian running against the same vault, and the Dataview community plugin enabled. Returns a normalized discriminated-union shape. kind='table' gives {headers, rows}. kind='list' gives {values}. kind='task' gives {items: [{task, text, path, line, tags, children, ...STask fields when task=true}]}. kind='calendar' gives {events: [{date, link, value?}]}. DQL reference: https://blacksmithgu.github.io/obsidian-dataview/queries/structure/. Default 30s timeout (override with timeoutMs). NOTE: timeoutMs only cancels the HTTP wait; Dataview has no cancellation API, so the query keeps running inside Obsidian to completion. Prefer LIMIT N in DQL for open-ended queries.
delete_notePermanently delete a note. Removes the file from disk AND its index rows (edges, embedding, node). Requires confirm: true to guard against accidents. When the delete removes inbound edges, the response is wrapped in a next_actions envelope suggesting a follow-up rank_notes(method=pagerank, minIncomingLinks=0) to spot newly orphaned notes.
detect_themesList auto-detected topic clusters across the vault (served from the community-detection cache). Pass a theme id or label to drill into one cluster. To recompute with a different Louvain resolution, call reindex({ resolution: X }) first — detect_themes itself is a read-only tool. Each returned cluster carries staleMembersFiltered — the number of cached nodeIds that no longer exist in the vault and were dropped on this read. A positive value means the cached community row is lagging; the filter also regenerates summary so it stays consistent with the filtered nodeIds. Broken-wikilink stub targets are excluded by default; pass includeStubs: true to include them. When the overall vault graph has LOW modularity (<0.3), the response includes { warning, modularity } at the envelope top-level — the clusters aren't clearly separable on this graph and may not reflect meaningful themes.
edit_noteModify an existing note. Supports six edit modes: append (add to end; defensively inserts a leading newline if the source didn't end with one), prepend (insert after frontmatter if present, otherwise at file start), replace_window (find a block of text and replace it — optionally fuzzy; fuzzy extends match to consume trailing .?! so the replacement has no doubled punctuation), patch_heading (insert or replace content under a specific heading; headingOp: 'before' | 'after' inserts immediately before/after the heading line — use before on the NEXT heading to append to a section's end; headingOp: 'replace' with scope: 'section' (default) replaces to the next same-or-higher heading or EOF — CAREFUL on the LAST heading, this consumes everything below including content separated by blank lines — pass scope: 'body' to stop at the first blank line after the body; if the target heading text appears MORE THAN ONCE the call throws MultipleMatches listing each occurrence with its line number — pass headingIndex: 0 | 1 | ... (0-indexed, top-to-bottom) to pick one), patch_frontmatter (set a single YAML key; pass value: null to clear — or from XML-stringifying clients use valueJson: 'null' for true null, valueJson: 'true' for a real boolean, valueJson: '42' for a number, valueJson: '["a"]' for an array; valueJson wins over value when both are set), at_line (insert or replace at a 1-indexed line number that counts from file start including frontmatter lines). Pass edits (array) to apply multiple edits atomically — all succeed or none are written.
find_connectionsFind notes linked to (from or to) a given note, up to N hops. Optionally return the full subgraph instead of a flat list. Response is wrapped as {data, context} where context.next_actions suggests follow-ups like clustering a dense neighbourhood via detect_themes or tracing a path to the furthest neighbour via find_path_between. Broken-wikilink stub neighbours are excluded by default; pass includeStubs: true to include them.
find_path_betweenFind link paths between two notes. Returns all simple paths up to maxDepth edges, optionally including their shared neighbors. Broken-wikilink stub nodes are excluded by default — they are degree-1 dead ends in the undirected graph and will block legitimate paths if left in. Pass includeStubs: true to include them.
index_statusReport the current state of the vault index: embedder model + dim, count of notes / chunks indexed, chunks skipped during the last reindex (if any), advertised vs discovered max tokens, last reindex reasons, whether a reindex is currently in flight, and the last init error. Read-only — does not mutate anything.
link_notesAdd a wiki-link from one note to another with a context sentence describing why they're connected. Appends the link to the source note and records the edge in the graph so analytics pick it up.
list_notesList notes in the vault. Optionally filter by directory prefix or by frontmatter tag. Pass includeStubs: false to exclude unresolved wiki-link targets (nodes with frontmatter._stub: true) and see only real on-disk notes.
move_noteRename or move a note. Inbound wiki-links in other notes are rewritten in place immediately (bare [[old]], [[old|alias]], ![[old]] embeds, and [[old#heading]]/[[old^block]] suffixes all handled). If the note's frontmatter has a title: field matching the old basename, it's auto-rewritten to the new basename (custom titles and missing titles are left alone). Response includes linksRewritten: { files, occurrences }.
rank_notesRank notes by importance: 'influence' (densely-connected hubs), 'bridging' (notes that connect otherwise-separate topic clusters), or both. Credibility guards (I): by default, influence excludes notes with fewer than minIncomingLinks: 2 incoming edges — this filters out random-orphan noise that makes PageRank feel meaningless on personal vaults. Pass minIncomingLinks: 0 to see the unfiltered ranking. Bridging scores are normalized by graph size (divided by n*(n-1)/2) so values compare across vaults of different sizes — a bridging score of 0.5 means the same thing in any vault. Broken-wikilink stub targets are excluded by default; pass includeStubs: true to include them.
read_noteRead a note's content. Brief mode (default) returns title + metadata + linked-note titles; full mode returns full content + edge context. Full mode also reports truncated: true when the body exceeded maxContentLength (default 2000 chars) and was sliced. Response is wrapped as {data, context} where context.next_actions suggests follow-ups like creating missing linked notes or exploring outgoing connections.
reindexRe-index the vault: re-embeds notes whose mtime changed, prunes orphan stubs, and re-runs community detection only when something actually changed. Pass resolution to force a Louvain rerun and tune cluster granularity (0.5 = fewer/broader clusters, 2.0 = more/finer); without it, a no-op vault skips Louvain entirely.
searchSearch vault notes. hybrid (default) fuses semantic + full-text ranks via Reciprocal Rank Fusion — no tuning needed, best for most queries. semantic is concept-only (better for abstract/paraphrased queries). fulltext is literal-token (better when you know the exact phrase exists). Semantic search is chunk-level — results are deduped to one-per-note by default. Set unique: "chunks" to return chunk-level hits with chunkHeading, chunkStartLine, and chunkExcerpt; supported by semantic and hybrid modes. fulltext is note-level only and ignores unique (full-text chunk search is not yet supported). Response is wrapped as {data, context} where context.next_actions suggests the agent's most useful follow-up call (read top hit, explore connections, or retry with broader phrasing on zero hits).
BEFORE YOU INSTALL
18 tools when we connected. active_note, dataview_query and base_query work only with the companion Obsidian plugin (sweir1/obsidian-brain-plugin, same major.minor) installed and Obsidian running, so a bare npx install has 15 usable tools.
npx on PATH
CONTEXT COST
Rated by the size of the 18 tool definitions that ride along on every turn, not by how many there are
INSTALLATION
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/sweir1/obsidian-brain/main/scripts/install.sh)"{
"mcpServers": {
"obsidian-brain": {
"command": "npx",
"args": [
"-y",
"obsidian-brain@latest",
"server"
],
"env": {
"VAULT_PATH": "<YOUR_VAULT_PATH>"
}
}
}
}claude mcp add --scope user --transport stdio obsidian-brain \
-e VAULT_PATH="$HOME/<YOUR_PATH_TO_VAULT>" \
-- npx -y obsidian-brain@latest server{
"servers": {
"obsidian-brain": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"obsidian-brain@latest",
"server"
],
"env": {
"VAULT_PATH": "<YOUR_VAULT_PATH>"
}
}
}
}{
"mcpServers": {
"obsidian-brain": {
"command": "npx",
"args": [
"-y",
"obsidian-brain@latest",
"server"
],
"env": {
"VAULT_PATH": "<YOUR_VAULT_PATH>"
},
"disabled": false,
"autoApprove": []
}
}
}{
"context_servers": {
"obsidian-brain": {
"source": "custom",
"command": "npx",
"args": [
"-y",
"obsidian-brain@latest",
"server"
],
"env": {
"VAULT_PATH": "<YOUR_VAULT_PATH>"
}
}
}
}{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"obsidian-brain": {
"type": "local",
"command": [
"npx",
"-y",
"obsidian-brain@latest",
"server"
],
"enabled": true,
"environment": {
"VAULT_PATH": "<YOUR_VAULT_PATH>"
}
}
}
}codex mcp add obsidian-brain --env VAULT_PATH="$HOME/<YOUR_PATH_TO_VAULT>" -- npx -y obsidian-brain@latest server{
"mcpServers": {
"obsidian-brain": {
"command": "npx",
"args": [
"-y",
"obsidian-brain@latest",
"server"
],
"env": {
"VAULT_PATH": "$HOME/<YOUR_PATH_TO_VAULT>"
},
"timeout": 60000
}
}
}{
"mcpServers": {
"obsidian-brain": {
"command": "npx",
"args": [
"-y",
"obsidian-brain@latest",
"server"
],
"env": {
"VAULT_PATH": "<YOUR_VAULT_PATH>"
},
"working_directory": null
}
}
}