Skip to main content
Glama
lukaisailovic

remarkable-mcp

README.md
# remarkable-mcp

Give an AI assistant **direct control of your [reMarkable](https://remarkable.com) tablet** — list notes, upload PDFs, export handwriting, write on pages — over SSH or [rmfakecloud](https://github.com/ddvk/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](https://modelcontextprotocol.io) server |
| SSH (USB `10.11.99.1` / Wi‑Fi) **or** rmfakecloud | stdio **or** Streamable HTTP (Docker)            |
| Claude / Cursor / VS Code / any MCP client        | Same `rme.*` on either storage backend           |

---

## 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).

1. Enable **Settings → General → Developer mode** on the tablet (SSH).
2. SSH over USB (`root@10.11.99.1`) or Wi‑Fi — **or** pair with your rmfakecloud.
3. Run this server. Point your MCP client at it.

---

## Install

Node 22+. No clone required.

### Claude Desktop, Cursor, VS Code, …

```json
{
  "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:

```json
{
  "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](https://modelcontextprotocol.io) on `/mcp`:

```bash
npx -y @lukaisailovic/remarkable-mcp --http
# → http://127.0.0.1:8080/mcp    health: /health
```

Or pull the image from GHCR:

```bash
docker run --rm -p 8080:8080 \
  -e REMARKABLE_HOST=10.11.99.1 \
  -e REMARKABLE_PASSWORD=your-tablet-password \
  ghcr.io/lukaisailovic/remarkable-mcp:latest
```

From 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`**.

```js
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.

| `rme.*`                                                                                | What it does                                                                              |
| -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `list({ includeTrash?, folder? })`                                                     | Library listing (trash hidden by default)                                                 |
| `browse({ path? })`                                                                    | One folder, or a single notebook                                                          |
| `search({ query, tag? })`                                                              | Name / path search, optional tag                                                          |
| `info({ notebook })`                                                                   | Id, path, type, tags, page count, `pages[].title`                                         |
| `read({ notebook, page? })`                                                            | Native paragraphs (and checkbox state), or all pages if `page` is omitted. PDF/EPUB text. |
| `download({ notebook })`                                                               | Raw PDF/EPUB as base64                                                                    |
| `exportPage({ notebook, page?, format? })`                                             | Ink on a page → `png` or `svg` (typed text is not drawn)                                  |
| `upload({ name, dataBase64, parent?, fileType? })`                                     | Put a PDF or EPUB on the tablet                                                           |
| `mkdir` / `move` / `rename` / `remove`                                                 | Folders and trash                                                                         |
| `createNotebook` / `addPage` / `removePage`                                            | Notebooks and pages                                                                       |
| `writeInk({ notebook, strokes, page? })`                                               | Pen / highlighter strokes                                                                 |
| `writeMermaid({ notebook, mermaid, page? })`                                           | Mermaid → ink (flowchart, sequence, state, class, ER, xychart)                            |
| `writeText({ notebook, text?, style?, checked?, blocks?, page?, newPage?, replace? })` | Native Type Folio: `title` `heading` `body` `bullet` `checkbox`. Stacks unless `replace`. |
| `tag({ notebook, tag, remove?, page? })` / `tags()`                                    | Notebook or page tags                                                                     |
| `refresh()`                                                                            | Apply now (SSH: restart xochitl; cloud: pull if remote moved, then commit)                |

---

## Configuration

Flags and env vars are interchangeable (`--host` ≡ `REMARKABLE_HOST`).

| Tablet                               | Default                            |                        |
| ------------------------------------ | ---------------------------------- | ---------------------- |
| `REMARKABLE_HOST` / `--host`         | `10.11.99.1`                       | SSH host (USB default) |
| `REMARKABLE_USER` / `--user`         | `root`                             | SSH user               |
| `REMARKABLE_PORT` / `--port`         | `22`                               | SSH port               |
| `REMARKABLE_PASSWORD` / `--password` |                                    | Password               |
| `REMARKABLE_KEY` / `--key`           | `~/.ssh/id_ed25519`, then `id_rsa` | Key path or PEM        |

| rmfakecloud                     | Default |                                  |
| ------------------------------- | ------- | -------------------------------- |
| `RMFAKECLOUD_URL` / `--cloud`   |         | Base URL of your rmfakecloud     |
| `RMFAKECLOUD_TOKEN` / `--token` |         | Device JWT from a prior `--pair` |
| `RMFAKECLOUD_PAIR` / `--pair`   |         | One-shot 8-letter pairing code   |

| Server                                   | Default     |                                           |
| ---------------------------------------- | ----------- | ----------------------------------------- |
| `--http` / `MCP_HTTP=1`                  | off         | Streamable HTTP instead of stdio          |
| `MCP_HTTP_HOST` / `--http-host`          | `127.0.0.1` | Bind address (`0.0.0.0` in Docker)        |
| `MCP_HTTP_PORT` / `PORT` / `--http-port` | `8080`      | HTTP port                                 |
| `--fake` / `REMARKABLE_FAKE=1`           | off         | In-memory tablet (tests, no device)       |
| `--fake-dir` / `REMARKABLE_FAKE_DIR`     |             | Local directory treated as a xochitl tree |

---

## Agent skill

Installable with [skills.sh](https://skills.sh):

```bash
npx skills add lukaisailovic/remarkable-mcp
```

The skill lives at [`skills/remarkable-mcp/SKILL.md`](skills/remarkable-mcp/SKILL.md).

---

## Develop

```bash
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 8080
```

`pnpm test` uses an in-memory tablet and a mock sync-v3 server. A live device is never required.

`pnpm test:e2e` starts [rmfakecloud](https://github.com/ddvk/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](https://github.com/sammorrowdrums/remarkable-mcp) and [itsfabioroma/remarkable-cli](https://github.com/itsfabioroma/remarkable-cli).

MIT. See [LICENSE](LICENSE).

TDQS

A3.9/5.0

Scored across 1 tool

Disambiguation1/5

All functionality is bundled into a single tool with a polymorphic 'rme' object offering many methods, making it impossible for an agent to select a specific operation without inspecting the runtime API. The tool description describes numerous distinct actions, but as a single tool it obscures boundaries and forces arbitrary disambiguation at runtime.

Naming Consistency1/5

The tool itself is named 'remarkable_execute' while its methods use a mix of verb forms (list, read, download, exportPage, writeInk, etc.) and inconsistent naming styles (e.g., exportPage camelCase vs. others snake-like? Actually all methods are camelCase but the tool name is snake_case, and verb-noun pattern is inconsistent: 'list', 'browse', 'info', 'read' are verbs without nouns, while 'createNotebook', 'addPage', 'writeMermaid' have nouns. The mixture of generic verbs and specific compound names lacks a coherent pattern.

Tool Count2/5

With only one entry point for a rich API covering file operations, reading, writing ink, exports, and tags, the tool count is far too low for the scope. A single mega-tool burdens the agent with parsing and routing, and the server would benefit from splitting into separate tools per operation.

Completeness4/5

The bundled operations cover a broad lifecycle: listing, browsing, search, info, read, download, export, upload, mkdir, move, rename, remove, notebook creation, page manipulation, writing ink/text/Mermaid, tagging, and refresh. Missing operations like bulk exports or custom storage sync are minor; the surface is quite comprehensive for typical reMarkable workflows.

Maintenance

ActivitySlowing
ResponsivenessNo issues