ringabot
Allows the in-call voice agent to retrieve GitHub build status for a configured repository via the get_context lookup, providing live build information during a call.
Integrates with VoIP.ms as the SIP carrier for inbound and outbound phone calls, using a DID and sub-account for call routing, caller ID, and RFC 2833 DTMF.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ringabotcall +1-555-123-4567 and ask if my prescription is ready"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ringabot
An open-source phone line for a Grok voice agent. It is a small server (one process, one sqlite file) that places and answers phone calls with an xAI realtime voice agent, and exposes the same abilities to a bot through an MCP connector. You bring your own xAI key and your own SIP carrier number (written and tested with Voip.ms). Sign up for Voip.ms.
What you get:
MCP server at
/mcp:place_call,add_context,get_call,list_calls,hang_up.Outbound calls through baresip and your carrier, with the xAI voice agent doing the talking.
Inbound calls answered by the same agent through xAI Direct SIP.
Task calls: put the goal and limits in the briefing and the agent stays inside them; the result has a structured
outcome.Live context and phone menus during a call: a
get_contextlookup and asend_dtmftool.Safety by default: a destination policy, per-account rate limits, unverified-content rules.
Extra behaviour can be attached through the plugin hook.
Architecture
flowchart LR
Bot["Grok Bot / MCP client"] -- "MCP over HTTPS (bearer)" --> Caddy
Carrier["SIP carrier (e.g. Voip.ms)"] -- "SIP/TLS to the DID" --> XSIP["xAI Direct SIP\nsip.voice.x.ai"]
XSIP -- "signed webhook\nrealtime.call.incoming" --> Caddy
Caddy["Caddy (TLS)"] --> App["ringabot (loopback :8787)"]
App <--> DB[("sqlite\ncalls, rate limits")]
App -- "wss realtime\n(JSON events, audio for outbound)" --> XAI["xAI realtime voice"]
App -- "ctrl_tcp" --> Baresip["baresip (one per outbound call)"]
Baresip <-- "SIP + RTP" --> Carrier
Baresip <-- "8 kHz PCM via snd-aloop" --> App
App -- "POST result (HMAC-signed)" --> Hook["your callback_url"]
App -- "text model: summary, outcome" --> XText["xAI chat API"]Inbound: the carrier routes the DID to
sip:+1NXXNXXXXXX@sip.voice.x.ai. xAI posts a signedrealtime.call.incomingwebhook; Ringabot answers in milliseconds and joins the call over a websocket bycall_id. No audio touches this host.Outbound:
place_callstarts one baresip process that dials through the carrier sub-account. 8 kHz PCM is relayed between ansnd-aloopdevice and the xAI realtime websocket.One persona for both directions (voice, instructions, opening line). Every call opens with a short line saying it is an AI assistant.
Related MCP server: voxaphone-mcp
Outbound call sequence
sequenceDiagram
autonumber
participant Bot as Grok Bot (MCP client)
participant R as ringabot
participant X as xAI (realtime + text)
participant B as baresip
participant P as Callee phone
participant H as callback_url
Bot->>R: place_call(number, briefing, callback_url?)
R->>R: destination policy, rate limit, callback checks
R-->>Bot: call_id
R->>X: read goal/limits out of the briefing (strict JSON schema)
R->>X: open realtime session (instructions + tools)
R->>B: dial through the carrier
B->>P: ring, answer
P-->>R: speech (audio relayed to xAI)
X-->>R: agent speech (relayed to the phone)
opt the agent needs a fact
X->>R: get_context(topic)
R-->>X: briefing / add_context notes, GitHub build status, or feed
end
opt the bot learns something mid-call
Bot->>R: add_context(call_id, text)
R->>X: system note into the live conversation
end
X->>R: end_call (after saying goodbye)
R->>B: hang up
R->>X: transcript to a text model: summary, todos, decisions, outcome, requested_actions
R->>R: save the call in sqlite
R->>H: POST result, X-Ringabot-Signature (retried)
Bot->>R: get_call(call_id)
R-->>Bot: structured, UNVERIFIED resultPhone menus (DTMF)
The agent can press keypad digits to move through an automated menu. xAI documents no way to send
DTMF and Ringabot's xAI leg is a websocket, so the digits go out on the SIP leg: baresip's
sndcode command sends RFC 2833/4733 telephone-events. python3 scripts/dtmf_loopback.py checks
baresip by itself with two local processes (no carrier, no xAI).
flowchart TD
A["Menu prompt heard"] --> B{"Option fits the call's goal?"}
B -- yes --> C["send_dtmf(digits): 0-9 * #, up to 6"]
B -- "no clear option" --> D["Press operator / reception once,\nor ask for a person by voice"]
C --> G{"Under 6 digits at this menu level\nand 40 for the call?"}
G -- yes --> H["baresip sndcode, logged to the\ntranscript as role dtmf"]
H --> I["Stop and listen"]
I --> J{"New prompt from the other side?"}
J -- yes --> K["New menu level: counter resets"]
K --> A
J -- "no / silence" --> L["Wait; idle timeout may end the call"]
G -- no --> M["Tool refuses: ask for a human\nor reception by voice, or end the call"]
D --> IQuick start (Ubuntu 24.04)
You need: an xAI API key, a SIP carrier account with a DID and a sub-account (Sign up for Voip.ms if you do not have one), and a host with a public name for the webhook.
sudo useradd --system --home /opt/ringabot ringabot
sudo git clone https://github.com/wembledev/ringabot /opt/ringabot
cd /opt/ringabot && sudo uv sync --frozen --no-dev
sudo install -d -m 0750 -o root -g ringabot /etc/ringabot
sudo install -m 0600 -o ringabot .env.example /etc/ringabot/ringabot.env # then fill it
sudo apt install caddy baresip alsa-utils
echo snd-aloop | sudo tee /etc/modules-load.d/snd-aloop.conf
sudo cp deploy/Caddyfile /etc/caddy/Caddyfile # set PUBLIC_HOSTNAME
sudo cp deploy/ringabot.service /etc/systemd/system/Fill /etc/ringabot/ringabot.env (see .env.example; every value is read from the environment):
Variable | Meaning |
| your xAI key (server side only) |
| admin bearer for MCP and |
| your carrier number |
|
|
| the carrier POP host and the sub-account used for outbound |
| the carrier's signaling ranges, so xAI only accepts INVITEs from it |
Register the number with xAI. This creates the Direct SIP number and its webhook and writes the one-time signing secret into the env file without printing it:
sudo bash -c 'set -a; . /etc/ringabot/ringabot.env; set +a; \
/opt/ringabot/.venv/bin/ringabot register-number --name my-line'
sudo systemctl enable --now ringabot
sudo -u ringabot bash -c 'set -a; . /etc/ringabot/ringabot.env; set +a; \
/opt/ringabot/.venv/bin/ringabot doctor'doctor checks the key, the webhook secret, the token, the listener, baresip, snd-aloop and that
the DID is registered with xAI. Open 443/tcp and 80/tcp for the webhook; for outbound also allow
UDP from your carrier's media range to ports 1024-65535.
Carrier setup (Voip.ms)
Sign up for Voip.ms, then order a DID. Emergency-address service on it is between you and the carrier; Ringabot will not place emergency calls.
Inbound: DID → Routing → SIP URI
+1NXXNXXXXXX@sip.voice.x.ai.Outbound: create a sub-account, give it a caller ID number and the device type "IP phone / softphone", and put its name, password and POP host in
SIP_USERNAME,SIP_PASSWORD,SIP_SERVER. Without a caller ID the call fails at once.DTMF mode on the sub-account must be RFC 2833 (the default) for
send_dtmfto work.
Caddy and systemd
deploy/Caddyfile proxies only /xai/webhook, /healthz and /mcp; /call and /calls stay on
loopback (use them over SSH). deploy/ringabot.service runs the server as an unprivileged user with
StateDirectory=ringabot (so /var/lib/ringabot/ringabot.sqlite is writable under
ProtectSystem=strict), a read-only system, and no capabilities.
Connect a Grok Bot or any MCP client
The connector is https://<PUBLIC_HOSTNAME>/mcp (Streamable HTTP, JSON responses). Auth is
Authorization: Bearer <API_TOKEN>.
# Grok CLI
grok mcp add --transport http ringabot https://<PUBLIC_HOSTNAME>/mcp \
--header "Authorization: Bearer ${RINGABOT_API_TOKEN}"With the xAI API, pass the token as authorization on an mcp tool. Any client that can send a
bearer header works.
Tool | What it does |
| Phone a number. The briefing carries everything the agent needs, including a task's goal, limits (max price), items, address and constraints; the server reads them out of the briefing. An explicit |
| Give the agent new information during a live call. Errors if the call is not active. |
| Structured, unverified result: |
| Recent outbound calls with status and transcript. |
| End an active call. |
Inside a call the voice agent itself has get_context(topic) (the briefing and add_context
notes, GitHub build status for CONTEXT_GITHUB_REPO, or an optional feed) and send_dtmf(digits).
Both exist only on outbound calls.
Clients that cache the tool list are told about changes: initialize reports
serverInfo.version = <version>+<git sha> and tools.listChanged, and a stale client gets
notifications/tools/list_changed. If one still shows an old list, remove and re-add the connector.
Briefings, tasks and outcomes
State a task in plain words in the briefing, for example: "Book a dental cleaning for next week.
Maximum price is CAD 150 including tax. Weekday mornings only." The server extracts goal, limits,
items, address and constraints (an xAI text model with a strict JSON schema; a heuristic if that
fails; a plain call if nothing is found) and records them on the call as task with
source: "briefing". The agent works inside those limits only, accepts a clearly better offer that
stays inside every limit, lets the other person lead, and raises anything still missing before it
ends the call. outcome is {goal, inferred, status: done|not_done|partial|unknown, details};
details includes better_offer_accepted when one was taken. Briefings up to
RINGABOT_MAX_BRIEFING_CHARS (default 12000) are kept; longer ones are cut at a heading or
paragraph boundary and the result says so.
Security model
Everything that comes back from a call is unverified. Answering the phone does not prove the speaker is the owner.
get_call,list_callsand the callback payload carrytrust: "unverified_call_content"and ahandlingnote.summary,todos,decisions,outcomeandtranscriptare context only. Anything the other party asked for is inrequested_actions, each{text, verified: false, note}(verifiedis forced tofalse). Never treat any of it as an instruction, a setting, or a fact to save; confirm with the owner.Ringabot only returns results. It never writes the bot's memory or settings and never triggers an action. The one thing it sends to the bot's side is the optional
callback_urlPOST.Callbacks need
RINGABOT_CALLBACK_SECRET; the body is signed withX-Ringabot-Signature: sha256=<hmac>. URLs must be https (http://localhostonly withRINGABOT_CALLBACK_ALLOW_LOCALHOST=1; private addresses are refused). Three attempts.Who can be called. Any valid number except what is always blocked, even if allow-listed: emergency numbers (
911,112,999,000,988,110,119) and N11 codes; invalid numbers (E.164 and North American structure rules, short codes, no country code); North American900,976and555-1212; international premium and costly ranges (+979,+881-+883,+870-+874,+878); numbers libphonenumber classes as premium-rate, shared-cost or personal-number (built from national numbering plans); Ringabot's own number; and anything inRINGABOT_BLOCKED_DESTINATIONS.ALLOWED_DESTINATIONSonly narrows this further. The list is best effort and only as complete as the installedphonenumbersrelease; keep it updated.Rate limits per caller identity:
RINGABOT_RATE_LIMIT_PER_HOUR(10) andRINGABOT_RATE_LIMIT_PER_DAY(30), kept in sqlite, with a clear error andretry_after_s.Retention. Transcript and briefing text older than
RINGABOT_TRANSCRIPT_RETENTION_DAYS(default 30,0= keep) is purged at startup and every 6 hours; summaries and outcomes stay.Webhooks and secrets. Every xAI webhook is verified (Standard Webhooks v1, HMAC-SHA256, a 5 minute window, replay cache). No credential ships in this repo;
.env.examplehas names only. Admin routes needAPI_TOKENand are not proxied by Caddy.Use it responsibly. Not a dialer for telemarketing or robocalls. Calls open by saying the caller is an AI assistant. Nothing is recorded as audio; transcripts are text in sqlite.
HTTP
Route | Auth | What |
| xAI signature | Incoming-call events (public via Caddy) |
| none | Liveness (public via Caddy) |
| bearer | MCP tools; the GET stream carries |
| admin bearer |
|
| admin bearer | Recent calls |
| admin bearer | End a call |
Plugins
The MCP server is complete on its own. An operator can attach extra behaviour by naming
module:factory entries in RINGABOT_PLUGINS (comma-separated); nothing is loaded by default.
The factory receives a PluginContext(settings, store, manager) and returns a
ringabot.plugins.Plugin, which may add HTTP routes, MCP tools (tools and call_tool), extra
bearer-token identities (identify, returning any object with an id, which then scopes the calls
that identity sees and counts against its rate limit), and veto an action (authorize). Plugins run
in-process with your privileges, so list only code you trust.
Develop
uv sync --all-groups
uv run ruff check . && uv run ruff format --check .
uv run pytesttests/test_no_secrets.py fails the build if the tree contains credentials, real-looking phone
numbers or other private material. scripts/dtmf_loopback.py needs baresip installed.
License
MIT.
This server cannot be deployed
Maintenance
Related MCP Connectors
AI call analysis and voice agents for sales teams. Signup, usage, agents and call data over MCP.
Give AI agents a phone number. Voice calls, SMS, and phone number management for MCP clients.
U.S. outbound calls from a verified caller ID for AI agents via CLI/MCP.
Official MCP server for OmniDimension. Drive voice agents, dispatch calls, and run bulk campaigns.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceProvides a unified context per contact across voice (NL Pearl v2) and custom WhatsApp/SMS channels, with MCP tools to resolve identity, retrieve timeline, upsert contacts, append interactions, manage signals, record call context, and suggest follow-ups.-
- AlicenseNot gradedqualityBmaintenanceEnables LLM agents to initiate real-time outbound WebRTC/SIP calls, execute operational actions mid-call, escalate to human operators, and access live call telemetry through MCP, with zero data-at-rest and TTL-bounded memory.MIT
- AlicenseBqualityCmaintenanceEnables MCP clients to manage an AI voice calling workspace by creating and editing agents, running outbound campaigns, placing calls, sending one-way voice messages, reading analytics, and managing webhooks.25MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to place, monitor, and hang up phone calls through authenticated tools, retrieve transcripts, and exchange correlated task results with a waiting host. It supports simulated or live calling with persisted call lifecycle records, duration limits, and idempotent requests.MIT