Skip to content

Repository files navigation

PeperNoten

An LLM-assisted tool that turns arXiv papers into a knowledge library inside an Obsidian vault. The name is a pun on paper (reading) notes and pepernoten, the Dutch spiced cookie.

pepernoten_cli.py parses papers into structured, interconnected notes: AI synthesis, figure extraction, topic clustering, and BibTeX generation. See docs/index.html for the full developer guide.

What it does

  • Fetches arXiv HTML/PDF, extracts figures, and calls an LLM (via Claude if found locally, or OpenRouter) to synthesise a structured markdown note
  • Notes include: TL;DR, problem, methodology, results, ablation, related work, gaps/limitations, BibTeX, embedded figures
  • Clusters notes into topic survey files and keeps them updated as new papers arrive, gradually building a living review per subfield. Each note gets a changelog of what it contributed to each topic
  • Generates up-to-date BibTeX by searching Papers With Code, CrossRef, Semantic Scholar, and DBLP
  • Optionally syncs with Scholar Inbox to parse your daily digest
  • Accessible via a MCP server for integration with Claude workflows

Setup

Prerequisites

  • uv for Python dependency management
  • An OpenRouter API key, or a logged-in claude CLI — with no key set, Claude models route through claude -p automatically (vision-assisted figure selection is disabled in that mode)
  • Optional: a Scholar Inbox account
git clone https://github.com/ameroyer/pepernoten.git
cd pepernoten
uv sync
# Optional - If `PEPERNOTEN_MODEL` is an Anthropic model and you have `claude` insteall, pepernoten will use that. Otherwise, it will attempt to use OpenRouter for model selection.
export OPENROUTER_API_KEY=sk-or-...

# Optional - authenticate scholarinboxcli for the "inbox" command:
#  1. Open a digest email from Scholar Inbox, right-click any paper link, and copy it.
#     It looks like: https://www.scholar-inbox.com/login?sha_key=YOUR_KEY&date=...&paper_id=...
#  2. Run:
uvx scholarinboxcli auth login --url "https://www.scholar-inbox.com/login?sha_key=YOUR_KEY"

The vault is the project root itself — point Obsidian at /path/to/pepernoten. Notes are stored under Research/.

Usage (CLI)

# From a local clone
uv run pepernoten_cli.py

# From GitHub directly (no install needed)
uvx --from git+https://github.com/ameroyer/pepernoten pepernoten
Command What it does
parse Paste one or more arXiv URLs or IDs. Fetches HTML/PDF, extracts figures, synthesises a structured note, updates matching topic files.
inbox Fetches your Scholar Inbox digest. Tick the papers you want — only those are parsed.
topics Lists all registered topics with paper counts and last-updated date.
list Browse all papers in the vault; press d to delete one.
update_knowledge Add or remove papers from topic files; propose and execute topic merges.
add_topic Describe a topic in a sentence. Claude searches the whole vault, picks the papers that belong, and writes the survey.
bibtex Generate BibTeX for a paper — checks PWC, CrossRef, Semantic Scholar, DBLP before falling back to @misc.
quit Exit.

MCP servers

There are two MCP servers, one with read-only features, and one allowing you to also access the parse and add_topic commands.

  • pepernoten-mcp (read-only) — consult your notes: read the note for a paper, read a topic survey, find a result/method/claim across the vault. Cannot modify anything; the safe choice when consultation is all you need.
  • pepernoten-write-mcp (read + write) — everything above, plus parse (turn arXiv papers into notes) and add_topic (write a survey from a description).

Both work against a local vault or a GitHub repo. Setup is two independent choices: how to launch the server, and which backend it uses.

Choosing the backend

The notes in Research are essentially the knowledge base used by the MCP. You can use two backends:

  • Notes are stored locally (e.g. if you have a local pepernoten install)
  • (Recommended) Notes are stored on a (private) GitHub repo. This will allow you to access your pepernoten notes from anywhere. You will only need to generate a Github token for this specific repo. In this setup, don't confuse the two repos involved: ameroyer/pepernoten is the code you're reading now, only used to launch the servers (uvx --from git+...). Your notes live somewhere else (e.g. ameroyer/pepernoten_notes).

Backing up the vault in a Github repo

Research/ is gitignored in this repo (your notes are yours). To version it into a separate private repo, make Research/ itself a git repository — nested repos are fine, the pepernoten repo ignores the whole directory:

# one-time setup (create the private repo first, e.g. gh repo create USERNAME/pepernoten_notes --private)
cd /path/to/pepernoten/Research
git init -b main
git remote add origin git@github.com:USERNAME/pepernoten_notes.git

# each backup
cd /path/to/pepernoten/Research
git add -A && git commit -m "backup" && git push -u origin main

The resulting repo holds the contents of Research/ at its root (notes, Topics/, index files) — exactly the default layout the MCP servers expect. Set PEPERNOTEN_GITHUB_REPO=USERNAME/pepernoten_notes and your backed-up notes are readable (or writable, with the write server) from anywhere, no further configuration.

Adding the MCP server

# 1. local checkout + local vault
claude mcp add pepernoten \
  --env PEPERNOTEN_VAULT=/path/to/vault \
  -- uv run --directory /path/to/pepernoten pepernoten-mcp

# 2. From Github repo eg.g USERNAME/pepernoten_notes
# Go to https://github.com/settings/personal-access-tokens and create new token for this repo, and add
# permissions for "Contents" in `Read` and `Write` (or just `Read` if you only want the MCP read features).
# Copy paste the token and save it somewhere you will only see it once

# 2a. Read-only version
claude mcp add pepernoten \
  --env PEPERNOTEN_GITHUB_REPO=USERNAME/pepernoten_notes \
  --env PEPERNOTEN_GITHUB_TOKEN=github_pat_... \
  -- uvx --from git+https://github.com/ameroyer/pepernoten pepernoten-mcp

# 2b. OR the read+write server on the same notes repo (token needs Contents read-write there)
claude mcp add pepernoten \
  --env PEPERNOTEN_GITHUB_REPO=ameroyer/pepernoten_notes \
  --env PEPERNOTEN_GITHUB_TOKEN=github_pat_... \
  --env OPENROUTER_API_KEY=sk-or-... \ # optional OpenRouter API key
  -- uvx --from git+https://github.com/ameroyer/pepernoten pepernoten-write-mcp

Equivalent .mcp.json / Claude Desktop claude_desktop_config.json entry:

{
  "mcpServers": {
    "pepernoten": {
      "command": "uvx",
      "args": ["--from", "/path/to/pepernoten", "pepernoten-mcp"],
      "env": { "PEPERNOTEN_VAULT": "/path/to/vault" }
    }
  }
}

Read tools (both servers): vault_info, list_papers, read_note (by arXiv ID, filename, or partial title), search_notes (full-text + tag filter), list_tags, list_topics, read_topic.

Write tools (read+write server only; needs LLM access — OPENROUTER_API_KEY, or a logged-in claude CLI when all configured models are Claude models):

  • parse(arxiv_ids, verbosity=2, update_topics=True) — full pipeline per paper: fetch, figures, synthesis, note, topic updates. Already-parsed papers are skipped.
  • add_topic(description) — an LLM selects the vault papers that fit the description and writes the survey.

Write calls take minutes per paper (arXiv rate limits + LLM calls). The server streams every pipeline step as an MCP progress notification, so clients with an idle timeout (like Claude Code) see a live heartbeat instead of assuming a hang — no timeout tuning needed. If arXiv rate limits your IP, calls fail fast with "arXiv rate limited this machine — wait a few minutes and try again" instead of stalling.

Safety model:

  • The read-only server cannot modify anything — no parse, no write, no delete tools exist.
  • All reads are confined to Research/ — absolute paths, .., and symlink escapes are rejected, and only .md notes (plus the three index JSONs, internally) are readable.
  • The read backend talks exclusively to api.github.com (repo/branch names validated, 30 s timeouts, 2 MB file cap) and the token is sent only in request headers — it never appears in URLs, errors, or logs. The write server passes its token to git via environment-based config, never on the command line or in the clone's on-disk config.
  • The write server's only mutations are the pipeline's own outputs, committed as plain pepernoten: … commits — review or revert them like any other commit.
  • Retrieved note content is wrapped in an "untrusted data" banner so a client LLM is less likely to follow instructions embedded in a note (prompt-injection hardening — the client still decides).

Configuring the MCP server

The variables below all describe where the notes are, never the pepernoten code, and configure both servers the same way:

Variable Meaning
PEPERNOTEN_VAULT (i) Notes in a local Obsidian vault - the vault root containing Research/ (default: the pepernoten checkout itself, which doubles as a vault)
PEPERNOTEN_GITHUB_REPO (ii) Notes in a GitHub repo - owner/name of the repo storing your notes, e.g. ameroyer/pepernoten_notes. Setting this switches to the GitHub backend
PEPERNOTEN_GITHUB_TOKEN Fine-grained PAT scoped to just that notes repo, with Contents permission: Read-only for the read MCP server (private notes repos only), Read and write for the write server (always required, it pushes commits to the notes repo)
PEPERNOTEN_GITHUB_BRANCH Branch of the notes repo to use (default: its default branch)
PEPERNOTEN_GITHUB_ROOT Where the notes live inside the repo. Default: the repo root itself holds the contents of Research/ (notes, Topics/, index files). Set to Research for a repo with a Research/ directory; the write server supports only these two layouts

The read-only server reads the notes repo over the REST API. The read+write server instead keeps a clone of it under ~/.cache/pepernoten/: write calls sync, run the pipeline, commit, and push; read calls serve the clone, refreshing it first whenever no write is in flight. Expect a pepernoten: parse … commit on the notes repo after each write call, authored as pepernoten <pepernoten@invalid> — a label, not an account (override with PEPERNOTEN_COMMIT_NAME / PEPERNOTEN_COMMIT_EMAIL if you want them attributed to you).

Configuring prompts

Edit pepernoten_prompts.yaml — no code changes needed:

paper_synthesis:
  analyst_role: "You are a sharp, critical research analyst writing notes for a PhD researcher"
  tones:
    1: "Precision and brevity. Expert reader."
    2: "Precision and depth. ML researcher, not a subfield specialist."
    3: "Subfield newcomer — explain design choices."
    4: "ML newcomer — define all jargon, use analogies."

topics:
  init_role: "You are a senior researcher writing a living review document…"
  update_role: "You are maintaining a living review document…"
  discover_role: "You analyze a vault's topic structure to find merge opportunities…"

Keys not present fall back to hardcoded defaults silently. Only semantics are configurable — JSON format, field specs, and retry logic are not exposed.

Tones correspond to verbosity levels, set per paper when parsing. Default levels:

Level Target reader Depth
1 Expert Minimal — terse bullets, key numbers only
2 ML researcher (default) Standard — full sections, key comparisons
3 Subfield newcomer Expanded — intuition for design choices
4 ML newcomer Full — definitions, analogies, concepts section

Scripts (for automation / one-shot use)

# Parse
uv run scripts/parse.py parse https://arxiv.org/abs/2405.12345
uv run scripts/parse.py parse_many 2405.12345 2406.67890
uv run scripts/parse.py sync                   # Scholar Inbox top-N

# Topics
uv run scripts/topic_manager.py list
uv run scripts/topic_manager.py create "My Topic" --tags a,b --benchmarks B1
uv run scripts/topic_manager.py add "efficient video tokenization for streaming models"
uv run scripts/topic_manager.py init <slug>
uv run scripts/topic_manager.py init_all
uv run scripts/topic_manager.py update Research/SomePaper.md
uv run scripts/topic_manager.py discover        # cluster unmatched papers into new topics
uv run scripts/topic_manager.py backlink_topics # refresh ## Papers sections

# BibTeX
uv run scripts/bibtex.py generate 2405.12345 --update_note
uv run scripts/bibtex.py batch 2405.12345 2406.67890 --bib_file=refs.bib

Note structure

Each parsed paper produces a .md note in Research/ with:

  • Frontmatter — title, authors, date, arXiv ID, tags, thumbnail, verbosity
  • TL;DR callout — one-sentence summary
  • Sections — Problem, Methodology, Results, Ablation (depth varies by verbosity)
  • Callouts — Gaps ([!danger]) and Limitations ([!warning])
  • Related Work table — direct competitors with arXiv links and gap analysis
  • BibTeX block — auto-populated after bibtex generate

Topic files

Topic files live in Research/Topics/ and are maintained automatically:

  • Created from a prompt: add_topic takes a one-line description, searches the vault, and writes the survey from whatever papers actually fit
  • Auto-matched: once a topic exists, new papers join it if their tags or benchmarks overlap its fingerprint
  • Living surveys — Introduction, Benchmarks, Methods & Baselines table, Techniques & Tricks, Architecture Overview, Open Problems & Gaps
  • Method Index — appended automatically; maps every method short name → full title → link (covers cited baselines even without an arXiv ID)
  • Paper backlinks## Papers section lists all vault papers as Obsidian wikilinks

Vault layout

pepernoten/
├── Research/
│   ├── Paper Title.md          ← synthesised paper notes
│   ├── .paper_index.json       ← {arxiv_id: {title, file}}
│   ├── .tag_index.json         ← accumulated tag vocabulary
│   ├── images/                 ← extracted figures
│   ├── Thumbnails/             ← banner images
│   └── Topics/
│       ├── my-topic.md         ← living survey files
│       └── .topic_index.json   ← topic metadata + fingerprints
├── pepernoten_cli.py
├── pepernoten_prompts.yaml     ← user-configurable prompt semantics
├── src/                        ← library modules
└── scripts/                    ← fire.Fire CLI entry points

About

Automated paper reading notes and growing knowledge base

Topics

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages