CallTool
Click on "Install 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., "@CallToolCall +49301234567 and tell them their order has arrived."
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.
CallTool
CallTool is a self-hosted telephone execution service for AI agents. Clients create and control outbound calls through MCP or REST, and the same worker can answer inbound calls to a Telnyx number. LiveKit carries media between Telnyx SIP and the selected native realtime voice provider: Gemini Live or OpenAI Realtime.
The complete architecture and product decisions are documented in projects-v0.1.md. The versioned delivery plan is documented in ROADMAP.md. Deployment and Telnyx provisioning are documented in docs/self-hosting.md.
Development status
The repository is under active development on the feature/initial branch. The v0.1 target
is a configurable multilingual inbound and outbound caller with durable state,
policy-controlled commitments, human-in-the-loop input, and low-latency native audio
conversation. German remains the default language.
Related MCP server: AgentPhone MCP Server
Prerequisites before docker compose
There are two separate requirements: Docker must be able to run the local stack, and
Telnyx, LiveKit, and at least one supported realtime AI provider must be configured before
real phone calls can work. Python and uv are not required on the host when everything is
run through Compose; they are only needed for local development commands.
Host and Docker
You need:
A Linux server or workstation with a public, reachable IP address for real SIP calls. A private laptop behind NAT is fine for software tests, but Telnyx cannot deliver inbound SIP traffic to
localhost.Docker Engine and the Docker Compose plugin. Verify both with:
docker --version docker compose version docker infoPermission for the current user to access Docker. If the user was just added to the
dockergroup, open a new terminal or runnewgrp dockerbefore using Docker.Outbound internet access from Docker. The first build downloads container images and Python packages from Docker Hub, GHCR, and PyPI. Test DNS before starting:
docker run --rm busybox:1.36 nslookup files.pythonhosted.orgIf this fails with a DNS or network error, fix the Docker daemon's DNS/forwarding first.
docker composewill otherwise fail duringuv synceven if DNS works on the host itself.Free host ports
80,443,5060,7881,10000-20000/udp, and50000-60000/udp. The Compose file uses host networking for LiveKit and LiveKit SIP; another SIP service or web server must not already use these ports.
For an internet-facing deployment, allow these ports in both the server firewall and the hosting provider's firewall:
Port | Protocol | Purpose |
80 | TCP | Caddy HTTP and certificate validation |
443 | TCP/UDP | CallTool and LiveKit HTTPS/HTTP3 |
5060 | TCP | Telnyx to LiveKit SIP signaling |
7881 | TCP | LiveKit RTC fallback |
10000-20000 | UDP | LiveKit SIP RTP media |
50000-60000 | UDP | LiveKit RTC media |
The checked-in LiveKit configuration uses use_external_ip: true. The advertised
address must therefore be reachable from the public internet. If the server is behind a
router, forward the ports above and make sure the public IP and SIP hostname resolve to
the router/server correctly.
DNS names
For a real deployment, create DNS A records pointing to the server's public IP:
Name | Used for |
| Public CallTool REST and MCP endpoint |
| Public LiveKit WebSocket endpoint through Caddy |
| Telnyx FQDN SIP destination, direct to port |
calltool.example.com and livekit.example.com are entered as hostnames only, without
https://, in CALLTOOL_DOMAIN and LIVEKIT_DOMAIN. The SIP hostname must not be put
behind an HTTP reverse-proxy or CDN proxy; Telnyx connects to it as SIP/TCP, not HTTP.
Caddy only handles the first two hostnames.
The defaults localhost and livekit.localhost are useful for local HTTP checks, but
they are not sufficient for Telnyx inbound calls. Caddy also needs ports 80 and 443
reachable from the internet to obtain public certificates for real DNS names.
Telnyx account and information
The following must already exist in the Telnyx Mission Control Portal:
A paid Telnyx account with the required identity/Level 2 verification completed. German numbers may require identity and address documents before activation.
An active German phone number (DID), written in
+E.164format, for example+49301234567. The number must be assigned to the SIP connection below.An FQDN SIP Connection configured for both inbound and outbound calling:
FQDN destination:
sip.example.com, port5060(replace the hostname). If the portal uses one SIP URI field, entersip:sip.example.com:5060.Inbound SIP region: Europe, unless another region is intentionally used.
Transport: TCP on port
5060.Outbound authentication:
Credentialswith a dedicated SIP username and password. These are the values forTELNYX_SIP_USERNAMEandTELNYX_SIP_PASSWORD.AnchorSite:
Latencyis suitable for the first setup; a fixed European site can be selected later if required.
An Outbound Voice Profile assigned to that SIP Connection. Allow Germany (
DE) and every other destination the agent is allowed to call. If the portal asks for a traffic type/service plan, selectConversationaland theGlobalplan intended for voice-bot traffic.The German number assigned to this SIP Connection under Telnyx's number settings.
This setup does not need a Telnyx API key in .env: the sip bootstrap command creates
the LiveKit-side trunks through the self-hosted LiveKit API. A Telnyx API key is only
needed if the Telnyx connection and number setup is automated through Telnyx's API.
The Telnyx SIP username/password are not the Telnyx portal login and not the API key.
The current European Telnyx SIP signaling addresses are 185.246.41.140 and
185.246.41.141; they are already allow-listed in config/calltool.yaml. If you select
another Telnyx SIP region, update calls.inbound.allowed_addresses from Telnyx's
current SIP network information before running the bootstrap.
Telnyx's current RTP networks and port requirements are listed on the same page and
should be considered when restricting the server firewall.
AI provider and LiveKit credentials
You also need:
Credentials for the selected realtime voice provider:
Gemini: a Google AI Studio API key in
GOOGLE_API_KEY, with access to the configured Gemini Live model.OpenAI: an OpenAI API key in
OPENAI_API_KEY, with Realtime API access. Supported models aregpt-realtime-2.1and the faster, lower-costgpt-realtime-2.1-mini. There is no OpenAI model namedgpt-realtime-2.1-flash.
The default background supervisor and Gemini scripted-TTS features also use
GOOGLE_API_KEY. To run with only an OpenAI key, disablevoice.supervisor.enabledinconfig/calltool.yaml; Gemini scripted TTS is bypassed automatically for OpenAI Realtime calls. Keep optional Gemini shadow STT disabled as well.A self-hosted LiveKit key and secret. For the first local test, keep the matching development values from
config/livekit.yamlandconfig/sip.yaml:devkey/secret. For production, generate strong values and replace them in all three places:.env,config/livekit.yaml, andconfig/sip.yaml.A strong random
CALLTOOL_API_KEY. It protects all REST and MCP routes except health, readiness, and metrics. Do not leavechange-mein an internet-facing deployment.A strong random
WEBHOOK_SIGNING_SECRETifWEBHOOK_URLis configured. Webhooks are optional; leaveWEBHOOK_URLempty if no event receiver is needed.
Configure .env before starting
From the repository root:
cp .env.example .envEdit .env and set at least these values for real calls:
CALLTOOL_ENV=development
CALLTOOL_API_KEY=<strong-calltool-api-key>
# Configure the provider key or keys that are used by your setup.
GOOGLE_API_KEY=<google-gemini-api-key>
OPENAI_API_KEY=<openai-api-key>
TELNYX_SIP_ADDRESS=sip.telnyx.eu
TELNYX_SIP_USERNAME=<telnyx-outbound-sip-username>
TELNYX_SIP_PASSWORD=<telnyx-outbound-sip-password>
TELNYX_FROM_NUMBER=+49...
CALLTOOL_DOMAIN=calltool.example.com
LIVEKIT_DOMAIN=livekit.example.com
LIVEKIT_SIP_TRUNK_ID=Use +E.164 for both TELNYX_FROM_NUMBER and every number sent to the API. Keep
LIVEKIT_SIP_TRUNK_ID empty initially; the bootstrap command prints the value after it
creates the reusable outbound trunk. The Compose file supplies the internal database,
Redis, and LiveKit URLs automatically. If you change the default PostgreSQL password,
also set POSTGRES_PASSWORD in .env; Compose uses it for both PostgreSQL and
CallTool. Set it before the first start; changing it later does not change the password
inside an already-initialized PostgreSQL volume.
For production, change CALLTOOL_ENV to production only after replacing every
development placeholder (change-me, devkey, and secret) and applying the matching
LiveKit config changes described above. Keep .env private and never commit it.
Voice provider, model, language, and voice
The checked-in default is Gemini in German with the Puck voice. You can change the
global default in config/calltool.yaml, or use these .env overrides without editing
YAML:
# Gemini default
CALLTOOL_VOICE_PROVIDER=gemini
CALLTOOL_VOICE_MODEL=gemini-3.1-flash-live-preview
CALLTOOL_VOICE_LANGUAGE=de
CALLTOOL_VOICE_NAME=PuckFor OpenAI Realtime, choose either the full model or the official mini model:
OPENAI_API_KEY=<openai-api-key>
CALLTOOL_VOICE_PROVIDER=openai
CALLTOOL_VOICE_MODEL=gpt-realtime-2.1
CALLTOOL_VOICE_LANGUAGE=de
CALLTOOL_VOICE_NAME=marinUse gpt-realtime-2.1-mini when lower latency and cost matter more than the full
model's capability. Both variants are native speech-to-speech models. The supported
built-in OpenAI voices are alloy, ash, ballad, coral, echo, sage, shimmer,
verse, marin, and cedar. Eligible OpenAI custom voice IDs beginning with voice_
are accepted as well.
Language accepts a compact BCP-47 language tag such as de, en, en-US, or fr-FR.
It is included in the system instructions and, for OpenAI, passed to input-audio
transcription. The voice is selected before the realtime session starts and cannot be
changed after that session has produced audio.
An individual outbound REST or MCP call can override all four values. Per-call values
take precedence over .env, and .env takes precedence over config/calltool.yaml:
{
"target": {"phone_number": "+49301234567", "name": "Test"},
"objective": "Vereinbare einen Rückruf.",
"voice": {
"provider": "openai",
"model": "gpt-realtime-2.1-mini",
"language": "en-US",
"voice": "cedar"
}
}For OpenAI calls, the native Realtime model also speaks the initial disclosure and greeting so the voice stays consistent from the first sentence. Gemini calls keep the pre-synthesized scripted greeting for German; other languages use the selected native realtime model for the localized greeting.
The exact model names and integration options are documented by OpenAI GPT-Realtime-2.1, OpenAI GPT-Realtime-2.1 Mini, and the LiveKit OpenAI Realtime plugin.
v0.1.1 voice and telephony test switches
The LiveKit-first v0.1.1 features are independently configurable. The checked-in self-hosted defaults use provider-native turn detection, VAD interruption handling, no Krisp, and keep IVR, AMD, and Cold Transfer disabled:
CALLTOOL_TURN_DETECTION_MODE=realtime_llm
CALLTOOL_INTERRUPTION_MODE=vad
CALLTOOL_IVR_ENABLED=false
CALLTOOL_AMD_ENABLED=false
CALLTOOL_COLD_TRANSFER_ENABLED=false
CALLTOOL_KRISP_ENABLED=falseEmpty values use config/calltool.yaml. Valid turn modes are realtime_llm and
livekit_v1_mini; the only interruption mode in this fully self-hosted build is vad.
The local v1-mini path disables the realtime provider's server-side endpointing for
that session, uses the local LiveKit VAD, and loads the approximately 108 MB local
turn-detector model in the worker process. No external LiveKit service or inference
gateway is used. Only one turn strategy is active per call.
For livekit_v1_mini, CALLTOOL_TURN_UNLIKELY_THRESHOLD and
CALLTOOL_TURN_BACKCHANNEL_THRESHOLD optionally override the detector thresholds for
the selected language. Keep them empty until German evaluation calls establish suitable
values; this makes the threshold decision measurable instead of silently treating the
English defaults as calibrated German values.
Enabling IVR exposes CallTool's audited send_dtmf tool on outbound calls and enables
LiveKit IVR loop/silence handling. telephony.ivr controls allowed digits, maximum
digits per action, inter-digit delay, the complete navigation timeout, and whether raw
digits may be written to audit events. Raw digits are hidden by default.
AMD is outbound-only and runs once immediately after answer. It uses LiveKit AMD with a
small provider-native text classifier (gemini-2.5-flash-lite or gpt-4.1-mini) and the
realtime session's input transcript, so the fully self-hosted path does not require a
second STT or an additional inference service. Keep voice.realtime.input_transcription: true.
The telephony.amd policy can hang up, continue, leave the file-prompted voicemail from
voicemail-instruction.md, or request a human decision through the existing MCP/REST
respond flow. AMD category, transcript, timings, action outcome, and errors are stored
in the call state and events.
Cold Transfer uses LiveKit's SIP transfer API/SIP REFER and is exposed only when both the
global switch is enabled and the outbound request grants may_transfer. Telnyx must
allow SIP REFER for the connection. A transfer-capable request includes:
{
"target": {"phone_number": "+49301234567", "name": "Praxis"},
"objective": "Rufe die Praxis an und übergib bei Bedarf an mich.",
"permissions": {
"may_commit": false,
"may_accept_costs": false,
"may_transfer": true,
"may_disclose": []
}
}The transfer target passes the same country, emergency-number, and premium-number policy as a normal outbound call. Transfer ID, target, timeout result, LiveKit reason, and SIP status remain available in PostgreSQL through call status/conversation responses.
LiveKit plugin metrics are aggregated in Prometheus without synchronously writing raw
samples to PostgreSQL. Per-turn EOT/transcription/response delays, native interruption
events, false interruptions, usage, and a compact final session report are also captured.
CallTool's /metrics endpoint exposes these alongside lifecycle, tool, policy, watchdog,
and barge-in metrics.
File-based prompt profiles
System prompts and default greetings are not embedded in Python. The checked-in profile
is located at config/prompts/default and contains:
File | Purpose |
| Complete system prompt for outbound calls |
| Complete system prompt for inbound calls |
| Default outbound greeting source text |
| Default inbound greeting source text |
| Instruction used when the native realtime model localizes the greeting |
| Policy-safe message used when AMD selects |
| Navigation strategy used after AMD recognizes an IVR |
| Model instruction used to recover from an unexpected silent turn |
| Final scripted recovery phrase if model recovery fails |
| Prompt for the optional post-call outcome supervisor |
To create a company-specific profile, copy the directory and edit the copied files:
cp -a config/prompts/default config/prompts/lwlpThen select it in .env using its path inside the Compose container:
CALLTOOL_PROMPT_DIR=/app/config/prompts/lwlpAlternatively, change voice.prompts.directory and the optional filenames in
config/calltool.yaml. Compose mounts the complete local config directory read-only
at /app/config, so prompt edits do not require rebuilding the image. A profile is read
as one snapshot when a call starts; changes therefore affect the next call, never the
middle of an active conversation.
Templates use deliberately limited {{ placeholder }} substitution without executable
template code. Available placeholders are:
Call data:
call_id,direction,objective,target_name,target_phone_number,caller_name,caller_phone_number,called_phone_number, andorganization_name.Runtime data:
language,context_json,constraints_json,permissions_json,may_commit,may_accept_costs,may_transfer, andmay_disclose_json.Greeting instruction only:
greeting_json, containing the rendered greeting as a JSON string.Supervisor only:
outcome_json, containing the structured call result.
Unknown or malformed placeholders, missing files, invalid UTF-8, empty files, and files larger than 128 KiB make the API/worker validation fail. Check a profile before calling:
docker compose run --rm calltool-api doctorFor security, MCP and REST callers cannot submit arbitrary server-side file paths. The operator selects the mounted profile, while call-specific objective, context, permissions, language, and voice continue to arrive through the normal request schema. Keep the AI disclosure in custom greeting and system templates where legally required.
Quick start
Once the prerequisites and .env are ready, validate and start the stack:
docker compose config --quiet
docker compose up -d --build
docker compose psWait until PostgreSQL and Redis are healthy and inspect startup errors with:
docker compose logs --tail=100 calltool-api calltool-worker livekit livekit-sipCreate the reusable LiveKit outbound trunk, inbound trunk, and inbound dispatch rule
once. This uses the Telnyx SIP values from .env:
docker compose run --rm calltool-api sip bootstrapCopy the printed Set LIVEKIT_SIP_TRUNK_ID=... value into .env, then recreate the API
and worker so they receive it:
docker compose up -d --force-recreate calltool-api calltool-worker
docker compose run --rm calltool-api doctorThe doctor must report READY before placing a real call. To test inbound calling, call
TELNYX_FROM_NUMBER from another phone while following the logs:
docker compose logs -f calltool-worker livekit-sipFor an explicit outbound diagnostic call to a number you control:
docker compose run --rm calltool-api doctor --call +49...Each inbound caller is routed to a dedicated room and the calltool worker answers with
the greeting from config/prompts/default/greeting-inbound.txt or the selected custom
prompt profile. The first inbound call should be made only after the Telnyx number is
assigned to the SIP Connection and the bootstrap has completed.
Durable call history and conversations
PostgreSQL is the source of truth for every inbound and outbound call. The calls.direction
column distinguishes inbound from outbound; created_at, connected_at, and ended_at
record when the job arrived, when the telephone conversation actually started, and when it
ended. History searches use started_at, which means connected_at when available and
otherwise created_at for unanswered or not-yet-connected calls.
phone_call.list returns both directions newest-first by default. It can filter by
direction, exact phone_number, case-insensitive partial target_name, status, and a
time window. started_after is inclusive, started_before is exclusive, and both require
an ISO 8601 timezone. Use next_cursor unchanged to fetch the next page. For example, an
agent can find the latest outbound call to a doctor with:
{
"direction": "outbound",
"target_name": "Arzt",
"limit": 1
}To list inbound caller IDs from a defined period:
{
"direction": "inbound",
"started_after": "2026-08-01T00:00:00+02:00",
"started_before": "2026-09-01T00:00:00+02:00",
"limit": 50
}Pass a returned call_id to phone_call.conversation. It returns the full call record,
timing and duration, structured summary and facts, and the ordered user/assistant text
transcript. The equivalent REST endpoints are:
GET /v1/calls
GET /v1/calls/{call_id}/conversationText transcripts are enabled through storage.transcript: true in
config/calltool.yaml; audio recording remains disabled. Set transcript storage to
false if full conversation text must not be retained. Existing calls and events remain
in PostgreSQL across container restarts through the Compose volume.
During an active call, the worker owns one dedicated ActiveCallContext in RAM. Local
voice tools read and update this hot state without a PostgreSQL lookup. A serialized
background writer persists facts, candidates, transcript turns, and events in order;
worker shutdown flushes the queue. Safety-critical commitment and completion writes are
acknowledged by PostgreSQL before the tool reports success. PostgreSQL remains the source
of truth after the call and after process restarts.
Local development without the full Compose stack
If only the API is being developed locally, install Python 3.13.15 and uv:
uv sync --all-extras
uv run calltool apiThis mode does not replace the LiveKit, LiveKit SIP, PostgreSQL, and Redis services needed
for real calls. Detailed public deployment and Telnyx provisioning notes are in
docs/self-hosting.md.
Container releases
Every successful push to feature/initial runs linting, type checks, unit and PostgreSQL
integration tests. The same workflow then creates the next development tag and publishes
a multi-architecture image for linux/amd64 and linux/arm64 to:
ghcr.io/lwlp24/voice-mcpBecause v0.1.0 already exists as the initial repository release, current development
builds target the next patch release and are numbered automatically:
v0.1.1-dev.1
v0.1.1-dev.2
v0.1.1-dev.3Do not create these development tags manually. The workflow serializes runs for the
branch, derives the release base from the PEP 440 version in pyproject.toml, determines
the highest matching -dev.N tag, increments N, tags the tested commit, and builds that
exact version. Development images receive both the raw v0.1.1-dev.N and normalized
0.1.1-dev.N image tags, but never update latest.
When the patch is ready, push a stable tag such as v0.1.1. A stable semantic version
also publishes the 0.1, 0, and latest aliases. Manually pushed v* tags still run
the complete quality gate before their image is published.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to make real-world phone calls with AI voice technology and provides tools to track call status, transcripts, and summaries. It supports automated communication with both live numbers and simulated businesses for testing and demonstration purposes.
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to manage phone numbers, send/receive SMS, and place voice calls through natural language, connecting to the phone network via the AgentPhone API.7,521119MIT
- AlicenseAqualityBmaintenanceManage voice AI agents, make calls, run campaigns, and control phone numbers through natural language.58201MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to place VoIP phone calls via Asterisk, play TTS messages, capture DTMF input, and optionally record and transcribe voice responses.MIT
Related MCP Connectors
Let AI agents place real phone calls from your verified number, with transcripts and recordings.
Give your AI agent a phone: place calls, navigate IVRs, wait on hold, get structured answers.
Voice and chat for AI agents — Discord, Teams, Meet, Slack, Zoom, Telegram, WhatsApp, NC Talk, SIP
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/LWLP24/voice-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server