Skip to main content
Glama
ParikshithV

paperplane-mcp

by ParikshithV
README.md
# paperplane-mcp

An MCP connector for [Paperplane](https://github.com/ParikshithV/Paperplane) — the inbox for the
events your machines send you.

It lets an AI tool page you on your phone when a long job finishes or needs a decision, read back
what fired, acknowledge or resolve an alert, and file a follow-up ticket. One long-lived credential,
no per-channel keys.

## Setup

### 1. Create a token

In the Paperplane app: **Settings → Developer tokens → Create token**. The secret is shown once —
copy it then. It looks like `pplt_` followed by 64 hex characters.

### 2. Point a client at it

Claude Code — `.mcp.json` in your project, or `~/.claude.json` for every project:

```json
{
  "mcpServers": {
    "paperplane": {
      "command": "npx",
      "args": ["-y", "paperplane-mcp"],
      "env": {
        "PAPERPLANE_TOKEN": "pplt_...",
        "PAPERPLANE_DEFAULT_CHANNEL": "Builds"
      }
    }
  }
}
```

Claude Desktop uses the same block inside `claude_desktop_config.json`.

| Variable | Required | Default |
| --- | --- | --- |
| `PAPERPLANE_TOKEN` | yes | — |
| `PAPERPLANE_API_BASE_URL` | no | `https://paperplane-backend.parikshithv01.workers.dev` |
| `PAPERPLANE_DEFAULT_CHANNEL` | no | none — tools then need an explicit `channel` |

The server checks the token at startup, so a revoked one fails fast with a message on stderr rather
than surfacing mid-conversation.

## Tools

| Tool | What it does |
| --- | --- |
| `paperplane_send_event` | Push a notification to the phone. Supports priority, nature, and action buttons. |
| `paperplane_ask` | Send a question to the phone and **block** until it's answered (or times out). |
| `paperplane_check_ask` | Non-blocking read of an ask's current status — recover an answer after a client-side timeout. |
| `paperplane_list_channels` | List the channels you belong to. |
| `paperplane_list_messages` | Read recent events and replies, newest first, with alert status. |
| `paperplane_set_alert_state` | Mark a message `open`, `acked`, or `resolved`. |
| `paperplane_reply` | Post a reply into a channel thread. |
| `paperplane_list_tickets` | Read the personal ticket board. |
| `paperplane_create_ticket` | File a follow-up, optionally linked back to an alert. |

### Making something an alert

An event becomes an alert — badged, counted under "Needs attention", requiring acknowledgement —
when `nature` is one of `error`, `failure`, `failed`, `critical`, `pending`, or `priority` is one of
`high`, `crit`, `critical`, `urgent`. Anything else is an ordinary message.

### Action buttons

`callback` attaches buttons to the notification:

```json
{
  "title": "Deploy failed",
  "message": "api-service · main @ abc123",
  "nature": "error",
  "callback": [{ "label": "Re-run pipeline", "webhook": "https://ci.example.com/hooks/rerun" }]
}
```

The phone fires the webhook when the button is pressed — the connector never calls it.

## Scopes

A token carries scopes; the connector uses `events:write`, `channels:read`, `messages:read`,
`messages:write`, `tickets:read`, and `tickets:write`. Tokens minted from the app carry all of them.
Mint a narrower one via the API if you want a send-only credential:

```bash
curl -X POST "$PAPERPLANE_API_BASE_URL/tokens" \
  -H "authorization: Bearer $SESSION_JWT" -H 'content-type: application/json' \
  -d '{"name":"CI notifier","scopes":["events:write"]}'
```

Channel administration, membership, retention, and token management are session-only and stay out
of reach of any token.

## Development

```bash
npm install
npm run build
npm run inspect   # builds, then opens the MCP Inspector
```

Point it at a local backend with `PAPERPLANE_API_BASE_URL=http://localhost:8787`.

Tool handlers in `src/tools/` take parsed arguments and return plain values — they know nothing
about MCP framing, so mounting them on a remote HTTP transport later is additive rather than a
rewrite.