rabbithole
Rabbithole is a local, infinite canvas for learning that lets an AI agent open, navigate, and answer questions about documents interactively in a browser โ no account or API keys required.
Core tools:
open_rabbithole: Open a new document (from raw markdown, a.mdfile path, or a PDF ingest ID) or resume a saved hole by ID. Blocks until the human interacts, returningbranch_request,keep_listening, orsession_closed. Supports local image assets, relative link resolution, and PDF-ingested assets.answer_branch: Answer a pending branch request by streaming rich markdown chunks as a new child document on the canvas. Supports math, syntax-highlighted code,showfences for inline SVG/HTML diagrams, and local image assets. Final call blocks waiting for the next event.ingest_pdf: Extract a local PDF into page-render PNGs, embedded raster images, metadata, and per-page text. Returns an ingest ID to pass toopen_rabbithole.list_rabbitholes: List all saved holes (most recently updated first) with their ID, title, last-updated time, and node count, enabling easy session resumption.
Additional capabilities:
Questions asked while no agent is listening are saved and re-queued on resume, enabling durable recursive exploration.
Rich content support: GFM markdown, LaTeX math, code blocks, interactive diagrams, and light/dark theme-aware styling.
Fully local-first: data persists as JSON files on the MCP server with no external API calls.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@rabbitholeopen this document"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Rabbithole
An infinite canvas for learning. Open a document, select any text, ask a question โ and the answer opens as a fully-rendered child document. Follow whatever pulls at you, as deep as it goes. Every hole is saved and revisitable.
๐ rabbithole.ing
There are two ways in:
The web app โ rabbithole.ing. Bring an OpenRouter key or point it at a local model. Static site, no account, no backend: your key stays in your browser, and so do your documents.
The MCP server โ for terminal agents. Claude Code, Codex, or any MCP client does the answering; Rabbithole gives it a canvas in your browser. The server, storage, and canvas all run on your machine โ your documents and questions go only to the agent you already use.
Use it on the web
Open rabbithole.ing and start from anywhere:
drop in a PDF or Markdown file, paste a URL, import a .rabbithole or
snapshot .html โ or just ask a question and let the answer become your
first document.
Two ways to run a model:
OpenRouter (recommended) โ one key, every major model. The model picker pulls OpenRouter's live catalog.
Local โ any OpenAI-compatible endpoint: Ollama, LM Studio, llama.cpp. No key required.
Keys never leave the browser: they're stored locally (or session-only, your choice) and sent exclusively to the provider origin you configure. Exports scrub anything credential-shaped.
Run the browser version locally
Requires Node 18+:
git clone https://github.com/shlokkhemani/rabbithole.git
cd rabbithole
npm install
npm run build
npx -y serve web/distOpen http://localhost:3000 (or the URL printed by
serve). The local browser build has the same OpenRouter and
OpenAI-compatible local-model options as rabbithole.ing,
and its documents and provider settings stay in that browser's local storage.
Holes persist in IndexedDB, and each document gets a memorable local URL such
as rabbithole.ing/curious-teacup-abcdef. That path names a record in your
browser's database โ it is not a sharing link. To move a hole between
machines, export the .rabbithole file; to share something readable,
download a snapshot.
Self-hosting is static: run npm run build and serve web/dist from any
host. The optional workers/fetch-proxy Cloudflare Worker enables URL
ingestion for sources that block browser CORS (set RABBITHOLE_PROXY_URL at
build time to point the app at your relay). Serve index.html as the
fallback for unknown single-segment paths so document URLs survive a direct
visit or refresh.
Related MCP server: team-docs-mcp
Quick start
Requires Node 18+ and a browser. Pick your agent:
Claude Code
claude mcp add rabbithole -- npx -y github:shlokkhemani/rabbitholeCodex
codex mcp add rabbithole -- npx -y github:shlokkhemani/rabbitholeThen raise the tool timeout in ~/.codex/config.toml โ Codex's 60-second
default is shorter than Rabbithole's blocking wait, and codex mcp add
cannot set this field:
[mcp_servers.rabbithole]
command = "npx"
args = ["-y", "github:shlokkhemani/rabbithole"]
tool_timeout_sec = 600Any other MCP client โ most accept this shape in their MCP config:
{
"mcpServers": {
"rabbithole": {
"command": "npx",
"args": ["-y", "github:shlokkhemani/rabbithole"]
}
}
}Then start a new agent session and say:
open this document in rabbithole
A browser tab opens. Select any text on the page, ask a question (or tap a lens โ Explain ยท ELI5 ยท Example ยท Go Deeper), and the answer streams in as a new document. Keep going.
If a host ever reports a tool timeout, nothing is lost โ questions are saved and re-queued the next time the agent listens.
git clone https://github.com/shlokkhemani/rabbithole.git
cd rabbithole && npm install
claude mcp add rabbithole -- node "$(pwd)/bin/mcp-server.js"๐ค If you are an AI agent setting this up
Follow these steps exactly:
Check Node:
node --versionmust be โฅ 18.Register the server with the one-liner above that matches the agent you are running inside (
claude mcp add โฆfor Claude Code,codex mcp add โฆfor Codex, or write the JSON block into the client's MCP config file). Thenpx -y github:shlokkhemani/rabbitholeform needs no clone and no install step โ npx fetches this repo and runsbin/mcp-server.jsover stdio. For Codex, also addtool_timeout_sec = 600under[mcp_servers.rabbithole]in~/.codex/config.toml;codex mcp addcannot set that field.Verify: after the client reconnects, three tools should be available โ
open_rabbithole,answer_branch,list_rabbitholes. (In Claude Code,claude mcp listshould showrabbitholeas connected. The server itself also responds to a standard MCPinitializewith server namerabbithole.)Tell your human what to do next: they should start a session and say "open <some document> in rabbithole" โ you then call
open_rabbitholewith{ title, content }(or{ title, file_path }; addbase_urlwhen the markdown came from a URL or repo; addassetsfor local images referenced asasset:name.png), which blocks until they select text and ask. Answer withanswer_branch(stream chunks withpartial: true, finish with a titled final call) and keep looping untilstatus='session_closed'. If a long wait returnsstatus='keep_listening', immediately callopen_rabbitholewith the returned{ hole_id }and do not re-send content.
Notes for you: the tool call blocks by design (long-poll) โ that is normal, not
a hang. The first npx run fetches this repo, so allow it time; later runs
are cached. If the browser must not auto-open (headless), set
RABBITHOLE_NO_BROWSER=1 in the server's env.
Tools
Tool | What it does |
| Open a doc ( |
| Answer a pending branch request โ a child document. Stream with |
| List saved holes to resume by id. |
The loop: open_rabbithole โ branch_request โ answer_branch โ branch_request โ โฆ โ session_closed.
Long waits may return keep_listening; immediately call open_rabbithole
again with the returned hole_id. If the host reports a tool timeout, do the
same โ questions are saved.
For research PDFs, page renders are the dependable figure source. For arXiv
links, prefer fetching the HTML version and opening that content with
base_url instead of ingesting the PDF.
What's inside
Reader mode (default): fullscreen reading, branches sidebar, breadcrumbs; selections become inline marks (pending โ ready); clicking a mark jumps to its answer; child docs carry a FROM strip that jumps back to the exact origin.
Streamed answers: words appear live with a breathing caret โ in the reader, the thread, and the canvas card.
Rich Markdown: answers can use math, highlighted language code fences,
showdiagrams, URL-based resolution for relative links/images, and local image assets viaasset:name.png; source stays as Markdown for copy/export, while frozen snapshots inline assets into the HTML.Interactive checks: answer multiple-choice questions inline; progress survives reloads and portable backups, while shared snapshots start clean.
Lenses: one-tap presets on the ask popup โ Explain ยท ELI5 ยท Example ยท Go Deeper (keys 1โ4).
Follow-up chat: a composer under each document asks about the doc as a whole; answers render inline and are branchable like any other text.
Canvas mode: infinite pan/zoom, draggable/resizable cards, edges that attach to the exact selected text in the parent, collapse, auto-layout.
Navigation:
j/kwalk marks,โตopens,โซjumps back up,โKsearches the whole hole.Share/export: copy any trail or document as Markdown; Download snapshot produces a single self-contained
.htmlโ data, assets, and a read-only client in one file anyone can open; Export Rabbithole (web app) produces a.rabbitholebackup for device transfer โ MCP holes are already plain JSON on disk; or ask the agent for a synthesis of the whole journey.Durable asks: questions asked while the agent is away are saved and re-queued on resume โ the agent answers them first thing.
Persistence: holes auto-save as JSON under
~/.rabbithole/; resuming restores the doc, scroll position, mode, and canvas framing.
The MCP host stores each hole as a JSON file directly under ~/.rabbithole/
(RABBITHOLE_DIR overrides the base directory) and assets under the matching
asset directory. The web .rabbithole file is the same persisted hole JSON
wrapped as { format: "rabbithole", format_version: 1, hole, assets }, with
assets base64-encoded into the single JSON file for portability.
Configuration
Env var | Effect |
| Override the storage directory (default |
| Don't auto-open the browser (headless/testing). |
| Max time for one blocking MCP wait before returning |
| Build-time: URL of your fetch-proxy relay for the web app (empty string disables the default). |
Repo layout
bin/mcp-server.jsโ entry point (stdio MCP server)src/core/โ host-independent document engine, rendering, artifacts, and contractssrc/ui/โ shared live/frozen browser runtimesrc/node/โ MCP host, filesystem storage, local HTTP/SSE, and PDF ingestionsrc/web/โ static BYOK browser host and IndexedDB storagebuild.mjsโ builds the committed MCP bundles and the static web appdist/โ committed browser bundles used by GitHubnpxinstallsweb/dist/โ generated static web app (untracked build output)scripts/โ reproducibility checks and publish assemblytest/โ capability-oriented unit, contract, integration, end-to-end, performance, and packaging suitesworkers/fetch-proxy/โ optional allowlisted URL-ingestion relaywebsite/public/โ public deployment assets consumed bybuild:publish
Development
See CONTRIBUTING.md for the development workflow and
ARCHITECTURE.md for system boundaries. Compatibility,
testing, and interface rules live under docs/. The browser runtime
source lives in src/ui/ and is bundled into committed artifacts under dist/.
When editing the UI, run:
npm run build
npm run check:distCommit both the source changes and dist/. There is no prepare build step;
GitHub npx installs use the committed artifacts.
Production deployment
The Deploy Cloudflare Pages workflow
runs the complete test suite and deploys publish/ to the rabbithole Pages
project on every push to main. It can also be rerun manually from GitHub
Actions. Each Cloudflare deployment is tagged with the exact Git commit.
The workflow requires:
repository variable
CLOUDFLARE_ACCOUNT_ID;repository secret
CLOUDFLARE_API_TOKEN, scoped to Account โ Cloudflare Pages โ Edit.
Configure them with gh variable set CLOUDFLARE_ACCOUNT_ID and gh secret set CLOUDFLARE_API_TOKEN; both commands prompt without committing credentials.
License
MIT
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/shlokkhemani/rabbithole'
If you have feedback or need assistance with the MCP directory API, please join our Discord server