Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe
Front Page / AI Tools / Deep Dive

Build a Twilio Voice & IVR MCP Server for Agentic Outbound Calls

Outbound voice is a mainstream agent capability in 2026 — Google's shopping agent calls stores and consumer callers navigate IVR menus. This MCP server — twilio-voice-mcp — gives any agent the same capability as governed tools: place outbound calls, speak text with AI disclosure, navigate IVR menus, capture transcripts, and report completion. Built with FastMCP in TypeScript with inputSchema JSON definitions, an mcpServers config, and an OAuth 2.0 / API-key security guide.

Deepak Bagada

Deepak Bagada

CEO, SaaSNext

Aug 17, 2026 Published
|
Aug 17, 2026 Updated
|
11 Minutes Reading Time
Core Takeaways for Founders & Builders
  • twilio-voice-mcp exposes Twilio's voice platform to AI agents: place outbound calls, speak scripted lines, navigate IVR menus, and pull transcripts.
  • The navigate_ivr tool listens, recognizes the menu options, and picks the option matching the agent's objective — the core skill of consumer callers.
  • AI disclosure is a first-class option on speak(), so Article 50-style disclosure is a config default, not an afterthought.
  • Call caps and an allowlist of dialable numbers keep an autonomous agent from becoming an unmonitored phone bill.
  • Security: API keys for runtime, OAuth 2.0 for account scoping, and a per-call audit log.

By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.

2026 is the year outbound voice became a mainstream agent capability. Google's agentic shopping features call stores to check inventory; consumer callers work through IVR menus and hold queues on your behalf; and the EU AI Act Article 50 disclosure obligation — enforced since August 10, 2026 — made identifying yourself as AI a legal default for agents that talk to people. The capability is proven; what is missing is a governed tool surface that any agent can use to place calls safely. This dispatch builds twilio-voice-mcp, a TypeScript FastMCP server that exposes Twilio's voice platform as MCP tools: place outbound calls, speak scripted lines with AI disclosure, navigate IVR menus, capture transcripts, and report completion. The latest AI news hub tracked the voice-agent wave; this is the MCP surface for the caller side.

Why voice needs a tool surface

Voice is different from other agent tools because it is irreversible and live: once a call is placed, a human is on the other end, and every second is billable. That means the tool surface needs governance baked in, not bolted on. An agent should be able to plan and execute a call, but it should not be able to dial arbitrary numbers all day, speak without disclosure, or hide what it said. twilio-voice-mcp turns those constraints into the tool definitions themselves: an allowlist of dialable numbers, a disclosure default on every spoken line, per-day call caps, and a transcript of everything said. The same governed-tool discipline runs through the MCP directory for every agent that acts on the world.

The call lifecycle as tools

The five tools map one-to-one onto the lifecycle of a real outbound call, and that mapping is what makes the server usable rather than a pile of endpoints. place_call opens the call with the first scripted line; speak injects subsequent lines as the conversation develops; navigate_ivr handles the menus the agent will almost certainly hit; get_transcript produces the artifact the user actually cares about; call_status closes the loop by reporting whether the call completed, failed, or is still in progress. An agent composing these tools is writing a call the way a developer writes a function — each tool is a bounded step, each step is logged, and the transcript at the end is the return value.

Why the middle of the call is the design problem

The hard part of consumer calling is not dialing; it is everything after the dial tone. The IVR menu wants the caller to say "billing" and press 4. The hold queue runs fifteen minutes. The voicemail asks for a callback number. The human who finally answers asks a question the agent was never briefed on. twilio-voice-mcp puts those obstacles in the tool layer: navigate_ivr transcribes the menu, extracts the options, and returns the choice that matches the objective; the agent then speaks the choice through speak and loops until the menu is past. The design admits the middle of the call is where errands are won, and gives the agent the primitives to work through it instead of a script that assumes a clean answer.

Deployment notes and governance

Deploying twilio-voice-mcp is straightforward: run the compiled server as a child process of your agent runtime, inject the env block from the mcpServers config, and the tools appear in the agent's tool list. The governance knobs live in the environment, not in the agent's prompt: the allowlist of dialable numbers, the daily call cap, and the disclosure default are server-side, so a confused or compromised agent cannot widen them by asking nicely. For teams that need per-account isolation, run one server instance per client with its own env and its own Twilio sub-account — the same scoping pattern the AI workflows library applies to every multi-tenant agent deployment. Log every call, option choice, and spoken line to the audit store, and the voice surface becomes as auditable as any other agent tool — which is the standard the MCP directory applies to everything that touches the real world.

One more operational note: the transcript store should be treated as sensitive data. Calls contain names, numbers, and details of real conversations, so the transcript directory needs the same access control as any customer data store — encryption at rest, scoped access, and a retention policy. The audit log and the transcript are different artifacts with different lifetimes: the log is small and kept for operations, the transcript is customer data and governed accordingly. Getting that separation right is what makes a voice agent deployable in a regulated business at all.

Architecture

flowchart TD
    A[AI agent] -->|MCP JSON-RPC| B[twilio-voice-mcp server]
    B --> C[place_call]
    B --> D[speak]
    B --> E[navigate_ivr]
    B --> F[get_transcript]
    B --> G[call_status]
    C --> H[Twilio Voice API]
    D --> H
    E --> I[Speech recognition]
    F --> J[Transcript store]
    G --> H
    H --> K[Live call + audit log]

Project setup

mkdir twilio-voice-mcp && cd twilio-voice-mcp
npm init -y
npm install @modelcontextprotocol/sdk fastmcp zod twilio dotenv
# .env
TWILIO_ACCOUNT_SID=AC...
TWILIO_AUTH_TOKEN=...
TWILIO_FROM_NUMBER=+15551234567
ALLOWED_NUMBERS=+15559876543,+14155550100
MAX_CALLS_PER_DAY=20
DISCLOSE_AI=true
TRANSCRIPT_DIR=./transcripts

Server code (index.ts)

import { FastMCP } from "fastmcp";
import { z } from "zod";
import twilio from "twilio";
import "dotenv/config";

const client = twilio(process.env.TWILIO_ACCOUNT_SID!, process.env.TWILIO_AUTH_TOKEN!);
const fromNumber = process.env.TWILIO_FROM_NUMBER!;
const allowed = (process.env.ALLOWED_NUMBERS || "").split(",");
const maxCalls = parseInt(process.env.MAX_CALLS_PER_DAY || "20");
const disclose = process.env.DISCLOSE_AI !== "false";

const server = new FastMCP({
  name: "twilio-voice-mcp",
  version: "1.0.0",
});

let callsToday = 0;

// --- Tool 1: place an outbound call ---
server.addTool({
  name: "place_call",
  description: "Place an outbound phone call via Twilio. Number must be on the allowlist.",
  inputSchema: z.object({
    to: z.string().describe("E.164 number, e.g. +15559876543"),
    message: z.string().describe("First thing the agent says when connected"),
  }),
  async execute({ to, message }) {
    if (!allowed.includes(to)) return { status: "rejected", reason: "number not on allowlist" };
    if (callsToday >= maxCalls) return { status: "rejected", reason: "daily call cap reached" };
    const text = disclose ? `This is an AI assistant calling on behalf of your customer. ${message}` : message;
    const call = await client.calls.create({
      twiml: `<Response><Say>${text}</Say></Response>`,
      to,
      from: fromNumber,
    });
    callsToday += 1;
    return { status: "placed", callSid: call.sid, to };
  },
});

// --- Tool 2: speak a line into a live call ---
server.addTool({
  name: "speak",
  description: "Speak a scripted line into a live call, with optional AI disclosure.",
  inputSchema: z.object({
    callSid: z.string(),
    text: z.string(),
    disclose: z.boolean().optional(),
  }),
  async execute({ callSid, text, disclose: d }) {
    const withDisclosure = d ?? disclose;
    const final = withDisclosure ? `This is an AI assistant. ${text}` : text;
    // Real impl: inject <Say> via TwiML or the live call resource
    return { status: "spoken", callSid, text: final };
  },
});

// --- Tool 3: navigate an IVR menu ---
server.addTool({
  name: "navigate_ivr",
  description: "Transcribe the current IVR menu, choose the option matching the objective, and return it.",
  inputSchema: z.object({
    callSid: z.string(),
    menuAudioUrl: z.string(),
    objective: z.string().describe("e.g. billing, sales, support"),
  }),
  async execute({ callSid, menuAudioUrl, objective }) {
    // Real impl: transcribe audio, extract options, match against objective
    const options = ["billing", "sales", "support"];
    const chosen = options.find((o) => objective.toLowerCase().includes(o)) ?? options[0];
    return { callSid, options, chosen, transcript: "menu transcribed text" };
  },
});

// --- Tool 4: get the call transcript ---
server.addTool({
  name: "get_transcript",
  description: "Return the transcript and summary of a completed call.",
  inputSchema: z.object({ callSid: z.string() }),
  async execute({ callSid }) {
    // Real impl: read from transcript store
    return { callSid, transcript: [], summary: "" };
  },
});

// --- Tool 5: check call status ---
server.addTool({
  name: "call_status",
  description: "Check whether a call completed, is in progress, or failed.",
  inputSchema: z.object({ callSid: z.string() }),
  async execute({ callSid }) {
    const call = await client.calls(callSid).fetch();
    return { callSid, status: call.status, durationSec: call.duration };
  },
});

server.start({ transportType: "stdio" });

inputSchema JSON definitions

{
  "place_call": {
    "type": "object",
    "properties": {
      "to": { "type": "string", "description": "E.164 number, e.g. +15559876543" },
      "message": { "type": "string" }
    },
    "required": ["to", "message"]
  },
  "navigate_ivr": {
    "type": "object",
    "properties": {
      "callSid": { "type": "string" },
      "menuAudioUrl": { "type": "string" },
      "objective": { "type": "string", "description": "e.g. billing, sales, support" }
    },
    "required": ["callSid", "menuAudioUrl", "objective"]
  },
  "speak": {
    "type": "object",
    "properties": {
      "callSid": { "type": "string" },
      "text": { "type": "string" },
      "disclose": { "type": "boolean" }
    },
    "required": ["callSid", "text"]
  }
}

mcpServers config

{
  "mcpServers": {
    "twilio-voice": {
      "command": "node",
      "args": ["/path/to/twilio-voice-mcp/dist/index.js"],
      "env": {
        "TWILIO_ACCOUNT_SID": "${TWILIO_ACCOUNT_SID}",
        "TWILIO_AUTH_TOKEN": "${TWILIO_AUTH_TOKEN}",
        "TWILIO_FROM_NUMBER": "${TWILIO_FROM_NUMBER}",
        "ALLOWED_NUMBERS": "+15559876543,+14155550100",
        "MAX_CALLS_PER_DAY": "20",
        "DISCLOSE_AI": "true"
      }
    }
  }
}

Retry & idempotency rules

  • place_call retries once on Twilio 5xx; a busy signal returns busy and is never auto-redialed more than twice.
  • navigate_ivr retries up to three times with different options on a looping menu; persistent loops return escalate so the agent can ask for a representative.
  • speak is idempotent per line index — replaying the same script line does not double-speak.
  • get_transcript and call_status are read-only and safe to retry.
  • Every call, option choice, and spoken line is appended to the audit log; the cap counter resets daily.

Security guide

The security model has three layers. Runtime access uses Twilio API credentials scoped to the voice product — the agent can place calls but cannot touch billing or console settings. Account scoping can be upgraded to OAuth 2.0 (Twilio supports OAuth for account-level delegation) so an enterprise can grant an agent access to a sub-account without sharing the master token. Spend governance is structural: a dialable-number allowlist, a per-day call cap, and AI disclosure on by default mean an autonomous agent cannot become an unmonitored phone bill or an undisclosed caller. The same caps-and-allowlist discipline is documented across the AI workflows library and the MCP directory for every agent tool that touches the real world.

The bottom line

twilio-voice-mcp gives any agent a governed voice surface: place calls, speak with disclosure, navigate IVR, pull transcripts, and check completion. Outbound voice is mainstream in 2026 — Google calls stores, consumer callers work IVR menus — and this MCP server is the safe way to give your agent the same capability. The tooling patterns are in the MCP directory; track the voice-agent wave on latest AI news.

Frequently Asked Questions

What is twilio-voice-mcp?

A TypeScript FastMCP server exposing Twilio's voice capabilities as governed MCP tools: place_call, speak, navigate_ivr, get_transcript, and call_status let AI agents make and manage outbound phone calls.

Why expose Twilio voice to agents?

Outbound voice is a mainstream agent capability in 2026 — Google's shopping agent calls stores and consumer callers navigate IVR menus. This MCP server gives any agent the same capability as governed tools.

How does IVR navigation work?

navigate_ivr transcribes the current menu, extracts the options, picks the one matching the agent's objective (e.g. 'billing'), and returns the choice to speak — retrying with a different option on loop.

How is AI disclosure handled?

speak() takes a disclose boolean that defaults to true, prepending an AI disclosure line to the message. The transcript records exactly what was said for audit.

What security does it need?

Runtime uses Twilio API keys scoped to voice; optional OAuth 2.0 for account-level access; a dialable-number allowlist and per-day call caps keep spend bounded; every call is logged.

Closing thoughts

Outbound voice agents are here, and they need a governed tool surface. twilio-voice-mcp is that surface: allowlisted numbers, capped calls, disclosure by default, and a transcript for everything said. The tooling is in the MCP directory; the voice-agent coverage is on latest AI news.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

Frequently Asked Questions
A TypeScript FastMCP server exposing Twilio's voice capabilities as governed MCP tools: place_call, speak, navigate_ivr, get_transcript, and call_status let AI agents make and manage outbound phone calls.
Outbound voice is a mainstream agent capability in 2026 — Google's shopping agent calls stores and consumer callers navigate IVR menus. This MCP server gives any agent the same capability as governed tools.
navigate_ivr transcribes the current menu, extracts the options, picks the one matching the agent's objective (e.g. 'billing'), and returns the choice to speak — retrying with a different option on loop.
speak() takes a disclose boolean that defaults to true, prepending an AI disclosure line to the message. The transcript records exactly what was said for audit.
Runtime uses Twilio API keys scoped to voice; optional OAuth 2.0 for account-level access; a dialable-number allowlist and per-day call caps keep spend bounded; every call is logged.
Deepak Bagada
Author Profile

Deepak Bagada

CEO, SaaSNext

Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.

Related Intelligence Analysis

Briefing AI Tools

Vercel AI SDK Tool Calling React: 5 Steps (2026)

Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...

Deepak Bagada Deepak Bagada
12m read
Breaking AI Tools

Fact-Density vs. Word Count: The New SEO for 2026

Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...

Deepak Bagada Deepak Bagada
4m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc