ripper-mcp
# ripper-mcp
A lightweight, local [MCP](https://modelcontextprotocol.io) server for
**Ripper**. It exposes everything the in-app assistant can do — contacts,
listings, tasks, notes, timeline, and cross-CRM search — to any MCP client
(Claude Desktop, Claude Code, Cursor, …), authenticated as **your own Ripper
account**.
It's a thin, generic shell: it knows no individual tool. It reads the tool
catalog from the deployed Ripper backend at runtime and dispatches every call
through a single gateway. So:
- **Tool changes are live.** It polls the backend's manifest; when the catalog
changes it swaps its tool list and emits `notifications/tools/list_changed` —
no reinstall, no restart.
- **The client stays current on its own.** Launched via `npx ripper-mcp@latest`,
every start fetches the newest version. A rare backend `shellVersion` bump just
prints an update notice.
> The backend half of this (the `mcp.*` gateway) lives in the private Ripper app.
> This repo is only the local client.
## Quick start
```bash
# 1. Log in once (browser SSO — your normal Google sign-in). Caches a personal
# token at ~/.ripper/credentials.json (chmod 0600).
npx ripper-mcp@latest login
# 2. Check state
npx ripper-mcp@latest status
```
`login` starts a loopback listener and opens the `/mcp/connect` consent page.
Sign in, click **Approve**, and the authorization code is redirected straight
back to the CLI on `127.0.0.1` and exchanged for your token (PKCE — see below).
## Wire it into a client
**Claude Desktop** — `claude_desktop_config.json`:
```json
{
"mcpServers": {
"ripper": {
"command": "npx",
"args": ["-y", "ripper-mcp@latest"]
}
}
}
```
**Claude Code** — `.mcp.json` (or `claude mcp add`):
```json
{
"mcpServers": {
"ripper": { "command": "npx", "args": ["-y", "ripper-mcp@latest"] }
}
}
```
Prefer a global install? `npm i -g ripper-mcp` then use `"command": "ripper-mcp"`.
## How it fits together
```
Claude Desktop / Cursor ──stdio──▶ ripper-mcp (local) ──HTTPS──▶ Ripper backend (Convex)
│ ├─ mcp.getManifest (tool catalog + versions)
│ ├─ mcp.callTool (runs a tool as you)
└─ ~/.ripper/credentials.json ├─ mcp.authorize (browser consent → code)
└─ mcp.token (PKCE exchange → token)
```
Login uses **OAuth 2.0 Authorization Code + PKCE with a loopback redirect**
(RFC 8252 — the same native-app flow as `gh` / `gcloud`): `login` starts a
listener on `127.0.0.1`, opens the consent page, and after you approve, the
authorization code is redirected to your own loopback and exchanged (with the
PKCE verifier that never leaves this machine) for a 90-day access token. Only the
token's SHA-256 hash is stored on the backend; no password is ever handled here.
## Commands
```bash
ripper-mcp # start the stdio server (default)
ripper-mcp serve # explicit
ripper-mcp login # browser-SSO login, cache token
ripper-mcp logout # clear cached token
ripper-mcp status # show config + login state
```
## Configuration
Resolution order for every setting: **CLI flag → env var → `~/.ripper/credentials.json` → default**.
| Setting | Flag | Env var | Default |
| ----------- | -------------- | -------------------- | ------------------------------------------ |
| Backend URL | `--convex-url` | `RIPPER_CONVEX_URL` | `reliable-bullfrog-975` (production) |
| App URL | `--app-url` | `RIPPER_APP_URL` | `https://rippr.dev` (production) |
| Token | — | `RIPPER_MCP_TOKEN` | cached after `login` |
| Poll period | — | `RIPPER_MCP_POLL_MS` | `60000` (min `10000`) |
Update controls:
| Env var | Effect |
| ---------------------------- | ------------------------------------------------------------------ |
| `RIPPER_MCP_NO_SELFUPDATE=1` | Suppress the "newer version available" notice. |
| `RIPPER_MCP_AUTO_UPDATE=1` | Also run `npm i -g ripper-mcp@latest` in the background when behind. |
## Security notes
- **PKCE + loopback** means an authorization code is only ever delivered to
*your* `127.0.0.1`, and can't be exchanged for a token without the verifier
that never leaves this machine — so a phished or intercepted code is useless
to anyone else. The listener binds to loopback only and checks the `state`.
- **Tokens expire after 90 days** and are revocable. Only their SHA-256 hash is
stored server-side; the plaintext is returned to the CLI once and cached at
`~/.ripper/credentials.json` (chmod 0600).
- Rotate/revoke by running `ripper-mcp logout` then `login` again (or revoking
the token from the Ripper app). Treat the credentials file like any credential.
## Development
```bash
npm install
npm run check # syntax-check all sources
node bin.mjs status
```
Run against a non-default deployment with `--convex-url` / `RIPPER_CONVEX_URL`.
## License
MIT
TDQS
Scored across 22 tools
Most tools map clearly to a distinct resource and action — contacts, tasks, listings, emails, and timeline events are well separated. The main ambiguities are create_note vs. add_timeline_event's note type and search_crm overlapping with the targeted find_* helpers.
All tool names follow a consistent snake_case verb_noun pattern with predictable prefixes like create_, get_, list_, update_, delete_, find_, and send_. Minor variation like add_timeline_event instead of create_timeline_event does not undermine the overall pattern.
22 tools cover contacts, tasks, notes, timeline events, listings, and email, so the count is high but not unreasonable. It sits in the 16–25 range that feels heavy and would benefit from consolidation, especially around note/timeline and search/find overlaps.
Several tool descriptions reference find_contacts and list_email_accounts, but those tools are not exposed, creating dead ends for obtaining contactIds and email account info. There are also lifecycle gaps: no update/delete for notes, no update for timeline events, and no delete for contacts or listings.