---
title: "Use ThunderPhone as an MCP server"
description: "Build, test, validate, and operate ThunderPhone voice agents from Claude, ChatGPT, Claude Code, Codex, Cursor, VS Code, or another Streamable HTTP MCP client."
---

ThunderPhone exposes a Streamable HTTP Model Context Protocol server at:

```text
https://api.thunderphone.com/v1/mcp
```

This is the opposite direction from [connecting a remote MCP server to a voice agent](/guides/mcp-servers):

| Direction | Result |
| --- | --- |
| Remote MCP server → ThunderPhone agent | A voice agent can call the remote server's tools. |
| ThunderPhone → your MCP client | Your coding agent can build, test, and operate ThunderPhone. |

## Authenticate

Use [OAuth](/guides/oauth) by default for directory clients: sign in, choose an organization, and approve the requested permissions. Revoke access under **Organization → API keys → Authorized apps**.

For clients configured with an API key, create an `sk_live_` key under **Organization → Keys** and expose it to your MCP client as `THUNDERPHONE_API_KEY`. The key is bound to one organization; an id owned by another organization behaves as not found.

<Warning>
  An `sk_live_` key can read organization data and perform production actions such as deploying agents, placing calls, buying numbers, starting campaigns, and deleting resources. Keep it out of browser code, repositories, screenshots, and chat logs. Use your client's secret store or environment support and revoke an exposed key immediately.
</Warning>

## CLI and stdio alternatives

The [ThunderPhone CLI](/guides/cli) can write client configuration while preserving
unrelated servers:

```bash
npx -y @thunderphone/mcp setup --client cursor --api-key-env THUNDERPHONE_API_KEY
npx thunderphone mcp setup --client claude-desktop --scope user
```

After a global install of `@thunderphone/mcp`, use `thunderphone-mcp setup` with the
same options. Setup supports Claude Code, Codex, Cursor, VS Code, Gemini, Claude
Desktop and Windsurf. Direct HTTP is preferred; Desktop uses stdio.

For any stdio-capable client, configure `command: "npx"` with
`args: ["-y", "@thunderphone/mcp"]`. The wrapper uses `THUNDERPHONE_API_KEY` first,
then credentials from `thunderphone login`, refreshing expired tokens, then
OAuth through `mcp-remote`. Device login and OAuth require the corresponding API
OAuth rollout. The API-key path does not. Direct HTTP configurations do not read
the CLI credential store; use the stdio wrapper to reuse device login.

These are alternatives to the manual client configurations below.

## Client configuration

Claude and ChatGPT sign in with [OAuth](/guides/oauth); no API key is involved. If you do not have a ThunderPhone account yet, choose **Create account** on the sign-in page and you return to the approval screen after verifying your email.

### Claude (web, desktop, and mobile)

1. Open **Customize → Connectors** (**Settings → Connectors** in older versions). If ThunderPhone appears in the connector directory, select it. Otherwise choose **Add custom connector**, name it `ThunderPhone`, and enter `https://api.thunderphone.com/v1/mcp`.
2. Select **Connect**, sign in to ThunderPhone, pick the organization, and approve the permissions.
3. In a chat, enable ThunderPhone from the tools menu and ask for what you need, for example "List my agents".

Claude asks before it uses each tool for the first time. To skip most of those prompts, open **Customize → Connectors → ThunderPhone** and set **Read-only tools** to always allow. Those tools only look things up: agents, calls, transcripts, balances, docs. Leave **Write/delete tools** on approval if you want to see each change before it happens.

Custom connectors need a paid Claude plan. On Team and Enterprise plans an owner adds the connector under the organization's connector settings first, then each member connects their own ThunderPhone account.

### ChatGPT

1. If ThunderPhone appears in the ChatGPT app directory, select it and connect.
2. Otherwise open **Settings → Apps & Connectors → Advanced settings**, turn on **Developer mode**, and create a connector with the URL `https://api.thunderphone.com/v1/mcp` and OAuth authentication.
3. Sign in to ThunderPhone, pick the organization, and approve the permissions. Add ThunderPhone to a chat from the tools menu.

Deleting an agent or phone number, discarding a draft, launching a campaign, and sending a test request that can write to an external system ask for a second confirmation in either app; see [Confirming destructive actions](#confirming-destructive-actions).

### Claude Code

```bash
claude mcp add --transport http thunderphone https://api.thunderphone.com/v1/mcp \
  --header "Authorization: Bearer $THUNDERPHONE_API_KEY"
```

### Codex

Add this to `~/.codex/config.toml`:

```toml
[mcp_servers.thunderphone]
url = "https://api.thunderphone.com/v1/mcp"
bearer_token_env_var = "THUNDERPHONE_API_KEY"
```

### Cursor

Create `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "thunderphone": {
      "url": "https://api.thunderphone.com/v1/mcp",
      "headers": {
        "Authorization": "Bearer ${env:THUNDERPHONE_API_KEY}"
      }
    }
  }
}
```

### Claude Desktop

Add an `mcp-remote` bridge to the Claude Desktop configuration:

```json
{
  "mcpServers": {
    "thunderphone": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://api.thunderphone.com/v1/mcp",
        "--header",
        "Authorization: Bearer ${THUNDERPHONE_API_KEY}"
      ],
      "env": {
        "THUNDERPHONE_API_KEY": "sk_live_YOUR_API_KEY"
      }
    }
  }
}
```

### VS Code

Create `.vscode/mcp.json` and enter the key through VS Code's input prompt:

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "thunderphone-api-key",
      "description": "ThunderPhone organization API key",
      "password": true
    }
  ],
  "servers": {
    "thunderphone": {
      "type": "http",
      "url": "https://api.thunderphone.com/v1/mcp",
      "headers": {
        "Authorization": "Bearer ${input:thunderphone-api-key}"
      }
    }
  }
}
```

## Tools

Every tool carries MCP annotations. In the tables, **R** means read-only, **D** destructive, **I** idempotent, and **O** open-world/network interaction. A dash means no hint is set.

### Connection

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `get_connection_identity` | — | R, I | Validates the credential and returns its organization and safe credential metadata. |

### Agents

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `list_agents` | — | R, I | Lists agents. |
| `get_agent` | `agent_id` | R, I | Gets one agent. |
| `create_agent` | agent configuration | — | Creates an agent. |
| `update_agent` | `agent_id`, changed fields | D | Stages fields in the agent draft, replacing what the draft had. Runs without a confirmation step: the deployed agent is unchanged until `deploy_agent`, and `discard_agent_draft` reverts the draft. |
| `deploy_agent` | `agent_id`, optional `if_updated_at` | D, I | Promotes the draft to production. |
| `discard_agent_draft` | `agent_id`, optional `if_updated_at`, `confirmation_token` on the second request | D, I | Discards staged changes. Two-step, see [Confirming destructive actions](#confirming-destructive-actions). |
| `duplicate_agent` | `agent_id`, optional `name` | — | Copies an agent inside the organization. |
| `delete_agent` | `agent_id`, `confirmation_token` on the second request | D, I | Permanently deletes an agent. Two-step. |
| `list_agent_versions` | `agent_id` | R, I | Lists deployed configuration versions. |

### Phone numbers and carriers

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `list_phone_numbers` | — | R, I | Lists organization numbers and routing. |
| `get_phone_number_limits` | — | R, I | Gets managed-number usage and limits. |
| `provision_phone_number` | optional `area_code`, `city`, `state`, `idempotency_key` | D, O | Buys a ThunderPhone-managed inbound number. |
| `list_voip_connections` | — | R, I | Lists connected customer carriers. |
| `search_voip_numbers` | `connection_id`, `country`, `type`, optional `area_code` | R, I, O | Searches carrier inventory. |
| `import_voip_numbers` | `connection_id`, `numbers` | D, O | Imports numbers already owned at the carrier. |
| `update_phone_number` | `phone_number_id`, routing/label/webhook fields | D | Updates number routing, including agent assignment. |
| `delete_phone_number` | `phone_number_id`, `confirm`, optional `release_at_provider`, `confirmation_token` on the second request | D, I, O | Releases a number. Two-step. |

### SMS

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `send_sms` | `from_number`, `to`, `body`, `idempotency_key` | D, I, O | Sends one plain-text SMS through a connected Twilio or Telnyx number. |

### Calls

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `list_calls` | optional filters, `limit`, `offset` | R, I | Lists calls with the REST call-history filters. |
| `get_call` | `call_id` | R, I | Gets call status and metadata. |
| `get_call_transcript` | `call_id`, optional `live` | R, I | Gets the call transcript. |
| `get_call_audio_url` | `call_id`, optional `download` | R, I, O | Returns a signed audio URL; it never streams audio through MCP. |
| `get_call_grade` | `call_id` | R, I | Gets the latest call grade. |
| `place_call` | `agent_id`, `from_number`, `to_number` | D, O | Places one non-idempotent outbound call. |
| `export_calls` | call filters, `export_format` | R, I | Exports up to the REST endpoint's cap as JSON or CSV. |

### Testing and validation

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `list_test_scenarios` | `agent_id` | R, I | Lists test scenarios. |
| `create_test_scenario` | `agent_id`, `title`, `scenario_prompt`, optional conditions | — | Creates a scenario. |
| `generate_test_scenarios` | `agent_id`, optional `count`, `include_edge_cases`, `locale` | O | Generates scenarios from the agent prompt. |
| `run_agent_tests` | `agent_id`, `channel`, `consent_to_charge`, optional selection/matrix | D, O | Executes scenarios through web or telephony. |
| `get_test_run` | `agent_id`, `batch_id` | R, I | Gets batch status and per-scenario results. |
| `list_validation_runs` | `agent_id` | R, I | Lists recent draft validation runs. |
| `get_validation_status` | `agent_id` | R, I | Gets the latest validation state and draft match. |

### Knowledge

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `list_knowledge_bases` | — | R, I | Lists knowledge bases. |
| `create_knowledge_base` | `name`, optional `description` | — | Creates a knowledge base. |
| `add_knowledge_document` | `knowledge_base_id`, `name`, `content` | — | Adds text or Markdown. |
| `import_knowledge_url` | `url`, optional `name` | O | Queues a public page for safe ingestion. |
| `search_knowledge` | `knowledge_base_id`, `query` | R, I | Searches through production retrieval. |

### Integrations, webhooks, and remote MCP servers

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `list_integrations` | — | R, I | Lists HTTP function integrations. |
| `create_integration` | `display_name`, function `spec`, optional endpoint fields | — | Creates an HTTP tool. Add authentication headers in the dashboard. |
| `test_integration` | `url`, optional method/body/timeout, `integration_id` with `header_names`, `confirmation_token` on the second request | D, O | Sends a bounded, SSRF-protected test request. Saved header values are filled in from the integration and only sent to its own host. `POST` (the default), `PUT`, `PATCH` and `DELETE` are two-step; `GET` runs immediately. |
| `list_webhook_endpoints` | — | R, I | Lists signed webhook endpoints. |
| `list_webhook_deliveries` | optional `agent_id`, optional `endpoint_id` | R, I | Lists recent payload-free webhook delivery outcomes. |
| `create_webhook_endpoint` | `label`, `url`, optional events/status | O | Creates a signed webhook endpoint. |
| `test_webhook_endpoint` | `endpoint_id` | O | Sends a synthetic event through normal delivery. |
| `list_mcp_servers` | — | R, I | Lists remote servers callable by voice agents. |
| `create_mcp_server` | `display_name`, `url` | O | Registers and syncs a remote server. Servers that need authentication headers are added in the dashboard. |
| `sync_mcp_server_tools` | `server_id` | D, O | Refreshes a remote server's tool catalog. |

### Campaigns

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `list_campaigns` | — | R, I | Lists outbound campaigns. |
| `create_campaign` | campaign fields | — | Creates a draft campaign. |
| `add_campaign_contacts` | `campaign_id`, `contacts` | — | Adds up to 5,000 JSON contacts. |
| `campaign_action` | `campaign_id`, `action`, optional `consent_to_charge`, `confirmation_token` on the second request | D, O | Runs `start`, `pause`, `resume`, or `stop`. `start` and `resume` are two-step; `pause` and `stop` run immediately. |
| `get_campaign_stats` | `campaign_id` | R, I | Gets counters and recent outcomes. |

### Voices, billing, and docs

| Tool | Main arguments | Annotations | What it does |
| --- | --- | --- | --- |
| `list_voices` | — | R, I | Lists voices and supported languages. |
| `preview_voice` | `voice`, `language`, `text` | O | Generates a sample and returns a signed URL. |
| `list_voice_clones` | — | R, I | Lists custom voice clones. |
| `get_billing_summary` | — | R, I | Gets balance and authoritative tier prices; no payment mutations are exposed. |
| `search_docs` | `query`, optional `limit` | R, I, O | Searches the public docs index. |
| `get_doc_page` | `path` | R, I, O | Fetches one public Markdown docs page. |

All product tools use the same REST code paths as the public API. REST validation, organization scoping, roles, billing admission, quotas, TCPA confirmation, provider safety, and audit behavior therefore apply unchanged.

## Confirming destructive actions

Tools marked D change or remove something, or begin dialing real people. Those that cannot be undone take two requests. The first request changes nothing and returns:

```json
{
  "status": "confirmation_required",
  "action": "Delete agent",
  "target": { "id": 195, "name": "Front Desk Receptionist" },
  "confirmation_token": "…",
  "expires_in_seconds": 600,
  "next_step": "…"
}
```

The assistant shows the user what is affected and asks for a yes. It then repeats the request with the same arguments plus `confirmation_token`. A token is valid for 10 minutes and covers one organization, one tool and one exact set of arguments, so deleting three agents takes three confirmations. `campaign_action` with `pause` or `stop` skips this step so a running campaign can always be halted at once, and so does `test_integration` with `GET`. Some D tools run in one request because the D marks their effect, not a server-side confirmation: `update_agent` replaces what the draft had (`discard_agent_draft` reverts it), `deploy_agent` replaces the live configuration, `update_phone_number` reroutes live calls, `sync_mcp_server_tools` replaces a remote server's tool catalog, `import_voip_numbers` re-points numbers at your carrier, `run_agent_tests` can spend credit, `send_sms` and `place_call` reach a real person (`place_call` keeps its own consent confirmation), and `provision_phone_number` buys a number. Your MCP client may still ask you to approve them.

## Starter prompts

`prompts/list` advertises five reusable workflows:

- `create_inbound_receptionist`
- `run_agent_tests`
- `buy_and_attach_number`
- `review_low_grade_calls`
- `add_url_to_agent_knowledge`

Use `prompts/get` with the listed prompt name and its declared arguments to receive a ready-to-run user message.

## Resources

`resources/list` exposes public, cached references:

| URI | Content |
| --- | --- |
| `thunderphone://docs/llms.txt` | Public documentation index. |
| `thunderphone://docs/quickstart` | Quickstart Markdown. |
| `thunderphone://pricing` | Current public pricing Markdown. |

Read one with `resources/read`. Public fetches use a short timeout, a 2 MiB limit, and a ten-minute in-process cache.

## Protocol and errors

The server supports protocol versions `2025-06-18` and `2025-03-26`; it echoes a supported client version and otherwise selects `2025-06-18`. It implements `initialize`, `ping`, tools, prompts, resources, and notifications. Notifications return `202 Accepted`. This stateless server does not expose an SSE listener or session deletion, so `GET` and `DELETE` return `405 Method Not Allowed` with `Allow: POST`.

Tool failures remain successful JSON-RPC responses with `isError: true`. Their text contains a short sentence followed by a JSON block:

```json
{
  "code": "insufficient_balance",
  "detail": "There is not enough prepaid balance.",
  "next_step": "Call get_billing_summary, add funds in Organization > Billing, then retry."
}
```

The distinct `outbound_tcpa_confirmation_required:` prefix is retained for `place_call`. Unknown JSON-RPC methods return `-32601` in an HTTP `200` response. JSON-RPC batches are not supported by MCP `2025-06-18` and return a clean `-32600` invalid-request error.
