Skip to main content
Glama
README.md
# ringback

[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![Platform](https://img.shields.io/badge/platform-macOS_|_Linux_|_Windows-lightgrey.svg)](#prerequisites)
[![MCP](https://img.shields.io/badge/MCP-server-8A2BE2.svg)](https://modelcontextprotocol.io)
[![Install with Claude Code](https://img.shields.io/badge/Install_with-Claude_Code-D97757.svg)](#let-claude-code-install-it-for-you)

> **Your AI agent can call your phone β€” and actually talk to you.**

**ringback** gives an LLM (Claude, or any MCP client) tools to **reach you on your phone** β€” from a one-way "fierce" alert all the way to a **live, interruptible voice conversation** β€” using only free, self-hosted pieces. No paid telephony. No extra API key for the conversation: the model already driving the MCP *is* the voice on the line.

**Highlights**
- πŸ“ž **Two-way voice calls** β€” the agent rings your phone, you talk, it transcribes you and replies in speech. **Barge-in**: talk over it and it stops.
- πŸ”” **Tiered alerts** β€” a loud push (ntfy / Pushover) or a real SIP ring + chat message, fired only when *the LLM* judges it urgent.
- πŸ†“ **Free & self-hosted** β€” pjsua2 + whisper.cpp + Piper neural TTS + a free Linphone SIP account. No Twilio, no per-minute fees.
- 🧠 **No conversation API key** β€” the calling model is the brain; these tools are just its ears and mouth.

It ships two MCP servers, `ringback-alert` and `ringback-voice`:

> **Platform:** macOS, **Linux**, and **Windows** (via WSL2 or Docker). TTS is [Piper](https://github.com/rhasspy/piper) by default (same voice everywhere), falling back to the OS-native voice (`say` on macOS). The engine is **headless** β€” it never opens a local mic/speaker (all audio is WAV ↔ SIP/RTP), so no sound card is required. Setup guides: [macOS](docs/SETUP_MACOS.md) Β· [Linux](docs/SETUP_LINUX.md) Β· [Windows](docs/SETUP_WINDOWS.md) Β· [Docker](docs/SETUP_DOCKER.md).

| Server | Tools | What it does |
|---|---|---|
| **ringback-alert** | `alert_me`, `alert_test`, `alert_status` | Fire-and-forget notification: a loud push (ntfy / Pushover) and/or a SIP ring + chat message. |
| **ringback-voice** | `call_start`, `converse`, `get_conversation`, `call_end`, … | A real **two-way phone conversation**. Rings your phone; you talk, it transcribes you, the LLM replies in speech. Supports **barge-in** (talk over it and it stops). |

The criteria for *when* to contact you live in the tool descriptions β€” the calling LLM decides. These servers are just the mechanism.

### What a call looks like

```
agent β†’ call_start("Your nightly deploy failed β€” want me to walk you through it?")
   πŸ“ž your phone rings; you pick up and hear the line
you   β†’ "yeah, which step broke?"
agent β†’ "The database migration. I can roll it back and retry β€” want that?"
you   β†’ "yes, do it"            ← you can also just talk over the agent to interrupt
agent β†’ call_end()
```

The LLM calls `call_start` once, then `converse(...)` for each turn. Plain alerts are even simpler: one `alert_me(...)` call.

---

## Install as a Claude Code plugin (Docker β€” one command)

If you have **Docker** and a free [Linphone](https://subscribe.linphone.org) account, this is the fastest path β€” no local build:

```text
/plugin marketplace add mohitbadwal/ringback
/plugin install ringback@ringback
```

Claude Code prompts for your SIP details (and optional alert backends), stores secrets in your OS keychain, and registers both `ringback-voice` and `ringback-alert` plus the watchdog skill. The engine runs in the prebuilt image (pulled on first call). See [plugin/README.md](plugin/README.md).

> Prefer no Docker, or want the native build? Use the paste-prompt below or the manual steps.

## Let Claude Code install it for you

πŸ€– Easiest path: **copy the prompt below and paste it into [Claude Code](https://claude.com/claude-code)** β€” it'll clone, build, configure, and register everything, asking you only for what it needs (a free SIP account + your phone to answer a test call).

```text
Set up the ringback MCP server for me β€” it lets you (the agent) call my phone when you
need a decision while I'm away. Repo: https://github.com/mohitbadwal/ringback
(runs on macOS, Linux, or Windows via WSL2/Docker).

Please:
1. Detect my OS and pick the path:
   - macOS                 β†’ ./setup.sh (Homebrew); read docs/SETUP_MACOS.md
   - Linux or Windows-WSL2 β†’ ./setup-linux.sh; read docs/SETUP_LINUX.md
   - Windows without WSL2   β†’ use Docker; read docs/SETUP_DOCKER.md
2. Clone https://github.com/mohitbadwal/ringback, cd in, and read the README + the doc above.
3. Run the setup for my OS (compiles pjsua2 from source β€” ~20–30 min β€” installs whisper +
   Piper TTS, downloads models, and creates voice.env).
4. I need a free Linphone SIP account (the phone line): walk me through signing up at
   https://subscribe.linphone.org and installing the Linphone app on my phone, then put my
   SIP id/username/password into voice.env (and set VOICE_DISPLAY_NAME to a caller-ID name).
5. Register it (per OS):
   - macOS:      claude mcp add ringback-voice --scope user -- "$PWD/run_voice_mcp.sh"
   - Linux/WSL2: claude mcp add ringback-voice --scope user -- python3 "$PWD/run_voice_mcp.py"
   - Docker:     see docs/SETUP_DOCKER.md (build image, convert creds to voice.docker.env, then register a `docker run -i` command)
6. macOS only: if a test call fails with error -32000 or a segfault, run ./fix_macos_twolevel.sh.
7. Tell me to start a fresh session, then call me to confirm two-way voice works.

Ask me whenever you need input (SIP credentials, my phone to answer the test call, etc.).
```

Prefer to do it by hand? Quick start and the full walkthrough are below.

---

## Quick start

```bash
git clone https://github.com/mohitbadwal/ringback && cd ringback
./setup.sh        # installs EVERYTHING (toolchain, pjsua2, whisper model, deps) + creates voice.env
# edit voice.env β†’ add your 3 SIP vars (free account: https://subscribe.linphone.org), then:
claude mcp add ringback-voice --scope user -- "$PWD/run_voice_mcp.sh"
```

Full walkthrough + env-var reference: **[Set up ringback-voice](#set-up-ringback-voice--4-steps)** below.

---

## Honest caveats (read first)

- **Cross-platform.** macOS (native), Linux (native), Windows (via WSL2 or Docker). The engine is headless β€” no sound card needed. Native Windows (MSVC) is intentionally not supported; WSL2/Docker is the Windows path.
- **Not ChatGPT-realtime.** The voice loop is record β†’ whisper STT β†’ LLM β†’ Piper/say TTS, so expect **~1–2 s per turn**. It's a reliable walkie-talkie with barge-in, not a streaming realtime voice.
- **The voice feature depends on GPL software** (pjproject/pjsua2). This repo is Apache-2.0, but redistributing a bundle that links pjsua2 carries GPL obligations β€” see [`NOTICE`](NOTICE). The ringback-alert server is unaffected.
- **Your machine must be awake and online**, and for a voice call a Claude session must be live (it's the brain) for the duration.
- **Barge-in assumes low acoustic echo** (handset or headset). On speakerphone, the TTS can echo into the mic and false-trigger "interruption." There's no echo cancellation in this path.
- **iOS push reality:** a self-hosted/free push can't truly pierce Focus/Silent on iPhone except via Pushover's Critical Alerts (paid) β€” see ringback-alert notes below.

---

## Architecture

```
  LLM (Claude)  ──MCP tools──▢  ringback-voice server (Python)
                                   β”‚  call_start / converse / listen / speak
                                   β–Ό
                 pjsua2 (SIP+SRTP, built from source)  ──▢  Linphone SIP server
                   β”‚  Piper/say β†’ ffmpeg β†’ WAV  (speak)        β”‚ APNs VoIP push
                   β”‚  record β†’ whisper.cpp (listen)            β–Ό
                   └───────────────────────────────────▢  your iPhone rings
```

ringback-alert is simpler: it shells out to `ntfy`/Pushover HTTP and/or `baresip` for a SIP ring + chat message.

---

## Prerequisites

- One of:
  - **macOS** (Apple Silicon or Intel) with [Homebrew](https://brew.sh) β€” run `./setup.sh`
  - **Linux** (Debian/Ubuntu/Fedora) β€” run `./setup-linux.sh` (see [docs/SETUP_LINUX.md](docs/SETUP_LINUX.md))
  - **Windows** β€” WSL2 (run `./setup-linux.sh` inside it) or Docker Desktop (see [docs/SETUP_WINDOWS.md](docs/SETUP_WINDOWS.md))
  - **Any OS with Docker** β€” `docker build -t ringback .` (see [docs/SETUP_DOCKER.md](docs/SETUP_DOCKER.md))
- A free **Linphone** SIP account (`sip.linphone.org`) and the **Linphone iOS/Android app** (for the ring/voice features)
- Python 3.10+ (the pjsua2 bindings are built against whichever `python3` you point at)

---

## Set up ringback-voice β€” 4 steps

**1. Clone + install everything:**
```bash
git clone https://github.com/mohitbadwal/ringback && cd ringback
./setup.sh
```
`setup.sh` installs the toolchain, **compiles pjsua2 from source** (~20–30 min β€” no Homebrew formula exists for the bindings), relinks the pjproject dylibs to a two-level OpenSSL namespace (the macOS fix that makes SIP/SRTP work), downloads the whisper model, installs Piper + a voice, installs deps, **and creates `voice.env` for you**. Safe to re-run. (Override `PYTHON_BIN` / `PJPROJECT_DIR` / `WHISPER_MODEL_NAME` if your layout differs.)

> **On Linux?** Use [`./setup-linux.sh`](docs/SETUP_LINUX.md) instead β€” it does the same build with apt/dnf and needs **no** OpenSSL relink. **On Windows?** Use WSL2 ([docs/SETUP_WINDOWS.md](docs/SETUP_WINDOWS.md)) or [Docker](docs/SETUP_DOCKER.md). Register the server with the cross-platform launcher `python3 run_voice_mcp.py` (the `.sh` is macOS-only).

> **Hit a snag on macOS?** [`docs/SETUP_MACOS.md`](docs/SETUP_MACOS.md) is a field-tested root-cause + troubleshooting guide (build target, the OpenSSL flat-namespace fix, whisper model, symptom→fix table).

**2. Get a free SIP account** (this is the phone that rings):
- Sign up at **<https://subscribe.linphone.org>** (or tap *Create account* in the Linphone app). You get a **username** and **password**; your address is `sip:<username>@sip.linphone.org`.
- Install the **Linphone app on your iPhone**, sign in, and confirm it shows **Connected**.

**3. Fill in `voice.env`** (already created by setup.sh β€” just edit it). Only **three** vars are required:
```bash
export VOICE_SIP_ID="sip:yourname@sip.linphone.org"
export VOICE_SIP_USER="yourname"
export VOICE_SIP_PASS="your-password"
```

Full variable reference:

| Variable | Required | Default | What it is |
|---|:---:|---|---|
| `VOICE_SIP_ID` | βœ… | β€” | Your SIP address, e.g. `sip:you@sip.linphone.org` |
| `VOICE_SIP_USER` | βœ… | β€” | SIP username (the part before `@`) |
| `VOICE_SIP_PASS` | βœ… | β€” | Your SIP password |
| `VOICE_SIP_CALLEE` | β€” | = `VOICE_SIP_ID` | Address to call (normally yourself) |
| `VOICE_SIP_PROXY` | β€” | `sip:sip.linphone.org;transport=tls` | SIP registrar/proxy |
| `WHISPER_MODEL` | β€” | `~/.whisper-models/ggml-small.en.bin` | STT model: `base.en` (fast) Β· `small.en` (default) Β· `medium.en` (accurate) |
| `VOICE_TTS` | β€” | `auto` | TTS engine: `auto` (Piper if installed, else OS voice) Β· `piper` Β· `say` Β· `espeak` Β· `sapi` |
| `VOICE_PIPER_MODEL` | β€” | `~/.piper-voices/en_US-lessac-medium.onnx` | Piper voice (`.onnx`; needs the matching `.onnx.json` beside it) |
| `VOICE_TTS_CMD` | β€” | β€” | Custom TTS command template with `{text}`/`{out}` (overrides `VOICE_TTS`) |
| `VOICE_NULL_AUDIO` | β€” | `auto` | Force pjsua2 null audio device (`auto` = on except macOS; `1`/`0` to force) |
| `RINGBACK_PRESENCE` | β€” | β€” | Override watchdog idle/presence: `present` or `absent` (for Wayland/headless) |
| `PJPROJECT_DIR` | β€” | `~/build/pjproject-2.17` | pjsua2 build dir (auto-detected) |
| `PYTHON_BIN` | β€” | `$(command -v python3)` | Python that has pjsua2 (auto-detected) |
| `OPENSSL_PREFIX` | β€” | `$(brew --prefix openssl@3)` | OpenSSL libs (macOS; auto-detected) |

**4. Register + test:**
```bash
# macOS:
claude mcp add ringback-voice --scope user -- "$PWD/run_voice_mcp.sh"
# Linux / Windows-WSL2 (cross-platform launcher):
claude mcp add ringback-voice --scope user -- python3 "$PWD/run_voice_mcp.py"
# Any OS via Docker (convert creds to voice.docker.env first β€” see docs/SETUP_DOCKER.md):
claude mcp add ringback-voice --scope user -- docker run -i --rm --network host --env-file voice.docker.env ringback
```
Then in a **fresh** Claude session say: *"Use ringback-voice to call me and say hello."* Your phone should ring.

> **Claude Desktop** instead of Code? Add this to `~/Library/Application Support/Claude/claude_desktop_config.json` (absolute path required; restart the app):
> ```json
> { "mcpServers": { "ringback-voice": { "command": "/absolute/path/to/ringback/run_voice_mcp.sh" } } }
> ```

---

## Set up ringback-alert (optional)

ringback-alert reads its config from the **MCP client's `env` block** (no file to source). Register it with the channels you want:

```bash
# Claude Code
claude mcp add ringback-alert --scope user \
  --env ALERT_CHANNEL=ntfy \
  --env NTFY_URL=https://ntfy.sh/your-long-random-topic \
  -- /opt/homebrew/bin/uv --directory "$PWD" run server.py
```
See `alert.env.example` for all variables (ntfy / Pushover / SIP ring). Use a **long random** ntfy topic β€” anyone who knows it can read/publish.

---

## Using ringback-voice (the conversation)

The LLM drives a simple loop:

```
reply = call_start("Hi, it's your assistant β€” your deploy failed. Want details?")
# rings the phone, speaks the line, returns the user's first words
reply = converse("It failed on the database migration step. Want me to retry it?")
# speaks AND listens in one interruptible turn
... repeat converse() each turn ...
call_end()   # when the user says "bye" / hangs up
```

- **`converse(text)`** speaks while listening. If you **talk over it**, it stops immediately and tells the LLM how far it got and what you said (barge-in).
- **`get_conversation()`** returns the full transcript so far β€” both sides, plus where it got interrupted.
- TTS reads text literally, so the tool descriptions instruct the model to speak **plain-language summaries**, never raw logs/codes β€” those go via `alert_me` as text instead.

Whisper model accuracy/speed trade-off (set `WHISPER_MODEL`): `base.en` (fast/rough) β†’ `small.en` (balanced, default) β†’ `medium.en` (most accurate/slow).

---

## Using ringback-alert (notifications)

`alert_me(message, severity, title)` with `severity` = `info` | `warn` | `critical`. Channels via `ALERT_CHANNEL` (comma list of `ntfy`, `pushover`, `call`):

- **ntfy** β€” free push; loud but does not pierce iOS Focus/Silent unless whitelisted per-Focus.
- **Pushover** β€” $5 one-time; true iOS **Critical Alerts** (pierces Focus/Silent, repeats until acknowledged) at `critical`.
- **call** β€” free SIP ring + Linphone chat message via baresip; rings full-screen, only at `critical` by default.

A built-in rate-limit guard (default 5/60s, per-process) stops a misfiring caller from spamming you.

---

## Bundled skill: watchdog

[`skills/watchdog/`](skills/watchdog/SKILL.md) is a ready-to-use Claude skill built on these servers. It watches a condition you give it (a CI run, a deploy, a pod, a file) and **escalates only when you're actually away** from the laptop β€” chat status β†’ chat warning β†’ `ringback-alert` push β†’ `ringback-voice` call β€” judged by input-idle time (macOS, Linux, or Windows; see `platform_compat.hid_idle_seconds`). It never interrupts you while you're typing, and de-escalates the moment you touch the keyboard.

```bash
cp -r skills/watchdog ~/.claude/skills/watchdog   # install for Claude Code
```
Then: `/watchdog <what to watch> | priority=<low|medium|critical>` β€” `low` = chat only, `medium` = may send a phone alert, `critical` = may place a call. Full design in [`skills/watchdog/SKILL.md`](skills/watchdog/SKILL.md).

---

## Security

- **SIP credentials** live only in your local, gitignored `voice.env` (and the baresip `accounts` file for ringback-alert) β€” never in the repo or the MCP client config when you can avoid it.
- The voice server only ever **calls the single SIP URI you configure** β€” it cannot dial arbitrary numbers.
- Treat ntfy topics as secrets (use a long random topic); don't put sensitive detail in alert bodies on public ntfy.sh.
- See [`NOTICE`](NOTICE) for the GPL/pjproject licensing caveat before redistributing.

---

## License

Apache-2.0 (see [`LICENSE`](LICENSE)), with an important GPL caveat for the voice
component's pjproject dependency β€” see [`NOTICE`](NOTICE).

## Credits

Built on [pjproject/pjsua2](https://www.pjsip.org), [whisper.cpp](https://github.com/ggerganov/whisper.cpp),
[baresip](https://github.com/baresip/baresip), [ntfy](https://ntfy.sh), and
[Linphone](https://www.linphone.org).

TDQS

A4.7/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: alert_me sends real alerts, alert_status checks configuration, and alert_test sends a test notification. There is no overlap or ambiguity.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern (alert_me, alert_status, alert_test) with underscores, making them predictable and easy to understand.

Tool Count5/5

With only 3 tools, the server is well-scoped for its purpose of sending alerts and checking status. Each tool earns its place, and the count is within the ideal 3-15 range.

Completeness4/5

The server covers the core operations: sending alerts, checking configuration, and testing connectivity. While missing features like alert history or cancellation, it provides a complete surface for its stated purpose.

Maintenance

ActivityStale
ResponsivenessUnresponsive