burkecampbell

twilio-call-data-mcp

Community burkecampbell
Updated

Read-only MCP server for Twilio call data — dual-labeled durations, explicit timezones, honest pagination

twilio-call-data-mcp

An MCP server that gives any MCP client read-only access to Twilio call data. It exposes six tools for searching calls, pulling recordings, querying agent activity, and generating summaries — with explicit handling for the ways Twilio's data misleads you if you take it at face value.

This server is read-only. It cannot place calls, send messages, modify resources, or access billing. That constraint is a feature.

Install

npm install twilio-call-data-mcp

Or clone and build:

git clone https://github.com/burkecampbell/twilio-call-data-mcp.git
cd twilio-call-data-mcp
npm install
npm run build

Credentials

Set three environment variables. The server refuses to start without the first two.

export TWILIO_ACCOUNT_SID=your_account_sid
export TWILIO_AUTH_TOKEN=your_auth_token
export TWILIO_WORKSPACE_SID=your_taskrouter_workspace_sid  # required for agent tools

Copy .env.example to .env for local development. Never commit .env.

MCP Client Configuration

Add to your MCP client config (e.g., Claude Desktop):

{
  "mcpServers": {
    "twilio-call-data": {
      "command": "npx",
      "args": ["twilio-call-data-mcp"],
      "env": {
        "TWILIO_ACCOUNT_SID": "your_account_sid",
        "TWILIO_AUTH_TOKEN": "your_auth_token",
        "TWILIO_WORKSPACE_SID": "your_workspace_sid"
      }
    }
  }
}

Tools

search_calls

Filter call records by date range, phone number, direction, status, minimum duration, or agent. Returns rows plus a pagination cursor.

Parameter Type Required Description
start_date string No Start of range (YYYY-MM-DD or ISO 8601), interpreted in timezone
end_date string No End of range
phone_number string No E.164 format, matches from or to
direction enum No inbound, outbound-dial, outbound-api, outbound
status enum No completed, busy, no-answer, canceled, failed, etc.
min_duration integer No Minimum CDR duration in seconds
agent string No Agent name or worker SID
page_size integer No 1-100, default 50
cursor string No From a previous response
timezone string No IANA timezone, default UTC

get_call

Full detail for one call SID, including child legs.

Parameter Type Required Description
call_sid string Yes Starts with "CA"
timezone string No IANA timezone, default UTC

get_recording

Recording metadata for a call: signed URL with expiry, duration, channel count, and transcript if one exists. Never returns audio bytes.

Parameter Type Required Description
call_sid string Yes Starts with "CA"
timezone string No IANA timezone, default UTC

list_agents

TaskRouter worker roster with current activity status.

Parameter Type Required Description
timezone string No IANA timezone, default UTC

get_agent_activity

Activity and reservation history for one worker over a time window, with summary statistics.

Parameter Type Required Description
worker_sid string Yes Starts with "WK"
start_date string Yes Window start
end_date string Yes Window end
timezone string No IANA timezone, default UTC

call_summary

Aggregates by day, agent, or queue: call counts, talk time, and abandonment rate.

Parameter Type Required Description
start_date string Yes Range start
end_date string Yes Range end
group_by enum No day, agent, or queue (default day)
timezone string No IANA timezone, default UTC

Talk Time: Why This Server Returns Two Numbers

Every tool that reports call duration returns two fields, not one:

{
  "duration_seconds": { "value": 42, "source": "cdr", "measures": "ring_to_hangup" },
  "talk_seconds":     { "value": 35, "source": "taskrouter", "measures": "agent_audio_open" }
}

What is happening: Twilio's CDR (Call Detail Record) duration counts from the moment the call connects — including ring time — to hangup. TaskRouter and Flex Insights count from when the agent's audio path actually opens. For the same call, these numbers disagree. In the example above, seven seconds of ringing are counted as "duration" in the CDR but are not part of the actual conversation.

Why this matters: An LLM handed a bare duration: 42 will average it, bill against it, and compare it to a talk_time: 35 from another source without knowing these are different measurements of the same call. It will produce confident, wrong totals. A human reviewing those totals will not know to question them because the numbers look precise.

What we do: Every duration field in every response carries its source (where the number came from) and measures (what the number actually counts). A consumer that receives both fields can choose which one fits their question. A consumer that ignores the labels is making that choice explicitly rather than accidentally.

What this costs: Slightly larger payloads. Every response carries two labeled objects instead of one bare integer. The trade-off is worth it — seven seconds of mislabeled data across a month of calls adds up to hours of phantom talk time.

What This Server Does Not Do

  • No call control. Cannot place, transfer, hold, or end calls.
  • No messaging. Cannot send or read SMS/MMS.
  • No resource modification. Cannot update call records, recordings, worker states, or any Twilio resource.
  • No billing access. Cannot read account balance, usage records, or pricing.
  • No audio streaming. Recording endpoints return signed URLs and metadata, never audio bytes.

If you need write operations, use the Twilio SDK directly. This server exists specifically to be safe to point an LLM at.

Decision Log

1. Never return one number for talk time

Naive approach: Return duration: 42 like Twilio's API does.

How it fails: A downstream consumer averages CDR durations against TaskRouter talk times from another report. The 7-second ring-time gap per call compounds across hundreds of calls into hours of phantom talk time. Worse, this looks correct — both numbers are real, they just measure different things.

What we do instead: Every duration field is a structured object with value, source, and measures. CDR duration and TaskRouter talk time are never presented as the same kind of number.

What it costs: Larger JSON payloads. Consumers must destructure an object instead of reading a bare integer. This friction is the point — it forces acknowledgment of what the number means.

2. Timezones are explicit or the tool refuses

Naive approach: Accept date strings and assume UTC, or worse, assume the server's local time.

How it fails: A twelve-hour offset between a source timestamp and the server's timezone assumption put an entire day of calls on the wrong date. The symptom looked like missing data — "we had no calls on Tuesday" — when the calls were there, just bucketed into Monday and Wednesday by a timezone mismatch.

What we do instead: Every tool accepts an optional timezone parameter (IANA identifiers only — "America/New_York", not "EST"). Defaults to UTC. Date range filters are interpreted in the caller's timezone. Every timestamp in every response carries its timezone. No implicit conversions.

What it costs: Callers must know their timezone, which they already do. Responses are slightly larger with the timezone field. The alternative — silent misattribution of calls to wrong dates — costs debugging hours.

3. Pagination that admits truncation

Naive approach: Return the first 50 rows. If there are more, include a next_page link.

How it fails: A consumer receives 50 rows and computes totals. There were actually 200 rows. Nothing in the response distinguished "there were 50 calls" from "we stopped at 50." The consumer's summary is confidently wrong by 75%.

What we do instead: Every paginated response includes has_more (boolean), truncated (boolean), total_available (integer or null), and cursor (opaque string). When a result set hits the page size limit, truncated: true tells the consumer explicitly that totals computed from this page are incomplete.

What it costs: One extra boolean and one nullable integer per response. Consumers that want complete data must page through. But now they know they need to.

4. Rate limits handled, not hoped about

Naive approach: Make the API call. If it fails, return the error.

How it fails: Twilio returns 429 under load. A bare error response gives the consumer no information about whether this is a transient throttle or a persistent problem, and no way to distinguish "the data doesn't exist" from "we couldn't get to it."

What we do instead: Exponential backoff with jitter on 429 and 5xx responses, up to 5 retries. Every response includes a retry_info object with the retry count and a throttled boolean. A consumer seeing retries: 3, throttled: true knows the data is fresh but the API is under pressure.

What it costs: Worst case, a single tool call takes 30+ seconds instead of failing immediately. Retry count in the response adds a small object to every payload. The alternative — silent failures that look like empty data — is worse.

Development

npm install
npm test          # runs Vitest with fixture data, no credentials needed
npm run build     # compiles TypeScript
npm run dev       # runs with tsx for development

Tests use recorded and scrubbed Twilio API responses. No credentials are needed to run CI.

License

MIT

MCP Server · Populars

MCP Server · New

    drakulavich

    Kesha Voice Kit

    Give your tools a voice — speech to text and back, 25 languages, up to ~19× faster than Whisper. On your machine.

    Community drakulavich
    lobu-ai

    Lobu — Open-source backend for AI teammates

    Open-source control plane and runtime for organisational agents: shared company context, isolated execution, approvals and MCP.

    Community lobu-ai
    minipuft

    Claude Prompts MCP Server

    Wolfflow: Model Context Protocol (MCP) server for reusable prompt templates, multi-step workflow chains, and quality gates. Compose agentic workflows with an operator syntax; export as native skills to Claude Code, Cursor, OpenCode, and Gemini CLI.

    Community minipuft
    docmancer

    Docmancer

    Find out what your coding agents already know. Docmancer indexes the memory, rules, and instructions Claude Code, Codex, Cursor, and Gemini wrote on your machine, then carries the durable parts to every agent. Local-first, MIT.

    Community docmancer
    lineai-intelligence

    codelogic-mcp-server

    An MCP Server to utilize Codelogic's rich software dependency data in your AI programming assistant.