obsidian-secondbrain-mcp
A local-only MCP server that turns an Obsidian vault into a second brain: it manages notes, captures sessions, and supports a distillation/review workflow — with no network calls, embeddings, or model calls.
Vault management: initialize the vault structure, list/read notes, get vault info, and create notes with optional frontmatter.
Note editing: append to notes (append-only), patch sections under headings, merge frontmatter updates, move/rename notes, and archive notes (never delete).
Search & discovery: find notes by name/glob, search note content (literal or regex), search by nested tags, search frontmatter, list backlinks, find related notes by graph structure, and list recent notes.
Session capture: manually compact a session into a durable summary note (capture_session), append timestamped log entries, and read daily logs.
Distillation workflow: pull undistilled raw captures, create atomic concept notes with claims and links, and mark sources as distilled.
Review & maintenance: check vault health (orphans, unresolved links, backlog), build map-of-content indexes, resurface random notes for re-reading, and list stale notes.
Automation support: injects session-start digests and captures raw session material via hooks on supported clients, plus MCP prompts for compacting, distilling, and reviewing.
Provides tools for working with an Obsidian vault as a second brain, including capturing notes, searching and linking notes, distilling raw material into concept notes, and reviewing vault health.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@obsidian-secondbrain-mcp search my vault for notes about MCP servers and summarize key takeaways"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
obsidian-secondbrain-mcp
A local-only MCP server that turns an Obsidian vault into a working second
brain. No network calls, no API keys, no embeddings, no model calls anywhere
in this package. Every tool either moves bytes on local disk or hands raw
material back to the agent with an explicit next_step — the thinking is the
agent's job, and the results come back through the same tools.
Install
cd obsidian-mcp
python -m venv .venv
.venv/Scripts/python.exe -m pip install -e . # Windows
# .venv/bin/python -m pip install -e . # macOS/Linux
# register with Claude Code (server -> ~/.claude.json, hooks -> ~/.claude/settings.json;
# both backed up first)
.venv/Scripts/python.exe scripts/install.py --vault "C:/path/to/YourVault"Then restart Claude Code and run /mcp to confirm obsidian-secondbrain is
connected. Add --dry-run to see the config without writing it, --no-hooks
to skip automatic session capture.
First thing in a fresh vault: ask the agent to call init_vault.
Related MCP server: Obsidian MCP Assistant
Quick setup: the secbrain-init skill
Copy skills/secbrain-init/ into ~/.claude/skills/, restart Claude Code, and
run /secbrain-init in any project. It creates or reuses a vault there,
registers the server and hooks for that project, fires the capture hook and
verifies it wrote a note, then reports.
The skill drives the CLI below rather than editing config by hand, so setup is tested code and behaves the same every time.
CLI
PY=.venv/Scripts/python.exe # or .venv/bin/python
$PY -m obsidian_secondbrain.cli detect --project . # OS, runtime, which client
$PY -m obsidian_secondbrain.cli doctor --project . # what is / isn't set up
$PY -m obsidian_secondbrain.cli init --project . # create or reuse the vault
$PY -m obsidian_secondbrain.cli install --project . # register server + hooks
$PY -m obsidian_secondbrain.cli test-hook --project . # fire the hook, verify, clean upEach prints one JSON object and exits non-zero on failure. init and
install are idempotent: existing notes are never touched, config files are
backed up before rewriting, unrelated entries are preserved, and re-running
replaces this hook rather than stacking duplicates.
Which clients work
The server is plain stdio MCP, so any MCP client can use all 27 tools; the vault is plain markdown that Obsidian itself reads. What differs is the automation around it.
Claude Code | Codex | Copilot / VS Code | Cursor | |
27 MCP tools | yes | yes | yes | yes |
| yes | yes | yes | yes |
MCP prompts | yes | varies | yes | yes |
Automatic capture hooks | yes | yes | no | no |
| yes | no | no | no |
Automatic capture now works on both Claude Code and Codex, but not the same
way. Claude Code's hook parses the PreCompact/SessionEnd transcript
directly. Codex documents its rollout transcript format as unstable, so its
hooks avoid it: UserPromptSubmit/Stop buffer each turn's stable fields
(the prompt, the final assistant message) to
<vault>/.secondbrain/buffer/<session_id>.jsonl, and PreCompact/SessionEnd
turn that buffer into the same kind of inbox note. Codex also skips every hook
until it is reviewed and trusted in /hooks — install and doctor both
say so, and re-trust is needed after any re-install that changes a command.
Copilot and Cursor have neither event, so capture stays manual there —
capture_session is an ordinary tool, so an agent writing its own summary and
filing it works anywhere. install --with-instructions writes
.github/copilot-instructions.md / .cursor/rules/secbrain.md for those two;
Codex gets AGENTS.md by default now, the same way Claude Code gets
CLAUDE.md, no flag needed.
Reading the vault at the start of a session
Two mechanisms cover this, and they are deliberately not the same thing.
A SessionStart hook (hooks/session_start.py, Claude Code and Codex) fires
unconditionally on every session start/resume/clear/compact and injects a
small, passive digest as additionalContext — the undistilled backlog count
and the tail of the most recent log. It never searches anything; it can't,
because the user's actual request doesn't exist yet at that point.
install also writes a marked block into the project's rule file — CLAUDE.md
for Claude Code, AGENTS.md for Codex, with no flag needed for either — whose
first instruction is: before acting on the first request of a session,
search the vault. This is the active half: a targeted
search_notes/find_notes once the request is known, which a hook firing
before any user text exists structurally cannot do.
Every client gets the rule, Claude Code and Codex included, because a capture
hook only writes the vault when a session ends — without the rule (or,
on top of the hook's passive digest), the vault only ever fills up, and a
session starts by re-deriving what is already written down. doctor reports
project_rules.has_session_start_rule and hooks.missing (which lists any of
PreCompact/SessionEnd/SessionStart not yet registered, plus
UserPromptSubmit/Stop on the Codex path), and --no-instructions opts out
of the rule file specifically.
detect identifies the client from evidence and distinguishes an agent
signal (we are running as it) from a host signal (it is merely the editor
hosting the terminal) — Claude Code inside a VS Code terminal makes both look
active.
$PY -m obsidian_secondbrain.cli install --project . --client codex --scope user
$PY -m obsidian_secondbrain.cli install --project . --client vscode --with-instructions
$PY -m obsidian_secondbrain.cli install --project . --client all
$PY -m obsidian_secondbrain.cli test-hook --project . --client codex
$PY -m obsidian_secondbrain.cli doctor --project . --client codextest-hook and doctor default to --client claude; pass --client codex
to check the Codex-side wiring (hooks.json, the turn buffer, AGENTS.md)
instead. Neither command can see whether Codex has actually trusted the
hooks in /hooks — that state lives only in Codex, keyed by each hook's
hash — so both report the caveat explicitly rather than implying a passing
check means capture is live.
Config shapes are not interchangeable, and a wrong shape is silently ignored
with no error: VS Code uses root key servers and requires "type": "stdio";
Claude Code, Copilot and Cursor use mcpServers; Codex uses TOML
[mcp_servers.NAME]. The Codex writer patches only its own table so comments
and other servers survive.
install --scope project (default) writes .mcp.json plus
.claude/settings.local.json — hooks go in the local file because they embed
absolute machine paths. --scope user registers one vault globally instead.
test-hook is the one that matters: it writes a synthetic transcript, runs
the command exactly as registered with OBSIDIAN_VAULT stripped from the
environment — what a real session gives a hook — asserts seven properties of
the note it produced, then restores the vault byte-for-byte.
That last detail is not pedantry. A hook does not inherit the env block of an
MCP server config, so a hook registered without --vault finds no vault, exits
0 and silently writes nothing. install therefore passes the vault on the
command line, and test-hook runs the registered command under a stripped
environment so a no-op hook cannot pass.
Configuration
Exactly one input: the OBSIDIAN_VAULT environment variable, set in the MCP
server config. Everything else — folder names, date formats, the frontmatter
key that marks a note distilled — lives in <vault>/.secondbrain/config.json,
created by init_vault. Reshape the method by editing that file; no code
changes needed.
{
"folders": {
"inbox": "00-Inbox", "log": "10-Log", "sessions": "20-Sessions",
"notes": "30-Notes", "maps": "40-Maps", "archive": "90-Archive"
},
"daily_note_format": "%Y-%m-%d",
"distilled_key": "distilled",
"exclude": [".obsidian", ".trash", ".git", ".secondbrain"]
}The method
Raw capture is append-only and never rewritten. Distillation is a separate, deliberate pass that turns raw material into atomic notes — one idea each, titled as a claim, linked into the existing graph. Review fights the write-only vault.
INJECT → the SessionStart hook (Claude Code, Codex) + the context-first rule
CAPTURE → log_entry, append_to_note, the PreCompact/SessionEnd hooks
COMPACT → capture_session (agent writes the summary, server files it)
DISTILL → distill_queue → create_concept_note → mark_distilled
REVIEW → vault_health, resurface_notes, related_notes, stale_notesTools (27)
Vault — vault_info, init_vault, list_notes, read_note,
create_note, append_to_note, patch_section, update_frontmatter,
move_note, archive_note
Find & search — find_notes (by name/glob), search_notes (inside
content, literal or regex), search_by_tag (nested tags match),
search_frontmatter, backlinks, related_notes (scored graph neighbours
with reasons), recent_notes
Session capture — capture_session, log_entry, read_daily_log
Distillation & review — distill_queue, create_concept_note,
mark_distilled, vault_health, build_map, resurface_notes,
stale_notes
Prompts
compact_to_vault · distill · review_brain — surfaced as slash commands
in Claude Code once the server is connected.
Session capture, and why the hook files raw text
capture_session is the /compact step: the agent writes the durable
summary (what, why, decisions, open questions, artifacts) and the server files
it as a session note marked undistilled, plus a pointer in the day's log.
The PreCompact/SessionEnd hook runs outside the model, so it cannot
summarise anything. It files raw material — user prompts and assistant text
only, no tool spam, most recent ~20k chars — into the inbox as an undistilled
note. On Claude Code that material is the transcript at transcript_path. On
Codex, whose transcript format is documented as unstable, it is instead
whatever hooks/codex_turn_buffer.py already appended from
UserPromptSubmit.prompt and Stop.last_assistant_message — two fields Codex
does treat as stable — to <vault>/.secondbrain/buffer/<session_id>.jsonl;
the buffer is consumed (deleted) once its note is written, so a PreCompact
capture followed by a SessionEnd capture never files the same turns twice.
The next distill_queue call hands the note to the agent, which does the
thinking. Automatic capture without ever needing a model behind the server's
back.
The SessionStart hook (hooks/session_start.py) is the same idea run in
reverse: it can't summarise either, so it just counts and quotes — the
undistilled backlog size and the tail of the latest log — and hands that back
as additionalContext. Injection without a model in the hook, same as capture.
Every hook always exits 0. A failed capture never blocks your session. On
Codex this matters doubly: SessionEnd gets 1 s by default and 3 s at the
hard ceiling, so its capture only ever has to rename a buffer file into a
note — the actual buffering already happened earlier, in UserPromptSubmit
and Stop, where there's no shared time budget to blow.
Safety
Every path is resolved and checked against the vault root, so ../ escapes
are refused. archive_note moves rather than deletes; nothing in this package
deletes a note. Tool errors come back as {"error": ..., "message": ...} data
so the agent can recover instead of the call blowing up.
Layout
src/obsidian_secondbrain/
config.py vault path + data-driven taxonomy
vault.py safe paths, frontmatter, read/append/patch, wikilinks
search.py content, tag and frontmatter search
ops.py find by name, move/archive, related, recent/stale/resurface
capture.py daily log, session notes
distill.py distill queue, concept notes, health, maps
server.py MCP tool + prompt surface
cli.py detect / doctor / init / install / test-hook bootstrap
environment.py evidence-based OS + client detection
clients.py per-client config writers (json / toml shapes)
hooks/capture_session.py PreCompact / SessionEnd raw capture (Claude Code + Codex)
hooks/session_start.py SessionStart digest injection (Claude Code + Codex)
hooks/codex_turn_buffer.py Codex UserPromptSubmit / Stop: buffers each turn's stable fields
hooks/_common.py shared vault resolution + the Codex buffer path, for all hook scripts
scripts/install.py user-scope registration (~/.claude.json + settings.json)
skills/secbrain-init/ Claude Code skill: /secbrain-init
tests/test_lifecycle.py 34 assertions over a real stdio MCP client
tests/test_hook_wiring.py proves test-hook rejects an env-only hook
tests/test_clients.py pins each client's config shape
tests/test_codex_hooks.py install/test-hook/doctor --client codex, end to endAvailable Tools
27 toolsappend_to_noteA
Append to a note, creating it if absent.
The append-only primitive: prefer this over rewriting raw capture notes.
Inputs: path (required), text (required), heading.
archive_noteA
Move a note into the archive folder. Nothing is ever deleted.
Inputs: path (required).
backlinksC
Notes that link to this one via [[wikilink]].
Inputs: path (required).
build_mapB
Write a Map-of-Content index note over notes you have curated.
Inputs: topic (required), note_paths (required), intro.
capture_sessionA
Persist the current session into the vault -- the /compact step.
YOU write the summary; this tool only files it. Before calling, compact the session yourself into: what was being done and why, decisions taken with their reasons, what is still open, which files/artifacts were touched. Write durable facts, not a transcript -- a reader six months from now should not need the conversation.
Creates a note in the sessions folder marked undistilled, and drops a pointer into today's log.
Inputs: title (required), summary (required), tags, project, +4 more.
create_concept_noteA
Write one atomic note: one idea, stated as a claim.
title should read as an assertion you could disagree with, not a topic
label. claim is the idea in one or two sentences. links are titles of
existing notes -- call find_notes or search_notes first so you link
into the graph instead of creating an island.
Inputs: claim (required), title (required), body, tags, +3 more.
create_noteC
Create a note. Refuses to clobber an existing note unless overwrite=true.
Inputs: body (required), path (required), overwrite, frontmatter.
distill_queueA
Raw captures not yet distilled, oldest first, with their content.
This is the core loop and it is YOUR job, not the server's: read each item,
pull out the ideas that will still matter later, write each one as a single
atomic note via create_concept_note (linking to existing notes with
[[Title]]), then call mark_distilled on the source listing what you
produced. Skip anything ephemeral -- mark it distilled with an empty
produced list.
Inputs: limit, include_body.
find_notesA
Find notes by NAME or path. */? globs work; a bare word matches any
name containing it.
Use this when you know roughly what a note is called. Use search_notes
when you need to look inside the content.
Inputs: pattern (required), limit, folder.
init_vaultA
Create the folder taxonomy and config file in an empty or new vault.
Idempotent: existing folders and notes are left untouched.
Inputs: seed_readme.
list_notesB
List note paths, optionally restricted to one folder.
Inputs: limit, folder, with_frontmatter.
log_entryA
Append a timestamped entry to the day's append-only log.
For in-flight capture: a fact, a link, a half-formed idea. Never rewrite the log -- it is the raw stream distillation feeds on.
Inputs: text (required), date, source.
mark_distilledA
Mark a raw capture as distilled, recording which notes came out of it.
Inputs: path (required), produced.
move_noteC
Move or rename a note inside the vault.
Inputs: src (required), dest (required), overwrite.
patch_sectionA
Insert text at the end of the section under heading, adding the
section at the end of the note if it is missing.
Inputs: path (required), text (required), heading (required).
read_daily_logB
Read one day's log (ISO date, default today).
Inputs: date.
read_noteB
Read one note: frontmatter, body, tags and outgoing links.
Inputs: path (required).
recent_notesC
Notes modified in the last N days, newest first.
Inputs: days, limit, folder.
resurface_notesC
Random notes pulled up for re-reading -- the anti-write-only-vault move.
Re-read them, then either link them somewhere new, sharpen the claim, or archive them if they no longer hold.
Inputs: count, folder, with_body.
search_by_tagB
Find notes carrying a tag (frontmatter tags or inline #tag).
Nested tags match: ml also matches ml/rl.
Inputs: tag (required), limit.
search_frontmatterA
Find notes by a frontmatter key, optionally matching an exact value.
Inputs: key (required), limit, value.
search_notesA
Literal or regex search across note CONTENT, with surrounding lines.
Plain text matching, no embeddings. Read the excerpts, judge relevance
yourself, then read_note the ones that matter.
Inputs: query (required), limit, regex, folder, +1 more.
stale_notesC
Notes untouched for N+ days, oldest first.
Inputs: days, limit, folder.
update_frontmatterB
Merge keys into a note's YAML frontmatter.
Inputs: path (required), updates (required).
vault_healthA
Where the graph is fraying: undistilled backlog, orphan notes, unresolved [[links]], most-connected hubs, per-folder counts.
Use it to pick the next maintenance job -- an unresolved link is usually a note worth writing; an orphan is usually a note worth linking.
Inputs: no named parameters.
vault_infoA
Describe the vault: root path, folder taxonomy, note counts, method.
Call this first in a session so you know the structure you are writing into. The taxonomy is data (.secondbrain/config.json), not code.
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 27 tools
Every tool targets a distinct operation: listing paths vs. searching by name/content/tag/frontmatter, editing via distinct primitives (create/append/patch/frontmatter), and graph-specific tools (related, backlinks, stale, recent). The search tools are clearly separated by what they search (name vs. content vs. tag vs. frontmatter), minimizing confusion. Even the distillation workflow tools (capture_session, log_entry, distill_queue, mark_distilled) each have non-overlapping responsibilities.
Most tools follow verb_noun snake_case (create_note, move_note, search_notes, build_map). A few are noun-first (vault_info, vault_health, backlinks) and one adjective_noun (distill_queue). The convention is predominantly consistent, and any deviations are clear from context. The minor inconsistency keeps it from a perfect score.
At 27 tools, this exceeds the typical 15-tool threshold, but the server manages a full Personal Knowledge Management system: CRUD, multiple search modes, graph analytics, session capture, distillation workflow, health checks, and map building. Each tool addresses a distinct need, so the count feels justified rather than bloated. It is on the heavy side but appropriate for the scope.
The tool surface is remarkably complete for a second-brain vault: full note lifecycle (create, read, append, patch, move, archive without deletion), comprehensive search (name, content, tag, frontmatter), graph operations (backlinks, related, stale, recent), a distillation pipeline (capture, log, distill, mark_distilled), and maintenance utilities (health, map, resurface). The only missing operation is permanent deletion, which is a deliberate design choice. This is a fully realized workflow.
Maintenance
Related MCP Connectors
Your personal library, readable by AI agents: durable snapshots, hybrid search, 46 MCP tools.
Turn your notes, links and conversations into a Living Brain your AI tools share and build on.
Token-efficient MCP memory for Markdown vaults. Tiered search, GraphRAG, AI memories.
Keeps your memory: WhatsApp, voice or any AI tool over MCP, Markdown you own, recall with sources
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceTurns your Obsidian vault into an MCP-enabled workspace with tools for reading/writing notes, managing folders, running semantic searches, and maintaining long-term memory—all while keeping data local to your vault.309,216 npm158MIT
- FlicenseNot gradedqualityCmaintenanceEnables AI-powered chat and file operations on Obsidian vaults with local indexing, hybrid search, and MCP access.-
- AlicenseBqualityBmaintenanceEnables local hybrid search over Obsidian and Markdown vaults via MCP, combining vector retrieval, full-text search, reranking, graph navigation, and safe CRUD while keeping data local.431MIT
- AlicenseNot gradedqualityCmaintenanceA local Obsidian Vault authoring MCP for building high-quality knowledge inputs for OKC, enabling note creation, search, and audit without the Obsidian app.MIT