NotebookLM Curator

NotebookLM Curator

An MCP server that audits and prunes Google NotebookLM libraries, enabling source listing, removal, and freshness-based audits through browser automation.

Category
访问服务器

README

notebooklm-curator

An MCP server that audits and prunes Gemini Notebook (formerly NotebookLM) libraries.

Not an official Google or Anthropic integration. This drives NotebookLM through your own signed-in Chrome profile — it is not affiliated with, endorsed by, or connected to Google in any way. Consider using a separate Google account rather than your primary one; the account parameter supports multiple profiles. See Known limitations for the full picture before pointing it at anything you care about.

Off-the-shelf NotebookLM MCPs give you add_source and ask_question. None of them give you these three:

Tool What it does In other NotebookLM MCPs
nlm_list_sources Lists the sources in a notebook ✗ missing
nlm_remove_source Deletes a source ✗ missing
nlm_audit Flags stale sources by shelf life ✗ missing

Also included: nlm_auth, nlm_list_notebooks, nlm_create_notebook, nlm_rename_notebook, nlm_add_source, nlm_ask.

nlm_audit output inside Claude Desktop

(Titles above are representative examples; the counts reflect a real run against a 56-source notebook.)


Why browser automation

NotebookLM has no public API on the consumer side. Source lists, publish dates, and deletion are not reachable over HTTP. The Gemini Notebook Enterprise API on Google Cloud is a separate product that needs an enterprise account.

So the only way in is a persistent Chrome profile. Every selector lives in one file, src/notebooklm.js — when Google ships a UI change, that's the only file that needs an update.

Selectors were verified live against notebooklm.google.com on 2026-07-29.


Requirements

  • Node.js 20+
  • Google Chrome installed (Windows or macOS — the standard desktop app from google.com/chrome). This tool automates your real Chrome install on purpose, not a downloaded Chromium build: patchright's anti-detection patches are far more effective against NotebookLM's bot checks when running inside an actual Chrome, so we don't trade that away for a "works anywhere" default. If Chrome isn't found, nlm_auth/any tool call fails immediately with a clear message instead of a cryptic browser-launch error. Because real Chrome is used by default, npm install does not download a separate Chromium build — nothing extra to fetch, nothing extra on disk. (If you deliberately opt into NLM_BROWSER_CHANNEL=chromium, run npx patchright install chromium once yourself first.)
  • Tested on Windows and macOS. Linux is not a current target.

Setup

git clone https://github.com/furkancakmakcreative/notebooklm-curator.git
cd notebooklm-curator
npm install

Copy .env.example to .env if you want nlm_audit's YouTube date resolution:

YOUTUBE_API_KEY=your_own_key

The key stays on your machine. The server only sends it to googleapis.com and never logs or forwards it elsewhere.

Connecting to Claude Desktop / Claude Code

%APPDATA%\Claude\claude_desktop_config.json (Windows) or the equivalent config on macOS/Linux:

{
  "mcpServers": {
    "notebooklm-curator": {
      "command": "node",
      "args": ["/full/path/to/notebooklm-curator/src/index.js"],
      "env": { "YOUTUBE_API_KEY": "your_own_key" }
    }
  }
}

Fully restart Claude Desktop. On first use, call nlm_auth: a visible Chrome window opens, you sign in with your Google account once, and the session is saved to a persistent local profile. Every run after that is headless.


Getting a YouTube API key

Google Cloud Console → APIs & Services

  1. Library → search "YouTube Data API v3" → Enable
  2. Credentials → Create credentials → API key
  3. Click the key → API restrictions → Restrict key → select only YouTube Data API v3 → Save

Don't skip step 3. If a restricted key ever leaks, it can only read public YouTube data — it can't touch your account or spend money.

Quota — this matters

The free daily quota is 10,000 units:

  • videos.list → 1 unit per call (up to 50 videos)
  • search.list → 100 units per call (one title)

NotebookLM never exposes a source's URL, so the first audit has to search by title: 83 sources ≈ 8,300 units, most of the daily quota. But that search returns a videoId. Save it and pass it back to nlm_audit as knownIds — every later audit then uses the cheap videos.list path, ~2 units total.

The searchBudget parameter caps this cost (default 60).


Shelf-life policy

A single global "40 days" threshold gets the wrong answer both ways: a model announcement is dead in three weeks; a typography video is still useful in three years. Shelf life is therefore a function of category:

Category Days What lands here
news 30 announcements, release notes, weekly roundups
tactics 45 rate limits, model ranking/picking advice tied to a moving target
tool 60 tool usage, workflow tied to a specific release
official 150 Anthropic/Claude official product-feature videos
tutorial 120 courses, walkthroughs, technical deep-dives
principle 1095 theory, strategy, timeless craft

nlm_audit guesses a category from the title heuristically; override any threshold with the categories parameter:

{ "notebookId": "...", "categories": { "news": 14, "tool": 45 } }

Status values: fresh → aging (75% of shelf life burned) → stale. Sources whose date can't be resolved are unknown — a date is never guessed.

By default the response only gives you counts for fresh/pinned sources, not their full per-item detail — you rarely need to see the sources that need no action. Pass includeFresh: true to get everything, e.g. for a full-library export.


Delete safety

nlm_remove_source is irreversible and refuses to run without confirm: true. nlm_audit never deletes anything — it only produces a report.

Intended flow: nlm_audit → show the list to the user → call nlm_remove_source one title at a time for what they approve. The model bulk-deleting on its own is blocked by design.


How nlm_ask actually detects "the answer is done"

This turned out to be the hardest part of the tool, worth documenting because the wrong approach looks like it works until it silently doesn't.

The first instinct is to poll document.querySelector('main').innerText until it stops changing. That's wrong: NotebookLM's chat panel renders outside <main> entirely. The text there never changes, so the polling loop reports "stable" almost instantly and returns whatever happens to be sitting in <main> at that moment — in one observed case, hidden emoji-picker markup that had nothing to do with the question asked.

The actual reliable signal is a .thinking-message element (it carries an is-changing class while streaming) detaching from the DOM once the model finishes. Even then, the query textarea's disabled attribute clears slightly after that detachment — firing the next question too early hits a still-disabled box and hangs. ask() in src/notebooklm.js waits for both, in order, before reading the last .chat-message-pair's answer text.

If you're extending this tool and NotebookLM's DOM changes again, that's the one thing worth re-verifying live before touching anything else. If a wait times out, nlm_ask sets incomplete: true on its response rather than silently returning stale or partial text as if it were final.

nlm_ask also enforces a minimum gap (default 4s, NLM_MIN_ASK_INTERVAL_MS) between question submissions. Firing questions back-to-back is a pattern real usage never produces, and it's the most plausible trigger for NotebookLM occasionally refusing to answer ("Şu anda yanıt vermekte zorlanıyorum") — this is cheap insurance against that, not a confirmed root cause.


Known limitations

  • Source URLs are not readable. NotebookLM never puts them in the DOM — no href, no data attribute, clicking a row doesn't reveal one either. Titles are used as the identifier instead, so two sources with an identical title can't be told apart (findDuplicates reports these separately).
  • The delete confirmation dialog may or may not appear depending on rollout; the code handles both and verifies the source count afterward. removeSource re-checks the target row's title immediately before acting on it, but a full guarantee against the list reordering mid-click isn't possible with index-based targeting alone.
  • UI text matching is English/Turkish only. Menu items, buttons, and the category-guessing heuristics in policy.js match against those two languages; a notebook in a third UI language may fail to categorize or to find the "remove" menu item.
  • Freshness dates only resolve for YouTube sources today. Web pages and PDFs always come back unknown — there's no per-page date extraction yet.
  • Fixed waitForTimeout calls are used in a few places instead of polling for a DOM signal; on a slow connection they can under-wait, on a fast one they add latency. ask() uses the more robust polling pattern — anything ported from removeSource/addSource should follow that model instead.
  • If Google changes the UI, src/notebooklm.js is the only file that needs updating.
  • renameNotebook and addSource report success once the DOM action is triggered, without re-reading the page to confirm it actually applied (unlike removeSource, which re-verifies). On a slow/flaky page a rename or add could silently no-op.
  • The Chrome profile directory is created with mode: 0o700, which is a no-op on Windows NTFS (no ACL is set) — on Windows the directory's permissions are whatever the OS default is for your user folder, not actually restricted to your account alone.

Roadmap

Ideas that didn't make v0.1, roughly in order of value:

  • Export an nlm_audit report to Markdown/CSV for offline review.
  • A batch-delete tool that takes a pre-approved list of {title, occurrence} pairs (still gated by an explicit human-approved list, not autonomous).
  • Cross-notebook source search/duplicate detection (findDuplicates already generalizes to this — it just isn't wired up across notebooks yet).
  • Date resolution for web sources via Last-Modified headers or article:published_time / JSON-LD datePublished metadata.

Contributions on any of these are welcome.

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选