Skip to main content
Glama

Forge — local RAG over your meeting notes

Forge turns a folder of Markdown meeting notes into a searchable memory that an AI assistant can query. Search runs entirely on your machine: the embedding model is local, the vectors live in a SQLite file, and no part of the search path makes a network call.

It exposes one MCP tool, vault_search, so from Claude Code (or any MCP client) you can ask things like "what did we decide about rate limits, and who owns the follow-up?" and get pointers to the exact sections of the exact notes.

You:    Use vault_search to find what we decided about API rate limits.
Claude: [searches] → 3 sections across 2 notes → reads them → answers with
        the decision, the owner, and the date.

Optionally, two LLM agents read your raw notes and distil them into durable per-theme and per-customer summaries. Those are the only parts that call out to a model provider, and they are opt-in.


Contents


Related MCP server: obsidian-rag-mcp

How it works

your notes (.md)  →  index.py  →  .forge-index/index.sqlite  →  server.py  →  MCP client
                     chunk by ##     vectors + FTS5 keyword       hybrid          (Claude Code)
                     embed local     index + metadata             retrieval
  1. index.py walks your vault, splits each note into one chunk per ## section, embeds each chunk locally with bge-small-en-v1.5, and stores the vectors, a keyword (FTS5) index, and metadata in one SQLite file.

  2. server.py answers vault_search(query, top_k) by running a semantic search and a keyword search in parallel and blending the scores. It returns pointers — file path, section heading, date, relevance, short preview — and the client reads the full files itself.

  3. ./forge is a small CLI that fronts everything, so you never have to remember individual script paths.

ARCHITECTURE.md has the full diagram and component map.


Requirements

  • Python 3.11+

  • uv (or use python -m venv and pip)

  • ~150 MB disk for the embedding model, downloaded once on first index

Your Python must support SQLite extensions

Forge uses sqlite-vec, which needs sqlite3.Connection.enable_load_extension. Several common Python builds omit it, and you will get AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension'. Check first:

python3 -c "import sqlite3; sqlite3.connect(':memory:').enable_load_extension"

Silence means you are fine. If it raises:

  • macOS — the system Python and most pyenv builds are compiled without it. Use Homebrew's:

    brew install python@3.13 sqlite
    uv venv --python /opt/homebrew/bin/python3.13 .venv
  • Linux — install a distro Python built with --enable-loadable-sqlite-extensions (Debian/Ubuntu's python3 is; a hand-built one may not be), or rebuild with PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" pyenv install 3.13.


Install

git clone https://github.com/blittlemore/forge-rag.git
cd forge-rag
uv venv .venv
uv pip install -r requirements.txt

You do not need to activate the venv — ./forge, index.py, and server.py each re-exec themselves under .venv automatically.


60-second demo

The repo ships a synthetic sample vault (six invented notes for a fictional company) so you can see it work before writing anything of your own:

FORGE_VAULT_ROOT=examples ./forge run index
FORGE_VAULT_ROOT=examples ./forge run search "api rate limits"

The first index downloads the embedding model (~130 MB); after that everything is offline. Other queries worth trying: single sign-on, sprint capacity, hybrid search, service account blocker. See examples/README.md for what each note contains.


Using it with your own notes

Two options.

A: keep notes inside the repo (simplest). Create meetings/ and put your .md files in it — it is git-ignored, so your notes are never committed:

mkdir -p meetings/2026/07
# add your notes, then:
./forge run index
./forge run search "whatever you discussed"

B: point Forge at an existing vault (e.g. an Obsidian folder), leaving your notes where they are:

export FORGE_VAULT_ROOT=~/Documents/MyVault   # must contain a meetings/ dir
./forge run index

Forge indexes meetings/, plus workstreams/ and customers/ if the agents have created them. The SQLite index always lives in .forge-index/ inside the repo, not in your vault.


The note format

YAML frontmatter, then ## sections:

---
title: "Reporting Pipeline Weekly"
date: "6 Jul 2026"
meeting_type: "Team sync"
tags: [reporting, pipeline]
---

# Reporting Pipeline Weekly

## Summary

Weekly sync on the nightly reporting pipeline...

## Decisions

- Ship the export job behind a feature flag.

## Action items

- Jordan to request a service account. Due 8 Jul.

What matters:

  • One chunk per ## section — sections are the retrieval unit, so keep each one about one thing. Adjacent chunks overlap slightly so a fact spanning a boundary still appears intact somewhere.

  • title and date come from frontmatter. date parses as D MMM YYYY. meeting_type and tags are optional. Only title and date are used for display.

  • Name files YYYY-MM-DD HH.MM - Title.md. The agents read the date prefix to find "today's" notes; it takes precedence over the frontmatter date.

  • Text before the first ## is not indexed — the H1 is only a title fallback, so put real content under a heading.

  • meetings/_transcripts/ is skipped, so raw transcripts can sit next to the summaries without polluting results.

Notes written by Minute already match this format. So does anything the included Notion importer produces.


Connect it to Claude Code

Register the MCP server once, from the repo root:

claude mcp add forge -- "$(pwd)/.venv/bin/python" "$(pwd)/server.py"

Then in any Claude Code session:

Use vault_search to find meeting sections about onboarding blockers,
then read the top file and summarise the decisions.

vault_search(query, top_k=5) returns the most relevant chunks with file path, section heading, date, and relevance score. It makes no LLM calls — the client does the reading and reasoning.

It also refreshes the index lazily on every query: edit a note and search immediately, and the change is picked up (detected by content hash) before results are returned. You rarely need to run index manually.


The forge CLI

./forge list                    # every capability
./forge info <name>             # usage, flags, whether it's safe unattended
./forge run <name> [args...]    # run it; extra args pass through
./forge interactive             # REPL with history and tab-completion

Capability

Kind

What it does

index

core

Index the vault into the local RAG database

search

core

Search from the CLI, no MCP client needed

test

core

Run the pytest suite

workstream-sync

agent

Distil notes into per-theme workstream files

customer-sync

agent

Distil notes into per-customer profiles

notion-import

helper

Import a Notion database into the vault

fix-names

helper

Normalise mis-transcribed names

Useful index flags:

  • --force — re-index every file even if unchanged

  • --rebuild — drop and recreate the DB (required after changing the embedding model; the indexer warns you when it detects a stale one)

  • --check — exit 0 if the index is current, 1 with stale paths on stderr. No embedding, no writes — good for a pre-commit hook or CI.

Indexing is incremental by content hash, so a git checkout that reverts a file is caught, and touch is a no-op. Deleted files are pruned.

Optionally put it on your PATH:

ln -s "$(pwd)/forge" /usr/local/bin/forge

Adding a capability means adding one entry to REGISTRY in cli/registry.py — nothing else in the CLI changes.


The sync agents (optional)

Everything above is free and offline. These two agents are the exception: they send note content to a Claude model and cost money per note.

They read raw meeting notes and merge the durable facts into living summaries:

  • workstream-syncworkstreams/NN-theme.md, one file per theme, so a project's current state is in one place instead of scattered across 30 notes.

  • customer-synccustomers/<slug>.md, one profile per customer, with issues, requests, action items, and sentiment.

Both re-index afterwards, so their output is searchable through vault_search like any other note.

Setup

cp .env.example .env
# add ANTHROPIC_API_KEY=sk-ant-...
./forge run customer-sync --latest 1 --dry-run   # compute only, write nothing
./forge run customer-sync --latest 1             # for real
./forge run workstream-sync --all --limit 5      # today's notes, capped

Start with --dry-run. It runs the full pipeline and prints what would change without writing.

Providers

FORGE_PROVIDER

Credential

Notes

anthropic (default)

ANTHROPIC_API_KEY

The Claude API directly

bedrock

BEDROCK_API_KEY

Claude on Amazon Bedrock, so note content stays inside your own AWS tenant

Override models with FORGE_WORKER_MODEL / FORGE_GUARD_MODEL (they take LiteLLM-prefixed ids, e.g. anthropic/claude-sonnet-5). Defaults are Sonnet 5 for the extraction and merge work, Haiku 4.5 for the cheap guardrail checks.

Guardrails

Because these agents write files you will later trust, they are wrapped in checks: a relevance check skips non-work notes before the expensive model runs, a template check rejects output that breaks the file structure, and a grounding check rejects claims that do not trace back to the source note or the previous version of the file. Blocked output is not written, and the note stays unmarked so a later run retries it.


Import from Notion (optional)

cp helpers/notion_import/.env.example .env    # add NOTION_TOKEN + NOTION_DATABASE_ID
./forge run notion-import --dry-run           # preview
./forge run notion-import                     # 10 most recent
./forge run notion-import --all               # everything
./forge run index

Re-runs are idempotent (existing files are skipped). See helpers/README.md for creating the integration and sharing the database with it.


Normalise mis-transcribed names (optional)

Transcription mangles names ("Jordyn" for "Jordan"). fix-names rewrites known variants across the vault, and runs automatically as part of index.

It is off until you configure it — without people.json it does nothing, so you can ignore it entirely:

cp helpers/fix_names/people.json.example helpers/fix_names/people.json
# {"Jordan": ["Jordan", "Jordyn", "Jordanne"]}

people.json is git-ignored, since it holds real names.


Tests

./forge run test

63 offline tests covering the Markdown chunker, hybrid score blending, incremental indexing, the lazy server refresh, the REPL, and the agent helpers. No credentials needed and no network calls.

The LLM evals under agents/*/evals/ are separate — they make real model calls, so they are skipped unless a credential is set:

.venv/bin/python -m pytest agents/customer_sync/evals/ -v

Troubleshooting

AttributeError: ... 'enable_load_extension' — your Python was built without SQLite extension support. See Requirements.

Search returns nothing — check the index has content:

./forge run index --check
sqlite3 .forge-index/index.sqlite "select count(*) from chunks;"

Zero chunks usually means your notes are not under a meetings/ directory inside FORGE_VAULT_ROOT, or the content sits above the first ## heading.

First index is slow — that is the one-time model download (~130 MB). Subsequent runs are incremental and only re-embed changed files.

MCP server won't start in Claude Code — the registered command must use absolute paths. Re-run the claude mcp add line from the repo root so $(pwd) expands, then check with claude mcp list. Test the server standalone with ./forge run search "test".

Agent fails with a missing-key error — it names the variable it wants. FORGE_PROVIDER decides which: ANTHROPIC_API_KEY by default, BEDROCK_API_KEY for Bedrock. It reads .env at the repo root.

Stale-model warning after upgrading — run ./forge run index -- --rebuild. Vectors from a different model are not comparable.


Privacy

Forge is built to keep meeting content private.

  • Search is fully local. Embedding, vector search, and keyword search all run on your machine. The only network call in the search path is the one-time model download.

  • Your notes are never committed. meetings/, workstreams/, customers/, owner.md, .env, and helpers/fix_names/people.json are all git-ignored. Only the synthetic sample vault under examples/ is tracked.

  • The agents are the exception. They send note content to whichever provider you configure. Use FORGE_PROVIDER=bedrock to keep that traffic inside your own AWS tenant. If you never run the agents, nothing leaves your machine.

  • Agent tracing is off by default. The OpenAI Agents SDK can upload traces, but only if you set OPENAI_API_KEY. Even then, note text and model responses are excluded from spans — metadata only.


Design notes

  • Retrieval, not generation. server.py returns pointers and previews; the client reads the files. This keeps the server cheap, deterministic, and free of any LLM dependency.

  • Hybrid retrieval. Semantic search catches "same idea, different words"; BM25 keyword search catches exact product names and error codes. Running both and blending (weighted toward semantic) covers both failure modes. Falls back to vector-only against an older index with no FTS5 table.

  • One SQLite file, no services. Vectors in a sqlite-vec vec0 virtual table, keywords in FTS5, metadata in a normal table. No vector database or search process to run.

  • Content-hash incrementality. Re-indexing keys on the sha256 of file content, not mtime — so mtime-preserving tools and git operations are handled correctly in both directions.

  • Why not approximate nearest neighbours? At ~3,000 chunks the vector search takes 2.9 ms and query embedding dominates. docs/scaling-notes.md has the measurements and the ~50,000-chunk threshold where this should be revisited.


Licence

MIT — see LICENSE.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    A
    quality
    C
    maintenance
    MCP server that integrates with LM Studio to provide a search_notes tool, allowing the chat model to retrieve and answer from a local Obsidian vault.
    1
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Local MCP server for querying and maintaining a Markdown vault. Provides full-text search, backlinks, note retrieval, and optional confined write tools, without sending the whole vault to the client context.
    14
    Apache 2.0

View all related MCP servers

Related MCP Connectors

  • Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.

  • Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.

  • Agentic search over your Dewey document collections from any MCP-compatible client.

View all MCP Connectors

Latest Blog Posts

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/blittlemore/forge-rag'

If you have feedback or need assistance with the MCP directory API, please join our Discord server