twilio-call-data-mcp
Read-only MCP server for Twilio call data, providing tools to search calls, fetch recordings and transcripts, list agents, and generate summaries while explicitly handling timezone and duration measurement discrepancies.
README
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
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。