PagerMCP
by Ofloo
README.md
# PagerMCP
PagerMCP is a Dockerized pager and relay service for asynchronous messages. Mailboxes are opaque UUIDs, so it works equally well for AI coding agents and for ordinary scripts and cron jobs: anything that can issue an HTTP `POST` can page, and anything with a mailbox (an agent plugin, a person, or another script) can be paged. The server relays opaque payloads and does not interpret them.
## Quickstart
Run server:
```bash
docker run -d --name pagermcp-server -p 8080:8080 -e MODE=server ofloo/pagermcp
```
Send a notification:
```bash
curl -X POST http://localhost:8080/notify \
-H "Authorization: Bearer <UUID>" \
-H "Content-Type: application/json" \
-d '{"project": "build", "status": "success", "message": "done"}'
```
Run client:
```bash
docker run --rm -i --user "$(id -u):$(id -g)" -v "$PWD":/workspace -e MODE=client -e PROJECT_DIR=/workspace -e SERVER_URL=https://pager.ofloo.io ofloo/pagermcp
```
## When to use PagerMCP
Use PagerMCP for any job that takes long enough that waiting for it wastes time or tokens. Instead of holding a session open while a command runs, start it in the background and let the notification arrive when it is done.
| Task type | Examples | Why page it |
|---|---|---|
| Compilation | `make`, `cargo build`, `npm run build` | Easily takes minutes. |
| Container builds | `docker build`, `docker buildx build` | Multi-arch builds are slow. |
| Test suites | `pytest`, `go test`, `npm test` | Full runs take minutes. |
| Deployments | `git push`, `rsync`, uploads | Network-bound and slow. |
| Remote jobs | CI runs, remote builds, batch processing | Run outside the agent session. |
AI agents should do this automatically: fire long jobs through the pager in the background, report the `JOB_ID`, and end the turn. Users should not have to remind an agent of this for every compile, build, or test. Skip the pager for short commands (`ls`, `git status`, quick greps); waiting for those costs less than paging.
## Sample script
`pager.sh` is only a sample. PagerMCP is a plain HTTP API: every notification can be sent directly with `curl` (or any HTTP client), no wrapper or special client required:
```bash
curl -X POST https://pager.ofloo.io/notify \
-H "Authorization: Bearer $(cat .pager_session)" \
-H "Content-Type: application/json" \
-d '{"JOB_ID": "build-latest", "status": "success", "message": "Build finished", "exit_code": 0}'
```
For convenience the server publishes a generic wrapper script. Download it on any machine, no repository needed:
```bash
curl -fsS -O https://pager.ofloo.io/sample/pager.sh
chmod +x pager.sh
./pager.sh --run "make" --message "Build finished" --tail 5
```
The script runs a command in the foreground, sends one notification with the status, message, exit code, and the last `--tail` lines of output, then exits with the command's exit code. Use `--dry-run` (or `--dry-run=5`) to simulate without sending, `--session <uuid|file>` to pass the mailbox address (defaults to `./.pager_session`), and `--notify` for an immediate page. `--project`, `--job-id`, and all other fields are free-form and passed through as-is. Run `./pager.sh --help` for the full list of modes, options, and examples.
## Pager Plugin
The OpenCode Pager Plugin source is in `plugins/pager.js`. It keeps one blocking request open to PagerMCP and sends each pagerbericht to the most recently active OpenCode session.
Install it globally for all projects:
```bash
mkdir -p ~/.config/opencode/plugins
cp plugins/pager.js ~/.config/opencode/plugins/pager.js
```
The client reads or creates the current session key in `.pager_session` and repairs its ownership. Run the client with `--user "$(id -u):$(id -g)"` so the mounted project file belongs to the host user. `PAGER_UID` and `PAGER_GID` can override the ownership when the container must run as root.
Restart OpenCode after installing or updating the plugin. A successful load is written to the OpenCode log as `Pager Plugin loaded for <project-directory>`. The plugin also adds automatic system guidance explaining how the AI should interpret pagerberichten. The default PagerMCP URL is `https://pager.ofloo.io`, so no environment variable is required. Set `PAGER_URL` only when using another server. You may also set `PAGER_SESSION_FILE` or `PAGER_SESSION_ID`. The plugin reads the UUID from each project's `.pager_session`, reconnects after errors, and logs failures through OpenCode without polling PagerMCP.
## Agent-to-agent messaging
Every participant has their own UUID, like a phone number: your mailbox address is your number, and anyone who has it can page you. Two agents (or a person and an agent) can therefore message each other directly once they exchange numbers. The sender `POST`s to the other's UUID, and the other's plugin delivers it as a normal notification. There is no registration, directory, or address book; the number is the whole address.
To start a conversation, exchange numbers once (any channel will do), then send and reply:
```bash
# Agent A sends to B's number
curl -X POST https://pager.ofloo.io/notify \
-H "Authorization: Bearer <B-UUID>" -H "Content-Type: application/json" \
-d '{"JOB_ID": "migratie-vraag", "status": "question", "message": "Welke dataset moet eerst?"}'
# Agent B replies to A's number
curl -X POST https://pager.ofloo.io/notify \
-H "Authorization: Bearer <A-UUID>" -H "Content-Type: application/json" \
-d '{"JOB_ID": "migratie-vraag", "status": "answer", "message": "Eerst de home-datasets."}'
```
Use a shared `JOB_ID` (a thread id) so both sides can correlate the exchange. `status` is free-form, so `question`/`answer`/`ack` work as well as `success`/`failed`. Treat your UUID like a phone number you hand out deliberately: anyone who knows it can page you, so share it only with the intended peer, and use separate mailboxes if you want a conversation isolated. Messages queue while the peer is offline and arrive when it reconnects, within the retention window.
## Patterns
PagerMCP is a transport: anything that can issue an HTTP `POST` can raise a pagerbericht, and any agent with a mailbox can receive one. A few patterns that fall out of this:
- **Event hooks from scripts and CI.** Any script, `cron` job, backup, health check, or CI step can page an agent by `POST`ing to its UUID. No SDK is needed, only `curl`. This is how a long build or a nightly job reports without anyone watching a terminal.
- **Scheduled reminders.** A `cron` entry that pages a mailbox is a reminder the agent will see when it next runs; combine with `JOB_ID` to keep recurring events grouped.
- **Agent-to-agent conversation.** See above: two agents exchange UUIDs and converse with the same `/notify` call.
- **Optional end-to-end encryption.** The server relays an opaque JSON payload; it never needs to read the message. For secrecy, encrypt the `message` (or the whole field set) with GPG before sending and decrypt on receipt:
```bash
# sender (public key of the peer)
BODY=$(printf '%s' "geheim" | gpg --armor --encrypt --recipient peer@example.com)
curl -X POST https://pager.ofloo.io/notify \
-H "Authorization: Bearer <UUID>" -H "Content-Type: application/json" \
-d "$(python3 -c 'import json,sys; print(json.dumps({"JOB_ID":"thread","status":"encrypted","message":sys.argv[1]}))' "$BODY")"
# receiver
printf '%s' '<message>' | gpg --decrypt
```
The mailbox UUID stays visible to the relay (it is the routing address), so treat the UUID as the addressing secret and GPG as the content secret.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues