Skip to main content

DuckDB-backed MCP memory server for Obsidian vaults — structured search, read, and write access for AI coding agents.

Project description

DuckBrain

DuckBrain

DuckDB-backed MCP memory server for Obsidian vaults. Gives AI coding agents read/write access to your personal wiki — structured pages, full-text search, automatic indexing.

Installation

Pick your agent:

  • OpenCode — MCP server + session plugin (recommended)
  • Claude Code — MCP server + CLAUDE.md + SessionStart hook (prototype)
  • Cursor — MCP server + rules + hooks (prototype)
  • Hermes — MCP server + AGENTS.md

OpenCode

Best experience — session plugin gives the AI automatic vault awareness.

pip install duckbrain

Add to opencode.json:

{
  "mcp": {
    "duckbrain": {
      "command": "uv",
      "args": ["run", "duckbrain"],
      "env": { "VAULT_PATH": "/path/to/your/vault" }
    }
  }
}

Download the session plugin (no repo clone needed):

mkdir -p ~/.config/opencode/plugins/
curl -o ~/.config/opencode/plugins/vault-context.ts \
  https://raw.githubusercontent.com/timhiebenthal/duckbrain/main/opencode/plugins/vault-context.ts

The plugin makes the AI aware of your vault topics and recent daily notes automatically — no manual tool calls needed. It also adds a learnings ritual and journaling rule so the AI saves session progress on its own.

Restart OpenCode.


Claude Code ⚠️ prototype

pip install duckbrain

Add to .claude/settings.json (project) or ~/.claude/settings.json (global):

{
  "mcpServers": {
    "duckbrain": {
      "command": "uv",
      "args": ["run", "duckbrain"],
      "env": { "VAULT_PATH": "/path/to/your/vault" }
    }
  }
}

CLAUDE.md

Add to .claude/CLAUDE.md:

# DuckBrain vault

Call vault_info() at session start to discover vault topics.
Use vault_search() or vault_read() when the query matches vault content.
Use vault_context() to load daily notes and search in one call.
After non-trivial work, save learnings with vault_write().

SessionStart hook (optional — auto-context)

Prototype — based on Claude Code docs, not yet validated end-to-end. Scripts work, but hook → injection pipeline needs manual verification.

For automatic vault awareness without manual tool calls, add a SessionStart hook. Download the script (no repo clone needed):

mkdir -p ~/.claude/hooks/
curl -o ~/.claude/hooks/vault-context.sh \
  https://raw.githubusercontent.com/timhiebenthal/duckbrain/main/scripts/claude-vault-context.sh
chmod +x ~/.claude/hooks/vault-context.sh

Add to the same .claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "/full/path/to/.claude/hooks/vault-context.sh"
          }
        ]
      }
    ]
  }
}

The hook injects vault tags and recent daily notes into Claude's context at session start — no manual vault_info() needed.

SessionEnd hook (optional — auto-journal)

Auto-stamp session end in today's daily note:

curl -o ~/.claude/hooks/vault-journal.sh \
  https://raw.githubusercontent.com/timhiebenthal/duckbrain/main/scripts/claude-vault-journal.sh
chmod +x ~/.claude/hooks/vault-journal.sh

Add to .claude/settings.json:

{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/full/path/to/.claude/hooks/vault-journal.sh"
          }
        ]
      }
    ]
  }
}

Appends ## Session end — HH:MM to today's daily note when the session ends.

Restart Claude Code.


Cursor ⚠️ prototype

pip install duckbrain

Add to .cursor/mcp.json:

{
  "mcpServers": {
    "duckbrain": {
      "command": "uv",
      "args": ["run", "duckbrain"],
      "env": { "VAULT_PATH": "/path/to/your/vault" }
    }
  }
}

Optionally add .cursor/rules/duckbrain.mdc with alwaysApply: true:

---
description: DuckBrain vault knowledge base integration
alwaysApply: true
---
# DuckBrain vault
Call vault_info() at session start to discover vault topics.
Use vault_search() when the query matches vault content.

SessionStart hook (prototype — context injection)

Prototype — Cursor's additional_context from sessionStart hooks has a confirmed bug (dropped due to timing). The env output works, but context injection does not yet. Track: Cursor forum

mkdir -p ~/.cursor/hooks/
curl -o ~/.cursor/hooks/vault-context.sh \
  https://raw.githubusercontent.com/timhiebenthal/duckbrain/main/scripts/cursor-vault-context.sh
chmod +x ~/.cursor/hooks/vault-context.sh

Add to ~/.cursor/hooks.json:

{
  "version": 1,
  "hooks": {
    "sessionStart": [
      { "command": "/full/path/to/.cursor/hooks/vault-context.sh" }
    ]
  }
}

SessionEnd hook (auto-journal)

curl -o ~/.cursor/hooks/vault-journal.sh \
  https://raw.githubusercontent.com/timhiebenthal/duckbrain/main/scripts/cursor-vault-journal.sh
chmod +x ~/.cursor/hooks/vault-journal.sh

Add to ~/.cursor/hooks.json:

{
  "version": 1,
  "hooks": {
    "sessionEnd": [
      { "command": "/full/path/to/.cursor/hooks/vault-journal.sh" }
    ]
  }
}

Appends ## Session end — HH:MM to today's daily note when a session ends.

Restart Cursor.


Hermes

Add to mcp.json:

{
  "mcpServers": {
    "duckbrain": {
      "command": "uv",
      "args": ["run", "duckbrain"],
      "env": { "VAULT_PATH": "/path/to/your/vault" }
    }
  }
}

Add to AGENTS.md:

# DuckBrain vault
Call vault_info() at session start to discover vault topics.
Use vault_search() when the query matches vault content.
After non-trivial work, save learnings with vault_write().

Restart Hermes.


Tools

Tool What it does
vault_search Full-text search over vault pages
vault_read Read a page by title or filepath
vault_write Create a page or append to today's daily note
vault_context Load daily notes + keyword search in one call
vault_info Vault stats: page counts, tags, last modified

Vault Schema

your-vault/
├── wiki/
│   ├── entities/       # people, orgs, tools
│   ├── concepts/       # ideas, frameworks
│   ├── sources/        # summaries of ingested content
│   ├── synthesis/      # cross-cutting analysis
│   ├── index.md        # page catalog (auto-updated)
│   ├── log.md          # write history (auto-updated)
│   └── tags.md         # topic index (auto-updated)
├── daily/              # daily notes (YYYY-MM-DD.md)
└── .env                # VAULT_PATH (optional)

Pages use YAML frontmatter:

---
title: Claude Mem
item-type: entity
tags: [ai, memory, mcp]
created: 2026-05-28
updated: 2026-05-28
---

Nerdy Details

Implementation internals — not needed for installation.

Architecture

┌──────────────────────────────────────────────────────────────┐
│                      AI Agent                                │
│  ┌──────────────────────────┐  ┌──────────────────────────┐  │
│  │ MCP Client (stdio)       │  │ Hooks / Plugins          │  │
│  │  vault_search,           │  │ (SessionStart, system    │  │
│  │  vault_read, vault_write │  │  transform — inject      │  │
│  │  vault_context, vault_info│  │  vault context into      │  │
│  │                          │  │  system prompt)          │  │
│  └──────────┬───────────────┘  └──────────┬───────────────┘  │
└─────────────│──────────────────────────────│──────────────────┘
              │ MCP stdio                    │ reads directly
              ▼                              ▼ from vault
┌──────────────────────────────┐  ┌──────────────────────────────┐
│  DuckBrain MCP Server        │  │ Side channel:                │
│  vault_info   ──► DuckDB FTS │  │  wiki/tags.md                │
│  vault_search ──► DuckDB FTS │  │  daily/YYYY-MM-DD.md         │
│  vault_read   ──► Filesystem │  │  wiki/log.md                 │
│  vault_write  ──► Filesystem │  │                              │
└──────────────┬───────────────┘  └──────────────────────────────┘
               │ reads/writes
               ▼
┌──────────────────────────────────────────────────────────────────────┐
│                     Your Obsidian Vault                              │
│  wiki/entities/  wiki/concepts/  wiki/sources/  wiki/synthesis/      │
│  daily/          wiki/index.md   wiki/log.md    wiki/tags.md         │
└──────────────────────────────────────────────────────────────────────┘
  • Reads vault files directly — no index to sync, no watchers, no duplicate storage
  • Searches via DuckDB FTS (BM25 ranking), rebuilt fresh from disk on every query
  • Writes new pages with YAML frontmatter, auto-updating index, log, and tags

Inspirations

This project stands on the shoulders of several ideas and tools:

  • Andrej Karpathy's LLM wiki pattern — the idea that a personal markdown wiki, co-maintained by humans and AI agents, compounds into a persistent knowledge base. The vault schema (entities, concepts, sources, synthesis, daily log) is directly inspired by this.
  • DuckDB — the embedded analytical database that makes full-text search over flat files viable without a server, index sync, or persistent storage. The decision to use in-memory FTS instead of a vector database was a deliberate trade-off for simplicity.
  • Obsidian — the local-first, markdown-native note-taking tool that treats your files as the truth. DuckBrain exists because Obsidian vaults deserve tooling that respects the filesystem.
  • MemSearch and Open Brain (OB1) — early experiments in cross-tool agent memory that demonstrated the need for structured vault write-back while choosing different architectures. Their strengths and gaps directly informed DuckBrain's design.
  • Agent Memory Systems (6-level taxonomy) — Simon Scrapes' comprehensive comparison of Claude Code memory approaches provided the framework for understanding where DuckBrain fits in the ecosystem (Level 6: cross-tool MCP with dedicated server).
  • trellis-datamodel — the same author's data modeling tool whose CI/CD patterns (trusted PyPI publishing, version-diff release detection, Keep a Changelog) were borrowed for this project's repository readiness.
  • mondayDB 3 — Solving HTAP for a Trillion-Table System — monday.com's engineering blog on their DuckDB-powered CQRS read serving layer at production scale. Proved that DuckDB in-process with per-tenant file isolation is a viable architecture — the same pattern DuckBrain applies at personal-wiki scale.

The core decision — build, don't integrate — came from a structured comparison of 7 existing tools. All failed on one requirement: vault schema-aware write-back. Rather than fork or extend, DuckBrain started from first principles: what's the simplest thing that gives agents structured read/write access to an Obsidian vault? The answer was DuckDB + MCP + ~500 lines of Python.


Configuration

DuckBrain works out of the box with standard vault layouts (entity, concept, source, synthesis, daily). If your vault has a custom structure, add a duckbrain.config.json file in the vault root.

Quick Start

# Copy the example config from the repo
curl -o /path/to/your/vault/duckbrain.config.json \
  https://raw.githubusercontent.com/timhiebenthal/duckbrain/main/duckbrain.config.example.json

Then edit the file to match your vault structure.

Config File Format

duckbrain.config.json defines how DuckBrain scans and writes your vault:

Section Purpose
scan.patterns[] Where to find pages and how to extract metadata
write.rules.{kind} Per-kind writing behavior (directory, frontmatter, index)
write.default Fallback for kinds without explicit rules

Scan Patterns

{
  "glob": "wiki/projects/*.md",   // path glob relative to vault root
  "kind": "project",               // page kind name
  "frontmatter": {
    "enabled": true,               // whether files have YAML frontmatter
    "kind_field": "item-type"      // frontmatter key that stores the kind
  },
  "dates": {
    "created": "frontmatter:created",  // "frontmatter:field" | "filename" | "mtime"
    "updated": "frontmatter:updated"
  }
}

Write Rules

{
  "mode": "create",                  // "create" (new file) or "append" (add to existing)
  "directory": "wiki/{kind}s/",      // directory template
  "filename": "{slug}.md",           // filename template
  "frontmatter": true,               // generate YAML frontmatter
  "frontmatter_fields": {            // fields to include in frontmatter
    "title": "{title}",
    "item-type": "{kind}",
    "tags": "{tags}",
    "created": "{date}",
    "updated": "{date}"
  },
  "update_log": true,                // append entry to wiki/log.md
  "update_index": true,              // insert entry into wiki/index.md
  "index_section": "{Kind}",         // section header name in index.md
  "excluded_tags": ["foo"]           // tags to exclude from wiki/tags.md
}

Template Variables

Variable Resolves to
{kind} Page kind string (e.g. "project")
{Kind} Capitalized kind (e.g. "Project")
{kinds} Kind with s appended (e.g. "projects")
{slug} Slugified title (e.g. "my-project")
{title} Original page title
{date} Today's date in ISO format (YYYY-MM-DD)
{tags} Comma-separated tag list

No Config = Defaults

If your vault doesn't have a duckbrain.config.json, DuckBrain uses built-in defaults that match the standard layout. Zero changes needed for most users.

Vault Audit

Unsure what your vault looks like? Run vault_audit to see its structure:

vault_audit()

It reports directories, file counts, frontmatter patterns, date conventions, and page kinds — useful when designing a custom config.


Building from Source

git clone https://github.com/timhiebenthal/duckbrain.git
cd duckbrain
uv sync
uv run duckbrain  # will hang waiting on stdio — that's correct

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

duckbrain-0.4.0b1.tar.gz (21.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

duckbrain-0.4.0b1-py3-none-any.whl (27.7 kB view details)

Uploaded Python 3

File details

Details for the file duckbrain-0.4.0b1.tar.gz.

File metadata

  • Download URL: duckbrain-0.4.0b1.tar.gz
  • Upload date:
  • Size: 21.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for duckbrain-0.4.0b1.tar.gz
Algorithm Hash digest
SHA256 fe1768efcf942298eaab123562bd66f1b96aa4af1930afba76a7dc2c2945f8ec
MD5 afd5cb0d8498cc4fea331c8cc58de927
BLAKE2b-256 ec420b2e421bbf3155598273cddf889b0b66c3f1909d4f2bd32b5412e82daac1

See more details on using hashes here.

File details

Details for the file duckbrain-0.4.0b1-py3-none-any.whl.

File metadata

  • Download URL: duckbrain-0.4.0b1-py3-none-any.whl
  • Upload date:
  • Size: 27.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for duckbrain-0.4.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 1c595ae918fd0eb6567e36089370dc0112ecd28d6d86f483ee8fb685b5fab6ae
MD5 71e3ab2dc9db2c4b69b38505daac47ab
BLAKE2b-256 5bfd4ea10c68a0394d17c4c47d17c10b2d35c7e3195e054211cb341609fcdc77

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page