AgentPhone MCP Server
Give AI agents real phone numbers, SMS, and voice calls via the Model Context Protocol.
AgentPhone lets your AI agent buy phone numbers, send/receive SMS, and place voice calls — all through natural language in Cursor, Claude Desktop, or any MCP-compatible client.
Agents are the core concept — each agent gets its own phone numbers, voice personality, system prompt, and webhook. Think of an agent as a virtual team member with its own phone line. You can create agents for different purposes (support, sales, scheduling) and configure how they sound and behave on calls.
What Can It Do?
Once configured, just ask your AI agent things like:
- "Buy me a phone number in the 415 area code"
- "Create a support agent that greets callers and helps with billing"
- "Call +14155551234 and have a conversation about scheduling a dentist appointment"
- "Text +14155551234 saying 'Your appointment is confirmed for 3pm tomorrow'"
- "Show me my recent calls and transcripts"
- "List the available voices and switch my agent to a different one"
- "Set up a webhook so I get notified when someone calls or texts my number"
- "Show me this month's usage breakdown"
Transport & hosting
The server ships two transports from one entry point (node dist/index.js):
-
stdio (default): bare npm start / npx agentphone-mcp runs the standard
MCP stdio transport for local clients, using AGENTPHONE_API_KEY.
-
HTTP: set PORT (or pass --http, optionally with --port <n>) to run the
Streamable HTTP server, reachable at /mcp. --port alone does not switch
transports — --http or PORT selects HTTP. The HTTP layer is built on the
mcp-use framework, which owns Streamable HTTP, the
SSE stream, session management, and the OAuth discovery endpoints.
-
Hosted: https://mcp.agentphone.ai/mcp
-
Self-hosted HTTP: PORT=3000 npm start → http://localhost:3000/mcp
Authentication
- OAuth (recommended for end users): the framework proxies an
Authorization Code + PKCE flow to the AgentPhone authorization server, so the
client opens a browser to sign in at agentphone.ai — no key to paste. Enable
it by setting
MCP_OAUTH_CLIENT_ID (a client pre-registered with the
AgentPhone AS). MCP_OAUTH_CLIENT_SECRET is optional: set it to run the
gateway as a confidential client, or leave it unset to run as a public
client (the gateway must then be registered with
token_endpoint_auth_method=none). Public mode advertises none to
downstream clients, which strict OAuth clients require.
- API key (scripts / single-tenant, OAuth off): each request sends
Authorization: Bearer <your API key>. The server-side AGENTPHONE_API_KEY is
only used for callers that send nothing, and only when
AGENTPHONE_ALLOW_ANONYMOUS=true. Without that flag an unauthenticated call
is rejected, so an exposed port cannot act as your account.
With OAuth enabled, bearer tokens are verified through the backend’s /auth/me;
the anonymous opt-in does not bypass that verification.
The caller’s credential is forwarded to the AgentPhone REST API for validation.
The optional anonymous mode instead uses the configured server API key.
Environment
| Var | Purpose |
|---|
PORT | HTTP port (default 3000) |
AGENTPHONE_API_KEY | Fallback API key when OAuth is off. Only used for callers that send no Authorization header when AGENTPHONE_ALLOW_ANONYMOUS=true |
AGENTPHONE_ALLOW_ANONYMOUS | Default off; ignored when OAuth is enabled. Set to true to let requests with no Authorization header act with the server's AGENTPHONE_API_KEY. Anyone who can reach the port then controls the account: put the server behind your own auth first |
MCP_OAUTH_CLIENT_ID | Enable OAuth; a gateway client pre-registered with the AgentPhone AS |
MCP_OAUTH_CLIENT_SECRET | Optional. Set = confidential gateway; unset = public client (gateway registered with token_endpoint_auth_method=none, advertises none downstream) |
AGENTPHONE_BASE_URL | API base (default https://api.agentphone.ai) |
AGENTPHONE_OAUTH_AUTHORIZE | Override authorize/consent URL (default https://agentphone.ai/oauth/authorize) |
AGENTPHONE_MCP_PUBLIC_URL | Public base URL this server is reached at, advertised in the /.well-known/mcp.json install manifest (e.g. https://mcp.agentphone.ai). If unset, it's derived from the request. |
Support matrix
| Dimension | Supported |
|---|
| Transports | stdio (default) and Streamable HTTP (set PORT / --http) |
| Tested runtime | Node ^20.19.0 || >=22.12.0 (CI runs 20, 22, 24) |
| Clients | Any MCP client — verified with Cursor, Claude Desktop, Claude Code, Windsurf (stdio); Streamable-HTTP clients and remote agent platforms (HTTP) |
| Auth modes | stdio: AGENTPHONE_API_KEY. HTTP: per-request bearer, OAuth (Authorization Code + PKCE), or anonymous server-key fallback (AGENTPHONE_ALLOW_ANONYMOUS=true, OAuth off) |
| Channels | SMS and iMessage (send/receive); voice calls (webhook-driven or built-in AI) |
| Number types | US and Canada numbers |
Highlights
- Phone numbers — buy and manage numbers in any US/CA area code
- SMS — send and receive text messages, view conversation threads
- Voice calls — place outbound calls with built-in AI conversation (no webhook needed) or bring your own webhook
- Inbound handling — set up webhooks to receive and respond to inbound calls and texts in real time
- Agents — create agents with custom voices, system prompts, call transfer, and voicemail
- Usage & billing — monitor number usage, message/call/webhook volume, and daily/monthly breakdowns
Authorization & AI disclosure
- Every outbound call and message is individually authorized by the user.
make_call, make_conversation_call, and send_message do nothing unless confirm=true is passed — without it they return a preview of the full destination(s) and content (all group recipients, the complete message body, every media URL, and any interactive payload) for the user to approve first (the same two-step pattern buy_number uses). There is no bulk-send path that skips a person in the loop.
- The connector adds AI self-identification to what it submits. Outbound calls placed through the call tools open with an AI disclosure that no argument disables. When a hosted agent is created or updated through this connector, the connector also prepends the disclosure to its system prompt, opening line, and voicemail so they identify as AI on inbound calls and voicemail. This is disclosure text and prompt instruction that the connector submits — it guides, but does not itself guarantee, model behavior or backend playback, and it only covers agents created/updated through this connector (the AgentPhone backend is the authoritative enforcer for all inbound calls). Webhook agents (your own backend, which may be human-staffed) are left untouched.
- No audio is exposed. Voice synthesis happens only inside live calls; no tool returns audio files or call recordings. (
list_voices includes a static voice-sample preview_audio_url, which is a library clip, not call audio.)
All Tools (32)
Account
| Tool | Description |
|---|
account_overview | Get a full snapshot of your account — agents, numbers, webhook, and usage |
get_usage | Get usage stats — number usage, billed SMS segments, and message/call/webhook volume. Use breakdown for daily or monthly time-series. |
Phone Numbers
| Tool | Description |
|---|
list_numbers | List all phone numbers in your account |
buy_number | Purchase a new phone number with optional area_code and agent_id |
SMS
| Tool | Description |
|---|
send_message | Send SMS, iMessage, or WhatsApp. Requires confirm=true (previews first — every message is individually authorized by the user). Supports media, threaded replies (reply_to_message_id), iMessage effects (send_style), existing groups (to_number) or new groups (recipients), and WhatsApp interactive content (channel=whatsapp with buttons/list/cta/template) |
check_capabilities | Check which channels a phone number can receive (iMessage / SMS / RCS) before sending |
check_readiness | Check a number is ready before sending/calling: active + assigned + channel-capable are hard blockers; A2P/10DLC registration and recipient reachability are warnings. Returns ready, blockers, warnings, and next steps (credit/concurrency are enforced at send time, not pre-checkable) |
get_messages | Get messages for a specific number. Page older history with cursor (from nextCursor); before/after are optional time filters. |
list_conversations | List SMS conversations. Pass agent_id to filter by agent. |
get_conversation | Get a conversation with a bounded tail of recent messages |
get_conversation_messages | Page through a conversation's full message history with cursor |
update_conversation | Set metadata on a conversation |
Contacts
| Tool | Description |
|---|
list_contacts | List saved contacts (address book). Filter with a search term. |
manage_contact | Create, update, or delete a contact (set action) |
Voice Calls
| Tool | Description |
|---|
list_calls | List calls. Scope by agent_id or number_id, or filter the account-wide list by status/direction/search. Scope and filters can't be combined. |
get_call | Get call details and transcript |
end_call | End an in-progress call (safe to retry; ending a finished call is a no-op) |
make_call | Place an outbound call (webhook-driven). Requires confirm=true (previews first — every call is individually authorized by the user). Supports recording, variables, model tier, and call-screening controls |
make_conversation_call | Place a call with built-in AI conversation — no webhook needed. Requires confirm=true (previews first — every call is individually authorized by the user). Supports recording, variables, model tier, and call-screening controls |
Agents
| Tool | Description |
|---|
list_agents | List all agents with their numbers and voice config |
create_agent | Create an agent with voice, system prompt, call transfer, voicemail, and voice tuning (speed, interruption sensitivity, backchannel, language, and more) |
update_agent | Update an agent's configuration |
delete_agent | Delete an agent (numbers are kept but unassigned) |
get_agent | Get agent details including phone numbers and voice config |
attach_number | Assign a phone number to an agent |
detach_number | Remove a phone number from an agent |
list_voices | List available voices for agents |
Webhooks
All webhook tools accept an optional agent_id — pass it to manage an agent-specific webhook, omit it for the project-level default. Agent webhooks take priority over project-level.
| Tool | Description |
|---|
get_webhook | Get webhook configuration |
set_webhook | Set a webhook URL for inbound messages and call events |
delete_webhook | Remove a webhook |
test_webhook | Send a test event to verify your webhook works |
list_webhook_deliveries | View delivery history for debugging |
get_webhook and set_webhook mask signing secrets by default, for both scopes.
Retrieve the secret from the dashboard when possible. Passing reveal_secret: true
includes it in tool output, exposing it to model context, transcripts and client logs.
This prevents accidental disclosure; it is not an authorization boundary against a
model that deliberately requests the secret.
Environment Variables
| Variable | Required | Description |
|---|
AGENTPHONE_API_KEY | stdio: yes; HTTP anonymous mode: yes | In HTTP mode, used only with AGENTPHONE_ALLOW_ANONYMOUS=true, OAuth off, and no caller Authorization header |
AGENTPHONE_ALLOW_ANONYMOUS | No | Explicit HTTP server-key fallback; default off, ignored when OAuth is enabled. See the self-hosting warning above |
AGENTPHONE_BASE_URL | No | Override the API base URL (defaults to https://api.agentphone.ai) |
PORT | No | Port for HTTP mode (defaults to 3000; set with PORT) |
Development
git clone https://github.com/AgentPhone-AI/agentphone-mcp.git
cd agentphone-mcp
npm install
npm run dev # Run with tsx (hot reload)
npm run build # Compile TypeScript
npm start # Run compiled JS — stdio by default; set PORT (or pass --http) for the HTTP server
npm test # Build + run the full test suite (both transports, output-schema contracts)
Release & deployment checks
| Command | What it verifies |
|---|
npm run check:release | Version is identical across package.json, server.json, and the built handshake (run in prepublishOnly). |
npm run smoke:pack | Runs npm pack, installs the resulting tarball in a clean dir, and runs the 204 detach regression against that packed tarball (the same bytes npm publish would upload; run in prepublishOnly). |
npm run check:contract | Fetches the live OpenAPI and asserts the endpoints/fields the tools depend on still exist (message cursors + item fields, call filters, outbound-call request/response fields the client uses, the detach endpoint, and the from_ sender field). Network-dependent, so it's not part of npm test — run before a release or as a non-blocking CI job. Override with OPENAPI_URL. |
npm run canary | Read-only post-deploy canary: connects to MCP_CANARY_URL, checks the handshake and tool surface, and (with MCP_CANARY_TOKEN) runs one read-only tool. No mutations; skips cleanly when MCP_CANARY_URL is unset. |
How It Works
This MCP server connects your AI assistant to the AgentPhone API. Your assistant talks to the MCP server, which calls the AgentPhone API, which talks to the phone network.
Your AI Assistant <--> agentphone-mcp <--> AgentPhone API <--> Phone Network
Outbound: your assistant places calls and sends texts through AgentPhone's API.
Inbound: when someone calls or texts your number, AgentPhone sends a webhook event to your server — you can then respond programmatically or let your agent's built-in AI handle it.
License
MIT