Sign inSign up

andreaskasper/secondbrain

By andreaskasper

โ€ขUpdated 9 days ago

An MCP server that gives an agent structured access to a knowledge base of plain Markdown notes

Image
Machine learning & AI
Developer tools
0

1.1K

andreaskasper/secondbrain repository overview

โ secondbrain ๐Ÿง 

A knowledge base your agent can use and you can still read without it.

secondbrain is an MCPโ  server that gives an LLM agent structured access to a vault of plain Markdown files โ€” full-text search, backlinks, tags, daily notes, tasks and git history.

Source Docs License Image size Pulls


โ The notes are the product

secondbrain stores nothing of its own inside your notes. A vault is a directory of .md files with optional YAML frontmatter โ€” the format Obsidian, Logseq, Foam, grep and git already understand. Everything the server keeps for itself lives in <vault>/.secondbrain/: a SQLite index, a trash directory, and a file of conventions. All three are disposable.

Delete the container and you are left with a directory you can open in an editor, commit, rsync or read with your eyes. A knowledge base you cannot read without its software is a hostage, not an asset.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   MCP (OAuth 2.1)   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   plain Markdown   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   LLM   โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ secondbrain  โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚  /data   โ”‚
โ”‚  agent  โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚    :2020     โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚  vaults  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   notes, diffs      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   files on disk    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                       โ”‚                                  โ–ฒ
                                 34 MCP tools                             โ”‚
                            search ยท read ยท write                    Obsidian,
                            curate ยท history                       git, rsync

โ Quick start

docker run -d --name secondbrain -p 2020:2020 \
  -v secondbrain-data:/data \
  -e SECONDBRAIN_USERNAME=andreas \
  -e SECONDBRAIN_PASSWORD='a-long-password' \
  -e SECONDBRAIN_PUBLIC_URL=https://notes.example.com \
  andreaskasper/secondbrain:latest

Three variables are all that is required. Everything else has a working default, and a first start with no vaults creates default for you.

Then point an MCP client at https://notes.example.com/mcp. The client discovers the OAuth endpoints, registers itself, opens the login page in a browser, and you sign in. The tool list and the vault's own conventions arrive with the initialize response.

secondbrain speaks plain HTTP and does not terminate TLS. Put a reverse proxy in front of it in production โ€” Traefik and Cloudflare Tunnel examples are in the repositoryโ .

โ โš ๏ธ Using a bind mount instead of a named volume?

The container runs as the distroless nonroot user, UID 65532, and will refuse to start on a directory it cannot write.

mkdir -p ./data && sudo chown -R 65532:65532 ./data

This is the single most common way a first deployment fails.

โ With Compose
services:
  secondbrain:
    image: andreaskasper/secondbrain:latest
    ports: ["2020:2020"]
    volumes:
      - secondbrain-data:/data
    environment:
      SECONDBRAIN_USERNAME: andreas
      SECONDBRAIN_PASSWORD: a-long-password
      SECONDBRAIN_PUBLIC_URL: https://notes.example.com
    restart: unless-stopped

volumes:
  secondbrain-data:

โ Configuration

Environment first. A config file is optional and only worth mounting for more than one user, or to restrict a user to some vaults.

VariableDefaultPurpose
SECONDBRAIN_USERNAMEโ€”Login name. Required unless a config file defines users.
SECONDBRAIN_PASSWORDโ€”Literal, bcrypt:<hash>, env:NAME or file:/path. A literal needs 8+ chars.
SECONDBRAIN_PUBLIC_URLโ€”Required. External base URL, no trailing slash.
SECONDBRAIN_LISTEN:2020Listen address inside the container.
SECONDBRAIN_DATA/dataVault root. One directory per vault below it.
SECONDBRAIN_DEFAULT_VAULTdefaultThe vault a tool call means when it names none.
SECONDBRAIN_GITtrueCommit every write to <vault>/.git.
SECONDBRAIN_TRASH_RETENTION720hHow long a trashed copy is kept.
SECONDBRAIN_TOKEN_TTL12hAccess token lifetime.
SECONDBRAIN_METRICSfalseExpose the Prometheus endpoint. Off unless you ask for it.
SECONDBRAIN_LOG_LEVELinfodebug, info, warn, error.
SECONDBRAIN_CONFIG/etc/secondbrain/config.yamlOptional config file. Absence is normal, not an error.

The full tableโ  includes git remotes, metrics keys, a separate metrics listener, rate limits and response ceilings.

Generate a password hash with docker run --rm -it andreaskasper/secondbrain hashpw. Check a config before deploying with secondbrain validate โ€” it reports every problem at once and never prints a password.

โ Why the tools look the way they do

An agent editing prose fails differently from an agent editing code. Wrong code does not compile. A wrong edit to a note is a paragraph that quietly disappears, and you find out months later when you go looking for it.

Almost every design decision follows from that asymmetry:

  • Section edits and exact-string edits, so changing a sentence does not mean rewriting a file.
  • Content hashes, so a note changed since it was read cannot be silently overwritten.
  • Dry runs returning a unified diff, on every tool that writes โ€” defaulting to true for the vault-wide ones.
  • Trash, so a deletion is a move rather than an erasure.
  • Git, so "what did that edit actually change" is answerable.
  • Refusal over guessing. An old_string that appears twice is refused, not guessed at.

None of these make an agent careful. They make carelessness recoverable.

โ The tools

Thirty-four, grouped: discovery and search (note_search with FTS5 ranking, vault_grep, note_list), reading (note_read, note_outline, note_backlinks, note_related), writing (note_create, note_edit, note_section_edit, note_frontmatter, note_move, note_delete), capture (daily_note, inbox_capture), curation (vault_review, note_merge, note_split), tasks, tag and vault-wide refactoring, attachments, and git history (note_history, note_diff, note_restore).

The count is deliberate. A model picks a tool by reading its name and description; tools with one clear purpose each get chosen correctly, and a single tool with a mode parameter gets chosen incorrectly โ€” with the failure showing up as a mangled note rather than an error.

A read_only user is never shown the eighteen mutating tools at all, so the model does not learn they exist.

โ Vault layouts

vault_create populates a new vault with a shape and an instructions.md describing it, which is sent to the client on connect. That is how a vault teaches an agent its own conventions without anything living in a system prompt somewhere else.

LayoutThe organising idea
wiki-raw (default)Source material is never rewritten; distilled wiki notes constantly are.
zettelkastenAtomic notes, densely linked, no hierarchy beyond the buckets.
paraOrganised by actionability rather than by subject.
emptyDirectories only. Write your own conventions.

None of it is enforced in code. The layout is an opinion, not a schema.

โ Storage

/data/
โ””โ”€โ”€ <vault>/
    โ”œโ”€โ”€ inbox/  raw/  wiki/ โ€ฆ   your notes, plain Markdown
    โ”œโ”€โ”€ attachments/  templates/
    โ”œโ”€โ”€ .git/                   one repository per vault
    โ””โ”€โ”€ .secondbrain/
        โ”œโ”€โ”€ index.db            SQLite + FTS5. A cache. Delete it, it rebuilds.
        โ”œโ”€โ”€ trash/              timestamped copies of anything overwritten
        โ””โ”€โ”€ instructions.md     the vault's conventions, sent to the client

Path safety is structural, not a blacklist: after cleaning, a path must be relative, stay inside the vault, and no component may begin with a dot. That one rule also makes .git, .obsidian and .secondbrain unreachable through every tool, with no special case to keep in sync.

โ Security

  • OAuth 2.1 with Dynamic Client Registration; PKCE S256 mandatory.
  • Tokens stored as SHA-256 hashes, in memory only. Refresh tokens rotate; reusing a rotated one invalidates the whole family.
  • Constant-time password comparison, and an unknown username still pays the cost so timing cannot enumerate accounts.
  • Strict CSP on the login page, single-use CSRF token, no external assets.
  • Rate limits on failed logins, client registrations and tool calls.
  • An audit log that never contains note text, at any log level.

The full threat model, including what secondbrain deliberately does not defend against: https://andreaskasper.github.io/secondbrain/security.htmlโ 

โ The image

  • Built FROM gcr.io/distroless/static-debian12:nonroot โ€” no shell, no package manager, one static Go binary. Pure-Go SQLite, so no libc.
  • Runs as nonroot, UID 65532. /data must be writable by that UID.
  • linux/amd64 and linux/arm64 โ€” arm64 is a first-class target, a good deal of this runs on small home servers.
  • Every release carries an SBOM and a signed build provenance attestationโ .
โ Tags
TagPoints at
latestthe most recent release
1.0.260731that exact release
1.0, 1the newest release of that line
sha-1a2b3c4one exact commit
โ Also on GitHub Container Registry

The identical image โ€” same digest, same build:

docker pull ghcr.io/andreaskasper/secondbrain:latest

Static binaries for Linux and macOS, if you would rather not run a container, are attached to each GitHub releaseโ .

Made by Andreas Kasperโ 

Tag summary

Content type

Image

Digest

sha256:a519059e2โ€ฆ

Size

8 MB

Last updated

9 days ago

docker pull andreaskasper/secondbrain