remarkable-mcp
remarkable-mcp
Give an AI assistant direct control of your reMarkable tablet — list notes, upload PDFs, export handwriting, write on pages — over SSH or rmfakecloud.
The assistant gets one MCP tool, remarkable_execute, and writes a short JavaScript snippet that calls rme.* (list, mkdir, write, export, …) in one shot.
You have | This project is |
A reMarkable with developer mode | An MCP server |
SSH (USB | stdio or Streamable HTTP (Docker) |
Claude / Cursor / VS Code / any MCP client | Same |
How it works
MCP client this process storage
─────────── ──────────── ───────
remarkable_execute({ node:vm sandbox SSH → xochitl
code: `async () => { → rme.list / rme.mkdir / … → or rmfakecloud sync v3
await rme.mkdir(…)
return await rme.list({})
}`
})Default is SSH into the tablet. rmfakecloud is opt-in (RMFAKECLOUD_URL + device token).
Enable Settings → General → Developer mode on the tablet (SSH).
SSH over USB (
root@10.11.99.1) or Wi‑Fi — or pair with your rmfakecloud.Run this server. Point your MCP client at it.
Install
Node 22+. No clone required.
Claude Desktop, Cursor, VS Code, …
{
"mcpServers": {
"remarkable": {
"command": "npx",
"args": ["-y", "@lukaisailovic/remarkable-mcp"],
"env": {
"REMARKABLE_HOST": "10.11.99.1",
"REMARKABLE_USER": "root",
"REMARKABLE_PASSWORD": "your-tablet-password"
}
}
}
}Prefer a key? Set REMARKABLE_KEY to a private-key path (or the PEM). If unset, ~/.ssh/id_ed25519 then id_rsa are tried.
rmfakecloud
Same rme.*, sync-v3 instead of SFTP. Pair once, save the device token:
{
"mcpServers": {
"remarkable": {
"command": "npx",
"args": ["-y", "@lukaisailovic/remarkable-mcp"],
"env": {
"RMFAKECLOUD_URL": "https://cloud.example",
"RMFAKECLOUD_TOKEN": "device-jwt-from-pair"
}
}
}
}First pair: npx -y @lukaisailovic/remarkable-mcp --cloud https://cloud.example --pair ABCDEFGH (code from the rmfakecloud UI). It prints a device token to stderr. Writes commit the hash tree; the tablet picks them up on next Check Sync. Not official reMarkable Cloud.
HTTP
Same server, Streamable HTTP on /mcp:
npx -y @lukaisailovic/remarkable-mcp --http
# → http://127.0.0.1:8080/mcp health: /healthOr pull the image from GHCR:
docker run --rm -p 8080:8080 \
-e REMARKABLE_HOST=10.11.99.1 \
-e REMARKABLE_PASSWORD=your-tablet-password \
ghcr.io/lukaisailovic/remarkable-mcp:latestFrom a clone: docker compose up --build (bind 0.0.0.0:8080, pass REMARKABLE_*).
A local checkout’s .mcp.json watches src/ and restarts the stdio child on save (no client restart). pnpm dev:http does the same for Streamable HTTP. In Grok, /mcps disable+enable respawns the process; r only refreshes the list.
What the model can do
The client only lists remarkable_execute. Inside the snippet, methods live on rme.
async () => {
const folder = await rme.mkdir({ name: "Projects" });
await rme.createNotebook({ name: "Ideas", parent: folder.id });
await rme.writeText({
notebook: "Ideas",
newPage: true,
blocks: [
{ text: "Ship it", style: "title" },
{ text: "Talk to design", style: "checkbox" },
],
});
await rme.writeText({ notebook: "Ideas", text: "Write the RFC", style: "checkbox" });
return await rme.read({ notebook: "Ideas", page: 2 });
};A notebook is the file (name, path /Work/Notes, or UUID). A page is 1-based inside that notebook. writeText always writes native Type Folio text and appends — call it again to stack more paragraphs. Ink points are [x, y] in 0–1 from the top-left. remove moves to trash. Writes apply once after the tool returns: SSH restarts xochitl; rmfakecloud commits the sync tree.
| What it does |
| Library listing (trash hidden by default) |
| One folder, or a single notebook |
| Name / path search, optional tag |
| Id, path, type, tags, page count, |
| Native paragraphs (and checkbox state), or all pages if |
| Raw PDF/EPUB as base64 |
| Ink on a page → |
| Put a PDF or EPUB on the tablet |
| Folders and trash |
| Notebooks and pages |
| Pen / highlighter strokes |
| Mermaid → ink (flowchart, sequence, state, class, ER, xychart) |
| Native Type Folio: |
| Notebook or page tags |
| Apply now (SSH: restart xochitl; cloud: pull if remote moved, then commit) |
Configuration
Flags and env vars are interchangeable (--host ≡ REMARKABLE_HOST).
Tablet | Default | |
|
| SSH host (USB default) |
|
| SSH user |
|
| SSH port |
| Password | |
|
| Key path or PEM |
rmfakecloud | Default | |
| Base URL of your rmfakecloud | |
| Device JWT from a prior | |
| One-shot 8-letter pairing code |
Server | Default | |
| off | Streamable HTTP instead of stdio |
|
| Bind address ( |
|
| HTTP port |
| off | In-memory tablet (tests, no device) |
| Local directory treated as a xochitl tree |
Agent skill
Installable with skills.sh:
npx skills add lukaisailovic/remarkable-mcpThe skill lives at skills/remarkable-mcp/SKILL.md.
Develop
pnpm test
pnpm test:e2e
pnpm lint
pnpm fmt
pnpm build
pnpm exec tsx src/index.ts --fake
pnpm exec tsx src/index.ts --fake --http --http-port 8080pnpm test uses an in-memory tablet and a mock sync-v3 server. A live device is never required.
pnpm test:e2e starts rmfakecloud via docker-compose-test.yml, seeds it with test/fixtures/mcp-test (a tablet-verified dump of /mcp-test), pairs, reads that tree, then writes a notebook and reads it back from a second client. Needs Docker. Override the URL with RMFAKECLOUD_E2E_URL to point at an already-running server (skips seed).
Inspired by sammorrowdrums/remarkable-mcp and itsfabioroma/remarkable-cli.
MIT. See LICENSE.