second-brain-mcp
Provides tools to read, search, write, and manage notes in an Obsidian vault, including capturing thoughts with appropriate frontmatter and managing tasks in Tasks.md.
Click on "Install 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., "@second-brain-mcpCapture a thought: schedule dentist appointment"
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.
second-brain-mcp
An MCP server that exposes an Obsidian vault to Claude — read, search, and write notes, capture
voice thoughts into the right folder with the right frontmatter, and manage Tasks.md.
Built so the vault's filing rules live in code rather than in a prompt that can be forgotten.
Status
Phase | State |
1. Local, stdio, five primitives | Done, 119 tests |
2. HTTP transport + bearer auth | Done, verified locally |
2b. Container image | Written but never built — no Docker daemon on the dev machine |
3. Expose via DDNS + reverse proxy | Not started — needs NAS access, see Deployment |
4. Semantic tools | Done |
Related MCP server: Vault MCP Server
Quick start
Requires uv. Python comes from uv; the system python3 is too old.
uv sync
# Point at a COPY of your vault first. Never the real one until you trust it.
rsync -a ~/Library/CloudStorage/SynologyDrive-Mergoth/Notes/PersonalObsidian/ /tmp/vault-copy/
VAULT_PATH=/tmp/vault-copy uv run python -m second_brain_mcpThat starts the stdio server. It will refuse to start without VAULT_PATH — there is no default,
deliberately, because a default is how a test run reaches the real vault.
Connect it to Claude Desktop
{
"mcpServers": {
"second-brain": {
"command": "uv",
"args": ["run", "--directory", "/Users/vladislav/work/second-brain-mcp",
"python", "-m", "second_brain_mcp"],
"env": { "VAULT_PATH": "/Users/vladislav/work/vault-sandbox/PersonalObsidian" }
}
}
}HTTP transport
VAULT_PATH=/tmp/vault-copy MCP_AUTH_TOKEN=$(openssl rand -hex 32) \
uv run python -m second_brain_mcp --transport httpServes on 127.0.0.1:8000, MCP endpoint at /mcp. Every request needs
Authorization: Bearer <token>; unauthenticated requests get a 401 with a WWW-Authenticate
header pointing at /.well-known/oauth-protected-resource (RFC 9728).
Configuration
All configuration is environment variables. None have defaults.
Variable | Required for | Notes |
| always | Absolute path to the vault root. Resolved and frozen at startup; never re-read. |
|
| Static bearer token. Compared with |
Tools
Primitives — no vault knowledge
Tool | Signature |
|
|
|
|
|
|
|
|
|
|
Semantic — encodes the vault's rules
Tool | Signature |
|
|
|
|
|
|
|
|
|
|
add_task requires an explicit section and raises listing the available ones if it doesn't
match. It does not guess and has no default section. This is deliberate: the vault's own
CLAUDE.md says "Sections are a view, not a taxonomy. Re-sort when reality moves", so any
hardcoded section table would silently misfile tasks the next time you reorganise. Call
list_task_sections() first.
add_task never invents a date or priority, and reports back any marker it added that you did not
state.
capture_thought rejects a domain outside the closed list (work finance legal health
trips home plants smart-home projects learning) rather than inventing one.
Security model
The server is a plain local-filesystem server. Its one real control is path confinement.
Every caller-supplied path becomes a real path in exactly one function,
vault/paths.py::resolve(). Nothing else in the package opens a file by a caller-supplied path.The vault root is resolved once at startup and frozen. It is not a tool argument and cannot be changed at runtime.
resolve()takes one parameter. There is no bypass flag, no per-call root, no trusted-path list, no follow-symlinks toggle.Containment is checked by path ancestry, never string prefix — with root
/vault, the sibling/vault-evilmust not pass.Rejected:
.., absolute paths, null bytes, empty paths, and symlinks that resolve outside the root even when the link itself lives inside the vault.Glob patterns and search scopes are validated too, and the search query goes to
rgafter-eso it can never be parsed as a flag.
That last point is not theoretical. An early build appended the caller's query to rg as a bare
positional argument, so a query of --pre=<script> executed arbitrary commands — with a
model-controlled argument, which is exactly the prompt-injection threat the design exists to stop.
See factory/adr/0002-single-path-resolution-chokepoint.md.
No hard delete anywhere. Archiving is move_note into raw/archive/.
Every mutation appends a line to meta/audit.log (machine-readable, append-only). That is
deliberately a different file from meta/log.md, which stays human-curated so an unexplained line
in it is still a usable tripwire.
Development
uv run --frozen pytest -q # 119 tests
uv run ruff check .Tests run against a synthetic fixture vault copied into tmp_path. An autouse guard fails the
session if the resolved vault root is not under tmp_path, so the suite cannot reach a real vault.
Design documents:
factory/briefs/vault-mcp-server.md— why it is built this way, and what was rejectedfactory/adr/— binding architecture decisionsspecs/— what each increment buildsdocs/initial_spec.md— the original design record
Deployment
Phase 3 is not done. What remains is NAS and browser work, not code:
Fix the advertised metadata URLs first — this is a blocker, not a nicety.
__main__.pycallsbuild_auth_settings()with no arguments, so it advertises the defaultsresource_url="http://127.0.0.1:8000"andissuer_url="https://auth.example.com". Behind a reverse proxy those are wrong: a remote client is told the resource lives on loopback. Make both read from the environment (e.g.MCP_RESOURCE_URL,MCP_ISSUER_URL) before exposing anything.Build the container.
deploy/Dockerfileanddeploy/compose.yamlare written — non-root user, read-only rootfs,ripgrepinstalled, port bound to127.0.0.1— but have never been built or run.Resolve container UID vs. vault file ownership.
Tasks.mdandmeta/log.mdare mode600on the real vault, so a non-root container with a mismatched UID getsEACCESon exactly the two highest-value writes while reads ofraw/keep working — a partial failure that looks like a tool bug.DSM reverse proxy, Let's Encrypt cert, rate limit, auto-block on failed auth. Never publish the container port directly.
Register as a custom connector and test from Android.
On auth
docs/initial_spec.md assumed custom connectors require OAuth 2.1 with dynamic client
registration. That is out of date: DCR is deprecated in the current MCP spec (Client ID
Metadata Documents replace it), and static bearer tokens are first-class on Anthropic's MCP client
surfaces. So this ships a static bearer.
The honest caveat: that is evidence about Anthropic's API surfaces. Whether the claude.ai
custom connector UI accepts a static bearer is a product question that needs a live test. If it
turns out to demand OAuth, auth.py is the only module that changes — transport and auth are
confined to the entrypoint by factory/adr/0003.
Known limitations
The container image is unbuilt and unverified.
RFC 9728 metadata URLs are hardcoded defaults (see Deployment step 1).
list_notes(since)filters on filesystem mtime, which on a Synology-synced folder is sync time, not edit time. The vault'sCLAUDE.mdsayscreated:in frontmatter is the real recency anchor.No end-to-end test drives a JSON-RPC tool call over HTTP; tools are covered over stdio and via direct calls.
propose_wiki_pagefrom the original spec is deliberately not built — its arguments were never specified and it serves deep work at the desk, where nothing is blocked.Synology sync conflicts are not handled. Writes are atomic (temp file +
os.replace), but there is no merge logic. The original spec cited conflict files as evidence this was urgent; there are none inPersonalObsidian/, so the risk is real but unproven and was not paid for.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityFmaintenanceA local MCP server that enables AI applications like Claude Desktop to securely access and work with Obsidian vaults, providing capabilities for reading notes, executing templates, and performing semantic searches.831MIT
- Flicense-qualityCmaintenanceBuilt on Obsidian Vault, this MCP server integrates with Claude Code to provide personal knowledge management including note saving, full-text search, code graph extraction, and context resumption.1
- Alicense-qualityCmaintenanceAn MCP server that gives Claude AI direct access to your Obsidian vault, enabling natural language search, note creation, file management, and automated workflows.2,4729MIT
- Flicense-qualityCmaintenanceAn MCP server that provides full read/write access to an Obsidian vault, enabling searching, task management, wiki-link graph analysis, and attachment organization from an MCP client like Claude Code.
Related MCP Connectors
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Augments MCP Server - A comprehensive framework documentation provider for Claude Code
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mergoth/second-brain-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server