Skip to main content
Glama
Second Brain
by robbrad

second-brain-mcp

An MCP server that lets many Claude sessions share one "LLM wiki" (an Obsidian-format Markdown vault, in the style of Karpathy's LLM Wiki) without trusting any of them to edit it.

Sessions on your LAN or VPN can search and read the wiki and contribute new material to an inbox. Only a separate curator, a locked-down headless Claude Code run each night, ever edits the wiki itself.

Claude sessions --Bearer token--> second-brain-mcp container (:8765)
(laptop, desktop, server)           | reads wiki/ scripts/ raw/   (mounted read-only)
                                    | writes raw/inbox/ only      (contribute)
                                    v
                         vault folder (local disk or a NAS share)
                                    ^
nightly curator timer --------------+ headless Claude Code: raw/inbox/ -> wiki/
Obsidian on your PC ----------------+ opens the same folder

Design rules

  • Single writer. The server can't edit wiki/. Docker mounts it read-only, and the only writable path is raw/inbox/. Curating is a separate step that follows the vault's CLAUDE.md rulebook.

  • Contributions are data, not instructions. Each one is a new file raw/inbox/YYYY-MM-DDTHHMMSS-<host>-<slug>.md with frontmatter type: contribution, title, kind (fact | decision | session-summary | snippet), origin_host (the token label, which is authenticated), origin_client (the MCP client info, self-reported), origin_ip, submitted, plus optional related and source_url. It's written to a temp file and renamed into place. Bodies over 100 KB, unsafe titles and non-http(s) URLs are refused. The curator defers anything that tries to instruct it or contains secrets.

  • No model in the server. Search is BM25. audit and /graph run the vault's own scripts/audit_graph.py and scripts/build_graph.py; the audit runs on a scratch copy, so the vault is never written.

  • Secrets stay out of the vault. Tokens live in secrets/tokens (directory mode 700, file mode 600), which is never committed. One token per client, so each can be revoked alone.

  • LAN and VPN only. The server binds one address you choose (loopback by default). Don't bind 0.0.0.0 and don't port-forward it: reach it from outside over your VPN.

Related MCP server: mindin

Endpoints and tools

Path

Auth

Purpose

/mcp

Bearer token

MCP over streamable HTTP

/graph

none, private networks only (GRAPH_ALLOW_CIDRS)

Interactive graph, rebuilt when wiki/ changes

/healthz

none

200 {"status":"ok",...}, or 503 if the vault is unreachable

Read-only tools: get_index, search(query, limit, type), read_note(title) (title, then alias, case-insensitive; suggests close matches), backlinks(title), list_notes(type, tag, status), recent_changes(n), audit(), inbox_status(). Write tool: contribute(title, body, kind, related, source_url). Resources: wiki://index, wiki://note/{title}.

The vault

The server expects a folder laid out like this. examples/vault is a starter vault you can copy, including CLAUDE.md, the rulebook the curator follows:

raw/              your drop-zone for source material
  inbox/          where `contribute` writes
  _processed/     ingested files are archived here
wiki/             the curated notes: index.md, log.md, MAP.md, concepts/, entities/
scripts/          audit_graph.py and build_graph.py
CLAUDE.md         the rulebook

The vault is plain files. Don't make it a git repo that the curator can push from, and keep this repo's secrets/, logs/ and backups/ outside it.

Setup

  1. Put the vault somewhere the Docker host can read. A local folder works. For a NAS share over NFS, a line like this in /etc/fstab keeps an outage from hanging the host:

    nas.example:/volume1/SecondBrain /mnt/secondbrain nfs4 soft,timeo=30,retrans=3,noatime,bg,tcp,actimeo=5,nosharecache,_netdev,nofail 0 0

    Keep actimeo low, or edits made in Obsidian take a long time to show up in search.

  2. For a network mount, add placeholders under the mount point (see If the vault is unavailable). With the share unmounted, create root-owned, read-only wiki/ scripts/ raw/inbox/ raw/_processed/ and a NAS-NOT-MOUNTED marker file in the mount point.

  3. Build the image: docker build -t second-brain-mcp:0.1.0 .

  4. Tokens: install -d -m 700 secrets && install -m 600 /dev/null secrets/tokens, then add clients (below). The container runs as uid 1000 and must be able to read this file.

  5. Configure: cp .env.example .env and set BIND_IP to the host's LAN address, VAULT_HOST_PATH to the vault, and OWNER_NAME to your name.

  6. Deploy: docker compose up -d. In Portainer, paste docker-compose.yml into a new stack instead and give SECRETS_DIR_HOST an absolute path. pull_policy: never stops either tool trying to pull the locally built image.

  7. Curator (optional): see The curator.

Operations

curl -s http://<host>:8765/healthz                 # health + note and inbox counts
docker logs --tail 50 second-brain-mcp             # server log (rotated 3 x 10 MB)
systemctl list-timers second-brain-curator.timer   # next curator run
journalctl -u second-brain-curator -n 50           # curator summaries and failures
tail logs/curator.log                              # one line per run
curator/run-curator.sh                             # run the curator now (skips if nothing is pending)
curator/run-curator.sh --test-notify               # check the Home Assistant push

Upgrading: bump version in pyproject.toml, build the image with the new tag, set SB_VERSION to match and redeploy.

Development: uv sync --extra dev, then uv run pytest && uv run ruff check . && uv run mypy.

Adding a client

printf 'laptop:%s\n' "$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" >> secrets/tokens
grep '^laptop:' secrets/tokens        # copy the token

The server re-reads the file when it changes, so no restart is needed. On the client machine:

claude mcp add --scope user --transport http second-brain http://<host>:8765/mcp \
  --header "Authorization: Bearer <token>"
claude mcp get second-brain           # expect: Connected

Then tell that machine's sessions when to use it, in ~/.claude/CLAUDE.md:

## Second Brain (MCP server `second-brain`)
- Before asking me for background I may already have recorded, call `get_index` or `search`
  first, then `read_note`.
- When a session produces something durable (a decision and its reason, a fact about my
  systems, a short summary of completed work), call `contribute`. Don't contribute chit-chat,
  guesses or anything secret.
- The wiki is read-only to you. Contributions land in `raw/inbox/` and a curator merges them
  later, so don't expect one to show up in `search` straight away.

The label becomes origin_host on that machine's contributions and appears in their filenames, so use a short machine name. Hosted clients whose connectors run in the vendor's cloud can't reach a LAN-only server.

Revoking or rotating a client

  • Revoke: delete the client's line from secrets/tokens. It takes effect on the next request.

  • Rotate: replace the token after the label: with a new one, then on that machine run claude mcp remove second-brain -s user and add it again.

  • Rotate any token that has been pasted into a chat, ticket or screenshot.

The curator

curator/run-curator.sh turns inbox files into wiki edits. It needs Claude Code installed and signed in for the user that runs it. The script:

  1. Takes a lock and checks the vault is really mounted, not the placeholder.

  2. Backs up the whole vault to backups/vault-<ts>.tar.gz (last 30 kept).

  3. Exits if raw/inbox/ and raw/ are empty, without calling a model.

  4. Otherwise runs claude -p with curator/prompt.md, restricted like this:

    • --strict-mcp-config with no servers, so the run has no connectors at all

    • --permission-mode dontAsk and --setting-sources project

    • curator-settings.json: Edit/Write only in wiki/**; denies mv, rm, curl, wget, ssh, git, web access, scheduling and sub-agents

    • a 45-minute timeout

  5. Runs archive_ingested.py, which moves to raw/_processed/ every file that got a new ## [..] ingest | raw/... entry in wiki/log.md. The model never moves files; deferred files stay put.

  6. Re-runs the audit and logs the result. With NOTIFY=1 it sends a Home Assistant push on failure, a missing vault or deferred files (HA_URL, HA_NOTIFY, and a mode-600 HA_ENV file holding HA_TOKEN=...).

To schedule it nightly, fill in the template unit and enable the timer:

sed -e "s|YOUR_USER|$USER|g" -e "s|/opt/second-brain-mcp|$PWD|g" curator/second-brain-curator.service |
    sudo tee /etc/systemd/system/second-brain-curator.service >/dev/null
sudo install -m 644 curator/second-brain-curator.timer /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now second-brain-curator.timer

Edit the unit's /mnt/secondbrain paths if your vault lives elsewhere, and set VAULT to match.

To undo a bad night, extract the backup from before it (tar -xzf backups/vault-<ts>.tar.gz -C /tmp) and copy back what you need.

If the vault is unavailable

This matters when the vault is a network mount.

  • Share down while running: with a soft mount, reads fail with I/O errors rather than hanging. /healthz returns 503 and Docker marks the container unhealthy. Tools return "Vault unavailable" and contribute returns "Inbox unavailable". It recovers on its own when the share returns.

  • Share down at boot: bg,nofail lets the host boot, and the container starts against the read-only placeholders under the mount point. It reports unhealthy, and contribute fails rather than leaving notes on the host's own disk. After 10 unhealthy minutes (UNHEALTHY_EXIT_AFTER) the server exits on purpose. Docker restarts it, which re-binds the real mount once it's back. That's at most one restart per 10 minutes.

  • Curator: the unit's ConditionPathExists plus the script's own mount check skip the night.

Troubleshooting

Symptom

Check

claude mcp get shows failed

curl -s http://<host>:8765/healthz from that machine; a wrong token gives 401 in docker logs

401 with the right token

Token line malformed (label:token, token at least 16 chars). The log says "Ignoring unreadable tokens file" and keeps the last good set

/healthz 503 "index.md missing"

Vault not mounted: findmnt <mount point>, then mount it. The container recovers within 10 minutes or on restart

Search misses a note you just edited

NFS attribute cache plus the index recheck (2 s); wait a few seconds

/graph 403

Your client IP isn't in GRAPH_ALLOW_CIDRS

Curator never runs

systemctl status second-brain-curator.timer; journalctl -u second-brain-curator

A file sits in the inbox for days

It was deferred: read its deferred entry in wiki/log.md, then delete it or ingest it yourself in an interactive session

Redeploy fails pulling the image

Keep pull_policy: never and build the image locally first

Licence

MIT. See LICENSE.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      B
      maintenance
      Memory for Claude and coding agents, kept as markdown notes you own: a schema that holds, search that reads your languages, a lifecycle for tasks and decisions, and a tie to your git history. Runs on a folder, an S3 bucket or your own vault server.
      19
      576 PyPI
      2
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables Claude to read, search, edit, and publish notes in an Obsidian vault directly from markdown files over HTTP, without needing the Obsidian app or plugin.
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI assistants to build and maintain a shared, git-backed Markdown wiki: they search, file, update and link notes with YAML frontmatter, keep verbatim sources, and optionally read code repos, so knowledge one assistant records is available to all the others. Runs without LLM calls, embeddings or a database, with the vault's own files defining page types, instructions and tools.
      MIT