rubien-mcp-server
v0.1.0
Published
MCP server wrapping rubien-cli so Claude Code and claude.ai can manage the Rubien reference library.
Downloads
161
Maintainers
Readme
rubien-mcp-server
A Model Context Protocol server that wraps rubien-cli, making the Rubien reference library available to Claude Code, Claude Desktop, and claude.ai chat as a callable tool catalog.
The server is a thin Node/TypeScript process. It spawns rubien-cli under the hood and speaks MCP over one of two transports:
- stdio — used by Claude Code and Claude Desktop. The client launches the server as a child process and talks to it over the pipe. No network, no auth.
- Streamable HTTP with bearer-token auth — used by claude.ai (web) via a custom MCP connector. Requires a tunnel (e.g. Cloudflare Tunnel) because your library lives on your Mac.
Pick the section below that matches your client. Most users want one of the two stdio paths.
Install (Claude Code)
claude mcp add rubien -- npx -y rubien-mcp-serverThis needs rubien-cli on the host:
- Mac: install Rubien.app (DMG) — the entitled
rubien-cliships inside it; nothing else to do. - Linux:
sudo apt install libsqlite3-0 libcurl4 libxml2 libpoppler-glib8 libcairo2 libgdk-pixbuf-2.0-0 libglib2.0-0 ca-certificates, then downloadrubien-cli-*-linux-x86_64.tar.gzfrom https://github.com/devzhk/Rubien-releases/releases and extract it to a directory, keepingrubien-cliand the*.resourcesfolders together (the CLI loads citation styles from beside the binary). Point the server at it:export RUBIEN_CLI=/path/to/extracted/rubien-cli. Do not copy the bare binary ontoPATHalone — it would lose its resource bundles.
The server checks the CLI's build at startup; if it's too old it prints an update
instruction and exits (rather than failing mid-call). On Mac, update Rubien.app
(Sparkle). On Linux, run rubien-cli self-update (signature-verified, replaces in
place) — or re-download the tarball.
Prerequisites
- Node.js ≥ 20
rubien-clireachable on the host. The server looks for it in this order:$RUBIEN_CLIenv var (explicit path)/Applications/Rubien.app/Contents/Helpers/rubien-cli(installed app bundle, Mac)~/Applications/Rubien.app/Contents/Helpers/rubien-cli(Mac)./build/Rubien.app/Contents/Helpers/rubien-cli(dev build output, Mac)rubien-clionPATH(Linux installs land here — see the Install section above)
On Mac, prefer the bundled helper. It's signed with the App Group entitlement and reads the same library.sqlite the Rubien app uses. A bare rubien-cli on PATH is typically an SPM dev build without the entitlement, which hits a different database — see "Database location" in Docs/CLI-Reference.md.
On Linux, the server runs against a rubien-cli installed per the Install section above (prebuilt Linux tarball, or built from source). Everything works except rubien_sync_status, which always errors (sync subcommand isn't registered on Linux builds — CloudKit doesn't exist there).
To pin the spawned CLI to a specific library directory regardless of which binary resolves, set RUBIEN_LIBRARY_ROOT in the MCP server's env block (Claude Code: claude mcp add rubien --env RUBIEN_LIBRARY_ROOT=...; Claude Desktop: under mcpServers.rubien.env in claude_desktop_config.json). The path is used verbatim — point it at the directory that contains library.sqlite:
- Mac sandboxed:
~/Library/Group Containers/9TXK4V3SS8.com.rubien.shared/Rubien - Mac unsandboxed:
~/Library/Application Support/Rubien - Linux:
~/.local/share/rubien(or wherever$XDG_DATA_HOME/rubienresolved to)
Claude Code (stdio)
The npx command above is the recommended path. To run a local checkout instead (see Development for the build):
claude mcp add rubien node $(pwd)/dist/index.jsTry it:
List my 5 most recently added references. Cite reference 42 in Nature style.
Claude Code's permission UI prompts for the destructive tools (rubien_delete, rubien_update, rubien_import, and the property write tools) on first use.
Claude Desktop (stdio)
Claude Desktop spawns MCP servers itself — no tunnel, no bearer token. Add Rubien to claude_desktop_config.json via either route:
- Edit the file directly at
~/Library/Application Support/Claude/claude_desktop_config.json. - Settings → Developer → Edit Config in Claude Desktop, which opens the same file in your editor.
Add Rubien under mcpServers:
{
"mcpServers": {
"rubien": {
"command": "node",
"args": ["/absolute/path/to/Rubien/mcp-server/dist/index.js"]
}
}
}Restart Claude Desktop and Rubien appears in the tool picker. To pin the spawned CLI to a specific library (see Prerequisites above for paths), add an env block:
"rubien": {
"command": "node",
"args": ["/absolute/path/to/Rubien/mcp-server/dist/index.js"],
"env": {
"RUBIEN_LIBRARY_ROOT": "/Users/you/Library/Group Containers/9TXK4V3SS8.com.rubien.shared/Rubien"
}
}Future work — Desktop Extension (
.dxt). Claude Desktop also supports one-click MCP installs via.dxtpackages (a zip containing amanifest.json+ bundled server). Rubien doesn't ship one yet; packaging it would replace the config-file edit above with a double-click install from a GitHub Release asset.
claude.ai web (Streamable HTTP + Cloudflare Tunnel)
claude.ai (the web app at https://claude.ai) can't spawn a local process, so it talks to the server over HTTPS via a custom MCP connector. Since your library lives on your Mac, you'll need a tunnel to expose the local server, and bearer-token auth to keep strangers out.
# 1. Start the server with a bearer token
RUBIEN_MCP_BEARER=$(openssl rand -hex 32) \
node dist/index.js --http --port 4000 --bearer-token "$RUBIEN_MCP_BEARER"
# 2. Open a tunnel
cloudflared tunnel --url http://localhost:4000
# → copy the https://*.trycloudflare.com URL
# 3. In claude.ai: Settings → Connectors → Add custom MCP connector
# URL: https://<your-tunnel>.trycloudflare.com
# Token: $RUBIEN_MCP_BEARER (from step 1)Save the bearer token — you'll need it to reconnect after restarts.
Security model
This server is designed for single-user personal use only.
- The bearer token is a long-lived static secret. There's no rotation, revocation, rate limiting, or replay protection.
- The server will happily accept any request with the correct token. Don't paste the token into anything you don't trust, and treat a leaked token like an SSH key — regenerate immediately.
- Cloudflare Tunnel provides TLS termination; the token travels over HTTPS. If you use a plaintext tunnel (e.g. raw ngrok HTTP) the token can be sniffed.
- A leaked URL without the token is safe — every request gets a 401 — but rotate both if in doubt.
If this setup ever becomes multi-user, the auth layer needs a real story: OAuth, short-lived tokens, and per-client revocation.
Tool catalog
Roughly 36 tools covering every rubien-cli subcommand mode. Names are rubien_<subject>_<action> so Claude can pick the right tool from a single-word hint:
| Surface | Tools |
|---|---|
| References | rubien_search, rubien_list, rubien_get, rubien_add, rubien_update, rubien_delete |
| Citations | rubien_cite, rubien_styles_list |
| Import/Export | rubien_import, rubien_export |
| PDFs | rubien_pdf_info, rubien_pdf_text, rubien_pdf_page_image, rubien_pdf_download, rubien_annotations_list |
| Web clips | rubien_web_get, rubien_web_annotations |
| Properties (incl. Tags) | rubien_properties_list, rubien_properties_create, rubien_properties_delete, rubien_properties_rename, rubien_properties_show, rubien_properties_hide, rubien_properties_add_option, rubien_properties_rename_option, rubien_properties_delete_option, rubien_properties_set, rubien_properties_add_values, rubien_properties_remove_values, rubien_properties_clear |
| Saved views | rubien_views_list, rubien_views_create, rubien_views_delete, rubien_views_rename, rubien_views_query |
| Sync | rubien_sync_status (Mac-only — errors on Linux hosts) |
The PDF tools cover inspection and acquisition — rubien_pdf_info returns page count plus a flattened outline, rubien_pdf_text extracts text by page range or section title, rubien_pdf_page_image renders a page to PNG for figure inspection, and rubien_pdf_download fetches an open-access PDF for an existing reference and attaches it to the library. rubien_annotations_list returns the user's highlights/underlines/anchored-notes on the attached PDF.
The web-clip tools are the counterpart for clipped web pages — rubien_web_get returns the extracted body (markdown or HTML, paginated by character offset) along with siteName and annotationCount, and rubien_web_annotations returns highlights with a W3C TextQuoteSelector triple (prefixText / anchorText / suffixText) so the highlight can be located inside the body. Library-only — neither tool fetches from the network.
Destructive tools are tagged with destructiveHint: true so Claude Code's permission UI flags them. rubien_delete always passes --force — the confirmation happens in the MCP client permission UI, not in the CLI's TTY prompt. See the comment in src/tools/references.ts for rationale.
Contract pinning
Every tool's argument shape is defined in zod in the tool file; the expected response shape is in src/schemas.ts, mirroring the Swift DTOs in Sources/RubienCLI/RubienCLI.swift (search for *DTO). Crucial convention (see tests in test/schemas.test.ts):
.optional()in zod for SwiftOptionalfields — Swift'sJSONEncoderomits nil optionals from output..nullable()only forAlwaysEncodedOptional<T>wrappers (currently justDatabaseViewDTO.groupBy).
If a Swift DTO changes, update both the Swift side and src/schemas.ts in the same commit, per the CLAUDE.md rule about keeping the CLI and data layer in lockstep.
Development
Build from a local checkout (instead of npx):
cd mcp-server
npm install
npm run build # → dist/
npm test # vitest unit testsWatch mode:
npm run dev # tsc --watch
npm run test:watchMCP Inspector is the fastest way to poke at tool schemas by hand:
npx @modelcontextprotocol/inspector node dist/index.js