Skip to main content
Glama
cprfirstaid

Dialpad MCP Server

by cprfirstaid
README.md
# Dialpad MCP Server

An [MCP](https://modelcontextprotocol.io) server that exposes **Dialpad calls and
transcripts** as tools, for use from Claude Desktop and Claude Code.

## Tools

| Tool | What it does |
| --- | --- |
| `get_call(call_id)` | Full details for a concluded call: direction, duration, participants, recording URLs, CSAT, routing, voicemail, transcription text. |
| `initiate_call(user_id, phone_number, ...)` | Place an outbound click-to-call from a Dialpad user to a number (E.164). |
| `get_call_transcript(call_id)` | Raw Dialpad Ai transcript (lines + moments). |
| `get_call_transcript_text(call_id)` | Transcript flattened into a readable `Speaker: text` block. |
| `get_call_transcript_url(call_id)` | A downloadable URL for the transcript. |
| `list_calls(days_ago, target_id, target_type, office_id, timezone, limit)` | Historical call records via the Stats export API (kicks off an export, polls, downloads + parses the CSV). Takes ~15–60s. |

Base API: `https://dialpad.com/api/v2` (override with `DIALPAD_BASE_URL` for sandbox).

## Setup

```bash
cd ~/projects/dialpad-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

# Add your key (kept out of git via .gitignore)
cp .env.example .env
# then edit .env and set DIALPAD_API_KEY=...
```

The server auto-loads `.env`, so your API key never has to live in client config JSON.

### Get a Dialpad API key

Dialpad Admin → **Company Settings → Authentication → API keys** (you need a Company
Admin role). Paste the key into `.env` as `DIALPAD_API_KEY`.

## Connect to Claude Code

From this project directory it's picked up automatically via `.mcp.json`. To register
it for **all** projects (user scope):

```bash
claude mcp add dialpad -s user \
  -- /home/fredericktejada/projects/dialpad-mcp/.venv/bin/python -m dialpad_mcp
```

Then run `claude mcp list` to confirm it shows up, and `/mcp` inside a session.

## Connect to Claude Desktop

Edit the config file:

- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

Add:

```json
{
  "mcpServers": {
    "dialpad": {
      "command": "/home/fredericktejada/projects/dialpad-mcp/.venv/bin/python",
      "args": ["-m", "dialpad_mcp"],
      "cwd": "/home/fredericktejada/projects/dialpad-mcp"
    }
  }
}
```

> On Windows + WSL, the desktop app can't call the Linux venv directly. Either run
> Claude Desktop's command through `wsl.exe` (`"command": "wsl.exe", "args": ["-e",
> "/home/fredericktejada/projects/dialpad-mcp/.venv/bin/python", "-m", "dialpad_mcp"]`)
> or run the server inside a native Windows Python install.

Restart Claude Desktop after editing.

## Export transcripts to a Google Sheet

`export_transcripts.py` pulls call transcripts into a Google Sheet, one row per call.

```bash
# New sheet, last 7 days, recorded calls only
.venv/bin/python export_transcripts.py --days 7 --recorded-only

# Full rebuild of a tab (clears + rewrites)
.venv/bin/python export_transcripts.py --days 14 --recorded-only --sheet-id <ID> --tab Transcripts

# Append only NEW calls (dedup by Call ID; skips transcript fetch for known calls)
.venv/bin/python export_transcripts.py --append --days 2 --recorded-only --sheet-id <ID> --tab Transcripts
```

### Daily scheduled append

`run_daily.sh` appends new calls to the live sheet
(`1Jlea4o_0g0h9HygILZV4XbN305E_fW9DWgD-qDh3q08`). Installed in cron at 07:45 Manila:

```cron
45 7 * * * /home/fredericktejada/projects/dialpad-mcp/run_daily.sh >> .../cron.log 2>&1
```

`--append` reads existing Call IDs first and skips them, so the daily run only fetches
transcripts for genuinely new calls (fast, idempotent — safe to overlap windows).

Columns: Call ID, Date started, Direction, Category, External/Internal number, Agent,
Email, Talk (s), Total (s), Recorded, Moments, Transcript.

Google auth reuses the shared OAuth client at `~/.api-credentials/google/oauth-client.json`.
The account token lives in `secrets/google-sheets-token.json` (gitignored). To (re)connect
a different Google account on WSL, use the manual paste flow:

```bash
.venv/bin/python g_auth.py --url      # open the printed URL, sign in, click Allow
#   copy the localhost redirect URL into code.txt
.venv/bin/python g_auth.py --finish   # saves the token
```

Key options: `--days`, `--limit`, `--recorded-only`, `--target-id`/`--target-type`,
`--office-id`, `--timezone` (default Australia/Melbourne), `--sheet-id`, `--title`, `--tab`.

## Analytics tabs (Overview / Call Reasons / Customer Type)

`build_analytics.py` adds three analysis tabs to the spreadsheet:

```bash
.venv/bin/python build_analytics.py --days 14 --limit 8000 \
    --sample-reasons 800 --sample-customers 800 \
    --sheet-id 1Jlea4o_0g0h9HygILZV4XbN305E_fW9DWgD-qDh3q08
```

- **Overview** — per agent/line: total calls, inbound/outbound, answered, avg talk, avg
  handle time, total talk hours. Built on the full call set (lines vs agents split by name).
- **Call Reasons** — recorded inbound transcripts classified (keyword rules + custom
  moments) into Booking / Voicemail / Theory / Certificate / Cancellation / Payment /
  Reschedule-transfer / Location / Group / Course-info / Other, with avg handle time per
  concern. Runs on a random sample (`--sample-reasons`), extrapolated to the population.
- **AHT by Agent x Concern** — matrix of each agent's average handle time per concern
  (cells show `m:ss (n)`), built from the same reason sample. Agents need ≥8 sampled calls.
- **Customer Type** — unique inbound numbers matched against **aXcelerate**
  (`/contacts/search?q=<local#>`) and **HubSpot** (`crm/v3/objects/contacts/search`,
  `CONTAINS_TOKEN *<9 digits>*` on phone+mobilephone). Returning = found in either.
  Runs on a random sample (`--sample-customers`).

Lookups are cached in `.cache_lookups.json` (gitignored) so re-runs are cheap. aXcelerate
tokens come from `~/.api-credentials/.env`; the HubSpot token from
`~/projects/cprfa-tickets/secrets/.env` (needs `crm.objects.contacts.read` scope). Volume
note: ~8k call legs / 14 days, so reasons + customer type are sampled by design.

## Daily CALLS + AHT tracker (build_daily_report.py)

Row-per-day tabs inspired by the ops "2026 REPORTS" sheet:
- **Daily AHT** — `Day | AHT (all) | <each agent's AHT>` (one row per day)
- **Daily CALLS** — `Day | volume metrics (Admin/Sales inbound, outbound, missed,
  abandoned, voicemail) | Top Call Drivers (reason categories)`

```bash
# Full 30-day (re)build — pulls in 5-day chunks (single 30d export won't generate)
.venv/bin/python build_daily_report.py --days 30
# Daily append of newly-completed days (run by run_daily.sh / cron)
.venv/bin/python build_daily_report.py --append
```

Dialpad can't generate a 30-day export in one shot, so `fetch_chunked` **primes all
day-range chunks at once** (concurrent server-side generation) then collects each as it
finishes (`days_ago_start`/`days_ago_end`). Today is excluded (partial); the daily cron
adds each day once complete. Drivers reuse the transcript cache.

`run_daily.sh` (cron, 07:45 Manila) now appends **transcripts + Daily AHT + Daily CALLS**.

## Notes

- `list_calls` uses the Stats **export** API, which is asynchronous — the tool blocks
  while polling (up to ~90s) then parses the returned CSV.
- Rate limits vary per endpoint (`get_call` is only ~10/min; transcripts ~1200/min).
- Secrets live in `.env` only — never commit it.